Use ARKit detect phone moving trail and distance - ios

I want log phone moving trail and distance .
since we can use ARKit measure real world objects.
func distance(from vector: SCNVector3) -> Float {
let distanceX = self.x - vector.x
let distanceY = self.y - vector.y
let distanceZ = self.z - vector.z
return sqrtf( (distanceX * distanceX) + (distanceY * distanceY) + (distanceZ * distanceZ))
}
i can get distance between 2 SCNVector3 .
but i don't know how to log phone's current SCNVector3 in AR Scene
is it possible use ARKit measure phone move distance and moving trail ?

When you create your ARSession, the camera is located at 0,0,0 and it moves as you move the phone. So if you want to know how far the phone is from its original position, just look at the translation portion of the camera transform matrix for the current frame:
frame.camera.transform
the x y and z translation components are in m41, m42 and m43 respectively. See the apple core animation docs for more on matrices if you are not familiar with the math.

Related

How to calculate distance from the centering point between two eyes to the camera using ARCore + SceneKit?

I'm using ARCore + SceneKit (Swift language) to calculate the distance from the centering point between two eyes to the camera.
I determine the coordinates of the camera:
let cameraPos = sceneView.pointOfView?.position
The coordinates of the left eye and right eye:
let buffer = face.mesh.vertices
let left = buffer[LF]
let right = buffer[RT]
NOTE:
LF and RT is defined base on: https://github.com/ManuelTS/augmentedFaceMeshIndices
LF = 159 is the index that contain the Vector3 condinate of the Left eye
RT = 386 is the index that contain the Vector3 condinate of the Right eye
Compute the centering point (in SCNVector3):
let center = SCNVector3(x: (left.x - right.x) * 0.5,
y: (left.y - right.y) * 0.5,
z: (left.z - right.z) * 0.5)
Finally, I calculate the distance:
let distance = distance(start: cameraPos!, end: center)
distance is defined as:
func distance(start: SCNVector3, end: SCNVector3) -> Float {
let dx = start.x - end.x
let dy = start.y - end.y
let dz = start.z - end.z
let distance = sqrt(dx * dx + dy * dy + dz * dz)
return round(distance * 100 * 10) / 10.0
}
Runtime result is incorrect.
Actual distance: ~20 cm
In-app distance: ~3 cm
Can someone tell me where the problem lies, even another solution?
Thanks.
Assuming center is the midpoint between the eyes, then shouldn't the formula be:
Midpoint:
(x1, y1, z1) and (x2, y2, z2) is (x1+x2 )/2,(y1+y2 )/2,(z1+z2 )/2.
Edit: Taking a guess here, but...
Example: So that a projectile will actually launch from a turret with a long barrel cannon exactly where the barrel is rotated to at the time of firing, you have to calculate that position at the end of the tube as it relates to the position of the node that the barrel is attached to, otherwise the shot will not look like it came from the right spot.
Requires a little imagination, but this is your face moving around = turret is moving around. I "think" that's what's happening to your math. I don't think you are getting the right LF/RF positions because you didn't mention converting the point. The link you sent [The face mesh consists of hundreds of vertices that make up the face, and is defined relative to the center pose.] Relative to the center pose - I'm pretty sure that means you have to convert LF with relation to the center to get the real position.
// Convert position something like this:
let REAL_LF = gNodes.gameNodes.convertPosition(LF.presentation.position, from: POSE_POSITION)
convertPosition(_:to:)
Converts a position from the node’s local coordinate space to that of another node

How to point the camera towards a SCNVector3 point below iOS 11

