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]); } } }); }
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 (byte R, byte G, byte B, byte A) GetPixelColor(Point point, List <Element3D> elements, Camera camera, List <ILightSource> lights, List <Triangle3DElement> shadowers, List <double> noObstructions, bool reproject) { List <(Element3D element, double z, Point3D correspPoint)> hits = new List <(Element3D element, double z, Point3D correspPoint)>(); foreach (Element3D element in elements) { if (element is Triangle3DElement triangle) { Point[] projection; if (reproject) { projection = new Point[] { camera.Project(triangle[0]), camera.Project(triangle[1]), camera.Project(triangle[2]) }; } else { projection = triangle.GetProjection(); } if (Intersections2D.PointInTriangle(point, projection[0], projection[1], projection[2])) { Point3D correspPoint = camera.Deproject(point, triangle); double z = camera.ZDepth(correspPoint); hits.Add((triangle, z, correspPoint)); } } else if (element is Point3DElement pointElement) { Point projection; if (reproject) { projection = camera.Project(pointElement[0]); } else { projection = pointElement.GetProjection()[0]; } if ((point.X - projection.X) * (point.X - projection.X) + (point.Y - projection.Y) * (point.Y - projection.Y) <= pointElement.Diameter * pointElement.Diameter * 0.25) { double z = camera.ZDepth(pointElement.Point); hits.Add((pointElement, z, pointElement.Point)); } } else if (element is Line3DElement line) { Point[] line2D; if (reproject) { line2D = new Point[] { camera.Project(line[0]), camera.Project(line[1]) }; } else { line2D = line.GetProjection(); } double lineLengthSq = (line2D[1].X - line2D[0].X) * (line2D[1].X - line2D[0].X) + (line2D[1].Y - line2D[0].Y) * (line2D[1].Y - line2D[0].Y); double lineLength = Math.Sqrt(lineLengthSq); double addedTerm = line2D[1].X * line2D[0].Y - line2D[1].Y * line2D[0].X; double dy = line2D[1].Y - line2D[0].Y; double dx = line2D[1].X - line2D[0].X; double unitsOn = line.LineDash.UnitsOn / lineLength; double unitsOff = line.LineDash.UnitsOff / lineLength; double phase = line.LineDash.Phase / lineLength; double dist = Math.Abs(dy * point.X - dx * point.Y + addedTerm) / lineLength; double thickness = line.Thickness * 0.5; if (dist < thickness) { (double t, Point pointOnLine) = Intersections2D.ProjectOnSegment(point, line2D[0], line2D[1]); bool isIn = false; if (line.LineCap == LineCaps.Butt) { if (t >= 0 && t <= 1) { isIn = true; } } else if (line.LineCap == LineCaps.Square) { if (t >= -thickness / lineLength && t <= 1 + thickness / lineLength) { isIn = true; } } else if (line.LineCap == LineCaps.Round) { if (t >= 0 && t <= 1) { isIn = true; } else if (t < -thickness / lineLength || t > 1 + thickness / lineLength) { isIn = false; } else if (t < 0) { double tipDist = (point.X - line2D[0].X) * (point.X - line2D[0].X) + (point.Y - line2D[0].Y) * (point.Y - line2D[0].Y); if (tipDist <= thickness * thickness) { isIn = true; } } else if (t > 1) { double tipDist = (point.X - line2D[1].X) * (point.X - line2D[1].X) + (point.Y - line2D[1].Y) * (point.Y - line2D[1].Y); if (tipDist <= thickness * thickness) { isIn = true; } } } if (isIn && IsDashOn(unitsOn, unitsOff, phase, t)) { Point3D correspPoint = camera.Deproject(pointOnLine, line); double z = camera.ZDepth(correspPoint); hits.Add((line, z, correspPoint)); } } } } hits.Sort((a, b) => { if (a.element.ZIndex == b.element.ZIndex) { return(Math.Sign(a.z - b.z)); } else { return(Math.Sign(b.element.ZIndex - a.element.ZIndex)); } }); byte pixelR = 0; byte pixelG = 0; byte pixelB = 0; byte pixelA = 0; foreach ((Element3D element, double z, Point3D correspPoint)hit in hits) { byte R = 0; byte G = 0; byte B = 0; byte A = 0; if (hit.element is Triangle3DElement triangle) { if (!triangle.ReceivesShadow || shadowers == null) { (R, G, B, A) = GetPixelColor(triangle, hit.correspPoint, camera, lights, noObstructions); } else { (R, G, B, A) = GetPixelColorWithShadow(triangle, lights, shadowers, hit.correspPoint, camera); } } else if (hit.element is Point3DElement pointElement) { R = (byte)(pointElement.Colour.R * 255); G = (byte)(pointElement.Colour.G * 255); B = (byte)(pointElement.Colour.B * 255); A = (byte)(pointElement.Colour.A * 255); } else if (hit.element is Line3DElement lineElement) { R = (byte)(lineElement.Colour.R * 255); G = (byte)(lineElement.Colour.G * 255); B = (byte)(lineElement.Colour.B * 255); A = (byte)(lineElement.Colour.A * 255); } BlendBack(R, G, B, A, ref pixelR, ref pixelG, ref pixelB, ref pixelA); if (pixelA == 255) { break; } } return(pixelR, pixelG, pixelB, pixelA); }