internal MovementSegment(MovementSegment previous, OrganismState state, Point startingPoint, int entryTime, int gridX, int gridY) { Debug.Assert((previous == null && entryTime == 0) || (previous != null && entryTime != 0)); this.State = state; this.StartingPoint = startingPoint; this.EntryTime = entryTime; this.GridX = gridX; this.GridY = gridY; this.Previous = previous; }
internal MovementSegment(MovementSegment previous, OrganismState state, Point startingPoint, int entryTime, int gridX, int gridY) { Debug.Assert((previous == null && entryTime == 0) || (previous != null && entryTime != 0)); State = state; StartingPoint = startingPoint; EntryTime = entryTime; GridX = gridX; GridY = gridY; Previous = previous; }
// Take an OrganismState and a path to move it on (from Point1 to Point2) and break up this // path into MovementSegments when it moves between cells. Each MovementSegment represents the part of the path // that is within a single cell. // // Even though we are only drawing a single line and rasterizing it along one path through the grid, // we can assume that the center of every cell that this organism occupies follows exactly the same path // and has segments that break down at exactly the same timeframes. Thus we call the AddSegment() routine // for each MovementSegment. AddSegment() wraps this MovementSegment with a wrapper (SegmentWrapper) and adds // this wrapper to every cell that the creature occupies so that all the cells that are occupied at any given // are properly noted. // // AddPath() records the time that the path entered and left each cell in the path. // Each cell of the grid is 2^GridWidthPowerOfTwo x 2^GridHeightPowerOfTwo pixels in size // Each is a power of two so we can shift instead of dividing // The first square of the grid starts at 0, 0 and goes to 2^GridWidthPowerOfTwo - 1, 2^GridHeightPowerOfTwo - 1 // Use Bresenham's algorithm to do the rasterization and figure out when the path enters or leaves grid squares. public void AddPath(OrganismState state, Point p1, Point p2) { var x0 = p1.X; var y0 = p1.Y; var x1 = p2.X; var y1 = p2.Y; var dy = y1 - y0; var dx = x1 - x0; int stepx, stepy; var timeslice = 0; Debug.Assert(x0 > -1 && x1 > -1 && y0 > -1 && y1 > -1); if (dy < 0) { dy = -dy; stepy = -1; } else { stepy = 1; } if (dx < 0) { dx = -dx; stepx = -1; } else { stepx = 1; } dy <<= 1; dx <<= 1; // start the first segment at the initial point at time 0 var gridX = x0 >> EngineSettings.GridWidthPowerOfTwo; var gridY = y0 >> EngineSettings.GridWidthPowerOfTwo; Debug.Assert(gridX == state.GridX && gridY == state.GridY); var segment = new MovementSegment(null, state, new Point(x0, y0), 0, gridX, gridY) {EndingPoint = new Point(p1.X, p1.Y)}; AddSegment(segment); if (p1 == p2) { return; } if (dx > dy) { // Determine how many points we'll plot and estimate time by that if ((x1 - x0) != 0) { Debug.Assert((x1 - x0) < TimeWindow); timeslice = TimeWindow/((x1 - x0)*stepx); Debug.Assert(timeslice != 0); } var fraction = dy - (dx >> 1); // same as 2*dy - dx while (x0 != x1) { if (fraction >= 0) { y0 += stepy; fraction -= dx; // same as fraction -= 2*dx } x0 += stepx; fraction += dy; // same as fraction -= 2*dy // See if we've crossed into a new grid square gridX = x0 >> EngineSettings.GridWidthPowerOfTwo; gridY = y0 >> EngineSettings.GridHeightPowerOfTwo; segment.ExitTime += timeslice; if (gridX != segment.GridX || gridY != segment.GridY) { // End the segment since we've entered a new grid square var lastSegment = segment; segment = new MovementSegment(lastSegment, state, new Point(x0, y0), lastSegment.ExitTime, gridX, gridY) {ExitTime = lastSegment.ExitTime}; lastSegment.Next = segment; AddSegment(segment); } var newEndingPoint = new Point {X = x0, Y = y0}; segment.EndingPoint = newEndingPoint; } } else { if ((y1 - y0) != 0) { Debug.Assert((y1 - y0) < TimeWindow); timeslice = TimeWindow/((y1 - y0)*stepy); Debug.Assert(timeslice != 0); } var fraction = dx - (dy >> 1); while (y0 != y1) { if (fraction >= 0) { x0 += stepx; fraction -= dy; } y0 += stepy; fraction += dx; // See if we've crossed into a new grid square gridX = x0 >> EngineSettings.GridWidthPowerOfTwo; gridY = y0 >> EngineSettings.GridHeightPowerOfTwo; segment.ExitTime += timeslice; if (gridX != segment.GridX || gridY != segment.GridY) { // End the segment since we've entered a new grid square var lastSegment = segment; segment = new MovementSegment(lastSegment, state, new Point(x0, y0), lastSegment.ExitTime, gridX, gridY) {ExitTime = lastSegment.ExitTime}; lastSegment.Next = segment; AddSegment(segment); } var newEndingPoint = new Point {X = x0, Y = y0}; segment.EndingPoint = newEndingPoint; } } // The last segment doesn't exit the grid, so its exit time is zero segment.ExitTime = 0; }
public SegmentWrapper(MovementSegment segment, ArrayList parentList) { Segment = segment; ParentList = parentList; }
// Adds a MovementSegment to all the proper cells in the grid to account for the creature's size by wrapping it // with a SegmentWrapper and adding the wrapper to the proper cells. When we calculate a creature's movement we have // to mark all the cells in a square around the center that their size covers as occupied. This function reserves // all the cells that a given segment would cover for a creature. internal void AddSegment(MovementSegment segment) { // Figure out how many cells on either side of the center we need to reserve var cellRadius = segment.State.CellRadius; if (segment.Previous == null) { // Beginning of a segment // We should have never started in a position where the radius of the organism // went outside the bounds of the universe Debug.Assert(segment.GridX >= 0 && segment.GridY >= 0 && segment.GridX - cellRadius >= 0 && segment.GridY - cellRadius >= 0 && segment.GridX + cellRadius < GameEngine.Current.GridWidth && segment.GridY + cellRadius < GameEngine.Current.GridHeight); Debug.Assert(segment.EntryTime == 0); StartSegments.Add(segment); } else { Debug.Assert(segment.EntryTime != 0); // If this segment pushes the organisms radius outside the bounds of the universe, clip it now // and don't bother evaluating it later if (segment.GridX < 0 || segment.GridY < 0 || segment.GridX - cellRadius < 0 || segment.GridY - cellRadius < 0 || segment.GridX + cellRadius > GameEngine.Current.GridWidth - 1 || segment.GridY + cellRadius > GameEngine.Current.GridHeight - 1) { segment.Previous.Next = null; return; } } // Do the top and bottom rows ArrayList list; SegmentWrapper wrapper; for (var x = segment.GridX - cellRadius; x <= segment.GridX + cellRadius; x++) { // *** Top row of square *** // Make a unique hash for every square in the grid var hash = (x << 16) | (segment.GridY - cellRadius); // retrieve the set of SegmentWrappers that are already in the cell of the grid list = (ArrayList) _gridSquares[hash]; if (list == null) { // None exist yet, create an ArrayList to hold them list = new ArrayList(); _gridSquares[hash] = list; } // Make sure two organisms didn't start in the same place Debug.Assert(segment.EntryTime != 0 || !HasStartingSegments(list)); // Create a wrapper for this segment, give it a backpointer to the arraylist that contains // all the segments in this cell wrapper = new SegmentWrapper(segment, list); // Add the SegmentWrapper itself to the list of segments in this cell list.Add(wrapper); // Now add the SegmentWrapper to our master sorted list of all SegmentWrappers anywhere _sortedList.Add(wrapper); // CellsLeftToResolve is there to recognize the fact that an animal overlaps many squares. // Until you know that it can occupy all of the squares it moves into, it can't move into // any of them. This property keeps track of whether we have resolved them all or not. // Here, we are adding one to it for every cell we occupy with this segment. segment.CellsLeftToResolve++; // *** Bottom row of square *** hash = (x << 16) | (segment.GridY + cellRadius); list = (ArrayList) _gridSquares[hash]; if (list == null) { list = new ArrayList(); _gridSquares[hash] = list; } // Make sure two organisms didn't start in the same place Debug.Assert(segment.EntryTime != 0 || !HasStartingSegments(list)); wrapper = new SegmentWrapper(segment, list); list.Add(wrapper); _sortedList.Add(wrapper); segment.CellsLeftToResolve++; } // Do left and right columns for (var y = segment.GridY - cellRadius + 1; y <= segment.GridY + cellRadius - 1; y++) { // Make a unique hash for every square in the grid var hash = ((segment.GridX - cellRadius) << 16) | y; list = (ArrayList) _gridSquares[hash]; if (list == null) { list = new ArrayList(); _gridSquares[hash] = list; } // Make sure two organisms didn't start in the same place Debug.Assert(segment.EntryTime != 0 || !HasStartingSegments(list)); wrapper = new SegmentWrapper(segment, list); list.Add(wrapper); _sortedList.Add(wrapper); segment.CellsLeftToResolve++; hash = ((segment.GridX + cellRadius) << 16) | y; list = (ArrayList) _gridSquares[hash]; if (list == null) { list = new ArrayList(); _gridSquares[hash] = list; } // Make sure two organisms didn't start in the same place Debug.Assert(segment.EntryTime != 0 || !HasStartingSegments(list)); wrapper = new SegmentWrapper(segment, list); list.Add(wrapper); _sortedList.Add(wrapper); segment.CellsLeftToResolve++; } }
// Adds a MovementSegment to all the proper cells in the grid to account for the creature's size by wrapping it // with a SegmentWrapper and adding the wrapper to the proper cells. When we calculate a creature's movement we have // to mark all the cells in a square around the center that their size covers as occupied. This function reserves // all the cells that a given segment would cover for a creature. internal void AddSegment(MovementSegment segment) { // Figure out how many cells on either side of the center we need to reserve var cellRadius = segment.State.CellRadius; if (segment.Previous == null) { // Beginning of a segment // We should have never started in a position where the radius of the organism // went outside the bounds of the universe Debug.Assert(segment.GridX >= 0 && segment.GridY >= 0 && segment.GridX - cellRadius >= 0 && segment.GridY - cellRadius >= 0 && segment.GridX + cellRadius < GameEngine.Current.GridWidth && segment.GridY + cellRadius < GameEngine.Current.GridHeight); Debug.Assert(segment.EntryTime == 0); StartSegments.Add(segment); } else { Debug.Assert(segment.EntryTime != 0); // If this segment pushes the organisms radius outside the bounds of the universe, clip it now // and don't bother evaluating it later if (segment.GridX < 0 || segment.GridY < 0 || segment.GridX - cellRadius < 0 || segment.GridY - cellRadius < 0 || segment.GridX + cellRadius > GameEngine.Current.GridWidth - 1 || segment.GridY + cellRadius > GameEngine.Current.GridHeight - 1) { segment.Previous.Next = null; return; } } // Do the top and bottom rows ArrayList list; SegmentWrapper wrapper; for (var x = segment.GridX - cellRadius; x <= segment.GridX + cellRadius; x++) { // *** Top row of square *** // Make a unique hash for every square in the grid var hash = (x << 16) | (segment.GridY - cellRadius); // retrieve the set of SegmentWrappers that are already in the cell of the grid list = (ArrayList)_gridSquares[hash]; if (list == null) { // None exist yet, create an ArrayList to hold them list = new ArrayList(); _gridSquares[hash] = list; } // Make sure two organisms didn't start in the same place Debug.Assert(segment.EntryTime != 0 || !HasStartingSegments(list)); // Create a wrapper for this segment, give it a backpointer to the arraylist that contains // all the segments in this cell wrapper = new SegmentWrapper(segment, list); // Add the SegmentWrapper itself to the list of segments in this cell list.Add(wrapper); // Now add the SegmentWrapper to our master sorted list of all SegmentWrappers anywhere _sortedList.Add(wrapper); // CellsLeftToResolve is there to recognize the fact that an animal overlaps many squares. // Until you know that it can occupy all of the squares it moves into, it can't move into // any of them. This property keeps track of whether we have resolved them all or not. // Here, we are adding one to it for every cell we occupy with this segment. segment.CellsLeftToResolve++; // *** Bottom row of square *** hash = (x << 16) | (segment.GridY + cellRadius); list = (ArrayList)_gridSquares[hash]; if (list == null) { list = new ArrayList(); _gridSquares[hash] = list; } // Make sure two organisms didn't start in the same place Debug.Assert(segment.EntryTime != 0 || !HasStartingSegments(list)); wrapper = new SegmentWrapper(segment, list); list.Add(wrapper); _sortedList.Add(wrapper); segment.CellsLeftToResolve++; } // Do left and right columns for (var y = segment.GridY - cellRadius + 1; y <= segment.GridY + cellRadius - 1; y++) { // Make a unique hash for every square in the grid var hash = ((segment.GridX - cellRadius) << 16) | y; list = (ArrayList)_gridSquares[hash]; if (list == null) { list = new ArrayList(); _gridSquares[hash] = list; } // Make sure two organisms didn't start in the same place Debug.Assert(segment.EntryTime != 0 || !HasStartingSegments(list)); wrapper = new SegmentWrapper(segment, list); list.Add(wrapper); _sortedList.Add(wrapper); segment.CellsLeftToResolve++; hash = ((segment.GridX + cellRadius) << 16) | y; list = (ArrayList)_gridSquares[hash]; if (list == null) { list = new ArrayList(); _gridSquares[hash] = list; } // Make sure two organisms didn't start in the same place Debug.Assert(segment.EntryTime != 0 || !HasStartingSegments(list)); wrapper = new SegmentWrapper(segment, list); list.Add(wrapper); _sortedList.Add(wrapper); segment.CellsLeftToResolve++; } }
// Take an OrganismState and a path to move it on (from Point1 to Point2) and break up this // path into MovementSegments when it moves between cells. Each MovementSegment represents the part of the path // that is within a single cell. // // Even though we are only drawing a single line and rasterizing it along one path through the grid, // we can assume that the center of every cell that this organism occupies follows exactly the same path // and has segments that break down at exactly the same timeframes. Thus we call the AddSegment() routine // for each MovementSegment. AddSegment() wraps this MovementSegment with a wrapper (SegmentWrapper) and adds // this wrapper to every cell that the creature occupies so that all the cells that are occupied at any given // are properly noted. // // AddPath() records the time that the path entered and left each cell in the path. // Each cell of the grid is 2^GridWidthPowerOfTwo x 2^GridHeightPowerOfTwo pixels in size // Each is a power of two so we can shift instead of dividing // The first square of the grid starts at 0, 0 and goes to 2^GridWidthPowerOfTwo - 1, 2^GridHeightPowerOfTwo - 1 // Use Bresenham's algorithm to do the rasterization and figure out when the path enters or leaves grid squares. public void AddPath(OrganismState state, Point p1, Point p2) { var x0 = p1.X; var y0 = p1.Y; var x1 = p2.X; var y1 = p2.Y; var dy = y1 - y0; var dx = x1 - x0; int stepx, stepy; var timeslice = 0; Debug.Assert(x0 > -1 && x1 > -1 && y0 > -1 && y1 > -1); if (dy < 0) { dy = -dy; stepy = -1; } else { stepy = 1; } if (dx < 0) { dx = -dx; stepx = -1; } else { stepx = 1; } dy <<= 1; dx <<= 1; // start the first segment at the initial point at time 0 var gridX = x0 >> EngineSettings.GridWidthPowerOfTwo; var gridY = y0 >> EngineSettings.GridWidthPowerOfTwo; Debug.Assert(gridX == state.GridX && gridY == state.GridY); var segment = new MovementSegment(null, state, new Point(x0, y0), 0, gridX, gridY) { EndingPoint = new Point(p1.X, p1.Y) }; AddSegment(segment); if (p1 == p2) { return; } if (dx > dy) { // Determine how many points we'll plot and estimate time by that if ((x1 - x0) != 0) { Debug.Assert((x1 - x0) < TimeWindow); timeslice = TimeWindow / ((x1 - x0) * stepx); Debug.Assert(timeslice != 0); } var fraction = dy - (dx >> 1); // same as 2*dy - dx while (x0 != x1) { if (fraction >= 0) { y0 += stepy; fraction -= dx; // same as fraction -= 2*dx } x0 += stepx; fraction += dy; // same as fraction -= 2*dy // See if we've crossed into a new grid square gridX = x0 >> EngineSettings.GridWidthPowerOfTwo; gridY = y0 >> EngineSettings.GridHeightPowerOfTwo; segment.ExitTime += timeslice; if (gridX != segment.GridX || gridY != segment.GridY) { // End the segment since we've entered a new grid square var lastSegment = segment; segment = new MovementSegment(lastSegment, state, new Point(x0, y0), lastSegment.ExitTime, gridX, gridY) { ExitTime = lastSegment.ExitTime }; lastSegment.Next = segment; AddSegment(segment); } var newEndingPoint = new Point { X = x0, Y = y0 }; segment.EndingPoint = newEndingPoint; } } else { if ((y1 - y0) != 0) { Debug.Assert((y1 - y0) < TimeWindow); timeslice = TimeWindow / ((y1 - y0) * stepy); Debug.Assert(timeslice != 0); } var fraction = dx - (dy >> 1); while (y0 != y1) { if (fraction >= 0) { x0 += stepx; fraction -= dy; } y0 += stepy; fraction += dx; // See if we've crossed into a new grid square gridX = x0 >> EngineSettings.GridWidthPowerOfTwo; gridY = y0 >> EngineSettings.GridHeightPowerOfTwo; segment.ExitTime += timeslice; if (gridX != segment.GridX || gridY != segment.GridY) { // End the segment since we've entered a new grid square var lastSegment = segment; segment = new MovementSegment(lastSegment, state, new Point(x0, y0), lastSegment.ExitTime, gridX, gridY) { ExitTime = lastSegment.ExitTime }; lastSegment.Next = segment; AddSegment(segment); } var newEndingPoint = new Point { X = x0, Y = y0 }; segment.EndingPoint = newEndingPoint; } } // The last segment doesn't exit the grid, so its exit time is zero segment.ExitTime = 0; }
public SegmentWrapper(MovementSegment segment, ArrayList parentList) { this.segment = segment; this.parentList = parentList; }