As a direct answer to the question: projection matrices are designed to output something in -1 ... 1 range, while CoreAnimation usually works with pixels. From this troubles come. So to cure the problem you should BEFORE multiplying modelview to projection matrix, decrease your model to fit well in -1 ... 1 range (depending on what you world is you can divide it to bounds.size) and after projection matrix multiplication you can return back to pixels.
So below is a code snippet in Swift (I personally hate Swift, but my customer likes it) I believe it can be understood. It's written for CoreAnimation and ARKit and tested that it works. I hope ARKit matrices can be thought as opengl ones
func initCATransform3D(_ t_ : float4x4) -> CATransform3D
{
let t = t_.transpose //surprise m43 means 4th column 3rd row, thanks to Apple for amazing documenation for CATransform3D and total disregarding standard math with Mij notation
//surprise: at Apple didn't care about CA & ARKit compatibility so no any easier way to do this
return CATransform3D(
m11: CGFloat(t[0][0]), m12: CGFloat(t[1][0]), m13: CGFloat(t[2][0]), m14: CGFloat(t[3][0]),
m21: CGFloat(t[0][1]), m22: CGFloat(t[1][1]), m23: CGFloat(t[2][1]), m24: CGFloat(t[3][1]),
m31: CGFloat(t[0][2]), m32: CGFloat(t[1][2]), m33: CGFloat(t[2][2]), m34: CGFloat(t[3][2]),
m41: CGFloat(t[0][3]), m42: CGFloat(t[1][3]), m43: CGFloat(t[2][3]), m44: CGFloat(t[3][3])
)
}
override func updateAnchors(frame: ARFrame) {
for animView in stickerViews {
guard let anchor = animView.anchor else { continue }
//100 here to make object smaller... on input it's in pixels, but we want it to be more real.
//we work in meters at this place
//surprise: nevertheless the matrices are column-major they are inited in "transposed" form because arrays/columns are written in horizontal direction
let mx = float4x4(
[1/Float(100), 0, 0, 0],
[0, -1/Float(100), 0, 0], //flip Y axis; it's directed up in 3d world while for CA on iOS it's directed down
[0, 0, 1, 0],
[0, 0, 0, 1]
)
let simd_atr = anchor.transform * mx //* matrix_scale(1/Float(bounds.size.height), matrix_identity_float4x4)
var atr = initCATransform3D(simd_atr) //atr = anchor transform
let camera = frame.camera
let view = initCATransform3D(camera.viewMatrix(for: .landscapeRight))
let proj = initCATransform3D(camera.projectionMatrix(for: .landscapeRight, viewportSize: camera.imageResolution, zNear: 0.01, zFar: 1000))
//surprise: CATransform3DConcat(a,b) is equal to mathematical b*a, documentation in apple is wrong
//for fun it's distinct from glsl or opengl multiplication order
atr = CATransform3DConcat(CATransform3DConcat(atr, view), proj)
let norm = CATransform3DMakeScale(0.5, -0.5, 1) //on iOS Y axis is directed down, but we flipped it formerly, so return back!
let shift = CATransform3DMakeTranslation(1, -1, 0) //we should shift it to another end of projection matrix output before flipping
let screen_scale = CATransform3DMakeScale(bounds.size.width, bounds.size.height, 1)
atr = CATransform3DConcat(CATransform3DConcat(atr, shift), norm)
atr = CATransform3DConcat(atr, CATransform3DMakeAffineTransform(frame.displayTransform(for: self.orientation(), viewportSize: bounds.size)));
atr = CATransform3DConcat(atr, screen_scale) //scale back to pixels
//printCATransform(atr)
//we assume center is in 0,0 i.e. formerly there was animView.layer.center = CGPoint(x:0, y:0)
animView.layer.transform = atr
}
}
P.S. I think for developers who created all the possible mix with Left & Right hand coordinate systems, Y and Z - axis direction, Column major matrices, float & CGFloat incompatibility and lack of CA and ARKit integration a place in hell will be especially hot...