/// <inheritdoc/> public override Point3D Deproject(Point point, Line3DElement line) { Point3D rotatedPoint = new Point3D(point.X / ScaleFactor, point.Y / ScaleFactor, 0); Point3D projectedPoint = RotationMatrix.Inverse() * (CameraRotationMatrix.Inverse() * rotatedPoint); Point3D cameraPlanePoint = projectedPoint + (Vector3D)this.Position; NormalizedVector3D v = this.Direction; NormalizedVector3D l = (line[1] - line[0]).Normalize(); double t; if (v.X * l.Y - v.Y * l.X != 0) { t = (l.X * (cameraPlanePoint.Y - line[0].Y) - l.Y * (cameraPlanePoint.X - line[0].X)) / (v.X * l.Y - v.Y * l.X); } else if (v.Z * l.Y - v.Y * l.Z != 0) { t = (l.Z * (cameraPlanePoint.Y - line[0].Y) - l.Y * (cameraPlanePoint.Z - line[0].Z)) / (v.Z * l.Y - v.Y * l.Z); } else if (v.Z * l.X - v.X * l.Z != 0) { t = (l.Z * (cameraPlanePoint.X - line[0].X) - l.X * (cameraPlanePoint.Z - line[0].Z)) / (v.Z * l.X - v.X * l.Z); } else { throw new Exception("The lines do not intersect!"); } Point3D pt = cameraPlanePoint + v * t; return(pt); }
/// <summary> /// Applies the transformation to a <see cref="Line3DElement"/>. /// </summary> /// <param name="line">The <see cref="Line3DElement"/> to which the transformation should be applied.</param> /// <returns>A <see cref="Line3DElement"/> corresponding to a line in which the transformation has been applied to the points from <paramref name="line" />. Properties are preserved between the two elements.</returns> public Line3DElement Apply(Line3DElement line) { Point3D p1 = this.Apply(line.Point1); Point3D p2 = this.Apply(line.Point2); Line3DElement tbr = new Line3DElement(p1, p2) { Colour = line.Colour, LineCap = line.LineCap, LineDash = line.LineDash, Thickness = line.Thickness, Tag = line.Tag, ZIndex = line.ZIndex }; return(tbr); }
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 IEnumerable <Element3D> Resample(Camera camera, Element3D element, double maxSize) { if (element is Point3DElement) { yield return(element); } else if (element is Line3DElement line) { if (this.ResampleLines) { Point p1 = camera.Project(line.Point1); Point p2 = camera.Project(line.Point2); if ((p1.X - p2.X) * (p1.X - p2.X) + (p1.Y - p2.Y) * (p1.Y - p2.Y) > maxSize) { Point3D half = (Point3D)(((Vector3D)line.Point1 + (Vector3D)line.Point2) * 0.5); Line3DElement line1 = new Line3DElement(line.Point1, half) { Colour = line.Colour, LineCap = line.LineCap, LineDash = line.LineDash, Tag = line.Tag, Thickness = line.Thickness, ZIndex = line.ZIndex }; Line3DElement line2 = new Line3DElement(half, line.Point2) { Colour = line.Colour, LineCap = line.LineCap, LineDash = line.LineDash, Tag = line.Tag, Thickness = line.Thickness, ZIndex = line.ZIndex }; foreach (Element3D el in Resample(camera, line1, maxSize)) { yield return(el); } foreach (Element3D el in Resample(camera, line2, maxSize)) { yield return(el); } } else { yield return(element); } } else { yield return(element); } } else if (element is Triangle3DElement triangle && element is IVectorRendererTriangle3DElement vectorRendererTriangle) { Point p1 = camera.Project(triangle.Point1); Point p2 = camera.Project(triangle.Point2); Point p3 = camera.Project(triangle.Point3); double area = 0.5 * Math.Abs((p2.X - p1.X) * (p3.Y - p1.Y) - (p2.Y - p1.Y) * (p3.X - p1.X)); if (area > maxSize) { Point3D proj12 = (Point3D)((triangle.Point2 - triangle.Point1) * 0.5 + triangle.Point1); Point3D proj23 = (Point3D)((triangle.Point3 - triangle.Point2) * 0.5 + triangle.Point2); Point3D proj31 = (Point3D)((triangle.Point1 - triangle.Point3) * 0.5 + triangle.Point3); NormalizedVector3D proj12Normal = (triangle.Point1Normal * 0.5 + triangle.Point2Normal * 0.5).Normalize(); NormalizedVector3D proj23Normal = (triangle.Point2Normal * 0.5 + triangle.Point3Normal * 0.5).Normalize(); NormalizedVector3D proj31Normal = (triangle.Point3Normal * 0.5 + triangle.Point1Normal * 0.5).Normalize(); VectorRendererTriangle3DElement t1 = new VectorRendererTriangle3DElement(triangle.Point1, proj12, proj31, triangle.Point1Normal, proj12Normal, proj31Normal) { Tag = triangle.Tag, ZIndex = triangle.ZIndex, OverFill = vectorRendererTriangle.OverFill, CastsShadow = triangle.CastsShadow, ReceivesShadow = triangle.ReceivesShadow }; t1.Fill.AddRange(triangle.Fill); t1.Parent = vectorRendererTriangle.Parent ?? triangle; VectorRendererTriangle3DElement t2 = new VectorRendererTriangle3DElement(triangle.Point2, proj23, proj12, triangle.Point2Normal, proj23Normal, proj12Normal) { Tag = triangle.Tag, ZIndex = triangle.ZIndex, OverFill = vectorRendererTriangle.OverFill, CastsShadow = triangle.CastsShadow, ReceivesShadow = triangle.ReceivesShadow }; t2.Fill.AddRange(triangle.Fill); t2.Parent = vectorRendererTriangle.Parent ?? triangle; VectorRendererTriangle3DElement t3 = new VectorRendererTriangle3DElement(triangle.Point3, proj31, proj23, triangle.Point3Normal, proj31Normal, proj23Normal) { Tag = triangle.Tag, ZIndex = triangle.ZIndex, OverFill = vectorRendererTriangle.OverFill, CastsShadow = triangle.CastsShadow, ReceivesShadow = triangle.ReceivesShadow }; t3.Fill.AddRange(triangle.Fill); t3.Parent = vectorRendererTriangle.Parent ?? triangle; VectorRendererTriangle3DElement t4 = new VectorRendererTriangle3DElement(proj12, proj23, proj31, proj12Normal, proj23Normal, proj31Normal) { Tag = triangle.Tag, ZIndex = triangle.ZIndex, OverFill = vectorRendererTriangle.OverFill, CastsShadow = triangle.CastsShadow, ReceivesShadow = triangle.ReceivesShadow }; t4.Fill.AddRange(triangle.Fill); t4.Parent = vectorRendererTriangle.Parent ?? triangle; foreach (Element3D el in Resample(camera, t1, maxSize)) { yield return(el); } foreach (Element3D el in Resample(camera, t2, maxSize)) { yield return(el); } foreach (Element3D el in Resample(camera, t3, maxSize)) { yield return(el); } foreach (Element3D el in Resample(camera, t4, maxSize)) { yield return(el); } } else { yield return(element); } } }
/// <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="Line3DElement"/> 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, Line3DElement element);
public LineEnumerator(Line3DElement line) { this.Line = line; }