Rotate SCNNode then move relative to rotation? - swift

My node setup:
let node = SCNNode()
node.position = SCNVector3(0.0, 0.0, 0.0)
I then wish to rotate the node by 90 degrees to the left which can be achieved with:
node.transform = SCNMatrix4Rotate(node.transform, .pi/2, 0.0, 1.0, 0.0)
Next, I want to translate the node forward one unit along the negative z axis relative to current rotation and end up with:
node.position = SCNVector3(-1.0, 0.0, 0.0)
I have no idea how to get from the rotation of the node to the position of the node programmatically.
Basically node begins at (0, 0, 0), its forward vector is the -z axis, node turns left and moves one unit forward to end up at (-1, 0, 0).
This isn't working:
func move(_ direction: moveDirection) {
switch direction {
case .forward: characterNode.position = SCNVector3(characterNode.position.x, characterNode.position.y, characterNode.position.z - 1.0)
case .left: characterNode.pivot = SCNMatrix4Rotate(characterNode.pivot, -.pi/32, 0.0, 1.0, 0.0)
case .backward: characterNode.position = SCNVector3(characterNode.position.x, characterNode.position.y, characterNode.position.z + 1.0)
case .right: characterNode.pivot = SCNMatrix4Rotate(characterNode.pivot, .pi/32, 0.0, 1.0, 0.0)
}
}

Assuming I have understood you correctly, once you have rotated your SCNNode, you want to move it forward it in that direction.
This can be done by using the worldFront value which is simply:
The local unit -Z axis (0,0,-1) in world space.
As such this may be the answer you are looking for:
//1. Create A Node Holder
let nodeToAdd = SCNNode()
//2. Create An SCNBox Geometry
let nodeGeometry = SCNBox(width: 0.2, height: 0.2, length: 0.2, chamferRadius: 0)
nodeGeometry.firstMaterial?.diffuse.contents = UIColor.cyan
nodeGeometry.firstMaterial?.lightingModel = .constant
nodeToAdd.geometry = nodeGeometry
//3. Position It 1.5m Away From The Camera
nodeToAdd.position = SCNVector3(0, 0, -1.5)
//4. Rotate The Node By 90 Degrees On It's Y Axis
nodeToAdd.rotation = SCNVector4Make(0, 1, 0, .pi / 2)
//5. Add The Node To The ARSCNView
augmentedRealityView.scene.rootNode.addChildNode(nodeToAdd)
//6. Use The World Front To Move The Node Forward e.g
/*
nodeToAdd.simdPosition += nodeToAdd.simdWorldFront * 1.2
*/
nodeToAdd.simdPosition += nodeToAdd.simdWorldFront
//7. Print The Position
print(nodeToAdd.position)
/*
SCNVector3(x: -0.999999881, y: 0.0, z: -1.50000024)
*/

Related

swift SceneKit calculate node angle

i trying to add a node (pin) to sphere node (when taping on sphere node), but can't correctly calculate an angle, can u help me pls ?
private func addPinToSphere(result: SCNHitTestResult) {
guard let scene = SCNScene(named: "art.scnassets/tree.scn") else { return }
guard let treeNode = scene.rootNode.childNode(withName: "Trunk", recursively: true) else { return }
let x = CGFloat(result.worldCoordinates.x)
let y = CGFloat(result.worldCoordinates.y)
treeNode.position = result.worldCoordinates
treeNode.eulerAngles = SCNVector3((x) * .pi / CGFloat(180), 0, (y - 90) * .pi / CGFloat(180)) // y axis is ignorable
result.node.addChildNode(treeNode)
}
when run code i have this
but want like this
You can do it similar to this code. This is a missile launcher, but it's basically the same thing. Your pin = my fireTube, so create a base node, then add a subnode with your materials to it and rotate it into place. You'll have to experiment with where to place the tube initially, but once you get it into the right place the lookat constraint will always point it in the right direction.
case .d1, .d2, .d3, .d4, .d5, .d6, .d7, .d8, .d9, .d10:
let BoxGeometry = SCNBox(width: 0.8, height: 0.8, length: 0.8, chamferRadius: 0.0)
let vNode = SCNNode(geometry: BoxGeometry)
BoxGeometry.materials = setDefenseTextures(vGameType: vGameType)
let tubeGeometry = SCNTube(innerRadius: 0.03, outerRadius: 0.05, height: 0.9)
let fireTube = SCNNode(geometry: tubeGeometry)
tubeGeometry.firstMaterial?.diffuse.contents = data.getTextureColor(vTheme: 0, vTextureType: .barrelColor)
fireTube.position = SCNVector3(0, 0.2, -0.3)
let vRotateX = SCNAction.rotateBy(x: CGFloat(Float(GLKMathDegreesToRadians(-90))), y: 0, z: 0, duration: 0)
fireTube.runAction(vRotateX)
vNode.addChildNode(fireTube)
return vNode
Then set target on your base node and your subnode will rotate with it:
func setTarget()
{
node.constraints = []
let vConstraint = SCNLookAtConstraint(target: targetNode)
vConstraint.isGimbalLockEnabled = true
node.constraints = [vConstraint]
}
In your case, target equals center mass of your sphere and the pin will always point to it, provided you built and aligned your box and pin correctly.

