algorithm to trace border in 2D array - algorithm

I have a 2D array of integers that represent groupings (crystal grains) on a 2D surface. something like this:
(each pixel of that image is assigned an integer depending on the group it belongs to, so every red pixel is assigned 1 for example, every blue is 2)
given an X,Y-coordinate on a border between two such groupings (user clicks there) how can I trace that border between these 2 groupings saving every pixel-coordinate that's along the border and get the two endpoint-coordinates (I'm not concerned with the case of an enclosure where there would be no endpoint, but a loop)
whatever algorithm I come up with seems to be so tedious to implement and I just can't imagine noone has done this before. Any help? Preferably would be a solution in c# but any hint about an algorithm is greatly appreciated.
EDIT:
I should have stated that I was going to implement an algorithm to vectorize that line like this:
between the two endpoints define a line
get the boundary point farthest away from the current line, if it is farther than d, split the line at this point, so there are two line segments
repeat until no boundary point is farther away than d from the line strip
I thought that was easy enough to implement that's why I didn't state it. To get there I just needed the solution to the current problem...
Concerning Questions:
how is the raw data formatted? - it's a simple short[,] labeling; with the size being approximately 250x150 something like this:
11111112222
11111222222
11112222222
11112222222 <- user clicks on leftmost 2 or rightmost 1 -> I want to trace that border
11111122222 down to the first
11111133322 encountered 3 and up to the
11333333333 frame-border
what is an endpoint? - as I had been thinking about global solutions, I could describe an endpoint as: a 2x2 area where the 4 pixels consist of color1, color2 and at least one third different color.
what is contiguously connected? - it doesn't really matter for the rest of the algorithm, see below
what about y-shaped regions? - I'm not concerned with them, you can assume the area of color1 behind the border is at least 2 pixels wide, that's why it also doesn't matter if we talk about a 4 or an 8 neighborhood.
what do I currently have? - at first I tried a 'walking'-algorithm, something like mvds posted, but found me doing stepping, neighborcalculation and checking in all 4 directions which was tedious and looked awful. I didn't find a nice representation for "this is the direction the last step came from, don't check that pixel for neighborhood".
I then abandoned the walking algorithm and tried a global approach (like a filter): for each pixel check if it is color1 AND has color2 in its 4-neighborhood. With this I get all boundaries between color1 and color2. I was going to remove all boundaries that are not connected to the user-clicked-coordinate by some kind of floodfill, but then I got the problem of: where are the endpoints?
I'm still thankful for more input. For now I'll see how far I can go with mvds' algorithm.

I'll assume you have already determined the 2 colours in question, e.g. as 'mvds' described, as a preprocessing step.
I think you'll find it helpful to use a coordinate system where each (x,y) represents not a pixel but the point where 4 pixels touch. Then you can write a function to determine whether North is a boundary pixel-border, likewise for South,East,West (or maybe you prefer the terminology up/down/left/right).
Start at a point on the border, e.g. scan the 4x4 neighbourhood for a point which has one of N/S/E/W as a border. Follow this border to the next point, and then scan all 4 directions other than the direction you came in, for the next pixel border. Repeat until you run out of pixel borders. Then you know you're at one endpoint.
Go back to the beginning and trace the border in a different direction to what you initially took, until you reach the other endpoint.
This gives you all the pixel borders. Each pixel border has colour 1 on one side and colour 2 on the other side.
(I would have thought that the vectorisation would be a lot more difficult than identifying the border, but that's not what your question is mainly about, right? To do that I'd start at an end-point and follow the sequence of pixel borders border by border, at each point checking whether the straight line from the end-point to the current point matches the pixel borders. As soon as it doesn't, that's the end of one line and you start a new line.)

