Select to view content in your preferred language

Symbols slide and glitch in 3D AR Scene as I walk

472
5
06-24-2025 11:59 AM
twicebaked
Emerging Contributor

Hi Community, 
I am making an AR experience where I can walk around boundaries placed in the real world. I am struggling a bit to keep the boundaries from sliding and glitching about when I move. I understand that the pinning of symbols to the world scale map works well only if there is proper location data. I just want to know if there are any tricks that can be used to keep symbols pinned (more) properly while I move larger distances while walking around. 

If you would like a video of the issue please let me know! 

Thank you!

0 Kudos
5 Replies
Destiny_Hochhalter
Esri Contributor

Hi @twicebaked, thanks for the question.

The issue sounds like AR drift, if you can provide a video that would be helpful. To clarify, are you making a custom AR experience or using the Swift Toolkit WorldScaleSceneView component?

0 Kudos
twicebaked
Emerging Contributor

Hi @Destiny_Hochhalter thanks for the reply! 

I am using the Swift Toolkit WorldScaleSceneView component.

I've uploaded a video, too!
Thank you!

0 Kudos
Destiny_Hochhalter
Esri Contributor

Hi @twicebaked, thanks for the video.

If you can upload your code to create the WorldScaleSceneView and add the graphics to the view, that may help. In terms of what is causing the graphics in the video to jump from various locations and drift, there are a few considerations:

  •  TrackingMode:
    Use the right tracking mode for your use-case to improve accuracy. Based on your video, there does not appear to be recognizable street imagery, so specify the worldTracking tracking mode when creating the WorldScaleSceneView.
  •  Clipping distance:
    If applicable, try using a smaller clipping distance when creating a WorldScaleSceneView, because the farther away a graphic is located, the less accurate its location will appear.
  •  Use the correct location datasource:
    If you are specifying a location for the graphics displayed in the WorldScaleSceneView, make sure that the location is from a SystemLocationDataSource because the world-scale component uses this datasource. Here is a tutorial showing this use-case. When using a custom location datasource, the custom initial location and the location the world-scale component uses will not align, causing the graphic to "jump" locations.
  •  Higher location accuracy:
    If the other suggestions do not apply, modify the world-scale component source code locally so that it has higher accuracy for your use-case. Here are some modifications to try:
    - Set a smaller time threshold and distance threshold for world tracking to improve location accuracy, which has the consequence of graphics appearing more "jumpy" since re-alignment happens more frequently.


0 Kudos
twicebaked
Emerging Contributor

Thank you for your suggestions, @Destiny_Hochhalter 

Here is the class where WorldScaleSceneView is created

 

import SwiftUI
import ArcGIS
import ArcGISToolkit
import CoreLocation

struct PropertyBoundsArView: View {
    @EnvironmentObject var locationManager: LocationModel
    @EnvironmentObject var geoDataParser: GeoDataParser
    
    var boundariesGraphicsOverlay = GraphicsOverlay()
    var modelsGraphicsOverlay = GraphicsOverlay()
    var postGraphicsOverlay = GraphicsOverlay()
    
    @State private var graphicsOverlays: [GraphicsOverlay] = [GraphicsOverlay()]
    
    @State private var initialElevation = 0.0
    
    @State private var mapTransparency: Float = 0.0
    
    var propertyBoundsFilename: String
    
    @State private var isShowing3dModels: Bool = false {
        didSet {
            updateGraphics()
        }
    }
    
    @State private var isShowingPosts: Bool = true {
        didSet {
            updateGraphics()
        }
    }
    
    @State public var scene: ArcGIS.Scene = {
        // Creates an elevation source from Terrain3D REST service.
        let elevationServiceURL = URL(
            string: "https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer"
        )!
        let elevationSource = ArcGISTiledElevationSource(url: elevationServiceURL)
        var surface = Surface()
        surface.addElevationSource(elevationSource)
        surface.navigationConstraint = .unconstrained
        surface.backgroundGrid.isVisible = false
        let scene = Scene(basemapStyle: .arcGISNavigationNight)
        scene.baseSurface = surface
        scene.baseSurface.opacity = 0
        return scene
    }()
    
    func updateGraphics() {
        graphicsOverlays.removeAll()
        if isShowing3dModels {
            graphicsOverlays.append(modelsGraphicsOverlay)
        }
        if isShowingPosts {
            graphicsOverlays.append(postGraphicsOverlay)
        }
        graphicsOverlays.append(boundariesGraphicsOverlay)
    }
    
    @State private var viewpoint: Viewpoint?
    
