Below is the function that loads my object. Whenever I run the app, the object loads perfectly but when I try getting close or moving around, it moves along with the camera. I tried loading using hit test, it works but then collisions do not work when I use hit results and world positioning.
func addBackboard() {
guard let bucketScene = SCNScene(named:"art.scnassets/BucketBlue.scn") else {
return
}
guard let bucketNode = bucketScene.rootNode.childNode(withName: "tube", recursively: false) else {
return
}
bucketNode.scale = SCNVector3Make(0.5, 0.5, 0.5);
bucketNode.position = SCNVector3(x: 0, y: -3.5, z: -5)
let physicsShape = SCNPhysicsShape(node: bucketNode, options: [SCNPhysicsShape.Option.type: SCNPhysicsShape.ShapeType.concavePolyhedron])
let physicsBody = SCNPhysicsBody(type: .static, shape: physicsShape)
bucketNode.physicsBody = physicsBody
sceneView.scene.rootNode.addChildNode(bucketNode)
}
I found some Apple documentation that might help. Summary: "To track the static positions and orientations of real or virtual objects relative to the camera, create anchor objects and use the add(anchor:) method to add them to your AR session."
And then, did you implement the ARSCNViewDelegate methods?
I suppose your code should look like the following one:
class ViewController: UIViewController, ARSCNViewDelegate {
#IBOutlet var sceneView: ARSCNView!
//.................................
func addBackboard() {
sceneView.delegate = self
guard let scene = SCNScene(named:"art.scnassets/BucketBlue.scn") else {
return
}
sceneView.scene = scene
let bucketNode = SCNNode()
bucketNode.geometry = SCNTube()
bucketNode.scale = SCNVector3(0.5, 0.5, 0.5)
bucketNode.position = SCNVector3(x: 0, y: -3.5, z: -5)
let physicsShape = SCNPhysicsShape(
node: bucketNode,
options: [SCNPhysicsShape.Option.type: SCNPhysicsShape.ShapeType.concavePolyhedron])
let physicsBody = SCNPhysicsBody(type: .static, shape: physicsShape)
bucketNode.physicsBody = physicsBody
scene.rootNode.addChildNode(bucketNode)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let configuration = ARWorldTrackingConfiguration()
sceneView.session.run(configuration)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
sceneView.session.pause()
}
}
Related
Good afternoon!
I'm new to SceneKit. And I can not solve this problem.
I need to get the cube has always been in the center of the screen and followed the camera until it finds a horizontal and stand on it.
And I have it stays in one place
Here is my simple code right now.
import UIKit
import SceneKit
import ARKit
class ViewController: UIViewController, ARSCNViewDelegate {
#IBOutlet var sceneView: ARSCNView!
override func viewDidLoad() {
super.viewDidLoad()
let scene = SCNScene()
let boxGeometry = SCNBox(width: 0.2, height: 0.2, length: 0.2, chamferRadius: 0)
let material = SCNMaterial()
material.diffuse.contents = UIColor.blue
material.specular.contents = UIColor(white: 0.6, alpha: 1.0)
let boxNode = SCNNode(geometry: boxGeometry)
boxNode.geometry?.materials = [material]
boxNode.position = SCNVector3(0,0,-1.0)
scene.rootNode.addChildNode(boxNode)
sceneView.scene = scene
//------------------------------------
// Set the view's delegate
sceneView.delegate = self
// Show statistics such as fps and timing information
sceneView.showsStatistics = true
// Set the scene to the view
sceneView.scene = scene
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Create a session configuration
let configuration = ARWorldTrackingConfiguration()
// Run the view's session
sceneView.session.run(configuration)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Pause the view's session
sceneView.session.pause()
}
}
Help me please. Do not swear)
Here is an update to your code that ensure's the box is always at the center of the screen. Once the code detects a plane, it set's the box's parent as the plane anchor.
This is all very primitive, but should help you. If you want the node to float in the center of the screen, uncomment the SCNTransactions within the willRenderScene callback. If you want the box to always face the user, you can add a lookAtConstraint
import UIKit
import SceneKit
import ARKit
class ViewController: UIViewController, ARSCNViewDelegate {
#IBOutlet var sceneView: ARSCNView!
var boxNode: SCNNode? // keep a reference to the cube
override func viewDidLoad() {
super.viewDidLoad()
let scene = SCNScene()
let boxNode = createBox()
scene.rootNode.addChildNode(boxNode)
self.boxNode = boxNode
sceneView.scene = scene
//------------------------------------
// Set the view's delegate
sceneView.delegate = self
// Show statistics such as fps and timing information
sceneView.showsStatistics = true
// Set the scene to the view
sceneView.scene = scene
}
func createBox() -> SCNNode {
let boxGeometry = SCNBox(width: 0.2, height: 0.2, length: 0.2, chamferRadius: 0)
let material = SCNMaterial()
material.diffuse.contents = UIColor.blue
material.specular.contents = UIColor(white: 0.6, alpha: 1.0)
let boxNode = SCNNode(geometry: boxGeometry)
boxNode.geometry?.materials = [material]
return boxNode;
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Create a session configuration
let configuration = ARWorldTrackingConfiguration()
configuration.planeDetection = .horizontal
// Run the view's session
sceneView.session.run(configuration)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Pause the view's session
sceneView.session.pause()
}
// on willRender update the cube's position.
func renderer(_ renderer: SCNSceneRenderer, willRenderScene scene: SCNScene, atTime time: TimeInterval) {
// get camera translation and rotation
guard let pointOfView = sceneView.pointOfView else { return }
let transform = pointOfView.transform // transformation matrix
let orientation = SCNVector3(-transform.m31, -transform.m32, -transform.m33) // camera rotation
let location = SCNVector3(transform.m41, transform.m42, transform.m43) // camera translation
let currentPostionOfCamera = orientation + location
// SCNTransaction.begin()
if let boxNode = self.boxNode {
boxNode.position = currentPostionOfCamera
}
// SCNTransaction.commit()
}
// detect plances
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
guard anchor is ARPlaneAnchor else { return }
if let boxNode = self.boxNode {
let newBoxNode = createBox() // create a new node for the center of the screen
self.boxNode = newBoxNode
SCNTransaction.begin()
boxNode.removeFromParentNode()
node.addChildNode(boxNode) // set the current box to the plane.
sceneView.scene.rootNode.addChildNode(newBoxNode) // add the new box node to the scene
SCNTransaction.commit()
}
}
}
extension SCNVector3 {
static func + (left: SCNVector3, right: SCNVector3) -> SCNVector3 {
return SCNVector3Make(left.x + right.x, left.y + right.y, left.z + right.z)
}
}
In an image detection app, the image is recognised, then an opaque overlay plane is created so when the user taps on the screen a hit test finds the overlay plane, and a new object can be created. But I want to position the object exactly at the centre of the underlying image. How can I get it to be always at the centre of the image / plane, and to have the same orientation. Can this be got from a hit test result? Thanks for any advice!
#objc func handleScreenTap(sender: UITapGestureRecognizer) {
let tappedSceneView = sender.view as! ARSCNView
let tapLocation = sender.location(in: tappedSceneView)
let planeIntersections = tappedSceneView.hitTest(tapLocation, types: [.estimatedHorizontalPlane, .estimatedVerticalPlane])
if !planeIntersections.isEmpty {
addSceneAtPositionOnPlane(hitTestResult: planeIntersections.first!)
}
func addSceneAtPositionOnPlane(hitTestResult: ARHitTestResult) {
let transform = hitTestResult.worldTransform
let positionColumn = transform.columns.3
let initialPosition = SCNVector3(positionColumn.x,
positionColumn.y,
positionColumn.z)
let node = self.createScene(for: initialPosition)
sceneView.scene.rootNode.addChildNode(node)
}
func createScene(for position: SCNVector3) -> SCNNode {
let box = SCNNode(geometry: SCNBox(width: 0.1, //x
height: 0.1, //y
length: 0.1, //z
chamferRadius: 0))
box.geometry?.firstMaterial?.diffuse.contents = UIColor.red
box.geometry?.firstMaterial?.isDoubleSided = true
box.opacity = 0.8
box.position = position
return box
}
if you already added a SCNNode to render a plane on top of the detected image, then you could just use the SceneKit hitTest method that returns a SceneKit node vs. trying to hit test against ARKit geometry.
Once you have the plane you added to the scene you can just add your new geometry as a child of that node.
Here is an example where once the image is detected a plane is drawn on top of it, then when the user clicks on the plane a box is added as a child, the box will then follow the tracked image around and have the correct position and orientation.
import UIKit
import SceneKit
import ARKit
class ViewController: UIViewController, ARSCNViewDelegate {
#IBOutlet var sceneView: ARSCNView!
override func viewDidLoad() {
super.viewDidLoad()
sceneView.delegate = self
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(onTap))
sceneView.addGestureRecognizer(tapGesture)
}
#objc func onTap(_ recognizer: UITapGestureRecognizer) {
let point = recognizer.location(in: sceneView)
guard let hit = sceneView.hitTest(point, options: nil).first else {
return
}
let box = SCNBox(width: 0.02, height: 0.02, length: 0.02, chamferRadius: 0)
let node = SCNNode(geometry: box)
node.position = SCNVector3(0, 0, 0.01)
box.materials.first?.diffuse.contents = UIColor.red
hit.node.addChildNode(node)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let configuration = ARWorldTrackingConfiguration()
guard let images = ARReferenceImage.referenceImages(inGroupNamed: "ARTest", bundle: nil) else {
return
}
configuration.detectionImages = images
configuration.maximumNumberOfTrackedImages = 1
sceneView.session.run(configuration)
}
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
guard let imageAnchor = anchor as? ARImageAnchor else {
return
}
let size = imageAnchor.referenceImage.physicalSize
let plane = SCNPlane(width: size.width, height: size.height)
let planeNode = SCNNode(geometry: plane)
planeNode.eulerAngles.x = -Float.pi / 2
planeNode.opacity = 0.9
node.addChildNode(planeNode)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
sceneView.session.pause()
}
}
I'm trying to add a semi transparent background that cover my camera view from ARKit.
I try different things :
Add background to sceneView.scene but that not support transparency
Add an overlaySKScene but nodes on my scene are overlayed too.
Use CIImage from session > capturedImage but too slow.
Use this post : Reliable access and modify captured camera frames under SceneKit, it's OK for transform to Black And White but I don't understand how I can keep colors and blend gray color.
Search on OpenGL or Metal but I'm a noob !
So, do you have an idea to realize that operation in ARKit ?
Thanks in advance.
You can do it this way:
import ARKit
class ViewController: UIViewController,
ARSCNViewDelegate {
#IBOutlet var sceneView: ARSCNView!
override func viewDidLoad() {
super.viewDidLoad()
sceneView.delegate = self
sceneView.session.delegate = self
let scene = SCNScene()
sceneView.scene = scene
let planeNode = SCNNode()
planeNode.geometry = SCNPlane(width: 100,
height: 100)
planeNode.geometry?.firstMaterial?.diffuse.contents = UIColor(white: 0,
alpha: 0.9)
planeNode.position.z = -5 // 5 meters away
sceneView.pointOfView?.addChildNode(planeNode) // PINNING TO CAMERA
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let configuration = ARWorldTrackingConfiguration()
sceneView.session.run(configuration)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
sceneView.session.pause()
}
}
... or in extension:
extension ViewController: ARSessionDelegate {
func session(_ session: ARSession,
didUpdate frame: ARFrame) {
let planeNode = SCNNode()
planeNode.geometry = SCNPlane(width: 100,
height: 100)
planeNode.position.z = -5
planeNode.geometry?.firstMaterial?.diffuse.contents = UIColor(white: 0,
alpha: 0.9)
// var translation = matrix_identity_float4x4
// translation.columns.3.z = -5
// planeNode.simdTransform = matrix_multiply(translation,
// frame.camera.transform)
sceneView.pointOfView?.addChildNode(planeNode)
}
}
I am trying to display a 3D model when a picture is tapped (See here). The picture gets tapped, it segues to 3DFloorPlanViewController (which is embedded in a navigation controller), the camera works but there is no 3D object displayed (See here).
I've tried this exact same code without a navigation controller and it works perfectly so I'm confused as to why it's not working when embedded in a navigation controller.
Storyboard picture
My 3D objects are located in a folder called "3D Objects" (See here). I've tried both named: "3D Objects/paperPlane.scn" and named: "paperPlane.scn" in the addPaperPlane function and neither worked.
Here's the code to 3DFloorPlanViewController:
import UIKit
import ARKit
import SceneKit
class _DFloorPlanViewController: UIViewController, ARSCNViewDelegate {
#IBOutlet weak var sceneView: ARSCNView!
override func viewDidLoad() {
super.viewDidLoad()
sceneView.delegate = self
addPaperPlane()
//addCar()
configureLighting()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let configuration = ARWorldTrackingConfiguration()
sceneView.session.run(configuration)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
sceneView.session.pause()
}
func addPaperPlane(x: Float = 0, y: Float = 0, z: Float = -0.5) {
guard let paperPlaneScene = SCNScene(named: "3D Objects/paperPlane.scn"), let paperPlaneNode = paperPlaneScene.rootNode.childNode(withName: "paperPlane", recursively: true) else { return }
paperPlaneNode.position = SCNVector3(x, y, z)
sceneView.scene.rootNode.addChildNode(paperPlaneNode)
}
func addCar(x: Float = 0, y: Float = 0, z: Float = -0.5) {
guard let carScene = SCNScene(named: "car.dae") else { return }
let carNode = SCNNode()
let carSceneChildNodes = carScene.rootNode.childNodes
for childNode in carSceneChildNodes {
carNode.addChildNode(childNode)
}
carNode.position = SCNVector3(x, y, z)
carNode.scale = SCNVector3(0.5, 0.5, 0.5)
sceneView.scene.rootNode.addChildNode(carNode)
}
func configureLighting() {
sceneView.autoenablesDefaultLighting = true
sceneView.automaticallyUpdatesLighting = true
}
}
At the moment there is no scene set to your scene view
let scene = SCNScene()
self.sceneView.scene = scene
in general try this:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// Set the view's delegate
self.sceneView.delegate = self
self.sceneView.showsStatistics = true
self.sceneView.autoenablesDefaultLighting = true
let scene = SCNScene()
self.sceneView.scene = scene
}
I am trying to access the childnode: boxNode, and then rotate the node using the input from a pan gesture recognizer. How should I access this node so I can manipulate it? Should I declare the cube node in a global scope and modify that way? I am new to Swift so I apologize if this code looks sloppy. I would like to add rotation actions inside of my panGesture() function. Thanks!
import UIKit
import SceneKit
class GameViewController: UIViewController {
var scnView: SCNView!
var scnScene: SCNScene!
override func viewDidLoad() {
super.viewDidLoad()
setupView()
setupScene()
}
// override var shouldAutorotate: Bool {
// return true
// }
//
// override var prefersStatusBarHidden: Bool {
// return true
// }
func setupView() {
scnView = self.view as! SCNView
}
func setupScene() {
scnScene = SCNScene()
scnView.scene = scnScene
scnView.backgroundColor = UIColor.blueColor()
initCube()
initLight()
initCamera()
}
func initCube () {
var cubeGeometry:SCNGeometry
cubeGeometry = SCNBox(width: 1.0, height: 1.0, length: 1.0, chamferRadius: 0.0)
let boxNode = SCNNode(geometry: cubeGeometry)
scnScene.rootNode.addChildNode(boxNode)
}
func initLight () {
let light = SCNLight()
light.type = SCNLightTypeAmbient
let lightNode = SCNNode()
lightNode.light = light
light.castsShadow = false
lightNode.position = SCNVector3(x: 1.5, y: 1.5, z: 1.5)
scnScene.rootNode.addChildNode(lightNode)
}
func initCamera () {
let camera = SCNCamera()
let cameraNode = SCNNode()
cameraNode.camera = camera
cameraNode.position = SCNVector3(x: 0.0, y: 0.0, z: 5.0)
scnScene.rootNode.addChildNode(cameraNode)
}
func initRecognizer () {
let panTestRecognizer = UIPanGestureRecognizer(target: self, action: #selector(GameViewController.panGesture(_:)))
scnView.addGestureRecognizer(panTestRecognizer)
}
//TAKE GESTURE INPUT AND ROTATE CUBE ACCORDINGLY
func panGesture(sender: UIPanGestureRecognizer) {
//let translation = sender.translationInView(sender.view!)
}
}
First of all you should update your Xcode. It looks like this is an older Swift version you are using. SCNLightTypeAmbient is .ambient in Swift 3.
So the way you should go is by giving your nodes names to identify them:
myChildNode.name = "FooChildBar"
and then you can call on your parent node
let theNodeYouAreLookingFor = parentNode.childNode(withName: "FooChildBar", recursively: true)
You can also use this on your scene's root node
let theNodeYouAreLookingFor = scene.rootNode.childNode(withName: "FooChildBar", recursively: true)
which will look at all nodes in your scene and return the one that you gave the name.