/// <summary> /// Creates a flat polygon. /// </summary> /// <param name="polygon2D">A 2D <see cref="GraphicsPath"/> representing the polygon.</param> /// <param name="triangulationResolution">The resolution that will be used to linearise curve segments in the <see cref="GraphicsPath"/>.</param> /// <param name="origin">A <see cref="Point3D"/> that will correspond to the origin of the 2D reference system.</param> /// <param name="xAxis">A <see cref="NormalizedVector3D"/> that will correspond to the x axis of the 2D reference system. This will be orthonormalised to the <paramref name="yAxis"/>.</param> /// <param name="yAxis">A <see cref="NormalizedVector3D"/> that will correspond to the y axis of the 2D reference system.</param> /// <param name="reverseTriangles">Indicates whether the order of the points (and thus the normals) of all the triangles returned by this method should be reversed.</param> /// <param name="fill">A collection of materials that will be applied to the <see cref="Triangle3DElement"/>s returned by this method.</param> /// <param name="tag">A tag that will be applied to the <see cref="Triangle3DElement"/>s returned by this method.</param> /// <param name="zIndex">A z-index that will be applied to the <see cref="Triangle3DElement"/>s returned by this method.</param> /// <returns>A list of <see cref="Triangle3DElement"/>s that constitute the polygon.</returns> public static List <Element3D> CreatePolygon(GraphicsPath polygon2D, double triangulationResolution, Point3D origin, NormalizedVector3D xAxis, NormalizedVector3D yAxis, bool reverseTriangles, IEnumerable <IMaterial> fill, string tag = null, int zIndex = 0) { xAxis = (xAxis - yAxis * (xAxis * yAxis)).Normalize(); List <GraphicsPath> triangles = polygon2D.Triangulate(triangulationResolution, true).ToList(); List <Element3D> tbr = new List <Element3D>(triangles.Count); for (int i = 0; i < triangles.Count; i++) { Point p1 = triangles[i].Segments[0].Point; Point p2 = triangles[i].Segments[1].Point; Point p3 = triangles[i].Segments[2].Point; Point3D p13D = origin + xAxis * p1.X + yAxis * p1.Y; Point3D p23D = origin + xAxis * p2.X + yAxis * p2.Y; Point3D p33D = origin + xAxis * p3.X + yAxis * p3.Y; Triangle3DElement t = !reverseTriangles ? new Triangle3DElement(p13D, p23D, p33D) : new Triangle3DElement(p13D, p33D, p23D); t.Fill.AddRange(fill); t.Tag = tag; t.ZIndex = zIndex; tbr.Add(t); } return(tbr); }
private static (byte R, byte G, byte B, byte A) GetPixelColor(Triangle3DElement triangle, Point3D correspPoint, Camera camera, List <ILightSource> lights, List <double> obstructions) { NormalizedVector3D normal = triangle.GetNormalAt(correspPoint); byte R = 0; byte G = 0; byte B = 0; byte A = 0; for (int i = 0; i < triangle.Fill.Count; i++) { Colour col = triangle.Fill[i].GetColour(correspPoint, normal, camera, lights, obstructions); if (col.A == 1) { R = (byte)(col.R * 255); G = (byte)(col.G * 255); B = (byte)(col.B * 255); A = (byte)(col.A * 255); } else { BlendFront(ref R, ref G, ref B, ref A, (byte)(col.R * 255), (byte)(col.G * 255), (byte)(col.B * 255), (byte)(col.A * 255)); } } return(R, G, B, A); }
/// <summary> /// Creates a new <see cref="VectorRendererTriangle3DElement"/> based on the specified base <paramref name="triangle"/>. /// </summary> /// <param name="triangle">The base <see cref="Triangle3DElement"/> from which all property values will be copied.</param> public VectorRendererTriangle3DElement(Triangle3DElement triangle) : this(triangle.Point1, triangle.Point2, triangle.Point3, triangle.Point1Normal, triangle.Point2Normal, triangle.Point3Normal) { this.CastsShadow = triangle.CastsShadow; this.Fill = triangle.Fill; this.ReceivesShadow = triangle.ReceivesShadow; this.Tag = triangle.Tag; this.ZIndex = triangle.ZIndex; }
/// <inheritdoc/> public override Point3D Deproject(Point point, Triangle3DElement triangle) { Point3D rotatedPoint = new Point3D(point.X / ScaleFactor, point.Y / ScaleFactor, 0); Point3D projectedPoint = RotationMatrix.Inverse() * (CameraRotationMatrix.Inverse() * rotatedPoint); Point3D cameraPlanePoint = projectedPoint + (Vector3D)this.Position; Point3D centroid = (Point3D)(((Vector3D)triangle[0] + (Vector3D)triangle[1] + (Vector3D)triangle[2]) * (1.0 / 3.0)); Vector3D l = Direction; double d = ((centroid - cameraPlanePoint) * triangle.ActualNormal) / (l * triangle.ActualNormal); Point3D pt = cameraPlanePoint + l * d; return(pt); }
/// <summary> /// Creates a quadrilater. All the vertices need not be coplanar. /// </summary> /// <param name="point1">The first vertex of the quadrilater.</param> /// <param name="point2">The second vertex of the quadrilater.</param> /// <param name="point3">The third vertex of the quadrilater.</param> /// <param name="point4">The fourth vertex of the quadrilater.</param> /// <param name="fill">A collection of materials that will be applied to the <see cref="Triangle3DElement"/>s returned by this method.</param> /// <param name="tag">A tag that will be applied to the <see cref="Triangle3DElement"/>s returned by this method.</param> /// <param name="zIndex">A z-index that will be applied to the <see cref="Triangle3DElement"/>s returned by this method.</param> /// <returns>A list containing two <see cref="Triangle3DElement"/>s representing the quadrilater.</returns> public static List <Element3D> CreateRectangle(Point3D point1, Point3D point2, Point3D point3, Point3D point4, IEnumerable <IMaterial> fill, string tag = null, int zIndex = 0) { Triangle3DElement triangle1 = new Triangle3DElement(point1, point2, point3); triangle1.Fill.AddRange(fill); triangle1.Tag = tag; triangle1.ZIndex = zIndex; Triangle3DElement triangle2 = new Triangle3DElement(point1, point3, point4); triangle2.Fill.AddRange(fill); triangle2.Tag = tag; triangle2.ZIndex = zIndex; return(new List <Element3D> { triangle1, triangle2 }); }
private static (byte R, byte G, byte B, byte A) GetPixelColorWithShadow(Triangle3DElement triangle, List <ILightSource> lights, IEnumerable <Triangle3DElement> shadowers, Point3D correspPoint, Camera camera) { List <double> pixelObstructions = new List <double>(lights.Count); for (int i = 0; i < lights.Count; i++) { if (!lights[i].CastsShadow) { pixelObstructions.Add(0); } else { pixelObstructions.Add(lights[i].GetObstruction(correspPoint, from el in shadowers where el != triangle select el)); } } byte R = 0; byte G = 0; byte B = 0; byte A = 0; NormalizedVector3D normal = triangle.GetNormalAt(correspPoint); for (int i = 0; i < triangle.Fill.Count; i++) { Colour col = triangle.Fill[i].GetColour(correspPoint, normal, camera, lights, pixelObstructions); if (col.A == 1) { R = (byte)(col.R * 255); G = (byte)(col.G * 255); B = (byte)(col.B * 255); A = (byte)(col.A * 255); } else { BlendFront(ref R, ref G, ref B, ref A, (byte)(col.R * 255), (byte)(col.G * 255), (byte)(col.B * 255), (byte)(col.A * 255)); } } return(R, G, B, A); }
/// <summary> /// Applies the transformation to a <see cref="Triangle3DElement"/>. /// </summary> /// <param name="triangle">The <see cref="Triangle3DElement"/> to which the transformation should be applied.</param> /// <returns>A <see cref="Triangle3DElement"/> corresponding to a triangle in which the transformation has been applied to the points from <paramref name="triangle" />. Properties are preserved between the two elements.</returns> public Triangle3DElement Apply(Triangle3DElement triangle) { Point3D p1 = this.Apply(triangle.Point1); Point3D p2 = this.Apply(triangle.Point2); Point3D p3 = this.Apply(triangle.Point3); if (!triangle.IsFlat) { Point3D p1Ref = triangle.Point1 + (Vector3D)triangle.Point1Normal; Point3D p2Ref = triangle.Point2 + (Vector3D)triangle.Point2Normal; Point3D p3Ref = triangle.Point3 + (Vector3D)triangle.Point3Normal; p1Ref = this.Apply(p1Ref); p2Ref = this.Apply(p2Ref); p3Ref = this.Apply(p3Ref); NormalizedVector3D n1 = (p1Ref - p1).Normalize(); NormalizedVector3D n2 = (p2Ref - p2).Normalize(); NormalizedVector3D n3 = (p3Ref - p3).Normalize(); Triangle3DElement tbr = new Triangle3DElement(p1, p2, p3, n1, n2, n3) { CastsShadow = triangle.CastsShadow, ReceivesShadow = triangle.ReceivesShadow, Tag = triangle.Tag, ZIndex = triangle.ZIndex }; tbr.Fill.AddRange(triangle.Fill); return(tbr); } else { Triangle3DElement tbr = new Triangle3DElement(p1, p2, p3) { CastsShadow = triangle.CastsShadow, ReceivesShadow = triangle.ReceivesShadow, Tag = triangle.Tag, ZIndex = triangle.ZIndex }; tbr.Fill.AddRange(triangle.Fill); return(tbr); } }
/// <summary> /// Creates a tetrahedron inscribed in a sphere. /// </summary> /// <param name="center">The centre of the tetrahedron.</param> /// <param name="radius">The radius of the sphere in which the tetrahedron is inscribed.</param> /// <param name="fill">A collection of materials that will be applied to the <see cref="Triangle3DElement"/>s returned by this method.</param> /// <param name="tag">A tag that will be applied to the <see cref="Triangle3DElement"/>s returned by this method.</param> /// <param name="zIndex">A z-index that will be applied to the <see cref="Triangle3DElement"/>s returned by this method.</param> /// <returns>A list of <see cref="Triangle3DElement"/>s that constitute the sphere.</returns> public static List <Element3D> CreateTetrahedron(Point3D center, double radius, IEnumerable <IMaterial> fill, string tag = null, int zIndex = 0) { Point3D tip = new Point3D(center.X, center.Y - radius, center.Z); Point3D base1 = new Point3D(Math.Sqrt(8.0 / 9) * radius + center.X, center.Y + radius / 3, center.Z); Point3D base2 = new Point3D(-Math.Sqrt(2.0 / 9) * radius + center.X, center.Y + radius / 3, center.Z + Math.Sqrt(2.0 / 3) * radius); Point3D base3 = new Point3D(-Math.Sqrt(2.0 / 9) * radius + center.X, center.Y + radius / 3, center.Z - Math.Sqrt(2.0 / 3) * radius); Triangle3DElement faceTriangle1 = new Triangle3DElement(tip, base2, base1) { Tag = tag, ZIndex = zIndex }; faceTriangle1.Fill.AddRange(fill); Triangle3DElement faceTriangle2 = new Triangle3DElement(tip, base3, base2) { Tag = tag, ZIndex = zIndex }; faceTriangle2.Fill.AddRange(fill); Triangle3DElement faceTriangle3 = new Triangle3DElement(tip, base1, base3) { Tag = tag, ZIndex = zIndex }; faceTriangle3.Fill.AddRange(fill); Triangle3DElement baseTriangle = new Triangle3DElement(base1, base2, base3) { Tag = tag, ZIndex = zIndex }; baseTriangle.Fill.AddRange(fill); return(new List <Element3D>() { faceTriangle1, faceTriangle2, faceTriangle3, baseTriangle }); }
/// <summary> /// Creates a sphere. /// </summary> /// <param name="center">The centre of the sphere.</param> /// <param name="radius">The radius of the sphere.</param> /// <param name="steps">The number of meridians and parallels to use when generating the sphere.</param> /// <param name="fill">A collection of materials that will be applied to the <see cref="Triangle3DElement"/>s returned by this method.</param> /// <param name="tag">A tag that will be applied to the <see cref="Triangle3DElement"/>s returned by this method.</param> /// <param name="zIndex">A z-index that will be applied to the <see cref="Triangle3DElement"/>s returned by this method.</param> /// <returns>A list of <see cref="Triangle3DElement"/>s that constitute the sphere.</returns> public static List <Element3D> CreateSphere(Point3D center, double radius, int steps, IEnumerable <IMaterial> fill, string tag = null, int zIndex = 0) { List <Point3D> points = new List <Point3D>(); for (int t = 0; t <= steps; t++) { for (int p = 0; p < steps * 2; p++) { double theta = Math.PI / steps * t; double phi = Math.PI / steps * p; double x = center.X + radius * Math.Sin(theta) * Math.Cos(phi); double y = center.Y + radius * Math.Sin(theta) * Math.Sin(phi); double z = center.Z + radius * Math.Cos(theta); points.Add(new Point3D(x, y, z)); if (t == 0 || t == steps) { break; } } } List <Element3D> tbr = new List <Element3D>(4 * steps + (points.Count - 2 - 2 * steps) * 2); for (int i = 0; i < points.Count - 1; i++) { if (i == 0) { for (int j = 0; j < 2 * steps; j++) { Point3D p1 = points[i]; Point3D p3 = points[i + 1 + j]; Point3D p2 = points[i + 1 + (j + 1) % (2 * steps)]; Triangle3DElement tri = new Triangle3DElement(p1, p2, p3, (center - p1).Normalize(), (center - p2).Normalize(), (center - p3).Normalize()); tri.Fill.AddRange(fill); tri.Tag = tag; tri.ZIndex = zIndex; tbr.Add(tri); } } else if (i >= points.Count - 1 - 2 * steps) { Point3D p1 = points[i]; Point3D p3 = points[points.Count - 1]; Point3D p2 = points[points.Count - 1 - 2 * steps + (i - (points.Count - 1 - 2 * steps) + 1) % (2 * steps)]; Triangle3DElement tri = new Triangle3DElement(p1, p2, p3, (center - p1).Normalize(), (center - p2).Normalize(), (center - p3).Normalize()); tri.Fill.AddRange(fill); tri.Tag = tag; tri.ZIndex = zIndex; tbr.Add(tri); } else { if ((i - 1) % (2 * steps) < 2 * steps - 1) { Point3D p4 = points[i + 2 * steps]; Point3D p3 = points[i + 2 * steps + 1]; Point3D p2 = points[i + 1]; Point3D p1 = points[i]; tbr.AddRange(CreateRectangle(p1, p2, p3, p4, (center - p1).Normalize(), (center - p2).Normalize(), (center - p3).Normalize(), (center - p4).Normalize(), fill, tag, zIndex)); } else { Point3D p4 = points[i + 2 * steps]; Point3D p3 = points[(i / (2 * steps)) * 2 * steps + 1]; Point3D p2 = points[(i / (2 * steps) - 1) * 2 * steps + 1]; Point3D p1 = points[i]; tbr.AddRange(CreateRectangle(p1, p2, p3, p4, (center - p1).Normalize(), (center - p2).Normalize(), (center - p3).Normalize(), (center - p4).Normalize(), fill, tag, zIndex)); } } } return(tbr); }
private unsafe void FillTriangleWithShadow(byte *imageData, Triangle3DElement triangle, Camera camera, List <ILightSource> lights, List <Triangle3DElement> shadowers) { Point[] triangle2D = triangle.GetProjection(); int minX = int.MaxValue; int minY = int.MaxValue; int maxX = int.MinValue; int maxY = int.MinValue; for (int i = 0; i < triangle2D.Length; i++) { triangle2D[i] = new Point((triangle2D[i].X - camera.TopLeft.X) / camera.Size.Width * this.RenderWidth, (triangle2D[i].Y - camera.TopLeft.Y) / camera.Size.Height * this.RenderHeight); minX = Math.Min(minX, (int)triangle2D[i].X); minY = Math.Min(minY, (int)triangle2D[i].Y); maxX = Math.Max(maxX, (int)Math.Ceiling(triangle2D[i].X)); maxY = Math.Max(maxY, (int)Math.Ceiling(triangle2D[i].Y)); } minX = Math.Max(minX, 0); minY = Math.Max(minY, 0); maxX = Math.Min(maxX, this.RenderWidth - 1); maxY = Math.Min(maxY, this.RenderHeight - 1); List <Triangle3DElement> otherShadowers = new List <Triangle3DElement>(shadowers.Count); for (int i = 0; i < shadowers.Count; i++) { if (shadowers[i] != triangle) { otherShadowers.Add(shadowers[i]); } } int totalPixels = (maxX - minX + 1) * (maxY - minY + 1); Parallel.For(0, totalPixels, index => { int y = index / (maxX - minX + 1) + minY; int x = index % (maxX - minX + 1) + minX; if (Intersections2D.PointInTriangle(x, y, triangle2D[0], triangle2D[1], triangle2D[2])) { Point3D correspPoint = camera.Deproject(new Point((double)x / this.RenderWidth * camera.Size.Width + camera.TopLeft.X, (double)y / this.RenderHeight * camera.Size.Height + camera.TopLeft.Y), triangle); double zDepth = camera.ZDepth(correspPoint); int prevZIndexBuffer = ZIndexBuffer[y * RenderWidth + x]; if (prevZIndexBuffer < triangle.ZIndex || (prevZIndexBuffer == triangle.ZIndex && ZBuffer[y * RenderWidth + x] > zDepth)) { (byte R, byte G, byte B, byte A) = GetPixelColorWithShadow(triangle, lights, otherShadowers, x, y, correspPoint, camera); if (A == 255) { imageData[y * RenderWidth * 4 + x * 4] = R; imageData[y * RenderWidth * 4 + x * 4 + 1] = G; imageData[y * RenderWidth * 4 + x * 4 + 2] = B; imageData[y * RenderWidth * 4 + x * 4 + 3] = A; } else { BlendFront(ref imageData[y * RenderWidth * 4 + x * 4], ref imageData[y * RenderWidth * 4 + x * 4 + 1], ref imageData[y * RenderWidth * 4 + x * 4 + 2], ref imageData[y * RenderWidth * 4 + x * 4 + 3], R, G, B, A); } ZBuffer[y * RenderWidth + x] = zDepth; ZIndexBuffer[y * RenderWidth + x] = triangle.ZIndex; } else if (imageData[y * RenderWidth * 4 + x * 4 + 3] < 255) { (byte R, byte G, byte B, byte A) = GetPixelColorWithShadow(triangle, lights, otherShadowers, x, y, correspPoint, camera); BlendBack(R, G, B, A, ref imageData[y * RenderWidth * 4 + x * 4], ref imageData[y * RenderWidth * 4 + x * 4 + 1], ref imageData[y * RenderWidth * 4 + x * 4 + 2], ref imageData[y * RenderWidth * 4 + x * 4 + 3]); } } }); }
private unsafe void FillTriangle(byte *imageData, Triangle3DElement triangle, Camera camera, List <ILightSource> lights, List <double> obstructions) { Point[] triangle2D = triangle.GetProjection(); int minX = int.MaxValue; int minY = int.MaxValue; int maxX = int.MinValue; int maxY = int.MinValue; for (int i = 0; i < triangle2D.Length; i++) { triangle2D[i] = new Point((triangle2D[i].X - camera.TopLeft.X) / camera.Size.Width * this.RenderWidth, (triangle2D[i].Y - camera.TopLeft.Y) / camera.Size.Height * this.RenderHeight); minX = Math.Min(minX, (int)triangle2D[i].X); minY = Math.Min(minY, (int)triangle2D[i].Y); maxX = Math.Max(maxX, (int)Math.Ceiling(triangle2D[i].X)); maxY = Math.Max(maxY, (int)Math.Ceiling(triangle2D[i].Y)); } minX = Math.Max(minX, 0); minY = Math.Max(minY, 0); maxX = Math.Min(maxX, this.RenderWidth - 1); maxY = Math.Min(maxY, this.RenderHeight - 1); int totalPixels = (maxX - minX + 1) * (maxY - minY + 1); Parallel.For(0, totalPixels, index => { int y = index / (maxX - minX + 1) + minY; int x = index % (maxX - minX + 1) + minX; if (Intersections2D.PointInTriangle(x, y, triangle2D[0], triangle2D[1], triangle2D[2])) { Point3D correspPoint = camera.Deproject(new Point((double)x / this.RenderWidth * camera.Size.Width + camera.TopLeft.X, (double)y / this.RenderHeight * camera.Size.Height + camera.TopLeft.Y), triangle); double zDepth = camera.ZDepth(correspPoint); int prevZIndexBuffer = ZIndexBuffer[y * RenderWidth + x]; if (prevZIndexBuffer < triangle.ZIndex || (prevZIndexBuffer == triangle.ZIndex && ZBuffer[y * RenderWidth + x] > zDepth)) { byte R = 0; byte G = 0; byte B = 0; byte A = 0; for (int i = 0; i < triangle.Fill.Count; i++) { Colour col = triangle.Fill[i].GetColour(correspPoint, triangle.GetNormalAt(correspPoint), camera, lights, obstructions); if (col.A == 1) { R = (byte)(col.R * 255); G = (byte)(col.G * 255); B = (byte)(col.B * 255); A = (byte)(col.A * 255); } else { BlendFront(ref R, ref G, ref B, ref A, (byte)(col.R * 255), (byte)(col.G * 255), (byte)(col.B * 255), (byte)(col.A * 255)); } } if (A == 255) { imageData[y * RenderWidth * 4 + x * 4] = R; imageData[y * RenderWidth * 4 + x * 4 + 1] = G; imageData[y * RenderWidth * 4 + x * 4 + 2] = B; imageData[y * RenderWidth * 4 + x * 4 + 3] = A; } else { BlendFront(ref imageData[y * RenderWidth * 4 + x * 4], ref imageData[y * RenderWidth * 4 + x * 4 + 1], ref imageData[y * RenderWidth * 4 + x * 4 + 2], ref imageData[y * RenderWidth * 4 + x * 4 + 3], R, G, B, A); } ZBuffer[y * RenderWidth + x] = zDepth; ZIndexBuffer[y * RenderWidth + x] = triangle.ZIndex; } else if (imageData[y * RenderWidth * 4 + x * 4 + 3] < 255) { byte R = 0; byte G = 0; byte B = 0; byte A = 0; for (int i = 0; i < triangle.Fill.Count; i++) { Colour col = triangle.Fill[i].GetColour(correspPoint, triangle.GetNormalAt(correspPoint), camera, lights, obstructions); if (col.A == 1) { R = (byte)(col.R * 255); G = (byte)(col.G * 255); B = (byte)(col.B * 255); A = (byte)(col.A * 255); } else { BlendFront(ref R, ref G, ref B, ref A, (byte)(col.R * 255), (byte)(col.G * 255), (byte)(col.B * 255), (byte)(col.A * 255)); } } BlendBack(R, G, B, A, ref imageData[y * RenderWidth * 4 + x * 4], ref imageData[y * RenderWidth * 4 + x * 4 + 1], ref imageData[y * RenderWidth * 4 + x * 4 + 2], ref imageData[y * RenderWidth * 4 + x * 4 + 3]); } } }); }
/// <summary> /// Projects a <see cref="Point"/> in 2D units to obtain the corresponding <see cref="Point3D"/> on the specified element. /// </summary> /// <param name="point">The <see cref="Point"/> to project.</param> /// <param name="element">The <see cref="Triangle3DElement"/> on which the point should be projected.</param> /// <returns>A <see cref="Point3D"/> corresponding to the point on <paramref name="element"/> that, when projected with the current camera, corresponds to <paramref name="point"/>.</returns> public abstract Point3D Deproject(Point point, Triangle3DElement element);