Strange behavior with SceneKit Node - ios

I created a circular array objects, no problems.
When I rotate there appears to be a sinc or gaussian function at the center.
The camera is a z 60, radius of structure is 30.
Initial view, no artifacts
Rotated 90 deg up, no artifacts
Rotated 180 deg, artifact appears in center of object
Continued rotation, artifact is still there.
The code for the object is here
class func Build(scene: SCNScene) -> SCNNode {
let radius: Double = 30.0
let numberOfStrands: Int = 24
//Create the base chromosomes.
let baseChromosome = SCNBox(width: 4.0, height: 24, length: 1.0, chamferRadius: 0.5)
let baseChromosomes = DNA.buildCircleOfObjects(baseChromosome, numberOfItems: numberOfStrands, radius: radius)
baseChromosomes.position = SCNVector3(x: 0, y: 0.5, z: 0)
return baseChromosomes
}
class func buildCircleOfObjects(_geometry: SCNGeometry, numberOfItems: Int, radius: Double) -> SCNNode {
var x: Double = 0.0
var z: Double = radius
let theta: Double = (M_PI) / Double(numberOfItems / 2)
let incrementalY: Double = (M_PI) / Double(numberOfItems) * 2
let nodeCollection = SCNNode()
nodeCollection.position = SCNVector3(x: 0, y: 0.5, z: 0)
for index in 1...numberOfItems {
x = radius * sin(Double(index) * theta)
z = radius * cos(Double(index) * theta)
let node = SCNNode(geometry: _geometry)
node.position = SCNVector3(x: Float(x), y: 0, z:Float(z))
let rotation = Float(incrementalY) * Float(index)
node.rotation = SCNVector4(x: 0, y: 1, z: 0, w: rotation)
nodeCollection.addChildNode(node)
}
return nodeCollection
}
}

Using these settings
cameraNode.camera?.xFov = 60
cameraNode.camera?.yFov = 60
cameraNode.camera?.zFar = 1000
cameraNode.camera?.zNear = 0.01
the artifacts disappeared. I think this is a problem with zFar, it should have clipped the back surface uniformly not like a lens aberration.

Falling out of Viewing frustum may be the one to cause most problem. However there is another common cause by misunderstanding the behavior of SCNView's allowsCameraControl, that is when you Pinch to zoom in or zoom out (change the camera's fieldOfView), but the pointOfView's position reminds the same.
Your nodes still stand BEHIND the pointOfView, however small the camera's zNear is. Hence you see them got clipped. The code below may help you in this case, at least avoid the problem.
sceneView.pointOfView!.simdPosition
= float3(0, 0, -scene.rootNode.boundingSphere.radius)

Related

ARKit Project 2D point to captured image coordinate system

