Is clockwise of Path.addArc in SwiftUI wrong? - path

Below is from the official Apple document:
This method creates an open subpath. The created arc lies on the perimeter of the
specified circle. When drawn in the default coordinate system, the start and end angles are based on the unit circle shown in Figure 1. For example, specifying a start angle of 0 radians, an end angle of π radians, and setting the clockwise parameter to true draws the bottom half of the circle. However, specifying the same start and end angles but setting the clockwise parameter set to false draws the top half of the circle.
But I found that the result seemed is just opposite. Below is my code
var body: some View {
Path { path in
path.addArc(center: CGPoint(x: 200, y: 370), radius: 50, startAngle: Angle(degrees: 0), endAngle: Angle(degrees: 180.0), clockwise: true)
path.closeSubpath()
}
}
I set the clockwise parameter to true but the result is the top half of the circle, not the bottom half
Did I understand wrong about Apple's document? My Xcode Version is 11.0 beta 4 (11M374r)

The meaning of the clockwise parameter exists in a quantum superposition that collapses when you examine the result. Unfortunately, it always collapses to the opposite meaning from what you wanted. 😅
More seriously, there are some flaws in Apple's documentation:
The “default coordinate system” really means the standard Cartesian coordinate system, in which the y axis increases toward the top of the canvas. But both SwiftUI and UIKit always set up the coordinate system with the y axis “flipped” so that y values increase toward the bottom of the canvas.
clockwise is accurate only in the standard Cartesian coordinate system. What it really means is “the direction of rotation that goes from the positive y axis toward the positive x axis”. So when you're working in a flipped coordinate system, clockwise means the opposite direction!
The diagrams below may clarify what I mean. The top diagram shows a standard Cartesian coordinate system, where the direction of rotation from the positive y axis to the positive x axis is indeed clockwise. The bottom diagram shows a flipped coordinate system, which is what SwiftUI and UIKit provide, and where the direction of rotation from the positive y axis to the positive x axis is actually counterclockwise, but which the APIs call “clockwise”.

Related

Why does ARFaceAnchor have negative Z position?

I am using ARKit's ARFaceTrackingConfiguration with ARConfiguration.WorldAlignment.camera alignment, but I found that the documentation (seemingly) does not reflect the reality;
Based on the excerpt of documentation below, I would expect that the face anchor's transform is expressed in right handed coordinate system. However, when I tried moving my head, I noticed that the Z coordinate of the face anchor is always negative (i.e. faceAnchor.transform.columns.3.z < 0). Note that moving head in the X and Y directions corresponds to expected outcome (unlike Z coordinate).
Camera alignment defines a coordinate system based on the native sensor orientation of the device camera. Relative to a AVCaptureVideoOrientation.landscapeRight-oriented camera image, the x-axis points to the right, the y-axis points up, and the z-axis points out the front of the device (toward the user).
I want the transform to behave as per the documentation, i.e. the Z coordinate of face anchor should be positive given that documentation says "the z-axis points out the front of the device (toward the user)". So far it seems the Z-axis points out the back of the device…
Am I missing something obvious?
I tried to repair the rotation by the following code, but I am not sure if it's correct way to fix this:
// Repair rotation
let oldFaceRotation = simd_quatf(face.transform) // get quaternion from
let repairedFaceRotation = simd_quatf(ix: oldFaceRotation.axis.y, iy: oldFaceRotation.axis.x, iz: -oldFaceRotation.axis.z, r: oldFaceRotation.real)
// Repair translation
var repairedPosition = face.transform.columns.3
repairedPosition.z *= -1
// Combine
var correctedFaceTransform = float4x4(repairedFaceRotation)
correctedFaceTransform.columns.3 = repairedPosition
It seems quite obvious:
When ARSession is running and ARCamera begins tracking environment, it places WorldOriginAxis in front of your face at (x: 0, y: 0, z: 0). Just check it using:
sceneView.debugOptions = [.showWorldOrigin]
So your face's position must be at positive part of Z axis of World Coordinates.
Thus, ARFaceAnchor will be placed at positive Z-axis direction, as well.
And when you use ARFaceTrackingConfiguration vs ARWorldTrackingConfiguration there's two things to consider:
Rear Camera moves towards objects along negative Z-axes (positive X-axis is on the right).
Front Camera moves towards faces along positive Z-axes (positive X-axis is on the left).
Hence, when you are "looking" through TrueDepth Camera, a 4x4 Matrix is mirrored.
Although I still don't know why does not the face anchor behave as described in the documentation, I can at least answer how to correct its left-handed system into the Metal- and SceneKit-friendly right-handed system (X axis to the right, Y axis up, Z axis from the screen towards user):
func faceAnchorPoseToRHS(_ mat: float4x4) -> float4x4 {
let correctedPos = float4(x: mat.columns.3.x, y: mat.columns.3.y, z: -mat.columns.3.z, w: 1)
let quat = simd_quatf(mat)
let newQuat = simd_quatf(angle: -quat.angle, axis: float3(quat.axis.x, quat.axis.y, -quat.axis.z))
var newPose = float4x4(newQuat)
newPose.columns.3 = correctedPos
return newPose
}

Why did Apple flip their unit circle for UIBezierPath?

