Procedural Buildings PART III: R0.1

Design Time

In this third part I am going to introduce some generative modelling ideas and then the concept of L-systems to those that are not familiar with it, and provide a draft design of my implementation.

At this time, the first iteration will be discussed, and then, as iterations are progressing while they are being developed, more updates will be added to these pages.


R0.1 Thoughts and ideas about Design (draft)

The first release will not employ any procedural algorithm, but will implement in script a simple operation: extruding a fixed 2D layout on a horizontal plane, across the y axis like the following figures depict:
step 1: design a 2D layout for the floor

step 2: extrude vertically to build the façade (here the layout 2d shape is offset a bit down the y axis for visibility)
For the design of this release, I will need a mechanism to generate a basic shape (a rectangle), and then another mechanism in order to apply a basic operation on that (extrude along the y axis).
I could as well write two methods respectively in the main class that inherits from Monobehaviour.
However, I would like to design this in a modular way, so that I will not refactor it from scratch in R0.2 when the procedural algorithm kicks in. Remember that in future releases I may need a range of shapes to be constructed, and a lot of operations to apply to them.

The answer lies in the Factory pattern method for the creation of different shapes. This is a popular software pattern for constructing different kinds of objects. ShapeFactory is an abstract class that is inherited by a set of other classes that help with the instantiation of objects of different shapes. I am hereafter including a UML class diagram depicting the dependencies of the main classes:
Note: The diagram is made with WhiteStarUML, an open source CASE tool for software design with the UML graphical modelling language (branch of the popular StarUML, the evolution of which has stagnated the recent years).

buildingBaseLayoutGeneration_R01 is the main class inheriting from Monobehaviour. It will be attached as a Script component to a permanent object in the scene. I have placed it on the main Camera. The camera will always be in the scene, rendering the scene. It is a good object to place scripting components that are required through out the scene.

Let us see what is the role of the different classes and structures in the above class diagram:
  • ShapeEnum: an enumeration type of the available shapes this script is constructing. At the time the only shape that will be created by the script is a rectangle, but in later versions I will add a number of primitive shapes.
  • ShapeFactory: an abstract class (meaning that it cannot be instantiated per se, an inherited class has to be instantiated instead) that defines some operations and is also a container for the constructed shapes, which are all GameObjects. Note that the main operation, create, is abstract as well, meaning that it is not implemented here, but in the inherited classes.
  • RectangularShapeFactory: this is the class that implements the createShape operation that in turn creates (spawns) rectangular shapes on the horizontal plane. It is a specialisation of ShapeFactory and as a result inherits from the latter.
  • CommandEnum: an enumeration type of the available operations that this script can apply on any shape. Two operations are listed and supported by R.01, namely create and extrude. More operations will be added in future releases.
  • ShapeCommand: an abstract class (as with ShapeFactory), that defines one abstract operation: execute. Pretty basic class actually, it may be extended in future releases.
  • ShapeCreate: a specialisation of ShapeCommand, implements the operation execute, which spawns a new rectangle shape in the scene.
  • ShapeExtrude: another class inheriting from ShapeCommand, this one also implements execute for altering a shape's mesh and extruding all edges vertically along the y axis by a fixed height.

The code please (R0.1)

I am going to explain the code that implements the design above. For this first release it is straight-forward, no strange angles, no surprises, no complex implementation.

buildingBaseLayoutGeneration_R01

/*
 * Project Name: Procedural Buildings
 * Scene Name: MainScene
 * Version: R0.1
 * Date: May 2013
 * Script Author: Elias Kalapanidas
 * 
 * Objective: Procedural Generation of as-much-as possible realistic buildings, to be used in a procedurally generated city (a destractibe urban environment)
 */ 

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using proceduralBuildings_R01;

public class buildingBaseLayoutGeneration_R01 : MonoBehaviour {
 public int maximumIterations = 6;
 
 private int currentIteration = 0;
 private RectangularShapeFactory rectangularFactory = new RectangularShapeFactory();
 
 // Use this for initialization
 void Start () {  
  GameObject rectangle = rectangularFactory.createShape();
  ShapeExtrude extrude = new ShapeExtrude();
  extrude.setShapeToTransform(rectangle);
  extrude.execute();
 }
 
 // Update is called once per frame
 void Update () {

 } // Update() ends here
 
}; // buildingBaseLayoutGeneration class ends here

