SceneKit physics add velocity in local space - swift

I am trying to manipulate the player node using physicsBody.velocity by adding or subtracting velocity to the axis for different directions. The problem is that I'm having trouble finding the method for applying that to local space or at least applying the velocity in relation to the direction the object is facing. In other words, it works fine if I have not rotated the object's node. If I do it will still add the velocity to the unrotated space.
I know there is a way to add velocity to the current SCNVector3, but I cannot figure it out.
if isZThrustPositive {
if let velocity = self.physicsBody?.velocity {
if velocity.z * 100 <= 8000 {
thrustDirection = SCNVector3(
x: velocity.x,
y: velocity.y,
z: velocity.z + kPlayerShipMainThrust)
self.physicsBody?.velocity = thrustDirection
}
}
}
I am also trying to rotate the node using angularVelocity in a similar fashion. That works fine as long as the node has not moved or rotated. If it has, it seems to be using world space as well.
if isYRotatingPositive {
if let angularVel = self.physicsBody?.angularVelocity {
self.physicsBody?.angularVelocity = SCNVector4(
x: angularVel.x,
y: angularVel.y + kPlayerShipRotationSpeed,
z: angularVel.z,
w: angularVel.w + kPlayerShipRotationMagnitude)
}
}
After the physics are simulated, I am updating the node's position and rotation.
I have also tried convertPosition, but could not figure out a way to make this work. (I got some really crazy results with this).
Any help is much appreciated.
UPDATE I got the playerShip object to add velocity in the proper directions when it is rotated, but I am still unable to perform multiple rotations without it not turning in the correct direction.
For the ship velocity I did this:
if isXThrustPositive {
thrustDirection = self.convertPosition(SCNVector3(x: kPlayerShipMainThrust, y: 0.0, z: 0.0), toNode: self.parentNode!)
thrustDirection.x = thrustDirection.x - self.position.x
thrustDirection.y = thrustDirection.y - self.position.y
thrustDirection.z = thrustDirection.z - self.position.z
self.physicsBody?.applyForce(thrustDirection, impulse: true)
}
When trying to use a similar method for rotation (I know it would be doomed), the resulting SCNVector3 is just a position, not the direction the node is currently facing. Is there any information on convertTransform and how I might use that?

As it turns out, I had to get the proper position from the rootNode of the scene to perform a proper rotation based on the current orientation of the SCNNode.
xAxis = self.convertPosition(SCNVector3Make(1.0, 0.0, 0.0), toNode: self.parentNode!)
yAxis = self.convertPosition(SCNVector3Make(0.0, 1.0, 0.0), toNode: self.parentNode!)
zAxis = self.convertPosition(SCNVector3Make(0.0, 0.0, 1.0), toNode: self.parentNode!)
if isXRotatingPositive {
self.physicsBody?.applyTorque(
SCNVector4(
x: sin(kPlayerShipRotationSpeed/2.0) * (xAxis.x - self.position.x),
y: sin(kPlayerShipRotationSpeed/2.0) * (xAxis.y - self.position.y),
z: sin(kPlayerShipRotationSpeed/2.0) * (xAxis.z - self.position.z),
w: cos(kPlayerShipRotationSpeed/2.0) * kPlayerShipRotationMagnitude),
impulse: true)
}
Then I just used the standard quaternion rotation formula to get the rotation based on the new axes from the current position.
I hope this helps someone else (and that more information on SceneKit is forthcoming)
If any SceneKit experts want to comment on this or offer suggestions, they are much appreciated. :)

Related

Gravity value in SpriteKit game scene