The image on the left is what a typical unit circle looks like. The one on the right is from the documentation. I haven't seen a more in depth explanation for why it was flipped anywhere online. Why is this so?
A drawing of a circle is often represented by x = center.x + r * cos(φ) and y = center.y + r * sin(φ) as φ progresses from 0 to 2π. With a standard Cartesian coordinate system, with the origin in the lower-left corner, this results in a circle drawn, starting at 3 o'clock and proceeding counterclockwise. See diagram to the left, below.
But the iOS coordinate system has the the y-axis flipped from the standard Cartesian coordinate system, with the origin in the upper-left corner and y increasing as you move down the screen. See right diagram below:
(This is adapted from Coordinate Systems in the Quartz 2D Programming Guide. The original diagram in the Apple documentation is merely illustrating how the coordinate systems are flipped, but I've changed the arrow to more accurately represent how this affects the drawing of an arc from 0 to π/2.)
The result is that, when using the iOS coordinate system, it will start at 3 o'clock but then proceed clockwise.
Because iOS draws everything upside-down.
In OS X (and just about every math book, and also by default in Core Graphics which came from OS X), the origin is in the lower-left corner and the Y-axis increases as you move up. In that coordinate system, the angles lay out the way you think they should. In UIKit's upside-down coordinate system, everything is flipped.
What's interesting is that the direction of the angles bothered you, but the bizarre Y-axis did not. (*) This inverted intuition among programmers is likely the reason that Apple flipped the coordinate system when they wrote iOS, but you can still see the artifacts here and there. (In fairness to Apple, layout code that mimics a page, like text views and scroll views, is much easier to compute when Y increases downward. Since those are very common design elements, it's not so crazy that UIKit flips the axes. It also goes to show that these things are very arbitrary in math and computers.)
(*) Yes, you noted where the origin was located, and that this was a likely part of the answer, but allow me some hyperbole to make the next point :D

Mapping a vector across the y-axis if it has 4 components

I am applying a force and a torque on an node. This is my code:
myNode?.physicsBody?.applyForce(SCNVector3Make(0, -6, 4), atPosition: SCNVector3Make(0, 1, -1), impulse: true)
myNode?.physicsBody?.applyForce(SCNVector3Make(0, -2, 10), impulse: true)
myNode?.physicsBody?.applyTorque(SCNVector4Make(4, 2, 2.5, 1.6), impulse: true)
The object now falls down and moves from left to right afterwards. I want it fall down and move from right to the left(basically a reflection of the first movement across y-axis). I figured it out that there is very little I can do about the first 2 lines of code, because the force has no x-component. The last line, applyTorque, is the one I need to manipulate. How do you map across the y-axis if the vector has 4 components? I am a little rusty with math
The fuller version of the applyTorque function looks something like this:
.applyTorque(SCNVector4Make(x:, y:, z:, w:), impulse:)
So any numbers you put in the second position should be torque amounts around the y axis.
There's probably a relationship between the numbers and what they create in terms of rotational force on an object, but I've always just used trial-and-error to find what works. Sometimes it's HUGE numbers.
I am assuming that the x-axis is horizontal and the y-axis is vertical and the z-axis points straight at you (see the black arrows below):
I found evidence that this is indeed the case in SceneKit.
If
applyTorque(SCNVector4Make(x, y, z, w), impulse: boolean)
is the correct usage, then x is the amount of counter-clockwise rotation around the x-axis (see green circle arrow), and similarly for y and z. Again, this is my best guess, and it is possible that SceneKit uses clockwise rotation. Therefore, x, y, and z together determine the axis of rotation of the torsional force.
Here is a simpler way to think of it. x, y, and z create a vector in the 3D space described above. The object will rotate counter-clockwise around this vector.
w on the other hand, is the magnitude of the torque, and has nothing to do with the axis of rotation.
Your request to "map vector across the y-axis" is actually a reflection across the yz-plane. If you think about it, what you want is to rotate the opposite direction around the y-axis (negate y) and the same for z.
So the answer should be:
myNode?.physicsBody?.applyTorque(SCNVector4Make(4, -2, -2.5, 1.6), impulse: true)
According to the SceneKit documentation the SCNVector4 argument specifies the direction (x, y, z vector components) and magnitude (w vector component) of the force in newton-meters. To mirror the direction of the applied torque, all you have to do is invert the magnitude. (x, y, z, -magnitude)

Trouble getting the angle between tapped point and midpoint x-axis line

I've opted for a picture, which I think may better describe the angle I'm trying to calculate:
CGPoint P2 is anywhere where the user tapped, and CGPoint P1 always has its P1.y = P2.y and P1.x = self.view.bounds.size.width/2.
I was wondering how I could try to calculate the angle between the two points given that I want to base the angle on the half way x-axis?
Thanks!
The angle is:
90-arctan((self.view.bounds.size.height - P2.y)/(P2.x - self.view.bounds.size.width/2))
(It will be positive at the right of the line, negative at the left. If you always want it positive, use abs())
What I'm doing is basically setting the origin at the bottom center of the screen, then calculating the slope of the line from the origin to P2. Then arctan gives the angle of the line respect to the X axis. Then you do 90-arctan(..) to make it respect the Y axis.
Alternatively you can use this formula, which is the same with the axes already flipped.
arctan((P2.x - self.view.bounds.size.width/2)/(self.view.bounds.size.height - P2.y))

Will the slope change for a line change if we change the origin from top-left to bottom-left?

In my iphone app, I am required to draw a line between two points and display its slope.
now my calculations are simply based on.
slope = (startPoint.y - endPoint.y) / (startPoint.x - endPoint.x )
the startPoint and endPoint which I have are in terms of screen-pixel coordinates where origin is on top left corner.
but I am displaying this line over a graph where my axes are laid with coordinate system having origin on bottom-left corner on screen.
Will this affect the slope I am calculating ?
I have already taken care of converting x,y coordinates with respect to graph's x,y axes scale units.
If you are not modifying startPoint.y or endPoint.y then the slope will be unchanged, but it won't match what is being drawn to the screen. Also, you will need to handle both x values being the same which will result in a division by zero error.

Resources