Scene Kit: projectPoint calculated is displaced - ios

I am trying do draw an frame over a sphere on the parent view of the scene, but for some reason the points converted by SCNScene.projectPoint method are displaced.
To make it simple, i created a sphere at the center of the scene with 2 radius. The sphere is attached to the root node, so top left corner on world coordinates is at (-2,-2,0) point.
Here the complete code:
func sphereWithFrame(){
var v1 = SCNVector3(x: 0,y: 0,z: 0)
var v2 = SCNVector3(x: 0,y: 0,z: 0)
let topSphere = SCNSphere(radius: 2.0)
topSphere.firstMaterial!.diffuse.contents = UIColor.greenColor()
let topSphereNode = SCNNode(geometry: topSphere)
topSphereNode.position = SCNVector3Make(0, 0, 0)
scene.rootNode.addChildNode(topSphereNode)
topSphereNode.getBoundingBoxMin(&v1, max: &v2)
//world coordinates
let v1w = topSphereNode.convertPosition(v1, toNode: scene.rootNode)
let v2w = topSphereNode.convertPosition(v2, toNode: scene.rootNode)
//projected coordinates
let v1p = scnview.projectPoint(v1w)
let v2p = scnview.projectPoint(v2w)
//frame rectangle
var rect = CGRectMake(CGFloat(v1p.x), CGFloat(v2p.y), CGFloat(v2p.x - v1p.x), CGFloat(v1p.y - v2p.y))
let rectView = UIView(frame: rect)
rectView.alpha = 0.3
rectView.backgroundColor = UIColor.blueColor()
scnview.addSubview(rectView)
println("v1 \(v1.toString()), v2\(v2.toString())")
println("v1w \(v1w.toString()), v2w\(v2w.toString())")
println("v1p \(v1p.toString()), v2w\(v2p.toString())")
println("rect\(rect)")
}
The console:
v1 x:-2.0,y:-2.0,z:-2.0, v2x:2.0,y:2.0,z:2.0
v1w x:-2.0,y:-2.0,z:-2.0, v2wx:2.0,y:2.0,z:2.0
v1p x:90.718,y:309.282,z:0.925926, v2wx:263.923,y:136.077,z:0.883838
rect(90.718, 136.077, 173.205, 173.205)
The result on screen is a rectangle a little to the up right from where should be.
I try to follow the post "
How do I find my mouse point in a scene using SceneKit?".
What am i getting wrong?

have to set z to zero on both world coordinates:
v1w.z = 0
v2w.z = 0
explaining things help

Related

ARKit add 2D Video flipped by X