SpriteKit weird behaviour of SKSpriteNode property

I'm moving my first steps into SpriteKt and found this apparently weird behaviour of the SkSpriteNode.position property: it should be a CGPoint, but when assigned and read back, it behaves differently of any other CGPoint.
In the code that follows I have tested some randomly chosen points. Some of them can be assigned to the SkSpriteNode.position property and read back again and everything seems to work fine.
Some other, when assigned, are rounded to some other (close) value.
It could be a floating-point value representation round off issue, but than comes the super weird thing: the exact same CGPoint, when assigned to another CGPoint (not to the position property, which, by the way, claims to be a CGPoint) the round off doesn't appear 😳
Does anybody knows what's going on?
check(position: CGPoint.zero)
check(position: CGPoint(x: 1, y: 1))
check(position: CGPoint(x: 100, y: 700))
check(position: CGPoint(x: -500, y: 200))
check(position: CGPoint(x: 0.5, y: 0.5))
check(position: CGPoint(x: 1.12, y: 1.14))
check(position: CGPoint(x: 1.12, y: 1.1415161718))
func check(position: CGPoint) {
let sprite = SKSpriteNode(color: .cyan, size: CGSize(width: 32, height: 32))
let point = position
sprite.position = position
print("position = \(position)")
print("point = \(point)")
print("sprite = \(sprite.position)")
if sprite.position == position {
print("✅ they match")
} else {
print("❌ they don't match")
}
assert(position == point, "assign \(position) to point result in \(point)")
}
On my system (Xcode Version 12.5 (12E262)) output is:
position = (0.0, 0.0)
point = (0.0, 0.0)
sprite = (0.0, 0.0)
✅ they match
position = (1.0, 1.0)
point = (1.0, 1.0)
sprite = (1.0, 1.0)
✅ they match
position = (100.0, 700.0)
point = (100.0, 700.0)
sprite = (100.0, 700.0)
✅ they match
position = (-500.0, 200.0)
point = (-500.0, 200.0)
sprite = (-500.0, 200.0)
✅ they match
position = (0.5, 0.5)
point = (0.5, 0.5)
sprite = (0.5, 0.5)
✅ they match
position = (1.12, 1.14)
point = (1.12, 1.14)
sprite = (1.1200000047683716, 1.1399999856948853)
❌ they don't match
position = (1.12, 1.1415161718)
point = (1.12, 1.1415161718)
sprite = (1.1200000047683716, 1.1415162086486816)
❌ they don't match
Internally the sprite is using 32-bit single-precision floats. The normal CGPoint is 64-bit double-precision. Assigning sprite.position is rounding 64-bit to 32-bit, and then for the printing and comparison it's going back from 32-bit to 64-bit. Your initial examples all happen to be numbers that are exactly representable in both 32- and 64-bits, so no loss of precision is happening there.

SCNKit: Scaling parent node not working

I'm trying to scale down a SCNNode that contains other nodes, but itself has no geometries. I read the documentation on scale, but I'm a little skeptical that they would make positioning relative to the parent, but not scale.
The problem:
scale does not seem to do anything.
Here's a snippet of my SCNNode sub-class
addChildNode(Node1)
addChildNode(Node2)
Node2.addChildNode(Node21)
addChildNode(Node3)
print("pre-scale", self.scale)
// prints SCNVector3(x: 1.0, y: 1.0, z: 1.0)
self.scale = SCNVector3(x:0.05, y:0.05, z:0.05)
print("post-scale", self.scale)
// prints SCNVector3(x: 0.05, y: 0.05, z: 0.05)
Visibly, nothing changes.
I've considered doing a loop and applying the scaling factor to every child node, but I think the relative positions will get all messed up.
I'd like everything to scale as one and retain its integrity. Is there something I'm missing?
Try to scale all child nodes one by one:
let newScale = Float(0.05)
for c in self.childNodes
{
c.scale = SCNVector3Make(newScale, newScale, newScale)
}
The parent node may not assign to any camera. try adding the camera to the node and try the scaling.
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(x: 0, y: 0, z: 5)
scene.rootNode.addChildNode(cameraNode)
parentNode.scale = SCNVector3(0.5, 0.5, 0.5)

