Skip to main content

Merging Houses

·1260 words·6 mins
Graphics Procedural Generation
Table of Contents

My mother really likes playing those mobile merging games.

For the ones who don’t know, the goal of those games is to put together 2 objects of the same type to create another. In most cases, the game gives you tasks to complete, which you need to make different objects for.

I noticed that there is always a limit to how much you can merge objects. Mainly because it requires a lot of people to design and draw that many assets.

So the question is: can we automate that process? And can we create a merging game with an infinite number of objects?

The tools
#

For this project, I’ll be using the gmsh python library, as well as pygmsh for a simpler code syntax. Here are the 2 repositories:

sasobadovinac/gmsh

A three-dimensional finite element mesh generator with built-in pre- and post-processing facilities

C++
32
4

nschloe/pygmsh

:spider_web: Gmsh for Python

Python
879
160

Building a house
#

For this project, we’ll try to make houses of infinite sizes. So first, we need a house!

The first cube
#

A good house starts with a cube. So we’ll start by trying to make a cube using gmsh. Here is the python script that does so:

import gmsh
import pygmsh

# Starts describing the geometry
with pygmsh.occ.Geometry() as geom:
    # Add a cube to it
    mesh = geom.add_box(
        (0, 0, 0), # position
        (1, 1, 1)  # scale
    )

    # Convert the geometry to a real mesh
    geom.generate_mesh()

    # Write the generated mesh to the 'out.msh' file
    gmsh.write("out.msh")
    gmsh.clear()

When opening the generated out.msh with gmsh, we get this beautiful cube:

a yellow cube
By default, gmsh only shows the cube’s triangles. You can go to Tools > Options > Mesh and select here what you want to see.

Better with a roof
#

Now our house needs a roof. Unfortunatly, we do not have any basic 3D form to put on top of it.

That’s why we’ll use extrusion.

Extrusion
#

The goal of extrusion is to extend a 2D face into 3D. For example, turning a square into a cube.

Here are the different steps of extrusion:

  1. make a copy of the object you want to extrude, and move it to the desired position;
  2. link each vertices from the 2 objects to create the missing faces.
extrusion on a triangle
Extrusion on a triangle

Making the roof
#

To use extrusion, we first need our 2D object. We’ll start by drawing a triangle on top of the cube:

triangle = geom.add_polygon([
    [-0.1, -0.1,   1], # Removed -0.1 from each side of the roof
    [-0.1,  1.1,   1], # for a little offset with the main cube
    [-0.1,  0.5, 1.5],
], mesh_size=1)
a cube with a triangle on top of it

Now we need to make the extrusion. Thankfully, pygmsh has a method to do just that:

# The copy is moved by 1.2 on the x axis
geom.extrude(triangle, (1.2, 0, 0))

This will give us our final roof:

a cube with a roof

How to get in
#

To make it seem like there is an entrance to the house, we need to carve a rectangle inside the house cube to imply its presence.

To do that, we’ll need boolean operations.

Boolean operations
#

There is 2 types of boolean operations we need for this project:

  • union, which combines 3D objects;
  • difference, which carves a object into the other.

It is identical to union and difference between 2 mathematical sets:

schema of union and difference operations
Union and difference boolean operations

Making the door
#

To create our door, we’ll use a boolean diffenrece. We first need to highlight the space we want to remove:

door = geom.add_box(
    (0.4,   0,   0),
    (0.4, 0.1, 0.6)
)

Here is the result with only edges for more visibility:

A house

Once the space is delimited properly, we simply remove it with the boolean difference.

geom.boolean_difference(mesh, door)
A house

A complete house
#

Here is the final script for our building house:

import gmsh
import pygmsh

# Starts describing the geometry
with pygmsh.occ.Geometry() as geom:
    # Add a cube to it
    mesh = geom.add_box(
        (0, 0, 0), # position
        (1, 1, 1)  # scale
    )

    # Create the door box
    door = geom.add_box(
        (0.4,   0,   0),
        (0.4, 0.1, 0.6)
    )

    # Remove it from the body
    geom.boolean_difference(mesh, door)

    # Create the triangle
    triangle = geom.add_polygon([
        [-0.1, -0.1,   1],
        [-0.1,  1.1,   1],
        [-0.1,  0.5, 1.5],
    ], mesh_size=1)

    # Extrude it
    geom.extrude(triangle, (1.2, 0, 0))

    # Convert the geometry to a real mesh
    geom.generate_mesh()

    # Write the generated mesh to the 'out.msh' file
    gmsh.write("out.msh")
    gmsh.clear()

Define a merge pattern
#

Now that we are able to build a house, let’s define a pattern that will allow us to describe any type of house.

We’ll consider the house built earlier to be of level 0.

Here is the pattern we will use:

  • a level 1 house has a larger base;
  • a level 2 house has a flower pot;
  • a level 3 house has a little balcony.

On level 4, we create a new level 0 floor for the house. Then the cycle continues: a level 5 house is composed of a level 3 base and a level 1 floor, a level 6 house is composed of a level 3 base and a level 2 floor, etc.

A larger house
#

To make a larger house, we simply to transform our base cube into a cuboid.

We also need to account for the change in the door placement.

A level 1 house

Adding some plants
#

For the plants, we’ll add a cuboid at the front of the house to act as a pot, and 3 random ellipsoids to fill it. You can see the 3 different variations below:

A level 2 house

A little balcony
#

For the balcony, same principle: we’ll combine cuboids with a boolean union:

A level 3 house

New floor
#

This is where things gets fun.

New floors follow the same rules, with one small detail: we can’t have a door.

To keep a consistent level of details, we’ll add a window with the same method used for the door.

We create a window shaped form:

And remove it with boolean difference:

Here is the code used to do so:

# Add a cube for the floor
mesh = geom.add_box(
    (0, 0, 0), # position
    (1, 1, 1)  # scale
)

# Create the window frame
window_frame = geom.add_box((0.2, -0.1, 0.25), (0.6, 0.15, 0.5))

# Window glasses
glasslb = geom.add_box((0.225, -0.1, 0.275), (0.25, 0.2, 0.2))
glasslt = geom.add_box((0.525, -0.1, 0.275), (0.25, 0.2, 0.2))
glassrb = geom.add_box((0.225, -0.1, 0.525), (0.25, 0.2, 0.2))
glassrt = geom.add_box((0.525, -0.1, 0.525), (0.25, 0.2, 0.2))

# Combine the window with boolean union
window = geom.boolean_union([window_frame, glasslb, glasslt, glassrb, glassrt])

# Remove it with a boolean difference
geom.boolean_difference(mesh, window)

We’re missing one final detail to make the house truly complete.

Currently, we have this big empty space next to the last floor:

To fix this problem, we simply need to add a little roof section next to it.

A level 4 house
The final level 4 house

Adding some variations
#

As a final touch, we’ll add some variations to the details. We’ll alternate the side of the window and the balcony on each floor.

Here is a level 8 house to illustrate:

A level 8 house

Finishing notes
#

The script works well, but it isn’t perfect. You may have noticed when opening a file with gmsh, but the generated house is composed of way too many triangles.

The script also does not have great performances due to the many boolean operations in play:

House LevelTime (in sec)
00.77
11.06
21.36
31.53
42.39
168.98
100111.55

If you want to tackle those problems, you can find the final of the script here:

Author
Julie `DaiF'
Computer Science Student, interested in computer graphics and compilation