iOS RealityKit. Changing Entity's translation causes unexpected behaviour - swift

I am trying to create some AR experience.
I load the Model with animations as an Entity. Lets call it a Toy.
I create an AnchorEntity.
I attach the Toy to the AnchorEntity. Up to this point everything works great.
I want the Toy to walk in random directions. And it does for the first time. Then it gets interesting, allow me to share my code:
First method uses a newly created Transform for the Toy with the modified translation x, y, to make the Toy move and that is it.
func walk(completion: #escaping () -> Void) {
guard let robot = robot else {
return
}
let currentTransform = robot.transform
guard let path = randomPath(from: currentTransform) else {
return
}
let (newTranslation , travelTime) = path
let newTransform = Transform(scale: currentTransform.scale,
rotation: currentTransform.rotation,
translation: newTranslation)
robot.move(to: newTransform, relativeTo: nil, duration: travelTime)
DispatchQueue.main.asyncAfter(deadline: .now() + travelTime + 1) {
completion()
}
}
We get that new Transform from the method below.
func randomPath(from currentTransform: Transform) -> (SIMD3<Float>, TimeInterval)? {
// Get the robot's current transform and translation
let robotTranslation = currentTransform.translation
// Generate random distances for a model to cross, relative to origin
let randomXTranslation = Float.random(in: 0.1...0.4) * [-1.0,1.0].randomElement()!
let randomZTranslation = Float.random(in: 0.1...0.4) * [-1.0,1.0].randomElement()!
// Create a translation relative to the current transform
let relativeXTranslation = robotTranslation.x + randomXTranslation
let relativeZTranslation = robotTranslation.z + randomZTranslation
// Find a path
var path = (randomXTranslation * randomXTranslation + randomZTranslation * randomZTranslation).squareRoot()
// Path only positive
if path < 0 { path = -path }
// Calculate the time of walking based on the distance and default speed
let timeOfWalking: Float = path / settings.robotSpeed
// Based on old trasnlation calculate the new one
let newTranslation: SIMD3<Float> = [relativeXTranslation,
Float(0),
relativeZTranslation]
return (newTranslation, TimeInterval(timeOfWalking))
}
The problem is that the value of Entity.transform.translation.y grows from 0 to some random value < 1. Always after the second time the walk() method is being called.
As you can see, every time the method is called, newTranslation sets the Y value to be 0. And yet the Toy's translation:
I am out of ideas any help is appreciated. I can share the whole code if needed.

I have managed to fix the issue by specifying parameter relativeTo as Toy's AnchorEntity:
toy.move(to: newTransform, relativeTo: anchorEntity, duration: travelTime)

Related

LiDAR – ARMeshAnchor to SCNNode, get some manipulation with it

I've run this Git. What I'm trying to do is when the node is further than the distance I set, for example 1 meter, node is removed. With in it, I've added this method (Code 1).
Code 1
func getDistanceBtw(cameraPostion: SCNVector3, nodePosition: SCNVector3) -> Float {
let powX = (cameraPostion.x - nodePosition.x) * (cameraPostion.x - nodePosition.x)
let powY = (cameraPostion.y - nodePosition.y) * (cameraPostion.y - nodePosition.y)
let powZ = (cameraPostion.z - nodePosition.z) * (cameraPostion.z - nodePosition.z)
let powXYZ = powX + powY + powZ
let distance = sqrt(powXYZ)
return distance
}
And in renderer didUpdate Node method, I added... (Code 2)
Code 2
let cameraPostion = sceneView.pointOfView?.position
let nodePosition = node.position
if getDistanceBtw(cameraPostion: cameraPostion!,
nodePosition: nodePosition) > 1 {
node.removeFromParentNode()
}
I thought this my solve what I want to achieve, it did something but not what I wanted.

How to get a vertex position in ARKit's reconstructed mesh?

