I was originally using a 2d Array of "Tile"s to store a procedural generated map with its various contents.
Each Tile contains a List adjacent which allows every single tile to know which vertices are closest to it, touching it on 8 different sides (straight adjacent and diagonally adjacent).
The general idea was taken from Amit's polygonal map generation, but I attempted to simplify it by using a grid setup instead of voronois, however I've run into more trouble than I originally thought would be possible. My current predicament is figuring out adjacency when I've scrapped 2d Arrays.
This is how I was doing it before changing to a list:
private void ConstructAdjacencyList() {
// Create Adjacency List
for (int x = 0; x < mapWidth; x++) {
for (int y = 0; y < mapHeight; y++) {
// Bool to find position of point
bool omitLeft = false; bool omitRight = false;
bool omitTop = false; bool omitBottom = false;
// Enable bools based on position, reset on each loop
if (x == 0)
omitLeft = true;
else if (x == mapWidth - 1)
omitRight = true;
if (y == 0)
omitTop = true;
else if (y == mapHeight - 1)
omitBottom = true;
// Add entries to list based on bool settings
if (!omitLeft) {
// Left center
islandMap[x,y].adjacent.Add(islandMap[x-1,y]);
if (!omitTop)
islandMap[x,y].adjacent.Add(islandMap[x-1,y-1]);
if (!omitBottom)
islandMap[x,y].adjacent.Add(islandMap[x-1,y+1]);
}
if (!omitTop) // Top Center
islandMap[x,y].adjacent.Add(islandMap[x,y-1]);
if (!omitBottom) // Bottom Center
islandMap[x,y].adjacent.Add(islandMap[x,y+1]);
if (!omitRight) {
// Right Center
islandMap[x,y].adjacent.Add(islandMap[x+1,y]);
if (!omitTop)
islandMap[x,y].adjacent.Add(islandMap[x+1,y-1]);
if (!omitBottom)
islandMap[x,y].adjacent.Add(islandMap[x+1,y+1]);
}
}
} // End Adjacency
Debug.Log ("Adjacencies Built");
}
The x, y values now are held in islandMap.point (A Vector 2d storing the x and y values generated as follows:)
public MapController() {
width = height = (int)Mathf.Sqrt (tileCount);
// Lists for points
var points = new List<Vector2>();
// Build a random set of points.
for (float x = 0; x < width; x++) {
for (float y = 0; y < height; y++) {
points.Add(new Vector2(x,y));
}
}
map = new Map (points, width, height, lakeTreshold);
}
And the Map itself has the following currently:
public class Map {
Func<Vector2, bool> inside; // Contains function to randomly seed area
bool needsMoreRandomness;
public List<Tile> islandMap; // Previously was Tile[,] islandMap
public int mapWidth { get; private set; } // Calculated as Sqrt(totalPoints)
public int mapHeight { get; private set; }
Along with other methods such as the ConstructAdjacencyList() method I'm currently stuck on.
So how can I go on about constructing an adjacency list of surrounding points without relying on array positioning? Could I temporarily reference the entire list from an array, place references to each tile in the entire list in this 2d array, setup adjacencies and then remove the array without losing the information? I believe it would only use references, so it should be fine... Each tile contains an index to store the order with which it was built like so:
foreach (var point in points) {
var p = new Tile { index = islandMap.Count, point = point };
p.border = point.x == 0 || point.x == mapWidth || point.y == 0 || point.y == mapHeight;
islandMap.Add (p);
tileLookup[point] = p;
}
Sorry if this is too long... I just realized it's quite massive -.-
Assuming you haven't done anything to mess with the order of the points, you can treat a 1D list/array as 2D list/array. The example I linked is in C, but the idea language agnostic.
That being said, assuming that you're only going to do this once during the initialization & given the relatively low number of points, you could just as easily brute force it with a function that loops through the point list & picks out the neighbors as needed. My C# is a bit rusty, but I'm talking about something like this:
private List<Vector2> getAdjacenctPointList(List<Vector2> pointsList, Vector2 point){
var adjacencyList = new List<Vector2>();
foreach (var pt in pointList){
var offset = Math.abs(pt.x - point.x) + Math.abs(pt.y - point.y);
if(offset > 0 && offset <= 1.0){
adjacencyList.add(pt);
}
}
return adjacencyList;
}
The final answer I went with which was the least amount of work involved and allowed me to recycle my original 2d Array code posted above was to simply construct a temporary 2d array and store all the references to it in it - create all adjacencies as necessary and then dispose (simply by losing scope) of the 2d Array.
Here's the actual method with the 2d array system in place. First couple statements followed by the nested for loop was all it took:
private void ConstructAdjacencyList() {
Tile[,] tempArray = new Tile[mapWidth, mapHeight];
int count = 0;
// Populate the temp 2D array with list references
for (int x = 0; x < mapWidth; x++) {
for (int y = 0; y < mapHeight; y++) {
tempArray[x,y] = islandMap[count];
count++;
}
}
// Create Adjacency List using our TempArray
for (int x = 0; x < mapWidth; x++) {
for (int y = 0; y < mapHeight; y++) {
// Bool to find position of point
bool omitLeft = false; bool omitRight = false;
bool omitTop = false; bool omitBottom = false;
// Enable bools based on position, reset on each loop
if (x == 0)
omitLeft = true;
else if (x == mapWidth - 1) // Optimize with if else to split checks in half.
omitRight = true;
if (y == 0)
omitTop = true;
else if (y == mapHeight - 1)
omitBottom = true;
// Add entries to list based on bool settings
if (!omitLeft) {
// Left center
tempArray[x,y].adjacent.Add(tempArray[x-1,y]);
if (!omitTop)
tempArray[x,y].adjacent.Add(tempArray[x-1,y-1]);
if (!omitBottom)
tempArray[x,y].adjacent.Add(tempArray[x-1,y+1]);
}
if (!omitTop) // Top Center
tempArray[x,y].adjacent.Add(tempArray[x,y-1]);
if (!omitBottom) // Bottom Center
tempArray[x,y].adjacent.Add(tempArray[x,y+1]);
if (!omitRight) {
// Right Center
tempArray[x,y].adjacent.Add(tempArray[x+1,y]);
if (!omitTop)
tempArray[x,y].adjacent.Add(tempArray[x+1,y-1]);
if (!omitBottom)
tempArray[x,y].adjacent.Add(tempArray[x+1,y+1]);
}
}
} // End Adjacency
Debug.Log ("Adjacencies Built");
}
To ensure this worked out as I wanted it to, I tested it visually by setting one random cube and tested various points all around the area to ensure there were no mistakes and there weren't.
The end result is as expected, depicted below:
Thanks for the help :)
Related
i have somewhat implemented marching cubes in unity/c# (you dont need to know unity to help me though) and i cant stop feeling like i have made a big mistake in my code because it is so slow. i am already running it on a separate thread but it just takes ages to complete. please help me optimize my code.
private void _UpdateChunk()
{
lock (this)
{
// clear the tri, vert and uv lists
ClearMeshData();
// Loop through each "cube" in the terrain.
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
for (int z = 0; z < width; z++)
{
// Create an array of floats representing each corner of a cube and get the value from our terrainMap.
float[] cube = new float[8];
float[] strengths = new float[8];
for (int i = 0; i < 8; i++)
{
Vector3Int corner = new Vector3Int(x, y, z) + gamedata.CornerTable[i];
cube[i] = terrainMap[corner.x, corner.y, corner.z].BlockType;
strengths[i] = terrainMap[corner.x, corner.y, corner.z].Strength;
}
// Pass the value into the MarchCube function.
MarchCube(new Vector3(x, y, z), cube, strengths);
}
}
}
}
}
void MarchCube(Vector3 position, float[] cube, float[] strengths)
{
// Get the configuration index of this cube.
int configIndex = GetCubeConfiguration(cube);
// If the configuration of this cube is 0 or 255 (completely inside the terrain or completely outside of it) we don't need to do anything.
if (configIndex == 0 || configIndex == 255)
return;
// Loop through the triangles. There are never more than 5 triangles to a cube and only three vertices to a triangle.
int edgeIndex = 0;
Vector3 vert1 = new Vector3();
Vector3 vert2 = new Vector3();
float vert1sample = 0;
float vert2sample = 0;
float lerp = 0;
int indice = 0;
for (int i = 0; i < 5; i++)
{
for (int p = 0; p < 3; p++)
{
// Get the current indice. We increment triangleIndex through each loop.
indice = gamedata.TriangleTable[configIndex, edgeIndex];
// If the current edgeIndex is -1, there are no more indices and we can exit the function.
if (indice == -1)
return;
// Get the vertices for the start and end of this edge.
vert1 = position + gamedata.EdgeTable[indice, 0];
vert2 = position + gamedata.EdgeTable[indice, 1];
vert1sample = strengths[gamedata.EdgeIndexTable[indice, 0]];
vert2sample = strengths[gamedata.EdgeIndexTable[indice, 1]];
// Get the midpoint of this edge.
lerp = Mathf.Abs(vert1sample) / (Mathf.Abs(vert2sample) + Mathf.Abs(vert1sample));
Vector3 vertPosition = Vector3.Lerp(vert1, vert2, lerp);
// Add to our vertices and triangles list and incremement the edgeIndex.
vertices.Add(vertPosition);
triangles.Add(vertices.Count - 1);
if (getChunkVoxel(vert1 + chunkPosition) != 0)
{
uvs.Add(new Vector2(getChunkVoxel(vert1 + chunkPosition) - 1, 0));
}
else
{
uvs.Add(new Vector2(getChunkVoxel(vert2 + chunkPosition) - 1, getChunkVoxel(vert2 + chunkPosition) - 1));
}
edgeIndex++;
}
}
}
int GetCubeConfiguration(float[] cube)
{
// Starting with a configuration of zero, loop through each point in the cube and check if it is below the terrain surface.
int configurationIndex = 0;
for (int i = 0; i < 8; i++)
{
// If it is, use bit-magic to the set the corresponding bit to 1. So if only the 3rd point in the cube was below
// the surface, the bit would look like 00100000, which represents the integer value 32.
if (cube[i] < terrainSurface)
configurationIndex |= 1 << i;
}
return configurationIndex;
}
it appears that this is the part that slows my game down, help would be appreciated
i already made it faster by changing terrainpoint from a class to a struct but it is still very slow.
One main reason it is slow is that there is a lot of allocations in the loop putting a lot of pressure on the garbadge collector. There is currently 11 allocation per "cube" in the terrain in _UpdateChunk and up to 17 in MarchCube (possibly even more if the expressions like position + gamedata.EdgeTable[indice, 0] allocates a new vector). This is not reasonable. Many allocation are not needed. For example cube and strengths can be preallocated once for all the cubes in the beginning of _UpdateChunk. You do not need to allocate the vector in the expression to compute corner: you can just compute the components separately manually (or you can possibly preallocate the vector and reset its component when needed). The same thing applies for the new Vector3(x, y, z) can can be preallocated and set in the loop. Such an algorithm is computationally intensive so you should get away any overhead like virtual method calls and allocations/GC -- only low-level arrays accesses and mathematical operations should remains.
Note that some computations can be optimized. For example GetCubeConfiguration can be modified so to be branchless. Mathf.Abs(vert1sample) can be precomputed so not to compute it twice (though the compiler may already do that). I am also wondering if the expression like vertices.Add are efficient but this is dependent of the type of container which is not provided here.
The title says it all. I'm trying to make a collision with the help of the per pixel collision method that I have work for all my enemies. My method works fine with if im not using a list. Here's the code im using:
public bool IntersectPixels(
Matrix transformA, int widthA, int heightA, Color[] dataA,
Matrix transformB, int widthB, int heightB, Color[] dataB)
{
// Calculate a matrix which transforms from A's local space into
// world space and then into B's local space
Matrix transformAToB = transformA * Matrix.Invert(transformB);
// For each row of pixels in A
for (int yA = 0; yA < heightA; yA++)
{
// For each pixel in this row
for (int xA = 0; xA < widthA; xA++)
{
// Calculate this pixel's location in B
Vector2 positionInB =
Vector2.Transform(new Vector2(xA, yA), transformAToB);
// Round to the nearest pixel
int xB = (int)Math.Round(positionInB.X);
int yB = (int)Math.Round(positionInB.Y);
// If the pixel lies within the bounds of B
if (0 <= xB && xB < widthB &&
0 <= yB && yB < heightB)
{
// Get the colors of the overlapping pixels
Color colorA = dataA[xA + yA * widthA];
Color colorB = dataB[xB + yB * widthB];
// If both pixels are not completely transparent,
if (colorA.A != 0 && colorB.A != 0)
{
// then an intersection has been found
return true;
}
}
}
}
// No intersection found
return false;
}
To check the collision I use this bit of code:
public bool Update(Matrix PersonTransform2,Color[] data2)
{
personTransform1 = Matrix.CreateTranslation(new Vector3(new Vector2(position.X, position.Y), 0.0f));
if (game.IntersectPixels(PersonTransform2, texture.Width, texture.Height, data2,
PersonTransform1, texture.Width, texture.Height, data1))
{
return true;
}
return false;
}
My question here is how I can transform this bit of code to be able to work with a list.
You could add an overload and use ToArray() from Linq:
public bool Update(Matrix personTransform2, List<Color> data2)
{
Update(Matrix personTransform2, data2.ToArray())
}
but because of performance (the above creates a copy) I would probably replace the Color[] by List<Color> in both signatures.
On the other end, for these kind of calculations you probably shouldn't be using the List<Color> to start with. If you care about performance work with array's directly.
Mistakes were made... I forgot to put a foreach loop that went through all my list... The code was:
for(int i = 0; i < lista.Count; i++)
{
if (lista[i].Update(blockTransform, data2))
{
touched = true;
}
}
I need to find out the front measure of chest for any individual using Kinect while facing the camera. My current solution is:
When a MultiFrameSource arrives get the color (to display the body in the ui) body (to get the Joints), and bodyIndex frames.
copy the BodyIndexFrame to an byte[] _bodyData by using:
bodyIndexFrame.CopyFrameDataToArray(_bodyData);
I get the Joint objects for: spineShoulder and spineMid. I have assumed that the chest will always be between those points.
I convert both Joints to CameraSpacePoint (x,y,z) and from CameraSpacePoint to DepthSpacePoint (x,y) by using
_sensor.CoordinateMapper.MapCameraPointToDepthSpace(jointPosition);
I still keep a reference to the z value of spineShoulder.
Second assumption => Starting from spineShoulderY to spineMidY I try to find the widest point which is in the player area. in order to do so I will try to find the longest segment between spineShoulderX and the first left region found which does not belong to the player and the longest segment between spineShoulderX and first right side region found which does not belong to the player. Both x segments found must be in the same y coordinate.
/***************
* Returns the distance between 2 points
*/
private static int getDistanceToMid(int pointX, int midX)
{
if (midX > pointX)
{
return (midX - pointX);
}
else if (pointX > midX)
{
return (pointX - midX);
}
else
{
return 0;
}
}
/*********
* Loops through the bodyData array
* It will look for the longest x distance from midX to the last left x value
* which still belongs to a player in the y coordinate
*/
private static int findFarLeftX(byte[] bodyData,int depthWidth, int midX, int y)
{
int farLeftX = -1;
for (int x = midX; x >= 0; --x)
{
int depthIndex = (y * depthWidth) + x;
if (depthIndex > 0 && depthIndex < bodyData.Length)
{
byte player = bodyData[depthIndex];
if (player != 0xff){
if (farLeftX == -1 || farLeftX > x)
{
farLeftX = x;
}
} else{
return farLeftX;
}
}
}
return farLeftX;
}
/*********
* Loops through the bodyData array
* It will look for the longest x distance from midX to the last right x value
* which still belongs to a player in the y coordinate
*/
private static int findFarRightX(byte[] bodyData, int depthWidth, int midX, int y)
{
int farRightX = -1;
for (int x = midX; x < depthWidth; ++x)
{
int depthIndex = (y * depthWidth) + x;
if (depthIndex > 0 && depthIndex < bodyData.Length)
{
byte player = bodyData[depthIndex];
if (player != 0xff)
{
if (farRightX == -1 || farRightX < x)
{
farRightX = x;
} else{
return farRightX;
}
}
}
}
return farRightX;
}
private static BodyMember findElement(byte[] bodyData, int depthHeight, int depthWidth, int startX, int startY, int endY)
{
BodyMember member = new BodyMember(-1, -1, -1, -1);
int totalMaxSum = 0;
int farLeftX = -1;
int farRightX = -1;
int selectedY = -1;
for (int y = startY; y < depthHeight && y <= endY; ++y)
{
int leftX = findFarLeftX(bodyData, depthWidth, startX, y);
int rightX = findFarRightX(bodyData, depthWidth, startX, y);
if (leftX > -1 && rightX > -1)
{
int leftToMid = getDistanceToMid(leftX, startX);
int rightToMid = getDistanceToMid(rightX, startX);
int sum = leftToMid + rightToMid;
if (sum > totalMaxSum)
{
totalMaxSum = sum;
farLeftX = leftX;
farRightX = rightX;
selectedY = y;
}
}
}
member.setFarLeftX(farLeftX);
member.setFarLeftY(selectedY);
member.setFarRightX(farRightX);
member.setFarRightY(selectedY);
return member;
}
findElement will return a BodyMember object which contains farLeftX, farRightX, farLeftY and farRightY.
I create 2 DepthSpacePoint objects:
DepthSpacePoint chestX1 = new DepthSpacePoint();
chestX1.X = bodyMemberObj.getFarLeftX();
chestX1.Y = bodyMemberObj.getFarLeftY();
DepthSpacePoint chestX2 = new DepthSpacePoint();
chestX2.X = bodyMemberObj.getFarRightX();
chestX2.Y = bodyMemberObj.getFarRightY();
In order to get real world coordinates in meters these points must be converted to CameraSpacePoint object. In order to do so I will use the joint's z value that I kept a reference to back in point 4.
CameraSpacePoint chestLeft = _sensor.CoordinateMapper.MapDepthPointToCameraSpace(chestX1,spineShoulderZ);
CameraSpacePoint chestRight = _sensor.CoordinateMapper.MapDepthPointToCameraSpace(chestX1,spineShoulderZ);
Now,If my code and assumptions are right I should be able to get the correct distance in meters for the front chest.
double chestLength = (chestLeft.X > chestRight.X) ? chestLeft - chestRight : chestRight - chestLeft;
However this does not seem to be returning the correct values. I have been looking into a solution for this during weeks but I seem to be stuck.
I have worked with Kinect V2, and can say that skeleton data alone wild be insufficient to get reliable results. Even clothes has impact how Kinect interprets body parts, so you will have to combine results from other sensors data.
Additionally I suggest you to be creative about how you approach this,
for example, you could investigate some possible anatomic correlations about human body, most likely height is a proxy indicator, maybe age from face-recognition and height is another hint, etc.
Are you using using kinect One? Measurements in the old kinects are probably not accurate enough to fit your requirements.
However, from my point of view it would be also worth to do extraction of the moving object inside the frames (if there is only one person in front of the Kinect you will receive this person's contour, but to be sure you can compare its position with Kinect skeleton).
You can tell person to rise their hands for a short period of time. Than use Kinect distance to the persons skeleton and based on the size of the contour and given distance - calculate your final chest measurement just below the shoulders (position taken from the skeleton). I'm also not fully aware how accurately Kinect handles "bigger" people.
Here is described one of the methods: http://onlinelibrary.wiley.com/doi/10.1002/scj.10237/abstract You can also google many other papers with given subject (for free).
What do you think about this idea?
Cheers
I'm struggling to set the values of an element at a specific index within a list to a new value.
The list is of type object Rectangle and i get the following error when I try change any of the values of the rectangle in the list e.g.
Property or indexer 'System.Drawing.Rectangle.Bottom' cannot be
assigned to -- it is read only
I've tried converting the list to an array but i still run into the same issue of the values being read-only.
Basically the app takes in a user defined number of rectangles and draws the rectangles with varying widths and heights but along the same baseline. The code im trying to implement needs to take those rectangles and redraw them vertically from the base upwards, while keeping the same number of rectangles and keeping the same outer shape as the previous rectangles created.
Code:
public void RotateRectangles(List<Rectangle> Rectangles, int startPositionX, int userInput, Graphics DrawingSurface)
{
Graphics RectangleGraphics = DrawingSurface;
try
{
// loop in reverse to compare one rectangle to all the other rectangles in the vector
for (int i = Rectangles.Count - 1; i > -1; --i)
{
bool mustChange = true;
for (int t = Rectangles.Count - 1; t > -1; --t)
{
// only compare if the current position in the vector A if different to the position in vector B.
if (i > t)
{
if (mustChange == true)
{
// If the top Y coordinate of RECT at Position i in vector A is bigger than Y coordinate
// at Position t in vector B
if (Rectangles[i].Top >= Rectangles[t].Top)
{
//adjusting points accordingly
Rectangles[i].Left = (Rectangles[t].Left);
Rectangles[t].Bottom = (Rectangles[i].Top);
}
else
{
// If the Y coordinate is not bigger, then we need to stop checking
mustChange = false;
}
}
}
}
}
// loop forward to compare one rectangle to all the other rectangles in the vector
for (int i = 0; i < Rectangles.Count; ++i)
{
bool forwardChange = true;
for (int t = 0; t < Rectangles.Count; ++t)
{
// If the top Y coordinate of RECT at Position i in vector A is bigger than Y coordinate at Position t
// in vector B AND the two rectangales touch
if (i < t && Rectangles[i].Top <= Rectangles[t].Bottom)
{
if (forwardChange == true)
{
// If the top Y coordinate of RECT at Position i in vector A is bigger than Y coordinate at Position t
// in vector B
if (Rectangles[i].Top > Rectangles[t].Top)
{
//adjusting points accordingly
Rectangles[i].Right = (Rectangles[t].Right);
Rectangles[t].Bottom = (Rectangles[i].Top);
}
else
{
// If the Y coordinate is not bigger, then we need to stop checking
forwardChange = false;
// Addjust the Y position of each rectangle so it does not overlap with the first drawing
for (int z = 0; z < Rectangles.Count; ++z)
{
Rectangles[z].Top = (250 - Rectangles[z].Top);
Rectangles[z].Bottom = (250 - Rectangles[z].Bottom);
}
}
}
}
}
}
for (int z = 0; z < Rectangles.Count; ++z)
{
Rectangle DrawRec = myRectangleClass.MyRectangle(Rectangles[z].Left, Rectangles[z].Top, Rectangles[z].Right, Rectangles[z].Bottom);
RectangleGraphics.DrawRectangle(Pen, DrawRec);
ReadWrite.writeOutput(Rectangles[z].Left, Rectangles[z].Top, Rectangles[z].Right, Rectangles[z].Bottom);
}
}
catch (Exception e)
{
}
}
The parts that are giving the errors are:
Rectangles[i].Left = (Rectangles[t].Left);
Rectangles[t].Bottom = (Rectangles[i].Top);
Rectangles[i].Right = (Rectangles[t].Right);
Rectangles[t].Bottom = (Rectangles[i].Top);
Rectangles[z].Top = (250 - Rectangles[z].Top);
Rectangles[z].Bottom = (250 - Rectangles[z].Bottom);
Please can someone help me out or at least direct me in the right direction
The properties Right, Left, Top, Bottom are read-only. You can use the methods Offset and Inflate, and the properties Location, Size, Width and Height, to adjust the position and size of the rectangle, or you can replace an existing rectangle with a newly created one.
e.g.
Rectangle ri = Rectangles[i];
Rectangle rt = Rectangles[t];
Rectangle[i] = new Rectangle( rt.Left, ri.Bottom, rt.Height, rt.Width );
the problem is less the value type of the rectangle, more that the List[] operator returns a new value object.
List<Rectangle> a;
a[4].X += 4; // does the same like the following code:
var r = a[4]
r.X += 4; // will change r, but not a[4]
so you need to store back the rectangle value in the list
Rectangles[i] = new Rectangle(Rectangles[t].Left, Rectangles[i].Top, Rectangles[i].Width, Rectangles[i].Height);
Assign a new rectangle to the wanted index.
I have a problem: I need an algorithm for my tile engine.
I have an 2d-array which stores my un-walkable tiles.
Now I want to implement a light engine, but this engine need shadow-hulls.
So I need an algorithm which will create these shadow-hulls.
I need a set of rectangles that bound the un-walkable portions of the array (the cells that have 1s)
For example:
The black tile are 1s; I need to find the set of red rectangles that fully enclose them.
After further thought, I've come up with a faster solution:
Create an immutable Range struct with StartX, StartY, and EndY properties.
Maintain a sparse array of current Ranges, with length equal to the height of the image, and a single nullable currentRange variable. This array will contain the range, if any, that starts at each Y coordinate
For each column,
Clear currentRange
For each cell in the column:
If there is no currentRange, or if there is but it ended before this cell (if currentRange.EndY <= y), set currentRange to the y'th element in the ranges array.
Thus, currentRange will always refer to the range that contains the current cell, or null if the current cell is not in a range.
If the current cell is 1:
If we're in a range, do nothing – the range is continuing into the next column.
If we're not in a range, loop down the column until we hit a 0 or the end of a column, then create a new range stretching from the 1 we just found until the end of the loop.
Then, proceed to the next if (since the current cell is now 0 or the end of the column, and we're not in a range)
If you hit an existing range as you loop ahead to create the new range, you can either stop the new range and let the existing range continue below it (best for fuzzy edges), or close the existing range (see below) and let the new range stretch downwards to take over from the existing range.
If the current cell is 0:
If we're in a range, close the range:
Return a new rectangle stretching from the range's start X and Y to the current Y and the range's end X.
Clear the range from the array of active ranges.
This algorithm is O(x * y) in computation and O(y) in space. I believe that this is the fastest solution to the problem.
You can also rotate this algorithm and scan rows rather than columns (so that ranges would be stretched downwards rather than rightwards), which will be O(x) in storage.
Here is a C# implementation:
class BoxFinder {
class Range {
public Range(int startX, int startY, int endY) {
StartX = startX;
StartY = startY;
EndY = endY;
}
public int StartX { get; private set; }
public int StartY { get; private set; }
public int EndY { get; private set; }
}
public BoxFinder(int[,] data) {
Data = data;
Width = data.GetLength(0);
Height = data.GetLength(1);
ranges = new Range[Height];
}
public int[,] Data { get; private set; }
public int Width { get; private set; }
public int Height { get; private set; }
private Range[] ranges;
public IEnumerable<Rectangle> GetBoxes() {
for (int x = 0; x < Width; x++) {
Range currentRange = null;
for (int y = 0; y < Height; y++) {
Func<Range, Rectangle> CloseRange = r => {
currentRange = null;
ranges[r.StartY] = null;
return new Rectangle(
r.StartY,
r.StartX,
x - r.StartX,
r.EndY - r.StartY
);
};
if (currentRange == null || currentRange.EndY <= y)
currentRange = ranges[y];
if (Data[x, y] == 1) {
if (currentRange == null) {
int startY = y;
for (; y < Height && Data[x, y] == 1; y++) {
if (ranges[y] != null)
yield return CloseRange(ranges[y]);
//If there are fuzzy edges, break; instead
}
ranges[startY] = new Range(x, startY, y);
if (y == Height)
continue;
//Otherwise, continue to the next if in case a previous range just ended
}
}
//No else; we can get here after creating a range
if(Data[x, y] == 0) {
if (currentRange != null)
yield return CloseRange(currentRange);
}
}
}
}
}
Try something like this:
Create a list containing every desired point. (In your case, the coordinates of every 1)
For each point in this list:
Loop down the Y axis from this point until you hit an undesired point (a 0)
Loop right along the X axis until you hit an X coordinate which has a 0 at any Y value between the point and the ending Y coordinate you got from step 1.
Add the rectangle you just found to the list of rectangles
Remove every point in the rectangle from the original list.
This probably isn't the fastest way to do this, but it should work.