iOS spriteKit child nodes position with respect to view coordinates - ios

I have a child node added to another node. I want to get the child nodes position with respect to the views coordinates and not the parent nodes coordinates

Get the child node's position with respect to its parent, then use the convertPoint:fromNode: or convertPoint:toNode: method to convert from the parent node's coordinate system to the scene's coordinate system. (Remember, SKScene inherits from SKNode and is the root of the node hierarchy, so you can use it with either of those methods.) If you then need to work in the UIKit view coordinate space, use the scene's convertPointToView: method.

If by 'view coordinates' you mean the scene coordinates, then check out this swift solution I use:
extension SKNode {
var positionInScene:CGPoint? {
if let scene = scene, let parent = parent {
return parent.convert(position, to:scene)
} else {
return nil
}
}
}
Then you can get the scene position in the same manner you get the regular position. Here is an example:
let positionInParent = childNode.position
let positionInScene = childNode.positionInScene? //optional return type
Note that the positionInScene property is optional for the same reason that the scene property of SKNode is optional, the node might not be added to a scene. In this case, you get nil. You could even reverse the process and add a setter, so you could position every node in the scene coordinates no matter how deeply it was buried.

Related

How to add SCNNode as a child to parent without changing relative position, size and scale in world coordinates

I have two SCNNodes, nodeCh and nodePrnt. Their world coordinates are available and their relative position/size/angle should remain the same.
I want to add nodeCh to the nodePrnt as a child, by using - (void)addChildNode:(SCNNode) method. In order to do that I should translate the world coordinates/size/angles of nodeCh in terms of relative position/scale/rotation of nodePrnt so that the relative positions/scale/sizes won't change comparing to the case when both nodes are added to scene.rootNode as children.
Clearly, I have to use some combination of transform operators. Currently I am using the following lines which work incorrectly:
SCNMatrix4 transform
= [self.nodePrnt convertTransform:SCNMatrix4Identity
fromNode:nil];
nodeChld.transform = transform;
Conversion utils are indeed what you'll need to use.
In your case:
nodeCh.transform = [nodePrnt convertTransform:SCNMatrix4Identity fromNode:nodeCh];
or
nodeCh.transform = [nodeCh convertTransform:SCNMatrix4Identity toNode:nodePrnt];
Here's a quick helper method inspired by SpriteKit.
Swift 5.2
extension SCNNode {
func move(toParent parent: SCNNode) {
let convertedTransform = convertTransform(SCNMatrix4Identity, to: parent)
removeFromParentNode()
transform = convertedTransform
parent.addChildNode(self)
}
}

Designing complex objects with SpriteKit Scene Editor

Near as I can tell, there isn’t a way to add physics joints in the scene editor. Is that right?
Consider a simple person object with a body, child legs and arms w/ pin joints. If I want to design this person in the scene editor and then programmatically add him to a scene, I’m not getting very far. I’m able to find the nodes in the scene, remove them from their parent and add them as a child at a new position in my scene, but I still have to specify all their joints manually.
Thoughts?
Here is my solution. I'm still hoping there is a way to create physics joints with the scene editor but I haven't found it so...
Step 1) Add all the child nodes to the scene, ensuring that objects are grouped by parent.
Step 2) Define a swift class for your complex node.
class MyNode : SKSpriteNode {
func spawn(parentNode: SKNode, position: CGPoint) {
parentNode.addChild(self) // before physics joints
let arm = self.childNode(withName:"arm")
// note if you didn't add physics bodies in scene file
// do that first
let shoulders = SKPhysicsJointPin.joint(withBodyA:self.physicsBody!, bodyB: arm.physicsBody!, anchor: CGPoint(x:position.x,y:position.y-1))
scene!.physicsWorld.add(shoulders)
// feature of pulling a child from a scene, it's always paused by default.
self.isPaused = false
}
}
Set the class for your body node in your scene.
Step 3) Transfer your node to your game scene at init time.
let tmpScene = SKScene.init(fileNamed: "MyNode.sks")
var myNode = tmpScene.childNamed(withName:"myNode") as! MyNode
myNode.removeFromParent()
myNode.spawn(world, position) // or your own parent and position as needed for your scene

ARKit: How to place one imported 3D model above another?