Here's a few thoughts and the start of an algorithm:
Finding the outline of an area of equal color is easier than finding a border, especially when the border is not really "binary" (i.e. only 2 colors exactly) as seemingly the case in your image.
Finding adjoining parts of two outlines is not very complicated. For every point on outline A, find the nearest point of outline B. If distance |A-B| < X, the point halfway between A and B is on the border. (X depends on the fuzzyness of your border)
If you can make your users click twice, at both sides of the border, that would be great. If you insist on one click, find the two biggest areas in a radius of X around the clicked point.
Finding the outline of an area is not complicated:
take a point (x,y) to start, take a direction (dx,dy)=(1,0) to start
take color C of point (x,y) which will be the color to trace
run x+=dx,y+=dy until at (x+dx,y+dy) you have another color and are on a boundary
peek at (x+dx,y+dy), if it is not the same color C, you are hitting a boundary: turn left and goto 4.
x+=dx, y+=dy, i.e. take a step
record (x,y) as part of the boundary
if ( x==xstart && y==ystart ) you're done
turn right and goto 4.
turn left means: (dx',dy') = (dy,-dx), revolutions++
turn right means: (dx',dy') = (-dy,dx), revolutions--
revolutions will end up positive or negative, depending on the trace direction (inside/outside)
There is one corner case in which this loops indefinitely, namely when you start in an area of 1 pixel. This is easily checked. Furthermore you may want to check x/y boundaries. "Same color" and "other color" may of course also be implemented as some kind of color distance limit (i.e. |(r,g,b)-(R,G,B)|<D)
disclaimer this is a working, but simple, slow algorithm I cooked up once without the burden of any relevant knowledge or experience.

Your description isn't a hundred percent clear to me, but if I understand you correctly, you want to compute the following:
the set of contiguously connected points of colour A which are adjacent to a point of colour B
that contains the given starting point.
Given that specification, your code practically writes itself. The only thing you need to decide on now is what "contiguously connected" means (e.g., are pixels adjacent only at their corners connected or not?).
Also, your description is ambiguous. Consider a y-shaped region where the arms of the region are a single pixel wide: this would have three "endpoints" if you define endpoint to mean "member of the set with only one neighbour also in the set". If you relax your requirement to allow any number of endpoints then your code can collect the set of endpoints as it goes.
EDIT
Glad you solved your problem. I sketched out a solution which produces this for your sample problem:
1111***2222
111**222222
111*2222222
111*2222222
111***22222
11111*33322
11333333333
Here's the code, provided only since I need validation for having coded it :-) It's written for clarity rather than speed.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
namespace StackOverflowEdgeDetection
{
class Program
{
private static HashSet<Point> FindBorder(char[,] grid, Point start, char inside, char outside)
{
var border = new HashSet<Point> {};
var endpoints = new HashSet<Point> { start };
while (endpoints.Count != 0)
{
var p = endpoints.First();
endpoints.Remove(p);
border.Add(p);
var newEndpoints = Neighbours(p).Where(q =>
Grid(grid, q) == inside &&
!border.Contains(q) &&
Neighbours(q).Any(r => Grid(grid, r) == outside)
);
endpoints.UnionWith(newEndpoints);
}
return border;
}
private static IEnumerable<Point> Neighbours(Point p)
{
yield return new Point(p.X - 0, p.Y - 1);
yield return new Point(p.X + 1, p.Y - 1);
yield return new Point(p.X + 1, p.Y + 0);
yield return new Point(p.X + 1, p.Y + 1);
yield return new Point(p.X + 0, p.Y + 1);
yield return new Point(p.X - 1, p.Y + 1);
yield return new Point(p.X - 1, p.Y - 0);
yield return new Point(p.X - 1, p.Y - 1);
}
public static char Grid(char[,] grid, Point p) {
var x = p.X;
var y = p.Y;
var height = grid.GetLength(0);
var width = grid.GetLength(1);
return (0 <= x && x < width && 0 <= y && y < height) ? grid[y, x] : '\0';
}
static void Main(string[] args)
{
var border = FindBorder(TestGrid, TestStart, TestInside, TestOutside);
var points = Enumerable.Range(0, TestHeight)
.SelectMany(y => Enumerable.Range(0, TestWidth)
.Select(x => new Point(x, y)));
foreach (var p in points) {
Console.Write(border.Contains(p) ? '*' : Grid(TestGrid, p));
if (p.X + 1 == TestWidth) Console.WriteLine();
}
Console.ReadLine();
}
private static readonly char[,] TestGrid = new char[,] {
{ '1', '1', '1', '1', '1', '1', '1', '2', '2', '2', '2' },
{ '1', '1', '1', '1', '1', '2', '2', '2', '2', '2', '2' },
{ '1', '1', '1', '1', '2', '2', '2', '2', '2', '2', '2' },
{ '1', '1', '1', '1', '2', '2', '2', '2', '2', '2', '2' },
{ '1', '1', '1', '1', '1', '1', '2', '2', '2', '2', '2' },
{ '1', '1', '1', '1', '1', '1', '3', '3', '3', '2', '2' },
{ '1', '1', '3', '3', '3', '3', '3', '3', '3', '3', '3' }
};
private static readonly Point TestStart = new Point(3, 3);
private static readonly Point TestAdjacent = new Point(4, 3);
private static readonly char TestInside = Grid(TestGrid, TestStart);
private static readonly char TestOutside = Grid(TestGrid, TestAdjacent);
private static readonly int TestWidth = TestGrid.GetLength(1);
private static readonly int TestHeight = TestGrid.GetLength(0);
}
}