I'm trying to create a game using Apple's SpriteKit game engine.
While implementing some physics-based calculations in the game, I noticed that the calculated results differ from what effectively then happens to objects.
Example: calculating a body's trajectory through projectile motion's equations causes the body to actually fall down much sooner/quicker than what calculated.
How can I make the physics engine match the real-world physics laws when calculating something gravity-related?
I think I know what's going on with the sample code you have supplied on GitHub, which I'll reproduce here as questions on SO should contain the code:
//
// GameScene.swift
// SpriteKitGravitySample
//
// Created by Emilio Schepis on 17/01/2020.
// Copyright © 2020 Emilio Schepis. All rights reserved.
//
import SpriteKit
import GameplayKit
class GameScene: SKScene {
private var subject: SKNode!
override func didMove(to view: SKView) {
super.didMove(to: view)
// World setup (no friction, default gravity)
// Note that this would work with any gravity set to the scene.
physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
physicsBody?.friction = 0
subject = SKShapeNode(circleOfRadius: 10)
subject.position = CGPoint(x: frame.midX, y: 30)
subject.physicsBody = SKPhysicsBody(circleOfRadius: 10)
subject.physicsBody?.allowsRotation = false
// Free falling body (no damping)
subject.physicsBody?.linearDamping = 0
subject.physicsBody?.angularDamping = 0
addChild(subject)
// Set an arbitrary velocity to the body
subject.physicsBody?.velocity = CGVector(dx: 30, dy: 700)
// Inaccurate prediction of position over time
for time in stride(from: CGFloat(0), to: 1, by: 0.01) {
let inaccuratePosition = SKShapeNode(circleOfRadius: 2)
inaccuratePosition.strokeColor = .red
// These lines use the projectile motion equations as-is.
// https://en.wikipedia.org/wiki/Projectile_motion#Displacement
let v = subject.physicsBody?.velocity ?? .zero
let x = v.dx * time
let y = v.dy * time + 0.5 * physicsWorld.gravity.dy * pow(time, 2)
inaccuratePosition.position = CGPoint(x: x + subject.position.x,
y: y + subject.position.y)
addChild(inaccuratePosition)
}
// Actual prediction of position over time
for time in stride(from: CGFloat(0), to: 1, by: 0.01) {
let accuratePosition = SKShapeNode(circleOfRadius: 2)
accuratePosition.strokeColor = .green
// These lines use the projectile motion equations
// as if the gravity was 150 times stronger.
// The subject follows this curve perfectly.
let v = subject.physicsBody?.velocity ?? .zero
let x = v.dx * time
let y = v.dy * time + 0.5 * physicsWorld.gravity.dy * 150 * pow(time, 2)
accuratePosition.position = CGPoint(x: x + subject.position.x,
y: y + subject.position.y)
addChild(accuratePosition)
}
}
}
What you've done is to:
Created an object called subject with a physicsBody and placed it
on screen with a initial velocity.
Plotted predicted positions for an object with that velocity under
gravity via the inaccuratePosition node, using Newton's laws of
motion (v = ut + 1/2at²)
Plotted predicted positions for an object with that velocity under
gravity * 150 via the accuratePosition node, using Newton's laws of
motion
All this is is didMoveTo. When the simulation runs, the path of the node subject follows the accuratePosition path accurately.
I think what's happening is that you are calculating the predicted position using subject's physicsBody's velocity, which is in m/s, but the position is in points, so what you should do is convert m/s into point/s first.
So what's the scale factor? Well from Apple's documentation here; it's.... 150 which is too much of a coincidence 😀, so I think that's the problem.
Bear in mind that you set the vertical velocity of your object to 700m/s - that's 1500mph or 105000 SK point/s. You'd expect it to simply disappear out through the top of the screen at high speed, as predicted by your red path. The screen is somewhere between 1,000 and 2,000 points.
Edit - I created a sample project to demonstrate the calculated paths with and without the multiplier.
https://github.com/emilioschepis/spritekit-gravity-sample
TL;DR - When calculating something gravity-related in SpriteKit multiply the gravity of the scene by 151 to obtain an accurate result.
When trying to solve this issue I first started reading the SpriteKit documentation related to gravity:
https://developer.apple.com/documentation/spritekit/skphysicsworld/1449623-gravity
The documentation says:
The components of this property are measured in meters per second. The default value is (0.0,-9.8), which represent’s Earth’s gravity.
Gravity, however is calculated in m/s^2 and not in m/s.
Thinking that was an error in the implementation of gravity in SpriteKit I began thinking that maybe real-world-based physics laws could not be applied in the scene.
I did, however, come across another documentation page about the linear gravity field that correctly reported that gravity is measured in m/s^2 in SpriteKit.
https://developer.apple.com/documentation/spritekit/skfieldnode/1520145-lineargravityfield
I then setup a simple free falling scene where I applied an initial velocity to a physics body and then calculated the expected trajectory, while comparing it to the actual trajectory.
The x-axis calculations were accurate from the start, suggesting that the only problem was with the gravity's value.
I then tried manually modified the gravity in the scene until the actual trajectory matched the predicted one.
What I found is that there is a "magic" value of ~151 that has to be factored in when using the physics world's gravity property in the game.
Modifying, for example, the y-axis calculations for the trajectory from
let dy = velocity.dy * time + 0.5 * gravity * pow(time, 2)
to
let dy = velocity.dy * time + 0.5 * 151 * gravity * pow(time, 2)
resulted in accurate calculations.
I hope this is useful to anyone who might encounter the same problem in the future.