simd_float4x4 Columns

I want to translate a plane without rotating the image. For any reason my image is being rotated.
var translation = matrix_identity_float4x4
translation.colum = -0.2
let transform = simd_mul(currentFrame.camera.transform, translation)
planeNode.simdWorldTransform = matrix_multiply(currentFrame.camera.transform, translation)
Also, I notice that matrix_identity_float4x4 contains 4 columns but the documentation is not available.
Why 4 columns? Are there the frame of the plane?
The simplest way to do it is to use the following code for positioning:
let planeNode = SCNNode()
planeNode.geometry = SCNPlane(width: 20, height: 20)
// At first we need to rotate a plane about its x axis in radians:
planeNode.rotation = SCNVector4(1, 0, 0, -Double.pi/2)
planeNode.geometry?.materials.first?.diffuse.contents = UIColor.red
planeNode.position.x = 10
planeNode.position.z = 10
// planeNode.position = SCNVector3(x: 10, y: 0, z: 10)
scene.rootNode.addChildNode(planeNode)
or this way:
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
scene.rootNode.addChildNode(cameraNode)
let planeNode = SCNNode()
planeNode.geometry = SCNPlane(width: 20, height: 20)
planeNode.rotation = SCNVector4(1, 0, 0, -Double.pi/2)
planeNode.geometry?.materials.first?.diffuse.contents = UIColor.red
let distance: Float = 50
planeNode.simdPosition = cameraNode.simdWorldFront * distance // -Z axis
planeNode.simdPosition = cameraNode.simdWorldRight * distance // +X axis
scene.rootNode.addChildNode(planeNode)
If you wanna know more about matrices used in ARKit and SceneKit frameworks just look at Figure 1-8 Matrix configurations for common transformations.
Hope this helps.

How to connect two SCNSpheres in 3D space using Bezier path in Swift?

I have the following code:
func createScene(){
count += 1
let sphereGeom = SCNSphere(radius: 1.5)
sphereGeom.firstMaterial?.diffuse.contents = UIColor.redColor()
let path = UIBezierPath()
path.moveToPoint(CGPoint(x: 0, y: 0))
let radius = 3.0
var radians = Double(0)
var yPosition = Float(5.4)
while count <= 20 {
if radians >= 2{
radians -= 2
}
let sphereNode = SCNNode(geometry: sphereGeom)
let angle = Double(radians * M_PI)
let xPosition = Float(radius * cos(angle))
let zPosition = Float(radius * sin(angle))
sphereNode.position = SCNVector3(xPosition, yPosition, zPosition)
let cgX = CGFloat(xPosition)
let cgY = CGFloat(yPosition)
path.addQuadCurveToPoint(CGPoint(x: cgX, y: cgY), controlPoint: CGPoint(x: (cgX / 2), y: (cgY / 2)))
path.addLineToPoint(CGPoint(x: (cgX - (cgX * 0.01)), y: cgY))
path.addQuadCurveToPoint(CGPoint(x: 1, y: 0), controlPoint: CGPoint(x: (cgX / 2), y: ((cgY / 2) - (cgY * 0.01))))
let shape = SCNShape(path: path, extrusionDepth: 3.0)
shape.firstMaterial?.diffuse.contents = UIColor.blueColor()
let shapeNode = SCNNode(geometry: shape)
shapeNode.eulerAngles.y = Float(-M_PI_4)
self.rootNode.addChildNode(shapeNode)
count += 1
radians += 0.5556
yPosition -= 1.35
self.rootNode.addChildNode(sphereNode)
}
I want to add a Bezier path connecting each sphere to the next one, creating a spiral going down the helix. For some reason, when I add this code, the shape doesn't even appear. But when I use larger x and y values, I see the path fine, but it is no way oriented to the size of the spheres. I don't understand why it disappears when I try to make it smaller.
Your SCNShape doesn't ever get extruded. Per Apple doc,
An extrusion depth of zero creates a flat, one-sided shape.
With larger X/Y values your flat shape happens to become visible. You can't build a 3D helix with SCNShape, though: the start and end planes of the extrusion are parallel.
You'll have to use custom geometry, or approximate your helix with a series of elongated SCNBox nodes. And I bet someone out there knows how to do this with a shader.