Performing a crowd simulation in a 3D space is not a trivial task. Many components should be known to perform simultaneous navigation in indoor and outdoor space, calculate impacts from other pedestrians and obstacles. The conceptual model is presented in
Figure 1 showing the main needed components. As a base of the model, navigation mesh is considered, which is used for space abstraction and subdivision. Using a navigation mesh, subdivision between horizontal and vertical spaces is performed, followed by heightmap identification and border extraction for accurate vertical placement and calculation of repulsive forces from obstacles, respectively. Using the information of vertical spaces, stairs classification and parametrisation are performed enabling us to calculate their effects on pedestrian’s speed. The next step involves the use of spatial indexes allowing a quick execution of different spatial queries. In the end, a crowd simulation approach is presented to highlight the way of using the extracted building information. In the following subsections, we will cover each of these components to understand the role they play in the whole process.
3.1. Navigation Mesh
In this subsection, we describe the way of generating a navigation mesh and its properties in the Unity game engine. The software considers by default several parameters in the construction of a navigation mesh such as agent radius, height, jumping step and maximum climbing slope. At the same time, parameters such as voxel and tile size can be adjusted (
https://docs.unity3d.com/Manual/classNavMeshSurface.html, accessed on 29 July 2021). Although the default ones are selfexplanatory the latter ones need an explanation. The voxel size plays an important role in the precision of navigation mesh, and it is used to represent the agent’s body size, which is afterwards considered to identify walkable voxels and regions. We will evaluate later on in the text the number of border edges that are being generated as a result of using different voxel sizes in
Section 4. To compute the navigation mesh quickly for large areas, space is tessellated into smaller areas, called tiles or chunks, allowing the execution of many calculations in parallel.
In terms of the classification of objects (e.g., stairs, doors, furniture) they should be separated into static and dynamic. The static ones are considered for the navigation mesh generation. Doors are recommended to be regarded as dynamic features to identify one continuous navigable space (
Figure 2). However, once the navigation mesh is generated not just one navigable region can be identified. Having many navigable areas is suitable for game developments, as agents can be programmed to jump long distances or teleport somewhere else. However, for pedestrian simulations and built environments, this is not the case, and only the largest navigable area should be considered which is usually much bigger than the rest. In our case, smaller navigable areas are taken as nonwalkable as pedestrians cannot reach them from the main navigation area by walking.
When a navigable mesh is constructed in the Unity there is an offset from walls which roughly represents the pedestrian’s radius. In this way, a pedestrian will not collide with any wall during a simulation. The radius size should be set up based on the maximum width that a crowd simulation model allows in order not to get stuck in some positions due to repulsive forces coming from the model.
To start working with the navigation mesh Unity provides triangles used for pathfinding which match to a good extent the actual navigation mesh except on locations with curved surfaces (
https://docs.unity3d.com/540/Documentation/ScriptReference/NavMesh.CalculateTriangulation.html, accessed on 29 July 2021). This particularly is not representing any problem, as the triangles can be used to find border edges and perform the subdivision. Additionally, a navigation mesh is not capturing anyway accurately such areas for pedestrian dynamics (Figure 5). For example, slope of stairs cannot be reliably identified from it, and it should be replaced with a more reliable solution.
3.2. Space Subdivision
Space subdivision is an important aspect of any navigation process. They are artificial representations supporting different agents’ activities. The subdivision can be performed on 2D and 3D levels depending on the application [
28,
29]. There are a few pros and cons of performing 2D and 3D subdivisions. The main advantages of the 2D subdivision are the execution of all or most calculations in 2D, the user can keep in memory only spaces that are occupied by pedestrians, and triangles are not needed for vertical placement of pedestrians. On the other hand, if everything is considered as one 3D space or chunks of 3D spaces simulated pedestrians can freely travel in an environment without the need to check if they travelled to another space for pedestrian dynamics purposes. However, it would be required to keep more information in memory of the whole space including heightmap or triangles, the slope of stairs and other properties. Therefore, a 2D space subdivision will be performed splitting a navigable area into horizontal and vertical areas.
Using the previously mentioned triangles we can identify border edges to calculate obstacles’ impact during a simulation and connecting edges to perform the transition between two subdivided spaces. The main issue with the provided triangles is the connection between them, as they do not represent exactly CDT due to the tessellation process. As a result, on locations where tiles share borders, additional edges and nodes can emerge.
Algorithm 1 shows all the functions needed to subdivide spaces and classify border edges. As a first step, we replace vertices that are in the proximity of 1 mm of each other. Based on the vertices and their edges all triangles are initialised. A slope of 0 is used to classify them into flat and tilted triangles. To find out the connection between the same triangles we use Depth First Search (DFS) algorithm. To identify the main navigation surface out of the identified ones we simply selected the one with the largest number of vertices. For each edge, a corresponding surface is identified. Flat surfaces with an area smaller than 0.5 m
^{2} are merged with the closest sloped surface. If a sloped surface has some overlapping parts, it should be separated into two or more parts. For example, this can happen in the case of having circular stairs and some other similar scenarios.
Algorithm 1 Space Subdivision of a Navigation Mesh

Input: Edges and vertices representing triangles of a navigation mesh Output: Subdivided spaces and border edges $ReplaceDuplicates\left(vertices\right)$ $triangles=InitialiseTriangles\left(vertices,edges\right)$ $edges=TinToEdges\left(triangles\right)$ $surfaces=IdentifySurfaces\left(edges\right)$ $mainSurface=IdentifyMainSurface\left(surfaces\right)\left[0\right]$ $tin=SplitTriangles\left(mainSurface\right)$ $slopedSurfaces=IdentifySurface\left(TinToEdges\left(tin\left[0\right]\right)\right)$ $flatSurfaces=IdentifySurface\left(TinToEdges\left(tin\left[1\right]\right)\right)$ $MergeSurfaces\left(slopedSurfaces,flatSurfaces\right)$ $SplitSurfaces\left(slopedSurfaces\right)$ $\mathit{f}\mathit{o}\mathit{r}\mathit{e}\mathit{a}\mathit{c}\mathit{h}surface\mathit{i}\mathit{n}\left[slopedSurfaces;flatSurfaces\right]\mathit{d}\mathit{o}$ $IdentifyBorders\left(surface\right)$ $MergeEdges\left(surface\right)$ 
The functions used in the algorithm are the following:
$ReplaceDuplicates\left(vertices\right)$: replacing vertices that are close to other ones.
$InitialiseTriangles\left(vertices,\text{}edges\right)$: initialising triangles based on vertices and edges.
$TinToEdges\left(triangles\right)$: identifying all edges.
$IdentifySurfaces\left(edges\right)$: identifying all connected triangles forming one connected surface.
$IdentifyMainSurface\left(edges\right)$: finding the surface based on the largest number of vertices.
$SplitTriangles\left(edges\right)$: splitting triangles of the main navigation surface based on the slope classification.
$MergeSurfaces\left(slopedSurfaces,\text{}flatSurfaces\right)$: merging small flat surfaces with neighbouring sloped ones.
$SplitSurfaces\left(slopedSurfaces\right)$: splitting a surface into several smaller surfaces until the edges do not intersect in 2D space.
$IdentifyBorders\left(surface\right)$: identifying border edges of a surface and connecting edges between them.
$MergeEdges\left(surface\right)$: merging connected and inlined border edges.
3.3. Border Edges Extraction
To detect border edges, we first identify all the edges that are appearing once, which usually leaves the edges that are at the border. As an exact CDT is not constructed in the first place some edges are also appearing in the middle of walking areas. Thus, we have vertices that are connected to more than two edges. For each of those edges, we test if their vertices and a point representing the middle part of the edge (e.g., there are situations when both vertices are on the border, but the edge still goes through the navigable area) are on the border of the navigation mesh using Unity function that identifies the closest edge (
https://docs.unity3d.com/ScriptReference/AI.NavMesh.FindClosestEdge.html, accessed on 29 July 2021) (
Figure 3). We should justify that we do not want to use this Unity function to check how far away is a pedestrian from the closest border edge in simulations, as it is performed in 3D, while in our case it will be 2D. However, the main reason is related to obstacle avoidance where Unity can only identify the closest edge, whereas for the social force model that we selected in the further text it is required to calculate the impact from all obstacles in a pedestrian’s proximity.
Once border edges are detected, connected and inlined edges can be merged (
Figure 4). Such edges are partitioned because of triangles that we use as input. The main reason for connecting those edges is to have fewer edges, which will allow quicker calculation of obstacles impact during simulations. This is done in two steps. The first step requires associating each edge to its closest object surface. To achieve this, we cast rays from five points equally distributed on the edges in the direction normal to the edge. Considering normals of the points that hit a surface and the object that is hit, we detect to which object’s surface each edge belongs. As a result, all edges that correspond to one surface of a wall are merged into one line. The second step involves connecting boundary edges that belong to two connected and approximately inlined surfaces. For this, we consider an azimuthal angle difference between such edges of 3°. In the end, we obtain border edges which more rationally represent obstacles. In the experiments section, we further assess the accuracy of identified border edges compared to actual walls and other obstacles.
3.4. Height Map Identification and Stairs Classification
Heightmap identification plays an important role during a crowd simulation for the vertical placement of agents. Unity provides a function to place an agent on a navigation mesh and determine the current vertical position of agents (
https://docs.unity3d.com/ScriptReference/AI.NavMesh.SamplePosition.html, accessed on 29 July 2021). However, the function casts rays which can be very expensive if you have hundreds or thousands of agents for which vertical position should be determined each time. Moreover, the navigation mesh and triangles representing it do not reflect correctly the actual stairs, which can result in the wrong calculation of agents’ speed (
Figure 5). Therefore, we identify a heightmap for each surface with tilted edge boundaries.
The height of flat spaces is calculated as an average value considering all vertices belonging to that mesh. Regarding sloped surfaces, we use its Minimum Bounding Box (MBB) to create a horizontal grid, where we identify corresponding heights on the ground for each cell. This is done to substitute casting always rays to a ground surface during simulations for vertical placement. Ray casting can be costly; thus, we store the information as a matrix of values covering the whole sloped area. In this way, based on one pivot point of a space (e.g., one point of MBB) and a pedestrian’s horizontal position we can easily determine the pedestrian’s vertical position. To determine a heightmap for sloped surfaces we cast rays from the top using the vertical extension of MBB. If the rays are hitting more than one object in its path, we check if the distance between the intersecting point and the triangles belonging to the sloped space is smaller than 0.5 m.
Apart from the identification of heights for accurate placement of pedestrians, parameters representing stairs should be identified to calculate the speed of pedestrians correctly. Stairs are usually represented via slope or riser height and tread depth, width, and steps count [
30]. The main problem related to stairs are configurations that they can take, making the identification of these parameters more difficult. Using BIM models these parameters can be found, but even then, they can be wrong and affect the simulation. For example, in
Figure 6 treads overlap by looking from the top, which can result in using a wrong value as a tread depth. This and many other stair configuration examples (e.g., spiral stairs, stairs without risers or stairs which are inconsistent on some parts) make the process of calculating correct agents’ speed more challenging.
In the further text, we will concentrate only on more regular stair configurations such as in
Figure 6, where both treads and risers are present. The goal is to determine their dimensions and use them afterwards in agents’ speed determination. To do so, we classify the hitting points that we detected for the heightmap. The grid resolution to perform the ray casting is 24 cm (i.e., minimum tread depth size), which is also in line with other researchers using 25 cm [
26]. We use the centroid of cells to shoot the rays (
Figure 7).
If 3D objects representing stairs do not have this information, we need to first classify points belonging to stairs. When casting the rays, we can group points based on objects that are hit. Thus, for each step, we can know all hit points. From each point (i.e., cell’s centroid) a horizontal filter with a size of 5 × 5 is considered to check the surrounding heights and cover an area that can identify even stairs with larger tread depths (e.g., maximum recorded tread depth in buildings is 33 cm). Considering all points obtained through the filter and the central point, we can identify height differences between them. If there are two or more height differences from the central point that are between 10 and 20 cm (i.e., minimum and maximum riser size) and ±2 cm from each other, we can say that the central point is belonging to stairs and height differences are riser heights from that point (
Figure 7). The presented thresholds for treads and risers are based on many stair configurations available in practice and standards [
30,
31]. In the case of having the metadata for stairs (e.g., data coming from BIM models), we can immediately know all points belonging to stairs.
Based on the classification, we can accurately determine the riser height for each point, but the tread depth is still unknown. To identify it, we send horizontally two rays one from a point on a step below to the considered point, and one from the considered point to a point on a step above (
Figure 8). As a result, we get two points hitting the risers P1, P2 as well as their normals N1, N2 based on the surface that is hit. By using the opposite direction of these normals the process is repeated and new points P3, P4 are determined. Considering P
_{1}P
_{3} and P
_{2}P
_{4} pairs we can create L
_{1} and L
_{2} lines, respectively. Now, we can calculate the minimal distance to the stair risers. To determine the rest of the distances we identify the closest points P5 and P6 to line segments L1 and L2 formed by the two points that hit each riser. This allows us to calculate tread depth for each classified point. For example, we can use the same approach if we have circular or some irregular stairs, but the tread depth will depend on a pedestrian’s walking direction. The width of stairs can be calculated based on the intersection of L
_{1} and L
_{2} with border edges.
3.5. Spatial Indexing
A spatial index is a data structure that enables the quick execution of spatial queries. This technique is commonly used in spatial databases, but it is required too for crowd simulations [
32]. The main goal of using spatial indexing is to bypass a sequential scan of all objects (e.g., border edges and agents) and only check objects in pedestrian’s proximity to calculate the impact. There are many types of indexing, which support using partitioned search trees such as quadtrees, kd trees, radix trees, Rtrees, and so on. For pedestrian dynamics, searching radiuses are consistent which can indicate that uniform size partitioning should be more performant. As a result, we utilise gridbased spatial indexing (
https://github.com/bartofzo/SuperGrid2D, accessed on 29 July 2021) allowing us to optimise spatial queries such as finding the closest point on a line segment and identifying agents within a searching radius. The optimal grid size should be two times the size of a searching radius to inspect objects only from a maximum of four cells (
Figure 9). At the same time, we can perform queries such as the visibility of the next route point, check if an agent is within a space (i.e., using point in polygon techniques) and check intersections between lines. Objects can also be divided into static (e.g., boundary edges) and dynamic (e.g., pedestrians’ position), enabling quicker execution of queries related to static objects due to using arrays instead of lists. If objects are organised in 3D space, octrees can be used (
https://github.com/Nition/UnityOctree, accessed on 29 July 2021). For example, the user can decide to use an octree to keep the positions of pedestrians, while spaces are still stored in 2D.
3.6. Crow Simulation
The most known pedestrian models are based on social forces [
33,
34] which can recreate certain phenomena such as queue formation and arching, shock waves and bottleneck effect [
35,
36]. The model integrates into pedestrians’ movements the selfdriven force
${\overrightarrow{f}}_{i}$, the forces by other pedestrians
${\overrightarrow{f}}_{ij}$, and
${\overrightarrow{f}}_{io}$ that represents the force by obstacles such as walls, pillars, furniture, etc. The total force
${\overrightarrow{f}}_{i}$ applied on a pedestrian can be formulated as Equation (1).
Based on this social force model we need to identify several parameters that we will use during simulations. Thus, parameters that correspond to the largest searching radius for other pedestrians in pedestrian’s proximity, maximum distance to obstacles that have a meaningful impact on pedestrians, and minimal width between obstacles that pedestrians can go through. For the first two parameters, we consider Equation (2) which checks whether the position would change significantly (i.e., we use the significance of 95%) within one frame if all pedestrians and obstacles or a limited number of them are considered based on specific searching radiuses. For a delta time of 0.05 s, we identified that we should use 1.25 and 1.05 m to search for pedestrians and obstacles, respectively. To identify the minimal width that pedestrians can pass through, we used two objects and change the gap between them identifying a distance of 1.1 m that is possible. This value is used to create the second navigation mesh that is considered as a network to find a path between points and make sure that an agent will not get stuck somewhere. For evacuation purposes, this aspect was investigated and a social force model for narrow exits was proposed [
37].
Regarding stairs, an equation capturing the relationship between the walking speed on stairs and the stairs’ characteristics was suggested [
38]. Equation (3) takes into account treaddepth T and riserheight R, in centimetres, to calculate the vertical speed of pedestrians
${\overrightarrow{v}}_{v}$ in meters per minute. The horizontal speed can be simply determined by dividing the vertical speed by the tangent between riser and tread. In this way, the impact of walking on stairs is determined and applied to pedestrians’ horizontal speed.
To perform a pedestrian simulation all discussed aspects should be integrated. Algorithm 2 shows all the steps performed in a fixed time frame for all pedestrians in the Unity game engine. We present the algorithm as having 8 components for easier explanation. The first part is to calculate the forces that impact the speed of pedestrians, where pedestrians and border edges from other connected surfaces are considered if a pedestrian is nearby those surfaces. After that, the impact of waking on stairs is calculated. The identification of a new pedestrian position is determined. Based on the previous and next position, we check if a connecting edge is intersected to change the pedestrian’s surface. If walking on a sloped surface, the vertical position is updated. The identification of a visible goal ahead on the path is determined, and direction towards it is calculated. For the path following we use the approach suggested by Reynolds [
39]. Last but not least, the next direction of a pedestrian is determined and all parameters are updated for the next iteration.
Algorithm 2 Pedestrian Simulation

Input: Pedestrians, surfaces, gridPed, gridBorders, constants (c) Output: Update pedestrian parameters $\mathit{f}\mathit{o}\mathit{r}\mathit{e}\mathit{a}\mathit{c}\mathit{h}ped\mathit{i}\mathit{n}pedestrians\mathit{l}\mathit{o}\mathit{o}\mathit{p}$ Calculate impact force ${\overrightarrow{f}}_{sd}=SelfDrivenForce\left(ped\right)$ ${\overrightarrow{f}}_{ij}=PedImpact\left(ped,PedestriansNearby\left(ped.pos,gridPed,c.pedRadius\right)\right)$ ${\overrightarrow{f}}_{io}=ObsImpact\left(ped,EdgesNearby\left(ped.pos,gridBorders,c.obsRadius\right)\right)$ ${\overrightarrow{f}}_{i}$ = ${\overrightarrow{f}}_{sd}$ + ${\overrightarrow{f}}_{ij}$ + ${\overrightarrow{f}}_{o}$ $velDev=ped.velDev+{\overrightarrow{f}}_{i}\ast c.dTime$ ${\overrightarrow{v}}_{h}=LimitingSpeed\left(velDev,ped.maxSpeed\right)$ Stairs surface impact $\mathit{i}\mathit{f}ped.surf.type==sloped\mathit{t}\mathit{h}\mathit{e}\mathit{n}$ $\mathit{i}\mathit{f}ped.cell.type==stair\mathit{t}\mathit{h}\mathit{e}\mathit{n}$ ${\overrightarrow{v}}_{h}\ast =StairImpact\left(ped.cell\right)$ Identify new pedestrian horizontal position $posDev={\overrightarrow{v}}_{h}\ast c.dTime$ $pedNewPos.x=ped.pos.x+posDev.x$ $pedNewPos.y=ped.pos.y+posDev.y$ Update pedestrian surface $connectingEdge=IntersectConnectingEdge\left(ped,pedNewPos\right)$ $\mathit{i}\mathit{f}connectingEdge!=null\mathit{t}\mathit{h}\mathit{e}\mathit{n}$ $ped.surf=GetSurface\left(ped.surfId,connectingEdge\right)$ $ped.pos=pedNewPos$ $surf=surfaces\left[ped.surfId\right]$ Update pedestrian vertical position $\mathit{i}\mathit{f}ped.surf.type==sloped\mathit{t}\mathit{h}\mathit{e}\mathit{n}$ $ped.cell=GetCell\left(surf,ped.pos\right)$ $ped.pos.z=cell.z$ Follow path $ped.goal=GetNextGoal\left(ped.pos,ped.goal\right)$ $ped.dir=GetDirection\left(ped.goal,ped.pos\right)$ $\mathit{e}\mathit{n}\mathit{d}\mathit{l}\mathit{o}\mathit{o}\mathit{p}$ Update pedestrians’ positions $UpdateParameters\left(gridPed,pedestrians\right)$ 
$SelfDrivenForce\left(ped\right)$: calculating the selfdriven force of a pedestrian.
$PedImpact\left(ped,pedestrians\right)$: calculating the impact of other pedestrians on the pedestrian under consideration.
$ObsImpact\left(ped,edges\right)$: calculating the impact of an obstacle on a pedestrian.
$LimitSpeed\left(velDev,maxSpeed\right)$: limiting pedestrian’s velocity to its maximum speed.
$StairImpact\left(cell\right)$: calculating stair’s impact.
$IntersectConnectingEdge$($surfId,pedNewPos$): check if a connecting edge is intersected to change a surface.
$GetSurface\left(surfId,\text{}edge\right)$: changing surface of a pedestrian.
$GetCell\left(surf,ped.pos\right):$identifying a cell where a pedestrian is positioned, and its properties such as elevation and slope.
$GetNextGoal\left(pos,goal\right)$: identifying the next visible goal to move towards.
$GetDirection\left(goal,pos\right)$: get normalised direction for a pedestrian.
$UpdateParameters\left(pedestrians\right):$updating pedestrian positions in the grid for a new iteration.