Placing Windows Freely on Walls
Author introduction |
Getting Started
Hi everyone, my name is Shoichiro Inoue from Holoearth’s engineering client team.
I’m mainly responsible for implementing the building mechanics in Holoearth.
Many games in recent years have included building and housing mechanics, and windows are an integral part of this kind of mechanic. However, in many games windows can only be placed in specific places, such as on a wall with a hole specifically made for window placement – but we want to place windows wherever!
I’ll introduce the solution we worked out for this problem in this development blog.
But first, here’s a short video demonstrating the end result.
As you can see in the video, it is possible to freely place a window wherever you want!
The part of the wall where a window is placed disappears, revealing the outside.
But in addition, it is also possible to cut a hole in the wall’s collider as well. This allows for the possibility in the future to have battles where, say, arrows can be shot through windows that were installed.
Here, I explain the process of how we approached the problem.
Solving the Problem
Requirements
The wall has to be in place first to install a window, the window must be installed such that it’s flush with the wall, and the window’s rotation must adjust automatically to the proper orientation with respect to the wall. I won’t go into detail about this part.
The window “prefab” must include the following information:
- Collider (to use collisions to detect which wall it is being placed on)
- Window width and height
Workflow
1. The window contains a collider and retrieves which wall it is to be placed on.
2. When colliding with a wall, the window passes its position and size data to the wall and requests to make a hole there.
3. The wall in question executes scripts to change its shader and collider values to create the hole.
And if the window is destroyed it asks the wall to restore those values.
I’ll talk about the shader that creates the appearance of a hole and the script that makes a hole in the collider later on.
So far the scope of this mechanic is limited as follows:
- It doesn’t take into account how the window’s edge protrudes from the wall, so we use raycasts to make sure all four window corners are on the same wall.
- It can’t allow for multiple windows on a single wall, so if a second window is added it will destroy the first window.
Using Shaders to Make the Window’s Appearance
In essence, the wall’s shader will not render where the window’s coordinates overlap the part of the wall being rendered.
This means the sides of the model will be empty, revealing the reverse side of the wall’s mesh, but this is covered by the 3D model of the window frame on the sides of the window.
(Left: Wall being overlapped by a window is removed by the shader. Right: Placing a window frame over the removed portion.)
A surface shader is created to grab the coordinates of what is being drawn.
The window’s center coordinate and size (width, height) are obtained from its script.
Making Calculations
We split the problem into the two parts below.
1. Whether the coordinates being drawn are in the window on the horizontal plane, and
2. Whether the coordinates being drawn vertically are in the window.
1. Horizontal plane
We make a line on the x-z plane using the formula: z = kx + b.
Let k be the slope of the line perpendicular to the window when viewed from directly above. Then there are two lines, b, which are the lines passing through the front and back edges of the window, respectively. Let us denote them as b1 and b2.
There’s no need to draw within the yellow area bounded by lines z = kx + b1 and z = kx + b2.
That is, if the coordinates we are drawing now are x1, y1, and z1:
kx1+b1 < z1 < kx1+b2
Then the coordinate is in the window on the horizontal.
*k can be obtained from the window’s y-axis rotation = rotY.
*Since the tan of 0 is undefined, we need to set up a branch process to avoid specifying 0 as the argument of the tan function.
k = tan(rotY/180f * π + 0.5π)
*b1, b2 can be obtained from the coordinates of both ends of the window
b = z / kx
2. Vertical Direction
Now, the Y axis: In Holoearth, the window rotates only on the Y-axis, so the equation is simple.
_windowBottomY < position.y < _windowTopY
We know that we are in a window in the vertical direction if the above is true
*The _windowBottomY and _windowTopY can be obtained by adding the window width to the window center point Y coordinate.
Taking all the above into account the code looks like this:
bool isInsideWindow(Vector3 pos) // pos: world coords of point to be drawn { // Is in window on x-z plane var isInsideXZ = _k * pos.x + _b1 < pos.z && pos.z < _k * pos.x + _b2; // Height is within window return isInsideXZ && isInsideY; |
*The actual shader was created by the same team. They are modified and shown here for clarity.
*To reduce processing loads, _k, _b1, _b2, _windowTopY, and _windowBottomY are calculated when the window is placed.
Future Endeavors
Right now this can’t support shapes other than rectangles.
To accommodate other shapes, the area of that shape needs to be calculated.
For example, a circular window can be implemented by projecting the drawing coordinates onto the x-y plane and then making sure the distance between the point being rendered and the center point is less than the radius of that circle.
Making Openings in Colliders
Now I’ll explain more about creating an actual hole, rather than just in appearance.
The wall collider consists of a single box collider. The original collider is replaced by four box colliders to avoid the window part, each of which is divided to avoid the window section to represent a hole in the wall.
The way to do it is pretty simple: we attach a new four-box collider after deleting the original collider, which requires computing the center coordinates and size of each collider.
This will result in the following variables:
A 3-dimensional vector (vector3) for the size of the wall, wallSize
Relative coordinates of the window to the wall in another vector3, windowCenter
Length of one side of the window in a vector2, windowSize
The actual calculation is as follows:
*The collider is a local coordinate system, so rotation doesn’t need to be considered.
*The origin of the wall is at the center of the bottom surface, and the origin of the window is at the center of the length and width
▼Upper collider
var sizeX = wallSize.x; var posX = 0f; |
▼Lower collider
var sizeX = wallSize.x; var posX = 0f; |
▼Left collider
(The x-axis is zero at the center of the wall, so the coordinate of the left end of the wall is -wallSize.x / 2f)
var sizeX = – wallSize.x/2f + windowCenter.x – windowSize.x/2f; var posX = -windowSize.x/2f + sizeX/2f; |
▼Right Collider
var sizeX = wallSize.x/2f – (wallCenter.x + wallSize.x/2f); var posX = wallSize.x/2f – sizeX/2f; |
Set these values to position the collider so that it doesn’t cover the window area.
Future Endeavors
With the process described here, it is difficult to make holes in the collider with non-rectangular shapes.
At the moment windows of different shapes can’t be made, but there are possible ways to accomplish this:
- Increasing the number of box colliders and arranging them like an integral, which could make shapes really close to circles and triangles. But this drastically increases the number of box colliders as accuracy increases.
- Preparing colliders with triangular or circular holes in advance. After making holes in a rectangle, place these prepared colliders inside the holes. It takes time and effort to prepare colliders with special shapes, but it might reduce the number of colliders necessary.
Final Thoughts
The combination of shaders and collider management allows us to create an experience where windows can be placed freely on walls!
We hope to create more and more awesome building features!
If you’re interested, definitely give it a try in Holoearth!