public MaskData BitwiseAnd(MaskData other) { Rectangle bounds = Rectangle.Intersect(this.Bounds, other.Bounds); if (bounds.Width == 0 || bounds.Height == 0) { return(new MaskData()); } int localStartX = bounds.X; int localStartY = bounds.Y; int localEndX = bounds.X + bounds.Width; int localEndY = bounds.Y + bounds.Height; MaskData result = new MaskData(bounds); for (int y = localStartY; y < localEndY; y++) { for (int x = localStartX; x < localEndX; x++) { result[x, y] = this[x, y] & other[x, y]; } } return(result.LazyCopyAutoCrop()); }
/// <summary>Set an area to a flat height, relative to the given height of the input mask</summary> public void SetFlatRelative(MaskData maskData, byte height, int offset) { var baseMask = maskData.Translated(0, -(int)height); // Convert a "top" mask int absoluteHeight = Math.Max(byte.MinValue, Math.Min(byte.MaxValue, (int)height + offset)); SetFromFlatBaseMask(baseMask, (byte)absoluteHeight); }
private static void CheckPoint(MaskData data, Stack <Point> points, int x, int y, bool setTo) { if (data[x, y] != setTo) { points.Push(new Point(x, y)); data[x, y] = setTo; } }
public static void DrawPixel(ref MaskData data, int x, int y, bool setTo) { if (setTo) { data = data.LazyCopyExpandToContain(new Rectangle(x, y, 1, 1)); } if (data.Bounds.Contains(x, y)) { data[x, y] = setTo; } }
// NOTE: Rendering from world heightmap is slow!! // NOTE: Much copy-pasted code from normal heightmap rendering public void DrawWorldPhysicsXZRegion(WorldPhysics worldPhysics, MaskData xzMask, SortedList <int, Color> heightColorGradient) { // Constrain rendering to the display bounds int startX = Math.Max(worldPhysics.StartX, displayBounds.Left); int endX = Math.Min(worldPhysics.EndX, displayBounds.Right); if (endX <= startX) { return; // Off screen } for (int z = worldPhysics.EndZ - 1; z >= worldPhysics.StartZ; z--) // From back to front of heightmap { for (int x = startX; x < endX; x++) { if (!xzMask.GetOrDefault(x, z)) // TODO: Fix up bounds so we never leave this area (and use regular [,] access) - PERF { continue; } int height = worldPhysics.GetGroundHeightAt(x, z, WorldPhysics.MaximumHeight, WorldPhysics.MaximumHeight, null); if (height == WorldPhysics.MaximumHeight) { continue; } int nextHeight = 0; // Height of the next Z-value (row) if (xzMask.GetOrDefault(x, z - 1)) { nextHeight = worldPhysics.GetGroundHeightAt(x, z - 1, WorldPhysics.MaximumHeight, WorldPhysics.MaximumHeight, null); } if (nextHeight != WorldPhysics.MaximumHeight && nextHeight > height) { continue; // Next row will cover this one entirely } // Draw top surface const int zTestOffset = 1; // <- The top surface should be "under" any other pixels DrawPixel(new Position(x, height, z), heightColorGradient.GetColorFromGradient(height), zTestOffset); if (nextHeight != WorldPhysics.MaximumHeight && nextHeight == height) { continue; // Next row covers this one's "solid" section } // Draw solidness for (int h = height - 1; h >= 0; h--) { Color c = Color.Lerp(heightColorGradient.GetColorFromGradient(h), Color.Black, 0.6f); DrawPixel(new Position(x, h, z), c); } } } }
public MaskData MakeFlippedX() { MaskData flipped = new MaskData(Bounds.FlipXIndexable()); for (int y = StartY; y < EndY; y++) { for (int x = StartX; x < EndX; x++) { flipped[-x, y] = this[x, y]; } } return(flipped); }
public static Data2D <Color> CreateColorData(this MaskData mask, Color color) { var data = new Data2D <Color>(mask.Bounds); for (var y = data.StartY; y < data.EndY; y++) { for (var x = data.StartX; x < data.EndX; x++) { data[x, y] = mask[x, y] ? color : Color.Transparent; } } return(data); }
public static void DrawLine(ref MaskData data, int x1, int y1, int x2, int y2, bool setTo) { data = data.LazyCopyExpandToContain(Rectangle.Union(new Rectangle(x1, y1, 1, 1), new Rectangle(x2, y2, 1, 1))); int x = x1; int y = y1; int dx = x2 - x1; int dy = y2 - y1; int x_inc = (dx < 0) ? -1 : 1; int l = System.Math.Abs(dx); int y_inc = (dy < 0) ? -1 : 1; int m = System.Math.Abs(dy); int dx2 = l << 1; int dy2 = m << 1; if ((l >= m)) { int err_1 = dy2 - l; for (int i = 0; i < l; i++) { data[x, y] = setTo; if (err_1 > 0) { y += y_inc; err_1 -= dx2; } err_1 += dy2; x += x_inc; } } else { int err_1 = dx2 - m; for (int i = 0; i < m; i++) { data[x, y] = setTo; if (err_1 > 0) { x += x_inc; err_1 -= dy2; } err_1 += dx2; y += y_inc; } } data[x, y] = setTo; }
public void SetBitwiseOrFrom(MaskData other) { int localStartX = System.Math.Max(this.StartX, other.StartX); int localStartY = System.Math.Max(this.StartY, other.StartY); int localEndX = System.Math.Min(this.EndX, other.EndX); int localEndY = System.Math.Min(this.EndY, other.EndY); for (int y = localStartY; y < localEndY; y++) { for (int x = localStartX; x < localEndX; x++) { this[x, y] |= other[x, y]; } } }
/// <summary>Set an area to a flat height given a mask of the base of the object</summary> public void SetFromFlatBaseMask(MaskData maskData, byte height) { // Ensure that there's enough room in the heightmap to contain mask... heightmapData = heightmapData.LazyCopyExpandToContain(maskData.Bounds, DefaultHeight); for (int y = maskData.StartY; y < maskData.EndY; y++) { for (int x = maskData.StartX; x < maskData.EndX; x++) { if (maskData[x, y]) { heightmapData[x, y] = height; } } } }
public void SetBitwiseAndFromMustBeContained(MaskData other) { Debug.Assert(other.Bounds.Contains(this.Bounds)); int localStartX = StartX; int localStartY = StartY; int localEndX = EndX; int localEndY = EndY; for (int y = localStartY; y < localEndY; y++) { for (int x = localStartX; x < localEndX; x++) { this[x, y] &= other[x, y]; } } }
/// <summary>Set from a 1px deep alpha mask (such as for a railing)</summary> public void SetFromRailingMask(MaskData maskData) { heightmapData = heightmapData.LazyCopyExpandToContain(new Rectangle(maskData.OffsetX, 0, maskData.Width, 1), DefaultHeight); for (int x = maskData.StartX; x < maskData.EndX; x++) // For each column in the image { for (int y = maskData.EndY - 1; y >= maskData.StartY; y--) // Search top-to-bottom { if (maskData[x, y]) { heightmapData[x, 0] = (byte)y; goto nextColumn; } } nextColumn: ; } }
/// <summary>Convert color data to a trimmed 1-bit mask</summary> public static MaskData CreateMask(this Data2D <Color> data, Color color, bool inverse = false) { Rectangle trimBounds = data.FindTrimBounds(color, inverse); // TODO: Avoid this copy... Data2D <Color> trimData = data.CopyWithNewBounds(trimBounds); MaskData mask = new MaskData(trimData.Bounds); for (int y = trimData.StartY; y < trimData.EndY; y++) { for (int x = trimData.StartX; x < trimData.EndX; x++) { mask[x, y] = ((trimData[x, y] == color) != inverse); } } return(mask); }
/// <summary> /// Copy the data from one region to another (without moving it). /// Any existing data outside the new boundary is lost. /// </summary> public MaskData CopyWithNewBounds(Rectangle newBounds) { MaskData copy = new MaskData(newBounds); int startX = System.Math.Max(copy.StartX, this.StartX); int startY = System.Math.Max(copy.StartY, this.StartY); int endX = System.Math.Min(copy.EndX, this.EndX); int endY = System.Math.Min(copy.EndY, this.EndY); for (int y = startY; y < endY; y++) { for (int x = startX; x < endX; x++) { copy[x, y] = this[x, y]; } } return(copy); }
public int GetHeightByWalkingObliqueForward(MaskData maskData, int frontEdgeDepth, Oblique oblique, int x, int y) { // Try to walk forward in the mask to find the front edge, from which we have a specified depth, and can calculate the height while (true) { // NOTE: Don't need to do special handling of "upright" sections, because our caller works top-down through the image // (So these sections will be overwritten as appropriate) int nextX = x - (int)oblique; // Walk forward (down the mask) in the oblique direction int nextY = y - 1; if (!maskData.GetOrDefault(nextX, nextY)) // Reached the front of the mask data { return(y - frontEdgeDepth); } x = nextX; y = nextY; } }
/// <param name="slope">Number of pixels to travel backwards before traveling in the oblique direction</param> public void SetFromFrontEdge(MaskData maskData, int frontEdgeDepth, int depth, Oblique obliqueDirection, int slope, int offset) { Debug.Assert(depth > 0); Debug.Assert(slope > 0); // How far do we travel on the X axis as we go backwards? int pixelsTraveledSideways = ((depth + slope - 1) / slope - 1) * (int)obliqueDirection; int outputStartX = Math.Min(maskData.StartX, maskData.StartX + pixelsTraveledSideways); int outputEndX = Math.Max(maskData.EndX, maskData.EndX + pixelsTraveledSideways); // Ensure that there's enough room in the heightmap to contain the maximum extents of the processed mask... Rectangle outputPotentialBounds = new Rectangle(outputStartX, frontEdgeDepth, outputEndX - outputStartX, depth); heightmapData = heightmapData.LazyCopyExpandToContain(outputPotentialBounds, DefaultHeight); // Read the mask upwards to find the "lip" of the mask surface for (int x = maskData.StartX; x < maskData.EndX; x++) // For each column in the image { for (int y = maskData.StartY; y < maskData.EndY; y++) // Search from bottom-to-top { if (maskData[x, y]) { // Found the lip at a given Y height, copy it backwards at the given pitch for (int d = 0; d < depth; d++) { int zz = frontEdgeDepth + d; int xx = x + (d / slope) * (int)obliqueDirection; heightmapData[xx, zz] = (byte)(y - frontEdgeDepth + offset); } goto nextColumn; } } nextColumn: ; } }
public static void DrawFloodFill(MaskData data, int x, int y, bool setTo) { // http://csharphelper.com/blog/2014/09/write-a-graphical-floodfill-method-in-c/ if (!data.Bounds.Contains(x, y)) { return; } if (data[x, y] == setTo) { return; } var points = new Stack <Point>(); points.Push(new Point(x, y)); data[x, y] = setTo; while (points.Count > 0) { var pt = points.Pop(); if (pt.X > data.StartX) { CheckPoint(data, points, pt.X - 1, pt.Y, setTo); } if (pt.Y > data.StartY) { CheckPoint(data, points, pt.X, pt.Y - 1, setTo); } if (pt.X < data.EndX - 1) { CheckPoint(data, points, pt.X + 1, pt.Y, setTo); } if (pt.Y < data.EndY - 1) { CheckPoint(data, points, pt.X, pt.Y + 1, setTo); } } }
/// <summary>Set heights from a top-surface mask where the front edge is at a particular depth</summary> /// <param name="maskData">The 1-bit mask representing the top surface</param> /// <param name="frontEdgeDepth">The depth of the front edge of pixels in the mask</param> /// <param name="perspective">Oblique direction that the mask projects backwards towards</param> public void SetFromObliqueTopMask(MaskData maskData, int frontEdgeDepth, Oblique oblique) { // Ensure that there's enough room in the heightmap to contain the maximum extents of the mask Rectangle outputPotentialBounds = maskData.Bounds; outputPotentialBounds.Y = frontEdgeDepth; // The usable Z range = [frontEdgeDepth, frontEdgeDepth + Height) heightmapData = heightmapData.LazyCopyExpandToContain(outputPotentialBounds, DefaultHeight); // Note: Y axis seeks from the top downwards (from back to front) for (int y = maskData.EndY - 1; y >= maskData.StartY; y--) { for (int x = maskData.StartX; x < maskData.EndX; x++) { if (maskData[x, y]) { int height = GetHeightByWalkingObliqueForward(maskData, frontEdgeDepth, oblique, x, y); int z = y - height; heightmapData[x, z] = (byte)height; // (If height overflows... at least it will be obvious) } } } }
// For high-performance mask comparisons: public MaskData CopyAndExpandForBitShift() { if (Width % 32 == 1) { // We can be a direct copy, because bit-shifting 31 times will not shift any data off the end of the image uint[] copyData = (uint[])packedData.Clone(); return(new MaskData(copyData, OffsetX, OffsetY, Width + 31, Height)); } else { // In this case we need to expand because we will eventually shift into a new 32-bit column: int dataWidth = DataWidth; int newDataWidth = dataWidth + 1; Debug.Assert(newDataWidth == MaskData.WidthToDataWidth(Width + 31)); uint[] copyData = new uint[newDataWidth * Height]; for (int y = 0; y < Height; y++) { Array.Copy(packedData, dataWidth * y, copyData, newDataWidth * y, dataWidth); } return(new MaskData(copyData, OffsetX, OffsetY, Width + 31, Height)); } }
public void SetFromObliqueSide(MaskData maskData, Oblique obliqueDirection, int offset) { // Straight and Right use the same input direction (because Straight input does not make sense, but Straight output is ok) int inputReadDirection = 1; int x = maskData.StartX; if (obliqueDirection == Oblique.Left) { inputReadDirection = -1; x = maskData.EndX - 1; } int y; while (x >= maskData.StartX && x < maskData.EndX) { for (y = maskData.StartY; y < maskData.EndY; y++) // read bottom-to-top { if (maskData[x, y]) { goto foundStartPosition; } } x += inputReadDirection; } // No data found! return; foundStartPosition: // Ensure that there's enough room in the heightmap to contain the maximum extents of the processed mask... { int left, right; if (inputReadDirection == 1) { left = x; right = maskData.EndX - 1; } else // reading right-to-left { left = maskData.StartX; right = x; } // Account for offset: left += Math.Min(offset, 0); right += Math.Max(offset, 0); int front = y; int back = front + (right - left); // can move back one pixel for each column of input Rectangle outputPotentialBounds = new Rectangle(left, front, (right - left) + 1, (back - front) + 1); heightmapData = heightmapData.LazyCopyExpandToContain(outputPotentialBounds, DefaultHeight); } // Convert mask to heightmap: int writeX = x; int writeZ = y; int baseY = y; while (x >= maskData.StartX && x < maskData.EndX) // For each column to end of image { y = baseY; // Count pixels from base upwards while (y < maskData.EndY && maskData[x, y]) { y++; } int height = y - baseY; if (height > 0) { int i = 0; do { heightmapData[writeX + i * Math.Sign(offset), writeZ] = (byte)Math.Min(byte.MaxValue, height); i++; } while(i < Math.Abs(offset)); } // Move input: x += inputReadDirection; baseY++; // Move output: writeX += (int)obliqueDirection; writeZ++; } }
/// <summary>Set an area to a flat height given a mask of the top of the object.</summary> public void SetFromFlatTopMask(MaskData maskData, byte height) { // Just convert it to a "base" mask and use that... SetFromFlatBaseMask(maskData.Translated(0, -(int)height), height); }
public TransformedMaskData(MaskData maskData, bool flipX) { this.maskData = maskData; this.flipX = flipX; }