1 Introduction
Global navigation often depends on efficient path planning which is thus crucial in various applications from planning motions for real robots to controlling autonomous agents in virtual environments. This paper focuses on the computation of optimal paths for agents in virtual environments. While several approaches have been introduced in recent years for computing paths among obstacles, the focus has mostly been on the efficiency of computation, without attention to providing global optimality guarantees.
This situation reflects the fact that computing optimal paths, or Euclidean shortest paths, efficiently is not a trivial task. One way of computing Euclidean shortest paths is by constructing a visibility graph of the environment and then running graph search on it [2]. Unfortunately in the worst case the number of edges in the visibility graph is , where is the number of vertices describing the obstacles, which can significantly slow down path queries based on search algorithms running on the graph.
Shortest Path Maps (SPMs) are constructed with respect to a “source point”, and like Voronoi diagrams, SPMs partition the space into regions. Whereas regions in Voronoi diagrams share the same closest site, regions in SPMs share the same parent points along the shortest path to the source, which means that an SPM encodes shortest paths between a specified source and all other points in a particular planar environment.
While SPMs have been studied in Computational Geometry for several years, they have not been popular in practical applications. This is probably because their computation involves several complex steps, even when considering nonoptimal construction algorithms. The proposed GPU computation approach greatly simplifies the process of building SPMs, allowing them to be easily computed with rasterization procedures triggered from OpenGL shaders without any precomputation.
Our approach introduces several advantages. While a CPU implementation requires some point localization technique in order to determine the region containing a query point, in the proposed GPU approach point localization is reduced to a simple constant time grid buffer mapping. After this mapping, since every point in the SPM has direct access to its parent point along the shortest path to the closest source, agents have direct access to the next point to aim at when executing their trajectories. In addition, if the entire shortest path is needed it can be retrieved in linear time with respect to the number of vertices in the shortest path.
Our approach is based on the idea of cone rasterization from source points, line segment sources, and obstacle vertices. Unlike previous work of our group [1], in the presented method we do not require precomputation of the shortest path tree of the environment and we also do not need to create any geometry for the rasterized cones. Instead we use dedicated fragment shaders to simply fill in the pixels that have direct lineofsight to the vertices, improving computation speed and also eliminating errors that were introduced from discretizing cone geometry into triangles.
Our shaders operate on the coordinates of the input vertices and when the buffer resolution is adequate our maps produce exact results not affected by the grid resolution. Our approach is able to compute shortest path maps both with source points and with line segment sources, and can produce relatively complex dynamicallychanging SPMs at realtime rates.
2 Related Work
Our work is related to different areas, from path planning and GPU computing to the computation of distance fields. The related work review below is organized according to these areas.
Approaches to Path Planning
Researchers in AI usually approach path planning with discrete search methods on gridbased environments, sometimes making use of hierarchical representations. While several advancements on discrete search methods have been explored (heuristics, dynamic replanning, anytime planning, etc.), only a few attempts have focused on approximating Euclidean shortest paths
[3], and still not guaranteeing to achieve global optimality. A similar situation can be also observed in Computer Animation. While several approaches have been introduced in recent years, the stateoftheart has focused mostly on the efficiency of computing collisionfree paths with the use of navigation meshes [4, 5, 6], and has mostly neglected addressing global optimality.One way to compute globallyoptimal Euclidean shortest paths is to first build the visibility graph of the environment and then run a graph search algorithm on it [7, 2]. Previous work [8] has presented specific cases where the problem can be solved with greedy time algorithms without explicitly building the entire visibility graph. However, a visibility graph can have nodes, where is the number of vertices describing the environment, and a new graph search on it must be computed for each path query [9, 10, 11]. It is therefore difficult to develop efficient methods based on visibility graphs.
Shortest Path Maps The first method related to Shortest Path Maps (SPMs) has worstcase time complexity [12], where is the “illumination depth”, a parameter bounded above by the number of different obstacles touching a shortest path. Later, the first worstcase subquadratic algorithm was proposed applying the continuous Dijkstra expansion, which naturally leads to the construction of SPMs [13]. A nearly optimal algorithm for computing SPMs has been proposed taking optimal time to preprocess the environment, allowing distancetosource queries to be answered in time, and paths to be returned in time, where is the number of turns along the path [14].
Unfortunately, these methods and all the known algorithms with good theoretical running times involve complex techniques and data structures that overburden their practical implementation in applications. In contrast, our GPUbased approach is relatively simple and has been able to produce SPMs of complexity not seen before in previous work. Our benchmarks also demonstrate faster times in comparison to a previous GPU approach to compute SPMs [15] (Table 2).
The idea of using shader rasterization as an efficient way to propagate wavefronts in the GPU was introduced by our group in 2014 [1]. The method we present here significantly improves the approach in multiple ways: 1) we eliminate the need to precompute the visibility graph and SPT, and in doing so are able to easily address maps with multiple source points and line segment sources, 2) the speed of the method is improved with a new computation of shadow areas, and 3) we no longer need to construct actual geometry for the rendered cones simulating wavefront expansions; instead we simply employ a dedicated fragment shader to directly fill in the relevant pixels, simplifying the process and most importantly eliminating error accumulation from cone discretization.
GPU Methods Previous work has investigated rasterizationbased GPU techniques for related applications, in particular for computing Voronoi diagrams [16]. Although we also employ rasterization techniques to accumulate distances, our approach introduces the significant insight of placing primitives at accumulated heights in order to compute SPMs and represent optimal paths.
GPU methods have also been explored for path planning from gridbased searches, for example by performing multiple shortrange searches in parallel [17], by parallelizing expansions perpixel on uniform grids [18] and based on a quadtree scheme [19]. However, gridbased approaches do not address global optimality in the Euclidean sense. We nevertheless compare reported times from some of these works with our approach (Table 2) and show that in addition to global optimality our method is also faster in most cases.
Distance Fields on Meshes Computing distance fields on meshes is a problem closely related to computing SPMs. The approach of Mitchell et al. [20] propagates front windows while solving front events during propagation, taking time. It is possible to perform window propagation without handling all events [21, 22], reaching time but in practice processing a high amount of windows. Window prunning techniques have been investigated to improve practical running times [23]. While a benchmark between our approach and these methods is left for future work, our approach represents a simpler solution for addressing the computation of distance fields and SPMs.
Summary While several algorithms exist for computing globallyshortest paths and shortest path maps, available methods are either somewhat complex for practical use or too expensive for realtime applications. The presented method is the first to be implemented entirely with GPU shaders, it does not require any precomputation, and it enables multiagent navigation based on paths with global optimality, a characteristic which has been neglected in simulated virtual environments developed to date.
3 MultiSource Shortest Path Maps
We first describe the base SPM case with multiple source points. Let be source points in the plane, such that , , and where defines a polygonal domain containing all sources. In all our examples is a rectangular area delimiting the environment of interest, and the GPU framebuffer will be configured to entirely cover . A set of polygonal obstacles , with a total of vertices, is also defined in such that shortest paths will not cross any obstacles in .
Given source points the respective SPM will efficiently represent globallyshortest paths , which are optimal collisionfree paths from any point to its closest source point in the geodesic sense, i.e., is the source that minimizes , where denotes the length of the shortest path , . Our SPM also efficiently represents the values of for all pixels of the framebuffer by storing them in a dedicated buffer created in the OpenGL pipeline. This representation gives us direct access to the distance field of the environment and allows us to easily draw the white isolines that can be seen in most of the figures in this paper. Depending on the situation source points can represent the start or the end point of a path. In most of the presented examples sources will represent goals to be reached by agents placed anywhere in the environment.
The plane represented by the framebuffer is located at . The basic idea of our method is to rasterize “clipped cones” with apices placed below source points and obstacle vertices, at heights equal to their values, so that the final rendered result from an orthographic topdown view is the desired SPM (see Figure 2).
The process is implemented as follows. An array containing the source points and obstacle vertices is stored in the GPU. At each iteration one point is copied into a reserved position of a data array where it will be used to rasterize a clipped cone. The point that is selected to generate the clipped cone at each iteration is referred to as that iteration’s “generator.” Each point is processed once, such that the result is given after iterations.
Important to our approach is the fact that we do not actually need to create discretized geometry for representing and then drawing cones. Instead we simply fill in pixels that have direct lineofsight to the generator, which is an equivalent operation. A cone apex is located below the generator relative to the plane. The depth values of the affected pixels increase proportionally to their Euclidean distances to the apex, as with the slope of a cone. Because the depth is accumulated over iterations, it represents the distance back to the source point along the shortest path, . When all clipped cones are drawn at their respective heights, the GPU’s depth test will maintain, for each pixel, the correct parent generator point, which is the immediate next point on the shortest path from that pixel to the closest source point. We say that a cone “loses” to another at a given pixel when its depth is greater, leading it to be discarded in favor of the “closer” cone.
3.1 Algorithm
Given polygonal obstacles with vertices and source points, , the total number of vertices to be processed is . These points are stored in array DataArray of size .
The extra position is reserved for storing at each iteration the current generator that will be used for cone rasterization. By convention this is the first position in the array, DataArray[0], and will be referred to as . Once DataArray is constructed, it is stored in the GPU as a Shader Storage Buffer Object.
Each of the positions in DataArray stores:
: The original coordinates of the point in .
Status : A flag that can be equal to Source for sources, Obstacle for obstacle vertices, or Expanded for points which have already generated a cone.
Distance : The current known shortest path distance to the closest source point, . This will always be 0 for source points and is initially undetermined for obstacle vertices.
ParentId : Array index into DataArray of the current parent point, which is the next point on the shortest path back to the closest source point. Since sources have no parent point, by convention they simply store their own index.
The framebuffer stores similar information for the pixels. For each pixel, its red and green components store the and coordinates of its parent point (equivalent to DataArray[ParentId]), its blue component stores (equivalent to Distance), and its alpha channel stores either if the pixel has yet to be reached by a cone or 0 otherwise. When the buffer is drawn, the color of each pixel is mapped in the following way: is used as the red component, is used as the green component, and the blue component is zeroed. Although this mapping is arbitrary, it allows to visualize the location of a region’s parent from the red and green intensities.
The SPM generation consists of four steps which repeat times such that each point is processed once. The steps are presented in Procedures 14. The hat notation (e.g.,
) denotes unit vectors.
Step 1 is a search in DataArray where the position with the smallest Distance is copied into the reserved position of the array, index 0. Only points which have not yet generated a cone (Status Expanded) are considered in this search, and once a point is chosen its status is updated to Expanded so that it cannot be processed again. The point that is chosen becomes , the current generator. This step can be skipped in the first iteration of the algorithm as we can just start with one of the source points.
Step 2 is to generate a shadow area in order to solve visibility constraints. Using a geometry shader, we draw into a stencil buffer three triangles behind every obstacle line segment that is frontfacing with respect to , in a manner illustrated in Figure 3. Any pixel covered by one of these triangles is considered to be in shadow. The resulting buffer is used as a stencil buffer in the next step. Three triangles is the minimum number of triangles needed to cover all possible shadow shapes. We use constant , which stands for shadow vector factor, when computing the points that make up the triangles. This constant must be large enough to handle shadows of all sizes. Since our coordinates are OpenGL normalized coordinates in the range, a value of 4 is always enough.
Step 3 draws a clipped cone with the generator directly above its apex along the axis. As previously stated, we do not actually create geometry for the cone but instead simply run a fragment shader over every pixel on the screen. The pixels that are not in shadow have direct lineofsight to , so they calculate their Euclidean distance to and add it to ’s accumulated distance, Distance. If this sum is smaller than the current Distance of the pixel (from the cone of a previous ), then its Distance is updated and its ParentId is set to ’s index.
Finally, step 4 is to update the Distance of all points visible from the current generator, in a way similar to step 3. Each point not in shadow calculates its distance to plus ’s Distance, and if that sum is smaller than its previous Distance it stores the new Distance and ’s index in its ParentId. The reason steps 3 and 4 are separate is because step 3 is updating the framebuffer, while step 4 is updating the DataArray.
After all points have been processed, which means iterations of steps 14, the result in the framebuffer will be the desired SPM. Examples of SPMs with a single source point are shown in Figure 7 and with multiple source points are shown in Figure 8.
The search in step 1 is because it is a sequential search in the array of size . Step 2 is , where is the resolution of the framebuffer, because in the worst case there will be enough triangles to render every pixel in the buffer. Step 3 is likewise . Step 4 takes time because potentially every obstacle vertex can have its distance updated. Steps 2, 3, and 4 are however executed in parallel by the GPU. Step 1 leads to a quadratic overall algorithm because the steps are iterated times; however, we have not observed any need to optimize this step as in our experiments this step represented about 1% of the total runtime cost.
4 Segment Sources
Line segment sources are one natural extension to our method, and are interesting as sources for what they can represent. Many goals in realworld scenarios are not single points but line segments, for example the finish line of a race, the thresholds of doorways or hallways, and the boundary of a coastline can all be represented as polygonal line segments. For instance, many of these cases appear when planning evacuation routes from buildings. Being able to compute SPMs with segments as sources allows us to maintain global optimality in these practical situations.
Consider that we now have additional line segment sources , such that , , consists of two endpoints . The SPM will then efficiently represent globallyshortest paths , which are now optimal collisionfree paths from any point to the closest reachable point on its closest segment source , in the geodesic sense.
Every line segment can have critical points, . A critical point denotes a point on the segment onto which at least one obstacle vertex projects. The obstacle vertex must have direct lineofsight to the segment. Critical points are where the visibility of the scene changes with respect to the segment and are useful because in practice every path that passes through the corresponding obstacle vertex will have its shortest path reach the line segment on that critical point (see Figure 4). For each , first the two endpoints of the segment create two entries in DataArray which are treated identically to source points. Then, further entries are created, where is equal to the number of critical points segment possesses. Every one of these entries stores two pairs of coordinates rather than just one, with Status set to SourceSegment, to represent the subsegments of . If , then the two endpoints are simply used because the segment has no subsegments. If , then every adjacent pair of points, including both endpoints and critical points, will create an entry in DataArray.
The distance calculation of the SPM generation process is different when the generator’s Status is marked as SourceSegment. It is necessary to determine whether the point being updated is closer to one of the endpoints of the subsegment, or somewhere inbetween. If it is closer to one of the endpoints, the distance is simply the distance to that endpoint. Otherwise, the distance is equal to the distance between the point and its projection on the subsegment.
The described changes are sufficient to handle both points and line segments as sources. Figure 5 shows additional examples of SPMs with line segment sources.
5 Results and Discussion
Comp.  Comp.+Transfer  
Map name  P  V  Time (s)  Time (s) 
Simple1  3  13  0.0011  0.0209 
Simple2  4  16  0.0018  0.0221 
Concave1  2  12  0.0011  0.0207 
Concave2  13  96  0.0088  0.0465 
Spiral  1  38  0.0022  0.0274 
SpmEx1  3  15  0.0016  0.0215 
SpmEx2  13  91  0.0100  0.0470 
Profiling0  4  16  0.0014  0.0221 
Profiling1  16  64  0.0054  0.0404 
Profiling2  36  144  0.0456  0.0858 
Profiling3  64  256  0.1251  0.1680 
Profiling4  100  400  0.2701  0.3099 
Profiling5  196  784  0.8564  0.8863 
Profiling6  400  1600  2.7371  2.8070 
Method  CPU  GPU  Resolution  O  V  Optimality  Time (s) 
Dynamic Search using uniform grid (1)  –  GF GT 650M  1024x1024  –  –  Average  32.93 
Dynamic Search using uniform grid (1)  –  GF GTX 680  1024x1024  –  –  Average  21.25 
Dynamic Search using uniform grid (2)  –  –  1024x1024  –  –  Average  14.12 
Dynamic Search using quadtree (2)  –  –  1024x1024  –  –  No  0.04 
CUDAbased SPM (3)  i7 2.66 GHz  GF GTX 580  1024x1024  64  256  Best  1.42 
Previous Shaderbased SPM (4)  i7 3.40 GHz  GF GTX 570  1024x1024  64  256  Best  0.110.17 
Current Shaderbased SPM (5)  i7 3.40 GHz  GF GTX 970  1000x1000  64  256  Best  0.13 
We evaluate the performance of our method with several benchmarks using a framebuffer resolution of 1000x1000 on a Nvidia GeForce GTX 970 GPU and an Intel Core i7 3.40 GHz computer with 16GB of memory.
Table 1 shows average execution times for computing 100 singlesource SPMs with random source points in . The table shows times both with and without transferring the resulting SPM back to the host memory.
Figure 6 charts out computation times on the Profiling maps. These maps are composed of uniform rows of square obstacles (in the same layout as the rightmost environment in Figure 7) with large visible areas from all points in the map. This represent a worstcase scenario for our method because the amount of points that have to be considered at each step is almost the maximum. Still we observe that the increase in computation time is close to linear.
Table 2 shows that our method is also able to compute SPMs and return optimal paths faster than some previous GPUbased methods which are gridbased and nonoptimal. For example, Kapadia et al. [18] gives times to plan paths on a grid environment with similar resolution to the buffer used in our benchmarks, 1024x1024, as follows: between 32.931 and 49.126 seconds for a GT 650M and between 21.246 and 30.778 seconds for a GTX 680. While our benchmarks used a newer GTX 970 GPU, we nevertheless believe that a new card would not offer the significant speed up to match even the 2.80 second running time we achieved on our most complicated map. In a later work a quadtree was employed to significantly speed up the computation [19], but sacrificing optimality even more in the process.
5.1 Discussion
Although our method uses a framebuffer grid and thus approximates points to the center of the closest pixel when drawing maps, the elimination of discretized cone geometries leads to all distance calculations being computed using the original coordinates of the obstacle vertices. This means that there is no accumulation of error introduced by our method when comparing distances and when integrating the lengths of computed paths. In practice, when the chosen framebuffer resolution correctly represents an environment, only the region borders formed by collision fronts might be affected by the pixel approximation. Regions dictate which parent to first take when constructing a shortest path to the closest source, so for a query point that falls on a region border the first vertex choice is subject to a maximum error equal to half a pixel’s diagonal. However even in this case it is possible to eliminate the error by comparing all possible neighboring parent points and choosing the one that is truly the closest to the query point.
Besides being resolutionsensitive the main limitation of our method is that it may only be suitable for realtime simulations in environments of moderate size. Our method is slower than stateoftheart path finding solutions that focus on speed of computation instead of global optimality [6]. However, our performance times have potential to increase over time given the rapid expansion of GPUbased computing hardware and techniques.
6 Conclusions
We have presented in this paper improved shaderbased GPU methods for computing shortest path maps, without the need of precomputation, and addressing maps with multiple source points and line segments as sources. These capabilities address practical reallife situations and our benchmarks show that our method outperforms comparable approaches in most cases.
Our approach opens new directions for incorporating navigation information within the traditional graphics pipeline by introducing mapping techniques that can instantly guide agents in multiagent simulations, with buffers directly storing distances to the closest source and providing the next point to aim for from any point in the environment.
Acknowledgements This research was partially sponsored by the Army Research Office under Grant Number W911NF1710463. The views and conclusions contained in this document are those of the authors and should not be interpreted as representing the official policies, either expressed or implied, of the Army Research Office or the U.S. Government. The U.S. Government is authorized to reproduce and distribute reprints for Government purposes notwithstanding any copyright notation herein. The authors also thank Prof. Joseph S. B. Mitchell for several discussions on the topic of this paper.
References
 [1] C. Camporesi and M. Kallmann, “Computing shortest path maps with gpu shaders,” in Proceedings of the Seventh International Conference on Motion in Games, ser. MIG ’14. New York, NY, USA: ACM, 2014, pp. 97–102. [Online]. Available: http://doi.acm.org/10.1145/2668064.2668092
 [2] M. De Berg, O. Cheong, M. Van Kreveled, and M. Overmars, Computational geometry: algorithms and application. Springer, 2008.