Recently, I make it about lidar scan project.
It is very difficult. And I need to manipulate vertex data.
So I tried by this code
guard let meshAnchors = arView.session.currentFrame?.anchors.compactMap { $0 as? ARMeshAnchor }
else { return }
meshAnchors.first?.geometry.vertices // I want to get vertex position
There is no position of vertex, only buffer data
How can I do that? is it change from buffer data to array?
Plz help me.
Just went through this myself so I figured I'd drop ya my solution.
First grab this extension from Apple's Documentation to get a vertex at a specific index:
extension ARMeshGeometry {
func vertex(at index: UInt32) -> SIMD3<Float> {
assert(vertices.format == MTLVertexFormat.float3, "Expected three floats (twelve bytes) per vertex.")
let vertexPointer = vertices.buffer.contents().advanced(by: vertices.offset + (vertices.stride * Int(index)))
let vertex = vertexPointer.assumingMemoryBound(to: SIMD3<Float>.self).pointee
return vertex
}
}
Then, to get the positions in ARKit world space, you can do something like this:
func getVertexWorldPositions(frame: ARFrame) {
let anchors = frame.anchors.filter { $0 is ARMeshAnchor } as! [ARMeshAnchor]
// Each mesh geometry lives in its own anchor
for anchor in anchors {
// Anchor's transform in world space
let aTrans = SCNMatrix4(anchor.transform)
let meshGeometry = anchor.geometry
let vertices: ARGeometrySource = meshGeometry.vertices
for vIndex in 0..<vertices.count {
// This will give you a vertex in local (anchor) space
let vertex = meshGeometry.vertex(at: UInt32(vIndex))
// Create a new matrix with the vertex coordinates
let vTrans = SCNMatrix4MakeTranslation(vertex[0], vertex[1], vertex[2])
// Multiply it by the anchors's transform to get it into world space
let wTrans = SCNMatrix4Mult(vTrans, aTrans)
// Use the coordinates for something!
let vPos = SCNVector3(wTrans.m41, wTrans.m42, wTrans.m43)
print(vPos)
}
}
}

Following a path in Spritekit EXC_BAD_ACCESS

I have a simple Snake game where the head draws a UIBezier path. That part works fine:
func addLineToSnakePath(snakeHead: SnakeBodyUnit) {
//add a new CGPoint to array
activeSnakePathPoints.append(CGPoint(x: snakeHead.partX, y: snakeHead.partY))
let index = activeSnakePathPoints.count-1
if (index == 1) {
path.moveToPoint(activeSnakePathPoints[index-1])
}
path.addLineToPoint(activeSnakePathPoints[index])
shapeNode.path = path.CGPath
}
The path generates with swipes as the Head moves around the screen. Now I add a body unit to follow the UIBezier path and I get a bad access error.
func addBodyPart() {
let followBody = SKAction.followPath(path.CGPath, asOffset: true, orientToPath: false, duration: 1.0)
snakePart.runAction(followBody)
}
Crash at:
0 SKCFollowPath::cpp_willStartWithTargetAtTime(SKCNode*, double)
Thread 1 EXC_BAD_ACCESS
Looking at the code I would rather strengthen my code this way:
func addLineToSnakePath(snakeHead: CGPoint) {
let count = activeSnakePathPoints.count
if count == 0 { return } // The snake don't have an head..
//add a new CGPoint to array
activeSnakePathPoints.append(CGPoint(x: snakeHead.x, y: snakeHead.y))
guard count > 1 else {
// There are only two element (the head point and the new point) so:
path = UIBezierPath.init()
path.moveToPoint(activeSnakePathPoints[count-1])
shapeNode.path = path.CGPath
return
}
// There are some elements:
path.addLineToPoint(activeSnakePathPoints[count-1])
shapeNode.path = path.CGPath
}
Next, when you make the followPath action I've seen you use offset value to true ( I don't know if this is wanted from you..):
asOffset : If YES, the points in the path are relative offsets to the
node’s starting position. If NO, the points in the node are absolute
coordinate values.
In other words true means the path is relative from the sprite's starting position, false means absolute
Finally in the line:
snakePart.runAction(followBody)
I don't know what is snakePart but you use shapeNode in your function
Update:
Thinking about your crash, seems snakePart is not a valid SKNode (like when you try to use a sprite or shape without initialize itself)

Closure CallBack Only Retain Last Object Created in Swift

