Hi thanks for the reply!
the problem I'm having is with the alignment of a model.
I tap 2 times on a 3D model and then 2 times on a grid of spheres. after calculating the alignment the model should jump to it's new position on the grid of spheres and match the target points.
I have an unknown visual offset after the alignment even though visually I can see the taps are in the correct place. I'll add a snippet of my alignment function and an image of the visual.
the brute force offset you can see in the code was to try and move the alignment a meter to the left (which wasn't successful).
func alignModel2Points() {
guard let modelPointA = modelPointA,
let modelPointB = modelPointB,
let targetPointA = targetPointA,
let targetPointB = targetPointB,
let modelRootEntity = modelRootEntity,
let modelAnchor = modelAnchor else {
print("❌ Missing data for alignment")
return
}
let offset = SIMD3(-1, 0, 0)
let modelPointA_shifted = modelPointA + offset
let modelPointB_shifted = modelPointB + offset
let targetPointA_shifted = targetPointA + offset
let targetPointB_shifted = targetPointB + offset
modelRootEntity.position = .zero
modelRootEntity.orientation = simd_quatf()
modelRootEntity.scale = SIMD3<Float>(repeating: 1.0)
pivotEntity?.position = .zero
// 1) compute scale from the two segments
let modelVec = modelPointB_shifted - modelPointA_shifted
let targetVec = targetPointB_shifted - targetPointA_shifted
let modelLen = length(modelVec)
let targetLen = length(targetVec)
guard modelLen > 1e-6 else {
print("❌ model points coincide")
return
}
let s = targetLen / modelLen
// 2) compute rotation that aligns modelVec -> targetVec
let modelDir = normalize(modelVec)
let targetDir = normalize(targetVec)
let crossProd = cross(modelDir, targetDir)
let crossLen = length(crossProd)
var rot: simd_quatf
if crossLen < 1e-6 {
// parallel or opposite
if dot(modelDir, targetDir) > 0 {
rot = simd_quatf(angle: 0, axis: [0,1,0])
} else {
var tmp = cross(modelDir, [0,1,0])
if length(tmp) < 1e-6 { tmp = cross(modelDir, [1,0,0]) }
rot = simd_quatf(angle: .pi, axis: normalize(tmp))
}
} else {
let dp = dot(modelDir, targetDir)
let clamped = min(max(dp, -1.0), 1.0)
let angle = acos(clamped)
rot = simd_quatf(angle: angle, axis: normalize(crossProd))
}
// 3) Compute world-space translation for the anchor:
// We want: worldPoint = AnchorTransform * (rot * (s * modelPoint) + modelRootLocalPosition)
// Since modelRootEntity is identity at origin, simplified:
// translation = targetPointA - rot.act(modelPointA_shifted * s)
let transformedModelA = rot.act(modelPointA_shifted * s)
let translation = targetPointA - transformedModelA
// 4) Build final Transform and set it on the modelAnchor (anchor is root — transform is world)
var final = Transform()
final.scale = SIMD3<Float>(repeating: s)
final.rotation = rot
final.translation = translation
// Apply final transform to the anchor (this places the whole model in world space)
modelAnchor.transform = final
alignedModelPosition = modelAnchor.position
alignmentDone = true
// debug prints
let modelA_world_after = modelRootEntity.convert(position: modelPointA_shifted, to: nil)
let modelB_world_after = modelRootEntity.convert(position: modelPointB_shifted, to: nil)
print("""
✅ ALIGN:
scale s = \(s)
rotation = \(rot)
translation = \(translation)
modelA_world_after = \(modelA_world_after)
targetA = \(targetPointA)
modelB_world_after = \(modelB_world_after)
targetB = \(targetPointB)
finalAnchor = \(modelAnchor.position)
""")
// cleanup
// removeAllMarkers()
NotificationCenter.default.post(name: .alignmentDidComplete, object: nil)
debugSpawnSpheres()
printAlignmentOffsets()
}
func printAlignmentOffsets() {
guard let mr = modelRootEntity,
let mA = modelPointA, let mB = modelPointB,
let tA = targetPointA, let tB = targetPointB else { return }
let mA_world = mr.convert(position: mA, to: nil)
let mB_world = mr.convert(position: mB, to: nil)
let offsetA = tA - mA_world
let offsetB = tB - mB_world
print("DEBUG OFFSETS:")
print(" mA_world = \(mA_world)")
print(" tA = \(tA)")
print(" offsetA = \(offsetA)")
print(" mB_world = \(mB_world)")
print(" tB = \(tB)")
print(" offsetB = \(offsetB)")
}
func debugSpawnSpheres() {
guard let c = content, let a = modelRootEntity,
let A = modelPointA, let B = modelPointB,
let tA = targetPointA, let tB = targetPointB else { return }
let s = [(a.convert(position: A, to: nil), UIColor.red),
(a.convert(position: B, to: nil), UIColor.orange),
(tA, UIColor.green),
(tB, UIColor.blue)]
for (pos, color) in s {
let e = ModelEntity(mesh: MeshResource.generateSphere(radius: 0.01),
materials: [SimpleMaterial(color: color, isMetallic: false)])
e.position = pos
c.add(e)
allMarkers.append(e)
}
}