En supposant que le rectangle est sur un plan horizontal, vous pouvez effectuer un test de recherche contre la scène sur tous les 4 coins et utilisez 3 de ces coins pour calculer la largeur, la hauteur, le centre et l'orientation du rectangle.
J'ai une application de démonstration disponible sur GitHub qui fait exactement cela: https://github.com/mludowise/ARKitRectangleDetection
Les coordonnées des coins du rectangle de VNRectangleObservation
sera par rapport à la taille de l'image et des coordonnées différentes en fonction de la rotation du téléphone. Vous aurez besoin de les multiplier par la taille de la vue et les inverser en fonction de la rotation du téléphone:
func convertFromCamera(_ point: CGPoint, view sceneView: ARSCNView) -> CGPoint {
let orientation = UIApplication.shared.statusBarOrientation
switch orientation {
case .portrait, .unknown:
return CGPoint(x: point.y * sceneView.frame.width, y: point.x * sceneView.frame.height)
case .landscapeLeft:
return CGPoint(x: (1 - point.x) * sceneView.frame.width, y: point.y * sceneView.frame.height)
case .landscapeRight:
return CGPoint(x: point.x * sceneView.frame.width, y: (1 - point.y) * sceneView.frame.height)
case .portraitUpsideDown:
return CGPoint(x: (1 - point.y) * sceneView.frame.width, y: (1 - point.x) * sceneView.frame.height)
}
}
Ensuite, vous pouvez effectuer un test de recherche sur les 4 coins. Il est important d'utiliser le type .existingPlaneUsingExtent
lors de l'exécution du test de hit afin que ARKit renvoie des hits pour les plans horizontaux.
let tl = sceneView.hitTest(convertFromCamera(rectangle.topLeft, view: sceneView), types: .existingPlaneUsingExtent)
let tr = sceneView.hitTest(convertFromCamera(rectangle.topRight, view: sceneView), types: .existingPlaneUsingExtent)
let bl = sceneView.hitTest(convertFromCamera(rectangle.bottomLeft, view: sceneView), types: .existingPlaneUsingExtent)
let br = sceneView.hitTest(convertFromCamera(rectangle.bottomRight, view: sceneView), types: .existingPlaneUsingExtent)
Ensuite, il devient un peu plus compliqué ...
Parce que chaque test de recherche pourrait revenir avec 0 à n résultats, vous devez filtrer les tests de succès qui sont contenus dans un plan différent. Vous pouvez le faire en comparant les points d'ancrage pour chaque ARHitTestResult
:
hit1.anchor == hit2.anchor
En outre, il vous suffit 3 sur 4 coins pour identifier les dimensions du rectangle, la position et l'orientation de sorte qu'il est correct si un coin ne retourne pas résultats de test de hit. Jetez un oeil here pour la façon dont je l'ai fait.
Vous pouvez calculer la largeur du rectangle à partir de la distance entre les coins gauche et droit (pour le haut ou le bas). De même, vous pouvez calculer la hauteur du rectangle à partir de la distance entre les coins inférieurs du haut (gauche ou droit).
func distance(_ a: SCNVector3, from b: SCNVector3) -> CGFloat {
let deltaX = a.x - b.x
let deltaY = a.y - b.y
let deltaZ = a.z - b.z
return CGFloat(sqrt(deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ))
}
let width = distance(right, from: left)
let height = distance(top, from: bottom)
On peut calculer sa position en obtenant le point central à partir des coins opposés du rectangle (soit topLeft & bottomRight ou topRight & bottomLeft):
let midX = (c1.x + c2.x)/2
let midY = (c1.y + c2.y)/2
let midZ = (c1.z + c2.z)/2
let center = SCNVector3Make(midX, midY, midZ)
Vous pouvez également calculer l'orientation du rectangle (rotation le long de l'axe y) à partir des coins gauche et droit (pour le haut ou le bas):
let distX = right.x - left.x
let distZ = right.z - left.z
let orientation = -atan(distZ/distX)
Ensuite, mettez tout cela ensemble et affichez quelque chose en AR superposé sur le rectangle. Voici un exemple d'affichage d'un rectangle virtuel par sous-classe SCNNode
:
class RectangleNode: SCNNode {
init(center: SCNVector3, width: CGFloat, height: CGFloat, orientation: Float) {
super.init()
// Create the 3D plane geometry with the dimensions calculated from corners
let planeGeometry = SCNPlane(width: width, height: height)
let rectNode = SCNNode(geometry: planeGeometry)
// Planes in SceneKit are vertical by default so we need to rotate
// 90 degrees to match planes in ARKit
var transform = SCNMatrix4MakeRotation(-Float.pi/2.0, 1.0, 0.0, 0.0)
// Set rotation to the corner of the rectangle
transform = SCNMatrix4Rotate(transform, orientation, 0, 1, 0)
rectNode.transform = transform
// We add the new node to ourself since we inherited from SCNNode
self.addChildNode(rectNode)
// Set position to the center of rectangle
self.position = center
}
}
Est-ce que cela fonctionne vraiment? l'origine et la taille du boundingBox retourné par l'observation de la vision est dans la plage [0, 1], pas la coordonnée uikit, alors que hitTest a besoin d'une coordonnée uikit –