I just started learning how to use SceneKit yesterday, so I may get some stuff wrong or incorrect. I am trying to make my cameraNode look at a SCNVector3 point in the scene.
I am trying to make my app available to people below iOS 11.0. However, the look(at:) function is only for iOS 11.0+.
Here is my function where I initialise the camera:
func initCamera() {
cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(5, 12, 10)
if #available(iOS 11.0, *) {
cameraNode.look(at: SCNVector3(0, 5, 0)) // Calculate the look angle
} else {
// How can I calculate the orientation? <-----------
}
print(cameraNode.rotation) // Prints: SCNVector4(x: -0.7600127, y: 0.62465125, z: 0.17941462, w: 0.7226559)
gameScene.rootNode.addChildNode(cameraNode)
}
The orientation of SCNVector4(x: -0.7600127, y: 0.62465125, z: 0.17941462, w: 0.7226559) in degrees is x: -43.5, y: 35.8, z: 10.3, and I don't understand w. (Also, why isn't z = 0? I thought z was the roll...?)
Here is my workings out for recreating what I thought the Y-angle should be:
So I worked it out to be 63.4 degrees, but the returned rotation shows that it should be 35.8 degrees. Is there something wrong with my calculations, do I not fully understand SCNVector4, or is there another method to do this?
I looked at Explaining in Detail the ScnVector4 method for what SCNVector4 is, but I still don't really understand what w is for. It says that w is the 'angle of rotation' which I thought was what I thought X, Y & Z were for.
If you have any questions, please ask!
Although #rickster has given the explanations of the properties of the node, I have figured out a method to rotate the node to look at a point using maths (trigonometry).
Here is my code:
// Extension for Float
extension Float {
/// Convert degrees to radians
func asRadians() -> Float {
return self * Float.pi / 180
}
}
and also:
// Extension for SCNNode
extension SCNNode {
/// Look at a SCNVector3 point
func lookAt(_ point: SCNVector3) {
// Find change in positions
let changeX = self.position.x - point.x // Change in X position
let changeY = self.position.y - point.y // Change in Y position
let changeZ = self.position.z - point.z // Change in Z position
// Calculate the X and Y angles
let angleX = atan2(changeZ, changeY) * (changeZ > 0 ? -1 : 1)
let angleY = atan2(changeZ, changeX)
// Calculate the X and Y rotations
let xRot = Float(-90).asRadians() - angleX // X rotation
let yRot = Float(90).asRadians() - angleY // Y rotation
self.eulerAngles = SCNVector3(CGFloat(xRot), CGFloat(yRot), 0) // Rotate
}
}
And you call the function using:
cameraNode.lookAt(SCNVector3(0, 5, 0))
Hope this helps people in the future!
There are three ways to express a 3D rotation in SceneKit:
What you're doing on paper is calculating separate angles around the x, y, and z axes. These are called Euler angles, or pitch, yaw, and roll. You might get results that more resemble your hand-calculations if you use eulerAngles or simdEulerAngles instead of `rotation. (Or you might not, because one of the difficulties of an Euler-angle system is that you have to apply each of those three rotations in the correct order.)
simdRotation or rotation uses a four-component vector (float4 or SCNVector4) to express an axis-angle representation of the rotation. This relies on a bit of math that isn't obvious for many newcomers to 3D graphics: the result of any sequence of rotations around different axes can be minimally expressed as a single rotation around a new axis.
For example, a rotation of π/2 radians (90°) around the z-axis (0,0,1) followed by a rotation of π/2 around the y-axis (0,1,0) has the same result as a rotation of 2π/3 around the axis (-1/√3, 1/√3, 1/√3).
This is where you're getting confused about the x, y, z, and w components of a SceneKit rotation vector — the first three components are lengths, expressing a 3D vector, and the fourth is a rotation in radians around that vector.
Quaternions are another way to express 3D rotation (and one that's even further off the beaten path for those of us with the formal math education common to undergraduate computer science curricula, but not crazy advanced, either). These have lots of great features for 3D graphics, like being easy to compose and interpolate between. In SceneKit, the simdOrientation or orientation property lets you work with a node's rotation as a quaternion.
Explaining how quaternions work is too much for one SO answer, but the practical upshot is this: if you're working with a good vector math library (like the SIMD library built into iOS 9 and later), you can basically treat them as opaque — just convert from whichever other rotation representation is easiest for you, and reap the benefits.

Detect initial position of iOS Device in 3D Space - Core Motion

Using Coremotion we can get the change in position of device using attitude, rotation using gyro. But to know the actual position of device in 3D space, we would need the initial position of the device. So that accordingly the userAcceleration, gyro data could be applied the get new actual position after changes. How to get the initial actual position of device. I want to detect the position like "45 degree tilted in left with face up" or "45 degree tilted in right with rotated at 30 degrees in y axis".
You can use CoreMotion accelerometers to estimate the initial position of your device using some equations.
let x = data.acceleration.x
let y = data.acceleration.y
let z = data.acceleration.z
let roll = atan (y / sqrt(pow(x,2.0) + pow(z, 2.0)));
let pitch = atan (x / sqrt(pow(y, 2.0) + pow(z, 2.0)));
let yaw = atan (sqrt(pow(x, 2.0) + pow(z, 2.0))/z);
Somehow, the Yaw is still relative to the starting position of your Device. In order to fix that issue, you should look for using the Compass.

how can i get the heading of the device with CMDeviceMotion in iOS 5

I'm developing an AR app using the gyro. I have use an apple code example pARk. It use the rotation matrix to calculate the position of the coordinate and it do really well, but now I'm trying to implement a "radar" and I need to rotate this in function of the device heading. I'm using the CLLocationManager heading but it's not correct.
The question is, how can I get the heading of the device using the CMAttitude to reflect exactly what I get in the screen??
I'm new with rotation matrix and that kind of things.
This is part of the code used to calculate the AR coordinates. Update the cameraTransform with the attitude:
CMDeviceMotion *d = motionManager.deviceMotion;
if (d != nil) {
CMRotationMatrix r = d.attitude.rotationMatrix;
transformFromCMRotationMatrix(cameraTransform, &r);
[self setNeedsDisplay];
}
and then in the drawRect code:
mat4f_t projectionCameraTransform;
multiplyMatrixAndMatrix(projectionCameraTransform, projectionTransform, cameraTransform);
int i = 0;
for (PlaceOfInterest *poi in [placesOfInterest objectEnumerator]) {
vec4f_t v;
multiplyMatrixAndVector(v, projectionCameraTransform, placesOfInterestCoordinates[i]);
float x = (v[0] / v[3] + 1.0f) * 0.5f;
float y = (v[1] / v[3] + 1.0f) * 0.5f;
I also rotate the view with the pitch angle.
The motions updates are started using the north:
[motionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXTrueNorthZVertical];
So I think that must be possible to get the "roll"/heading of the device in any position (with any pitch and yaw...) but I don't know how.
There are a few ways to calculate heading from the rotation matrix returned by CMDeviceMotion. This assumes you use the same definition of Apple's compass, where the +y direction (top of the iPhone) pointing due north returns a heading of 0, and rotating the iPhone to the right increases the heading, so East is 90, South is 180, and so forth.
First, when you start updates, be sure to check to make sure headings are available:
if (([CMMotionManager availableAttitudeReferenceFrames] & CMAttitudeReferenceFrameXTrueNorthZVertical) != 0) {
...
}
Next, when you start the motion manager, ask for attitude as a rotation from X pointing true North (or Magnetic North if you need that for some reason):
[motionManager startDeviceMotionUpdatesUsingReferenceFrame: CMAttitudeReferenceFrameXTrueNorthZVertical
toQueue: self.motionQueue
withHandler: dmHandler];
When the motion manager reports a motion update, you want to find out how much the device has rotated in the X-Y plane. Since we are interested in the top of the iPhone, we'll pick a point in that direction and rotate it using the returned rotation matrix to get the point after rotation:
[m11 m12 m13] [0] [m12]
[m21 m22 m23] [1] = [m22]
[m31 m32 m33] [0] [m32]
The funky brackets are matrices; it's the best I can do using ASCII. :)
The heading is the angle between the rotated point and true North. We can use the X and Y coordinates of the rotated point to extract the arc tangent, which gives the angle between the point and the X axis. This is actually 180 degrees off from what we want, so we have to adjust accordingly. The resulting code looks like this:
CMDeviceMotionHandler dmHandler = ^(CMDeviceMotion *aMotion, NSError *error) {
// Check for an error.
if (error) {
// Add error handling here.
} else {
// Get the rotation matrix.
CMAttitude *attitude = self.motionManager.deviceMotion.attitude;
CMRotationMatrix rm = attitude.rotationMatrix;
// Get the heading.
double heading = PI + atan2(rm.m22, rm.m12);
heading = heading*180/PI;
printf("Heading: %5.0f\n", heading);
}
};
There is one gotcha: If the top of the iPhone is pointed straight up or straight down, the direction is undefined. The result is m21 and m22 are zero, or very close to it. You need to decide what this means for your app and handle the condition accordingly. You might, for example, switch to a heading based on the -Z axis (behind the iPhone) when m12*m12 + m22*m22 is close to zero.
This all assumes you want to rotate about the X-Y plane, as Apple usually does for their compass. It works because you are using the rotation matrix returned by the motion manager to rotate a vector pointed along the Y axis, which is this matrix:
[0]
[1]
[0]
To rotate a different vector--say, one pointed along -Z--use a different matrix, like
[0]
[0]
[-1]
Of course, you also have to take the arc tangent in a different plane, so instead of
double heading = PI + atan2(rm.m22, rm.m12);
you would use
double heading = PI + atan2(-rm.m33, -rm.m13);
to get the rotation in the X-Z plane.

converting gps coordinates to opengl word in AR

i have a list of gps coordinates (long,lat) and i have my current position (long,lat).
i found out that by subtracting the two coordinates i find the relative coordinates from my position, and that coordinates i use in my AR app to draw the pois in the opengl world.
the problem is that far-away coordinates will still be too far to "see", so i want an equation to translate everything to be close to my position, but with their original relative position.
double kGpsToOpenglCoorRatio = 1000;
- (void)convertGlobalCoordinatesToLocalCoordinates:(double)latitude x_p:(double *)x_p longitude:(double)longitude y_p:(double *)y_p {
*x_p = ((latitude - _userLocation.coordinate.latitude) * kGpsToOpenglCoorRatio);
*y_p = ((longitude - _userLocation.coordinate.longitude) * kGpsToOpenglCoorRatio);
}
i tried applying Square root in order to give them a "distance limit", but their positions got messed up relatively to their original position.
This might be because GPS uses a spherical(ish) coordinate system, and you're trying to directly map it to a cartesian coordinate system (a plane).
What you could to do is convert your GPS coordinates to a local reference plane, rather than map them directly. If you consider your own location the origin of your coordinate system, you can get the polar coordinates of the points on the ground plane relative to the origin and true north by using great circle distance (r) and bearing (theta) between your location and the remote coordinate, and then covert that to cartesian coordinates using (x,y) = (r*cos(theta), r*sin(theta)).
Better again for your case, once you have the great circle bearing, you can just foreshorten r (the distance). That will drag the points closer to you in both x and y, but they'll still be at the correct relative bearing, you'll just need to indicate this somehow.
Another approach is to scale the size of the objects you're visualizing so that they get larger with distance to compensate for perspective. This way you can just directly use the correct position and orientation.
This page has the bearing/distance algorithms: http://www.movable-type.co.uk/scripts/latlong.html
I ended up solving it using the equation of the gps coordinate intercepted with the circle i want all the pois to appear on, it works perfectly. I didn't use bearings anywhere.
here is the code if anyone interested:
- (void)convertGlobalCoordinatesToLocalCoordinates:(double)latitude x_p:(double *)x_p longitude:(double)longitude y_p:(double *)y_p {
double x = (latitude - _userLocation.coordinate.latitude) * kGpsToOpenglCoorRatio;
double y = (longitude - _userLocation.coordinate.longitude) * kGpsToOpenglCoorRatio;
y = (y == 0 ? y = 0.0001 : y);
x = (x == 0 ? x = 0.0001 : x);
double slope = x / ABS(y);
double outY = sqrt( kPoiRadius / (1+pow(slope,2)) );
double outX = slope * outY;
if (y < 0) {
outY = -1 * outY;
}
*x_p = outX;
*y_p = outY;
}

Resources