/// <inheritdoc/> public override bool IsCulled(Element3D element) { if (element is Point3DElement) { return((element[0] - this.Position) * this.Direction < 0); } else if (element is Line3DElement) { foreach (Point3D pt in element) { if ((pt - this.Position) * this.Direction > 0) { return(false); } } return(true); } else if (element is Triangle3DElement triangle) { bool found = false; foreach (Point3D pt in element) { if ((pt - this.Position) * this.Direction > 0) { found = true; } } if (!found) { return(true); } else { return(triangle.ActualNormal * this.Direction < 0); } } else { return(true); } }
/// <inheritdoc/> public void AddElement(Element3D element) { this.sceneElements.Add(element); }
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> /// Compares two <see cref="Element3D"/>s to determine which one lies on top of the other when viewed with the current camera. /// </summary> /// <param name="element1">The first element. If this element is in front, the function returns 1.</param> /// <param name="element2">The second element. If this element is in front, the function returns -1.</param> /// <returns>1 if <paramref name="element1"/> is in front of <paramref name="element2"/>; -1 if <paramref name="element2"/> is in front of <paramref name="element1"/>; 0 if the two elements do not overlap, intersect or if the order could not be determined for other reasons. </returns> public virtual int Compare(Element3D element1, Element3D element2) { if (element1.ZIndex != element2.ZIndex) { return(element1.ZIndex - element2.ZIndex); } if (element1 is Point3DElement pt1) { Point proj1 = this.Project(pt1[0]); if (element2 is Point3DElement pt2) { Point proj2 = this.Project(pt2[0]); if ((proj1.X - proj2.X) * (proj1.X - proj2.X) + (proj1.Y - proj2.Y) * (proj1.Y - proj2.Y) < (pt1.Diameter * 0.5 + pt2.Diameter * 0.5) * (pt1.Diameter * 0.5 + pt2.Diameter * 0.5)) { double dist1 = this.ZDepth(pt1[0]); double dist2 = this.ZDepth(pt2[0]); if (Math.Abs(dist1 - dist2) < Camera.Tolerance) { return(0); } return(-Math.Sign(dist1 - dist2)); } else { return(0); } } else if (element2 is Line3DElement line) { Point p0 = proj1; Point p1 = this.Project(line[0]); Point p2 = this.Project(line[1]); double distSq = ((p2.Y - p1.Y) * p0.X - (p2.X - p1.X) * p0.Y + p2.X * p1.Y - p2.Y * p1.X) * ((p2.Y - p1.Y) * p0.X - (p2.X - p1.X) * p0.Y + p2.X * p1.Y - p2.Y * p1.X) / ((p2.X - p1.X) * (p2.X - p1.X) + (p2.Y - p1.Y) * (p2.Y - p1.Y)); double maxDistSq = (pt1.Diameter * 0.5 + line.Thickness * 0.5) * (pt1.Diameter * 0.5 + line.Thickness * 0.5); Point v1 = new Point(p0.X - p1.X, p0.Y - p1.Y); Point v2 = new Point(p2.X - p1.X, p2.Y - p1.Y); double v2Length = v2.Modulus(); Point e2 = new Point(v2.X / v2Length, v2.Y / v2Length); double dotProd = v1.X * e2.X + v1.Y * e2.Y; if (dotProd >= -Math.Sqrt(maxDistSq) && dotProd <= v2Length + Math.Sqrt(maxDistSq) && distSq < maxDistSq) { Point pointOnLine = new Point(p1.X + dotProd * e2.X, p1.Y + dotProd * e2.Y); try { Point3D pt = this.Deproject(pointOnLine, line); double dist1 = this.ZDepth(pt1[0]); double dist2 = this.ZDepth(pt); if (Math.Abs(dist1 - dist2) < Camera.Tolerance) { return(0); } return(-Math.Sign(dist1 - dist2)); } catch { return(0); } } else { return(0); } } else if (element2 is Triangle3DElement triangle) { Point p0 = proj1; Point p1 = this.Project(triangle[0]); Point p2 = this.Project(triangle[1]); Point p3 = this.Project(triangle[2]); double area = 0.5 * (-p2.Y * p3.X + p1.Y * (-p2.X + p3.X) + p1.X * (p2.Y - p3.Y) + p2.X * p3.Y); double s = 1 / (2 * area) * (p1.Y * p3.X - p1.X * p3.Y + (p3.Y - p1.Y) * p0.X + (p1.X - p3.X) * p0.Y); double t = 1 / (2 * area) * (p1.X * p2.Y - p1.Y * p2.X + (p1.Y - p2.Y) * p0.X + (p2.X - p1.X) * p0.Y); if (s >= 0 && t >= 0 && 1 - s - t >= 0) { Point3D pt = this.Deproject(p0, triangle); double dist1 = this.ZDepth(pt1[0]); double dist2 = this.ZDepth(pt); if (Math.Abs(dist1 - dist2) < Camera.Tolerance) { return(0); } return(-Math.Sign(dist1 - dist2)); } else { return(0); } } else { throw new NotImplementedException(); } } else if (element1 is Line3DElement line1) { if (element2 is Point3DElement) { return(-Compare(element2, element1)); } else if (element2 is Line3DElement line2) { Point l11 = this.Project(line1[0]); Point l12 = this.Project(line1[1]); Point l21 = this.Project(line2[0]); Point l22 = this.Project(line2[1]); (Point inters, double t, double s)? intersection = Intersections2D.Intersect(l11, l12, l21, l22); if (intersection != null) { double vLength = new Point(l12.X - l11.X, l12.Y - l11.Y).Modulus(); double lLength = new Point(l22.X - l21.X, l22.Y - l21.Y).Modulus(); if (intersection?.t >= -line1.Thickness && intersection?.t <= vLength + line1.Thickness && intersection?.s >= -line1.Thickness && intersection?.s <= lLength + line1.Thickness) { try { Point3D point1 = this.Deproject(intersection.Value.inters, line1); Point3D point2 = this.Deproject(intersection.Value.inters, line2); double dist1 = this.ZDepth(point1); double dist2 = this.ZDepth(point2); if (Math.Abs(dist1 - dist2) < Camera.Tolerance) { return(0); } return(-Math.Sign(dist1 - dist2)); } catch { return(0); } } else { return(0); } } else { return(0); } } else if (element2 is Triangle3DElement triangle) { Point l11 = this.Project(line1[0]); Point l12 = this.Project(line1[1]); Point lineDirPerp = new Point(l12.Y - l11.Y, l11.X - l12.X); lineDirPerp = new Point(lineDirPerp.X / lineDirPerp.Modulus(), lineDirPerp.Y / lineDirPerp.Modulus()); Point l21 = new Point(l11.X + lineDirPerp.X * line1.Thickness * 0.5, l11.Y + lineDirPerp.Y * line1.Thickness * 0.5); Point l22 = new Point(l12.X + lineDirPerp.X * line1.Thickness * 0.5, l12.Y + lineDirPerp.Y * line1.Thickness * 0.5); Point l31 = new Point(l11.X - lineDirPerp.X * line1.Thickness * 0.5, l11.Y - lineDirPerp.Y * line1.Thickness * 0.5); Point l32 = new Point(l12.X - lineDirPerp.X * line1.Thickness * 0.5, l12.Y - lineDirPerp.Y * line1.Thickness * 0.5); Point A = this.Project(triangle[0]); Point B = this.Project(triangle[1]); Point C = this.Project(triangle[2]); List <Point> interss1 = Intersections2D.Intersect(l11, l12, A, B, C, line1.Thickness, Camera.Tolerance); List <Point> interss2 = Intersections2D.Intersect(l21, l22, A, B, C, Camera.Tolerance); List <Point> interss3 = Intersections2D.Intersect(l31, l32, A, B, C, Camera.Tolerance); List <Point> interss = new List <Point>(); if (interss1 != null && interss1.Count > 0) { interss.AddRange(interss1); } if (interss2 != null && interss2.Count > 0) { interss.AddRange(from el in interss2 select Intersections2D.ProjectOnLine(el, l11, l12)); } if (interss3 != null && interss3.Count > 0) { interss.AddRange(from el in interss3 select Intersections2D.ProjectOnLine(el, l11, l12)); } if (interss != null && interss.Count > 0) { int sign = 0; foreach (Point pt in interss) { try { Point3D pt3D1 = this.Deproject(pt, line1); Point3D pt3D2 = this.Deproject(pt, triangle); double dist1 = this.ZDepth(pt3D1); double dist2 = this.ZDepth(pt3D2); int currSign = -Math.Sign(dist1 - dist2); if (Math.Abs(dist1 - dist2) < Camera.Tolerance) { currSign = 0; } if (sign == 0 || currSign == sign || currSign == 0) { sign = currSign; } else { //Line intersects with triangle return(0); } } catch { } } return(sign); } else { return(0); } } else { throw new NotImplementedException(); } } else if (element1 is Triangle3DElement triangle1) { if (element2 is Point3DElement || element2 is Line3DElement) { return(-Compare(element2, element1)); } else if (element2 is Triangle3DElement triangle2) { Point[] triangle1Projection = triangle1.GetProjection(); Point[] triangle2Projection = triangle2.GetProjection(); Point A1 = triangle1Projection[0]; Point B1 = triangle1Projection[1]; Point C1 = triangle1Projection[2]; Point A2 = triangle2Projection[0]; Point B2 = triangle2Projection[1]; Point C2 = triangle2Projection[2]; List <Point> intersections = Intersections2D.IntersectTriangles(A1, B1, C1, A2, B2, C2, Camera.Tolerance); if (intersections.Count >= 3) { double meanX = 0; double meanY = 0; foreach (Point p in intersections) { meanX += p.X; meanY += p.Y; } meanX /= intersections.Count; meanY /= intersections.Count; Point pt = new Point(meanX, meanY); try { Point3D pt3D1 = this.Deproject(pt, triangle1); Point3D pt3D2 = this.Deproject(pt, triangle2); double dist1 = this.ZDepth(pt3D1); double dist2 = this.ZDepth(pt3D2); if (double.IsNaN(dist1 - dist2)) { return(0); } int sign = -Math.Sign(dist1 - dist2); if (Math.Abs(dist1 - dist2) < Camera.Tolerance) { sign = 0; } return(sign); } catch { return(0); } } else { return(0); } } else { throw new NotImplementedException(); } } else { throw new NotImplementedException(); } }
/// <summary> /// Determines whether an object should be culled (i.e. hidden) when the scene is rendered using the current camera. /// </summary> /// <param name="element">The <see cref="Element3D"/> to test.</param> /// <returns>A boolean value indicating whether the element should be culled or not.</returns> public abstract bool IsCulled(Element3D element);