ShapeEnum

 // Elementary enumeration set, listing all shape types
 public enum ShapeEnum {Rectangle};

 ShapeFactory

 // Abstract class that all shape classes should inherit from
 public abstract class ShapeFactory {
  protected ShapeEnum _type;
  protected ArrayList _shapes;
  
  public ShapeFactory() { _shapes = new ArrayList(); }
  public ShapeEnum getShapeType() { return _type; }
  public int getShapesCount() { 
   if (_shapes != null) 
    return _shapes.Count; 
   else {
    Debug.LogError("You have called the method ShapeFactory.getShapesCount but the _shapes ArrayList property was null. Make sure that you have properly constructed your instance of ShapeFactory class.");
    return 0;
   }
  }
  public GameObject getShape(int shapeIndex) { 
   if (_shapes == null) {
    Debug.LogError("You have called the method ShapeFactory.getShape but the _shapes ArrayList property was null. Make sure that you have properly constructed your instance of ShapeFactory class.");
    return null;
   }
   if (shapeIndex >= _shapes.Count) {
    Debug.LogError("You have called the method ShapeFactory.getShape but the shapeIndex argument was greater than the _shapes ArrayList size. Make sure that the argument is always lower than the size of the _shapes ArrayList.");
    return null;
   }
   return (GameObject)_shapes[shapeIndex];
  }
  
  public abstract GameObject createShape();
 };

 RectangularShapeFactory

 //ToDo: introduce a side ratio so that we don't have to create squares all the time
 public class RectangularShapeFactory : ShapeFactory {
  override public GameObject createShape() {
   GameObject _object = new GameObject("Rectangle_" + _shapes.Count);
   _object.AddComponent <MeshFilter>();
   _object.AddComponent <MeshRenderer>();
   Mesh myMesh = _object.GetComponent<MeshFilter>().mesh;
      myMesh.vertices = new Vector3[] {new Vector3(-1,0,1), new Vector3(-1,0,-1), new Vector3(1,0,-1), new Vector3(1,0,1)};
      myMesh.normals = new Vector3[] {Vector3.up, Vector3.up, Vector3.up, Vector3.up};
      myMesh.triangles =  new int[] {0,2,1,0,3,2}; //The winding order of triangles controls which side is visible. Clockwise facing = visible, counter-clockwise = invisible.
      myMesh.uv = new Vector2[] {new Vector2(0,1), new Vector2(0,0), new Vector2(1,0), new Vector2(1,1)};
      myMesh.colors = new Color[] {Color.white,Color.white,Color.white,Color.white};
   myMesh.Optimize();
   myMesh.RecalculateBounds();
   myMesh.RecalculateNormals();
   // Add the new _object GameObject to the _shapes array;
   _shapes.Add(_object);
 
   return _object;
  }
 };

CommandEnum

 //Commands enumeration
 public enum CommandEnum {create, extrude};

ShapeCommand

 // Abstract class that all command classes should inherit from
 public abstract class ShapeCommand {
  protected CommandEnum _class;
  
  abstract public GameObject execute();
  //public abstract bool executeShapeCommand(CommandEnum command); 
 };

ShapeCreate

 public class ShapeCreate : ShapeCommand {
  protected ShapeEnum _type = ShapeEnum.Rectangle;
  protected ShapeFactory _factory = null;
  
  public void setShapeType(ShapeEnum shapeType) { _type = shapeType; }
  public void setShapeFactory(ShapeFactory aFactory) {
   if (aFactory != null)
    _factory = aFactory;
   else
    Debug.LogError("ShapeFactory argument in a call to ShapeCreate.setShapeFactory was null. Please provide a properly instantiated value (not-null).");   
  }   
  override public GameObject execute() {
   if (_factory == null) {
    Debug.LogError("ShapeCreate.execute is called but the _factory property is null. Make sure that you have properly instantiated _factory by calling the setShapeFactory method of this class just before calling execute.");
    return null;
   }
   return _factory.createShape();
  }
 };

 ShapeExtrude

 public class ShapeExtrude : ShapeCommand {
  protected GameObject _shape;

  public void setShapeToTransform(GameObject aShape) { _shape = aShape; }
  override public GameObject execute() {
   Mesh mesh = _shape.GetComponent().mesh;
   Matrix4x4 [] extrusionPath = new Matrix4x4 [2];
   extrusionPath[0] = _shape.transform.worldToLocalMatrix * Matrix4x4.TRS(_shape.transform.position, Quaternion.identity, Vector3.one);
   extrusionPath[1] = _shape.transform.worldToLocalMatrix * Matrix4x4.TRS(_shape.transform.position + new Vector3(0, 1f, 0), Quaternion.identity, Vector3.one);
   MeshExtrusion.ExtrudeMesh(mesh, _shape.GetComponent().mesh, extrusionPath, false);
   return _shape;
  }
 };
  
Shape extrusion is using a script distributed with Unity's asset called Procedural Examples, found in the tutorial section of Unity's site. The specific script is called MeshExtrusion.cs and is placed under the Plugins folder after installing the asset package in your project tree. The operation that does the actual trick of extrusion is called ExtrudeMesh. In order to be invoked, the right arguments should be passed. Except from the shape's mesh (old instance and new instance after the extrude operation), the most important argument is the extrusion path that has to be constructed prior invoking the operation. The path can include multiple segments, in my case I only required one segment, made of 2 parts (starting and ending).
The first one corresponds to the shape's actual transformation (position, rotation, scale) before the extrusion, while the second differs by an offset of 1 on the Y axis. The extrusion path is expressed as an array of Matrix4x4 variables, one for each segment's starting and ending point.

The results

 The following image depicts the completion of the first line in the method Start of BuildingBaseLayoutGeneration_R01, line 24, where a simple rectangle is generated. This was simple enough.
2D square generation result in this R0.1 release as seen in Unity's Scene View

The next image presents the result of the next lines of Start method, where the same shape is extruded:
3D solid cube generation result in this R0.1 release as seen in Unity's Scene View

Fine so far, I am going to experiment with a simple shape randomisation algorithm in the next post in this series and build a combined 3D volume out of the various solids generated.
Stay tuned.

Comments