SceneKit applyTorque - ios

I am trying to applyTorque to a node in my scene. The documentation states:
Each component of the torque vector relates to rotation about the
corresponding axis in the local coordinate system of the SCNNode
object containing the physics body. For example, applying a torque of
{0.0, 0.0, 1.0} causes a node to spin counterclockwise around its
z-axis.
However in my tests it seems that Physics animations do not affect actual position of the object. Therefore, the axis remain static (even though the actual node obviously moves). This results in the torque always being applied from the same direction (wherever the z axes was when the scene was initiated).
I would like to be able to apply torque so that it is always constant in relation to the object (e.g. to cause node to spin counterclockwise around z-axis of the node's presentationNode not the position node had(has?) when the scene was initiated)

SceneKit uses two versions of each node: the model node defines static behavior and the presentation node is what's actually involved in dynamic behavior and used on screen. This division mirrors that used in Core Animation, and enables features like implicit animation (where you can do things like set node.position and have it animate to the new value, without other parts of your code that query node.position having to working about intermediate values during the animation).
Physics operates on the presentation node, but in some cases--like this one--takes input in scene space.
However, the only difference between the presentation node and the scene is in terms of coordinate spaces, so all you need to do is convert your vector from presentation space to scene space. (The root node of the scene shouldn't be getting transformed by physics, actions, or inflight animations, so there's no practical difference between model-scene space and presentation-scene space.) To do that, use one of the coordinate conversion methods SceneKit provides, such as convertPosition:fromNode:.
Here's a Swift playground that illustrates your dilemma:
import Cocoa
import SceneKit
import XCPlayground
// Set up a scene for our tests
let scene = SCNScene()
let view = SCNView(frame: NSRect(x: 0, y: 0, width: 500, height: 500))
view.autoenablesDefaultLighting = true
view.scene = scene
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(x: 0, y: 0, z: 5)
scene.rootNode.addChildNode(cameraNode)
XCPShowView("view", view)
// Make a pyramid to test on
let node = SCNNode(geometry: SCNPyramid(width: 1, height: 1, length: 1))
scene.rootNode.addChildNode(node)
node.physicsBody = SCNPhysicsBody.dynamicBody()
scene.physicsWorld.gravity = SCNVector3Zero // Don't fall off screen
// Rotate around the axis that looks into the screen
node.physicsBody?.applyTorque(SCNVector4(x: 0, y: 0, z: 1, w: 0.1), impulse: true)
// Wait a bit, then try to rotate around the y-axis
node.runAction(SCNAction.waitForDuration(10), completionHandler: {
var axis = SCNVector3(x: 0, y: 1, z: 0)
node.physicsBody?.applyTorque(SCNVector4(x: axis.x, y: axis.y, z: axis.z, w: 1), impulse: true)
})
The second rotation effectively spins the pyramid around the screen's y-axis, not the pyramid's y-axis -- the one that goes through the apex of the pyramid. As you noted, it's spinning around what was the pyramid's y-axis as of before the first rotation; i.e. the y-axis of the scene (which is unaffected by physics), not that of the presentation node (that was rotated through physics).
To fix it, insert the following line (after the one that starts with var axis):
axis = scene.rootNode.convertPosition(axis, fromNode: node.presentationNode())
The call to convertPosition:fromNode: says "give me a vector in scene coordinate space that's equivalent to this one in presentation-node space". When you apply a torque around the converted axis, it effectively converts back to the presentation node's space to simulate physics, so you see it spin around the axis you want.
Update: Had some coordinate spaces wrong, but the end result is pretty much the same.

Unfortunately the solution provided by rickster does not work for me :(
Trying to solve this conundrum I have created (what i believe to be) a very sub-standard solution (more a proof of concept). It involves creating (null) objects on the axis i am trying to find, then I use their position to find the vector aligned to the axes.
As I have a fairly complex scene, I am loading it from a COLLADA file. Within that file i have modelled a simple coordinate tripod: three orthogonal cylinders with cones on top (makes it easer to visualise what is going on).
I then constrain this tripod object to the object I am trying to apply torque to. This way I have objects that allow me to retrieve two points on the axes of the presentationNode of the object I am trying to apply torque to. I can then use these two points to determine the vector to apply the torque from.
// calculate orientation vector in the most unimaginative way possible
// retrieve axis tripod objects. We will be using these as guide objects.
// The tripod is constructed as a cylinder called "Xaxis" with a cone at the top.
// All loaded from an external COLLADA file.
SCNNode *XaxisRoot = [scene.rootNode childNodeWithName:#"XAxis" recursively:YES];
SCNNode *XaxisTip = [XaxisRoot childNodeWithName:#"Cone" recursively:NO];
// To devise the vector we will need two points. One is the root of our tripod,
// the other is at the tip. First, we get their positions. As they are constrained
// to the _rotatingNode, presentationNode.position is always the same .position
// because presentationNode returns position in relation to the parent node.
SCNVector3 XaxisRootPos = XaxisRoot.position;
SCNVector3 XaxisTipPos = XaxisTip.position;
// We then convert these two points into _rotatingNode coordinate space. This is
// the coordinate space applyTorque seems to be using.
XaxisRootPos = [_rotatingNode convertPosition:XaxisRootPos fromNode:_rotatingNode.presentationNode];
XaxisTipPos = [_rotatingNode convertPosition:XaxisTipPos fromNode:_rotatingNode.presentationNode];
// Now, we have two *points* in _rotatingNode coordinate space. One is at the center
// of our _rotatingNode, the other is somewhere along it's Xaxis. Subtracting them
// will give us the *vector* aligned to the x axis of our _rotatingNode
GLKVector3 rawXRotationAxes = GLKVector3Subtract(SCNVector3ToGLKVector3(XaxisRootPos), SCNVector3ToGLKVector3(XaxisTipPos));
// we now normalise this vector
GLKVector3 normalisedXRotationAxes = GLKVector3Normalize(rawXRotationAxes);
//finally we are able to apply toque reliably
[_rotatingNode.physicsBody applyTorque:SCNVector4Make(normalisedXRotationAxis.x,normalisedXRotationAxis.y,normalisedXRotationAxis.z, 500) impulse:YES];
As you can probably see, I am quite inexperienced in SceneKit, but even I can see that much easier/optimised solution does exits, but I am unable to find it :(

I recently had this same problem, of how to convert a torque from the local space of the object to the world space required by the applyTorque method. The problem with using the node's convertPosition:toNode and fromNodes methods, is that they are also applying the node's translation to the torque, so this will only work when the node is at 0,0,0. What these methods do is treat the SCNVector3 as if it's a vec4 with a w component of 1.0. We just want to apply the rotation, in other words, we want the w component of the vec4 to be 0. Unlike SceneKit, GLKit gives us 2 options for how we want our vec3s to be multiplied:
GLKMatrix4MultiplyVector3 where
The input vector is treated as it were a 4-component vector with a w-component of 0.0.
and GLKMatrix4MultiplyVector3WithTranslation where
The input vector is treated as it were a 4-component vector with a w-component of 1.0.
What we want here is the former, just the rotation, not the translation.
So, we could roundtrip to GLKit. To convert for instance the local x axis (1,0,0), eg a pitch rotation, to the global axis needed for apply torque, would look like this:
let local = GLKMatrix4MultiplyVector3(SCNMatrix4ToGLKMatrix4(node.presentationNode.worldTransform), GLKVector3(v: (1,0,0)))
node.physicsBody?.applyTorque(SCNVector4(local.x, local.y, local.z, 10), impulse: false)
However, a more Swiftian approach would be to add a * operator for mat4 * vec3 which treats the vec3 like a vec4 with a 0.0 w component. Like this:
func * (left: SCNMatrix4, right: SCNVector3) -> SCNVector3 { //multiply mat4 by vec3 as if w is 0.0
return SCNVector3(
left.m11 * right.x + left.m21 * right.y + left.m31 * right.z,
left.m12 * right.x + left.m22 * right.y + left.m32 * right.z,
left.m13 * right.x + left.m23 * right.y + left.m33 * right.z
)
}
Although this operator makes an assumption about how we want our vec3s to be multiplied, my reasoning here is that as the convertPosition methods already treat w as 1, it would be redundant to have a * operator that also did this.
You could also add a mat4 * SCNVector4 operator that would let the user explicity choose whether or not they want w to be 0 or 1.
So, instead of having to roundtrip from SceneKit to GLKit, we can just write:
let local = node.presentationNode.worldTransform * SCNVector3(1,0,0)
node.physicsBody?.applyTorque(SCNVector4(local.x, local.y, local.z, 10), impulse: false)
You can use this method to apply rotation on multiple axes with one applyTorque call. So say if you have stick input where you want x on the stick to be yaw (local yUp-axis) and y on the stick to be pitch (local x-axis), but with flight-sim style "down to pull back/ up", then you could set it to SCNVector3(input.y, -input.x, 0)

Related

iOS ARKit + SceneKit physics contact detection scaling issue

I have a simple 3d area that contains 4 walls, each is a SCNNode with a simple SCNBox geometry, of rectangular shape, and matching SCNPhysicsBody attached. The SCNPhysicsBody uses a SCNPhysicsShape.ShapeType.boundingBox, and is set to static type. Here is a code snippet:
let size = (self.levelNode.boundingBox.max - self.levelNode.boundingBox.min) * self.levelNode.scale
//x //z
let geometryA = SCNBox(width: CGFloat(size.x), height: CGFloat(1 * self.levelNode.scale.x), length: 0.01, chamferRadius: 0)
let geometryB = SCNBox(width: CGFloat(size.z), height: CGFloat(1 * self.levelNode.scale.x), length: 0.01, chamferRadius: 0)
geometryA.firstMaterial?.diffuse.contents = UIColor(red: 0.0, green: 0.2, blue: 1.0, alpha: 0.65)
geometryB.firstMaterial?.diffuse.contents = UIColor(red: 0.0, green: 0.2, blue: 1.0, alpha: 0.65)
let nodeA = SCNNode(geometry: geometryA)
nodeA.position += self.levelNode.position
nodeA.position += SCNVector3(0, 0.25 * self.levelNode.scale.y, -size.z/2)
nodeA.name = "Boundary-01"
let nodeB = SCNNode(geometry: geometryA)
nodeB.position += self.levelNode.position
nodeB.position += SCNVector3(0, 0.25 * self.levelNode.scale.y, size.z/2)
nodeB.name = "Boundary-03"
let nodeC = SCNNode(geometry: geometryB)
nodeC.position += self.levelNode.position
nodeC.position += SCNVector3(-size.x/2, 0.25 * self.levelNode.scale.y, 0)
nodeC.eulerAngles = SCNVector3(0, -Float.pi/2, 0)
nodeC.name = "Boundary-02"
let nodeD = SCNNode(geometry: geometryB)
nodeD.position += self.levelNode.position
nodeD.position += SCNVector3(size.x/2, 0.25 * self.levelNode.scale.y, 0)
nodeD.eulerAngles = SCNVector3(0, Float.pi/2, 0)
nodeD.name = "Boundary-04"
let nodes = [nodeA, nodeB, nodeC, nodeD]
for node in nodes {
//
let shape = SCNPhysicsShape(geometry: node.geometry!, options: [
SCNPhysicsShape.Option.type : SCNPhysicsShape.ShapeType.boundingBox])
let body = SCNPhysicsBody(type: .static, shape: shape)
node.physicsBody = body
node.physicsBody?.isAffectedByGravity = false
node.physicsBody?.categoryBitMask = Bitmask.boundary.rawValue
node.physicsBody?.contactTestBitMask = Bitmask.edge.rawValue
node.physicsBody?.collisionBitMask = 0
scene.rootNode.addChildNode(node)
node.physicsBody?.resetTransform()
}
Inside this area, I spawn entities at a regular time interval. Each also has a SCNBox geometry, that is a cube shape this time, smaller than the walls, and same parameters for the physics body as above.
To simplify the behaviour of my entities inside this game area, I am calculating their paths to travel, then applying a SCNAction to the relevant node to move them. The SCNAction moves both the node and physics body attached to it.
I am using the SCNPhysicsWorld contact delegate to detect when an entity reaches one of the boundary walls. I then calculate a random trajectory for it from that wall in another direction, clear its actions, and apply a new move SCNAction.
This is where it gets interesting...
When this 'world' is at 1:1 scale. The contacts are detected as normal both in a standard SCNScene, and a scene projected using ARKit. The visible contact, i.e. the visible change in direction of the entity appears to be close to the boundary as expected. When I check the contact.penetrationDistance of each contact their values are e.g. 0.00294602662324905.
BUT when I change the scale of this 'world' to something smaller, say the equivalent of 10cm width, in ARKit, the simulation falls apart.
The contacts between an entity and a boundary node have a comparatively huge visible gap between them when the contact is detected. Yet the contact.penetrationDistance is of the same magnitude as before.
I switched on the ARSCNView debug options to show the physics shapes in the render, and they all appear to be the correct proportions, matching the bounding box of their node.
As you can see from the code example above, the boundary nodes are generated after I have scaled the level, during my AR user setup. They are added to the root node of the scene, not as a child of the level node. The same code is being used to generate the entities.
Previously I had tried using the resetTransform() function on the physics bodies but this did not produce a reliable scaling of the physics bodies, after I had scaled the level, so I decided to generate the nodes for the boundaries and entities after the level has been scaled.
In Apple's documentation, it does state that if the SCNPhysicsBody is not a custom shape, that it will adopt the scale of the node geometry applied to it. I am not affected by this as I am generating the geometries and their respective nodes, after the scaling has been applied to the level.
One of assumptions at the moment is that the physics simulation falls apart at such a small scale. But I am not relying on the simulation of forces to move the bodies ...
Is there a more appropriate way to scale the physics world?
Or, am I staring a bug in the SCNPhysicsWorld, that is something beyond my control, at the moment.
One solution I did think about was to run the entire simulation at 1:1 scale but hidden, then apply those movements to the smaller entities. As you can imagine, that will affect the performance of the entire scene...
The penetration distance of the first contact is a negative value, suggesting there is a gap. This gap does not appear to scale as you scale down the size of the simulation.
As a way to resolve the above excess, I have implemented an additional check on the contacts in the Contact Delegate to not take the first contact detected for a particular category, but rather ensure the penetrationDistance value is positive, so ensuring that there is overlap between the two objects, before triggering a change in direction of the entity which connected with a boundary.

How do I rotate a SCNNode around a point?

I have a SCNNode - sceneNode - which is a child of rootNode and contains all of my child nodes. Upon the user tapping a button, I want to rotate the scene around a certain point on the y-axis. For example, the camera's point of view is known, and I want to rotate everything by 90º around the camera. The camera is no longer at 0, 0, 0.
I've tried playing around with the SCNMatrix4MakeTranslation function, and then changing the y value on the euler angles, but I've not been able to get it to work expectedly.
Your question is rather ambiguous, if you were to rotate the entire scene 90degrees “around the camera” the scene would end up next to the camera and you wouldn’t see it.
To rotate a SCNNode around a point other than its own pivot, as the question in the title, create a translation matrix that moves it to that point, multiply with a rotation matrix to the SCNNode, and then multiply with the inverse of the translation matrix.
However, what you probably want to do instead is do basically the same process with the camera node instead of the sceneNode. That will make the camera move around the sceneNode, giving the appearance the scene is rotating in place.
For example:
//get the current camera's transform and store it in cammat
GLKMatrix4 cammat = SCNMatrix4ToGLKMatrix4(self.myView.pointOfView.transform);
//create a rotation matrix of 90 degrees over axis Y
GLKQuaternion quaty = GLKQuaternionMakeWithAngleAndAxis(M_PI/2, 0, 1, 0);
GLKMatrix4 rotMat = GLKMatrix4MakeWithQuaternion(quaty);
//set the camera transform to rotMat * cammat, which basically rotates the camera first, and then moves it back to the same offset it was.
self.myView.pointOfView.transform = SCNMatrix4FromGLKMatrix4(GLKMatrix4Multiply(rotMat, cammat));

SceneKit - Rotate object around X and Z axis

I’m using ARKit with SceneKit. When user presses a button I create an anchor and to the SCNNode corresponding to it I add a 3D object (loaded from a .scn file in the project).
The 3D object is placed facing the camera, with the same orientation the camera has. I would like to make it look like the object is laying on a plane surface and not inclined if it is that way. So, if I got it right, I’d need to apply a rotation transformation so that it’s rotation around the X and Z axis become 0.
My attempt at this is: take the node’s x and z eulerAngles, invert them, and rotate that amount around each axis
let rotationZ = rotationMatrixAroundZ(radians: -node.eulerAngles.z)
let rotationX = rotationMatrixAroundX(radians: -node.eulerAngles.x)
let rotationTransform = simd_mul(rotationTransformX, rotationTransformZ)
node.transform = SCNMatrix4(simd_mul(simd_float4x4(node.transform), rotationTransform))
This works all right for most cases, but in some the object is rotated in completely strange ways. Should I be setting the
rotation angle to anything else than just the inverse of the current Euler Angle? Setting the angles to 0 directly did not work at all.
I've come across this and figured out I was running into gimbal lock. The solution was to rotate the node around one axis, parent it to another SCNNode(), then rotate the parent around the other axis. Hope that helps.
You don't have to do the node transform on a matrix, you can simply rotate around a specific axis and that might be a bit simpler in terms of the logic of doing the rotation.
You could do something like:
node.runAction(SCNAction.rotateBy(x: x, y: y, z: z, duration: 0.0))
Not sure if this is the kind of thing you're looking for, but it is simpler than doing the rotation with the SCNMatrix4
Well, I managed a workaround, but I'm not truly happy with it, so I'll leave the question unanswered. Basically I define a threshold of 2 degrees and keep applying those rotations until both Euler Angles around X and Z are below the aforementioned threshold.
func layDownNode(_ node: SCNNode) {
let maxErrDegrees: Float = 2.0
let maxErrRadians = GLKMathDegreesToRadians(maxErrDegrees)
while (abs(node.eulerAngles.x) > maxErrRadians || abs(node.eulerAngles.z) > maxErrRadians) {
let rotationZ = -node.eulerAngles.z
let rotationX = -node.eulerAngles.x
let rotationTransformZ = rotationMatrixAroundZ(radians: rotationZ)
let rotationTransformX = rotationMatrixAroundX(radians: rotationX)
let rotationTransform = simd_mul(rotationTransformX, rotationTransformZ)
node.transform = SCNMatrix4(simd_mul(simd_float4x4(node.transform), rotationTransform))
}
}

Scaling an object after rotating in SceneKit

I am trying to set up a simple scene (one spherical node and the default camera) in a square SceneView. Currently I set up the scene as below:
let scene = SCNScene()
let planet = SCNSphere(radius: 1.0)
let planetNode = SCNNode(geometry: planet)
scene.rootNode.addChildNode(planetNode)
To certain views, I also rotate the node as such:
let rotationNode = SCNNode()
rotationNode.addChildNode(planetNode)
scene.rootNode.addChildNode(rotationNode)
rotationNode.rotation = (SCNVector4: SCNVector4(x: 0, y: 0, z: 1, w: some_amount_of_radians))
What I noticed, however, is the objects that get rotated are smaller than the ones that don't get rotated. I am not really sure what the ratio is, but it seems to be dependent on how much rotation is added, to a point.
In the below screenshot, Earth is rotated 45 degrees, and the other 2 are not rotated. If I rotated it 90 degrees instead, there is no difference, which leads me to believe there is a square bounding box around the sphere and the default camera is forcing its point of view to contain this box.
I have also tried to change the euler angles, position, and scale of the rotated nodes to compensate, but no transormations I apply seem to have any effect. Any pointers for solving this camera issue would be perfect.

How to do transforms on a CALayer?

Before writing this question, I've
had experience with Affine transforms for views
read the Transforms documentation in the Quartz 2D Programming Guide
seen this detailed CALayer tutorial
downloaded and run the LayerPlayer project from Github
However, I'm still having trouble understanding how to do basic transforms on a layer. Finding explanations and simple examples for translate, rotate and scale has been difficult.
Today I finally decided to sit down, make a test project, and figure them out. My answer is below.
Notes:
I only do Swift, but if someone else wants to add the Objective-C code, be my guest.
At this point I am only concerned with understanding 2D transforms.
Basics
There are a number of different transforms you can do on a layer, but the basic ones are
translate (move)
scale
rotate
To do transforms on a CALayer, you set the layer's transform property to a CATransform3D type. For example, to translate a layer, you would do something like this:
myLayer.transform = CATransform3DMakeTranslation(20, 30, 0)
The word Make is used in the name for creating the initial transform: CATransform3DMakeTranslation. Subsequent transforms that are applied omit the Make. See, for example, this rotation followed by a translation:
let rotation = CATransform3DMakeRotation(CGFloat.pi * 30.0 / 180.0, 20, 20, 0)
myLayer.transform = CATransform3DTranslate(rotation, 20, 30, 0)
Now that we have the basis of how to make a transform, let's look at some examples of how to do each one. First, though, I'll show how I set up the project in case you want to play around with it, too.
Setup
For the following examples I set up a Single View Application and added a UIView with a light blue background to the storyboard. I hooked up the view to the view controller with the following code:
import UIKit
class ViewController: UIViewController {
var myLayer = CATextLayer()
#IBOutlet weak var myView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// setup the sublayer
addSubLayer()
// do the transform
transformExample()
}
func addSubLayer() {
myLayer.frame = CGRect(x: 0, y: 0, width: 100, height: 40)
myLayer.backgroundColor = UIColor.blue.cgColor
myLayer.string = "Hello"
myView.layer.addSublayer(myLayer)
}
//******** Replace this function with the examples below ********
func transformExample() {
// add transform code here ...
}
}
There are many different kinds of CALayer, but I chose to use CATextLayer so that the transforms will be more clear visually.
Translate
The translation transform moves the layer. The basic syntax is
CATransform3DMakeTranslation(_ tx: CGFloat, _ ty: CGFloat, _ tz: CGFloat)
where tx is the change in the x coordinates, ty is the change in y, and tz is the change in z.
Example
In iOS the origin of the coordinate system is in the top left, so if we wanted to move the layer 90 points to the right and 50 points down, we would do the following:
myLayer.transform = CATransform3DMakeTranslation(90, 50, 0)
Notes
Remember that you can paste this into the transformExample() method in the project code above.
Since we are just going to deal with two dimensions here, tz is set to 0.
The red line in the image above goes from the center of the original location to the center of the new location. That's because transforms are done in relation to the anchor point and the anchor point by default is in the center of the layer.
Scale
The scale transform stretches or squishes the layer. The basic syntax is
CATransform3DMakeScale(_ sx: CGFloat, _ sy: CGFloat, _ sz: CGFloat)
where sx, sy, and sz are the numbers by which to scale (multiply) the x, y, and z coordinates respectively.
Example
If we wanted to half the width and triple the height, we would do the following
myLayer.transform = CATransform3DMakeScale(0.5, 3.0, 1.0)
Notes
Since we are only working in two dimensions, we just multiply the z coordinates by 1.0 to leave them unaffected.
The red dot in the image above represents the anchor point. Notice how the scaling is done in relation to the anchor point. That is, everything is either stretched toward or away from the anchor point.
Rotate
The rotation transform rotates the layer around the anchor point (the center of the layer by default). The basic syntax is
CATransform3DMakeRotation(_ angle: CGFloat, _ x: CGFloat, _ y: CGFloat, _ z: CGFloat)
where angle is the angle in radians that the layer should be rotated and x, y, and z are the axes about which to rotate. Setting an axis to 0 cancels a rotation around that particular axis.
Example
If we wanted to rotate a layer clockwise 30 degrees, we would do the following:
let degrees = 30.0
let radians = CGFloat(degrees * Double.pi / 180)
myLayer.transform = CATransform3DMakeRotation(radians, 0.0, 0.0, 1.0)
Notes
Since we are working in two dimentions, we only want the xy plane to be rotated around the z axis. Thus we set x and y to 0.0 and set z to 1.0.
This rotated the layer in a clockwise direction. We could have rotated counterclockwise by setting z to -1.0.
The red dot shows where the anchor point is. The rotation is done around the anchor point.
Multiple transforms
In order to combine multiple transforms we could use concatination like this
CATransform3DConcat(_ a: CATransform3D, _ b: CATransform3D)
However, we will just do one after another. The first transform will use the Make in its name. The following transforms will not use Make, but they will take the previous transform as a parameter.
Example
This time we combine all three of the previous transforms.
let degrees = 30.0
let radians = CGFloat(degrees * Double.pi / 180)
// translate
var transform = CATransform3DMakeTranslation(90, 50, 0)
// rotate
transform = CATransform3DRotate(transform, radians, 0.0, 0.0, 1.0)
// scale
transform = CATransform3DScale(transform, 0.5, 3.0, 1.0)
// apply the transforms
myLayer.transform = transform
Notes
The order that the transforms are done in matters.
Everything was done in relation to the anchor point (red dot).
A Note about Anchor Point and Position
We did all our transforms above without changing the anchor point. Sometimes it is necessary to change it, though, like if you want to rotate around some other point besides the center. However, this can be a little tricky.
The anchor point and position are both at the same place. The anchor point is expressed as a unit of the layer's coordinate system (default is 0.5, 0.5) and the position is expressed in the superlayer's coordinate system. They can be set like this
myLayer.anchorPoint = CGPoint(x: 0.0, y: 1.0)
myLayer.position = CGPoint(x: 50, y: 50)
If you only set the anchor point without changing the position, then the frame changes so that the position will be in the right spot. Or more precisely, the frame is recalculated based on the new anchor point and old position. This usually gives unexpected results. The following two articles have an excellent discussion of this.
About the anchorPoint
Translate rotate translate?
See also
Border, rounded corners, and shadow on a CALayer
Using a border with a Bezier path for a layer

Resources