how to get the force being applied on an object in SpriteKit (Swift)

So I am kinda new to SpriteKit and I am wondering how to get the amount of force on an object and put it into a variable. When you put force on an object, for example, this code,
line.physicsBody?.applyImpulse(CGVector(dx: 0, dy: -30))
it asks you how much in each direction. Well, I want to do the opposite. I want to get the amount of force that the object has and what direction.
An impulse a force that lasts for a single instant. The object can only applies a force on another physics body when it collides relative to its velocity and mass.
If your looking to find your objects current avg speed, you use
sprite.physicsbody?.velocity
or you can check if the object is moving with
physicsbody?.isResting
if you want to calculate the average force being applied on an object between frames you would have to calculate the magnitude of the change in position between the last update cycle and then multiply by the objects mass like so.
var lastPosition: CGPoint?
var avgForce: CGPoint?
func updatedForce(for sprite: SKSprite, timeDelta: Double) {
guard let lastPosition = lastPosition, let currentVelocity = currentVelocity) else {
lastPosition = sprite.position
}
let position = sprite.position
let mass = sprite.physicsbody.mass
var changeInX = position.x - lastPosition.x
var changeInY = position.y - lastPosition.y
avgForce = CGPoint(x: changeInX * mass, y: changeInY * mass)
}`
I apologize for any formatting issues or incorrect spellings, made this from my iPhone and will correct later.

How to move a rotated SCNNode in SceneKit, on its "own" axis?

The image below shows a rotated box that should be moved horizontally on the X and Z axes. Y should stay unaffected to simplify the scenario. The box could also be the SCNNode of the camera, so I guess a projection does not make sense at this point.
So lets say we want to move the box in the direction of the red arrow. How to achieve this using SceneKit?
The red arrow indicates -Z direction of the box. It also shows us it is not parallel to the camera's projection or to the global axes that are shown as dark grey lines of the grid.
My last approach is the product of a translation matrix and a rotation matrix that results in a new transformation matrix. Do I have to add the current transform to the new transform then?
If yes, where is the SceneKit function for the addition of matrices like SCNMatrix4Mult for multiplication or do I have to write it myself using Metal?
If no, what I'm missing out with the matrix calculations?
I don't want to make use of GLKit.
So my understanding is that you want to move the Box Node along its own X axis (not it's parents X axis). And because the Box Node is rotated, its X axis is not aligned with its parent's one, so you have the problem to convert the translation between the two coordinate systems.
The node hierarchy is
parentNode
|
|----boxNode // rotated around Y (vertical) axis
Using Transformation Matrices
To move boxNode along its own X axis
// First let's get the current boxNode transformation matrix
SCNMatrix4 boxTransform = boxNode.transform;
// Let's make a new matrix for translation +2 along X axis
SCNMatrix4 xTranslation = SCNMatrix4MakeTranslation(2, 0, 0);
// Combine the two matrices, THE ORDER MATTERS !
// if you swap the parameters you will move it in parent's coord system
SCNMatrix4 newTransform = SCNMatrix4Mult(xTranslation, boxTransform);
// Allply the newly generated transform
boxNode.transform = newTransform;
Please Note: The order matters when multiplying matrices
Another option:
Using SCNNode coordinate conversion functions, looks more straight forward to me
// Get the boxNode current position in parent's coord system
SCNVector3 positionInParent = boxNode.position;
// Convert that coordinate to boxNode's own coord system
SCNVector3 positionInSelf = [boxNode convertPosition:positionInParent fromNode:parentNode];
// Translate along own X axis by +2 points
positionInSelf = SCNVector3Make(positionInSelf.x + 2,
positionInSelf.y,
positionInSelf.z);
// Convert that back to parent's coord system
positionInParent = [parentNode convertPosition: positionInSelf fromNode:boxNode];
// Apply the new position
boxNode.position = positionInParent;
Building on #Sulevus's correct answer, here's an extension to SCNNode that simplifies things by using the convertVector rather than the convertPosition transformation, in Swift.
I've done it as a var returning a unit vector, and supplied an SCNVector3 overload of multiply so you can say things like
let action = SCNAction.move(by: 2 * cameraNode.leftUnitVectorInParent, duration: 1)
public extension SCNNode {
var leftUnitVectorInParent: SCNVector3 {
let vectorInSelf = SCNVector3(x: 1, y: 0, z: 0)
guard let parent = self.parent else { return vectorInSelf }
// Convert to parent's coord system
return parent.convertVector(vectorInSelf, from: self)
}
var forwardUnitVectorInParent: SCNVector3 {
let vectorInSelf = SCNVector3(x: 0, y: 0, z: 1)
guard let parent = self.parent else { return vectorInSelf }
// Convert to parent's coord system
return parent.convertVector(vectorInSelf, from: self)
}
func *(lhs: SCNVector3, rhs: CGFloat) -> SCNVector3 {
return SCNVector3(x: lhs.x * rhs, y: lhs.y * rhs, z: lhs.z * rhs)
}
func *(lhs: CGFloat, rhs: SCNVector3) -> SCNVector3 {
return SCNVector3(x: lhs * rhs.x, y: lhs * rhs.y, z: lhs * rhs.z)
}
}
The far easier way this is usually done:
The usual, normal, and extremely easy way to do this in any game engine or 3D engine is:
You simply have a wrapper node, which, holds the node in question.
This is indeed the entire point of transforms, they enable you to abstract out a certain motion.
That's the whole point of 3D engines - the GPU just multiplies out all the quaternions on the way down to the object; it's wholly pointless to (A) figure out in your head the math and (B) do it manually (indeed in the CPU).
In Unity it's "game objects", in scene kit it's "nodes" and so on.
In all 3D engines, including scene kit, almost everything has one or more "holders" around it.
To repeat, the reasons for this are (A) it's the entire raison d'etre of a game engine, to achieve performance in multiplying out the quaternions of every vertex and (B) sheer convenience and code solidity.
One of a million examples ...
Of course you can trivially do it in code,
cameraHolder.addChildNode(camera)
In the OP's example. It looks like you would use cameraHolder only to rotate the camera. And then for the motion the OP is asking about, simply move camera.
It's perfectly normal to have a chain of a number of nodes to get to an object.
This is often used for "effects". Say you have an object, which sometimes has to "vibrate up and down". You can have one node which only does that movement. Note that then, all the animations etc for that movement only have to be on that node. And critically, they can run independently of any other animations or movements. (And indeed you can just use the node elsewhere to jiggle something else.)

Player controlled node ignoring collisions

I have a SKShapeNode controlled by the player. I want to keep this node inside a parent node. So I created edges and I use SKPhysicsBody and collision bit mask to prevent my node from moving outside its parent.
When I try to move the node by updating its position each frame, it just ignores the edges. Here is the function used :
func move(direction: MoveDirection, withTimeInterval timeInterval: TimeInterval) {
var x, y: CGFloat
switch direction {
case .North:
x = 0
y = movementSpeed * CGFloat(timeInterval)
case .South:
x = 0
y = -movementSpeed * CGFloat(timeInterval)
case .East:
x = movementSpeed * CGFloat(timeInterval)
y = 0
case .West:
x = -movementSpeed * CGFloat(timeInterval)
y = 0
}
let sprite = spriteComponent.sprite
sprite.position = CGPoint(x: sprite.position.x + x, y: sprite.position.y + y)
}
The moving works great but the node can go everywhere and just doesn't care about the edges (I turned skView.showPhysics on so I can see them).
But, if I replace the line :
sprite.position = CGPoint(x: sprite.position.x + x, y: sprite.position.y + y)
by :
sprite.physicsBody?.applyForce(CGVector(dx: x, dy: y))
collisions work just fine.
It feels like we have to move objects using physics if we want them to collide. But I didn't see anything about this restriction in Apple's doc. So is this behavior expected? Or did I miss something?
Bonus point :
In the TaskBot game provided by Apple, the player's position is (or seems) changed by setting node.position (the code is a bit...complicated, so not really sure). If someone can give me a hint?
Thank you!
If you move an SKSpriteNode manually, there will be no collisions because you are overriding the physics engine by saying "Put this node there no matter what".
If you want the physics engine to reliably generate collisions, then you'll need to use only the physics engine to move your objects via forces or impulses.
If you manually move a node into a position where it generates a collision, the the physics engine will attempt to move it away, but if you carry on trying to move the node, results will be unpredictable.

GameplayKit: NPC element is going to very high floating number coordinates and back on update

I have plugged in my GKBehavior/GKAgent2D/GKComponent system for some behavioral routines and am having trouble fixing this bug I can't figure out.
Let me first be descriptive:
My current Entity/Component system is based on apple's ECS where:
1-entitymanager:GKEntity manages all components and contains all entities in the game and uses updateWithDelta which goes into all GKComponentSystems which themselves update every component within their system.
Currently, the update order is as follows:
behavior> node> playerNode> sound> interface> physics
2-Each system is a GKComponentSystem which handles updating all elements within.
I have two extensions of NSViewController called GameRenderer and GameControls which both are delegates of SCNSceneRendererDelegate and a custom protocol called KBAndTouchDelegate which update render on screen and control respectively (will be changing this to be incorporated in the ECS once my order updates feel right).
Now, in the behavior componentSystem, it updates a MoveComponent which contains a few functions for updating (willUpdate and didUpdate). Both have a 2DAgent position that is first taken from an SCNNode's position (converted from SCNVector3 to Float2 with just x and z coordinates being changed) then reflected back onto the node after the behavioral algorithm is evaluated.
Here are snippets for the updates:
func agentWillUpdate(agent: GKAgent) {
guard let nodeComponent = entity?.componentForClass(SCNNodeComponent.self)
else{
return
}
let nodePos = nodeComponent.node.position
position = float2(Float(nodePos.x), Float(nodePos.z))
}
func agentDidUpdate(agent: GKAgent) {
guard let nodeComponent = entity?.componentForClass(SCNNodeComponent.self)
else{
return
}
let pos = SCNVector3(x: CGFloat(position.x), y: nodeComponent.node.position.y, z: CGFloat(position.y))
print("\(nodeComponent.node.name) changed to \(pos)")
nodeComponent.node.position = pos
}
The #override updateWithDeltaTime routine checks for the party in which the moveComponent's owner is and placed in an array for getting the closest enemy distance and return the enemy.
The behavior has very low maxSpeed and maxVelocity factors.
Running the application, I printed a set of movements for two nodes which are enemies and monitor each other's position. The enemy search routine works fine as they reflect each other’s position in alternate sequence. The result is below:
Optional("zombie") changed to SCNVector3(x: -20.0, y: 0.0, z: -20.0)
enemy position is: (0.0, 0.0)
Optional("player") changed to SCNVector3(x: 0.0, y: 0.0, z: 0.0)
enemy position is: (-20.0, -20.0)
Optional("zombie") changed to SCNVector3(x: -20.0, y: 0.0, z: 41567.44140625)
enemy position is: (0.0, 0.0)
Optional("player") changed to SCNVector3(x: 0.0, y: 0.0, z: 0.0)
enemy position is: (-20.0, 41567.44140625)
HRTF loaded
Optional("zombie") changed to SCNVector3(x: 30.136764526367188, y: 0.0, z: -20.2421875)
enemy position is: (0.0, 0.0)
Optional("player") changed to SCNVector3(x: 0.0, y: 0.0, z: 0.0)
enemy position is: (30.1367645263672, -20.2421875)
Optional("zombie") changed to SCNVector3(x: -38636.87109375, y: 0.0, z: 15289.939453125)
If looking at the first positions of the "zombie" you will see it starts at -20, -20 then goes to impossibly big values for both x and z positions (again, x and z in the scene's world). The player doesn't move as I don't use any controls just to see if the zombie is able to find the player with the following GKBehavior call
init(targetSpeed: Float, seek: GKAgent, avoid: [GKAgent]){
super.init()
if targetSpeed>0{
setWeight(0.1, forGoal: GKGoal(toReachTargetSpeed: targetSpeed))
setWeight(0.5, forGoal: GKGoal(toSeekAgent: seek))
setWeight(1.0, forGoal: GKGoal(toAvoidAgents: avoid, maxPredictionTime: 1.0))
}
}
The above behavior is called in MoveComponent for the zombie's enemy (the player). Meaning the zombie will seek for the player.
Can anyone tell me what the hell these ginormous values for x and z are ? The zombie and player are about sqrt(800) or 28 meters away. But the zombie goes to stuff like 35800 meters away on x then switches back to about 50 then back and forth at each frame.
There’s a definite pattern and I’m not sure whether this GKBehavior uses A star at which point the probabilistic roadmap sets some very odd coordinate samples but nowhere in my code is there any scalar that would move the zombie so far away and flip the distances back to something reasonably normal every frame.
For anyone who might be having the same issue, as of yesterday 21-03-2016, the issue has been resolved by apple with their newest public update of x-code 7.3.