Not able to add texture image to a cube in ARKit - swift

I am not able to add an image to the cube in ARKit using the "Material" object.
Here is the code:
import UIKit
import SceneKit
import ARKit
class SimpleBoxViewController: UIViewController, ARSCNViewDelegate {
var sceneView: ARSCNView!
override func viewDidLoad() {
super.viewDidLoad()
self.sceneView = ARSCNView(frame: self.view.frame)
self.view.addSubview(self.sceneView)
sceneView.delegate = self
sceneView.showsStatistics = true
let scene = SCNScene()
let box = SCNBox(width: 0.2, height: 0.2, length: 0.2, chamferRadius: 0)
let material = SCNMaterial()
//This is not working
material.diffuse.contents = UIImage(named: "<someImage>.png")
let node = SCNNode()
node.geometry = box
node.geometry?.materials = [material]
node.position = SCNVector3(0, -0.1, -0.5)
scene.rootNode.addChildNode(node)
sceneView.scene = scene
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let configuration = ARWorldTrackingConfiguration()
sceneView.session.run(configuration)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Pause the view's session
sceneView.session.pause()
}
I tried to add various different images but nothing works. The only image that works is the image named "textures.png" which is preloaded into an ARKit project.
Is there a specific requirement for an image to be to loaded?

I'm not 100% sure on this one, but the issue might be with including the .png with the imageName, since this image should be in your Assets folder.
Anyway, this code is working for me and tries do to the same thing with regards to creating the cube with an image.
var box = SCNBox(width: pd.width, height: pd.height, length: 0.01,
chamferRadius: 0.0)
var imageMaterial = SCNMaterial()
var image = UIImage(named: "image")
imageMaterial.diffuse.contents = image
box.materials = [imageMaterial, imageMaterial, imageMaterial, imageMaterial, imageMaterial, imageMaterial]
var cube = SCNNode(geometry: box)

you have to add the path like:
material.diffuse.contents = UIImage(named: "art.scnassets/textur")
this works for me.

I was also facing the same issue. I have copied the png files into Assets.xcassets folder and its works for me.

Related

Change texture image for 3d model

I'm investigating iOS and trying to understand is it possible to change the texture of the model using any other picture?
Let's say I have model.obj with related texture green.png which is applied to this model so the appearance of the product is green. Is it possible to choose any other image, for example, blue.png, and apply it programmatically in runtime to 3d model and make the appearance of the product blue?
I have one working example
override func viewDidLoad() {
super.viewDidLoad()
let node = SCNNode()
let geometry = SCNSphere(radius: 0.2)
node.position = SCNVector3(0, 0, -1)
sceneView.backgroundColor = .black
sceneView.scene = SCNScene()
node.geometry = self.geometry
let image = UIImage(named: "art.scnassets/green.jpeg")
print(image)
node.geometry?.firstMaterial?.diffuse.contents = image
sceneView.scene.rootNode.addChildNode(node)
}
But when I try apply image to uploaded 3d model it's appearance doesn't change, here is a code.
override func viewDidLoad() {
super.viewDidLoad()
let tempScene = SCNScene(named: "art.scnassets/California_chair_1.obj")!
let node = tempScene.rootNode
node.position = SCNVector3(0, 0, -1)
sceneView.scene = SCNScene()
let image = UIImage(named: "art.scnassets/green.jpeg")
print(image)
/**/
let material = SCNMaterial()
material.isDoubleSided = false
material.diffuse.contents = image
node.geometry?.materials = [material]
/**/
node.geometry?.firstMaterial?.diffuse.contents = image
sceneView.scene.rootNode.addChildNode(node)
}
How to apply image to any 3d model created by designer?
Many thanks for any help!
For retrieving a model from SCNScene, you may use subscript .childNodes[0] several times to get to geometry and its corresponding materials in hierarchy.
import ARKit
class ViewController: UIViewController {
#IBOutlet var sceneView: ARSCNView!
let node = SCNNode()
override func viewDidLoad() {
super.viewDidLoad()
sceneView.scene = SCNScene(named: "art.scnassets/California_chair.scn")!
sceneView.autoenablesDefaultLighting = true
node = sceneView.scene.rootNode.childNode(withName: "firstChair",
recursively: true)
let green = UIColor.green
node?.childNodes[0].geometry?.firstMaterial?.diffuse.contents = green
}
#IBAction func changeTexture(_ sender: UIButton) {
let blue = UIImage(named: "art.scnassets/blueTexture.png")
node.geometry?.firstMaterial?.diffuse.contents = blue
}
}
Consider that SCNGeometry may be nested inside deep hierarchy:
node?.childNodes[0].childNodes[0].childNodes[0].geometry.firstMaterial?.diffuse
In Xcode's Scene graph such a nested hierarchy looks like this:
Also always check your node's size (scale), to find out if your camera is inside 3D model or not.
P.S.
In case you use obj models – use their corresponding mtl textures:
sceneView.scene = SCNScene(named: "art.scnassets/file.obj")!
let obj = sceneView.scene.rootNode.childNode(withName: "default",
recursively: true)
obj?.geometry?.firstMaterial?.diffuse.contents = UIImage(named:
"art.scnassets/file.mtl")

Apply GIF to SCNPlane

Right now my Swift code creates a SCNPlane with an image called we.jpg. What I would like to do is to replace we.jpg with a ball.gif. material.diffuse.contents is where the UIImage is attached to the SCNPlane.
let planeGeometry = SCNPlane(width: 0.2, height: 0.35)
let material = SCNMaterial()
material.diffuse.contents = UIImage(named: "we.jpg")
planeGeometry.materials = [material]
You can easily replace SCNPlane's .jpg texture with .gif texture. But remember that in SceneKit there's no support for animated GIF textures.
Here's a code:
import ARKit
import SceneKit
class ViewController: UIViewController {
#IBOutlet var sceneView: ARSCNView! // in case you're using AR app
//#IBOutlet var sceneView: SCNView! // in case you're using VR app
let planeNode = SCNNode()
override func viewDidLoad() {
super.viewDidLoad()
let scene = SCNScene()
sceneView.scene = scene
sceneView.allowsCameraControl = true
planeNode.geometry = SCNPlane(width: 0.2, height: 0.35)
planeNode.position = SCNVector3(0, 0,-0.5)
sceneView.scene?.rootNode.addChildNode(planeNode)
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { // 3 sec delay
let image = UIImage(named: "texture.jpg")
self.planeNode.geometry?.firstMaterial?.diffuse.contents = image
}
// Other stuff...
}
//...and you can replace a texture with a button's click:
#IBAction func replaceTexture(_ sender: UIButton) {
let image = UIImage(named: "texture.gif")
planeNode.geometry?.firstMaterial?.diffuse.contents = image
}
}

How to add a cube in the center of the screen, and so that it never leaves?

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)
}
}

How to add a semi-transparent background on top of camera ARKit

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 can't move around a loaded SceneKit model

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()
}
}