i'm not sure if the title fit my question but my question is as below.
First of all, i will talk how i encounter this problem. Basically, I'm creating a game(you can just imagine) which has A MainPlayer and Many Of Enemies(AI). So, the MainPlayer will move around and enemies will chase after him. As you can imagine, the MainPlayer's position will update in every frame(maybe) and i need also to update enemies' chasing position(which is main character's position). So i'm using Closure to do it. The reason why i'm not using array to store all the enemies and update it every frame is because the enemies can be killed and will random spawn new enemies in the amount of time i set. If i use array it is kind of tricky and unsafe.
So, back to my question, i created this beginning of GameScene class:
typealias CallBacks = () -> Void
var playerDidMoveCallBacks: CallBacks?
This is my create enemy class: (The callback is at most bottom)
// Create Enemies
func createEnemyAtPosition(position: CGPoint) {
let enemyNode = EnemyNode()
enemyNode.name = "ENEMY_NODE"
enemyNode.position = position
enemyNode.setScale(1.5)
addChild(enemyNode)
let sprite = SKSpriteNode(imageNamed: "enemyWisp")
enemyNode.addChild(sprite)
enemyNode.physicsBody = SKPhysicsBody(circleOfRadius: sprite.size.width / 2)
enemyNode.physicsBody?.dynamic = true
enemyNode.physicsBody?.categoryBitMask = CollisionCategoryBitMask.Enemy
enemyNode.physicsBody?.collisionBitMask = 0
enemyNode.physicsBody?.contactTestBitMask = CollisionCategoryBitMask.ThrowingKnife | CollisionCategoryBitMask.Player
enemyNode.physicsBody?.usesPreciseCollisionDetection = true
let distanceWillCollideWithPlayer = sqrt(pow(enemyNode.position.x - self.playerNode.position.x, 2) + pow(enemyNode.position.y - self.playerNode.position.y, 2))
let durationWillColldeWithPlayer = NSTimeInterval(distanceWillCollideWithPlayer / self.enemyMovingSpeed)
let enemyMoveAction = SKAction.moveTo(self.playerNode.position, duration: durationWillColldeWithPlayer)
enemyNode.runAction(enemyMoveAction)
println("\((unsafeAddressOf(enemyNode)))")
// Update Player Position
playerDidMoveCallBacks = {() -> Void in
let distanceWillCollideWithPlayer = sqrt(pow(enemyNode.position.x - self.playerNode.position.x, 2) + pow(enemyNode.position.y - self.playerNode.position.y, 2))
let durationWillColldeWithPlayer = NSTimeInterval(distanceWillCollideWithPlayer / self.enemyMovingSpeed)
println("\((unsafeAddressOf(enemyNode)))")
let enemyMoveAction = SKAction.moveTo(self.playerNode.position, duration: durationWillColldeWithPlayer)
enemyNode.runAction(enemyMoveAction)
}
}
When i know my hero changed position i call the callback like this:
if self.playerDidMoveCallBacks != nil {
self.playerDidMoveCallBacks!()
}
But this can only work for the last object created and i think this make sense. Anyone can give me a solution?
What you want is an array of callbacks.
var playerDidMoveCallBacks: [CallBack]?
As every enemy is captured in the closure you could just iterate the array and call each callback.
for callback in playerDidMoveCallBacks {
callback()
}
But the solution of 0x141E is better.
The problem is that the enemy could not exist anymore when you call the closure, but the closure captures the object which can lead to strange behaviour. (ghost enemies)
With help from 0x141E i create this line of code in func update and solve my problem
// Update Enemies Position
for childNode in children {
let childSprite = childNode as? SKNode
if (childSprite?.name == "ENEMY_NODE") {
let distanceWillCollideWithPlayer = sqrt(pow(CGFloat(childSprite!.position.x) - self.playerNode.position.x, 2) + pow(CGFloat(childSprite!.position.y) - self.playerNode.position.y, 2))
let durationWillColldeWithPlayer = NSTimeInterval(distanceWillCollideWithPlayer / self.enemyMovingSpeed)
let enemyMoveAction = SKAction.moveTo(self.playerNode.position, duration: durationWillColldeWithPlayer)
childSprite!.runAction(enemyMoveAction)
}
}

GameplayKit Pathfinding with obstacles and agents