    var body: some View {
        ZStack {
            WorldScaleSceneView { proxy in
                SceneView(scene: scene, graphicsOverlays: graphicsOverlays)
            }
            .task {
                try? await locationManager.locationDataSource.start()
                guard let initialLocation = await locationManager.locationDataSource.locations.first(where: { location in
                    return true
                }) else {
                    print("There was an issue getting the initial location")
                    return
                }
                if let userElevation = initialLocation.position.z {
                    initialElevation = userElevation
                } else {
                    guard let tryGetElevation = try? await scene.baseSurface.elevation(at: initialLocation.position) else {
                        return
                    }
                    initialElevation = tryGetElevation
                }
                print("Users Initial Elevation is \(initialElevation)")
                guard let modelPath = Bundle.main.url(forResource: "GJ_Logo", withExtension: "FBX") else
                {
                    print("Path doesn't exists")
                    return
                }
                let symbol: ModelSceneSymbol = ModelSceneSymbol(url: modelPath, scale: 0.03)
//                await locationManager.locationDataSource.stop()
                boundariesGraphicsOverlay.sceneProperties.surfacePlacement = .relative
                boundariesGraphicsOverlay.renderingMode = .dynamic
           
                guard let graphics = geoDataParser.graphicsFromGeoJson(fileName: propertyBoundsFilename) else {
                    return
                }

                let fillSymbol = SimpleFillSymbol()
                let renderer = SimpleRenderer(symbol: fillSymbol)
                renderer.sceneProperties.extrusionMode = .baseHeight
                renderer.sceneProperties.extrusionExpression = "2"
                boundariesGraphicsOverlay.renderer = renderer

                boundariesGraphicsOverlay.addGraphics(graphics["lines"]!)
                
                guard let postGraphics = geoDataParser.graphicsFromGeoJson(fileName: propertyBoundsFilename, symbol: SimpleMarkerSceneSymbol(style: .cube, color: UIColor(red: 0.0, green: 1.0, blue: 1.0, alpha: 0.5), height: 1, width: 0.3, depth: 0.3, anchorPosition: .bottom)) else {
                    return
                }
                postGraphicsOverlay.addGraphics(postGraphics["posts"]!)
                
                var modelGraphics = [Graphic]()
                for graphic in graphics["lines"]! {
                    let point = graphic.geometry?.extent.center
                    modelGraphics.append(Graphic(geometry: point, symbol: symbol))
                }
                modelsGraphicsOverlay.addGraphics(modelGraphics)
                updateGraphics()
            }
           
            VStack {
                VStack {
                    VStack {
                        HStack {
                            Text("Map Transparency")
                            Spacer()
                        }
                        Slider(value: $mapTransparency, in: 0.0...1.0, step: 0.1)
                            .onChange(of: mapTransparency) { oldValue, newValue in
                                scene.baseSurface.opacity = newValue
                            }
                    }
                    .padding()
                    HStack {
                        Spacer()
                        Button {
                            withAnimation(.none) {
                                isShowing3dModels = !isShowing3dModels
                            }
                        } label: {
                            Text(isShowing3dModels ? "Hide 3D Models" : "Show 3D Models")
                        }
                        .buttonStyle(.bordered)
                        .foregroundStyle(isShowing3dModels ? .red : .primary)
                        .padding()
                    }
                }
                .padding(.top, 100)
                Spacer()
            }
        }
    }
}
0 Kudos
Destiny_Hochhalter
Esri Contributor

Thanks @twicebaked for the code snippet.

The usage of the WorldScaleSceneView looks correct. Note that AR drift and some location inaccuracy is expected for AR experiences in general. I have a few suggestions to try below to improve location accuracy that require directly modifying the Toolkit source code locally.

  • CLLocationManager.desiredAccuracy
    Specify the `desiredAccuracy` for the CLLocationManager used in the WorldScaleSceneView here to an accuracy that is best for your use-case:
locationManager.desiredAccuracy = kCLLocationAccuracyBest​
  • CLLocation horizontalAccuracy and verticalAccuracy:
    The WorldScaleSceneView filters location updates by their horizontal and vertical accuracy. Negative horizontal and vertical accuracy values are invalid so they are discarded here. Jumpy location updates can be improved by filtering out valid low accuracy location updates. `100` meters is used here as an example, but this value will depend on your use-case. It may not be necessary to filter out valid vertical accuracy values since elevation changes are not as frequent.
// Make sure that horizontal and vertical accuracy are valid. Filter out a horizontal accuracy more than 100 meters.
guard location.horizontalAccuracy >= .zero,
      location.horizontalAccuracy < 100,
      location.verticalAccuracy >= .zero else { return }   ​
0 Kudos