I would like to project ARPlaneAnchor point to captured image coordinate system.
At this moment i am able to get plane point and project it to view coordinate system by method
func translateTransform(_ x: Float, _ y: Float, _ z: Float) -> float4x4 {
var tf = float4x4(diagonal: SIMD4<Float>(repeating: 1))
tf.columns.3 = SIMD4<Float>(x: x, y: y, z: z, w: 1)
return tf
}
extension ARPlaneAnchor {
func worldPoints() -> (SCNVector3, SCNVector3, SCNVector3, SCNVector3) {
let worldTransform = transform * translateTransform(center.x, 0, center.z)
let width = extent.x
let height = extent.z
let topLeft = worldTransform * translateTransform(-width / 2.0, 0, -height / 2.0)
let topRight = worldTransform * translateTransform(width / 2.0, 0, -height / 2.0)
let bottomLeft = worldTransform * translateTransform(-width / 2.0, 0, height / 2.0)
let bottomRight = worldTransform * translateTransform(width / 2.0, 0, height / 2.0)
let pointTopLeft = SCNVector3(
x: topLeft.columns.3.x,
y: topLeft.columns.3.y,
z: topLeft.columns.3.z
)
let pointTopRight = SCNVector3(
x: topRight.columns.3.x,
y: topRight.columns.3.y,
z: topRight.columns.3.z
)
let pointBottomLeft = SCNVector3(
x: bottomLeft.columns.3.x,
y: bottomLeft.columns.3.y,
z: bottomLeft.columns.3.z
)
let pointBottomRight = SCNVector3(
x: bottomRight.columns.3.x,
y: bottomRight.columns.3.y,
z: bottomRight.columns.3.z
)
return (
pointTopLeft,
pointTopRight,
pointBottomLeft,
pointBottomRight
)
}
And then by projectPoint convert 3D point to 2D like in example
guard let point = arPlane?.worldPoints().0 else { return }
let pp = self.sceneView.session.currentFrame?.camera.projectPoint(SIMD3.init(point), orientation: .landscapeRight, viewportSize: self.view.bounds.size)
But how to convert this point to capturedImage coordinate system?

How to scale SCNNodes to fit within a bounding box

I'm downloading Collada DAE scenes and rendering them in SceneKit, but having trouble getting the downloaded node to "fit" within its parent node. I mainly care about scaling it's y-height to fit in the parent node.
Here's the code I'm using:
// Reset everything
node.position = SCNVector3(0, 0, 0)
node.pivot = SCNMatrix4Identity
node.scale = SCNVector3(1, 1, 1)
// Calculate absolute bounding box
let (min, max) = node.boundingBox
let size = max - min
// Get the biggest dimension of the node
guard let scaleRef = [size.x, size.y, size.z].max() else {
return
}
// Desired bounding box is of size SCNVector3(0.4, 0.4, 0.4)
let parentSize: Float = 0.4
let ratio = (parentSize/scaleRef)
node.scale = SCNVector3(node.scale.x * ratio, node.scale.y * ratio, node.scale.z * ratio)
//Correctly position the node at the bottom of its parent node by setting pivot
let (minNode, maxNode) = node.boundingBox
let dx = (maxNode.x - minNode.x) / 2
let dy = minNode.y
let dz = (maxNode.z - minNode.z) / 2
node.pivot = SCNMatrix4Translate(node.pivot, dx, dy, dz)
node.position.x = dx * ratio
node.position.y = dy * ratio
node.position.z = dz * ratio
This seems to work for most cases, but I'm ending up with a few that scale incorrectly.
For example, this object scales correctly (Poly Astornaut):
MIN: SCNVector3(x: -1.11969805, y: -0.735845089, z: -4.02169418)
MAX: SCNVector3(x: 1.11969805, y: 0.711179018, z: 0.0)
NEW SCALE: SCNVector3(x: 0.099460572, y: 0.099460572, z: 0.099460572)
This one does not (Basketball player):
MIN: SCNVector3(x: -74.8805618, y: 0.0459594727, z: -21.4300499)
MAX: SCNVector3(x: 74.8805618, y: 203.553589, z: 15.6760511)
NEW SCALE: SCNVector3(x: 0.00196552835, y: 0.00196552835, z: 0.00196552835)
Screenshots:
Correct scaling
Incorrect scaling
So what I ended up doing was a little different, as I wanted to scale my asset to exactly the reference size.
In my .scn file, going to the Node Inspector tab and looking at the bounding box of the asset I'm working with:
Know the size of your reference image. This is something you should be measuring in the real world, and entering it in your Assets.xcassets resource
This reference image size is passed to you in the func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) function. I access mine through the following code:
guard let imageAnchor = anchor as? ARImageAnchor else { return }
let referenceImage = imageAnchor.referenceImage
print(referenceImage.physicalSize.width)
print(referenceImage.physicalSize.height)
Wherever you want to scale, you'll simply use algebra to scale the bounding box size, to the desired size. So our scale is simply
let threeDimensionalAssetToRealReferenceImageScale = referenceImageWidth / threeDimensionalAssetBoundingBoxWidth
Call scale on your SCNNode
I wrote this up, as I was very confused with this situation, I hope it helps someone.

Repeat Texture on X axis over 3dObject in ARkit SCNODE - SWIFT

I wanted to move the texture over the SCNode in my current scene.
I am currently animating the node but i want the texture to run over the object instead of animating it.
let moveup = SCNAction.moveBy(x: 0.01, y: 0, z: 0, duration: 1)
moveup.timingMode = .easeInEaseOut
moveup.speed = CGFloat(20.5)
let moveDown = SCNAction.moveBy(x: -0.01, y: 0, z: 0, duration: 1)
moveDown.timingMode = .easeInEaseOut;
moveDown.speed = CGFloat(20.5)
let moveupSequence = SCNAction.sequence([moveup, moveDown])
print(moveupSequence.duration, moveupSequence.speed)
WaterNode?.runAction(moveupSequence)
This is my code for annimating the object. I need to know how can i access the texture and move them to X axis.
There is contentsTransform property of material
let action = SCNAction.customAction(duration: 1) { (node, elapsedTime) in
let c = node.geometry?.materials.first?.diffuse.contentsTransform
node.geometry?.materials.first?.diffuse.contentsTransform = SCNMatrix4MakeTranslation((c?.m41)! + 0.01, (c?.m42)!, (c?.m43)!)
}

