Sample Map Nodes via Code
This guide covers accessing and sampling procedural map data (such as road distance maps, heightmaps, or density maps) programmatically outside the Elastic Graph using Unity C# scripts.
Note
Motivation:
Because the Elastic Graph is strictly location-dependent and is entirely time-blind (see Location vs. Time), any runtime movement or time-dependent behaviors (like animating NPCs, guiding pedestrians, or driving virtual traffic) cannot be processed inside the graph itself. Instead, you must attach C# movement scripts directly to your spawned prefabs and query the graph's map output programmatically inside their Update() loops to keep them grounded.
We will write a clean, generic, and lightweight MyMapSampler component that registers with the ElasticSceneGenerator events, fetches any global MapWorker output by its configured ID, and samples its interpolated value and slope heading direction (yaw) angle at any Unity world position.
1. Exposing Map Data from the Elastic Graph
Before you can sample map data in code, you must expose the desired map node as a global output inside your Elastic Graph:
- Open your Elastic Graph window.
- Create or find your Road Distance Map node (or Height Map node, etc.).
- Create a Global Map Output node (or MapWorkerProvider-mapped output node) and set its Key / Name to a unique identifier (e.g.,
RoadDistances). - Connect the output of your map node to the input of this Global Output node.
- Save the graph.
2. The Generic Map Sampler Component
Here is a ready-to-use C# script. Create a new script in Unity named MyMapSampler.cs and paste the following code:
using Cysharp.Threading.Tasks;
using Holoride.ElasticSDK;
using UnityEngine;
public class MyMapSampler : MonoBehaviour
{
[SerializeField] private ElasticSceneGenerator elasticSceneGenerator;
[SerializeField] private string mapId = "RoadDistances";
private MapWorker mapWorker;
private void Start()
{
this.elasticSceneGenerator.OnGenerationFinished.AddListener(this.OnGenerationFinished);
}
private void OnDestroy()
{
if (this.elasticSceneGenerator != null)
{
this.elasticSceneGenerator.OnGenerationFinished.RemoveListener(this.OnGenerationFinished);
}
}
private void OnGenerationFinished(GenerationContext context)
{
this.LoadMapDataAsync().Forget();
}
private async UniTaskVoid LoadMapDataAsync()
{
// Query the global output registered with the specified key name
var output = await this.elasticSceneGenerator.TryGetGlobalOutput<MapWorker>(this.mapId);
if (output.Success)
{
this.mapWorker = output.Result;
}
}
/// <summary>
/// Samples the loaded map at the specified shifted Unity world position.
/// </summary>
/// <param name="shiftedPosition">The Unity world position (Vector3).</param>
/// <returns>The sampled value of the map at this position, or PositiveInfinity if the map is not loaded.</returns>
public float SampleMapValue(Vector3 shiftedPosition)
{
if (this.mapWorker == null)
{
return float.PositiveInfinity;
}
var globalPosition = GlobalPosition.FromShifted(shiftedPosition);
return this.mapWorker.GetCPUReadBufferValueInterpolated(globalPosition);
}
/// <summary>
/// Computes the gradient angle in degrees at the specified shifted Unity world position.
/// </summary>
/// <param name="shiftedPosition">The Unity world position (Vector3).</param>
/// <returns>The gradient angle in degrees (-180 to 180), or 0 if the map is not loaded.</returns>
public float GetGradientAngleAtPosition(Vector3 shiftedPosition)
{
if (this.mapWorker == null)
{
return 0f;
}
var globalPosition = GlobalPosition.FromShifted(shiftedPosition);
var gradient = this.mapWorker.GetGradientAtPosition(globalPosition);
// Use Atan2 to calculate the heading angle of the slope in degrees
return Mathf.Atan2(gradient.y, gradient.x) * Mathf.Rad2Deg;
}
}
3. Performance Considerations (O(1) Complexity)
The MapWorker data access implementation is designed to be lightweight:
- O(1) Time Complexity: Sampling via
GetCPUReadBufferValueInterpolatedorGetGradientAngleAtPositionperforms direct index calculations on a flat float array pre-allocated on the CPU, followed by basic bilinear interpolation math. - No Side-Effects: The operation does not execute raycasts, octree traversals, database lookups, or garbage collection (GC) allocations.
- Throughput: Because sampling consists of simple memory reads and basic arithmetic, it is fast enough to support hundreds of queries per frame. This makes it usable for high-frequency operations such as procedural crowd animations, custom particle behaviors, or frame-by-frame path adjustments.
4. How to Use It in Unity
- Attach the
MyMapSamplercomponent to any active GameObject in your scene. - In the inspector, link your Elastic Scene Generator to the reference field.
- Set the Map Id field to match the exact string key of your global output node in the graph (e.g.,
RoadDistancesorHeights). - To sample values dynamically, call
SampleMapValue(transform.position)inside your update loops. Since the script is generic, you can use multiple instances of this script to sample different map outputs simultaneously! - To align an object to face the heading direction of a slope gradient (such as turning a prop or character to face directly up or down a hill's yaw direction), you can fetch this slope heading yaw angle using
GetGradientAngleAtPosition(transform.position).