/// <summary> /// Tile in space constructor. /// </summary> /// <param name="baseObject">Base tile.</param> /// <param name="position">Position of the tile in space.</param> /// <param name="quaternion">Orientation of the tile.</param> public TileInSpace(Tile baseObject, Point3D position, Quaternion quaternion) : base(baseObject.Name, baseObject.Vertices.ToList(), baseObject.Connectors.Select(conn => (Connector)conn), baseObject.SurfaceGlue, baseObject.Proteins, baseObject.Color, baseObject.Alpha, baseObject.AlphaRatio) { Vertices = baseObject.Vertices; // Must precede the connectors var connectors = baseObject.Connectors.Select(connector => new ConnectorOnTileInSpace(this, connector)).ToList(); Connectors = new ReadOnlyCollection <ConnectorOnTileInSpace>(connectors); // Tile and the connectors are now in the basic position NorthConnector = Connectors.FirstOrDefault(c => c.Positions.All(p => p.Y > Position.Y)); EastConnector = Connectors.FirstOrDefault(c => c.Positions.All(p => p.X > Position.X)); SouthConnector = Connectors.FirstOrDefault(c => c.Positions.All(p => p.Y < Position.Y)); WestConnector = Connectors.FirstOrDefault(c => c.Positions.All(p => p.X < Position.X)); var proteins = base.Proteins.Select(protein => new ProteinOnTileInSpace(this, protein)).ToList(); Proteins = new ReadOnlyCollection <ProteinOnTileInSpace>(proteins); Surface = new SurfaceConnector(this); Color = baseObject.Color; Color = baseObject.Color; RotateAndMove(quaternion, position.ToVector3D()); ID = v_Counter++; Note = ""; }
/// <summary> /// Shortens a 2D object to a given length, starting at a given connector. /// </summary> /// <param name="length">New length of the object.</param> /// <param name="connector">Connector from which the new length is measured.</param> /// <exception cref="InvalidOperationException">If the object is not new /// or its vertices is not Segment3D or the required length >= than the current one.</exception> public void ShortenTo(double length, ConnectorOnTileInSpace connector) { var segment = Vertices as Segment3D; if (State != FState.Create || segment == null) { throw new InvalidOperationException($"{Name} is not a new 2D object and so it cannot be shortened."); } if (length >= segment.Vector.Length) { throw new ArgumentException($"Shortened length of object {Name} must be smaller than the current one."); } Note = "mutated"; int start = connector.Positions[0].MyEquals(Vertices[0]) ? 0 : 1; var newStartPoint = Vertices[start]; var newEndPoint = newStartPoint + length * (Vertices[1 - start] - newStartPoint).Normalize(); Vertices = new Segment3D(newStartPoint, newEndPoint, Name); // Move connectors at the cut-off end to the new end foreach (var conn in Connectors.Where(conn => conn.Positions[0].DistanceTo(newStartPoint) > length)) { conn.Positions = new ReadOnlyCollection <Point3D>(new[] { newEndPoint }); } // Move proteins at the cut-off end to the new end foreach (var prot in Proteins.Where(prot => prot.Position.DistanceTo(newStartPoint) > length)) { prot.Position = newEndPoint; } }
/// <summary> /// Tries to insert a new tile between two existing conected tiles at a given connection. /// TODO at the moment allows only insertion of rods, generalize also for 2D tiles /// Eventually releases the signal objects corresponding to the new connection. /// </summary> /// <param name="tile">New tile to be inserted.</param> /// <param name="connector1">Connector at which the new tile should be inserted.</param> /// <returns> /// True if the tile was inserted at the given connection. /// </returns> public bool Insert(Tile tile, ConnectorOnTileInSpace connector1) { if (tile == null) { throw new ArgumentNullException($"Parameter \'tile\' cannot be null"); } if (connector1 == null) { throw new ArgumentNullException($"Parameter \'connector1\' cannot be null"); } if (!(tile.Vertices is Segment3D)) { throw new ArgumentException($"Parameter \'tile\' must be a 2D rod"); } var connector2 = connector1.ConnectedTo; if (connector2 == null) { throw new ArgumentException($"Parameter \'connector1\' is not connected"); } var connectorsOnTile = v_MSystem.MatchingConectors(tile, connector1.Glue, connector2.Glue); if (_Insert(connectorsOnTile, connector1)) { var newTile = connector1.ConnectedTo.OnTile; _Add(newTile); AutoConnect(newTile); ReleaseSignalObjects(connector1); ReleaseSignalObjects(connector2); return(true); } return(false); }
/// <summary> /// Connects bi-directionally this connector to another. /// </summary> /// <param name="connector">Connector to which to connect.</param> public void ConnectTo(ConnectorOnTileInSpace connector) { if (this.OnTile == connector.OnTile) { throw new ArgumentException($"Tile {OnTile.Name} cannot connect to itself."); } ConnectedTo = connector; connector.ConnectedTo = this; }
/// <summary> /// Releases signal objects emited by a newly established connection to the floating objects world. /// </summary> /// <param name="connector">Old connector to which a new connector was attached.</param> private void ReleaseSignalObjects(ConnectorOnTileInSpace connector) { NamedMultiset signalObjects; if (v_MSystem.GlueRelation.TryGetValue(Tuple.Create(connector.Glue, connector.ConnectedTo.Glue), out signalObjects)) { v_FltObjectsWorld.AddAt(signalObjects, connector.SidePoint(connector.Positions.First())); } }
/// <summary> /// Gets the set of floating objects within the reaction distance from any position of a given connector. /// Only objects unused in this simulation step are considered. /// TODO low priority: return floating objects close to the whole shape of the connector except endpoints /// </summary> public FloatingObjectsSet GetNearObjects(ConnectorOnTileInSpace connector, NamedMultiset targetMultiset) { FloatingObjectsSet objectsSet = new FloatingObjectsSet(); foreach (var position in connector.Positions) { objectsSet.UnionWith(GetNearObjects(connector.SidePoint(position), targetMultiset)); } return(objectsSet); }
/// <summary> /// Disonnects bi-directionally this connection, resets the flag "SetDisconect". /// </summary> public void Disconnect() { if (ConnectedTo != null) { ConnectedTo.ConnectedTo = null; ConnectedTo.WasConnectedTo = this; ConnectedTo.SetDisconnect = false; WasConnectedTo = ConnectedTo; ConnectedTo = null; } SetDisconnect = false; }
/// <summary> /// Calculates the direction in which this growing tile pushes others. /// </summary> /// <param name="connector">Connector from which this object grows.</param> /// <returns>Pushing direction.</returns> public static UnitVector3D PushingDirection(ConnectorOnTileInSpace connector) { if (connector.ConnectedTo == null) { throw new ArgumentException("Pushing direction for unconnected connector is undefined"); } var tile = connector.OnTile; // Point-to-point connection of a segment - pushing in the segment direction if (tile.Vertices is Segment3D) { return((tile.Position - connector.Positions[0]).Normalize()); } // Edge-to-edge connection of a polygon - pushing from connector to the object's center if (connector.Positions.Count == 2) { return((new Line3D(connector.Positions[0], connector.Positions[1])). LineTo(tile.Position, false).Direction); } return((connector.Positions[0] - connector.ConnectedTo.OnTile.Position).Normalize()); }
/// <summary> /// Tries to add a new tile to a given connection on an existing tile. /// Tries possible various connectors on the new object => various orientations of the new object. /// Eventually releases the signal objects corresponding to the new connection. /// </summary> /// <param name="tile">New tile to be attached.</param> /// <param name="freeConnector">Connector to which the new tile should be attached.</param> /// <param name="newlyCreatedTile">Out parameter with newly created tile in space.</param> /// <returns> /// True if the tile was attached to the given connection. /// </returns> public bool Add(Tile tile, ConnectorOnTileInSpace freeConnector, out TileInSpace newlyCreatedTile) { if (tile == null) { throw new ArgumentNullException($"Parameter \'tile\' cannot be null"); } if (freeConnector == null) { throw new ArgumentNullException($"Parameter \'freeConnector\' cannot be null"); } // already connected connector cannot be used if (freeConnector.ConnectedTo != null) { throw new ArgumentException($"Parameter \'freeConnector\' is already connected"); } foreach (var connector in tile.Connectors.Where(connector => v_MSystem.AreCompatible(freeConnector, connector))) { freeConnector.ConnectObject(connector); if (Attach(freeConnector.ConnectedTo)) { TileInSpace newTile = freeConnector.ConnectedTo.OnTile; _Add(newTile); AutoConnect(newTile); FltObjectsWorld.ExpandWith(new Box3D(newTile.Vertices), v_MSystem.RefillEnvironment); ReleaseSignalObjects(freeConnector); newlyCreatedTile = newTile; return(true); } freeConnector.Disconnect(); } newlyCreatedTile = null; return(false); }
/// <summary> TODO low priority: move to class Connector when MSystem is available as singleton /// Checks whether two connector are mutually compatible (dimension, size, glues). /// Order of connectors matters due to possibly asymmetric glue relation! /// </summary> /// <param name="connector1"></param> /// <param name="connector2"></param> public bool AreCompatible(ConnectorOnTileInSpace connector1, ConnectorOnTile connector2) { // compatible glues and connector sizes bool result = GlueRelation.ContainsKey(Tuple.Create(connector1.Glue, connector2.Glue)) && connector1.IsSizeComppatibleWith(connector2); if (!result || Nu0 <= 0) { return(result); } //if Nu0 > 0, then this is a cTAM: check if connector voltage exceeds the threshold if (connector2.OnTile.Connectors.Count == 4) { // Assume that an inner cTAM tile can connect only to an east or south connector // It is attachable only if it connects to two connectors simultaneously and // the sum o heir voltage exceeds the threshold Tau ConnectorOnTileInSpace otherConnector = null; if (connector1 == connector1.OnTile.EastConnector) { otherConnector = connector1.OnTile.NorthConnector?.ConnectedTo?.OnTile.EastConnector?.ConnectedTo ?.OnTile.SouthConnector; } if (connector1 == connector1.OnTile.SouthConnector) { otherConnector = connector1.OnTile.WestConnector?.ConnectedTo?.OnTile.SouthConnector?.ConnectedTo ?.OnTile.EastConnector; } return(otherConnector != null && otherConnector.Voltage + connector1.Voltage >= Tau); } else { // Border cTAM return(connector1.Voltage >= Tau); } }
/// <summary> /// Calculates position and angle of a new tile connecting to this connection and connects it. /// The connector given as parameter determines also the connecting object. /// </summary> /// <param name="connector">Connector on the new tile by which it connects</param> /// <returns>New tile in space connected to this connection.</returns> public TileInSpace ConnectObject(ConnectorOnTile connector) { // Place the old and the new object into the same plane or line TileInSpace newTile = new TileInSpace(connector.OnTile, Point3D.Origin, OnTile.Quaternion); int index = connector.OnTile.Connectors.IndexOf(connector); ConnectorOnTileInSpace newConnector = newTile.Connectors[index]; UnitVector3D axis = OnTile.Vertices.Normal; // rotation axis - TRY REVERSE IN CASE OF MALFUNCTION // Edge-to-edge if (newConnector.Positions.Count == 2 && Positions.Count == 2) { // Rotate the new object so that connector directions are opposite Vector3D vector1 = Positions[1] - Positions[0]; Vector3D vector2 = newConnector.Positions[0] - newConnector.Positions[1]; Angle angle = vector2.SignedAngleTo(vector1, axis); newTile.Rotate(axis, angle); // Rotate the new tile around the connector edge so that // the angle between both tiles is the angle associated with the connector newTile.Rotate(vector1.Normalize(), Angle.FromDegrees(180) - Angle); // TRY REVERSE IN CASE OF MALFUNCTION // Move the new object so that the connectors match newTile.Move(Positions[0] - newConnector.Positions[1]); } // Point-to-point else if (newConnector.Positions.Count == 1 && Positions.Count == 1) { // Rotate the new object around an axis perpendicular to it by the angle associated with the connector var newSegment = newTile.Vertices as Segment3D; var thisSegment = OnTile.Vertices as Segment3D; var thisAxis = thisSegment?.Vector.Normalize() ?? OnTile.Vertices.Normal; if (newSegment == null) { // Tile connecting to a segment if (thisSegment != null) { axis = newTile.Vertices.Normal.CrossProduct(-Math.Sign(connector.Angle.Radians) * thisSegment.Vector).Normalize(); } // ELSE 2D tile connecting to a 2D tile by a single point - should never happen } else { if (OnTile.Vertices is Polygon3D) // Segment connecting to a tile { axis = axis.CrossProduct(-newSegment.Vector).Normalize(); } // ELSE segment connecting to a segment - axis set above as a normal to this segment } newTile.Rotate(axis, Angle); // TRY REVERSE AXIS OR ROTATION IN CASE OF MALFUNCTION // the newly connected object has one degree of freedom - rotate randomly newTile.Rotate(thisAxis, Angle.FromRadians(Randomizer.NextDoubleBetween(0, 2 * Math.PI))); // Move the new object so that the connectors match newTile.Move(Positions[0] - newConnector.Positions[0]); } else { throw new ArgumentException("Connecting incompatible connectors:" + this + "\n and" + newConnector); } ConnectTo(newConnector); return(newTile); }
/// <summary> /// True if (vertices of) both connectors overlap within "tolerance". /// </summary> public bool Overlaps(ConnectorOnTileInSpace another, double tolerance = MSystem.Tolerance) { return(Geometry.Overlap(Positions, another.Positions, tolerance)); }
/// <summary> /// Tries to insert a tile between two existing conected tiles due to space constraints. /// Resolves pushing of fixed and floating objects. /// TODO at the moment allows only insertion of rods, generalize also for 2D tiles /// </summary> /// <param name="connectorsOnTile">Connectors on the new tile by which it should be attached.</param> /// <param name="connector1">COnnector on an existing tile at which the new tile is inserted</param> /// <returns> /// True if the given tile in space can be inserted to the world. /// </returns> private bool _Insert(Tuple <ConnectorOnTile, ConnectorOnTile> connectorsOnTile, ConnectorOnTileInSpace connector1) { var newTile = connectorsOnTile.Item1.OnTile; // TODO create new tile by "ConnectObject(olderConnector)" var connector2 = connector1.ConnectedTo; var olderConnector = (connector1.OnTile.ID < connector2.OnTile.ID ? connector1 : connector2); var pushingVector = newTile.Vertices[1] - newTile.Vertices[0]; // TODO correct // TODO FINISH return(false); }
/// <summary> /// Tries to attach a tile in space to the tiles world due to space constraints. /// Resolves pushing of fixed and floating objects. /// TODO More realistic pushing: /// For each component containing tiles: calculate pushing vectors by tiles growing from them. /// Calculate their common (average) pushing veector. /// If its scalar product with all particular pushing vectors is positive, then try to apply the average vector /// to all objects pushed by these interconnected growing tiles. /// </summary> /// <param name="connector">Connector on the new tile by which it is attached to an existing one.</param> /// <returns> /// True if the given tile in space can be attached to the world. /// </returns> private bool Attach(ConnectorOnTileInSpace connector) { var pushingDirection = PushingDirection(connector); /* Randomize - up to phi/4 deviation - a parameter of randomization angle would have to be added to the input XML * Vector3D randomVector = new Vector3D(Randomizer.Rng.NextDouble(), Randomizer.Rng.NextDouble(), * Randomizer.Rng.NextDouble()); * var length = randomVector.Length; * if (length > 0.001) * randomVector = randomVector / (2 * length); // Length is 0.5 now * pushingDirection = (pushingDirection + randomVector).Normalize(); */ Vector3D maxPushingVector = default; // This will be the longest of all pushing vectors in the world var newTile = connector.OnTile; var objectsToBeChecked = new HashSet <TileInSpace>(); // Objects to be checked for pushing by newObject var objectsToBePushed = new HashSet <TileInSpace>(); // Objects which passed the check and will be pushed // Check whether the newObject intersects existing ones, and add those to pushingList // together with their components foreach (var checkedObject in v_TileSet) // TODO PARALLEL.FOREACH { if (newTile.IntersectsWith(checkedObject) || newTile.OverlapsWith(checkedObject)) { var pushingVector = newTile.PushingIntersected(checkedObject, pushingDirection); if (newTile.Vertices is Polygon3D) { pushingVector = v_MSystem.PushingCoef * pushingVector; } if (pushingVector.Length > float.Epsilon) // lock(objectsToBeChecked) { objectsToBeChecked.UnionWith(checkedObject.SetAndGetPushedComponent(pushingVector)); if (pushingVector.Length > maxPushingVector.Length) { maxPushingVector = pushingVector; } } } } var invalidPushing = newTile.PushingVector != default; // Check all objects in checkList whether they can be pushed, as they may in turn push other objects while (objectsToBeChecked.Any() && !invalidPushing) { var checkedObject = objectsToBeChecked.First(); foreach (var existingObject in v_TileSet) // TODO PARALLEL.FOREACH { if (existingObject != checkedObject) // checked object is not checked against itself { var pushingVector = checkedObject.PushingNonIntersected(existingObject); // Secondary pushing // Fact: pushingVector <= checkedObject.PushingVector; // hence any object with maximum pushing vector in checkList is never returned back to the list. // This guarantees that the *while* cycle will eventually end if (pushingVector.Length > float.Epsilon) // lock(objectsToBeChecked) { objectsToBeChecked.UnionWith(existingObject.SetAndGetPushedComponent(pushingVector)); } } } objectsToBeChecked.Remove(checkedObject); objectsToBePushed.Add(checkedObject); invalidPushing |= newTile.PushingVector != default; } // If any object after pushing still intersects with newObjectm, then the pushing is unsuccessful. foreach (var pushedObject in v_TileSet.Where(obj => obj.PushingVector != default)) { if (invalidPushing) // No further cycling needed { break; } // Push the object for intersection test var oldState = pushedObject.State; pushedObject.Move(pushedObject.PushingVector); invalidPushing |= newTile.IntersectsWith(pushedObject) || newTile.OverlapsWith(pushedObject); // Revert back pushedObject.Move(-pushedObject.PushingVector); pushedObject.State = oldState; } // If newObject == segment has not enough room, it can be shortened if (invalidPushing) { var segment = newTile.Vertices as Segment3D; var newLength = Math.Max(0D, segment?.Vector.Length - maxPushingVector.Length ?? 0D); if (newLength < MSystem.Tolerance) { // newObject not attached = there is just a tiny room, shortening would not help ClearPushing(); return(false); } newTile.ShortenTo(newLength, connector); } else { // Pushing possible => it is applied now ApplyPushing(objectsToBePushed); } ClearPushing(); return(true); }