public void RoundTripTest() { var tests = new[] { new XbimVector3D(0.0749087609620539, 0.264167604633194, 0.961563390626687), new XbimVector3D(-0.0535755113215756, 0.316639902069201, 0.947031592400295), new XbimVector3D(-0.0403690470743153, -0.0845001599207948, 0.995605375142015), new XbimVector3D(-0.170389413996118, 0.321003309980549, 0.931624560957681) }; foreach (var vec in tests) { Debug.WriteLine(vec); var pack = new XbimPackedNormal(vec); var roundVec = pack.Normal; Debug.WriteLine(roundVec); var a = vec.CrossProduct(roundVec); var x = Math.Abs(a.Length); var y = vec.DotProduct(roundVec); var angle = Math.Atan2(x, y); if (angle > 0.1) Debug.WriteLine("vector: {0}, angle: {1:F3}", vec, angle*180.0/Math.PI); Assert.IsTrue(angle < 0.13); } }
/// <summary> /// returns the normal for a specific point at a specific index on the face /// </summary> /// <param name="index"></param> /// <returns></returns> public XbimVector3D NormalAt(int index) { var indexOffset = _offsetStart + sizeof(int); //offset + trianglecount if (IsPlanar) //no matter what you send in for the index you will get the same value because it is planar { var u = _array[indexOffset]; var v = _array[indexOffset + 1]; var pn = new XbimPackedNormal(u, v); return(pn.Normal); } else { var indexSpan = _sizeofIndex + 2; int normalOffset = indexOffset + (index * indexSpan) + _sizeofIndex; var u = _array[normalOffset]; var v = _array[normalOffset + 1]; var pn = new XbimPackedNormal(u, v); return(pn.Normal); } }
public void PackedNormalTests() { var vectors = (List<XbimVector3D>)UniformPointsOnSphere(100); foreach (var v in vectors) { var packed = new XbimPackedNormal(v); var v2 = packed.Normal; var a = v.CrossProduct(v2); var x = Math.Abs(a.Length); var y = v.DotProduct(v2); var angle = Math.Atan2(x, y); if (angle > 0.1) Debug.WriteLine("vector: {0}, angle: {1:F3}", v, angle); Assert.IsTrue(angle < 0.13); } //text axis aligned normals (this should be much more precise) var vArray = new[] { new XbimVector3D(0, 0, 1), new XbimVector3D(0, 0, -1), new XbimVector3D(0, 1, 0), new XbimVector3D(0, -1, 0), new XbimVector3D(1, 0, 0), new XbimVector3D(-1, 0, 0) }; foreach (var v in vArray) { var packed = new XbimPackedNormal(v); var v2 = packed.Normal; var a = v.CrossProduct(v2); var x = Math.Abs(a.Length); var y = v.DotProduct(v2); var angle = Math.Atan2(x, y); Assert.IsTrue(angle < 1e-10); } }
internal void AddNormal(XbimPackedNormal xbimPackedNormal) { _normals.Add(xbimPackedNormal); }
public static void Read(this MeshGeometry3D m3D, string shapeData, XbimMatrix3D? transform = null) { transform = null; RotateTransform3D qrd = new RotateTransform3D(); Matrix3D? matrix3D = null; if (transform.HasValue) { XbimQuaternion xq = transform.Value.GetRotationQuaternion(); qrd.Rotation = new QuaternionRotation3D(new Quaternion(xq.X, xq.Y, xq.Z, xq.W)); matrix3D = transform.Value.ToMatrix3D(); } using (StringReader sr = new StringReader(shapeData)) { int version = 1; List<Point3D> vertexList = new List<Point3D>(512); //holds the actual unique positions of the vertices in this data set in the mesh List<Vector3D> normalList = new List<Vector3D>(512); //holds the actual unique normals of the vertices in this data set in the mesh List<Point3D> positions = new List<Point3D>(1024); //holds the actual positions of the vertices in this data set in the mesh List<Vector3D> normals = new List<Vector3D>(1024); //holds the actual normals of the vertices in this data set in the mesh List<int> triangleIndices = new List<int>(2048); String line; // Read and display lines from the data until the end of // the data is reached. while ((line = sr.ReadLine()) != null) { string[] tokens = line.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); if (tokens.Length > 0) //we need a command { string command = tokens[0].Trim().ToUpper(); switch (command) { case "P": vertexList = new List<Point3D>(512); normalList = new List<Vector3D>(512); if (tokens.Length > 0) version = Int32.Parse(tokens[1]); break; case "V": //process vertices for (int i = 1; i < tokens.Length; i++) { string[] xyz = tokens[i].Split(','); Point3D p = new Point3D(Convert.ToDouble(xyz[0], CultureInfo.InvariantCulture), Convert.ToDouble(xyz[1], CultureInfo.InvariantCulture), Convert.ToDouble(xyz[2], CultureInfo.InvariantCulture)); if (matrix3D.HasValue) p = matrix3D.Value.Transform(p); vertexList.Add(p); } break; case "N": //processes normals for (int i = 1; i < tokens.Length; i++) { string[] xyz = tokens[i].Split(','); Vector3D v = new Vector3D(Convert.ToDouble(xyz[0], CultureInfo.InvariantCulture), Convert.ToDouble(xyz[1], CultureInfo.InvariantCulture), Convert.ToDouble(xyz[2], CultureInfo.InvariantCulture)); normalList.Add(v); } break; case "T": //process triangulated meshes Vector3D currentNormal = new Vector3D(0,0,0); //each time we start a new mesh face we have to duplicate the vertices to ensure that we get correct shading of planar and non planar faces var writtenVertices = new Dictionary<int, int>(); for (int i = 1; i < tokens.Length; i++) { string[] indices = tokens[i].Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); if (indices.Length != 3) throw new Exception("Invalid triangle definition"); for (int t = 0; t < 3; t++) { string[] indexNormalPair = indices[t].Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); if (indexNormalPair.Length > 1) //we have a normal defined { if (version == 1) { string normalStr = indexNormalPair[1].Trim(); switch (normalStr) { case "F": //Front currentNormal = new Vector3D(0, -1, 0); break; case "B": //Back currentNormal = new Vector3D(0, 1, 0); break; case "L": //Left currentNormal = new Vector3D(-1, 0, 0); break; case "R": //Right currentNormal = new Vector3D(1, 0, 0); break; case "U": //Up currentNormal = new Vector3D(0, 0, 1); break; case "D": //Down currentNormal = new Vector3D(0, 0, -1); break; default: //it is an index number int normalIndex = int.Parse(indexNormalPair[1]); currentNormal = normalList[normalIndex]; break; } } else //we have support for packed normals { var packedNormal = new XbimPackedNormal(ushort.Parse(indexNormalPair[1])); var n = packedNormal.Normal; currentNormal = new Vector3D(n.X, n.Y, n.Z); } if (matrix3D.HasValue) { currentNormal = qrd.Transform(currentNormal); } } //now add the index int index = int.Parse(indexNormalPair[0]); int alreadyWrittenAt; //in case it is the first mesh if (!writtenVertices.TryGetValue(index, out alreadyWrittenAt)) //if we haven't written it in this mesh pass, add it again unless it is the first one which we know has been written { //all vertices will be unique and have only one normal writtenVertices.Add(index, positions.Count); triangleIndices.Add(positions.Count + m3D.TriangleIndices.Count); positions.Add(vertexList[index]); normals.Add(currentNormal); } else //just add the index reference { if(normals[alreadyWrittenAt] == currentNormal) triangleIndices.Add(alreadyWrittenAt); else //we need another { triangleIndices.Add(positions.Count + m3D.TriangleIndices.Count); positions.Add(vertexList[index]); normals.Add(currentNormal); } } } } break; case "F": //skip faces for now, can be used to draw edges break; default: throw new Exception("Invalid Geometry Command"); } } } m3D.Positions = new Point3DCollection(m3D.Positions.Concat(positions)); //we do this for wpf performance issues m3D.Normals = new Vector3DCollection(m3D.Normals.Concat(normals)); //we do this for wpf performance issues m3D.TriangleIndices = new Int32Collection(m3D.TriangleIndices.Concat(triangleIndices)); //we do this for wpf performance issues } }
/// <summary> /// Reads an ascii string of Xbim mesh geometry data /// </summary> /// <param name="data"></param> /// <returns></returns> public bool Read(String data, XbimMatrix3D? trans = null) { var version = 2; //we are at at least verson 2 now var q = new XbimQuaternion(); if (trans.HasValue) q = trans.Value.GetRotationQuaternion(); using (var sr = new StringReader(data)) { var vertexList = new List<XbimPoint3D>(); //holds the actual positions of the vertices in this data set in the mesh var normalList = new List<XbimVector3D>(); //holds the actual normals of the vertices in this data set in the mesh String line; // Read and display lines from the data until the end of // the data is reached. while ((line = sr.ReadLine()) != null) { var tokens = line.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); if (tokens.Length > 1) //we need a command and some data { var command = tokens[0].Trim().ToUpper(); switch (command) { case "P": version = Int32.Parse(tokens[1]); var pointCount = 512; //var faceCount = 128; //var triangleCount = 256; var normalCount = 512; if (tokens.Length > 1) pointCount = Int32.Parse(tokens[2]); // if (tokens.Length > 2) faceCount = Int32.Parse(tokens[3]); // if (tokens.Length > 3) triangleCount = Int32.Parse(tokens[4]); //version 2 of the string format uses packed normals if (version < 2 && tokens.Length > 4) normalCount = Int32.Parse(tokens[5]); vertexList = new List<XbimPoint3D>(pointCount); normalList = new List<XbimVector3D>(normalCount); break; case "V": //process vertices for (var i = 1; i < tokens.Length; i++) { var xyz = tokens[i].Split(','); var p = new XbimPoint3D(Convert.ToDouble(xyz[0], CultureInfo.InvariantCulture), Convert.ToDouble(xyz[1], CultureInfo.InvariantCulture), Convert.ToDouble(xyz[2], CultureInfo.InvariantCulture)); if (trans.HasValue) p = trans.Value.Transform(p); vertexList.Add(p); } break; case "N": //processes normals for (var i = 1; i < tokens.Length; i++) { var xyz = tokens[i].Split(','); var v = new XbimVector3D(Convert.ToDouble(xyz[0], CultureInfo.InvariantCulture), Convert.ToDouble(xyz[1], CultureInfo.InvariantCulture), Convert.ToDouble(xyz[2], CultureInfo.InvariantCulture)); normalList.Add(v); } break; case "T": //process triangulated meshes var currentNormal = XbimVector3D.Zero; //each time we start a new mesh face we have to duplicate the vertices to ensure that we get correct shading of planar and non planar faces var writtenVertices = new Dictionary<int, int>(); for (var i = 1; i < tokens.Length; i++) { var triangleIndices = tokens[i].Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); if (triangleIndices.Length != 3) throw new Exception("Invalid triangle definition"); for (var t = 0; t < 3; t++) { var indexNormalPair = triangleIndices[t].Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); if (indexNormalPair.Length > 1) //we have a normal defined { var normalStr = indexNormalPair[1].Trim(); if (version < 2) { switch (normalStr) { case "F": //Front currentNormal = new XbimVector3D(0, -1, 0); break; case "B": //Back currentNormal = new XbimVector3D(0, 1, 0); break; case "L": //Left currentNormal = new XbimVector3D(-1, 0, 0); break; case "R": //Right currentNormal = new XbimVector3D(1, 0, 0); break; case "U": //Up currentNormal = new XbimVector3D(0, 0, 1); break; case "D": //Down currentNormal = new XbimVector3D(0, 0, -1); break; default: //it is an index number var normalIndex = int.Parse(indexNormalPair[1]); currentNormal = normalList[normalIndex]; break; } } else { var normalIndex = ushort.Parse(indexNormalPair[1]); var packedNormal = new XbimPackedNormal(normalIndex); currentNormal = packedNormal.Normal; } if (trans.HasValue) { XbimVector3D v; XbimQuaternion.Transform(ref currentNormal, ref q, out v); currentNormal = v; } } //now add the index var index = int.Parse(indexNormalPair[0]); int alreadyWrittenAt; if (!writtenVertices.TryGetValue(index, out alreadyWrittenAt)) //if we haven't written it in this mesh pass, add it again unless it is the first one which we know has been written { //all vertices will be unique and have only one normal writtenVertices.Add(index, PositionCount); TriangleIndices.Add(PositionCount); Positions.Add(vertexList[index]); Normals.Add(currentNormal); } else //just add the index reference { TriangleIndices.Add(alreadyWrittenAt); } } } break; case "F": break; default: throw new Exception("Invalid Geometry Command"); } } } } return true; }
public PointAndNormal(int index, XbimPackedNormal n) { Index = index; Normal = n; }
public bool Read(string data, XbimMatrix3D? tr = null) { int version = 1; using (var sr = new StringReader(data)) { Matrix3D? m3D = null; var r = new RotateTransform3D(); if (tr.HasValue) //set up the windows media transforms { m3D = new Matrix3D(tr.Value.M11, tr.Value.M12, tr.Value.M13, tr.Value.M14, tr.Value.M21, tr.Value.M22, tr.Value.M23, tr.Value.M24, tr.Value.M31, tr.Value.M32, tr.Value.M33, tr.Value.M34, tr.Value.OffsetX, tr.Value.OffsetY, tr.Value.OffsetZ, tr.Value.M44); r = tr.Value.GetRotateTransform3D(); } var vertexList = new Point3DCollection(); //holds the actual positions of the vertices in this data set in the mesh var normalList = new Vector3DCollection(); //holds the actual normals of the vertices in this data set in the mesh string line; // Read and display lines from the data until the end of // the data is reached. while ((line = sr.ReadLine()) != null) { var tokens = line.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); if (tokens.Length <= 1) continue; var command = tokens[0].Trim().ToUpper(); switch (command) { case "P": var pointCount = 512; // ReSharper disable once NotAccessedVariable var faceCount = 128; // ReSharper disable once NotAccessedVariable var triangleCount = 256; var normalCount = 512; if (tokens.Length > 0) version = Int32.Parse(tokens[1]); if (tokens.Length > 1) pointCount = Int32.Parse(tokens[2]); // ReSharper disable once RedundantAssignment if (tokens.Length > 2) faceCount = Int32.Parse(tokens[3]); // ReSharper disable once RedundantAssignment if (tokens.Length > 3) triangleCount = Int32.Parse(tokens[4]); if (tokens.Length > 4) normalCount = Math.Max(Int32.Parse(tokens[5]),pointCount); //can't really have less normals than points vertexList = new Point3DCollection(pointCount); normalList = new Vector3DCollection(normalCount); //for efficienciency avoid continual regrowing //this.Mesh.Positions = this.Mesh.Positions.GrowBy(pointCount); //this.Mesh.Normals = this.Mesh.Normals.GrowBy(normalCount); //this.Mesh.TriangleIndices = this.Mesh.TriangleIndices.GrowBy(triangleCount*3); break; case "F": break; case "V": //process vertices for (int i = 1; i < tokens.Length; i++) { string[] xyz = tokens[i].Split(','); Point3D p = new Point3D(Convert.ToDouble(xyz[0], CultureInfo.InvariantCulture), Convert.ToDouble(xyz[1], CultureInfo.InvariantCulture), Convert.ToDouble(xyz[2], CultureInfo.InvariantCulture)); if (m3D.HasValue) p = m3D.Value.Transform(p); vertexList.Add(p); } break; case "N": //processes normals for (int i = 1; i < tokens.Length; i++) { string[] xyz = tokens[i].Split(','); Vector3D v = new Vector3D(Convert.ToDouble(xyz[0], CultureInfo.InvariantCulture), Convert.ToDouble(xyz[1], CultureInfo.InvariantCulture), Convert.ToDouble(xyz[2], CultureInfo.InvariantCulture)); normalList.Add(v); } break; case "T": //process triangulated meshes var currentNormal = new Vector3D(); //each time we start a new mesh face we have to duplicate the vertices to ensure that we get correct shading of planar and non planar faces var writtenVertices = new Dictionary<int, int>(); for (var i = 1; i < tokens.Length; i++) { var triangleIndices = tokens[i].Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); if (triangleIndices.Length != 3) throw new Exception("Invalid triangle definition"); for (var t = 0; t < 3; t++) { var indexNormalPair = triangleIndices[t].Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); if (indexNormalPair.Length > 1) //we have a normal defined { if (version == 1) { var normalStr = indexNormalPair[1].Trim(); switch (normalStr) { case "F": //Front currentNormal = new Vector3D(0, -1, 0); break; case "B": //Back currentNormal = new Vector3D(0, 1, 0); break; case "L": //Left currentNormal = new Vector3D(-1, 0, 0); break; case "R": //Right currentNormal = new Vector3D(1, 0, 0); break; case "U": //Up currentNormal = new Vector3D(0, 0, 1); break; case "D": //Down currentNormal = new Vector3D(0, 0, -1); break; default: //it is an index number int normalIndex = int.Parse(indexNormalPair[1]); currentNormal = normalList[normalIndex]; break; } } else //we have support for packed normals { var packedNormal = new XbimPackedNormal(ushort.Parse(indexNormalPair[1])); var n = packedNormal.Normal; currentNormal = new Vector3D(n.X, n.Y, n.Z); } if (tr.HasValue) { currentNormal = r.Transform(currentNormal); } } //now add the index var index = int.Parse(indexNormalPair[0]); int alreadyWrittenAt; //in case it is the first mesh if (!writtenVertices.TryGetValue(index, out alreadyWrittenAt)) //if we haven't written it in this mesh pass, add it again unless it is the first one which we know has been written { //all vertices will be unique and have only one normal writtenVertices.Add(index, PositionCount); _unfrozenIndices.Add(PositionCount); _unfrozenPositions.Add(vertexList[index]); _unfrozenNormals.Add(currentNormal); } else //just add the index reference { if (_unfrozenNormals[alreadyWrittenAt] == currentNormal) _unfrozenIndices.Add(alreadyWrittenAt); else //we need another { _unfrozenIndices.Add(PositionCount); _unfrozenPositions.Add(vertexList[index]); _unfrozenNormals.Add(currentNormal); } } } } break; default: throw new Exception("Invalid Geometry Command"); } } } return true; }
private IXbimShapeGeometryData MeshPolyhedronBinary(IEnumerable<IEnumerable<IfcFace>> facesList, int entityLabel, float precision) { IXbimShapeGeometryData shapeGeometry = new XbimShapeGeometry(); shapeGeometry.Format = (byte)XbimGeometryType.PolyhedronBinary; using (var ms = new MemoryStream(0x4000)) using (var binaryWriter = new BinaryWriter(ms)) { var faceLists = facesList as IList<IEnumerable<IfcFace>> ?? facesList.ToList(); var triangulations = new List<XbimTriangulatedMesh>(faceLists.Count); foreach (var faceList in faceLists) triangulations.Add(TriangulateFaces(faceList, entityLabel, precision)); // Write out header uint verticesCount = 0; uint triangleCount = 0; uint facesCount = 0; var boundingBox = XbimRect3D.Empty; foreach (var triangulatedMesh in triangulations) { verticesCount += triangulatedMesh.VertexCount; triangleCount += triangulatedMesh.TriangleCount; facesCount += (uint)triangulatedMesh.Faces.Count; if (boundingBox.IsEmpty) boundingBox = triangulatedMesh.BoundingBox; else boundingBox.Union(triangulatedMesh.BoundingBox); } binaryWriter.Write((byte)1); //stream format version // ReSharper disable once RedundantCast binaryWriter.Write((UInt32)verticesCount); //number of vertices binaryWriter.Write(triangleCount); //number of triangles foreach (var v in triangulations.SelectMany(t=>t.Vertices)) { binaryWriter.Write(v.X); binaryWriter.Write(v.Y); binaryWriter.Write(v.Z); } shapeGeometry.BoundingBox = boundingBox.ToFloatArray(); //now write out the faces binaryWriter.Write(facesCount); uint verticesOffset = 0; int invalidNormal = new XbimPackedNormal(255, 255).ToUnit16(); foreach (var triangulatedMesh in triangulations) { foreach (var faceGroup in triangulatedMesh.Faces) { var numTrianglesInFace = faceGroup.Value.Count; //we need to fix this var planar = invalidNormal != faceGroup.Key; //we have a mesh of faces that all have the same normals at their vertices if (!planar) numTrianglesInFace *= -1; //set flag to say multiple normals binaryWriter.Write((Int32)numTrianglesInFace); bool first = true; foreach (var triangle in faceGroup.Value) { if (planar && first) { triangle[0].PackedNormal.Write(binaryWriter); first = false; } WriteIndex(binaryWriter, (uint)triangle[0].StartVertexIndex + verticesOffset, verticesCount); if (!planar) triangle[0].PackedNormal.Write(binaryWriter); WriteIndex(binaryWriter, (uint)triangle[0].NextEdge.StartVertexIndex + verticesOffset, verticesCount); if (!planar) triangle[0].NextEdge.PackedNormal.Write(binaryWriter); WriteIndex(binaryWriter, (uint)triangle[0].NextEdge.NextEdge.StartVertexIndex + verticesOffset, verticesCount); if (!planar) triangle[0].NextEdge.NextEdge.PackedNormal.Write(binaryWriter); } } verticesOffset += triangulatedMesh.VertexCount; } binaryWriter.Flush(); shapeGeometry.ShapeData = ms.ToArray(); } return shapeGeometry; }