SKScene in SCNMaterial's contents appears upside down - ios

I am trying to have an SCNNode with plane geometry, which presents 2d text inside it. I'm doing that through setting SKLabelNode as geometry's material (took that from here)
Here's my code:
let label = SKLabelNode(text: "Test Text 1")
label.fontColor = .white
label.fontName = UIFont.systemFont(ofSize: 32).fontName
label.fontSize = 32
let backgroundRectShape = SKShapeNode(rect: CGRect(origin: .zero, size: label.frame.size))
backgroundRectShape.fillColor = .orange
backgroundRectShape.addChild(label)
label.position = CGPoint(x: backgroundRectShape.frame.width / 2, y: 0)
let skScene = SKScene(size: label.frame.size)
skScene.addChild(backgroundRectShape)
backgroundRectShape.position = .zero
let plane = SCNPlane(width: label.frame.width / 10, height: label.frame.height / 10)
plane.firstMaterial?.diffuse.contents = skScene
let node = SCNNode(geometry: plane)
scene.rootNode.addChildNode(node)
Everything seems fine, but the SKLabelNode appears upside-down:
The SCNNode is surely properly aligned and not rotated, because when I set an image as material content, everything is ok:
I just can't understand why it is happening. The easiest solution would be setting SKLabelNode's yScale to -1, but I don't want to do that without understanding the reason why it appears upside down by default.

plane.firstMaterial?.diffuse.contentsTransform = SCNMatrix4Translate(SCNMatrix4MakeScale(1, -1, 1), 0, 1, 0)

It is seemingly a bug or one of the many limitations of SpriteKit/SceneKit framework.
I suppose SCNMaterial's content erroneously appears upside-down due to the origin of iOS coordinate system.
But instead of scaling the SCNNode itself you should better mirror a texture along Y-axis using contentsTransform instance property of the SCNMaterial. Like so:
material.diffuse.contentsTransform = SCNMatrix4MakeScale(1, -1, 1)
Hope this helps.

Related

How to manage SCNNode in ARKit as i require to switching from ARSCNView to Other view controller many time

I am working on ARKit app and my requirement for switching from ARSCNView to another Viewcontroller frequently and also need to manage SCNNode in ARSCNView.
Main Issue:
Sometimes placed SCNNode is moving very fast uncommonly and goes
out of AR screen.
Sometimes When I try to put a node on the
surface it does not judge the surface and floating node in Air only.
I want to put SCNNode on the surface with transform vertically.
Thanks.
# E.com Please check below sample code which I am using right now to add SCNNode with transform.
let planeGeometry = SCNPlane(width: CGFloat(ARRectWidth), height: CGFloat(ARRectHeight))
let material = SCNMaterial()
material.diffuse.contents = UIImage.init(named: "img")
let matirials : [SCNMaterial] = [material]
planeGeometry.materials = matirials
let rectNode = SCNNode(geometry: planeGeometry)
var transform = SCNMatrix4MakeRotation(-Float.pi / 2.0, 1.0, 0.0, 0.0)
transform = SCNMatrix4Rotate(transform, Float(orientation), 0, 1, 0)
rectNode.transform = transform
self.addChildNode(rectNode)
self.position = position

How to create a physics body for an SKSpriteNode with an interior in Sprite Kit (Swift)

I am currently making an iOS game in which a ball must bounce within a frame defined by the below image:
The game relies on the below method to automatically create and assign a physics body to the frame surrounding the game.
frame.physicsBody = SKPhysicsBody(texture: frameTexture, size: frame.size)
This code, however, assigns a body that encompasses the outside of the image, rather than the interior, and thus treats the blank interior as a solid object. This, in turn, means that the ball cannot be inside the frame and is forced off the screen immediately after being spawned.
Is there any way to give a physics body an interior?
Another post here: SpriteKit SKPhysicsBody with Inner Edges aims to do something similar, but does not involve automatic wrapping around a texture (hence the new question)
You can use init(edgeLoopFrom:) initializer to achieve inner edge friction. you can use this code below:
override func didMove(to view: SKView) {
//Setup scene's physics body (setup the walls)
physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
let back = SKSpriteNode(imageNamed: "1")
back.position = CGPoint(x: frame.midX, y: frame.midY)
back.size = CGSize(width: 500, height: 600)
back.zPosition = -3
let rect = CGRect(origin: CGPoint(x: -back.size.width/2, y: -back.size.height/2), size: back.size)
back.physicsBody = SKPhysicsBody(edgeLoopFrom: rect)
addChild(back)
//Add Red ball "inside" the back sprite
let red = SKShapeNode(circleOfRadius: 20)
red.fillColor = .red
red.strokeColor = .clear
red.position = back.position
red.physicsBody = SKPhysicsBody(circleOfRadius: 20)
red.physicsBody?.restitution = 1
red.physicsBody?.friction = 0
red.zPosition = 1
red.physicsBody?.affectedByGravity = false
addChild(red)
red.physicsBody?.applyImpulse(CGVector(dx: 20, dy: 15))
}
please follow this Question and have a look to the answer given below.
i am providing a screenshot of my project

