In SCNARView I can access a property of camera node called worldFront, which represents camera rotation. I would like to calculate similar vector from CoreMotion values not using SCNARView, just data from CoreMotion. So that I can get a vector that would be equal to worldFront in SCNARView if camera was facing the same direction. Can someone explain me how to calculate such a value?
The attitude property probably could help
func rollCam(motion: CMDeviceMotion) {
let attitude = motion.attitude
let roll = Float(attitude.roll-M_PI/2)
let yaw = Float(attitude.yaw)
let pitch = Float(attitude.pitch)
camNode.eulerAngles = SCNVector3Make(roll, -yaw, pitch)
}
With this piece of code, quite long time ago, I experimented a bit with CoreMotion. I was trying to first detect human walking and then (with the startDeviceMotionUpdates data) move and roll the camera near to an "anchored" SCNBox. Later on ARKit solved my need with the ARAnchor class
What feature are you looking after?
I have found the answer:
override var cameraFrontVector: double3 {
guard let quaternion = motionService.deviceMotion?.attitude.quaternion else { return .zero }
let x = 2 * -(quaternion.x * quaternion.z + quaternion.w * quaternion.y)
let z = 2 * (quaternion.y * quaternion.z - quaternion.w * quaternion.x)
let y = 2 * (quaternion.x * quaternion.x + quaternion.y * quaternion.y) - 1
return double3(x: x, y: y, z: z)
}
This gives me values like worldFront in SCNNode.
Related
I am trying to find the area of a polygon generated by a users path. The path gives back an array of lat/lon. The path is always self closing. I have tried multiple methods that I found online. Below is what I currently have, I can't make sense of the outputted data. The area * 6378137.0 * 6378137.0 is meant to give me back an area in m^2, but the results are massive numbers.
My ideal solution would be to find a way to (with an assumed % of error) map my lat/lon to xy coordinates and apply the shoelace theorem. I understand that this is pretty hard to do accurately because of the curvature of the earth.
Building from the last paragraph, maybe the best way to map to x,y coordinates would be some sort of projection method. I have not gone too far down that path yet.
What would be the best method to try in order to solve this problem? If someone could set me on the right path or decipher the code I have already tried I would greatly appreciate it (code is done in swift playground). Thanks!
func deg2rad(_ number: Double) -> Double {
return number * .pi / 180
}
func areaCalc(lat: [Double]?, lon: [Double]?){
guard let lat = lat,
let lon = lon
else { return }
var area: Double = 0.0
if(lat.count > 2){
for i in stride(from: 0, to: lat.count - 1, by: 1) {
let p1lon = lon[i]
let p1lat = lat[i]
let p2lon = lon[i+1]
let p2lat = lat[i+1]
area = area + (deg2rad(p2lon - p1lon)) * (2 + sin(deg2rad(p1lat))) + (sin(deg2rad(p2lat)))
}
area = area * 6378137.0 * 6378137.0
area = abs(area / 2)
}
}
I am currently facing the problem that I want to calculate the angle in radians from the camera's position to a target position. However, this calculation needs to take into account the heading of the camera as well.
For example, when the camera is facing away from the object the function should return π. So far the function I have written works most of the time. However when the user gets close to the X and Z axis the arrow does not point to the target any more, rather it points slightly to the left or right depending if you are at positive or negative X and z space.
Currently, I'm not sure why my function does not work. The only explanation I would have for this behavior is gimbal lock. However I'm not quite sure how to implement the same function using quaternions.
I also attached some photos to this post that the issue is a little bit more clear.
Here is the function I'm using right now:
func getAngle() -> Float {
guard let pointOfView = self.sceneView.session.currentFrame else { return 0.0 }
let cameraPosition = pointOfView.camera.transform.columns.3
let heading = getUserVector()
let distance = SCNVector3Make(TargetPosition.x - cameraPosition.x ,TargetPosition.y - cameraPosition.y - TargetPosition.y,TargetPosition.z - cameraPosition.z)
let heading_scalar = sqrtf(heading.x * heading.x + heading.z * heading.z)
let distance_scalar = sqrtf(distance.z * distance.z + distance.z * distance.z)
let x = ((heading.x * distance.x) + (heading.z * distance.z) / (heading_scalar * distance_scalar))
let theta = acos(max(min(x, 1), -1))
if theta < 0.35 {
return 0
}
if (heading.x * (distance.z / distance_scalar) - heading.z * (distance.x/distance_scalar)) > 0{
return theta
}
else{
return -theta
}
}
func getUserVector() -> (SCNVector3) { // (direction)
if let frame = self.sceneView.session.currentFrame {
let mat = SCNMatrix4(frame.camera.transform) // 4x4 transform matrix describing camera in world space
let dir = SCNVector3(-1 * mat.m31, -1 * mat.m32, -1 * mat.m33) // orientation of camera in world space
print(mat)
return dir
}
return SCNVector3(0, 0, -1)
}
Consider the following image as an example. The arrow in the top right corner should be pointing straight up to follow the line to the center object but instead it is pointing slightly to the left. As I am aligned with the z-axis the same behavior happens when aligning with the x-axis.
I figured out the answer to my problem the solution was transforming the object into the prospective of the camera and then simply taking the atan2 to get the angle in between the camera and object hope this post will help future readers!
func getAngle() -> Float {
guard let pointOfView = self.sceneView.session.currentFrame else { return 0.0 }
let cameraPosition = pointOfView.camera.transform
let targetPosition = simd_float4x4(targetNode.transform)
let newTransform = simd_mul(cameraPosition.inverse, targetPosition).columns.3
let theta = atan2(newTransform.z, newTransform.y)
return theta + (Float.pi / 2)
}
I'm trying to create a game in which a projectile is launched at a random angle.
To do this I need to be able to generate two random Int's. I looked up some tutorials and came up with this:
var random = CGFloat(Int(arc4random()) % 1500)
var random2 = CGFloat(Int(arc4random()) % -300)
self.addChild(bullet)
bullet.physicsBody!.velocity = CGVectorMake((random2), (random))
It worked for a while but now it just crashes.
Any help would be appreciated.
What I find I use the most is arc4random_uniform(upperBound) which returns a random Int ranging from zero to upperBound -1.
let random = arc4random_uniform(1500)
let random2 = arc4random_uniform(300) * -1
//pick a number between 1 and 10
let pick = arc4random_uniform(10)+1
The lowdown on the arc4 functions: arc4random man page
GameplayKit has a nice class wrapping the arc4 functions: GameplayKit Randomness
and a handy reference: Random Hipster
I dont understand very well your issue but I think it could be useful:
func getRandomPointFromCircle(radius:Float, center:CGPoint) -> CGPoint {
let randomAngle = Float(arc4random())/Float(UInt32.max-1) * Float(M_PI) * 2.0
// polar => cartesian
let x = radius * cosf(theta)
let y = radius * sinf(theta)
return CGPointMake(CGFloat(x)+center.x,CGFloat(y)+center.y)
}
I'm new to SpriteKit game development. I'm trying give a boss character the ability to cast fireballs in multiple directions (16 fireballs all at once, 360 degree/16 = 22.5 degree apart).
I know how to get him to fire at a certain position by providing the player's current position, but how to get him to fire at 16 different angles regardless of player's position?
Thanks for any help in advance.
First, set up a loop over the angles
let numAngles = 16
var angle:CGFloat = 0
var angleIncr = CGFloat(2 * M_PI) / CGFloat(numAngles)
let strength:CGFloat = 50
for _ in 0..<numAngles {
...
angle += angleIncr
}
In the loop, convert the angle to the corresponding vector components and then create a vector
let dx = strength * cos (angle)
let dy = strength * sin (angle)
let vector = CGVectorMake (dx, dy)
and create a new fireball and apply an impulse to its physics body
let fireball = ...
fireball.position = player.position
fireball.zRotation = angle
// Add a physics body here
fireball.physicsBody?.appyImpulse (vector)
I'm not sure what code you have in place. for shooting. but ill give this a shot. angles in spritekit are in radians and a there are 2*pi radians in a circle. so you just need to do something like this
let fireballs = 16
let threeSixty = CGFloat(M_PI*2)
for i in 1...fireballs {
let angle = (CGFloat(i) / CGFloat(fireballs)) * threeSixty
// do something useful with your angle
}
In a iOS prototype I use a combination of CMDeviceMotion.deviceMotion.yaw and CLHeading.trueHeading to make stable compass heading that is responsive and accurate. This works well when the iPhone is held flat, where I have a graphical arrow that point to a stable compass heading.
The problem appear when the iPhone is held vertical in portait mode. The UIDeviceOrientation constantly changes from UIDeviceOrientationFaceDown to UIDeviceOrientationFaceUp and back. This makes the yaw value to skip back and forth +/-180 degrees based on small changes of the pitch. Is it possible to lock the device to one orientation that gives a stable yaw value, predict the change without glitches or compute the gyro yaw (or roll in this orientation) in other ways?
This poor guy have the same problem, with no answers. Double points possible people! :)
https://stackoverflow.com/questions/10470938/euler-angle-yaw-not-working-when-iphone-orientation-changes
I was just searching for an answer to this problem. It broke my heart a bit to see that you posted this over a year ago, but I figured maybe you or someone else could benefit from the solution.
The issue is gimbal lock. When pitch is about 90 degrees, yaw and roll match up and the gyro loses a degree of freedom. Quaternions are one way of avoiding gimbal lock, but I honestly didn't feel like wrapping my mind around that. Instead, I noticed that yaw and roll actually match up and can simply be summed to to solve the problem (assuming you only care about yaw).
SOLUTION:
float yawDegrees = currentAttitude.yaw * (180.0 / M_PI);
float pitchDegrees = currentAttitude.pitch * (180.0 / M_PI);
float rollDegrees = currentAttitude.roll * (180.0 / M_PI);
double rotationDegrees;
if(rollDegrees < 0 && yawDegrees < 0) // This is the condition where simply
// summing yawDegrees with rollDegrees
// wouldn't work.
// Suppose yaw = -177 and pitch = -165.
// rotationDegrees would then be -342,
// making your rotation angle jump all
// the way around the circle.
{
rotationDegrees = 360 - (-1 * (yawDegrees + rollDegrees));
}
else
{
rotationDegrees = yawDegrees + rollDegrees;
}
// Use rotationDegrees with range 0 - 360 to do whatever you want.
I hope this helps someone else!
If somebody is interested in the implementation in iOS Swift the code is given below:
let queue = NSOperationQueue()
motionManager.startDeviceMotionUpdatesToQueue(queue) {
[weak self] (data: CMDeviceMotion!, error: NSError!) in
var yawDegrees: Double = self!.motionManager.deviceMotion.attitude.yaw * (180.0 / M_PI)
var pitchDegrees: Double = self!.motionManager.deviceMotion.attitude.pitch * (180.0 / M_PI)
var rollDegrees: Double = self!.motionManager.deviceMotion.attitude.roll * (180.0 / M_PI)
if(rollDegrees < 0 && yawDegrees < 0){
self!.rotationDegrees = 360 - (-1 * (yawDegrees + rollDegrees))
}
else {
self!.rotationDegrees = yawDegrees + rollDegrees
}
}
However I am having some problems and I hope #blkhp19 can help me with this because at certain points the angles go into negative values which then messes up the entire calculation and I can't figure out what the problem is.
The problem is a bit confusing because there are at least two different ways to think about Yaw. One is from the phone's perspective, and one from the world perspective.
I'll use this image from Apple to explain further:
If the phone is flat on a table:
Rotations along the phone's yaw (or Z axis): change the compass heading.
Rotations along the phone's roll (or Y axis): do not change compass heading.
Rotations along the phone's pitch (or X axis): do not change compass heading.
If the phone is flat against a wall:
Rotations along the phone's yaw (or Z axis): change the compass heading.
Rotations along the phone's roll (or Y axis): change the compass heading.
Rotations along the phone's pitch (or X axis): do not change compass heading.
For the remainder of this answer, I'll assume the phone is upright and yaw, pitch, and roll refer to exactly what's in the photo above.
Yaw
You'll need to use atan2 and inspect gravity as in this example.
let yaw = -Angle(radians: .pi - atan2(motion.gravity.x, motion.gravity.y))
Pitch
Similar to the above, I primarily just swapped x and z and it seems to be returning the correct values:
let pitch = Angle(radians: .pi - atan2(motion.gravity.z, motion.gravity.y))
Roll (aka Compass Heading)
Use blkhp19's code above which sums up the attitude yaw and roll. If you import SwiftUI, you can leverage the Angle struct to make radian + degrees conversion easier:
func roll(motion: CMDeviceMotion) -> Angle {
let attitudeYaw = Angle(radians: motion.attitude.yaw)
let attitudeRoll = Angle(radians: motion.attitude.roll)
var compassHeading: Angle = attitudeYaw + attitudeRoll
if attitudeRoll.degrees < 0 && attitudeYaw.degrees < 0 {
compassHeading = Angle(degrees: 360 - (-1 * compassHeading.degrees))
}
return compassHeading
}
Also note that if you don't need the actual angle, and all you need is the relationship (e.g. isPhoneUpright), you can simply read gravity values for those.
extension CMDeviceMotion {
var yaw: Angle {
-Angle(radians: .pi - atan2(gravity.x, gravity.y))
}
var pitch: Angle {
Angle(radians: .pi - atan2(gravity.z, gravity.y))
}
var roll: Angle {
let attitudeYaw = Angle(radians: attitude.yaw)
let attitudeRoll = Angle(radians: attitude.roll)
var compassHeading: Angle = attitudeYaw + attitudeRoll
if attitudeRoll.degrees < 0 && attitudeYaw.degrees < 0 {
compassHeading = Angle(degrees: 360 - (-1 * compassHeading.degrees))
}
return compassHeading
}
}