/// <summary> /// Gets all intersection of this polygon with a polytope (segment or polygon). /// Ignores boundary touches within Msystem.Tolerance. /// If the polytope is parallel to this polygon, nothing is returned even if they overlap. /// </summary> /// <param name="polytope">Polyline to intersect with.</param> /// <returns>List of intersection points.</returns> public HashSet <Point3D> IntersectionsWith(IPolytope polytope) { var result = new HashSet <Point3D>(Geometry.PointComparer); if (Center.DistanceTo(polytope.Center) > Radius + polytope.Radius) { return(result); // Optimization - quick test excluding intersection } var polygon = polytope as Polygon3D; if (polygon != null) { var intersections = OneWayIntersectionsWith(polygon); intersections.UnionWith(polygon.OneWayIntersectionsWith(this)); if (intersections.Any()) { var center = Point3D.Centroid(intersections); if (ContainsPoint(center, MSystem.Tolerance) && polygon.ContainsPoint(center, MSystem.Tolerance)) { return(intersections); } } } if (polytope is Segment3D) { var intersection = IntersectionWith(polytope[0], polytope[1], MSystem.Tolerance, false); if (!intersection.IsNaN()) { result.Add(intersection); } } return(result); }
/// <summary> /// Calculates pushing of another object intersecting with this one. /// </summary> /// <param name="another">The tile to be pushed.</param> /// <param name="direction">Pushing direction.</param> /// <returns>Pushing vector of another object.</returns> public Vector3D PushingIntersected(TileInSpace another, UnitVector3D direction) { // Estimate the maximum length of both intersecting objects (lower bound) // Create vector of sum of their lengths in the pushing direction var box1 = new Box3D(another.Vertices); var box2 = new Box3D(this.Vertices); var falsePushingVector = ((box1.MaxCorner - box1.MinCorner).Length + (box2.MaxCorner - box2.MinCorner).Length) * direction; // Create false object moved by negation of the created vector as if it was to be pushed to its actual position // This false object surely does not intersects with the "another" IPolytope falseVertices = CreateVertices(RotateAndMoveCollection(null, -falsePushingVector, Vertices), Name + "-false"); // The method "PushingOf" returns the actual pushing vector of the intersecting object return(falseVertices.PushingOf(another.Vertices, falsePushingVector)); }
/// <summary> /// Throws exception if the connector does not lie entirely on its tile or does not have smaller dimension /// or when a connector with Angle==0 on segment is not its endpoint /// or when a connector with Angle==0 on polygon is not at its border (if it is edge, must be clocwise) /// </summary> protected void CheckPositions(IReadOnlyList<Point3D> positions, IPolytope polytope) { var message = $"{this} of object {OnTile.Name}: "; if (! (positions.Count == 1 || polytope is Polygon3D && positions.Count == 2)) throw new InvalidOperationException(message + "connector contains too many points."); if (! positions.All(point => polytope.ContainsPoint(point))) throw new InvalidOperationException(message + "all points must lie on the object."); if (Angle.Radians == 0) if (polytope is Polygon3D) { var polygon = polytope as Polygon3D; switch (positions.Count) { case 1: // If Angle == 0 => connector must be on the polygon's border if (polygon.ContainsPoint(positions[0], MSystem.Tolerance)) throw new InvalidOperationException(message + "connector with zero angle must lie on the border."); break; case 2: int i = -1; int j = -1; for (int k = 0; k < polygon.Count; k++) { if (polygon[k].MyEquals(positions[0])) i = k; if (polygon[k].MyEquals(positions[1])) j = k; } if (! (i >= 0 && (i + 1) % polygon.Count == j)) throw new InvalidOperationException(message + "connector with zero angle must be an edge of polygon, clockwise."); break; } } else if (polytope is Segment3D && ! polytope.Contains(positions[0], PointComparer)) throw new InvalidOperationException(message + "connector with zero angle must lie at an endpoint of segment."); }
/// <summary> /// Tile constructor. TODO low priority: separate constructor for vertices 1D, vertices 2D /// </summary> /// <param name="name">Name of the tile.</param> /// <param name="vertices">Vertices of the tile.</param> /// <param name="connectors">Connectors of the tile.</param> /// <param name="surfaceGlue">Surface glue of the tile.</param> /// <param name="proteins">Proteins of the tile.</param> /// <param name="color">Color of the tile (named if possible).</param> /// <param name="alpha">Alpha attribute of the color</param> /// <param name="alphaRatio">Tile resistor ratio, for cTAM tilings</param> public Tile(string name, IList <Point3D> vertices, IEnumerable <Connector> connectors, Glue surfaceGlue, IList <ProteinOnTile> proteins, Color color, int alpha = 255, double alphaRatio = 1) : base(name) { Vertices = CreateVertices(vertices, name); SurfaceGlue = surfaceGlue ?? new Glue("EmptyGlue"); Color = color; Alpha = alpha; AlphaRatio = alphaRatio; Proteins = new ReadOnlyCollection <ProteinOnTile>(proteins ?? new List <ProteinOnTile>()); foreach (var protein in Proteins) { if (!Vertices.ContainsPoint(protein.Position)) { throw new InvalidOperationException($"{protein} must be on the object {name}."); } } Connectors = new ReadOnlyCollection <ConnectorOnTile>(connectors?.Select(conn => new ConnectorOnTile(this, conn)).ToList() ?? new List <ConnectorOnTile>()); }
/// <summary> /// Returns minimal distance of a vertex of "polytope" to an edge of this polygon in the direction of "movement". /// Boundary touches are ignored. /// Original position of the vertex must be outside the polygon. /// The result is bounded from above by the length of "movement". /// </summary> /// <param name="polytope"></param> /// <param name="movement"></param> private double UnidirectionalDistance(IPolytope polytope, Vector3D movement) { var distance = movement.Length; foreach (var point in polytope) { for (int i = 0; i < Edges.Count; i++) { // if the final point of the movement lies towards inside the polygon from the edge if (v_EdgeNormals[i].DotProduct(point.ToVector3D() + movement) + v_D[i] > MSystem.Tolerance) { var closePoints = new Line3D(point, point + movement).ClosestPointsBetween(Edges[i], true); if (closePoints.Item1.DistanceTo(closePoints.Item2) <= MSystem.Tolerance) { // The projection of "point" by pushingVector intersects "edge" distance = Math.Min(distance, point.DistanceTo(closePoints.Item1)); } } } } return(distance); }
/// <summary> /// Calculates possible pushing of "another" polytope by this segment being itself /// pushed by "pushingVector" which MAY cause its intersection with "another". /// If this segment already intersects "another", "pushingVector" is returned. /// </summary> /// <param name="another">The vertices of object to be eventually pushed.</param> /// <param name="pushingVector">Pushing vector of this object.</param> /// <returns>Pushing vector of another object.</returns> public Vector3D PushingOf(IPolytope another, Vector3D pushingVector) { Polygon3D polygon = another as Polygon3D; if (polygon == null || pushingVector.Length < MSystem.Tolerance) // length must be tested, otherwise the Polygon3D ctor may throw an exception { return(default(Vector3D)); } if (this.Vector.IsParallelTo(pushingVector)) { var intersection0 = polygon.IntersectionWith(this[0], this[0] + pushingVector); var intersection1 = polygon.IntersectionWith(this[1], this[1] + pushingVector); var vector0 = intersection0.IsNaN() ? pushingVector : intersection0 - this[0]; var vector1 = intersection1.IsNaN() ? pushingVector : intersection1 - this[1]; // Get minimal vector from this segment to the intersection point var minVector = vector0.Length > vector1.Length ? vector1 : vector0; return(pushingVector - minVector); } var projectionFace = new Polygon3D(new List <Point3D> { this[0], this[1], this[1] + pushingVector, this[0] + pushingVector }, Name + "-pushing projection"); double pLength = pushingVector.Length, distance = pLength; foreach (var point in polygon.IntersectionsWith(projectionFace)) { distance = Math.Min(distance, point.DistanceTo( Edges[0].ClosestPointsBetween(new Line3D(point, point - pushingVector), true).Item2)); } return(pushingVector.ScaleBy((pLength - distance) / pLength)); }
/// <summary> /// Calculates pushing of "another" polytope by this polygon, being itself /// pushed by "pushingVector" which MAY cause its intersection with "another". /// If this polygon already intersects "another", "pushingVector" is returned. /// </summary> /// <param name="another">The polytope to be eventually pushed.</param> /// <param name="pushingVector">Pushing vector of this polytope</param> /// <returns>Pushing vector of another polytope which is not longer than "pushingVector".</returns> public Vector3D PushingOf(IPolytope another, Vector3D pushingVector) { if (pushingVector.Length < MSystem.Tolerance) // must be tested, otherwise the Polygon3D ctor may throw an exception { return(default);
/// <summary> /// Gets all intersection of this segment with a list of vertices. /// Ignores boundary touchs within Msystem.Tolerance. /// </summary> /// <param name="another">Vertices to intersect with.</param> /// <returns>List of intersection points.</returns> public HashSet <Point3D> IntersectionsWith(IPolytope another) { var polygon = another as Polygon3D; return(polygon?.IntersectionsWith(this) ?? new HashSet <Point3D>()); }