由于xbim是基于.net framework的一款ifc开源库,且unity同样支持C#语言的编写,故使用xbim在unity中解析.ifc文件。本文主要讲解如何在unity中构建三维模型。
使用xbim提供方法
const string fileName = @"SampleHouse4.ifc";
var wexBimFilename = Path.ChangeExtension(fileName, "wexBIM");
var xbimDbFilename = Path.ChangeExtension(fileName, "xBIM");
// Make sure we are using an IModel implementation that supports saving of '.xbim' files
IfcStore.ModelProviderFactory.UseHeuristicModelProvider();
using (var model = IfcStore.Open(fileName))
{
// IFC file is already parsed and open. Now build the 3D
var context = new Xbim3DModelContext(model);
context.CreateContext(); // Creates the Geometry using native GeometryEngine
// Optional: Export to 'wexbim' format for use in WebUI's xViewer - geometry only
using (var wexBimFile = File.Create(wexBimFilename))
{
using (var wexBimBinaryWriter = new BinaryWriter(wexBimFile))
{
model.SaveAsWexBim(wexBimBinaryWriter);
wexBimBinaryWriter.Close();
}
wexBimFile.Close();
}
// Save IFC to the internal XBIM format, which includes geometry
model.SaveAs(xbimDbFilename, StorageType.Xbim);
}
然而此种方法在unity中会导致unity闪退,原因见此:因为xbim.Geomotry重写了模型构建的方法,与unity内置模型构建方法冲突,故context.CreateContext()方法与unity冲突。但由于xbim是基于.net framework的开源库,而此bug则是xbim和unity的冲突,故开发者不予以解决。
我的方法
因为context.CreateContext()在unity中会报错,故要在一个新的项目中调用此语句生成中间文件,而后把中间文件解析获得模型。
wexbim
为适配web端,xbim团队开发了wexbim。因为weixbim文件储存的是且全是ifc文件的几何数据,故这是一个很好的中间文件来生成模型。至于如何生成wexbim文件,见前文超链接。
解析wexbim
如何解析wexbim则要看wexbim如何生成,建议可以根据xbim的源码进行查看。model.SaveAsWexBim(wexBimBinaryWriter);是生成wexbim的语句,这里使用的是Binary Write进行二进制数字的写入。
读取wexbim的代码见下:
public void ReadWexbimFile(string fileName)
{
float scale;
Vector3 offsite;
using (var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read))
{
using (var br = new BinaryReader(fs))
{
var magicNumber = br.ReadInt32();
var version = br.ReadByte();
var shapeCount = br.ReadInt32();
var vertexCount = br.ReadInt32();
var triangleCount = br.ReadInt32();
var matrixCount = br.ReadInt32();
var productCount = br.ReadInt32();
var styleCount = br.ReadInt32();
var meter = br.ReadSingle();
var regionCount = br.ReadInt16();
scale = meter;
//Region
for (int i = 0; i < regionCount; i++)
{
var population = br.ReadInt32();
var centreX = br.ReadSingle(); centreX /= scale;
var centreY = br.ReadSingle(); centreY /= scale;
var centreZ = br.ReadSingle(); centreZ /= scale;
var boundsBytes = br.ReadBytes(6 * sizeof(float));
var bounds = XbimRect3D.FromArray(boundsBytes);
offsite = regions[0].position;
}
//texture
for (int i = 0; i < styleCount; i++)
{
var styleId = br.ReadInt32();
var red = br.ReadSingle();
var green = br.ReadSingle();
var blue = br.ReadSingle();
var alpha = br.ReadSingle();
}
//product
for (int i = 0; i < productCount; i++)
{
var entityLabel = br.ReadInt32();
var typeId = br.ReadInt16();
var boxBytes = br.ReadBytes(6 * sizeof(float));
XbimRect3D bb = XbimRect3D.FromArray(boxBytes);
}
//shape
for (int i = 0; i < shapeCount; i++)
{
var shapeRepetition = br.ReadInt32();
if (shapeRepetition > 1)
{
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));
}
var triangulation = br.ReadShapeTriangulation();
}
else if (shapeRepetition == 1)
{
var ifcProductLabel = br.ReadInt32();
var ifcTypeId = br.ReadInt16();
var instanceLabel = br.ReadInt32();
var styleLabel = br.ReadInt32();
XbimShapeTriangulation triangulation = br.ReadShapeTriangulation();
}
}
}
}
}
wexbim主要储存了region,texture,product,shapeInstance,shapeTriangulation
这些数据类型,而后根据前面储存的相应类型的数量来遍历获取对应的数据。
region
相当于模型的整体范围,储存了:population,center X,center Y,centerZ,BoundingBox
。XYZ
是中心点坐标、boundingBox
为方形包围盒(最小点和最大点的坐标)。texture
储存了对应的颜色信息,储存了:r,g,b,a,styleId
。不同的颜色对应不相同的id。product
则是模型的一些实体组件(门,窗,墙壁等),储存了:entiityLabel,typeId,boundingBox
。entityLabel
为对应实体的label,每个product
拥有一个专属的label。tyepId
则是product
的类型的id,所有的门共享相同的id,boundingBox
则是方形包围盒见上。shapeInstance
可以理解为unity中的一个mesh所对应的gameObject。例如一个窗户,可能包含3-4个mesh(窗框、玻璃等)故需要3-4个不同的gameObject,则这一个gameObeject就是一个shapeInstance
。shapeInstance
中储存的是:ifcProductLabel,ifcTypeId,instanceLabel,styleLabel,transformation/*可选*/,shapeTriangulation
。ifcProductLabel
用于对照之前product.entityLabel
来获取product
和shapeInstance
的对应关系(一个product
对应一到多个shapeInstance
);ifcTypeId
和product.typeId
同理;instanceLabel
则是这个shapeInstance
的label无用;styleLabel
用来和前文texture.styleId
来对应用来添加material。shapeTriangulation
是对应的mesh信息,它里面储存的数据需要读取源码才能知道,这里笔者将其写出来:vertices,faces
同时faces
里存储了indices,normals
。vertices
是mesh对应顶点在世界坐标系中的xyz坐标,faces
是mesh中对应的面数,indices
为绘制mesh时的三角形索引,normals
为法线信息。
我们可以看到,在读取shapeInstance
的时候我们需要先判断shpeRepetition
的值,大于1是一部分,等于1是另一部分。这里来解读一下这个shapeRepetition
的玄机。
在构建建筑模型的时候,有些实体会大量重复的使用,例如居民楼模型上的所有门窗和部分家具。如果把这些门窗的triangulation信息都单独存储会占用大量不必要的空间,故wexbim在这里进行了一下优化,即shapeRepetiton
,shapeRepetition
是对应mesh的重复信息,例如这个模型中有三个一模一样的的窗户,则在储存窗户时,对应的shapeRepetition=3
,这个时候对于这三个窗户而言,储存了他们各自的ifcProductLabel,ifcTypeId,instanceLabel,styleLabel,transform
和一个共用的shapeTriangulation
。
如果忽略transform
直接加载模型信息,则由于这三个窗户共享相同的shapeTriangulation
他们会加载在相同的地方,此时就要使用我们储存的transform
信息。transform
是一个4x4的Matrix,用来进行模型的各种旋转平移缩放等操作。于是,用几个matrix就可以不储存复杂的三角形信息,进而节省了大量的空间。xbim也提供了相应的方法用来帮助我们解析模型:XbimShapeTriangulation.Transform(XbimMatrix3D matrix)
。
至此我们已经读取了模型的所有几何信息,下章我将会将一下模型的重构。