[3]
A. Nash, K. Daniel, S. Koenig, and A. Feiner, “Theta*: Anyangle path planning
on grids,” in
Proceedings of the 22Nd National Conference on Artificial Intelligence  Volume 2
, ser. AAAI’07. AAAI Press, 2007, pp. 1177–1183. [Online]. Available: http://dl.acm.org/citation.cfm?id=1619797.1619835  [4] R. Geraerts, “Planning short paths with clearance using explicit corridors,” in IEEE International Conference on Robotics and Automation, 2010, pp. 1997–2004.
 [5] R. Oliva and N. Pelechano, “Neogen: Near optimal generator of navigation meshes for 3d multilayered environments,” Comput. Graph., vol. 37, no. 5, pp. 403–412, Aug. 2013. [Online]. Available: http://dx.doi.org/10.1016/j.cag.2013.03.004
 [6] M. Kallmann, “Dynamic and robust local clearance triangulations,” ACM Transactions on Graphics (TOG), vol. 33, no. 4, 2014.
 [7] N. J. Nilsson, “A mobius automation: An application of artificial intelligence techniques,” in Proceedings of the 1st International Joint Conference on Artificial Intelligence, ser. IJCAI ’69. San Francisco, CA, USA: Morgan Kaufmann Publishers Inc., 1969, pp. 509–520.
 [8] D. T. Lee and F. P. Preparata, “Euclidean shortest paths in the presence of rectilinear barriers,” Networks, vol. 14, no. 3, pp. 393–410, 1984. [Online]. Available: http://dx.doi.org/10.1002/net.3230140304
 [9] E. Welzl, “Constructing the visibility graph for line segments in O time,” Information Processing Letters, vol. 20, no. 4, 1985.
 [10] M. H. Overmars and E. Welzl, “New methods for computing visibility graphs,” in Proceedings of the Fourth Annual Symposium on Computational Geometry, ser. SCG ’88. New York, NY, USA: ACM, 1988, pp. 164–171.
 [11] J. A. Storer and J. H. Reif, “Shortest paths in the plane with polygonal obstacles,” J. ACM, vol. 41, no. 5, pp. 982–1012, Sep. 1994.
 [12] J. S. B. Mitchell, “A new algorithm for shortest paths among obstacles in the plane,” Annals of Mathematics and Artificial Intelligence, vol. 3, no. 1, pp. 83–105, 1991. [Online]. Available: http://dx.doi.org/10.1007/BF01530888
 [13] ——, “Shortest paths among obstacles in the plane,” in Proceedings of the Ninth Annual Symposium on Computational Geometry, ser. SCG ’93. New York, NY, USA: ACM, 1993, pp. 308–317.
 [14] J. Hershberger and S. Suri, “An optimal algorithm for euclidean shortest paths in the plane,” SIAM J. Comput, vol. 28, pp. 2215–2256, 1999.
 [15] E. Wynters, “Constructing shortest path maps in parallel on GPUs,” Proceedings of 28th Annual Spring Conference of the Pennsylvania Computer and Information Science Educators, 2013.
 [16] K. E. Hoff, III, J. Keyser, M. Lin, D. Manocha, and T. Culver, “Fast computation of generalized voronoi diagrams using graphics hardware,” in Proceedings of SIGGRAPH ’99. New York, NY, USA: ACM Press/AddisonWesley Publishing Co., 1999, pp. 277–286. [Online]. Available: http://dx.doi.org/10.1145/311535.311567
 [17] M. Henderson, J. T. Kider, M. Likhachev, and A. Safonova, “Highdimensional planning on the gpu,” in 2010 IEEE International Conference on Robotics and Automation, May 2010, pp. 2515–2522.
 [18] M. Kapadia, F. M. Garcia, C. D. Boatright, and N. I. Badler, “Dynamic search on the GPU,” in Intelligent Robots and Systems (IROS), 2013 IEEE/RSJ International Conference on, Nov 2013, pp. 3332–3337.
 [19] F. M. Garcia, M. Kapadia, and N. I. Badler, “Gpubased dynamic search on adaptive resolution grids,” in Robotics and Automation (ICRA), 2014 IEEE International Conference on, May 2014, pp. 1631–1638.
 [20] J. S. B. Mitchell, D. M. Mount, and C. H. Papadimitriou, “The discrete geodesic problem,” 1987.
 [21] J. Chen and Y. Han, “Shortest paths on a polyhedron,” in Proceedings of the Sixth Annual Symposium on Computational Geometry, ser. SCG ’90. New York, NY, USA: ACM, 1990, pp. 360–369. [Online]. Available: http://doi.acm.org/10.1145/98524.98601
 [22] S.Q. Xin and G.J. Wang, “Improving chen and han’s algorithm on the discrete geodesic problem,” ACM Trans. Graph., vol. 28, no. 4, pp. 104:1–104:8, Sep. 2009. [Online]. Available: http://doi.acm.org/10.1145/1559755.1559761
 [23] Y. Qin, X. Han, H. Yu, Y. Yu, and J. Zhang, “Fast and exact discrete geodesic computation based on triangleoriented wavefront propagation,” ACM Transactions on Graphics, vol. 35, no. 4, pp. 125:1–125:13, Jul. 2016. [Online]. Available: http://doi.acm.org/10.1145/2897824.2925930
Comments
There are no comments yet.