Scene Kit node not rotating

I am trying to rotate by scene kit node, however it is not rotating.
I want it to rotate around the y axis. It is a sphere.
let node = SCNNode()
node.geometry = SCNSphere(radius: 1)
node.geometry?.firstMaterial?.diffuse.contents = UIImage(named: "diffuse.png")
node.geometry?.firstMaterial?.specular.contents = UIImage(named: "specular.png")
node.geometry?.firstMaterial?.emission.contents = UIImage(named: "emission.png")
scene.rootNode.addChildNode(node)
let action = SCNAction.rotate(by:360 * CGFloat((Double.pi)/180.0), around: SCNVector3(x: 0, y: 1, z: 0), duration: 8)
let repeatAction = SCNAction.repeatForever(action)
node.runAction(repeatAction)
Are you certain it isn't rotating? I'm not sure what your diffuse, specular and emission images are but maybe because it's a sphere it just doesn't really look like it is rotating because it looks the same from all sides.
Running the same action on a cube in viewDidAppear is working for me, so it could just be that you can't really notice the sphere rotating. I used this code:
let node = SCNNode()
node.geometry = SCNBox (width: 0.5, height: 0.5, length: 0.5, chamferRadius: 0.1)
node.position = SCNVector3Make(0, 0, -1)
sceneView.scene.rootNode.addChildNode(node)
let action = SCNAction.rotate(by:360 * CGFloat((Double.pi)/180.0), around: SCNVector3(x: 0, y: 1, z: 0), duration: 8)
let repeatAction = SCNAction.repeatForever(action)
node.runAction(repeatAction)
The cube is rotating fine with the way that you rotated the SCNNode.
Hope this helps

Xcode - Swift SpriteKit: SKTileMapNode with a "physics gap"

I am playing around with SpriteKit and the TileMapNode and i've got an annoying problem.
That's how it should look like.
That's how its actually looking in the simulator/device.
The white spaces are terrible and i have no idea, how to get rid of them.
My Tiles are about 70x70 in the "Sprite Atlas - Part" of the assets, i've configured my tilemapnode with a scale of 0.5 and tile size of 70x70.
While testing some cases, i figured out that this part of code triggers the error, but i have no idea, what could be wrong. Changing the SKPhysicsBody size to a smaller one, did not helped.
guard let tilemap = childNode(withName: "LevelGround") as? SKTileMapNode else { return }
let tileSize = tilemap.tileSize
let halfWidth = CGFloat(tilemap.numberOfColumns) / 2.0 * tileSize.width
let halfHeight = CGFloat(tilemap.numberOfRows) / 2.0 * tileSize.height
for row in 0..<tilemap.numberOfRows {
for col in 0..<tilemap.numberOfColumns {
if tilemap.tileDefinition(atColumn: col, row: row) != nil {
let x = CGFloat(col) * tileSize.width - halfWidth
let y = CGFloat(row) * tileSize.height - halfHeight
let rect = CGRect(x: 0, y: 0, width: tileSize.width, height: tileSize.height)
let tileNode = SKShapeNode(rect: rect)
tileNode.position = CGPoint(x: x, y: y)
tileNode.physicsBody = SKPhysicsBody(rectangleOf: CGSize(width: 70, height: 70), center: CGPoint(x: tileSize.width / 2.0, y: tileSize.height / 2.0))
tileNode.physicsBody?.isDynamic = false
tileNode.physicsBody?.collisionBitMask = 2
tileNode.physicsBody?.categoryBitMask = 1
tileNode.physicsBody?.contactTestBitMask = 2 | 1
tileNode.name = "Ground"
tilemap.addChild(tileNode)
}
}
}
tileNode.strokeColor = .clear
solved the problem
Update:
problem not solved... just moved :/
When checking, if ground & player are in contact, every new tile the status is switching between "contact" and "no contact".
When using a cube instead a circle, the cube begins to rotate. It seems, the corner of the cube get's stuck at the minimal space between the tiles.

Resources