/// <summary> /// Draws a bundle of halls from one direction, going up to the specified point, and connects them. /// </summary> /// <param name="map"></param> /// <param name="dir"></param> /// <param name="forwardEnd"></param> /// <param name="starts"></param> public void DrawCombinedHall(ITiledGenContext map, Dir4 dir, int forwardEnd, int[] starts) { bool vertical = dir.ToAxis() == Axis4.Vert; // Choose whether to start at the min X/Y, or the max X/Y Loc forwardStartLoc = (dir == Dir4.Up || dir == Dir4.Left) ? this.Draw.Start : this.Draw.End - new Loc(1); IntRange start = this.Draw.GetSide(dir.ToAxis()); start.Max -= 1; start = new IntRange(start.Max, start.Min); // draw the halls for (int jj = 0; jj < starts.Length; jj++) { start.Min = Math.Min(start.Min, starts[jj]); start.Max = Math.Max(start.Max, starts[jj]); Loc startLoc = new Loc(vertical ? starts[jj] : forwardStartLoc.X, vertical ? forwardStartLoc.Y : starts[jj]); Loc endLoc = new Loc(vertical ? starts[jj] : forwardEnd, vertical ? forwardEnd : starts[jj]); this.DrawHall(map, startLoc, endLoc, vertical); } // combine the halls Loc combineStart = new Loc(vertical ? start.Min : forwardEnd, vertical ? forwardEnd : start.Min); Loc combineEnd = new Loc(vertical ? start.Max : forwardEnd, vertical ? forwardEnd : start.Max); this.DrawHall(map, combineStart, combineEnd, !vertical); }
public static Rect GetSupportRect(FloorPlan floorPlan, IRoomGen oldGen, IRoomGen newGen, Dir4 dir, List <RoomHallIndex> adjacentsInDir) { bool vertical = dir.ToAxis() == Axis4.Vert; Rect supportRect = newGen.Draw; supportRect.Start += dir.GetLoc() * supportRect.Size.GetScalar(dir.ToAxis()); supportRect.SetScalar(dir, oldGen.Draw.GetScalar(dir)); IntRange minMax = newGen.Draw.GetSide(dir.ToAxis()); foreach (RoomHallIndex adj in adjacentsInDir) { IRoomGen adjGen = floorPlan.GetRoomHall(adj).RoomGen; IntRange adjMinMax = adjGen.Draw.GetSide(dir.ToAxis()); minMax = new IntRange(Math.Min(adjMinMax.Min, minMax.Min), Math.Max(adjMinMax.Max, minMax.Max)); } IntRange oldMinMax = oldGen.Draw.GetSide(dir.ToAxis()); minMax = new IntRange(Math.Max(oldMinMax.Min, minMax.Min), Math.Min(oldMinMax.Max, minMax.Max)); if (vertical) { supportRect.SetScalar(Dir4.Left, minMax.Min); supportRect.SetScalar(Dir4.Right, minMax.Max); } else { supportRect.SetScalar(Dir4.Up, minMax.Min); supportRect.SetScalar(Dir4.Down, minMax.Max); } return(supportRect); }
/// <summary> /// /// </summary> /// <param name="room"></param> /// <param name="dir">Direction from room to hall.</param> /// <param name="tier"></param> /// <returns></returns> public virtual IntRange GetHallTouchRange(IRoomGen room, Dir4 dir, int tier) { bool vertical = dir.ToAxis() == Axis4.Vert; // The hall will touch the entire side of each room under normal circumstances. // Things get tricky for a target room that occupies more than one cell. // First, try to cover only the part of the target room that's in the cell. // If that's not possible, try to extend the hall until it covers one tile of the target room. IntRange startRange = room.Draw.GetSide(dir.ToAxis()); // factor possibletiles into this calculation int borderLength = room.GetBorderLength(dir); for (int ii = 0; ii < borderLength; ii++) { if (room.GetFulfillableBorder(dir, ii)) { startRange.Min += ii; break; } } for (int ii = 0; ii < borderLength; ii++) { if (room.GetFulfillableBorder(dir, borderLength - 1 - ii)) { startRange.Max -= ii; break; } } int tierStart = vertical ? tier * (this.WidthPerCell + this.CellWall) : tier * (this.HeightPerCell + this.CellWall); int tierLength = vertical ? this.WidthPerCell : this.HeightPerCell; IntRange newRange = new IntRange(Math.Max(startRange.Min, tierStart), Math.Min(startRange.Max, tierStart + tierLength)); if (newRange.Max <= newRange.Min) { // try to extend the hall until it covers one tile of the target room. // first, note that the current end range is covering the zone between the tier and the edge of the room (inverted) // un-invert this and inflate by 1, and you will have a zone that covers 1 tile with the room // get the intersection of this zone and the room. newRange = new IntRange(Math.Max(startRange.Min, newRange.Max - 1), Math.Min(startRange.Max, newRange.Min + 1)); } return(newRange); }
protected static bool HasBorderOpening(IRoomGen roomFrom, Rect rectTo, Dir4 expandTo) { Loc diff = roomFrom.Draw.Start - rectTo.Start; // how far ahead the start of source is to dest int offset = diff.GetScalar(expandTo.ToAxis().Orth()); // Traverse the region that both borders touch int sourceLength = roomFrom.GetBorderLength(expandTo); int destLength = rectTo.Size.GetScalar(expandTo.ToAxis().Orth()); for (int ii = Math.Max(0, offset); ii - offset < sourceLength && ii < destLength; ii++) { bool sourceFulfill = roomFrom.GetFulfillableBorder(expandTo, ii - offset); if (sourceFulfill) { return(true); } } return(false); }
public static int GetBorderMatch(IRoomGen roomFrom, IRoomGen room, Loc candLoc, Dir4 expandTo) { int totalMatch = 0; Loc diff = roomFrom.Draw.Start - candLoc; // how far ahead the start of source is to dest int offset = diff.GetScalar(expandTo.ToAxis().Orth()); // Traverse the region that both borders touch int sourceLength = roomFrom.GetBorderLength(expandTo); int destLength = room.GetBorderLength(expandTo.Reverse()); for (int ii = Math.Max(0, offset); ii - offset < sourceLength && ii < destLength; ii++) { bool sourceFulfill = roomFrom.GetFulfillableBorder(expandTo, ii - offset); bool destFulfill = room.GetFulfillableBorder(expandTo.Reverse(), ii); if (sourceFulfill && destFulfill) { totalMatch++; } } return(totalMatch); }
public override void ApplyToPath(IRandom rand, GridPlan floorPlan) { if (floorPlan.GridWidth < 3 || floorPlan.GridHeight < 3) { throw new InvalidOperationException("Not enough room to create path."); } int maxRooms = (2 * floorPlan.GridWidth) + (2 * floorPlan.GridHeight) - 4; int roomOpen = maxRooms * this.CircleRoomRatio.Pick(rand) / 100; int paths = this.Paths.Pick(rand); if (roomOpen < 1 && paths < 1) { roomOpen = 1; } GenContextDebug.StepIn("Outer Circle"); // draw the top and bottom for (int xx = 0; xx < floorPlan.GridWidth; xx++) { this.RollOpenRoom(rand, floorPlan, new Loc(xx, 0), ref roomOpen, ref maxRooms); GenContextDebug.DebugProgress("Room"); this.RollOpenRoom(rand, floorPlan, new Loc(xx, floorPlan.GridHeight - 1), ref roomOpen, ref maxRooms); GenContextDebug.DebugProgress("Room"); if (xx > 0) { floorPlan.SetHall(new LocRay4(new Loc(xx, 0), Dir4.Left), this.GenericHalls.Pick(rand), this.HallComponents.Clone()); GenContextDebug.DebugProgress("Hall"); floorPlan.SetHall(new LocRay4(new Loc(xx, floorPlan.GridHeight - 1), Dir4.Left), this.GenericHalls.Pick(rand), this.HallComponents.Clone()); GenContextDebug.DebugProgress("Hall"); } } // draw the left and right (excluding the top and bottom) for (int yy = 0; yy < floorPlan.GridHeight; yy++) { // exclude the top and bottom if (yy > 0 && yy < floorPlan.GridHeight - 1) { this.RollOpenRoom(rand, floorPlan, new Loc(0, yy), ref roomOpen, ref maxRooms); GenContextDebug.DebugProgress("Room"); this.RollOpenRoom(rand, floorPlan, new Loc(floorPlan.GridWidth - 1, yy), ref roomOpen, ref maxRooms); GenContextDebug.DebugProgress("Room"); } if (yy > 0) { floorPlan.SetHall(new LocRay4(new Loc(0, yy), Dir4.Up), this.GenericHalls.Pick(rand), this.HallComponents.Clone()); GenContextDebug.DebugProgress("Hall"); floorPlan.SetHall(new LocRay4(new Loc(floorPlan.GridWidth - 1, yy), Dir4.Up), this.GenericHalls.Pick(rand), this.HallComponents.Clone()); GenContextDebug.DebugProgress("Hall"); } } GenContextDebug.StepOut(); GenContextDebug.StepIn("Inner Paths"); Rect innerRect = new Rect(1, 1, floorPlan.GridWidth - 2, floorPlan.GridHeight - 2); // create inner paths for (int pathsMade = 0; pathsMade < paths; pathsMade++) { GenContextDebug.StepIn($"Path {pathsMade}"); Dir4 startDir = DirExt.VALID_DIR4.ElementAt(rand.Next(DirExt.DIR4_COUNT)); int x = rand.Next(innerRect.Start.X, innerRect.End.X); int y = rand.Next(innerRect.Start.Y, innerRect.End.Y); switch (startDir) { case Dir4.Down: y = 0; break; case Dir4.Left: x = floorPlan.GridWidth - 1; break; case Dir4.Up: y = floorPlan.GridHeight - 1; break; case Dir4.Right: x = 0; break; case Dir4.None: break; default: throw new ArgumentOutOfRangeException(nameof(startDir), "Invalid enum value."); } Loc wanderer = new Loc(x, y); Dir4 prevDir = Dir4.None; // direction of movement int pathLength = (startDir.ToAxis() == Axis4.Vert) ? innerRect.Height : innerRect.Width; for (int currentLength = 0; currentLength < pathLength; currentLength++) { Dir4 chosenDir = startDir; // avoid this block the first time if (currentLength > 0) { List <Dir4> dirs = new List <Dir4>(); foreach (Dir4 dir in DirExt.VALID_DIR4) { // do not backtrack if (dir == prevDir) { continue; } // do not hit edge if (!Collision.InBounds(innerRect, wanderer + dir.GetLoc())) { continue; } dirs.Add(dir); } chosenDir = dirs[rand.Next(dirs.Count)]; } Loc dest = wanderer + chosenDir.GetLoc(); GridRoomPlan existingRoom = floorPlan.GetRoomPlan(dest); if (existingRoom == null) { if (currentLength == pathLength - 1) // only the end room can be a non-hall { floorPlan.AddRoom(dest, this.GenericRooms.Pick(rand), this.RoomComponents.Clone()); } else { floorPlan.AddRoom(dest, this.GetDefaultGen(), this.HallComponents.Clone(), true); } GenContextDebug.DebugProgress("Room"); } else if (existingRoom.PreferHall) { if (currentLength == pathLength - 1) { // override the hall room existingRoom.RoomGen = this.GenericRooms.Pick(rand).Copy(); existingRoom.PreferHall = false; } } floorPlan.SetHall(new LocRay4(wanderer, chosenDir), this.GenericHalls.Pick(rand), this.HallComponents.Clone()); GenContextDebug.DebugProgress("Hall"); wanderer = dest; prevDir = chosenDir.Reverse(); } GenContextDebug.StepOut(); } GenContextDebug.StepOut(); }
/// <summary> /// Gets the minimum range along the side of a room that includes all of its fulfillable borders. /// Special cases arise if the room is multi-cell. /// </summary> /// <param name="room"></param> /// <param name="dir">Direction from room to hall.</param> /// <param name="tier"></param> /// <returns></returns> public virtual IntRange GetHallTouchRange(IRoomGen room, Dir4 dir, int tier) { bool vertical = dir.ToAxis() == Axis4.Vert; // The hall will touch the whole fulfillable side of each room under normal circumstances. // Things get tricky for a target room that occupies more than one cell. // First, try to cover only the part of the target room that's in the cell. int tierStart = vertical ? tier * (this.WidthPerCell + this.CellWall) : tier * (this.HeightPerCell + this.CellWall); int tierLength = vertical ? this.WidthPerCell : this.HeightPerCell; IntRange tierRange = new IntRange(tierStart, tierStart + tierLength); IntRange roomRange = room.Draw.GetSide(dir.ToAxis()); // factor possibletiles into this calculation int borderStart = tierStart - roomRange.Min; if (borderStart < 0) { tierRange.Min -= borderStart; borderStart = 0; } for (int ii = borderStart; ii < roomRange.Length; ii++) { if (room.GetFulfillableBorder(dir, ii)) { break; } else { tierRange.Min += 1; } } int borderEnd = tierStart + tierLength - roomRange.Min; if (borderEnd > roomRange.Length) { tierRange.Max += roomRange.Length - borderEnd; borderEnd = roomRange.Length; } for (int ii = borderEnd - 1; ii >= 0; ii--) { if (room.GetFulfillableBorder(dir, ii)) { break; } else { tierRange.Max -= 1; } } if (tierRange.Max > tierRange.Min) { return(tierRange); } // If that's not possible, then it means that the room must have fulfillable tiles outside of the current bound. // Try to extend the hall until it covers one fulfillable tile of the target room. // Easy method: note that the current tierRange range is covering the zone between the tier and the edge of the room (inverted) // There will be either a workable range at the start or a workable range at the end, never neither. IntRange startRange = new IntRange(tierRange.Max - 1, tierStart + 1); IntRange endRange = new IntRange(tierStart + tierLength - 1, tierRange.Min + 1); bool chooseStart = true; bool chooseEnd = true; // if tierRanges reached the absolute limits of the roomRange, then there is no fulfillable tile on that side if (startRange.Min < roomRange.Min) { chooseStart = false; } else if (endRange.Length > startRange.Length) { chooseEnd = false; } if (endRange.Max > roomRange.Max) { chooseEnd = false; } else if (startRange.Length > endRange.Length) { chooseStart = false; } if (!chooseStart && !chooseEnd) { throw new ArgumentException("PrepareFulfillableBorders did not open at least one open tile for each direction!"); } if (chooseStart) { return(startRange); } return(endRange); }
/// <summary> /// Decides on the bounds for each hall. Also writes to the adjacent rooms' SideReqs and tile permissions /// </summary> /// <param name="x"></param> /// <param name="y"></param> /// <param name="vertical"></param> /// <param name="rand">todo: describe rand parameter on ChooseHallBounds</param> public void ChooseHallBounds(IRandom rand, int x, int y, bool vertical) { GridRoomPlan startRoom = this.GetRoomPlan(new Loc(x, y)); GridRoomPlan endRoom = this.GetRoomPlan(new Loc(x + (vertical ? 0 : 1), y + (vertical ? 1 : 0))); GridHallGroup hallGroup = vertical ? this.VHalls[x][y] : this.HHalls[x][y]; if (hallGroup.MainHall != null) { // also sets the sidereqs int tier = vertical ? x : y; Dir4 dir = vertical ? Dir4.Down : Dir4.Right; IntRange startRange = this.GetHallTouchRange(startRoom.RoomGen, dir, tier); IntRange endRange = this.GetHallTouchRange(endRoom.RoomGen, dir.Reverse(), tier); IntRange combinedRange = new IntRange(Math.Min(startRange.Min, endRange.Min), Math.Max(startRange.Max, endRange.Max)); Loc start = startRoom.RoomGen.Draw.End; Loc end = endRoom.RoomGen.Draw.Start; Rect startCell = this.GetCellBounds(startRoom.Bounds); Rect endCell = this.GetCellBounds(endRoom.Bounds); Rect bounds = vertical ? new Rect(combinedRange.Min, start.Y, combinedRange.Length, end.Y - start.Y) : new Rect(start.X, combinedRange.Min, end.X - start.X, combinedRange.Length); // a side constitutes intruding bound when the rectangle moves forward enough to go to the other side // and the other side touched is outside of side B's bound (including borders) // startRange intrudes if startRange goes outside end's tier (borders included) bool startIntrude = !endCell.GetSide(dir.ToAxis()).Contains(startRange); // and end is greater than the edge (borders excluded) bool endTouch = bounds.GetScalar(dir) == endCell.GetScalar(dir.Reverse()); bool endIntrude = !startCell.GetSide(dir.ToAxis()).Contains(endRange); // and end is greater than the edge (borders excluded) bool startTouch = bounds.GetScalar(dir.Reverse()) == startCell.GetScalar(dir); // neither side intrudes bound: use the computed rectangle if ((!startIntrude && !endIntrude) || (endTouch && startTouch) || (!(startIntrude && endIntrude) && ((startIntrude && endTouch) || (endIntrude && startTouch)))) { hallGroup.MainHall.RoomGen.PrepareSize(rand, bounds.Size); hallGroup.MainHall.RoomGen.SetLoc(bounds.Start); } else { int divPoint = startCell.GetScalar(dir) + 1; IntRange startDivRange = startRange; IntRange endDivRange = endRange; if (startIntrude && !endIntrude) { // side A intrudes bound, side B does not: divide A and B; doesn't matter who gets border // side A touches border, side B does not: divide A and B; A gets border // // side A does not, side B touches border: A gets border; don't need B - this cannot happen // side A touches border, side B touches border: A gets border; don't need B - this cannot happen // // in short, divide with start getting the border // startDivRange needs to contain endRange startDivRange = combinedRange; } else if (!startIntrude && endIntrude) { // side A does not, side B intrudes bound: divide A and B; doesn't matter who gets border // side A does not, side B touches border: divide A and B; B gets border // // side A touches border, side B does not: B gets border; don't need A - this cannot happen // side A touches border, side B touches border: B gets border; don't need B - this cannot happen // // in short, divide with end getting the border // endDivRange needs to contain startRange divPoint = startCell.GetScalar(dir); endDivRange = combinedRange; } else { // side A intrudes bound, side B intrudes bound: divide A and B; doesn't matter who gets border if (startTouch) { // side A touches border, side B does not: divide A and B; A gets border } if (endTouch) { // side A does not, side B touches border: divide A and B; B gets border divPoint = startCell.GetScalar(dir); } // side A touches border, side B touches border: A gets border; don't need B - this cannot happen // both sides need to cover the intersection of their cells IntRange interCellSide = IntRange.Intersect(startCell.GetSide(dir.ToAxis()), endCell.GetSide(dir.ToAxis())); startDivRange = IntRange.IncludeRange(startDivRange, interCellSide); endDivRange = IntRange.IncludeRange(endDivRange, interCellSide); } Rect startBox = vertical ? new Rect(startDivRange.Min, start.Y, startDivRange.Length, divPoint - start.Y) : new Rect(start.X, startDivRange.Min, divPoint - start.X, startDivRange.Length); Rect endBox = vertical ? new Rect(endDivRange.Min, divPoint, endDivRange.Length, end.Y - divPoint) : new Rect(divPoint, endDivRange.Min, end.X - divPoint, endDivRange.Length); GridHallPlan originalHall = hallGroup.MainHall; hallGroup.HallParts.Add(new GridHallPlan((IPermissiveRoomGen)originalHall.RoomGen.Copy(), originalHall.Components)); hallGroup.HallParts[0].RoomGen.PrepareSize(rand, startBox.Size); hallGroup.HallParts[0].RoomGen.SetLoc(startBox.Start); hallGroup.HallParts[1].RoomGen.PrepareSize(rand, endBox.Size); hallGroup.HallParts[1].RoomGen.SetLoc(endBox.Start); } } }
public static void AddLegalPlacements(SpawnList <Loc> possiblePlacements, FloorPlan floorPlan, RoomHallIndex indexFrom, IRoomGen roomFrom, IRoomGen room, Dir4 expandTo) { bool vertical = expandTo.ToAxis() == Axis4.Vert; // this scaling factor equalizes the chances of long sides vs short sides int reverseSideMult = vertical ? roomFrom.Draw.Width * room.Draw.Width : roomFrom.Draw.Height * room.Draw.Height; IntRange side = roomFrom.Draw.GetSide(expandTo.ToAxis()); // subtract the room's original size, not the inflated trialrect size side.Min -= (vertical ? room.Draw.Size.X : room.Draw.Size.Y) - 1; Rect tryRect = room.Draw; // expand in every direction // this will create a one-tile buffer to check for collisions tryRect.Inflate(1, 1); int currentScalar = side.Min; while (currentScalar < side.Max) { // compute the location Loc trialLoc = roomFrom.GetEdgeRectLoc(expandTo, room.Draw.Size, currentScalar); tryRect.Start = trialLoc + new Loc(-1, -1); // check for collisions (not counting the rectangle from) List <RoomHallIndex> collisions = floorPlan.CheckCollision(tryRect); // find the first tile in which no collisions will be found int maxCollideScalar = currentScalar; bool collided = false; foreach (RoomHallIndex collision in collisions) { if (collision != indexFrom) { IRoomGen collideRoom = floorPlan.GetRoomHall(collision).RoomGen; // this is the point at which the new room will barely touch the collided room // the +1 at the end will move it into the safe zone maxCollideScalar = Math.Max(maxCollideScalar, vertical ? collideRoom.Draw.Right : collideRoom.Draw.Bottom); collided = true; } } // if no collisions were hit, do final checks and add the room if (!collided) { Loc locTo = roomFrom.GetEdgeRectLoc(expandTo, room.Draw.Size, currentScalar); // must be within the borders of the floor! if (floorPlan.DrawRect.Contains(new Rect(locTo, room.Draw.Size))) { // check the border match and if add to possible placements int chanceTo = FloorPlan.GetBorderMatch(roomFrom, room, locTo, expandTo); if (chanceTo > 0) { possiblePlacements.Add(locTo, chanceTo * reverseSideMult); } } } currentScalar = maxCollideScalar + 1; } }
protected static ListPathTraversalNode?GetRoomToConnect(FloorPlan floorPlan, RoomHallIndex chosenFrom, Dir4 dir) { // extend a rectangle to the border of the floor in the chosen direction bool vertical = dir.ToAxis() == Axis4.Vert; int dirSign = dir.GetLoc().GetScalar(dir.ToAxis()); IRoomGen genFrom = floorPlan.GetRoomHall(chosenFrom).RoomGen; Rect sampleRect = genFrom.Draw; // expand from the start of that border direction to the borders of the floor sampleRect.Start += dir.GetLoc() * sampleRect.Size.GetScalar(dir.ToAxis()); // it doesn't have to be exactly the borders so just add the total size to be sure sampleRect.Expand(dir, vertical ? floorPlan.Size.Y : floorPlan.Size.X); // find the closest room. var chosenTo = new RoomHallIndex(-1, false); foreach (RoomHallIndex collision in floorPlan.CheckCollision(sampleRect)) { Rect collidedRect = floorPlan.GetRoomHall(collision).RoomGen.Draw; // limit the expansion by direction int sampleScalar = sampleRect.GetScalar(dir); int collidedScalar = collidedRect.GetScalar(dir.Reverse()); bool limit = dirSign == Math.Sign(sampleScalar - collidedScalar); if (limit) { // update the boundaries sampleRect.SetScalar(dir, collidedScalar); chosenTo = collision; } } // if it didn't collide with ANYTHING, then quit if (chosenTo.Index == -1) { return(null); } IRoomGen genTo = floorPlan.GetRoomHall(chosenTo).RoomGen; // narrow the rectangle if touching something on the side // widen the rectangle by width Rect widthRect = sampleRect; widthRect.Inflate(vertical ? 1 : 0, vertical ? 0 : 1); bool retractLeft = false; bool retractRight = false; Dir4 leftDir = DirExt.AddAngles(dir, Dir4.Left); Dir4 rightDir = DirExt.AddAngles(dir, Dir4.Right); foreach (RoomHallIndex collision in floorPlan.CheckCollision(widthRect)) { Rect collidedRect = floorPlan.GetRoomHall(collision).RoomGen.Draw; if (!retractLeft) { if (collidedRect.GetScalar(leftDir.Reverse()) == sampleRect.GetScalar(leftDir)) { retractLeft = true; } } if (!retractRight) { if (collidedRect.GetScalar(rightDir.Reverse()) == sampleRect.GetScalar(rightDir)) { retractRight = true; } } } // retract the rectangle if (retractLeft) { sampleRect.Expand(leftDir, -1); } if (retractRight) { sampleRect.Expand(rightDir, -1); } // if the rectangle has been retracted too much, we can't go on if (sampleRect.Area <= 0) { return(null); } // check for border availability between start and end bool foundFrom = HasBorderOpening(genFrom, sampleRect, dir); bool foundTo = HasBorderOpening(genTo, sampleRect, dir.Reverse()); // return the expansion if one is found if (foundFrom && foundTo) { return(new ListPathTraversalNode(chosenFrom, chosenTo, sampleRect)); } else { return(null); } }