private unsafe void DrawLine(byte *imageData, Line3DElement line, Camera camera) { Point[] line2D = line.GetProjection(); int minX = int.MaxValue; int minY = int.MaxValue; int maxX = int.MinValue; int maxY = int.MinValue; for (int i = 0; i < line2D.Length; i++) { line2D[i] = new Point((line2D[i].X - camera.TopLeft.X) / camera.Size.Width * this.RenderWidth, (line2D[i].Y - camera.TopLeft.Y) / camera.Size.Height * this.RenderHeight); minX = Math.Min(minX, (int)line2D[i].X); minY = Math.Min(minY, (int)line2D[i].Y); maxX = Math.Max(maxX, (int)Math.Ceiling(line2D[i].X)); maxY = Math.Max(maxY, (int)Math.Ceiling(line2D[i].Y)); } double thicknessX = line.Thickness * 0.5 / camera.Size.Width * this.RenderWidth; double thicknessY = line.Thickness * 0.5 / camera.Size.Height * this.RenderHeight; double thicknessSquare = thicknessY * thicknessX; double thickness = Math.Sqrt(thicknessSquare); minX = (int)Math.Floor(minX - thicknessX); minY = (int)Math.Floor(minY - thicknessY); maxX = (int)Math.Ceiling(maxX + thicknessX); maxY = (int)Math.Ceiling(maxY + thicknessY); 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); 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 / line.Thickness * thickness * 2) / lineLength; double unitsOff = (line.LineDash.UnitsOff / line.Thickness * thickness * 2) / lineLength; double phase = (line.LineDash.Phase / line.Thickness * thickness * 2) / lineLength; bool dashOn(double t) { if (unitsOff == 0) { return(true); } else { t += phase; t = t % (unitsOn + unitsOff); while (t < 0) { t += unitsOn + unitsOff; } return(t <= unitsOn); } }; Parallel.For(0, totalPixels, index => { int y = index / (maxX - minX + 1) + minY; int x = index % (maxX - minX + 1) + minX; double dist = Math.Abs(dy * x - dx * y + addedTerm) / lineLength; double howMuch = dist <= thickness ? 1 : Math.Max(0, 1 - (dist - thickness)); if (howMuch > 0) { (double t, Point pointOnLine) = Intersections2D.ProjectOnSegment(x, y, line2D[0], line2D[1]); if (line.LineCap == LineCaps.Butt) { if (t < 0) { howMuch *= Math.Max(0, 1 + t * lineLength); } else if (t > 1) { howMuch *= Math.Max(0, 1 - (t - 1) * lineLength); } } else if (line.LineCap == LineCaps.Square) { if (t < -thickness / lineLength) { howMuch *= Math.Max(0, 1 + (t + thickness / lineLength) * lineLength); } else if (t > 1 + thickness / lineLength) { howMuch *= Math.Max(0, 1 - (t - thickness / lineLength - 1) * lineLength); } } else if (line.LineCap == LineCaps.Round) { if (t < -(thickness + 1) / lineLength || t > 1 + (thickness + 1) / lineLength) { howMuch = 0; } else if (t < 0) { double tipDist = (x - line2D[0].X) * (x - line2D[0].X) + (y - line2D[0].Y) * (y - line2D[0].Y); howMuch *= tipDist <= thicknessSquare ? 1 : Math.Max(0, 1 - (Math.Sqrt(tipDist) - thickness)); } else if (t > 1) { double tipDist = (x - line2D[1].X) * (x - line2D[1].X) + (y - line2D[1].Y) * (y - line2D[1].Y); howMuch *= tipDist <= thicknessSquare ? 1 : Math.Max(0, 1 - (Math.Sqrt(tipDist) - thickness)); } } if (howMuch > 0 && dashOn(t)) { Point3D correspPoint = camera.Deproject(new Point((double)pointOnLine.X / this.RenderWidth * camera.Size.Width + camera.TopLeft.X, (double)pointOnLine.Y / this.RenderHeight * camera.Size.Height + camera.TopLeft.Y), line); double zDepth = camera.ZDepth(correspPoint); int prevZIndexBuffer = ZIndexBuffer[y * RenderWidth + x]; if (prevZIndexBuffer < line.ZIndex || (prevZIndexBuffer == line.ZIndex && ZBuffer[y * RenderWidth + x] >= zDepth)) { byte R = (byte)(line.Colour.R * 255); byte G = (byte)(line.Colour.G * 255); byte B = (byte)(line.Colour.B * 255); byte A = (byte)(line.Colour.A * 255 * howMuch); 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] = line.ZIndex; } else if (imageData[y * RenderWidth * 4 + x * 4 + 3] < 255) { byte R = (byte)(line.Colour.R * 255); byte G = (byte)(line.Colour.G * 255); byte B = (byte)(line.Colour.B * 255); byte A = (byte)(line.Colour.A * 255 * howMuch); 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); }