I am working on a AR project using ARKit.
If I touch only the imported 3D object on a point, I want to place another 3D object above it.
(For example I have placed a table above which I have to place something else like a flower vase on the touched point).
How can I solve the problem that the second object should only be placed, when I touch the first 3D object?
The surface of the object is not flat, so I can not use hittest with bounding box.
One approach is to give the first imported 3D object a node name.
firstNode.name = “firstObject”
Inside you tapped gesture function you can do a hitTest like this
let tappedNode = self.sceneView.hitTest(location, options: [:])
let node = tappedNode[0].node
if node.name == “firstObject” {
let height = firstNode.boundingBox.max.y -firstNode.boundingBox.min.y
let position2ndNode = SCNVector3Make(firstNode.worldPosition.x, (firstNode.worldPosition.y + height), firstNode.worldPosition.z)
2ndNode.position = position2ndNode
sceneView.scene.rootNode.addChildNode(2ndNode)
} else {
return
}
This way when you tap anywhere else the 2nd object won’t get placed. It will only place when you tap on the node itself. It doesn’t matter where you tap on the node, because we only want the height & we can determine that from its boundingBox max - min which we then add to the firstnode.worldPosition.y
Make sure you set at the top of ARSCNView class
var firstNode = SCNNode!
this way we can access the firstNode in the tap gesture function.
Edit: If the first 3D model has many nodes. You can flattenNode on the parent Node in the sceneGraph (best illustrated with photo below). This removes all the childNodes and wraps from the sceneGraph. You can then just work with the parentNode.

How to dynamically create annotations for 3D object using scenekit - ARKit in iOS 11?

I am working on creating annotations using overlaySKScene something similar to this(https://sketchfab.com/models/1144d7be20434e8387a2f0e311eca9b1#). I followed https://github.com/halmueller/ImmersiveInterfaces/tree/master/Tracking%20Overlay to create the overlay.
But in the provided example, they are creating only one annotation and it is static. I want to create multiple annotations dynamically based on the number of child nodes we have and also should be able to position annotation on top of respective child node. How to achieve this?
I am adding overlay like below,
sceneView.overlaySKScene = InformationOverlayScene(size: sceneView.frame.size)
where InformationOverlayScene is the SKScene in which i have added two childnodes to create one annotation.
Create an array with the annotation sprites that is mapped to the childnodes array, and then do something like the following:
func renderer(_ aRenderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
let scnView = self.view as! SCNView
//for each character
for var i in 0...inBattleChars.count-1 {
let healthbarpos = scnView.projectPoint(inBattleChars[i].position)
battleSKO.healthbars[i].position = CGPoint(x: CGFloat(healthbarpos.x), y: (scnView.bounds.size.height-10)-CGFloat(healthbarpos.y))
}
}
Before every frame is rendered this updates the position of an SKSprite (in healthBars) for each SCNNode in inBattleChars. The key part is where projectPoint is used to get the SK overlay scene's 2D position based on the SCNNode in the 3D scene.
To prevent the annotations of non-visible nodes from showing up (such as childnodes on the back side of the parent object) use the SCNRenderer’s nodesInsideFrustum(of:) method.
You can add a SKScene or a CALayer as a material property.
You could create a SCNPlane with a specific width and height and add a SpriteKit scene as the material.
You can find an example here.
Then you just position the plane where you want it to be and create and delete the annotations as you need them.

How do I maintain child SKNode velocities when moving the parent?

I'm working on a game in Swift with SpriteKit. I have a parent node with an SKPhysicsBody and several subnodes with their own physics bodies. All of the subnodes have some velocity relative to the parent node. How do I keep that relative velocity when giving the parent node a velocity? Here's pseudocode to show what I'm trying to do:
let parent:SKNode = SKNode()
let child:SKNode = SKNode()
parent.physicsBody = SKPhysicsBody()
child.physicsBody = SKPhysicsBody()
parent.addChild(child)
child.physicsBody!.velocity = CGVectorMake(dx: 5, dy: 5)
// child now has velocity
parent.physicsBody!.velocity = CGVectorMake(dx: 10, dy:10)
// parent has velocity, but child velocity is still (5,5)
How would I get the child velocity to be set such that it maintains a velocity relative to the parent? (e.g. the child's absolute velocity should become (15,15) once the parent is given a velocity of (10,10) so that it maintains (5,5) relative to the parent).
I tried using SKPhysicsJoint but that seems to fix the node and not allow velocity. Any solutions? (I'm sure I'm overlooking something obvious, but I'm new to SpriteKit)
Thanks!
If you want the child's velocity to be relative to its parent, add the parent's velocity to the child's constant velocity in the update method. For example,
override func update(currentTime: CFTimeInterval) {
child.physicsBody?.velocity = CGVectorMake(parentNode.physicsBody!.velocity.dx + 5.0, parentNode.physicsBody!.velocity.dy + 5.0)
}
BTW, you shouldn't use parent as a variable name in Sprite Kit, since it's a property of SKNode.
Update
You can loop over all children of the parent node with the following.
override func update(currentTime: CFTimeInterval) {
for (i, child) in parentNode.children.enumerate() {
child.physicsBody?.velocity = CGVectorMake(parentNode.physicsBody!.velocity.dx + velocity[i].dx, parentNode.physicsBody!.velocity.dy + velocity[i].dy)
}
}
velocity is an array of CGVectors that contains the velocity of each child.

Resources