Generating Buildings – Medieval City Example

This page will detail how to generate a small medieval city that can be seen in the BuildR examples folder BuildR2\Examples under the Unity scene Carcassonne.unity. At the very bottom I have dumped the whole code example if you just want to take a look at that. I will however try to go through it. This example scene though is an ongoing project and will be modified. The current version of BuildR is 2.17 released in early January 2018.

The basic premise of this generator is to create a city based using a Voronoi diagram to generate its internal road structure and building plots. The plots are then subdivided using a basic oriented bounding box method. Some plots are left to be open spaces. The roads are currently rendered as simple untextured planes. The buildings are fully rendered from a selection of pre-made facade designs.

 1234567 _rGen = new RandomGen(seed); Rectf map = new Rectf(0, 0, mapSize, mapSize); List points = new List(); for (int i = 0; i < voronoiCount; i++)     points.Add(new Vector2f(_rGen.Range(0, mapSize), _rGen.Range(0, mapSize))); Voronoi v = new Voronoi(points, map); v.LloydRelaxation(1);

The initial part generates a Voronoi graph based on randomly selected points within a specified size. The points are relaxed once to create a more even spacing and remove any extreme values. The plots generated will therefore be more uniform but will retain some decent chaos.

 1234567891011121314151617181920 List> vQuarters = v.Regions();             int numberOfQuarters = vQuarters.Count;             for (int r = 0; r < numberOfQuarters; r++)             {                 List vQuarter = vQuarters[r];                 int quarterSize = vQuarter.Count;                 CarcassonneQuarter newQuarter = new CarcassonneQuarter();                 quarters.Add(newQuarter);                 for (int s = 0; s < quarterSize; s++)                 {                     Vector2Int vip = new Vector2Int(vQuarter[s].x, vQuarter[s].y);                     newQuarter.points.Add(vip);                     if (!pointQuarters.ContainsKey(vip))                         pointQuarters.Add(vip, new List());                     pointQuarters[vip].Add(newQuarter);                     if (!newQuarter.externalQuarter && PointOnEdge(vip)) newQuarter.externalQuarter = true;//                 }                 newQuarter.CalculateCenter();             }

The Voronoi graph is then iterated over to turn it into something that can be used for the city generation. Each cell is converted into a CarcassonneQuarter (think of a quarter as being a medieval block of buildings. Each side will be flanked with a road). The quarters that touch the edge of the Voronoi graph are marked external. These will be ignored for the city generation and will give rise to a city that is more organically shaped along its boundary. BuildR uses Vector2Int which can be more easily compared against each other.

After the quarters have been created, the roads network will be defined. Each quarter it iterated over to extract the road points that would surround it. As we go through, most of the roads will be flanked by two quarters. The roads and quarters are both assigned to each other for later. The final part we shrink the quarter be a random amount, allowing space for the road to exist in.

Now that the roads have been created and the quarters have been shrunk the real physical points of the road can be assigned. Each road has the shrunk points assigned to them so they can generate. This is only done on roads that are internal to the city.

 1234567891011121314151617181920212223242526272829303132333435363738 List gplots = new List();             unbuiltPlots.Clear();             for (int r = 0; r < numberOfQuarters; r++)             {                 CarcassonneQuarter quarter = quarters[r];                 if (quarter.externalQuarter) continue;                 Plot plot = new Plot(_rGen.generateSeed, quarter.shrunkPoints);                 plot.minArea = _rGen.OutputRange(30, 50);                 plot.maxArea = _rGen.OutputRange(64, 96);                 plot.minimumAspect = _rGen.OutputRange(0.1f, 0.45f);                 plot.variation = _rGen.OutputRange(0.3f, 0.6f);                 plot.minimumAccessLengthPercent = _rGen.OutputRange(0.01f, 0.05f);                 plot.mergeThreashold = 1.0f;//rGen.OutputRange(0.1f, 0.2f);                 plot.allowTrianglularPlots = true;//rGen.OutputRange(0.1f, 0.2f);                 plot.Split();                 IPlot[] plots = plot.getSubplots;                 int plotCount = plots.Length;                 for (int p = 0; p < plotCount; p++)                 {                     //Create usable plots from the generated plot data                     CarcassonnePlot newPlot = new CarcassonnePlot();                     if (plots[p].area > 550)                     {                         Debug.Log(plots[p].notes);                     }                     newPlot.AddPlot(plots[p]);                     quarter.plots.Add(newPlot);                     GenesisPlot gPlot = newPlot.GenerateGPlot();                     gPlot.landUse = _rGen.OutputRange(0.6f, 1.0f);                     gPlot.density = _rGen.OutputRange(2, 7);                     gPlot.generatePlotMesh = true;                     gplots.Add(gPlot);                     unbuiltPlots.Add(gPlot);                 }             }

The quarters will be too big for a single building so we’ll subdivide them into smaller parcels of land. The Plot class is capable of doing this with specific constraints. The min and max area can allow us to define what size is acceptable for the buildings. The subdivision will continue until the plot is within this size. The minimum aspect will stop plots being generated that are too long and thin. Variation will define how far from the centre a subdivision can be made in normalised space. minimumAccessLengthPercent is defined to make sure each plot has a defined amount of access to the external plot of land. The subdivision will not be allowed to generate plots that have no external access. mergeThreashold defines the ability to merge new plot points with existing plot points. This will stop subdivisions creating plot points that are very close together.
Finally the Split() method is called which will create all the subplots. We then take these subplots and create new GenesisPlots to be used in the creation of buildings.

 1234567891011121314 settings = GenesisSettings.Get();             GRandom.seed = seed;             if (!queueGenerateBuildings)             {                 int gplotCount = gplots.Count;                 for (int gp = 0; gp < gplotCount; gp++)                 {                     if (gplots[gp].area > 550)                         continue;                     constraints.CheckFacades();                     BuildingRuntime building = BuildingGenerator.CreateRuntime(gplots[gp], constraints, settings.defaultGeneratedBuildingName);                     building.Place(gplots[gp].transform.position);                 }             }

Finally, take the generated plots and create some buildings from it using the defined building constraints file. The buildings are then placed into the position of the plots.