ModelEntity move duration visionOS 2 issue

The following RealityView ModelEntity animated text works in visionOS 1.0. In visionOS 2.0, when running the same piece of code, the model entity move duration does not seem to work. Are there changes to the way it works that I am missing? Thank you in advance.

        RealityView { content in
            let textEntity = generateMovingText()
            
            content.add(textEntity)
            
            _ = try? await arkitSession.run([worldTrackingProvider])
        } update: { content in
            
            guard let entity = content.entities.first(where: { $0.name == .textEntityName}) else { return }
            if let pose = worldTrackingProvider.queryDeviceAnchor(atTimestamp: CACurrentMediaTime()) {
                entity.position = .init(
                    x: pose.originFromAnchorTransform.columns.3.x,
                    y: pose.originFromAnchorTransform.columns.3.y,
                    z: pose.originFromAnchorTransform.columns.3.z
                )
            }
            
            if let modelEntity = entity as? ModelEntity {
                let rotation = Transform(rotation: simd_quatf(angle: -.pi / 6, axis: [1, 0, 0])) // Adjust angle as needed
                modelEntity.transform = Transform(matrix: rotation.matrix * modelEntity.transform.matrix)
                
                let animationDuration: Float = 60.0  // Adjust the duration as needed
                let moveUp = Transform(scale: .one, translation: [0, 2, 0])
                modelEntity.move(to: moveUp, relativeTo: modelEntity, duration: TimeInterval(animationDuration), timingFunction: .linear)
            }
        }

The source is available at the following:

https://github.com/Sebulec/crawling-text

Answered by Vision Pro Engineer in 801557022

Hi @jamesboo

Really cool project!

I took a look and this appears to be a race condition. For the first few frames worldTrackingProvider.queryDeviceAnchor returns a position of (0, 0, 0). I'm not sure if this is or isn't a bug. In the meantime, the path forward is to wait until worldTrackingProvider.queryDeviceAnchor returns a transform with a non zero position. Here's the modified code to do this. For brevity I'm omitting some the unchanged code.

struct ImmersiveView: View {
    
    let worldTrackingProvider = WorldTrackingProvider()
    let arkitSession = ARKitSession()
    @State var textPosition:simd_float3?
    
    var body: some View {
        ZStack {
            RealityView { content in
                
                let textEntity = generateCrawlingText()
                
                content.add(textEntity)
            } update: { content in
                
                guard let textPosition,
                      let entity = content.entities.first(where: { $0.name == .textEntityName}) else { return }
                
                entity.position = textPosition
                
                if let modelEntity = entity as? ModelEntity {
                    let rotation = Transform(rotation: simd_quatf(angle: -.pi / 6, axis: [1, 0, 0])) // Adjust angle as needed
                    modelEntity.transform = Transform(matrix: rotation.matrix * modelEntity.transform.matrix)
                    
                    let animationDuration: Float = 60.0  // Adjust the duration as needed
                    let moveUp = Transform(scale: .one, translation: [0, 2, 0])
                    modelEntity.move(to: moveUp, relativeTo: modelEntity, duration: TimeInterval(animationDuration), timingFunction: .linear)
                }
            }
            
            Starfield()
        }
        .task {
            // Wait up to 2 seconds for a non zero position.
            let pauseInMilliseconds:UInt64 = 100
            let maxTries = 20
            _ = try? await arkitSession.run([worldTrackingProvider])
            
            for _ in 0..<maxTries {
                
                if let transform = worldTrackingProvider.queryDeviceAnchor(atTimestamp: CACurrentMediaTime())?.originFromAnchorTransform {
                    let proposedTextPosition = simd_make_float3(transform.columns.3)
                    
                    if proposedTextPosition != .zero {
                        textPosition = proposedTextPosition
                        break
                    }
                    
                }
                
                try? await Task.sleep(nanoseconds: 1_000_000 * pauseInMilliseconds)
            }
        }
    }
    
}
Accepted Answer

Hi @jamesboo

Really cool project!

I took a look and this appears to be a race condition. For the first few frames worldTrackingProvider.queryDeviceAnchor returns a position of (0, 0, 0). I'm not sure if this is or isn't a bug. In the meantime, the path forward is to wait until worldTrackingProvider.queryDeviceAnchor returns a transform with a non zero position. Here's the modified code to do this. For brevity I'm omitting some the unchanged code.

struct ImmersiveView: View {
    
    let worldTrackingProvider = WorldTrackingProvider()
    let arkitSession = ARKitSession()
    @State var textPosition:simd_float3?
    
    var body: some View {
        ZStack {
            RealityView { content in
                
                let textEntity = generateCrawlingText()
                
                content.add(textEntity)
            } update: { content in
                
                guard let textPosition,
                      let entity = content.entities.first(where: { $0.name == .textEntityName}) else { return }
                
                entity.position = textPosition
                
                if let modelEntity = entity as? ModelEntity {
                    let rotation = Transform(rotation: simd_quatf(angle: -.pi / 6, axis: [1, 0, 0])) // Adjust angle as needed
                    modelEntity.transform = Transform(matrix: rotation.matrix * modelEntity.transform.matrix)
                    
                    let animationDuration: Float = 60.0  // Adjust the duration as needed
                    let moveUp = Transform(scale: .one, translation: [0, 2, 0])
                    modelEntity.move(to: moveUp, relativeTo: modelEntity, duration: TimeInterval(animationDuration), timingFunction: .linear)
                }
            }
            
            Starfield()
        }
        .task {
            // Wait up to 2 seconds for a non zero position.
            let pauseInMilliseconds:UInt64 = 100
            let maxTries = 20
            _ = try? await arkitSession.run([worldTrackingProvider])
            
            for _ in 0..<maxTries {
                
                if let transform = worldTrackingProvider.queryDeviceAnchor(atTimestamp: CACurrentMediaTime())?.originFromAnchorTransform {
                    let proposedTextPosition = simd_make_float3(transform.columns.3)
                    
                    if proposedTextPosition != .zero {
                        textPosition = proposedTextPosition
                        break
                    }
                    
                }
                
                try? await Task.sleep(nanoseconds: 1_000_000 * pauseInMilliseconds)
            }
        }
    }
    
}

Thanks for the answer! Just to add. Much thanks to

https://github.com/Sebulec

for sharing the project.

ModelEntity move duration visionOS 2 issue
 
 
Q