Raycast to map elevation is false

344
6
04-02-2024 12:23 PM
AndrewMcLane
New Contributor III

I am attempting to place a player avatar on a map that is generated at runtime. I have looked at the "API" and "Routes" ArcGIS example scenes within Unity and learned a lot about creating the map at runtime with the appropriate api. 

Yet, the biggest issue, and one I am trying to solve, is that raycasting from above the ground to the ground to get a specific ArcGisPoint returns a point that is hovering a few hundred meters above the actual ground. (Picture 1)

I have created a coroutine that raycasts until it hits the map tile colliders, then it returns the raycast hit point, which I then turn into an ArcGisPoint via a method I found in the sample projects, specifically this 

ArcGISPoint HitToGeoPosition(RaycastHit hit, float yOffset = 0)
{
var worldPosition = math.inverse(mapComponent.WorldMatrix).HomogeneousTransformPoint(hit.point.ToDouble3());
var geoPosition = mapComponent.View.WorldToGeographic(worldPosition);
var offsetPosition = new ArcGISPoint(geoPosition.X, geoPosition.Y, geoPosition.Z + yOffset, geoPosition.SpatialReference);
return GeoUtils.ProjectToSpatialReference(offsetPosition, geoPosition.SpatialReference);
}

 

I have tried changing the spacial references to all be of type

ArcGISSpatialReference.WGS84()

but this create strange glitches. 

 

... Okay, so while writing this, I have discovered that the collider that the raycast hits is not the same as the collider of the map tile that sits right under the map ... it actually hits a preliminary layer that is blank and brown and flat (see picture 3 & 4 below). Once the rest of the elevation and layer load to the final state, the player has already been positioned.  

I have sought a way to register a callback to know exactly when the layer and elevation are active (not initializing) ... and I came up with using the ArcGISViewStateLoggingComponent and adding an `action Event`  that will be called once the

mapComponent.View.LayerViewStateChanged

event is fired and 

status.HasFlag(ArcGISLayerViewStatus.Active)

is true (see picture #5). However, this ALWAYS crashes Unity editor. 

 

What can I do to easily know when the map has fully and properly rendered its tiles? Isn't there a simple callback and Esri has instilled into their API? 

Thank you for all of your help! 

 

 

 

Screenshot 2024-04-02 at 10.45.49 AM.pngScreenshot 2024-04-02 at 10.57.50 AM.png

Screenshot 2024-04-02 at 11.39.07 AM.pngScreenshot 2024-04-02 at 11.39.36 AM.png

Screenshot 2024-04-02 at 12.16.29 PM.png

0 Kudos
6 Replies
MasonGaw
New Contributor III

Hi there. 
Please take a look at this code and let me know if this works. This is pulled directly from our Third Person Character Controller Sample, located here, and keeps our character from dropping down until the map has loaded. Hope this helps.
Mason Gaw

0 Kudos
AndrewMcLane
New Contributor III

Thanks for replying so soon! I have rolled my own implementation of this sample code ... the issue is that the player drops so far from where it is instantiated (at the position of the brown flat preliminary map) to the final tiles that are the correctly rendered map tiles. It literally takes like 30 seconds for the player avatar to fall and hit the ground. That's just not going to cut it. The scene needs to load and the player needs to be there, not fall from 1000 above the surface of the complete map. 
There MUST be a simple callback attached the Map object that can send a message when the map is completely done loading. 
I have found some callback events, but these are all NOT on the main thread, and so calling another method from them makes Unity editor crash (yay!, jk). 

0 Kudos
AShahbaz
Esri Contributor

Hi AndrewMcLane,
the basemap and the layers have a 'DoneLoading' property that you can add delegates to. However, I doubt that it will help you in this scenario. I believe the issue here is that different levels of detail are being created as more data are received. My guess is that the blank surface that you mentioned is the worst LoD available for that area, which is loaded first.

Here are a couple of workarounds that you could try until hopefully a better solution is available:

  1. Querying a rest api or similar service at runtime that provides the ground elevation for a particular lat and lon. Using the same lat and lon and the queried altitude, you can create the position without a raycast.
  2. Another option is to keep doing a ray cast until the hit location is stable. You will have a counter that tracks the number of frames where the hit location has remained the same. If the hit location changes, that means that a new LoD is loaded, so you reset the counter and keep casting and counting. Once the counter reaches a threshold, you can assume the best LoD has been loaded at that location and place your character. I am not  sure if we have a C# implementation of this approach in our samples, but there is a C++ example that you can find in our Unreal samples repo

Hope this helps.

0 Kudos
AndrewMcLane
New Contributor III

@AShahbaz Thanks so much for replying. After a while of thinking I solved it, my test flight testers have seen the player drop through the map after I place the player and turn on the character controller after the map "loads". So I'm back at fixing it. I think I'll go with option 2 from above. I'll report back to you after I get some QA results for how well this addressed the problem. Thanks again!

0 Kudos
AShahbaz
Esri Contributor

I hope that will fix it.

Also, if the character always spawns at the same location, your could place it on a small "platform" just above the map (e.g., a plane or cube just big enought for the character to land on). And enable the controller/inputs once the map "loads". The platform could even be made invisible.

0 Kudos
AndrewMcLane
New Contributor III

It's a location based game, so the player always spawns at different map locations and the map is also always different. But I added some quick fixes to make sure the player spawns just 1 unit off the ground so that the slope doesn't break through the character's colliders before the character controller is turned on

0 Kudos