BaedekAR
大綱
Introduction
In this section, you’ll be building an AR guidebook — a Baedeker — for paintings and photos in galleries, museums, or any place that displays 2D art.
Importing the Reference Images
The first step in building BaedekAR is providing it with the set of images that it should detect and recognize. You’ll do that in this step.

Detecting Reference Images in the Real World
With the reference images imported into the app, it’s time to take the first step and detect those images in the real world, making note of their location and size.
func startARSession() {
// Make sure that we have an AR resource group.
guard let referenceImages = ARReferenceImage.referenceImages(inGroupNamed: "AR Resources", bundle: nil) else {
fatalError("This app doesn't have an AR resource group named 'AR Resources'!")
}
// Set up the AR configuration.
config.worldAlignment = .gravityAndHeading
config.detectionImages = referenceImages
// Start the AR session.
sceneView.session.run(config, options: [.resetTracking, .removeExistingAnchors])
statusViewController.scheduleMessage("Look around for art!",
inSeconds: 7.5,
messageType: .contentPlacement)
}
// This delegate method gets called whenever the node corresponding to
// a new AR anchor is added to the scene.
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
// We only want to deal with image anchors, which encapsulate
// the position, orientation, and size, of a detected image that matches
// one of our reference images.
guard let imageAnchor = anchor as? ARImageAnchor else { return }
let referenceImage = imageAnchor.referenceImage
let imageName = referenceImage.name ?? "[Unknown]"
let isArtImage = !imageName.hasPrefix("block_this-")
var statusMessage = ""
if isArtImage {
statusMessage = "Found \(artworkDisplayNames[imageName] ?? "artwork")."
} else {
statusMessage = "Unpleasant image blocked."
}
DispatchQueue.main.async {
self.statusViewController.cancelAllScheduledMessages()
self.statusViewController.showMessage(statusMessage)
}
// Draw the appropriate plane over the image.
updateQueue.async {
}
DispatchQueue.main.async {
self.statusViewController.cancelAllScheduledMessages()
self.statusViewController.showMessage(statusMessage)
}
}
Making Detected Images Tappable
Now that the app can detect the reference images in the real world, let’s make it so that the user can tap them to find out more about them.
// Draw the appropriate plane over the image.
updateQueue.async {
var planeNode = SCNNode()
if isArtImage {
// If the detected artwork is one that we’d like to highlight (and one which we’d
// like the user to tap to find out more), draw an “artwork” plane and
// the name of the artwork over the image.
planeNode = self.createArtworkPlaneNode(withReferenceImage: referenceImage, andImageName: imageName)
let nameNode = self.createArtworkNameNode(withImageName: imageName)
node.addChildNode(nameNode)
} else {
// If the detected artwork is one that we’d like to obscure,
// draw a “blocker” plane over the image.
planeNode = self.createBlockerPlaneNode(withReferenceImage: referenceImage, andImageName: imageName)
planeNode.name = self.blockedName
}
// Create a translucent plane that will overlay the detected artwork,
// which the user can tap for more information.
// The plane will flash momentarily when it first appears.
func createArtworkPlaneNode(withReferenceImage referenceImage: ARReferenceImage,
andImageName imageName: String) -> SCNNode {
// "Flash" the plane so the user is aware that it’s now available.
// Called when the plane first appears.
let flashPlaneAction = SCNAction.sequence([
.wait(duration: 0.25),
.fadeOpacity(to: 0.85, duration: 0.25),
.fadeOpacity(to: 0.25, duration: 0.25),
.fadeOpacity(to: 0.85, duration: 0.25),
.fadeOpacity(to: 0.25, duration: 0.25),
.fadeOpacity(to: 0.85, duration: 0.25),
.fadeOpacity(to: 0.25, duration: 0.25),
])
// Draw the plane.
let plane = SCNPlane(width: referenceImage.physicalSize.width * 1.5,
height: referenceImage.physicalSize.height * 1.5)
let planeNode = SCNNode(geometry: plane)
planeNode.opacity = 0.25
planeNode.runAction(flashPlaneAction)
planeNode.name = imageName
return planeNode
}
@objc func handleScreenTap(sender: UITapGestureRecognizer) {
let tappedSceneView = sender.view as! ARSCNView
let tapLocation = sender.location(in: tappedSceneView)
let tapsOnPlane = tappedSceneView.hitTest(tapLocation,
options: nil)
if !tapsOnPlane.isEmpty {
let firstObject = tapsOnPlane.first
if let firstObjectName = firstObject?.node.name {
performSegue(withIdentifier: "showArtNotes",
sender: firstObjectName)
}
}
}
Annotating Detected Images With AR text
Let’s improve the app by displaying the name of the detected image using AR text. We’ll also make use of billboard constraints to make sure that the text is always facing the user, so that it’s easy to read.
// Create a text node to display the name of an artwork.
func createArtworkNameNode(withImageName imageName: String) -> SCNNode {
let textScaleFactor: Float = 0.15
let textFont = "AvenirNext-BoldItalic"
let textSize: CGFloat = 0.2
let textDepth: CGFloat = 0.02
let artworkNameText = SCNText(string: artworkDisplayNames[imageName],
extrusionDepth: 0.02)
artworkNameText.font = UIFont(name: textFont, size: textSize)?.withTraits(traits: .traitBold)
artworkNameText.alignmentMode = kCAAlignmentCenter
artworkNameText.firstMaterial?.diffuse.contents = UIColor.orange
artworkNameText.firstMaterial?.specular.contents = UIColor.white
artworkNameText.firstMaterial?.isDoubleSided = true
artworkNameText.chamferRadius = CGFloat(textDepth)
let artworkNameTextNode = SCNNode(geometry: artworkNameText)
artworkNameTextNode.scale = SCNVector3(textScaleFactor, textScaleFactor, textScaleFactor)
artworkNameTextNode.name = imageName
let (minBound, maxBound) = artworkNameText.boundingBox
artworkNameTextNode.pivot = SCNMatrix4MakeTranslation((maxBound.x - minBound.x) / 2,
minBound.y,
0)
let billboardConstraint = SCNBillboardConstraint()
billboardConstraint.freeAxes = SCNBillboardAxis.Y
artworkNameTextNode.constraints = [billboardConstraint]
return artworkNameTextNode
}
Blocking Images
What if there were images that we wanted to block from the user?
An AR Cure for Clown Phobia
Let’s protect the user from scary clown images by blocking them with a soothing picture of Ray.
// Create an opaque plane featuring the soothing image of Ray Wenderlich,
// which will be used to obscure unwanted images.
// Yes, we’re in “Black Mirror” territory now.
func createBlockerPlaneNode(withReferenceImage referenceImage: ARReferenceImage,
andImageName imageName: String) -> SCNNode {
// Draw the plane.
let plane = SCNPlane(width: referenceImage.physicalSize.width * 2.0,
height: referenceImage.physicalSize.height * 2.0)
let planeNode = SCNNode(geometry: plane)
planeNode.geometry?.firstMaterial?.diffuse.contents = UIImage(named: "ray")
planeNode.name = imageName
return planeNode
}
Conclusion
Last updated
Was this helpful?