Skip to content

iOS: Pick the point-goal on screen instead of typing distances - #564

Open
harishthakur52 wants to merge 7 commits into
ob-f:masterfrom
3dwesupport:ios/point-goal-nav
Open

harishthakur52 wants to merge 7 commits into
ob-f:masterfrom
3dwesupport:ios/point-goal-nav

Conversation

@harishthakur52

Copy link
Copy Markdown
Contributor

Replaces the Set Goal dialog in point-goal navigation with tap-to-set-goal in the camera view, matching Android. The goal is the point where the tap ray meets the floor, calculated from the camera height measured from ARKit floor planes. Also fixes a heading error that made the robot circle when the phone is mounted sideways.

Changes

  • Sets the goal from a tap and rejects taps that do not land on the floor.
  • Fixes the direction to the goal in landscape by using simd_quatf.act() instead of Pose.rotateVector, which was off by up to 90°. Pose.rotateVector is unchanged because Blockly still uses it.
  • Sends a confirmed Bluetooth stop and caps motor power at the selected speed.
  • Holds the robot still when AR tracking is lost and stops it after 2 seconds.
  • Adds unit tests for the goal checks and the direction to the goal.

Testing

  • Tested on an iPhone with an RTR_520 and TT over Bluetooth.

@thias15 thias15 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks @harishthakur52, this matches Android nicely, and holding still for short tracking drops is a good idea.

One fix before merging:

  • Pose.rotateVector: this is wrong whenever the phone is rolled. The last step should subtract the w2 terms (- qx * w2, - qy * w2, - qz * w2). In landscape it turns "forward" into a zero vector. Blockly navigation (RunRobot.swift, line 943) still uses it, so it has the same circling bug. Please fix rotateVector itself, or make it call simd_quatf.act(), so both screens use one correct version.

Camera height:

  • Same as on Android: don't save the height between sessions. Start from the body the robot reports (the type is the first field of bluetooth.robotInfo, f<type>:…), and use the live measurement once the floor is found. Starting heights: RTR_520 0.16 m, RC_CAR 0.22 m, MTV 0.23 m, everything else 0.12 m.

@thias15 thias15 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@harishthakur52 the same floor issue applies here; see #563 for the details. The goal comes from the camera height below the current phone position, so taps on objects pass through to an imaginary floor, and holding the robot up while tapping puts the goal in the air. Please use the detected floor plane's world height once found, check what the tap hits with an ARRaycastQuery (existing plane geometry, plus scene depth on LiDAR iPhones), use that point if it's on the floor and reject it if it's clearly above, and only fall back to the estimated height while no floor is found. Also check that pressing Stop doesn't place a new goal; on Android it does (#563).

@thias15 thias15 left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@harishthakur52 Stop and floor detection are fixed. Two remaining cases:

1. Walls are not hit on iPhones without LiDAR. viewWillAppear() sets configuration.planeDetection = [.horizontal]. Without .sceneDepth, surfaceHit(at:) only queries .existingPlaneGeometry, so a tap on a wall returns no hit, goalOnFloor() falls back to intersectFloor(), and the goal lands on the floor behind the wall. Android uses HORIZONTAL_AND_VERTICAL. Use [.horizontal, .vertical]; measureFloor() already filters anchor.alignment == .horizontal, so the floor height is unaffected.

2. Hit below an estimated floor is discarded. Same as #563: PointGoalMath.goalOnFloor() treats hit.y < floorY - floorTolerance as a reflection. While floorY is nil, handleTap() uses cameraPosition.y - cameraHeight, which is wrong when the robot is held up, so a hit on the real floor is discarded and the goal is placed in the air. Pass floorFound and use the hit in that case; add a test for it.

@thias15

thias15 commented Nov 5, 2026

Copy link
Copy Markdown
Collaborator

Tolerance for raycast hits. FLOOR_TOLERANCE_M = 0.05 fits detected-plane hits (≈1–3 cm error), but raycast hits get less accurate with distance. A real floor tap at 3–4 m can come out ~10 cm high and be rejected. Suggest: plane hits keep 5 cm; raycast hits use max(0.05, 0.04 × distance) (≈12 cm at 3 m). That still rejects furniture (≥ ~40 cm). Tests: a far floor hit is accepted, a far tabletop hit is rejected.

@thias15

thias15 commented Nov 6, 2026

Copy link
Copy Markdown
Collaborator

Floor selection. pickFloorY takes the largest horizontal plane below the camera, and measureFloor() stores it as floorY unconditionally. A table or bed can win (robot held up, or later while driving): taps on it are then accepted, and real floor taps are treated as reflections, so the goal ends up at table height.

Fix: keep a plane as the floor once it's 4–40 cm below the camera (robot on the floor), and only update it within ±5 cm. Until then, use the lowest plane ≥ 0.25 m², not the largest. Test: floor plane plus a larger table plane, robot held up and on the floor.

(LiDAR iPhones are fine via .floor classification.)

@thias15

thias15 commented Nov 13, 2026

Copy link
Copy Markdown
Collaborator

Same as #563 (#563 (comment)): points 2–4 apply here too.

This branch has not been deployed

No deployments
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.

2 participants