I'm working on a game in C# with XNA and I've been learning program in C# thanks to Nick Gravelyn's tutorials, but I've hit a snag. While I'm using Nick's collision system, I'm not using his player code. I'm using one that's based on a tutorial by Fatso784 that I've modified. So, as a result, I'm having trouble making my modified version of the collision system work properly. I've got it to the point that it pushes the player out of certain tiles, but I need it to be more solid because the player is still able walk through walls occasionally. I'm pretty sure I'm handling the collision wrong, but it could be that the collision is a little mushy. So here's the relevant code from my player class, the move code:
public void Move()
{
pos.X = bounds.X;
pos.Y = bounds.Y;
offsetPos.X = bounds.Width;
offsetPos.Y = bounds.Height;
if (frameCount % delay == 0)
{
switch (direction)
{
case "stand":
if (sideCollide == "none")
{
Equalize(2);
}
else if (sideCollide == "left")
{
speed += 1f;
}
else if (sideCollide == "right")
{
speed -= 1f;
}
bounds.X += (int)speed;
if (frameCount / delay >= 8)
frameCount = 0;
srcBounds = new Rectangle(frameCount / delay * 64, 0, 64, 64);
break;
case "left":
if (sideCollide != "left")
{
if (speed > -maxspeed)
{
speed -= acceleration;
}
else if (speed < -maxspeed)
{
speed -= acceleration;
speed += drag;
Equalize(2);
}
speed += friction;
}
bounds.X += (int)speed;
if (frameCount / delay >= 4)
frameCount = 0;
srcBounds = new Rectangle(frameCount / delay * 64, 64, 64, 64);
break;
case "right":
if (sideCollide != "right")
{
if (speed < maxspeed)
{
speed += acceleration;
}
else if (speed > maxspeed)
{
speed += acceleration;
speed -= drag;
Equalize(2);
}
speed -= friction;
}
bounds.X += (int)speed;
if (frameCount / delay >= 4)
frameCount = 0;
srcBounds = new Rectangle(frameCount / delay * 64, 64, 64, 64);
break;
case "up":
if (speed > -4 && speed < 4)
srcBounds.Y = 128;
else
srcBounds.Y = 64;
if (srcBounds.Y == 0 || srcBounds.Y == 128)
{
if (jumpCount < 2)
{
if (frameCount / delay >= 9)
frameCount = 0;
}
else if (jumpCount > 2 && jumpCount <= 10)
{
if (frameCount / delay > 3)
frameCount = 2 * delay;
}
else if (jumpCount > 10 && jumpCount <= 18)
{
if (frameCount / delay > 5)
frameCount = 4 * delay;
}
else if (jumpCount > 18)
{
if (frameCount / delay >= 9)
frameCount = 0;
}
srcBounds = new Rectangle(frameCount / delay * 64, 128, 64, 64);
}
else if (srcBounds.Y == 64)
{
if (frameCount / delay >= 4)
frameCount = 0;
if (jumpCount <= 10)
srcBounds = new Rectangle((frameCount / delay) / 2 * 64, 64, 64, 64);
else
srcBounds = new Rectangle(frameCount / delay * 64, 64, 64, 64);
}
if (jumpCount == 0)
startY = bounds.Y;
bounds = new Rectangle(bounds.X + (int)speed,
(jumpCount - 10) * (jumpCount - 10) - 100 + startY, 64, 64);
jumpCount++;
if (bounds.Y > startY)
{
bounds.Y = startY;
direction = "stand";
jumpCount = 0;
}
break;
}
}
frameCount++;
}
And the collision code:
public void CollideOutside(TileMap tilemap)
{
Point cell = Engine.PointCell(PlayerCenter);
Point? upLeft = null, Up = null, upRight = null, Right = null, downRight = null, Down = null, downLeft = null, Left = null;
if (cell.Y > 0)
{
Up = new Point(cell.X, cell.Y - 1);
}
if (cell.Y < tilemap.collisionMap.HeightinPixels)
{
Down = new Point(cell.X, cell.Y + 1);
}
if (cell.X > 0)
{
Left = new Point(cell.X - 1, cell.Y);
}
if (cell.X < tilemap.collisionMap.WidthinPixels)
{
Right = new Point(cell.X + 1, cell.Y);
}
if (cell.X > 0 && cell.Y > 0)
{
upLeft = new Point(cell.X - 1, cell.Y - 1);
}
if (cell.X < tilemap.collisionMap.WidthinPixels - 1 && cell.Y > 0)
{
upRight = new Point(cell.X + 1, cell.Y - 1);
}
if (cell.X > 0 && cell.Y < tilemap.collisionMap.HeightinPixels - 1)
{
downLeft = new Point(cell.X - 1, cell.Y + 1);
}
if (cell.X < tilemap.collisionMap.WidthinPixels - 1 && cell.Y < tilemap.collisionMap.Height - 1)
{
downRight = new Point(cell.X + 1, cell.Y + 1);
}
if (Up != null && tilemap.collisionMap.GetCellIndex(Up.Value) == 1)
{
Rectangle rect = Engine.CreateCell(Up.Value);
Rectangle playerCell = Boundary;
if (rect.Intersects(playerCell))
{
}
}
if (Down != null && tilemap.collisionMap.GetCellIndex(Down.Value) == 1)
{
Rectangle rect = Engine.CreateCell(Down.Value);
Rectangle playerCell = Boundary;
if (rect.Intersects(playerCell))
{
}
}
if (Right != null && tilemap.collisionMap.GetCellIndex(Right.Value) == 1)
{
Rectangle rect = Engine.CreateCell(Right.Value);
Rectangle playerCell = Boundary;
if (rect.Intersects(playerCell))
{
speed = -1f;
sideCollide = "right";
}
else
{
sideCollide = "none";
}
}
if (Left != null && tilemap.collisionMap.GetCellIndex(Left.Value) == 1)
{
Rectangle rect = Engine.CreateCell(Left.Value);
Rectangle playerCell = Boundary;
if (rect.Intersects(playerCell))
{
speed = 1f;
sideCollide = "left";
}
else
{
sideCollide = "none";
}
}
if (upLeft != null && tilemap.collisionMap.GetCellIndex(upLeft.Value) == 1)
{
Rectangle rect = Engine.CreateCell(upLeft.Value);
Rectangle playerCell = Boundary;
if (rect.Intersects(playerCell))
{
}
}
if (upRight != null && tilemap.collisionMap.GetCellIndex(upRight.Value) == 1)
{
Rectangle rect = Engine.CreateCell(upRight.Value);
Rectangle playerCell = Boundary;
if (rect.Intersects(playerCell))
{
}
}
if (downLeft != null && Left != null && tilemap.collisionMap.GetCellIndex(downLeft.Value) == 1)
{
Rectangle rect = Engine.CreateCell(downLeft.Value);
Rectangle playerCell = Boundary;
if (rect.Intersects(playerCell))
{
speed = 1f;
sideCollide = "left";
}
}
if (downRight != null && Right != null && tilemap.collisionMap.GetCellIndex(downRight.Value) == 1)
{
Rectangle rect = Engine.CreateCell(downRight.Value);
Rectangle playerCell = Boundary;
if (rect.Intersects(playerCell))
{
speed = -1f;
sideCollide = "right";
}
}
if (Right == null && Left == null)
{
sideCollide = "none";
}
}
public Rectangle Boundary
{
get
{
Rectangle rect = bounds;
rect.X = (int)pos.X;
rect.Y = (int)pos.Y;
return rect;
}
}
So how can I improve the collision?
This answer is mostly in response to Tim's answer - because he's given very much the wrong approach. (Your question is a very large code dump, I can't really play spot-the-error with that much code.)
The trick with collision detection - the way the "real" physics engines do it - is to always treat your objects as solids. You always - each frame - check objects for interpenetration, and then separate them if they interpenetrate.
If you only test for moving across the boundary of an object you are going to miss collisions. This includes any approach where you attempt to predict and avoid a collision by snapping onto the surface. If you do this, you are just asking for floating-point precision errors to let you slip inside objects.
EDIT: of course, your code all seems to be integer-based (Point and Rectangle). So at least floating-point precision should not be an issue. But maybe you have a < where you should have a <= or something to that effect?
(Also you're using strings in a lot of places where you very much should be using enumerations.)
Related
I have a QuadTree but the insert method is not working like I want it to. Right now it only inserts in the first intersecting quad it sees. The aim is that all nodes are inserted in all the quads its intersecting with. For example: when a node is on the border of two quads, it is inserted in both quads. Can somebody help me to get the insert method to where it is inserted in all intersecting quads?
This is my current implementation:
This is the call to insert the nodes:
QuadTree = new QuadTreeNode < Artist > (new Rect(0, 0, Width, Height), 1);
foreach(Artist artist in Artists) {
QuadTree.Insert(artist);
}
This is my QuadTree class:
public interface WithRect {
Rect Rect {
get;
}
}
public class QuadTreeNode < T > where T: WithRect {
Rect bounds;
List < T > contents;
int numberOfNodesInserted = 0;
int max = 4;
int depth = 0;
int maxDepth = 6;
bool divided;
QuadTreeNode < T > TopLeft;
QuadTreeNode < T > TopRight;
QuadTreeNode < T > BottomLeft;
QuadTreeNode < T > BottomRight;
public QuadTreeNode(Rect _bounds, int _depth) {
depth = _depth;
bounds = _bounds;
contents = new List < T > ();
divided = false;
}
public void DivideQuad() {
int newDepth = depth + 1;
double x = bounds.X;
double y = bounds.Y;
double width = bounds.Width;
double height = bounds.Height;
TopLeft = new QuadTreeNode < T > (new Rect(x, y, width / 2, height / 2), newDepth);
TopRight = new QuadTreeNode < T > (new Rect(x + width / 2, y, width / 2, height / 2), newDepth);
BottomLeft = new QuadTreeNode < T > (new Rect(x, y + height / 2, width / 2, height / 2), newDepth);
BottomRight = new QuadTreeNode < T > (new Rect(x + width / 2, y + height / 2, width / 2, height / 2), newDepth);
divided = true;
foreach(T item in contents) {
Insert(item);
}
contents.Clear();
}
public bool Insert(T item) {
if (!bounds.IntersectsWith(item.Rect)) return false;
if (numberOfNodesInserted < max || depth == maxDepth) {
contents.Add(item);
numberOfNodesInserted++;
return true;
}
else {
if (!divided) {
DivideQuad();
}
if (TopLeft.Insert(item)) return true;
else if (TopRight.Insert(item)) return true;
else if (BottomLeft.Insert(item)) return true;
else if (BottomRight.Insert(item)) return true;
else return false;
}
}
public void GetBounds(ref List < Rect > results) {
if (bounds != null) results.Add(bounds);
if (TopLeft != null) TopLeft.GetBounds(ref results);
if (TopRight != null) TopRight.GetBounds(ref results);
if (BottomLeft != null) BottomLeft.GetBounds(ref results);
if (BottomRight != null) BottomRight.GetBounds(ref results);
}
public void Query(T item, ref List < T > collisions, List < Square > squares, int squareWidth, int squareHeight) {
if (item.Rect.IntersectsWith(bounds)) {
if (divided) {
TopLeft.Query(item, ref collisions, squares, squareWidth, squareHeight);
TopRight.Query(item, ref collisions, squares, squareWidth, squareHeight);
BottomLeft.Query(item, ref collisions, squares, squareWidth, squareHeight);
BottomRight.Query(item, ref collisions, squares, squareWidth, squareHeight);
}
for (int i = 0; i < contents.Count; i++) {
if (item.Rect != contents[i].Rect) {
if (item.Rect.IntersectsWith(contents[i].Rect)) {
if (!collisions.Contains(contents[i])) {
collisions.Add(contents[i]);
}
}
}
foreach(Square square in squares) {
if (square.IsVisited) {
if (item.Rect.IntersectsWith(new Rect(square.X * squareWidth, square.Y * squareHeight, squareWidth, squareHeight))) {
if (!collisions.Contains(contents[i])) {
collisions.Add(contents[i]);
}
}
}
}
}
}
}
}
Remove the else from else if to ensure a item is inserted to each of its subnodes.
if (TopLeft.Insert(item)) return true;
else if (TopRight.Insert(item)) return true;
else if (BottomLeft.Insert(item)) return true;
else if (BottomRight.Insert(item)) return true;
else return false;
should be
var result = TopLeft.Insert(item);
result |= TopRight.Insert(item);
result |= BottomLeft.Insert(item);
result |= BottomRight.Insert(item);
return result;
Another problem is
if (numberOfNodesInserted < max || depth == maxDepth)
you should never insert items to the node if it is divided, so it should be:
if (!divided && (numberOfNodesInserted < max || depth == maxDepth))
That is, assuming you are only storing items in the leaf-nodes.
I'm trying to raycast / raymarch through a (virtual) volume of 64^3 voxels for about 90x90 rays. The marching algorithm looks like this:
public byte[] GetVoxel(Vec3 rayStart, Vec3 direction)
{
direction.Normalize();
bool wasInside = false;
IVec3 step = new IVec3(Math.Sign(direction.x), Math.Sign(direction.y), Math.Sign(direction.z));
IVec3 p = new IVec3(rayStart);
if (step.x > 0)
p.x += 1;
if (step.y > 0)
p.y += 1;
if (step.z > 0)
p.z += 1;
Vec3 max = new Vec3(
direction.x != 0f ? Math.Abs((p.x - rayStart.x) / direction.x) : float.PositiveInfinity,
direction.y != 0f ? Math.Abs((p.y - rayStart.y) / direction.y) : float.PositiveInfinity,
direction.z != 0f ? Math.Abs((p.z - rayStart.z) / direction.z) : float.PositiveInfinity
);
Vec3 delta = new Vec3(
direction.x != 0f ? Math.Abs(1f / direction.x) : float.PositiveInfinity,
direction.y != 0f ? Math.Abs(1f / direction.y) : float.PositiveInfinity,
direction.z != 0f ? Math.Abs(1f / direction.z) : float.PositiveInfinity
);
byte[] col = new byte[4] {0,0,0,0};
byte[] newCol;
int maxSteps = voxelResolution * 2;
int k = 0;
do
{
if(max.x < max.y)
{
if(max.x < max.z)
{
p.x += step.x;
max.x += delta.x;
}
else
{
p.z += step.z;
max.z += delta.z;
}
}
else
{
if(max.y < max.z)
{
p.y += step.y;
max.y += delta.y;
}
else
{
p.z += step.z;
max.z += delta.z;
}
}
if(p.x >= 0 && p.x < voxelResolution && p.y >= 0 && p.y < voxelResolution)
{
if (!wasInside)
wasInside = true;
}
else if(wasInside)
{
break;
}
newCol = GetVoxel(p);
if(newCol[3] > 0)
col = ColorHelper.Mix(col, newCol);
k++;
}
while (col[3] <= 255 && k < maxSteps);
return col;
}
This takes over a second to perform. Shouldn't this be way faster or do I have a bottleneck?
Update:
I measured a bit and it turns out the most time is lost fetching the actual color from the voxel. I improved it already, but it still costs a total of over 300 ms:
Time total: 659
Time make: 659
Time setup: 3
Time march: 102
Time color: 318
Time update image: 0
I don't really understand where I'm missing 200 ms though. Also I'm confused why it takes so long to fetch the color for each voxel. I've updated the code and I'll include the voxel fetching method:
public void DrawImage(Vec3 camCenter, Vec3 viewDir)
{
int w = ResInternal;
int h = ResInternal;
viewDir.y *= -1;
long tSetup = 0, tTotal = 0, tMake = 0, tUpdateImage = 0, tMarch = 0, tGetColor = 0;
Stopwatch sw = new Stopwatch();
sw.Start();
if (w > 0 && h > 0)
{
int x, y;
viewDir.Normalize();
Vec3 right = Vec3.Cross(new Vec3(0, 1, 0), viewDir);
Vec3 up = Vec3.Cross(viewDir, right);
Vec3 halfS = new Vec3(w, h, 0) * 0.5f;
Vec3 loc;
for (int i = 0; i < bitmapData.Length / bpp; i++)
{
x = i % w;
y = i / w;
loc = camCenter + right * (x - w * 0.5f) + up * (y - h * 0.5f);
isoObject.GetVoxel(loc, viewDir,ref bitmapData, i * bpp, ref sw, ref tSetup, ref tMarch, ref tGetColor);
}
tMake = sw.ElapsedMilliseconds;
UpdateImage();
tUpdateImage = sw.ElapsedMilliseconds - tMake;
tTotal = sw.ElapsedMilliseconds;
}
Console.WriteLine("Time total: " + tTotal);
Console.WriteLine("Time make: " + tMake);
Console.WriteLine("Time setup: " + tSetup);
Console.WriteLine("Time march: " + tMarch);
Console.WriteLine("Time color: " + tGetColor);
Console.WriteLine("Time update image: " + tUpdateImage);
}
here is the getVoxel method:
public void GetVoxel(Vec3 rayStart, Vec3 direction, ref byte[] imgData, int index, ref System.Diagnostics.Stopwatch stopwatch, ref long timeSetup, ref long timeMarch, ref long timeColor)
{
long t = stopwatch.ElapsedMilliseconds;
direction.Normalize();
bool wasInside = false;
IVec3 step = new IVec3(Math.Sign(direction.x), Math.Sign(direction.y), Math.Sign(direction.z));
IVec3 p = new IVec3(rayStart);
if (step.x > 0)
p.x += 1;
if (step.y > 0)
p.y += 1;
if (step.z > 0)
p.z += 1;
Vec3 max = new Vec3(
direction.x != 0f ? Math.Abs((p.x - rayStart.x) / direction.x) : float.PositiveInfinity,
direction.y != 0f ? Math.Abs((p.y - rayStart.y) / direction.y) : float.PositiveInfinity,
direction.z != 0f ? Math.Abs((p.z - rayStart.z) / direction.z) : float.PositiveInfinity
);
Vec3 delta = new Vec3(
direction.x != 0f ? Math.Abs(1f / direction.x) : float.PositiveInfinity,
direction.y != 0f ? Math.Abs(1f / direction.y) : float.PositiveInfinity,
direction.z != 0f ? Math.Abs(1f / direction.z) : float.PositiveInfinity
);
byte[] col = new byte[4] {0,0,0,0};
byte[] newCol;
int maxSteps = voxelResolution * 2;
int k = 0;
timeSetup += stopwatch.ElapsedMilliseconds - t;
t = stopwatch.ElapsedMilliseconds;
do
{
if(max.x < max.y)
{
if(max.x < max.z)
{
p.x += step.x;
max.x += delta.x;
}
else
{
p.z += step.z;
max.z += delta.z;
}
}
else
{
if(max.y < max.z)
{
p.y += step.y;
max.y += delta.y;
}
else
{
p.z += step.z;
max.z += delta.z;
}
}
if(p.x >= 0 && p.x < voxelResolution && p.y >= 0 && p.y < voxelResolution)
{
if (!wasInside)
wasInside = true;
}
else if(wasInside)
{
break;
}
timeMarch += stopwatch.ElapsedMilliseconds - t;
t = stopwatch.ElapsedMilliseconds;
newCol = Root.GetVoxel(p);
if(newCol[3] > 0)
col = ColorHelper.Mix(col, newCol);
timeColor += stopwatch.ElapsedMilliseconds - t;
t = stopwatch.ElapsedMilliseconds;
k++;
}
while (col[3] <= 255 && k < maxSteps);
Buffer.BlockCopy(col, 0, imgData, index, 4);
//return col;
}
and the actual voxel fetching:
public byte[] GetVoxel(IVec3 location)
{
byte[] col = new byte[] { 0, 0, 0, 0 };
if (voxelData != null)
{
if (location.x >= 0 && location.y >= 0 && location.z >= 0 && location.x < resolution && location.y < resolution && location.z < resolution)
{
col = voxelData[location.x, location.y, location.z];
}
}
foreach(var c in children.Values)
{
if(c.Carve)
{
col[3] = (byte)(col[3] * (1f - c.GetVoxel(location)[3] / 255f));
}
}
if(col[3] < 255)
{
foreach (var c in children.Values)
{
if (!c.Carve)
{
col = ColorHelper.Mix(c.GetVoxel(location), col);
if (col[3] >= 255)
break;
}
}
}
return col;
}
I'm testing with only two layers, the root layer which is empty (voxelData is null) and one child layer with the actualy voxel data. Somehow ~300 ms are lost on a simple array fetch.
I used this code for segmentation, I'm trying to detect pixels one by one because my object is a binary, not a grayscale. when i run the program, it draws 2 object. The first object is successfully drawn (object still has a black color and a red rectangle), but the second object fails get drawn. Screenshot is here. Please help me, why does this happen?
#region Edge Detection
private void btnSegmentasi_Click(object sender, EventArgs e)
{
Segments = new List<ImageSegment>();
Bitmap bmp = (Bitmap)pb2.Image;
imageArea = new Rectangle(0, 0, pb2.Image.Width - 1, pb2.Image.Height - 1);
for (int y = 0; y < pb2.Image.Height; y++)
{
for (int x = 0; x < pb2.Image.Width; x++)
{
bool skip = false;
foreach (ImageSegment segment in Segments)
{
if (pointIsInRect(x, y, segment.Rect))
{
skip = true;
break;
}
}
if (skip) continue;
Color warna = bmp.GetPixel(x, y);
if (warna.G == 0)
startEdgeDetection(x, y, ref bmp);
}
}
DGVProses.DataSource = Segments;
if (Segments.Count > 0)
{
Graphics g = pb2.CreateGraphics();
Rectangle[] rects = (from theSegment in Segments select theSegment.Rect).ToArray();
g.DrawRectangles(new Pen(Brushes.Red), rects);
g.Dispose();
}
}
private void startEdgeDetection(int x, int y, ref Bitmap bmp)
{
Point startPoint = new Point(x, y);
Point currPoint = new Point(x, y);
int sudut = 180;
int xMin = x, yMin = y, xMax = x, yMax = y;
do
{
sudut -= 45;
Point offset = angleToPoint(ref sudut);
Point trialPoint = new Point(currPoint.X + offset.X, currPoint.Y + offset.Y);
if (!pointIsInRect(trialPoint.X, trialPoint.Y, imageArea))
continue;
Color theColor = bmp.GetPixel(trialPoint.X, trialPoint.Y);
if (theColor.G == 0)
{
currPoint = trialPoint;
sudut -= 180;
if (currPoint.X > xMax)
xMax = currPoint.X;
else if (currPoint.X < xMin)
xMin = currPoint.X;
if (currPoint.Y > yMax)
yMax = currPoint.Y;
else if (currPoint.Y < yMin)
yMin = currPoint.Y;
if (sudut < 0)
sudut += 360;
if (currPoint == startPoint && sudut == 180)
break;
}
}
while (!(currPoint == startPoint && sudut == 180));
Rectangle r = new Rectangle(xMin, yMin, xMax - xMin + 1, yMax - yMin + 1);
Bitmap newImage = new Bitmap(r.Width + 2, r.Height + 2);
using (Graphics g = Graphics.FromImage(newImage))
{
g.FillRectangle(Brushes.White, 0, 0, newImage.Width, newImage.Height);
g.DrawImage(bmp, new Rectangle(1, 1, r.Width, r.Height), r, GraphicsUnit.Pixel);
g.Dispose();
}
Segments.Add(new ImageSegment(r, newImage));
}
private Point angleToPoint(ref int sudut)
{
if (sudut < 0)
sudut += 360;
switch (sudut)
{
case 135: return new Point(-1, -1);
case 90: return new Point(0, -1);
case 45: return new Point(1, -1);
case 0: return new Point(1, 0);
case 315: return new Point(1, 1);
case 270: return new Point(0, 1);
case 225: return new Point(-1, 1);
default: return new Point(-1, 0);
}
}
private bool pointIsInRect(int x, int y, Rectangle rect)
{
if (x < rect.X)
return false;
if (x > rect.X + rect.Width)
return false;
if (x < rect.Y)
return false;
if (x > rect.Y + rect.Height)
return false;
return true;
}
#endregion
Okay, I think I've now got a clue of how your algorithm is supposed to work. I'd guess you are running around in circles within the object. I do not really know why it does not happen for the first object, but this is another story.
When you enter startEdgeDetection you start at some point, check if it's black, move by an angle and repeat the whole procedure. You stop when the current point reaches the starting point. The crux is, that this algorithm does not guarantee to walk the whole object, but may just do the following (I do not know it is exactly like this, but pretty much):
OOOOOO
O####O
O####O
OOOOOO
OOOOOO
O*###O
O####O
OOOOOO
OOOOOO
O**##O
O####O
OOOOOO
OOOOOO
O**##O
O#*##O
OOOOOO
OOOOOO
O**##O
O**##O
OOOOOO
O = pixels filled with white
# = pixels filled with black
* = pixels you stepped through
You've reached your starting point again and the algorithm stops, but the bounding box does not contain the whole object, but just a part. If all of your objects bounding boxes have either a width or a height of 1 you fill up your whole object with bounding boxes, hence it appears red.
You'll have to fix the startEdgeDetection to avoid the described case and make sure that you really detect the edge.
I made up a simple class that finds the bounding box of an object. It should be easy to apply it to your problem.
public class BoundingBoxCalculator
{
Bitmap bitmapToCalculateBoundingBoxFor;
Point startingPoint;
Point[] neighborOffsets =
{new Point(-1,-1),
new Point(0,-1),
new Point(1,-1),
new Point(-1, 0),
new Point(1, 0),
new Point(-1,1),
new Point(0,1),
new Point(1,1)};
public BoundingBoxCalculator(Bitmap bitmapContainingObject, Point borderPoint)
{
this.bitmapToCalculateBoundingBoxFor = bitmapContainingObject;
this.startingPoint = borderPoint;
}
public Rectangle CalculateBoundingBox()
{
List<Point> edgePoints = CalculateEdge();
int minX = edgePoints.Min(p => p.X);
int maxX = edgePoints.Max(p => p.X);
int minY = edgePoints.Min(p => p.Y);
int maxY = edgePoints.Max(p => p.Y);
return new Rectangle(minX, minY, maxX - minX, maxY - minY);
}
List<Point> CalculateEdge()
{
List<Point> edgePoints = new List<Point>();
Point currentPoint = startingPoint;
do
{
IEnumerable<Point> neighboringEdgePoints = GetNeighboringEdgePoints(currentPoint);
IEnumerable<Point> neighboringEdgePointsNotVisited = from p in neighboringEdgePoints where !edgePoints.Contains(p) select p;
edgePoints.Add(currentPoint);
if(neighboringEdgePointsNotVisited.Count() == 0
&& neighboringEdgePoints.Contains(startingPoint))
{
currentPoint = startingPoint;
}
else if(neighboringEdgePointsNotVisited.Count() == 1)
{
Point nextPoint = neighboringEdgePointsNotVisited.First();
currentPoint = nextPoint;
}
else if(neighboringEdgePointsNotVisited.Count() > 1)
{
Point nextPoint = GetPointWithMinDistance(currentPoint, neighboringEdgePointsNotVisited);
currentPoint = nextPoint;
}
else
{
throw new Exception();
}
} while(currentPoint != startingPoint);
return edgePoints;
}
Point GetPointWithMinDistance(Point origin, IEnumerable<Point> pointsToTest)
{
double minDistance = double.MaxValue;
Point pointWithMinDistance = new Point(0,0);
foreach(Point pointToTest in pointsToTest)
{
double currentDistance = GetPointsDistance(origin, pointToTest);
if(currentDistance < minDistance)
{
minDistance = currentDistance;
pointWithMinDistance = pointToTest;
}
}
return pointWithMinDistance;
}
double GetPointsDistance(Point p1, Point p2)
{
return Math.Sqrt((p1.X - p2.X) * (p1.X - p2.X) + (p1.Y - p2.Y) * (p1.Y - p2.Y));
}
IEnumerable<Point> GetNeighboringEdgePoints(Point currentPoint)
{
IEnumerable<Point> neighboringPoints = GetNeighboringPoints(currentPoint);
List<Point> neighboringEdgePoints = new List<Point>();
foreach(Point pointToTest in neighboringPoints)
{
if(GetNeighboringPoints(pointToTest).Count() < 8)
{
neighboringEdgePoints.Add(pointToTest);
}
}
return neighboringEdgePoints;
}
IEnumerable<Point> GetNeighboringPoints(Point currentPoint)
{
List<Point> neighbors = new List<Point>();
for(int offsetsCount = 0; offsetsCount < neighborOffsets.Length; offsetsCount++)
{
Point currentPointWithOffset = AddPointOffset(currentPoint, neighborOffsets[offsetsCount]);
if(IsInImage(currentPointWithOffset) &&
IsInObject(currentPointWithOffset))
{
neighbors.Add(currentPointWithOffset);
}
}
return neighbors;
}
bool IsInImage(Point pointToTest)
{
return pointToTest.X >= 0
&& pointToTest.X < bitmapToCalculateBoundingBoxFor.Width
&& pointToTest.Y >= 0
&& pointToTest.Y < bitmapToCalculateBoundingBoxFor.Height;
}
bool IsInObject(Point pointToTest)
{
Color colorInPointPosition = bitmapToCalculateBoundingBoxFor.GetPixel(pointToTest.X, pointToTest.Y);
//assume object is color is not white
return colorInPointPosition.R != 255
|| colorInPointPosition.G != 255
|| colorInPointPosition.B != 255;
}
Point AddPointOffset(Point point, Point offset)
{
return new Point(point.X + offset.X, point.Y + offset.Y);
}
}
Find an example at:
https://dotnetfiddle.net/49bnTV
I just tested it with a rectangle, but I guess it should work with any shape. Just give it a try.
Does anyone know of any code to render an Ellipse to an array in C#? I had a look about, I couldn't find anything that answered my problem.
Given the following array:
bool[,] pixels = new bool[100, 100];
I'm looking for functions to render both a hollow and filled ellipse within a rectangular area. e.g:
public void Ellipse(bool[,] pixels, Rectangle area)
{
// fill pixels[x,y] = true here for the ellipse within area.
}
public void FillEllipse(bool[,] pixels, Rectangle area)
{
// fill pixels[x,y] = true here for the ellipse within area.
}
Ellipse(pixels, new Rectangle(20, 20, 60, 60));
FillEllipse(pixels, new Rectangle(40, 40, 20, 20));
Any help would be greatly appreciated.
Something like this should do the trick
public class EllipseDrawer
{
private static PointF GetEllipsePointFromX(float x, float a, float b)
{
//(x/a)^2 + (y/b)^2 = 1
//(y/b)^2 = 1 - (x/a)^2
//y/b = -sqrt(1 - (x/a)^2) --Neg root for upper portion of the plane
//y = b*-sqrt(1 - (x/a)^2)
return new PointF(x, b * -(float)Math.Sqrt(1 - (x * x / a / a)));
}
public static void Ellipse(bool[,] pixels, Rectangle area)
{
DrawEllipse(pixels, area, false);
}
public static void FillEllipse(bool[,] pixels, Rectangle area)
{
DrawEllipse(pixels, area, true);
}
private static void DrawEllipse(bool[,] pixels, Rectangle area, bool fill)
{
// Get the size of the matrix
var matrixWidth = pixels.GetLength(0);
var matrixHeight = pixels.GetLength(1);
var offsetY = area.Top;
var offsetX = area.Left;
// Figure out how big the ellipse is
var ellipseWidth = (float)area.Width;
var ellipseHeight = (float)area.Height;
// Figure out the radiuses of the ellipses
var radiusX = ellipseWidth / 2;
var radiusY = ellipseHeight / 2;
//Keep track of the previous y position
var prevY = 0;
var firstRun = true;
// Loop through the points in the matrix
for (var x = 0; x <= radiusX; ++x)
{
var xPos = x + offsetX;
var rxPos = (int)ellipseWidth - x - 1 + offsetX;
if (xPos < 0 || rxPos < xPos || xPos >= matrixWidth)
{
continue;
}
var pointOnEllipseBoundCorrespondingToXMatrixPosition = GetEllipsePointFromX(x - radiusX, radiusX, radiusY);
var y = (int) Math.Floor(pointOnEllipseBoundCorrespondingToXMatrixPosition.Y + (int)radiusY);
var yPos = y + offsetY;
var ryPos = (int)ellipseHeight - y - 1 + offsetY;
if (yPos >= 0)
{
if (xPos > -1 && xPos < matrixWidth && yPos > -1 && yPos < matrixHeight)
{
pixels[xPos, yPos] = true;
}
if(xPos > -1 && xPos < matrixWidth && ryPos > -1 && ryPos < matrixHeight)
{
pixels[xPos, ryPos] = true;
}
if (rxPos > -1 && rxPos < matrixWidth)
{
if (yPos > -1 && yPos < matrixHeight)
{
pixels[rxPos, yPos] = true;
}
if (ryPos > -1 && ryPos < matrixHeight)
{
pixels[rxPos, ryPos] = true;
}
}
}
//While there's a >1 jump in y, fill in the gap (assumes that this is not the first time we've tracked y, x != 0)
for (var j = prevY - 1; !firstRun && j > y - 1 && y > 0; --j)
{
var jPos = j + offsetY;
var rjPos = (int)ellipseHeight - j - 1 + offsetY;
if(jPos == rjPos - 1)
{
continue;
}
if(jPos > -1 && jPos < matrixHeight)
{
pixels[xPos, jPos] = true;
}
if(rjPos > -1 && rjPos < matrixHeight)
{
pixels[xPos, rjPos] = true;
}
if (rxPos > -1 && rxPos < matrixWidth)
{
if(jPos > -1 && jPos < matrixHeight)
{
pixels[rxPos, jPos] = true;
}
if(rjPos > -1 && rjPos < matrixHeight)
{
pixels[rxPos, rjPos] = true;
}
}
}
firstRun = false;
prevY = y;
var countTarget = radiusY - y;
for (var count = 0; fill && count < countTarget; ++count)
{
++yPos;
--ryPos;
// Set all four points in the matrix we just learned about
// also, make the indication that for the rest of this row, we need to fill the body of the ellipse
if(yPos > -1 && yPos < matrixHeight)
{
pixels[xPos, yPos] = true;
}
if(ryPos > -1 && ryPos < matrixHeight)
{
pixels[xPos, ryPos] = true;
}
if (rxPos > -1 && rxPos < matrixWidth)
{
if(yPos > -1 && yPos < matrixHeight)
{
pixels[rxPos, yPos] = true;
}
if(ryPos > -1 && ryPos < matrixHeight)
{
pixels[rxPos, ryPos] = true;
}
}
}
}
}
}
Although there already seems to be a perfectly valid answer with source code and all to this question, I just want to point out that the WriteableBitmapEx project also contains a lot of efficient source code for drawing and filling different polygon types (such as ellipses) in so-called WriteableBitmap objects.
This code can easily be adapted to the general scenario where a 2D-array (or 1D representation of a 2D-array) should be rendered in different ways.
For the ellipse case, pay special attention to the DrawEllipse... methods in the WriteableBitmapShapeExtensions.cs file and FillEllipse... methods in the WriteableBitmapFillExtensions.cs file, everything located in the trunk/Source/WriteableBitmapEx sub-folder.
This more applies to all languages in general, and I'm not sure why you're looking for things like this in particular rather than using a pre-existing graphics library (homework?), but for drawing an ellipse, I would suggest using the midpoint line drawing algorithm which can be adapted to an ellipse (also to a circle):
http://en.wikipedia.org/wiki/Midpoint_circle_algorithm
I'm not sure I fully agree that it's a generalisation of Bresenham's algorithm (certainly we were taught that Bresenham's and the Midpoint algorithm are different but proved to produce identical results), but that page should give you a start on it. See the link to the paper near the bottom for an algorithm specific to ellipses.
As for filling the ellipse, I'd say your best bet is to take a scanline approach - look at each row in turn, work out which pixels the lines on the left and right are at, and then fill every pixel inbetween.
The simplest thing to do would do is iterate over each element of your matrix, and check whether some ellipse equation evaluates to true
taken from http://en.wikipedia.org/wiki/Ellipse
What I would start with is something resembling
bool[,] pixels = new bool[100, 100];
double a = 30;
double b = 20;
for (int i = 0; i < 100; i++)
for (int j = 0; j < 100; j++ )
{
double x = i-50;
double y = j-50;
pixels[i, j] = (x / a) * (x / a) + (y / b) * (y / b) > 1;
}
and if your elipse is in reverse, than just change the > to <
For a hollow one, you can check whether the difference between (x / a) * (x / a) + (y / b) * (y / b) and 1 is within a certain threshold. If you just change the inequality to an equation, it will probably miss some pixels.
Now, I haven't actually tested this fully, so I don't know if the equation being applied correctly, but I just want to illustrate the concept.
I've always worked with Windows Forms, but now I'm trying to learn WPF due to it's advantages. Some time ago I created a picturebox control (with help of Damien here). And for me it's very hard to convert this control into WPF's Image control. I haven't found any appropriate help on the Internet.
My control is used for displaying (founded before) middle between two pages on a scanned image of book. It consists of two moveable points, line between them and areas to the left and to the right filled with semitransparent polygons.
The problem is that WPF is VERY different. It's even hard to draw a filled circle on a Image control.
Here is my code listing:
public partial class SplitPictureBox : System.Windows.Forms.PictureBox
{
public SplitPictureBox()
{
InitializeComponent();
}
private int mPointMoveInProgress = 0;
private int handleRadius = 5;
public int HandleRaduis
{
get { return handleRadius; }
set { handleRadius = value; }
}
private int middleTop = 0;
private int middleBottom = 0;
private int middle;
public int Middle
{
get
{
return (middleTop + middleBottom) /2;
}
set { middle = value; }
}
private double theta;
public double Theta
{
get
{
return (Math.Atan(((middleTop - middleBottom) / (double)this.Height)) * 180) / Math.PI;
}
set
{
theta = value;
int deltaX = (int)((Math.Tan((Math.PI / 180) * value)) * this.Height / 2);
middleTop = middle + deltaX;
middleBottom = middle - deltaX;
}
}
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
SolidBrush left = new SolidBrush(Color.FromArgb(80, Color.Blue));
SolidBrush right = new SolidBrush(Color.FromArgb(80, Color.Green));
SolidBrush brush = new SolidBrush(Color.Red);
pe.Graphics.FillPolygon(left, new Point[4] { new Point(0,0), new Point(middleTop,0),
new Point(middleBottom, this.Height), new Point(0, this.Height)
});
pe.Graphics.FillPolygon(right, new Point[4] { new Point(this.Width,0), new Point(middleTop,0),
new Point(middleBottom, this.Height), new Point(this.Width, this.Height)
});
// Draw line
pe.Graphics.DrawLine(new Pen(Color.Red, 2), new Point(middleTop, handleRadius), new Point(middleBottom, this.Height - handleRadius));
Rectangle rectangle;
// Draw first handle
rectangle = new Rectangle(middleTop - handleRadius, 0, handleRadius * 2, handleRadius * 2);
pe.Graphics.FillEllipse(brush, rectangle);
// Draw second handle
rectangle = new Rectangle(middleBottom - handleRadius, this.Height - handleRadius * 2, handleRadius * 2, handleRadius * 2);
pe.Graphics.FillEllipse(brush, rectangle);
}
private Point moveLineTop;
private Point moveLineBottom;
protected override void OnMouseDown(MouseEventArgs e)
{
moveLineTop = new Point(e.X - middleTop, 0);
moveLineBottom = new Point(e.X - middleBottom, this.Height);
if (Math.Abs(e.X - middleTop) < handleRadius && Math.Abs(e.Y) <= handleRadius * 2)
{
Cursor.Current = Cursors.Hand;
mPointMoveInProgress = 1;
}
else if (Math.Abs(e.X - middleBottom) < handleRadius && Math.Abs(e.Y - this.Height) <= handleRadius * 2)
{
Cursor.Current = Cursors.Hand;
mPointMoveInProgress = 2;
}
else if (Math.Abs(e.X - x) < handleRadius && e.Y > handleRadius * 2 && e.Y < this.Height - handleRadius * 2)
{
Cursor.Current = Cursors.SizeWE;
mPointMoveInProgress = 3;
}
else mPointMoveInProgress = 0;
base.OnMouseDown(e);
}
private int x = 0;
protected override void OnMouseMove(MouseEventArgs e)
{
x = middleTop - (int)((e.Y * (middleTop - middleBottom)) / (double)this.Height);
if (mPointMoveInProgress == 1)
{
Cursor.Current = Cursors.Hand;
if (e.X > 0 && e.X < this.Width)
{
middleTop = e.X;
Refresh();
}
}
else if (mPointMoveInProgress == 2)
{
Cursor.Current = Cursors.Hand;
if (e.X > 0 && e.X < this.Width)
{
middleBottom = e.X;
Refresh();
}
}
else if (mPointMoveInProgress == 3)
{
if (e.X - moveLineTop.X >= 0 && e.X - moveLineTop.X <= this.Width &&
e.X - moveLineBottom.X >= 0 && e.X - moveLineBottom.X <= this.Width)
{
Cursor.Current = Cursors.SizeWE;
middleTop = e.X - moveLineTop.X;
middleBottom = e.X - moveLineBottom.X;
Refresh();
}
}
else
{
if (Math.Abs(e.X - middleTop) < handleRadius && Math.Abs(e.Y) <= handleRadius * 2)
Cursor.Current = Cursors.Hand;
else if (Math.Abs(e.X - middleBottom) < handleRadius && Math.Abs(e.Y - this.Height) <= handleRadius * 2)
Cursor.Current = Cursors.Hand;
else if (Math.Abs(e.X - x) < handleRadius && e.Y > handleRadius * 2 && e.Y < this.Height - handleRadius * 2)
Cursor.Current = Cursors.SizeWE;
else Cursor.Current = Cursors.Default;
}
base.OnMouseMove(e);
}
protected override void OnMouseUp(MouseEventArgs e)
{
mPointMoveInProgress = 0;
middle = (middleTop + middleBottom) / 2;
base.OnMouseUp(e);
}
}
Could anybody to help me with this? Give me some useful links or code samples.
Thanks!
You are looking for Adorners, they can draw items over other controls, and also handle events, etc.
Some tips:
How to: Implement an Adorner
Adorners How-To Topics