Nice resources for this stuff here detecting edge pixels with marching squares algorithm and on wikipedia (should that link go away) marching squares algo

Related

Terrain Quadtree LOD can't detect out where T-junctions appears that generates cracks

I implemented a Quadtree that stores a single mesh of data with different LOD levels, where each four children have their own vertices and their corresponding indexes to the four corners(x0, y0, x1, y1 -> from top to bottom) that makes up their corresponding LOD level.
QuadTree.prototype.fillTree = function(currentNode, depth, currentBox, currentIndex) {
if(depth === 0 || !currentBox.checkPartition())
return;
let node = {
"vertices" : [],
"lines" : [],
"children" : [],
"box" : currentBox
};
currentNode.push(node);
currentBox.getVerticesCoord(this.cols, currentIndex).forEach((coord) => {
this.getPlaneVertices(node.vertices, coord);
});
currentBox.getLinesCoord(this.cols, currentIndex).forEach((coord) => {
this.getPlaneVertices(node.lines, coord);
});
currentBox.getPartitions().forEach((box, index) => {
this.fillTree(node["children"], depth-1, box, index);
});
};
I have also a checkFrustumBoundaries method where I calculate the minimum distance between a LOD level and the camera location [0, 0, 0] and also checks if it's visible by being within [-1, 1] range for all the coordinates by being multiplied by the projection matrix and divided by w.
Finally I have the method that selects needed LOD levels for the current state by finding the minimum distance between camera origins and their corresponding depth and all 4 children being checked if they are within the visible zone and store them into an array that will be rendered.
Note: That I want children to inherit the depth of their sibling with lowest Complexity level if he is ready to be rendered, thus I will always have a 4 four square with same LOD level.
QuadTree.prototype.readComplexity = function(projection, viewCamera, currentNode, currentIndex, currentDepth = 1) {
let childrenToBeRendered = [];
let minDepth = this.depth;
currentNode.children.forEach((child, index) => {
let frustumBoundaries = this.checkFrustumBoundaries(child.vertices, projection, viewCamera);
if(frustumBoundaries.withinFrustum) {
//Section is the 1.0/this.depth
let depth = this.depth-Math.ceil(frustumBoundaries.minDistance/this.section);
minDepth = Math.min(minDepth, depth);
childrenToBeRendered.push({
"child" : child,
"index" : index
});
}
});
childrenToBeRendered.forEach((child => {
if(minDepth <= currentDepth) {
//Even complexity, or the others quarters inherits on of their siblings with lowest complexity level
this.fetchLines(projection, viewCamera, child.child, currentDepth, child.index);
} else if(minDepth > currentDepth) {
//Needs more complexity because it's too near to camera origins
this.readComplexity(projection, viewCamera, child.child, child.index, currentDepth+1);
}
}));
};
But here I stumbled on the biggest problem, it appears T-junctions are causing cracks between different LOD levels:
I figured out that I could remove the cracks by disabling the vertices that make up the T-junction by using a stack and append to it the 2 vertices that makes a half diamond and use the following child where I use his vertex whose index is different from the previous two used. By cycling, starting from the top-left to top-right, bottom-right, bottom-left with a flag in case there is a LOD difference between the top-right and the left neighbor of him.
But before doing that, I need to know if the child's neighbors have less complexity or equal, thus if the child let's say is at top-left, I need to know if there is a LOD level at left and top that takes four times more space and by logic has less complexity.
How can I manage to do it, how can I reach for neighbors if they can be located at different quad-tree levels, if I try to use the node's box to generate the two neighbors' boxes and calculate their depth, I can't compare them with the node because during the selection process, there is the possibility that the neighbor inherited his siblings depth, thus the comparison will be wrong. But, if I chose to not use the rule of four, consequently I can't use the tactics of diamond selection I mentioned above.

