所需数据结构
根据上一篇文章,我们得知在解析wexbim时,我们主要获取了region,color,product,shapeInstance,faceTriangulation
故我们要先构建他们相应的数据结构,这里我分别用了五个structure来储存他们。
//储存读取的region数据
public struct MyBimRegion
{
public int population;
public Vector3 position;
public Vector3 scale;
public XbimRegion bimRegion;
public MyBimRegion(int population, float px, float py, float pz, float sx, float sy, float sz)
{
this.population = population;
position = new Vector3(px, py, pz);
scale = new Vector3(sx, sy, sz);
bimRegion = new XbimRegion()
{
Population = population,
Centre = new XbimPoint3D(px, py, pz),
Size = new XbimVector3D(sx, sy, sz)
};
}
}
//储存读取的color数据
public struct MyBimColor
{
public int styleLabel;
public float r, g, b, a;
public Color color;
public XbimColour xbimColour;
public MyBimColor(int label, float r, float g, float b, float a = 1)
{
styleLabel = label;
this.r = r; this.g = g;
this.b = b; this.a = a;
color = new Color(r, g, b, a);
xbimColour = new XbimColour(r, g, b, a);
}
}
//储存读取的product数据
public struct MyBimProduct
{
public int entityLabel;
public short typeId;
public Vector3 position;
public Vector3 scale;
public List<MyBimShape> shapes;
public MyBimProduct(int label, short id, Vector3 pos, Vector3 scale)
{
entityLabel = label;
typeId = id;
position = pos;
this.scale = scale;
shapes = new List<MyBimShape>();
}
public void AddShape(MyBimShape shape)
{
shapes.Add(shape);
}
}
//储存读取的shapeInstance数据
public struct MyBimShape
{
public XbimShapeInstance shape;
public int ifcProductLabel;
public short ifcTypeId;
public int instanceLabel;
public int styleLabel;
public List<MyBimTriangulation> triangulations;
public XbimMatrix3D transform;
public MyBimShape(int productLabel, short typeId, int instanceLabel,int styleLabel,XbimMatrix3D matrix)
{
this.ifcProductLabel = productLabel;
this.ifcTypeId = typeId;
this.instanceLabel = instanceLabel;
this.styleLabel = styleLabel;
this.triangulations = new List<MyBimTriangulation>();
shape = new XbimShapeInstance(instanceLabel)
{
IfcProductLabel = productLabel,
IfcTypeId = typeId,
StyleLabel = styleLabel,
Transformation = matrix
};
transform = matrix;
}
public void AddTriangulation(MyBimTriangulation ta)
{
triangulations.Add(ta);
}
}
//储存读取的shapeTriangulation数据
public struct MyBimTriangulation
{
public XbimShapeTriangulation bimTriangulation;
public List<Vector3> vertices;
public List<int> triangles;
public List<Vector3> normals;
public MyBimTriangulation(XbimShapeTriangulation triangulation, float scale, Vector3 offsite, XbimMatrix3D matrix = new XbimMatrix3D(), bool bMatrix = false)
{
//判断是否需要对三角形进行几何操作
if (bMatrix)
{
bimTriangulation = triangulation.Transform(matrix);
}
else
bimTriangulation = triangulation;
vertices = new List<Vector3>();
triangles = new List<int>();
normals = new List<Vector3>();
foreach (var v in bimTriangulation.Vertices)
{
vertices.Add(new Vector3((float)v.X, (float)v.Y, (float)v.Z) / scale - offsite);
}
foreach(var f in bimTriangulation.Faces)
{
triangles.AddRange(f.Indices);
foreach (var n in f.Normals)
{
normals.Add(new Vector3((float)n.Normal.X, (float)n.Normal.Y, (float)n.Normal.Z));
}
}
}
}
存储读取的数据
而后在解析wexbim文件时用List把相应的数据储存起来。
tips1:在存储坐标数据时,记得除以最开始读取的meter值,meter代表了游戏中的一个坐标单位和真实世界中1米的对应比例,如果不除的话,模型可能过大
tips2:region的center坐标是整体模型的偏移量,如果想要每次模型的中心都在原点处记得对所有的坐标都减去region的center坐标
List<MyBimColor> colors = new List<MyBimColor>();
List<MyBimShape> shapeInstances = new List<MyBimShape>();
List<MyBimTriangulation> triangulations = new List<MyBimTriangulation>();
List<MyBimRegion> regions = new List<MyBimRegion>();
List<MyBimProduct> products = new List<MyBimProduct>();
在for循环解析wexbim文件时,将对应的数据存储其中方便之后的模型重建。
这里主要讲一下shapeInstance的存储。
if (shapeRepetition > 1)
{
List<MyBimShape> myshapes = new List<MyBimShape>();
for (int j = 0; j < shapeRepetition; j++)
{
//读取数据
var ifcProductLabel = br.ReadInt32();
var ifcTypeId = br.ReadInt16();
var instanceLabel = br.ReadInt32();
var styleLabel = br.ReadInt32();
var transform = XbimMatrix3D.FromArray(br.ReadBytes(sizeof(double) * 16));
//将读取的shapeInstance添加到list中,并根据label获得他的product
myShape = new MyBimShape(ifcProductLabel, ifcTypeId, instanceLabel, styleLabel, transform);
shapeInstances.Add(myShape);
myshapes.Add(myShape);
var p = products.Find(product => product.entityLabel == ifcProductLabel);
p.AddShape(myShape);
}
var triangulation = br.ReadShapeTriangulation();
//给这些重复的shapeInstance分别添加对应的三角形,并在三角形初始化时进行几何操作
foreach (var ms in myshapes)
{
var tri = new MyBimTriangulation(triangulation, scale, offsite, ms.transform, true);
ms.AddTriangulation(tri);
triangulations.Add(tri);
}
}
由于shapeInstance
中含有较多的label和id故需要以它为中心把其它数据给连接起来。
因为shapeInstance
相当于是product
的一个部分,故在shapeInstance
初始化时就把它添加给对应的prooduct
。
同时shapeInstance
要包含相应的mesh信息,故在读取triangulation
时,也将对应的shapeInstance
添加对应的triangulation
。
模型的生成
模型的生成主要依靠的是unity的mesh类。方法如下:
- 遍历
products
,生成相应的空的gameObject(物体名字可为product.EntityLabel
方便后面查找使用) - 再遍历
product.shapes
,生成相应的带有mesh的gameObject。并将这些新生成的gameObject的父对象设置为对应的product
的gameObject - 再将所有的
product
放置在一个空物体下,旋转(-90,0,0)的欧拉角。这是因为ifc文件是z轴向上,而unity则是y轴向上。
代码如下:
List<GameObject> productGO = new List<GameObject>();
GameObject mainGO;
public void GenerateProduct(MyBimProduct product)
{
var go = new GameObject
{
name = product.entityLabel.ToString()
};
go.transform.position = product.position;
go.transform.localScale = product.scale;
productGO.Add(go);
foreach (var s in product.shapes)
GenerateShape(s);
go.transform.parent = mainGO.transform;
}
private void GenerateShape(MyBimShape shape)
{
GameObject shapeGO = new GameObject(shape.instanceLabel.ToString());
var go = productGO.Find(p => p.name == shape.ifcProductLabel.ToString());
shapeGO.transform.parent = go.transform;
//添加mesh
var mf = shapeGO.AddComponent<MeshFilter>();
var mesh = mf.mesh;
foreach(var tri in shape.triangulations)
{
mesh.vertices = tri.vertices.ToArray();
mesh.triangles = tri.triangles.ToArray();
mesh.Optimize();
mesh.RecalculateNormals();
}
//添加texture
var mr = shapeGO.AddComponent<MeshRenderer>();
var mat = new Material(Shader.Find("Standard"));
var c = colors.Find(cl => cl.styleLabel == shape.styleLabel);
mat.color = c.color;
mr.material = mat;
}
在添加mesh的时候我只是读取了triangulation
的vertices和triangles这两个值,并未读取当时存储的normals。
这是因为:wex在存储法线时是按照一个面一个面的存储法线,这就导致了如果这个点是被多个面共享的话,那么这个点的法线就被多次存储,即tri.vertices.Count<=tri.normals.Count
但unity中mesh.vertices.Count==mesh.normals.Count
,如不相等会报错。故在这里使用了unity的mesh.RecalculateNormals()
方法,发现效果还不错就先暂时这样解决,如果以后有时间会进一步修改。
最后整体在start方法中的调用如下:
void Start()
{
mainGO = new GameObject();
ReadWexbimFile(fileName);
foreach (var product in products)
GenerateProduct(product);
mainGO.transform.rotation = Quaternion.Euler(-90, 0, 0);
}
效果图
下面是六种不同类型的模型,大家可以使用UnityShader对模型进行进一步的渲染优化。