BIM Viewer apps, how them work? How i build my own BIM Viewer?
For the last couple of years, I've dug a little deeper to understand how BIM viewer apps work—not just how to implement them in my app, but what’s going on behind the scenes.
So, let's break it down, what is the difference between BIM viewer and a regular 3D viewer? - short answer: data (information). Your objects in BIM viewer must include 2 things: geometry and information.
Here’s what it looks like underneath:
That it, with geometries and data you can bring your BIM Model to anywhere, any engine or technology. Threejs, Unity, ... it doesnt matter, it has the same mechanism.
This is the concept Speckle wants to deliver, they drive data by converting native software data into their structure (like i said: geometries + data).
It brought me to the idea: "ok, enough with the theory, im gonna build my own viewer"
You can take a look at my result here: https://app.viralution.io
Tech stack i use for my BIM viewer is: ThreeJS (its open-source and has a huge community we can rely on) and it will be a web application, and i will extract data from Revit, using Revit API.
- First extract geometries:
private void ConvertRevitElementToViralutionElement(Element item, ViralutionFragmentModel viralutionModel)
{
// get all solids in element, one element may have many solids
List<Solid> solids = GetTotalSolids(item);
if (solids.Count > 0)
{
foreach (Solid solid in solids)
{
if (solid.Volume <= 0) continue;
ElementFragment viralutionSolid = new ElementFragment();
foreach (Autodesk.Revit.DB.Face face in solid.Faces)
{
Autodesk.Revit.DB.Mesh mesh = face.Triangulate();
if (mesh is null) continue;
int currentCount = viralutionSolid.Vertices.Count;
List<XYZ> vertices = (List<XYZ>)mesh.Vertices;
List<Point> points = vertices.Select(x => new Point(x)).ToList();
foreach (var point in points)
{
viralutionSolid.Vertices.Add(point);
}
int numberOfTriangles = mesh.NumTriangles;
for (int i = 0; i < numberOfTriangles; i++)
{
//Face viralFace = new Face();
MeshTriangle meshTriangle = mesh.get_Triangle(i);
int indicesIndex1 = currentCount + (int)meshTriangle.get_Index(0);
int indicesIndex2 = currentCount + (int)meshTriangle.get_Index(1);
int indicesIndex3 = currentCount + (int)meshTriangle.get_Index(2);
viralutionSolid.Indices.Add(indicesIndex1);
viralutionSolid.Indices.Add(indicesIndex2);
viralutionSolid.Indices.Add(indicesIndex3);
}
}
}
}
}
There are 2 important things here: Indices and Vertices
public class ViralutionSolid
{
public List<Point> Vertices { get; set; } = new List<Point>();
public List<int> Indices { get; set; } = new List<int>();
public int MaterialIndex { get; set; } = 0;
}
Indices are numeric labels for the vertices in a given 3D scene
To simplify, indices are orders, which tell us how to build a triangle,
For Example:
Indices: [0, 1, 2, ...]
Vertices: [point1, point2, point3, ...]
Base on Indices above, it tells us get point number 1, point number 2, point number 3 to build a Triangle 1.
Another Example:
Indice: [... 5,10,15,...], it tells us get point number 5, number 10 and 15 to build another Triangle.
So on, Geometry is built by a lot of triangles.
- Extract data
private void AddElementParamsToDict(
Element element,
Dictionary<string, ViralutionParameter> paramDict,
bool isTypeParameter = false,
List<string> exclusions = null
)
{
if (element == null)
return;
exclusions ??= new List<string>();
using var parameters = element.Parameters;
foreach (Parameter param in parameters)
{
if (param.Definition is null) continue;
var internalName = param.Definition.Name;
if (paramDict.ContainsKey(internalName) || exclusions.Contains(internalName))
{
continue;
}
var viralutionParam = ParameterToViralution(param, internalName, isTypeParameter);
paramDict[internalName] = viralutionParam;
}
}
get all parameters from element and store in Dictionary object, this step is quite simple.
- Rebuild it with ThreeJS
Im not gonna go through the whole process, setup simple scene you can read here.
In ThreeJS you can add an object with vertices and indices like this:
function addMesh(indices, vertices, transform = null) {
let verticePoints = [];
if (transform == null) {
for (let i = 0; i < indices.length; i++) {
const index = indices[i];
const point = vertices[index];
verticePoints.push(-point.X);
verticePoints.push(point.Z);
verticePoints.push(point.Y);
}
const buffer = new Float32Array(verticePoints);
return buffer;
}
let numbers = [
transform.BasisX.X, -transform.BasisX.Z, -transform.BasisX.Y,
0, -transform.BasisZ.X,
transform.BasisZ.Z,
transform.BasisZ.Y,
0, -transform.BasisY.X,
transform.BasisY.Z,
transform.BasisY.Y,
0, -transform.Offset.X,
transform.Offset.Z,
transform.Offset.Y,
1,
];
//transform first
let matrix4 = new Matrix4();
matrix4.fromArray(numbers);
let newVertices = [];
for (let i = 0; i < vertices.length; i++) {
const point = vertices[i];
const point2 = new Vector3(-point.X, point.Z, point.Y);
point2.applyMatrix4(matrix4);
newVertices.push(point2);
}
for (let i = 0; i < indices.length; i++) {
const index = indices[i];
const point = newVertices[index];
verticePoints.push(point.x);
verticePoints.push(point.y);
verticePoints.push(point.z);
}
const buffer = new Float32Array(verticePoints);
return buffer;
}
There is 1 thing you should care about is axis. in revit axis follow x,y,z structure. but in threejs follow x,z,y structure, which y stand for height. It means you must transform it to make it work in ThreeJS
You can do some research to understand it more deeply, a code block above already include transform things, dont worries about it.
Conclusion
BIM Viewer base on a simple theory, but there are a lot of sinigficant challenges due to bad performance you must dealing with like huge data size, optimize things, ....There are a lot of BIM Viewer out there, understand how it work is quite interesting.
Member discussion