public void DrawTriangle(Vertex v1, Vertex v2, Vertex v3, Color4 color) { // Sorting the points in order to always have this order on screen p1, p2 & p3 // with p1 always up (thus having the Y the lowest possible to be near the top screen) // then p2 between p1 & p3 if (v1.Coordinates.Y > v2.Coordinates.Y) { var temp = v2; v2 = v1; v1 = temp; } if (v2.Coordinates.Y > v3.Coordinates.Y) { var temp = v2; v2 = v3; v3 = temp; } if (v1.Coordinates.Y > v2.Coordinates.Y) { var temp = v2; v2 = v1; v1 = temp; } Vector3 p1 = v1.Coordinates; Vector3 p2 = v2.Coordinates; Vector3 p3 = v3.Coordinates; // Light position Vector3 lightPos = new Vector3(0, 10, 10); // computing the cos of the angle between the light vector and the normal vector // it will return a value between 0 and 1 that will be used as the intensity of the color float nl1 = ComputeNDotL(v1.WorldCoordinates, v1.Normal, lightPos); float nl2 = ComputeNDotL(v2.WorldCoordinates, v2.Normal, lightPos); float nl3 = ComputeNDotL(v3.WorldCoordinates, v3.Normal, lightPos); var data = new ScanLineData { }; // computing lines' directions float dP1P2, dP1P3; // http://en.wikipedia.org/wiki/Slope // Computing slopes if (p2.Y - p1.Y > 0) dP1P2 = (p2.X - p1.X) / (p2.Y - p1.Y); else dP1P2 = 0; if (p3.Y - p1.Y > 0) dP1P3 = (p3.X - p1.X) / (p3.Y - p1.Y); else dP1P3 = 0; if (dP1P2 > dP1P3) { for (var y = (int)p1.Y; y <= (int)p3.Y; y++) { data.currentY = y; if (y < p2.Y) { data.ndotla = nl1; data.ndotlb = nl3; data.ndotlc = nl1; data.ndotld = nl2; ProcessScanLine(data, v1, v3, v1, v2, color); } else { data.ndotla = nl1; data.ndotlb = nl3; data.ndotlc = nl2; data.ndotld = nl3; ProcessScanLine(data, v1, v3, v2, v3, color); } } } else { for (var y = (int)p1.Y; y <= (int)p3.Y; y++) { data.currentY = y; if (y < p2.Y) { data.ndotla = nl1; data.ndotlb = nl2; data.ndotlc = nl1; data.ndotld = nl3; ProcessScanLine(data, v1, v2, v1, v3, color); } else { data.ndotla = nl2; data.ndotlb = nl3; data.ndotlc = nl1; data.ndotld = nl3; ProcessScanLine(data, v2, v3, v1, v3, color); } } } }
// drawing line between 2 points from left to right // papb -> pcpd // pa, pb, pc, pd must then be sorted before void ProcessScanLine(ScanLineData data, Vertex va, Vertex vb, Vertex vc, Vertex vd, Color4 color) { Vector3 pa = va.Coordinates; Vector3 pb = vb.Coordinates; Vector3 pc = vc.Coordinates; Vector3 pd = vd.Coordinates; // Thanks to current Y, we can compute the gradient to compute others values like // the starting X (sx) and ending X (ex) to draw between // if pa.Y == pb.Y or pc.Y == pd.Y, gradient is forced to 1 var gradient1 = pa.Y != pb.Y ? (data.currentY - pa.Y) / (pb.Y - pa.Y) : 1; var gradient2 = pc.Y != pd.Y ? (data.currentY - pc.Y) / (pd.Y - pc.Y) : 1; int sx = (int)Interpolate(pa.X, pb.X, gradient1); int ex = (int)Interpolate(pc.X, pd.X, gradient2); // starting Z & ending Z float z1 = Interpolate(pa.Z, pb.Z, gradient1); float z2 = Interpolate(pc.Z, pd.Z, gradient2); var snl = Interpolate(data.ndotla, data.ndotlb, gradient1); var enl = Interpolate(data.ndotlc, data.ndotld, gradient2); // drawing a line from left (sx) to right (ex) for (var x = sx; x < ex; x++) { float gradient = (x - sx) / (float)(ex - sx); var z = Interpolate(z1, z2, gradient); var ndotl = Interpolate(snl, enl, gradient); // changing the color value using the cosine of the angle // between the light vector and the normal vector DrawPoint(new Vector3(x, data.currentY, z), color * ndotl); } }
// Project takes some 3D coordinates and transform them // in 2D coordinates using the transformation matrix // It also transform the same coordinates and the norma to the vertex // in the 3D world public Vertex Project(Vertex vertex, Matrix transMat, Matrix world) { // transforming the coordinates into 2D space var point2d = Vector3.TransformCoordinate(vertex.Coordinates, transMat); // transforming the coordinates & the normal to the vertex in the 3D world var point3dWorld = Vector3.TransformCoordinate(vertex.Coordinates, world); var normal3dWorld = Vector3.TransformCoordinate(vertex.Normal, world); // The transformed coordinates will be based on coordinate system // starting on the center of the screen. But drawing on screen normally starts // from top left. We then need to transform them again to have x:0, y:0 on top left. var x = point2d.X * renderWidth + renderWidth / 2.0f; var y = -point2d.Y * renderHeight + renderHeight / 2.0f; return new Vertex { Coordinates = new Vector3(x, y, point2d.Z), Normal = normal3dWorld, WorldCoordinates = point3dWorld }; }
public Mesh(string name, int verticesCount, int facesCount) { Vertices = new Vertex[verticesCount]; Faces = new Face[facesCount]; Name = name; }
public static Mesh LoadOBJ(string objFile) { StreamReader reader = new StreamReader(File.OpenRead(objFile)); List <Vector3> vertices = new List <Vector3>(); List <Vector3> normals = new List <Vector3>(); List <Vector2> uvs = new List <Vector2>(); List <Tuple <int, int, int>[]> faces = new List <Tuple <int, int, int>[]>(); while (!reader.EndOfStream) { string line = reader.ReadLine(); string[] tokens = line.Split(' ').Skip(1).SkipWhile(t => t == string.Empty).ToArray(); if (line.StartsWith("#")) { continue; } else if (line.StartsWith("vn")) { float x = float.Parse(tokens[0]); float y = float.Parse(tokens[1]); float z = float.Parse(tokens[2]); normals.Add(new Vector3(x, y, z)); } else if (line.StartsWith("vt")) { float x = float.Parse(tokens[0]); float y = float.Parse(tokens[1]); uvs.Add(new Vector2(x, y)); } else if (line.StartsWith("v")) { float x = float.Parse(tokens[0]); float y = float.Parse(tokens[1]); float z = float.Parse(tokens[2]); vertices.Add(new Vector3(x, y, z)); } else if (line.StartsWith("f")) { Tuple <int, int, int> v1 = ParseIndicies(tokens[0]); Tuple <int, int, int> v2 = ParseIndicies(tokens[1]); Tuple <int, int, int> v3 = ParseIndicies(tokens[2]); faces.Add(new Tuple <int, int, int>[3] { v1, v2, v3 }); if (tokens.Length > 3) { Tuple <int, int, int> v4 = ParseIndicies(tokens[3]); faces.Add(new Tuple <int, int, int>[3] { v1, v3, v4 }); } } } List <Vertex> modelVerts = new List <Vertex>(); List <Face> modelFaces = new List <Face>(); for (int i = 0; i < faces.Count; i++) { Vertex[] faceVerts = new Vertex[3]; for (int j = 0; j < 3; j++) { int vertIdx = faces[i][j].Item1; int uvIdx = faces[i][j].Item2; int normIdx = faces[i][j].Item3; faceVerts[j] = new Vertex { Coordinates = vertices[vertIdx], Normal = (normIdx == -1) ? Vector3.Zero : normals[normIdx], TextureCoordinates = (uvIdx == -1) ? Vector2.Zero : uvs[uvIdx] }; } modelVerts.AddRange(faceVerts); modelFaces.Add(new Face { A = i * 3 + 0, B = i * 3 + 1, C = i * 3 + 2 }); } Mesh mesh = new Mesh("objMesh", modelVerts.Count, modelFaces.Count); modelVerts.CopyTo(mesh.Vertices); modelFaces.CopyTo(mesh.Faces); mesh.ComputeFacesNormals(); return(mesh); }