I set train.position = [10, 0, 0], and camera.target = [10, 0, 0].
So in my opinion the camera should rotate around the train when moved, but as you can see in the movie it is rotating around in a bizarre way.

Yes, you’re right. The Arcball camera doesn’t work correctly.

Try this code for Arcball:

class ArcballCamera: Camera {
var fov = Float(70).degreesToRadians
let minDistance: Float = 0.0
let maxDistance: Float = 20
var target: float3 = [0, 0, 0]
var distance: Float = 2.5
override var viewMatrix: float4x4 {
let matrix: float4x4
if target == position {
matrix = (float4x4(translation: target) * float4x4(rotationYXZ: rotation)).inverse
} else {
matrix = float4x4(eye: position, center: target, up: [0, 1, 0])
}
return matrix
}
override func zoom(delta: Float) {
let sensitivity: Float = 0.05
distance -= delta * sensitivity
let rotateMatrix = float4x4(
rotationYXZ: [-rotation.x, rotation.y, 0])
let distanceVector = float4(0, 0, -distance, 0)
let rotatedVector = rotateMatrix * distanceVector
position = target + rotatedVector.xyz
}
override func rotate(delta: float2) {
let sensitivity: Float = 0.005
rotation.y += delta.x * sensitivity
rotation.x += delta.y * sensitivity
rotation.x = max(
-Float.pi/2,
min(rotation.x, Float.pi/2))
let rotateMatrix = float4x4(
rotationYXZ: [-rotation.x, rotation.y, 0])
let distanceVector = float4(0, 0, -distance, 0)
let rotatedVector = rotateMatrix * distanceVector
position = target + rotatedVector.xyz
}
}

This one rotates the vector properly and takes into account distance. It’s not completely correct, as it does a weird flip at extremes, but you can probably put limits on the rotation to prevent that.

In Renderer, when you initialise camera, after setting camera.rotation.x = Float(-10).degreesToRadians, add this: