top of page
  • Joshua Henderson

Stocking Shelves at Swolemart

HELLO SHOPPERS! Here I will be outlining the problems I have been facing while creating my procedurally stocked shops aka my COURSES in PROJECT WINTON. I will talk about debugging meshes in Unity, barycentric coordinate systems and the majesty of UV space.


Don't worry, there are lots of pictures.


Thumbnail screenshot from the original demo Swolemart level.

Each course in PROJECT WINTON is set in one the mall's many stores.


My guinea pig course is SWOLEMART - a supermarket level. This level was the original course from the game jam demo and has underwent major changes since then, both under the hood, and cosmetically.


One of the systems that needed a major upgrade is the shopping list and shelf population system. Which are pretty integral to the framework of the game. In the demo, the shopping list is generated randomly, and rather clunkily just chooses from the set of shelves made available to it.


The old hand-placed system was clunky, and tedious.

I identified several simple yet key design pillars that the shelving system would need to uphold.


  • Should be procedural - the course should be capable of handling both designed course hole placements, and random generation of holes.


  • Should be agnostic to the geometry, the number of items and the density of the clusters should be independent of the guide mesh.


  • Should allow me to populate multiple shelves at once.


  • Visual and intuitive. The shelves should be able to be easily populated via the editor.


 

Enter SHELVES!


The SHELF system that I have come up with has many different components, but what makes it really purr are the CATEGORIES, and the UV-world space conversion based on BARYCENTRIC COORDINATES. Each shelf is given a GUIDE MESH that is just the 3D surface of the shelf extracted to its own object, and assigned in the inspector as a SHELF SLOT.


A cylindrical and fridge shelf

 

Why not just spawn it on the vertices of the guide mesh?


This works reasonably well actually. Especially for simple, planar guide meshes. These are easy to create in Maya and pose no complications when spawning items to the vertex positions, as they are all easily grabbed via the mesh vertex array. In fact, the game worked this way for a long time!


BUT...


Because I grab the vertex positions directly, I must always create a mesh that exactly represents each possible spawn position. This caused a few problems that I ultimately couldn't live with.



  • Large, or irregularly proportioned items may clip into each other or otherwise be orientated on the shelf in an odd way.


  • Especially in the case of curved guide meshes, I had limited agency over the density of the spawn positions, as the smooth edges of the cylinder necessitated more vertices, so they would cause items to become bunched up.


  • Having to create exact mesh guides ahead of time was still annoying


Without control over the density of each shelf, items become mashed together

 

OK - then just pass in coordinates based on the outer vertices?


ROWS AND COLUMNS

The next system was functionally very similar, in that it took a guide mesh and used their positions to spawn objects. The difference was that the system used the limits of the mesh only, and figured out the rows and columns by dividing the mesh up into the required number of sections.


For example, dividing the limits of the mesh below into 2, would give us a total of 3 rows and 3 columns (including the outermost positions)




This adds procedurality, but has one huge drawback...



Non rectangular/planar objects cannot be easily divided into rows and columns since they don't conform to a normalised 0-1 form factor. What I needed to be able to do, is abstract any given guide mesh as a square, so that I can traverse the width and depth in a 0-1 step-wise fashion.


 

Glory to UVs


As a 3D artist at heart, I am very familiar with that idea that any given mesh can be represented in 3D space, and 2D space simultaneously. UVs allow me to map each vertex and face of a mesh into a normalized square so that no matter the mesh shape, density, topology or size, I can always abstract it into 'rows' and 'columns'.


The donut shaped topology is now mapped to normalised space

UV Space to World Space - The Magic of Barycentric Coordinates


This is all well and good, but as mentioned above, UVs are just abstracted representations of a point in 3D space. In order to transform a UV position into 3D world space, I will have to do a bit of maths to decide where our theoretical UV point would lie on the guide mesh.


I wrote a script to help me visualise the properties of my guide mesh

As an aside - I found it much easier to visualise the properties of my guide mesh in-engine by writing an editor script that draws each triangle, each triangle centre, and the UV position of each vertex. Its a relatively simple tool and makes use of Unity's very handy handles API.


I would massively recommend getting to grips with the Unity Editor API to help you create your own visualisation tools

From here I was much more easily able to conceptualise the problem, and realised that I basically had all the pieces waiting to be put into place.


In order to transform from UV space to world space, I need to compare each target UV against each triangle in the guide mesh. By calculating the BARYCENTRIC AREA of each triangle that is created by the mesh UV and the target UV, I can discover if the target UV lives inside any given mesh face.




Each set of triangle mesh UVs is checked, represented here by the large, pink triangle. I use the BARYCENTRIC AREA to calculate its area. I then, in turn, calculate each theoretical triangle area made with the target UV in the same way. When calculating these sets, if any of the triangle areas, divided by the main triangle (pink) area equals zero - I know that the target UV does not lie inside this set of mesh UVs! And thus is not inside this triangle.

If I pass each of these tests, I know that I am in the correct mesh triangle. It is then a (relatively) simple case of interpolating between each known UV-vertex position using BARYCENTRIC INTERPOLATION to get the world position of the desired UV coordinate!


Phew!

I can now very quickly populate any of my shelves, with any number of items, at arbitrary spacings and densities!




Thanks for tuning in.








bottom of page