public ProjectedCellRegion(Map map, PPos topLeft, PPos bottomRight) { TopLeft = topLeft; BottomRight = bottomRight; // The projection from MPos -> PPos cannot produce a larger V coordinate // so the top edge of the MPos region is the same as the PPos region. // (in fact the cells are identical if height == 0) mapTopLeft = (MPos)topLeft; // The bottom edge is trickier: cells at MPos.V > bottomRight.V may have // been projected into this region if they have height > 0. // Each height step is equivalent to 512 WRange units, which is one MPos // step for isometric cells, but only half a MPos step for classic cells. Doh! var maxHeight = map.Grid.MaximumTerrainHeight; var heightOffset = map.Grid.Type == MapGridType.RectangularIsometric ? maxHeight : maxHeight / 2; // Use the MapHeight data array to clamp the bottom coordinate so it doesn't overflow the map mapBottomRight = map.MapHeight.Value.Clamp(new MPos(bottomRight.U, bottomRight.V + heightOffset)); }
public PPos[] ProjectedCellsCovering(MPos uv) { // Shortcut for mods that don't use heightmaps if (MaximumTerrainHeight == 0) { return new[] { (PPos)uv } } ; if (!initializedCellProjection) { InitializeCellProjection(); } if (!cellProjection.Contains(uv)) { return(NoProjectedCells); } return(cellProjection[uv]); }
public void SetBounds(MPos tl, MPos br) { // The tl and br coordinates are inclusive, but the Rectangle // is exclusive. Pad the right and bottom edges to match. Bounds = Rectangle.FromLTRB(tl.U, tl.V, br.U + 1, br.V + 1); CellsInsideBounds = new CellRegion(TileShape, tl.ToCPos(this), br.ToCPos(this)); // Directly calculate the projected map corners in world units avoiding unnecessary // conversions. This abuses the definition that the width of the cell is always // 1024 units, and that the height of two rows is 2048 for classic cells and 1024 // for diamond cells. var wtop = tl.V * 1024; var wbottom = (br.V + 1) * 1024; if (TileShape == TileShape.Diamond) { wtop /= 2; wbottom /= 2; } ProjectedTopLeft = new WPos(tl.U * 1024, wtop, 0); ProjectedBottomRight = new WPos(br.U * 1024 - 1, wbottom - 1, 0); }
bool ContainsAllProjectedCellsCovering(MPos uv) { if (Grid.MaximumTerrainHeight == 0) { return(Contains((PPos)uv)); } // If the cell has no valid projection, then we're off the map. var projectedCells = ProjectedCellsCovering(uv); if (projectedCells.Length == 0) { return(false); } foreach (var puv in projectedCells) { if (!Contains(puv)) { return(false); } } return(true); }
// Resolve an array index from map coordinates int Index(MPos uv) { return(uv.V * Size.Width + uv.U); }
public MPos Clamp(MPos uv) { return(uv.Clamp(new Rectangle(0, 0, Size.Width - 1, Size.Height - 1))); }
public bool Contains(MPos uv) { return(bounds.Contains(uv.U, uv.V)); }
public byte[] SavePreview() { var tileset = Rules.TileSet; var resources = Rules.Actors["world"].TraitInfos <ResourceTypeInfo>() .ToDictionary(r => r.ResourceType, r => r.TerrainType); using (var stream = new MemoryStream()) { var isRectangularIsometric = Grid.Type == MapGridType.RectangularIsometric; // Fudge the heightmap offset by adding as much extra as we need / can. // This tries to correct for our incorrect assumption that MPos == PPos var heightOffset = Math.Min(Grid.MaximumTerrainHeight, MapSize.Y - Bounds.Bottom); var width = Bounds.Width; var height = Bounds.Height + heightOffset; var bitmapWidth = width; if (isRectangularIsometric) { bitmapWidth = 2 * bitmapWidth - 1; } using (var bitmap = new Bitmap(bitmapWidth, height)) { var bitmapData = bitmap.LockBits(bitmap.Bounds(), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb); unsafe { var colors = (int *)bitmapData.Scan0; var stride = bitmapData.Stride / 4; Color leftColor, rightColor; for (var y = 0; y < height; y++) { for (var x = 0; x < width; x++) { var uv = new MPos(x + Bounds.Left, y + Bounds.Top); var resourceType = Resources[uv].Type; if (resourceType != 0) { // Cell contains resources string res; if (!resources.TryGetValue(resourceType, out res)) { continue; } leftColor = rightColor = tileset[tileset.GetTerrainIndex(res)].Color; } else { // Cell contains terrain var type = tileset.GetTileInfo(Tiles[uv]); leftColor = type != null ? type.LeftColor : Color.Black; rightColor = type != null ? type.RightColor : Color.Black; } if (isRectangularIsometric) { // Odd rows are shifted right by 1px var dx = uv.V & 1; if (x + dx > 0) { colors[y * stride + 2 * x + dx - 1] = leftColor.ToArgb(); } if (2 * x + dx < stride) { colors[y * stride + 2 * x + dx] = rightColor.ToArgb(); } } else { colors[y * stride + x] = leftColor.ToArgb(); } } } } bitmap.UnlockBits(bitmapData); bitmap.Save(stream, ImageFormat.Png); } return(stream.ToArray()); } }
public MPos Clamp(MPos uv) { if (Grid.MaximumTerrainHeight == 0) { return((MPos)Clamp((PPos)uv)); } // Already in bounds, so don't need to do anything. if (ContainsAllProjectedCellsCovering(uv)) { return(uv); } // Clamping map coordinates is trickier than it might first look! // This needs to handle three nasty cases: // * The requested cell is well outside the map region // * The requested cell is near the top edge inside the map but outside the projected layer // * The clamped projected cell lands on a cliff face with no associated map cell // // Handling these cases properly requires abuse of our knowledge of the projection transform. // // The U coordinate doesn't change significantly in the projection, so clamp this // straight away and ensure the point is somewhere inside the map uv = cellProjection.Clamp(new MPos(uv.U.Clamp(Bounds.Left, Bounds.Right), uv.V)); // Project this guessed cell and take the first available cell // If it is projected outside the layer, then make another guess. var allProjected = ProjectedCellsCovering(uv); var projected = allProjected.Any() ? allProjected.First() : new PPos(uv.U, uv.V.Clamp(Bounds.Top, Bounds.Bottom)); // Clamp the projected cell to the map area projected = Clamp(projected); // Project the cell back into map coordinates. // This may fail if the projected cell covered a cliff or another feature // where there is a large change in terrain height. var unProjected = Unproject(projected); if (!unProjected.Any()) { // Adjust V until we find a cell that works for (var x = 2; x <= 2 * Grid.MaximumTerrainHeight; x++) { var dv = ((x & 1) == 1 ? 1 : -1) * x / 2; var test = new PPos(projected.U, projected.V + dv); if (!Contains(test)) { continue; } unProjected = Unproject(test); if (unProjected.Any()) { break; } } // This shouldn't happen. But if it does, return the original value and hope the caller doesn't explode. if (!unProjected.Any()) { Log.Write("debug", "Failed to clamp map cell {0} to map bounds", uv); return(uv); } } return(projected.V == Bounds.Bottom ? unProjected.MaxBy(x => x.V) : unProjected.MinBy(x => x.V)); }
public bool ShroudObscures(MPos uv) { return(RenderPlayer != null && !RenderPlayer.Shroud.IsExplored(uv)); }
public MPos Clamp(MPos uv) { var bounds = new Rectangle(Bounds.X, Bounds.Y, Bounds.Width - 1, Bounds.Height - 1); return(uv.Clamp(bounds)); }