Get a geosjon band based on a random point and a random angle

I'm trying to do some kind of rubber band ball in d3, I thought it was going to be easy because I remember seeing Jason Davies example (the 2nd one at the top) but it looks like it was more complex actually.
Jason's example for a vertical band is quite straightfoward:
let band = [2, -2].map((d, i) => {
let stripe = d3.range(-180, 180).map((x) => [x, d])
stripe.push(stripe[0])
return i ? stripe.reverse() : stripe
})
So I starting by making a vertical band like this:
let band = [2, -2].map((d, i) => {
let stripe = d3.range(-90, 90).map((y) => [d, y])
stripe.push(stripe[0])
return i ? stripe.reverse() : stripe
})
But instead, I get this weird shape that closes on each pole (it's supposed to be filled too, not stroked):
My end goal would be to simply specify a point, an angle and a width (4 in my example) and it would return the coordinates necessary to draw a band around the globe.
Thanks.
So it was a fairly simple problem because of d3.
I used d3.geo.circle with an angle of 90° (default) and a random point, so:
let circle = d3.geo.circle()
.origin(coords)()
Since d3.geo.circle returns a polygon, I needed to edit it's properties:
circle.type = 'LineString'
circle.coordinates = circle.coordinates[0]
Here's the final result:

Ordering coordinates from top left to bottom right

How can I go about trying to order the points of an irregular array from top left to bottom right, such as in the image below?
Methods I've considered are:
calculate the distance of each point from the top left of the image (Pythagoras's theorem) but apply some kind of weighting to the Y coordinate in an attempt to prioritise points on the same 'row' e.g. distance = SQRT((x * x) + (weighting * (y * y)))
sort the points into logical rows, then sort each row.
Part of the difficulty is that I do not know how many rows and columns will be present in the image coupled with the irregularity of the array of points. Any advice would be greatly appreciated.
Even though the question is a bit older, I recently had a similar problem when calibrating a camera.
The algorithm is quite simple and based on this paper:
Find the top left point: min(x+y)
Find the top right point: max(x-y)
Create a straight line from the points.
Calculate the distance of all points to the line
If it is smaller than the radius of the circle (or a threshold): point is in the top line.
Otherwise: point is in the rest of the block.
Sort points of the top line by x value and save.
Repeat until there are no points left.
My python implementation looks like this:
#detect the keypoints
detector = cv2.SimpleBlobDetector_create(params)
keypoints = detector.detect(img)
img_with_keypoints = cv2.drawKeypoints(img, keypoints, np.array([]), (0, 0, 255),
cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
points = []
keypoints_to_search = keypoints[:]
while len(keypoints_to_search) > 0:
a = sorted(keypoints_to_search, key=lambda p: (p.pt[0]) + (p.pt[1]))[0] # find upper left point
b = sorted(keypoints_to_search, key=lambda p: (p.pt[0]) - (p.pt[1]))[-1] # find upper right point
cv2.line(img_with_keypoints, (int(a.pt[0]), int(a.pt[1])), (int(b.pt[0]), int(b.pt[1])), (255, 0, 0), 1)
# convert opencv keypoint to numpy 3d point
a = np.array([a.pt[0], a.pt[1], 0])
b = np.array([b.pt[0], b.pt[1], 0])
row_points = []
remaining_points = []
for k in keypoints_to_search:
p = np.array([k.pt[0], k.pt[1], 0])
d = k.size # diameter of the keypoint (might be a theshold)
dist = np.linalg.norm(np.cross(np.subtract(p, a), np.subtract(b, a))) / np.linalg.norm(b) # distance between keypoint and line a->b
if d/2 > dist:
row_points.append(k)
else:
remaining_points.append(k)
points.extend(sorted(row_points, key=lambda h: h.pt[0]))
keypoints_to_search = remaining_points
Jumping on this old thread because I just dealt with the same thing: sorting a sloppily aligned grid of placed objects by left-to-right, top to bottom location. The drawing at the top in the original post sums it up perfectly, except that this solution supports rows with varying numbers of nodes.
S. Vogt's script above was super helpful (and the script below is entirely based on his/hers), but my conditions are narrower. Vogt's solution accommodates a grid that may be tilted from the horizontal axis. I assume no tilting, so I don't need to compare distances from a potentially tilted top line, but rather from a single point's y value.
Javascript below:
interface Node {x: number; y: number; width:number; height:number;}
const sortedNodes = (nodeArray:Node[]) => {
let sortedNodes:Node[] = []; // this is the return value
let availableNodes = [...nodeArray]; // make copy of input array
while(availableNodes.length > 0){
// find y value of topmost node in availableNodes. (Change this to a reduce if you want.)
let minY = Number.MAX_SAFE_INTEGER;
for (const node of availableNodes){
minY = Math.min(minY, node.y)
}
// find nodes in top row: assume a node is in the top row when its distance from minY
// is less than its height
const topRow:Node[] = [];
const otherRows:Node[] = [];
for (const node of availableNodes){
if (Math.abs(minY - node.y) <= node.height){
topRow.push(node);
} else {
otherRows.push(node);
}
}
topRow.sort((a,b) => a.x - b.x); // we have the top row: sort it by x
sortedNodes = [...sortedNodes,...topRow] // append nodes in row to sorted nodes
availableNodes = [...otherRows] // update available nodes to exclude handled rows
}
return sortedNodes;
};
The above assumes that all node heights are the same. If you have some nodes that are much taller than others, get the value of the minimum node height of all nodes and use it instead of the iterated "node.height" value. I.e., you would change this line of the script above to use the minimum height of all nodes rather that the iterated one.
if (Math.abs(minY - node.y) <= node.height)
I propose the following idea:
1. count the points (p)
2. for each point, round it's x and y coordinates down to some number, like
x = int(x/n)*n, y = int(y/m)*m for some n,m
3. If m,n are too big, the number of counts will drop. Determine m, n iteratively so that the number of points p will just be preserved.
Starting values could be in alignment with max(x) - min(x). For searching employ a binary search. X and Y scaling would be independent of each other.
In natural words this would pin the individual points to grid points by stretching or shrinking the grid distances, until all points have at most one common coordinate (X or Y) but no 2 points overlap. You could call that classifying as well.

Folding a selection of points on a 3D cube

I am trying to find an effective algorithm for the following 3D Cube Selection problem:
Imagine a 2D array of Points (lets make it square of size x size) and call it a side.
For ease of calculations lets declare max as size-1
Create a Cube of six sides, keeping 0,0 at the lower left hand side and max,max at top right.
Using z to track the side a single cube is located, y as up and x as right
public class Point3D {
public int x,y,z;
public Point3D(){}
public Point3D(int X, int Y, int Z) {
x = X;
y = Y;
z = Z;
}
}
Point3D[,,] CreateCube(int size)
{
Point3D[,,] Cube = new Point3D[6, size, size];
for(int z=0;z<6;z++)
{
for(int y=0;y<size;y++)
{
for(int x=0;x<size;x++)
{
Cube[z,y,x] = new Point3D(x,y,z);
}
}
}
return Cube;
}
Now to select a random single point, we can just use three random numbers such that:
Point3D point = new Point(
Random(0,size), // 0 and max
Random(0,size), // 0 and max
Random(0,6)); // 0 and 5
To select a plus we could detect if a given direction would fit inside the current side.
Otherwise we find the cube located on the side touching the center point.
Using 4 functions with something like:
private T GetUpFrom<T>(T[,,] dataSet, Point3D point) where T : class {
if(point.y < max)
return dataSet[point.z, point.y + 1, point.x];
else {
switch(point.z) {
case 0: return dataSet[1, point.x, max]; // x+
case 1: return dataSet[5, max, max - point.x];// y+
case 2: return dataSet[1, 0, point.x]; // z+
case 3: return dataSet[1, max - point.x, 0]; // x-
case 4: return dataSet[2, max, point.x]; // y-
case 5: return dataSet[1, max, max - point.x];// z-
}
}
return null;
}
Now I would like to find a way to select arbitrary shapes (like predefined random blobs) at a random point.
But would settle for adjusting it to either a Square or jagged Circle.
The actual surface area would be warped and folded onto itself on corners, which is fine and does not need compensating ( imagine putting a sticker on the corner on a cube, if the corner matches the center of the sticker one fourth of the sticker would need to be removed for it to stick and fold on the corner). Again this is the desired effect.
No duplicate selections are allowed, thus cubes that would be selected twice would need to be filtered somehow (or calculated in such a way that duplicates do not occur). Which could be a simple as using a HashSet or a List and using a helper function to check if the entry is unique (which is fine as selections will always be far below 1000 cubes max).
The delegate for this function in the class containing the Sides of the Cube looks like:
delegate T[] SelectShape(Point3D point, int size);
Currently I'm thinking of checking each side of the Cube to see which part of the selection is located on that side.
Calculating which part of the selection is on the same side of the selected Point3D, would be trivial as we don't need to translate the positions, just the boundary.
Next would be 5 translations, followed by checking the other 5 sides to see if part of the selected area is on that side.
I'm getting rusty in solving problems like this, so was wondering if anyone has a better solution for this problem.
#arghbleargh Requested a further explanation:
We will use a Cube of 6 sides and use a size of 16. Each side is 16x16 points.
Stored as a three dimensional array I used z for side, y, x such that the array would be initiated with: new Point3D[z, y, x], it would work almost identical for jagged arrays, which are serializable by default (so that would be nice too) [z][y][x] but would require seperate initialization of each subarray.
Let's select a square with the size of 5x5, centered around a selected point.
To find such a 5x5 square substract and add 2 to the axis in question: x-2 to x+2 and y-2 to y+2.
Randomly selectubg a side, the point we select is z = 0 (the x+ side of the Cube), y = 6, x = 6.
Both 6-2 and 6+2 are well within the limits of 16 x 16 array of the side and easy to select.
Shifting the selection point to x=0 and y=6 however would prove a little more challenging.
As x - 2 would require a look up of the side to the left of the side we selected.
Luckily we selected side 0 or x+, because as long as we are not on the top or bottom side and not going to the top or bottom side of the cube, all axis are x+ = right, y+ = up.
So to get the coordinates on the side to the left would only require a subtraction of max (size - 1) - x. Remember size = 16, max = 15, x = 0-2 = -2, max - x = 13.
The subsection on this side would thus be x = 13 to 15, y = 4 to 8.
Adding this to the part we could select on the original side would give the entire selection.
Shifting the selection to 0,6 would prove more complicated, as now we cannot hide behind the safety of knowing all axis align easily. Some rotation might be required. There are only 4 possible translations, so it is still manageable.
Shifting to 0,0 is where the problems really start to appear.
As now both left and down require to wrap around to other sides. Further more, as even the subdivided part would have an area fall outside.
The only salve on this wound is that we do not care about the overlapping parts of the selection.
So we can either skip them when possible or filter them from the results later.
Now that we move from a 'normal axis' side to the bottom one, we would need to rotate and match the correct coordinates so that the points wrap around the edge correctly.
As the axis of each side are folded in a cube, some axis might need to flip or rotate to select the right points.
The question remains if there are better solutions available of selecting all points on a cube which are inside an area. Perhaps I could give each side a translation matrix and test coordinates in world space?
Found a pretty good solution that requires little effort to implement.
Create a storage for a Hollow Cube with a size of n + 2, where n is the size of the cube contained in the data. This satisfies the : sides are touching but do not overlap or share certain points.
This will simplify calculations and translations by creating a lookup array that uses Cartesian coordinates.
With a single translation function to take the coordinates of a selected point, get the 'world position'.
Using that function we can store each point into the cartesian lookup array.
When selecting a point, we can again use the same function (or use stored data) and subtract (to get AA or min position) and add (to get BB or max position).
Then we can just lookup each entry between the AA.xyz and BB.xyz coordinates.
Each null entry should be skipped.
Optimize if required by using a type of array that return null if z is not 0 or size-1 and thus does not need to store null references of the 'hollow cube' in the middle.
Now that the cube can select 3D cubes, the other shapes are trivial, given a 3D point, define a 3D shape and test each part in the shape with the lookup array, if not null add it to selection.
Each point is only selected once as we only check each position once.
A little calculation overhead due to testing against the empty inside and outside of the cube, but array access is so fast that this solution is fine for my current project.

Basic rectangle splitting

I'm stuck on some trivial question and, well, I guess I need help here.
I have two rectangles and it's guaranteed that they have one common point from their 4 base points (upper part of the picture). It's also guaranteed that they are axis-aligned.
I know this common point (which also can be easily deduced), dimensions and the coordinates of these rectangles.
Now, I need to retrieve the coordinates of the rectangles named 1 and 2 and I'm seeking for an easy way to do that (lower part of the picture).
My current implementation relies on many if statements and I suspect I'm too stupid to find a better way.
Thank you.
Update: My current implementation.
Point commonPoint = getCommonPoint(bigRectangle, smallRectangle);
rectangle2 = new Rectangle(smallRectangle.getAdjacentVerticalPoint(commonPoint),
bigRectangle.getOppositePoint(commonPoint));
rectangle1 = new Rectangle(smallRectangle.getAdjacentHorizontalPoint(commonPoint)
bigRectangle.getOppositePoint(commonPoint));
// Now simply adjust one of these rectangles to remove the overlap,
// it's trivial - we take the 'opposite' points for 'small' and 'big'
// rectangles and then use their absolute coordinate difference as
// a fix for either width of 'rectangle2' or height of 'rectangle1'
// (in this situation it's going to be width).
adjustRectangle(rectangle2);
This is refactored, but still methods getCommonPoint and getAdjacent... and getOpposite have many if statements and I thought if this can be done better.
The top and bottom values of Rectangle 1 are the same as the big rectangle. The left and right values of rectangle 2 are the same as the small rectangle. We only need to obtain the left and right values of rectangle 1, and the top and bottom values for rectangle 2. So we only have 2 simple if-statements:
if (bigRectangle.Left == smallRectangle.Left)
left = smallRectangle.Right
right = bigRectangle.Right
else
left = bigRectangle.Left
right = smallRectangle.Left
rectangle1 = new Rectangle(left, bigRectangle.Top, right - left, bigRectangle.Height)
if (bigRectangle.Top == smallRectangle.Top)
top = smallRectangle.Bottom
bottom = bigRectangle.Bottom
else
top = bigRectangle.Top
bottom = smallRectangle.Top
rectangle2 = new Rectangle(smallRectangle.Left, top, smallRectangle.Width, bottom - top)
In the above, the Rectangle constructors takes as inputs: left, top, width, height.
From what I understand, seems like you need to have an if (or switch) statement to determine the orientation of the rectangle, and from there it would just be some easy adding and subtracting:
If you know the coords of the inner blue rectangle (and the dimensions of the rect as a whole), then finding the others should be no problem. One of the R1 and R2 points will always be the same: equal to the adjacent blue rect point. and the others is just a lil math.
Doesn't seem like you can get away from the initial if/switch statement. If the rectangle could only be up or down, then you could just make the offset negative or positive, but it can also be left or right..so you might be stuck there. You can make a -/+ offset for a vertical or horizontal state,but then you'd have to do a check on each calculation
Assuming you had RA and RB as your inputs, and whatever language you're using has a Rectangle class, here's a way to do it with 4 ifs, Math.Min, Math.Max, and Math.Abs:
Rectangle r1, r2; // Note - Rectangle constructor: new Rectangle(X, Y, Width, Height)
if (RA.X = RB.X) {
r1 = new Rectangle(Math.Min(RA.Right, RB.Right), Math.Min(RA.Y, RB.Y), Math.Abs(RA.Width - RB.Width), Math.Max(RA.Height, RB.Height));
if (RA.Y = RB.Y) {
// Intersects Top Left
r2 = new Rectangle(RA.X, Math.Min(RA.Bottom, RB.Bottom), Math.Min(RA.Width, RB.Width), Math.Abs(RA.Height - RB.Height));
} else {
// Intersects Bottom Left
r2 = new Rectangle(RA.X, Math.Max(RA.Bottom, RB.Bottom), Math.Min(RA.Width, RB.Width), Math.Abs(RA.Height - RB.Height));
}
} else {
r1 = new Rectangle(Math.Min(RA.X, RB.X), Math.Min(RA.Y, RB.Y), Math.Abs(RA.Width - RB.Width), Math.Max(RA.Height, RB.Height));
if (RA.Y = RB.Y) {
// Intersects Top Right
r2 = new Rectangle(Math.Max(RA.X, RB.X), Math.Min(RA.Bottom, RB.Bottom), Math.Min(RA.Width, RB.Width), Math.Abs(RA.Height - RB.Height));
} else {
// Intersects Bottom Right
r2 = new Rectangle(Math.Max(RA.X, RB.X), Math.Min(RA.X, RA.Y), Math.Min(RA.Width, RB.Width), Math.Abs(RA.Height - RB.Height));
}
}
This code was written in Notepad so it might have a typo or two, but the logic is sound.

Resources