So I have following code to create custom wall
let wall = SCNPlane(width: CGFloat(distance),
height: CGFloat(height))
wall.firstMaterial = wallMaterial()
let node = SCNNode(geometry: wall)
// always render before the beachballs
node.renderingOrder = -10
// get center point
node.position = SCNVector3(from.x + (to.x - from.x) * 0.5,
from.y + height * 0.5,
from.z + (to.z - from.z) * 0.5)
node.eulerAngles = SCNVector3(0,
-atan2(to.x - node.position.x, from.z - node.position.z) - Float.pi * 0.5,
0)
And Now I am adding simple SCNPlane on hit test and add video (skscene to it)
// first.node is hittest result
let node = SCNNode(geometry: SCNPlane(width: CGFloat(width) , height: CGFloat(height))
node.geometry?.firstMaterial?.isDoubleSided = true
node.geometry?.firstMaterial?.diffuse.contents = self.create2DVideoScene(xScale: first.node.eulerAngles.y < 0 ? -1 : nil)
node.position = nodesWithDistance.previous.node.mainNode.position
node.eulerAngles = first.node.eulerAngles
Here How I created 2d node
/// Creates 2D video scene
private func create2DVideoScene (xScale:CGFloat?) -> SKScene {
var videoPlayer = AVPlayer()
if let validURL = Bundle.main.url(forResource: "video", withExtension: "mp4", subdirectory: "/art.scnassets") {
let item = AVPlayerItem(url: validURL)
videoPlayer = AVPlayer(playerItem: item)
}
let videoNode = SKVideoNode(avPlayer: videoPlayer)
videoNode.yScale *= -1
// While debug I observe that if first.node.rotation.y in - , then we need to change xScale to -1 (when wall draw from right -> left )
if let xScale = xScale {
videoNode.xScale *= xScale
}
videoNode.play()
let skScene = SKScene(size: self.sceneView.frame.size)
skScene.scaleMode = .aspectFill
skScene.backgroundColor = .green
skScene.addChild(videoNode)
videoNode.position = CGPoint(x: skScene.size.width/2, y: skScene.size.height/2)
videoNode.size = skScene.size
return skScene
}
Issue :: If I draw wall node from left to right means first point is on left side and other point on right side and draw wall between them. Then Video is flipped.
If I draw from right to left means first point is on right side and second point is on left side and draw line between them then video is perfectly fine.
To fix this I checked wall eulerAngles check the line self.create2DVideoScene but this is not working in every area of real world
I want video should not be start flipped in front of user
EDIT
video is flipped because of eulerAngles is different in both case while create a wall
Angle point1 to point2 --> (0.000000 -1.000000 0.000000 3.735537)
Angle point2 to point1 -- > (0.000000 -1.000000 0.000000 0.478615)
Issue video Click here to play video
Please please provide the suggestion or solution of this issue .
Video flipped
I have fixed issue with temporary solution still looking for a better one
Issue is of wall. wall is flipped other side of camera when draw from right to left direction. I have figure it out by setting isDoubleSided = false and by applying a image as diffuse contents which has text and I can see that image is flipped itself.
I have tried many things but this one helps me
Normalize vector
Find the cross between to SCNVectors
If y > 0 I just swapped from and to Value
Code
let normalizedTO = to.normalized()
let normalizedFrom = from.normalized()
let angleBetweenTwoVectors = normalizedTO.cross(normalizedFrom)
var from = from
var to = to
if angleBetweenTwoVectors.y > 0 {
let temp = from
from = to
to = temp
}
// Inside extension of SCNVector3
func normalized() -> SCNVector3 {
if self.length() == 0 {
return self
}
return self / self.length()
}
func cross(_ vec: SCNVector3) -> SCNVector3 {
return SCNVector3(self.y * vec.z - self.z * vec.y, self.z * vec.x - self.x * vec.z, self.x * vec.y - self.y * vec.x)
}
Hope it is helpful. If anyone know better solution please answer it.

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)

SKTileMapNode Collisions not working

I'm trying to make a sidescroller using SKTileMapNode and SKTileMaps, but I'm having trouble with the physics bodies of individual tiles. I create them in a loop as described in this link:
https://forums.developer.apple.com/thread/50043
However my player node passes through the ground. What am I doing wrong?
guard let landBackground = childNode(withName: "ground")
as? SKTileMapNode else {
fatalError("Background node not loaded")
}
self.landBackground = landBackground
self.tileSize = landBackground.tileSize
for x in 0...(landBackground.numberOfColumns-1){
for y in 0...(landBackground.numberOfRows-1){
let tile = landBackground.tileDefinition(atColumn: x, row: y)
if(tile != nil){
let rect = CGRect(x: 0, y: 0, width: tileSize.width, height: tileSize.height)
let tileNode = SKShapeNode(rect: rect)
tileNode.position = self.landBackground.centerOfTile(atColumn: x, row: y)
tileNode.position.x -= (tileSize.width * 0.5)
tileNode.position.y -= (tileSize.height * 0.5)
tileNode.physicsBody = SKPhysicsBody(rectangleOf: tileSize, center: tileNode.position)
tileNode.physicsBody?.isDynamic = false
tileNode.physicsBody?.collisionBitMask = playerCategory|groundCategory
//tileNode.physicsBody?.contactTestBitMask = playerCategory
tileNode.physicsBody?.categoryBitMask = groundCategory
landBackground.addChild(tileNode)
}
}
}
SKPhysicsBody(rectangleOf: tileSize, center: tileNode.position)
This is probably not correct. The documentation for the call says:
The center of the square in the owning node’s coordinate system.
This means you should probably use .zero. The physics body is always assumed to be at the same location as the node it belongs to, unless specifically offset. You should never use scene coordinates for this.
Also make sure you have showsPhysics set to true on your SKView so you can see where they really are.

Resources