I've been searching for days about this new framework and trying to make usage of some of it's funcionalities,
but... there're some things that are not fitting together for me, the demobot source code isn't helping at some points,
I miss some kind of simple tutorial but here goes my main doubts:
let obstacles = scene["obstacle"]
polygonObstacles = SKNode.obstaclesFromNodePhysicsBodies(obstacles)
graph = GKObstacleGraph(obstacles: polygonObstacles, bufferRadius: 60.0)
func drawGraph() {
for node in graph.nodes as! [GKGraphNode2D] {
for destination in node.connectedNodes as! [GKGraphNode2D] {
let points = [CGPoint(node.position), CGPoint(destination.position)]
let shapeNode = SKShapeNode(points: UnsafeMutablePointer<CGPoint>(points), count: 2)
shapeNode.strokeColor = SKColor(white: 1.0, alpha: 0.5)
shapeNode.lineWidth = 5.0
shapeNode.zPosition = 3
scene.addChild(shapeNode)
}
}
}
So, when I try to draw this graph and see the connections, I get this: http://i.imgur.com/EZ3dx5v.jpg
I find it really weird, anywhere I put my obstacles, even in low numbers, the left-corner portion of the screen have always more connections(the radius don't have influence on that)
I don't use GKComponents on my game, but I tried to run some GKAgents2D to hunt the player, like this:
func calculateBehaviorForAgents(){
let mainCharacterPosition = float2(scene.mainCharacter.position)
let mainCharacterGraphNode = GKGraphNode2D(point: mainCharacterPosition)
graph.connectNodeUsingObstacles(mainCharacterGraphNode)
for i in 0...monsters.count-1{
let monster = monsters[i]
let agent = agents[i]
let behavior = GKBehavior()
let monsterPosition = float2(monster.position)
let monsterGraphNode = GKGraphNode2D(point: monsterPosition)
graph.connectNodeUsingObstacles(monsterGraphNode)
let pathNodes = graph.findPathFromNode(monsterGraphNode, toNode: mainCharacterGraphNode) as! [GKGraphNode2D]
let path = GKPath(graphNodes: pathNodes, radius: 00.0)
let followPathGoal = GKGoal(toFollowPath: path, maxPredictionTime: 1.0, forward: true)
behavior.setWeight(1.0, forGoal: followPathGoal)
let stayOnPathGoal = GKGoal(toStayOnPath: path, maxPredictionTime: 1.0)
behavior.setWeight(1.0, forGoal: stayOnPathGoal)
agent.behavior = behavior
graph.removeNodes([monsterGraphNode])
}
graph.removeNodes([mainCharacterGraphNode])
}
Now when I call the updateWithDeltaTime method, his delegate methods:
func agentWillUpdate(agent: GKAgent){}
func agentDidUpdate(agent: GKAgent){}
give me unexpected values for the agents, it's position doesn't make any sense, with giant numbers that leads to outside of the battlefield
But I saw that the his velocity vector were making sense, so I matched it to my monster and updated the agent to the monster's position
func updateWithDeltaTime(currentTime : CFTimeInterval){
for i in 0...monsters.count-1{
let agent = agents[i]
let monster = monsters[i]
monster.physicsBody?.velocity = CGVectorMake(CGFloat(agent.velocity.x), CGFloat(agent.velocity.y))
agent.updateWithDeltaTime(currentTime)
agent.position = float2(monster.position)
monster.gameSceneUpdate(currentTime)
}
Now I was getting some results, but it's far away from what I want:
The monsters are not following the character to the edges or the right-top portion of screen, I remove their points from the graph but after make a path for them to follow (the image doesn't have this points, but they exists).
Apparently because there was no path leading to there, remember the image?
The question is: how to make this agent system to work?
Maybe I'm totally wrong at the usage of agents, goals and even the graphs! I read the documentation but I still can't make it right
And more...
At first, the monster were not avoid obstacles, even with GKGoals like "avoidObstacles", passing the same PolygonObstacles,
but when I change
graph.connectNodeUsingObstacles(mainCharacterGraphNode)
graph.connectNodeUsingObstacles(monsterGraphNode)
to
graph.connectNodeUsingObstacles(mainCharacterGraphNode, ignoringObstacles: polygonObstacles)
graph.connectNodeUsingObstacles(monsterGraphNode, ignoringObstacles: polygonObstacles)
it worked! o.O
I really need some help, thank you all :D!