我遇到了我在C#中工作的Quad Tree实现的问题。在文件Tree.cs中,以下行将导致堆栈溢出异常,始终在树中的大约50个对象开始(可能足以导致右下角的第一个分支):
else
{
//bottom right
TreeList[3].PushB(b);
return;
}
出于某种原因,似乎当我允许调用此代码时,它会创建一个无限循环,因此Stack Overflow Exception
。我不明白为什么这会导致无限递归,而其他人则没有。
这是代码。 Ball.cs和Tree.cs都位于Classes文件夹中。
Ball.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace QuadTree.Classes
{
class Ball
{
protected int x, y, r;
protected decimal vx, vy;
public static int min_w = 0,
max_w = 200,
min_h = 0,
max_h = 200;
//treating origin as top-left of screen
public Ball(int set_x = 1, int set_y = 1, decimal set_vx = 1, decimal set_vy = 1, int set_r = 1)
{
x = set_x;
y = set_y;
vx = set_vx;
vy = set_vy;
r = set_r;
}
public int get_x()
{
return x;
}
public int get_y()
{
return y;
}
public void Print()
{
Console.WriteLine("x: {0} y: {1} vx: {2} vy: {3} r: {4}", x, y, vx, vy, r);
}
//get the y-intercept of the current ball
protected decimal getB()
{
return (decimal)y - ((vy / vx) * (decimal)x);
}
//get the y-intercept given an x, y, and slope
public decimal getB(int x, int y, decimal m)
{
return (decimal)y - (m * (decimal)x);
}
//get the slop of the line that goes through both balls
protected decimal getM(Ball b)
{
return getM(y, b.y, x, b.x);
}
//get the slop of the line going through two points
public decimal getM(int y1, int y2, int x1, int x2)
{
if (x1 - x2 == 0)
{
return 0;
}
else
{
return ((decimal)(y1 - y2)) / ((decimal)(x1 - x2));
}
}
public void Move()
{
x += (int)vx;
y += (int)vy;
if (x > max_w)
{
vx *= -1;
x = x - (x - max_w);
}
else if (x < min_w)
{
vx *= -1;
x *= -1; //won't work if min_w != 0
}
if(y > max_h)
{
vy *= -1;
y = y - (y - max_h);
}
else if (y < min_h)
{
vy *= -1;
y *= -1; //won't work if min_h !=0
}
}
//detect if the current ball collides with the given ball
public void Collide(Ball b)
{
decimal d;
d = (decimal)Math.Sqrt(Math.Pow((x - b.x), 2) + Math.Pow((y - b.y), 2));
if (d<= r || d <= b.r)
{
ResolveCollision(b);
}
return;
}
//determine the resulting vectors after the collision
private void ResolveCollision(Ball b)
{
//get the line between the center points
decimal M;
M = getM(b);
//determine angle between the line and ball a
double theta_1;
if (b.vx != 0)
{
double top = (double)((M - (b.vy / b.vx)));
double bottom = (double)(1 + (M * (b.vy / b.vx)));
if (bottom != 0)
{
theta_1 = Math.Atan(top / bottom);
}
else
{
theta_1 = 0;
}
}
else
{
if (1 + M != 0)
{
theta_1 = Math.Atan((double)(M / (1 + M)));
}
else
{
theta_1 = 0;
}
}
theta_1 = theta_1 * (Math.PI / 180);
//calculate new vx and vy for ball a
//http://www.gamefromscratch.com/post/2012/11/24/GameDev-math-recipes-Rotating-one-point-around-another-point.aspx
double new_vx, new_vy;
new_vx = Math.Cos(theta_1) * (double)(vx) - Math.Sin(theta_1) * (double)(vy) + x;
new_vy = Math.Sin(theta_1) * (double)(vx) + Math.Cos(theta_1) * (double)(vy) + y;
vx = (decimal)new_vx - x;
vy = (decimal)new_vy - y;
//determine angle between the line and ball b
if (b.vx != 0)
{
double top = (double)((M - (b.vy / b.vx)));
double bottom = (double)(1 + (M * (b.vy / b.vx)));
if (bottom != 0)
{
theta_1 = Math.Atan(top / bottom);
}
else
{
theta_1 = 0;
}
}
else
{
if (1 + M != 0)
{
theta_1 = Math.Atan((double)(M / (1 + M)));
}
else
{
theta_1 = 0;
}
}
theta_1 = theta_1 * (Math.PI / 180);
//calculate new vx and vy for ball a
new_vx = Math.Cos(theta_1) * (double)(b.vx) - Math.Sin(theta_1) * (double)(b.vy) + b.x;
new_vy = Math.Sin(theta_1) * (double)(b.vx) + Math.Cos(theta_1) * (double)(b.vy) + b.y;
b.vx = (decimal)new_vx - x;
b.vy = (decimal)new_vy - y;
}
}
}
Tree.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace QuadTree.Classes
{
class Tree //: IDisposable
{
protected int min_w,
max_w,
min_h,
max_h,
thresh_hold, level;
bool leaf = true;
protected List<Ball> BallList = new List<Ball>();
protected List<Tree> TreeList = new List<Tree>();
public Tree(int set_min_w, int set_max_w, int set_min_h, int set_max_h, int set_thresh_hold, int set_level)
{
min_w = set_min_w;
max_w = set_max_w;
min_h = set_min_h;
max_h = set_max_h;
thresh_hold = set_thresh_hold;
level = set_level;
}
//push a ball onto the tree
public void PushB(Ball b)
{
if(leaf)
{
BallList.Add(b);
if (BallList.Count > thresh_hold)
{
Branch();
}
}
else
{
LeafPush(b); //push the ball to a leaf node
}
return;
}
//push a ball onto a leaf of the current node
protected void LeafPush(Ball b)
{
if (b.get_x() <= max_w / 2)
{
//left
if (b.get_y() <= max_h / 2)
{
//top left
TreeList[0].PushB(b);
return;
}
else
{
//bottom left
TreeList[2].PushB(b);
return;
}
}
else
{
//right
if (b.get_y() <= max_h / 2)
{
//top right
TreeList[1].PushB(b);
return;
}
else
{
//bottom right
TreeList[3].PushB(b);
return;
}
}
}
private void Branch()
{
Console.WriteLine("Branching level {0}", level);
leaf = false;
TreeList.Add(new Tree(min_w, max_w / 2, min_h, max_h / 2, thresh_hold, level + 1)); //top left
TreeList.Add(new Tree((max_w / 2) + 1, max_w, min_h, max_h / 2, thresh_hold, level + 1)); //top right
TreeList.Add(new Tree(min_w, max_w / 2, (max_h / 2) + 1, max_h, thresh_hold, level + 1)); //bottom left
TreeList.Add(new Tree((max_w / 2) + 1, max_w, (max_h / 2) + 1, max_h, thresh_hold, level + 1)); //bottom right
foreach(Ball b in BallList)
{
LeafPush(b);
}
BallList.Clear();
return;
}
}
}
Program.cs的
using QuadTree.Classes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace QuadTree
{
class Program
{
static void Main(string[] args)
{
Random rnd = new Random();
List<Ball> BallList = new List<Ball>();
for (int i = 0; i < 100; i++)
{
BallList.Add(new Ball(rnd.Next(Ball.min_w, Ball.max_w),
rnd.Next(Ball.min_h, Ball.max_h),
rnd.Next(1, 5),
rnd.Next(1, 5),
rnd.Next(1, 5)));
}
Tree t = new Tree(Ball.min_w, Ball.max_w, Ball.min_h, Ball.max_h, 10, 0);
foreach (Ball b in BallList)
{
b.Move();
t.PushB(b);
}
Console.ReadLine();
}
}
}
答案 0 :(得分:1)
您需要修改您创建子树的方式。当您创建第四个子树(右下象限)时,您将使用以下数字:
(max_w / 2) + 1, max_w, (max_h / 2) + 1, max_h
这总是会产生右下象限分支的相同尺寸(101,200,101,200),因为您只使用了最大数字。对于每个后续分支中的右下象限也是如此。
程序将运行正常,直到您达到第四个子树的阈值。然后它尝试分支,并且当它分支时,它将所有它的球发送到随后的第四子树中。这将继续发生,因为所有这些球在该象限中都有坐标。这就是你的无限循环发生的地方。
如果您试图继续细分象限,则需要将新尺寸基于父象限的最小和最大宽度和高度。
编辑:
此代码应正确细分象限:
int center_w = min_w + (max_w - min_w) / 2;
int center_h = min_h + (max_h - min_h) / 2;
TreeList.Add(new Tree(min_w, center_w, min_h, center_h,
thresh_hold, level + 1)); // top left
TreeList.Add(new Tree(center_w + 1, max_w, min_h, center_h,
thresh_hold, level + 1)); //top right
TreeList.Add(new Tree(min_w, center_w, center_h + 1, max_h,
thresh_hold, level + 1)); //bottom left
TreeList.Add(new Tree(center_w + 1, max_w, center_h + 1, max_h,
thresh_hold, level + 1)); //bottom right
答案 1 :(得分:0)
所以我似乎需要逻辑来测试创建一组新的节点是否可行。我决定要一个节点的最小宽度为10,所以我更改了这段代码:
//push a ball onto the tree
public void PushB(Ball b)
{
if(leaf)
{
BallList.Add(b);
if (BallList.Count > thresh_hold)
{
//test if branching will produce a viable area
if ((max_w / 2) - min_w >= 10)
{
Branch();
}
}
}
else
{
LeafPush(b); //push the ball to a leaf node
}
return;
}
现在我不再获得Stack Overflow异常