How to get SceneKit SCNCameraController frameNodes to set target

The new SCNCameraController in iOS 11 looks very useful, but unfortunately there doesn't appear to be any documentation other than the header file and a short description in the WWDC2017 video. The frameNodes function looks particularly handy, but it doesn't seem to actually work or at least not reliably. Specifically the header comment for frameNodes says:
"Move the camera to a position where the bounding sphere of all nodes is fully visible. Also set the camera target has the center of the bounding sphere."
For an array of nodes with more than one entry, it usually adjusts the camera so they are all visible, but it almost never seems to set the camera controller target to the bounding sphere of the nodes. At least I have not been able to get it to work reliably. Has anyone figured out how to get it to work (I'm using iOS 11.2.1 and Xcode 9.2)?
I've enclosed a playground to demo the problem. If I don't set the cameraController.target manually, the camera rotates around the torus, i.e., the target appears to be set to SCNVector(0,0,0). If I set the target manually then the camera seems to rotate around the correct target, roughly between the torus and the cube. If this is just a bug that I can work around, can anyone suggest a (straightforward?) way to compute the bounding volume for an array of nodes?
import UIKit
import SceneKit
import PlaygroundSupport
var sceneView = SCNView(frame: CGRect(x: 0, y: 0, width: 400, height: 400))
var scene = SCNScene()
sceneView.scene = scene
PlaygroundPage.current.liveView = sceneView
sceneView.autoenablesDefaultLighting = true
var cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(x: 0, y: 0, z: 10)
scene.rootNode.addChildNode(cameraNode)
sceneView.allowsCameraControl = true
sceneView.defaultCameraController.interactionMode = .orbitTurntable
sceneView.defaultCameraController.pointOfView = sceneView.pointOfView
var torus = SCNTorus(ringRadius: 1, pipeRadius: 0.5)
var torusNode = SCNNode(geometry: torus)
torusNode.position = SCNVector3(0, 0, 0)
scene.rootNode.addChildNode(torusNode)
torus.firstMaterial?.diffuse.contents = UIColor.red
torus.firstMaterial?.specular.contents = UIColor.white
var cube = SCNBox(width: 1, height: 1, length: 1, chamferRadius: 0)
var cubeNode = SCNNode(geometry: cube)
cubeNode.position = SCNVector3(4, 0, 0)
scene.rootNode.addChildNode(cubeNode)
cube.firstMaterial?.diffuse.contents = UIColor.blue
cube.firstMaterial?.specular.contents = UIColor.white
SCNTransaction.begin()
SCNTransaction.animationDuration = 1.0
sceneView.defaultCameraController.frameNodes([torusNode, cubeNode])
//sceneView.defaultCameraController.target = SCNVector3(2, 0, 0)
SCNTransaction.commit()
the bounding volume of an array of nodes could be shown by creating a sphere of radius equal to the largest x,y or z value (float) in the x,y,z arrays.
pseudo-code below:
maxval = 0
for i in x[] {
if i > maxval {
maxval = i
}
}
for i in y[] {
if i > maxval {
maxval = i
}
}
for i in z[] {
if i > maxval {
maxval = i
}
}
let a = SCNSPhere(radius: maxval)

ARKit and Vision – How to place a SCNPlane on a found QR-Code?

Through Vision Framework I'm able to detect a QR-code. The next thing I would like to do is to place a SCNPlane exactly on the QRCode using ARKit. I wrote the code below to find the position of the QRCode in the real world. But the SCNPlane keeps added in the wrong place. The barcode variable in the code below is of the VNBarcodeObservation type.
Maybe one of you guys knows what I'm doing wrong.
let rect = CGPoint(x: barcode.boundingBox.origin.x, y: barcode.boundingBox.origin.y)
let hitTestResults = sceneView.hitTest(rect, types: [.existingPlaneUsingExtent, .existingPlane])
if let hitResult = hitTestResults.first {
//TODO: change to width and height
let plane = SCNPlane(width: 0.1, height: 0.1)
let material = SCNMaterial()
material.diffuse.contents = UIColor.red
plane.materials = [material]
let node = SCNNode()
node.transform = SCNMatrix4MakeRotation(-Float.pi / 2, 1, 0, 0)
node.position = SCNVector3(hitResult.worldTransform.columns.3.x,
hitResult.worldTransform.columns.3.y,
hitResult.worldTransform.columns.3.z)
node.geometry = plane
sceneView.scene.rootNode.addChildNode(node)
}
Edit:
Added a picture of the current situation. The red plane is added in the upper left corner. However, this plane has to be added exactly on te QRCode.
Works for me using the center point instead of the origins:
let center2 = CGPoint(x: result.boundingBox.midX, y: result.boundingBox.midY)
let hitTestResults2 = frame.hitTest(center2, types: [.featurePoint/*, .estimatedHorizontalPlane, .existingPlane, .existingPlaneUsingExtent*/] )

SceneKit - adding SCNNode on another SCNNode

I'm trying to put sphere on top of the box, but the position is strange: Ball is half hidden inside box. I tried to change box and sphere pivot, but it didn't help. Here's the code:
let cubeGeometry = SCNBox(width: 10, height: 10, length: 10,
chamferRadius: 0)
let cubeNode = SCNNode(geometry: cubeGeometry)
//cubeNode.pivot = SCNMatrix4MakeTranslation(0, 1, 0)
scene.rootNode.addChildNode(cubeNode)
let ballGeometry = SCNSphere(radius: 1)
let ballNode = SCNNode(geometry: ballGeometry)
ballNode.pivot = SCNMatrix4MakeTranslation(0.5, 0, 0.5)
ballNode.position = SCNVector3Make(0, 5, 0)
cubeNode.addChildNode(ballNode)`
and the result:
what I'm doing wrong? How to put the ball right on the top side of the box?
UPDATE: If I add Cube instead of ball, it looks good as I want
You need to translate on the Y-axis by cube-height/2 + sphere-radius. Thus you should have:
ballNode.position = SCNVector3Make(0, 6, 0)
Here is the screenshot:
The relevant complete code:
override func viewDidLoad() {
super.viewDidLoad()
// create a new scene
let scene = SCNScene()
let cubeGeometry = SCNBox(width: 10, height: 10, length: 10,
chamferRadius: 0)
cubeGeometry.firstMaterial?.diffuse.contents = UIColor.yellow
let cubeNode = SCNNode(geometry: cubeGeometry)
scene.rootNode.addChildNode(cubeNode)
let ballGeometry = SCNSphere(radius: 1)
ballGeometry.firstMaterial?.diffuse.contents = UIColor.green
let ballNode = SCNNode(geometry: ballGeometry)
ballNode.position = SCNVector3Make(0, 6, 0)
cubeNode.addChildNode(ballNode)
// retrieve the SCNView
let scnView = self.view as! SCNView
// set the scene to the view
scnView.scene = scene
// allows the user to manipulate the camera
scnView.allowsCameraControl = true
// show statistics such as fps and timing information
scnView.showsStatistics = true
// configure the view
scnView.backgroundColor = UIColor.gray
}
Update : Why radius and not radius / 2
See this screenshot from the Scene Editor in Xcode. The original position of cube is (0, 0, 0) and so is that of the ball; hence the ball needs to be moved by r and not r / 2; with r / 2 the lower section of the ball with height r/2 will be still inside the cube. You can just add a cube and sphere as in the scene below in the editor and that should help clarify.
Everything is running normally.
You added the ball node to the cube node. So the origin of the ball node is on the center of the cube.
Then you change the position of the ball to half the size of the cube on the y axis. So basically it pops off and you see just the half of the ball (its radius is 1).
So you have to add again half the size of the ball to put it just on the top of the cube :
ballNode.position = SCNVector3Make(0, 5.5, 0)

Resources