I am creating a quadtree for a 2D game in C# to make the rendering faster. Some of the objects (planets) are moving and that is why, I created a method called removeAndUpdate to update the tree by deleting and reinserting the specific object from the leaves (so not from the root) as I have already read in link. Unfortunately, I got System.OutOfMemoryException after some time at the reinsertion part. I am quite sure, that the removeAndUpdate method has some problems.
Maybe it is good to mention, that when a quad is subdivided (in case, when the quad's storing capacity is full), the content will not distributed among the subquads. I made this way to improve some performance. However, I don't think it has some connection with the problem above.
I guess, there is an infinite loop somewhere, which causes to run out of memory. If I insert new objects from the root, there is no problem.
I wrote a comment to the place where the exception occurs.
Here you can see the important parts of the QuadTree class:
public class QuadTree
{
BoundingBox boundary;
int capacity;
List<WorldObject> leafContent;
public QuadTree parent;
bool divided;
public List<QuadTree> childQuadTrees;
public QuadTree(BoundingBox _boundary, int _capacity, QuadTree _parent)
{
this.boundary = _boundary;
this.capacity = _capacity;
this.leafContent = new List<WorldObject>();
this.childQuadTrees = new List<QuadTree>();
this.divided = false;
this.parent = _parent;
}
// Subdividing the quad, if its storing capacity is full.
public void subdivide()
{
// North-East
this.childQuadTrees.Add(new QuadTree(new BoundingBox(new Vector3(boundary.Center.X, 0, boundary.Min.Z),
new Vector3(boundary.Max.X, 0, boundary.Center.Z)), this.capacity, this));
// North-West
this.childQuadTrees.Add(new QuadTree(new BoundingBox(new Vector3(boundary.Min.X, 0, boundary.Min.Z),
new Vector3(boundary.Center.X, 0, boundary.Center.Z)), this.capacity, this));
// South-East
this.childQuadTrees.Add(new QuadTree(new BoundingBox(boundary.Center, boundary.Max), this.capacity, this));
// South-West
this.childQuadTrees.Add(new QuadTree(new BoundingBox(new Vector3(boundary.Min.X, 0, boundary.Center.Z), new Vector3(boundary.Center.X, 0, boundary.Max.Z)), this.capacity, this));
this.divided = true;
}
public void insert(WorldObject wO)
{
// Checking, whether the quad contains the box at all.
if (!this.ContainOrOverlap(wO.BoundingBox))
{
return;
}
// If there is space, insert the box.
if (this.leafContent.Count < this.capacity && this.divided == false)
{
/*This is the instruction, where System.OutOfMemoryException occures*/
this.leafContent.Add(wO); // <-------
return;
}
// If not, subdivide the quad then insert the obj to the subquads.
else
{
if (this.divided == false)
{
this.subdivide();
}
this.childQuadTrees[0].insert(wO);
this.childQuadTrees[1].insert(wO);
this.childQuadTrees[2].insert(wO);
this.childQuadTrees[3].insert(wO);
}
}
/* Here is the method to update the moving objects bounding volume.
It first removes the element from the tree, updates the boundingbox, then reinsert the object from the leaves*/
public void removeAndUpdate(WorldObject obj, Vector3 displacement)
{
if (!this.ContainOrOverlap(obj.BoundingBox))
{
return;
}
if (this.divided)
{
this.childQuadTrees[0].removeAndUpdate(obj, displacement);
this.childQuadTrees[1].removeAndUpdate(obj, displacement);
this.childQuadTrees[2].removeAndUpdate(obj, displacement);
this.childQuadTrees[3].removeAndUpdate(obj, displacement);
}
/* if the obj is found, remove it, the try to reinsert it to its parent. If its parent does not contain it, move upwards in the hierarchy and try again to insert*/
if (leafContent.Contains(obj))
{
this.leafContent.Remove(obj);
QuadTree q = this.parent;
while (q.ContainOrOverlap(obj.BoundingBox) == false)
{
q = q.parent;
}
obj.BoundingBox.Translate(displacement);
q.insert(obj);
}
}
public void query(BoundingBox range, List<WorldObject> resultList)
{
if (!this.ContainOrOverlap(range))
{
return;
}
if (this.divided)
{
this.childQuadTrees[0].query(range, resultList);
this.childQuadTrees[1].query(range, resultList);
this.childQuadTrees[2].query(range, resultList);
this.childQuadTrees[3].query(range, resultList);
}
resultList.AddList(this.leafContent);
}
}
Here is some info from the debug at the break of the exception:
Related
I am trying to traverse what I believe is called a binomial tree. However, there is a problem with traversing the bottom left node.
This is a representation of the data structure
This is the code. I feel it is pretty close to a solution but not quite -- there is something missing and it might be something minor. When the bottom left node is to be traversed, the binomial function has low == high, so it doesn't traverse it.
private State? BFS()
{
State state = new State();
foo(0,items.Count,state);
return bestState;
}
private void foo(int low, int high, State state)
{
int lCount = low;
while (lCount < high)
{
State newState = new State(items[lCount++], state);
if (predicate(newState))
{
bestState = newState;
}
queue.Enqueue(newState);
}
while(low<high)
{
foo(++low, high, queue.Dequeue());
}
}
I'm developing a search game, where players must look for certain objects. Whenever the targeted object is found and has been picked up, the player wins and go to the next level. I tagged the targeted objects as "TargetObj". I successfully implemented this when there is only one object to look for. I want to modify my code to include cases where there is more than one object to look for. Here is my code :
public void someFunction() {
//if we press the button of choice
if (Input.GetKeyDown(KeyCode.Space)) {
//and we're not holding anything
if (currentlyPickedUpObject == null) {
//and we are looking an interactable object
if (lookObject != null) {
PickUpObject();
}
} else { //if we press the pickup button and have something, we drop it
BreakConnection();
}
}
}
/* ommitted lines */
public void PickUpObject() {
if (GameObject.FindGameObjectsWithTag("TargetObj").Length == 1 & lookObject.tag == "TargetObj") {
physicsObject = lookObject.GetComponentInChildren<PhysicsObjects>();
currentlyPickedUpObject = lookObject;
pickupRB = currentlyPickedUpObject.GetComponent<Rigidbody>();
pickupRB.constraints = RigidbodyConstraints.FreezeRotation;
physicsObject.playerInteractions = this;
winUI.SetActive(true);
Time.timeScale = 0f;
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
Time.timeScale = 1f;
} else if (GameObject.FindGameObjectsWithTag("TargetObj").Length > 1) {
} else {
physicsObject = lookObject.GetComponentInChildren<PhysicsObjects>();
currentlyPickedUpObject = lookObject;
pickupRB = currentlyPickedUpObject.GetComponent<Rigidbody>();
pickupRB.constraints = RigidbodyConstraints.FreezeRotation;
physicsObject.playerInteractions = this;
}
}
I added this line, to check if there is more than one object to look for.
else if (GameObject.FindGameObjectsWithTag("TargetObj").Length > 1)
How to implement this (if the player picked up all objects of tag "TargetObj", go to next level.)?
A fast way of doing this is to keep a counter of picked objects. Then, if the counter is equal with the number of objects with the "TargetObj" tag, then the player wins. As snippet you can have something like this:
Gameobject[] targetObjects; // an array where you will keep your objects with "TargetObj" tag
List<GameObject> targetObjectsList;
void Start()
{
targetObjects = GameObject.FindGameObjectsWithTag("TargetObj");
targetObjectsList = new List<GameObject>();
}
.
.
.
// In your method (You didn't put all your code so I will use your snippet)
if (Input.GetKeyDown(KeyCode.Space))
{
//and we're not holding anything
if (currentlyPickedUpObject == null)
{
//and we are looking an interactable object
if (lookObject != null )
{
PickUpObject();
// I suppose that "lookObject" is the gameobject that you want to pickup. If not, replase this variable with the right gameobject.
if(!targetObjectsList.Contains(lookObject.gameObject))
{
targetObjectsList.Add(lookObject.gameObject);
if (targetObjectsList.Count == targetObjects.Length)
{
//Finish the game
winUI.SetActive(true);
Time.timeScale = 0f;
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
Time.timeScale = 1f;
}
}
}
}
}
//if we press the pickup button and have something, we drop it
else
{
BreakConnection();
}
}
Then you modify your PickUpObject method, just to pick and drop objects.
I'm sorry if I miss something. I wrote this without an editor and I didn't test the code, so please tell me if is something that I miss.
I have a working collision system in place in my Monogame/C# project that uses a quadtree to determine potential collisions, and test them accordingly. My problem is that I want to be able to send out a method to the entity (that has been collided with), either OnCollisionEnter() or OnCollisionStay() depending on if the collision was present in the previous frame.
In the following code, OnCollisionEnter() is never called:
public class Collision : iSystem
{
private List<Entity> currentCollisions; // Collisions occurring in the current frame
private List<Entity> previousCollisions; // Collisions from the previous frame
public override void Initialize(Entity caller)
{
currentCollisions = new List<Entity>();
previousCollisions = new List<Entity>();
base.Initialize(caller);
hasInitialized = true;
}
public override void Update(GameTime gameTime)
{
List<Entity> nearby = Global.QuadTree.GetNeighbours(parent);
for (int i = 0; i < nearby.Count; i++)
{
if (nearby[i] != parent)
if (parent.Collider.Intersects(nearby[i].Collider))
{
currentCollisions.Add(nearby[i]);
AlertEntity(nearby[i]);
}
}
previousCollisions = currentCollisions; // Ready for the next frame
currentCollisions.Clear();
base.Update(gameTime);
}
public void AlertEntity(Entity entity)
{
if (previousCollisions.Contains(entity))
parent.OnCollisionStay(entity.Collider);
else
parent.OnCollisionEnter(entity.Collider); // This is never called
}
}
('parent' refers to the entity that the component 'Collision' is attached to)
If you could suggest why this is happening, I'd be grateful. Thanks
Calling currentCollisions.Clear() will also clear previousCollisions because they are both referencing the same List object. Instead of calling currentCollisions.Clear(), you should set it to a new List<Entity>().
// . . .
previousCollisions = currentCollisions; // Ready for the next frame
currentCollisions = new List<Entity>(); // Now both lists are not clear
base.Update(gameTime);
I've been trying without much luck to develop an algorithm to sort a collection of closed geometric figures based on whether one shape is completely enclosed within the perimeter of another. When completely analyzed, I should end up with a tree structure that defines the hierarchy.
I can take care of the actual comparison, which is whether one shape is completely within the perimeter of another. I'm having difficulty though with the sorting of unorganized input. I suspect that the solution involves binary tree structures and recursive code, which I've never been strong with.
Geometric data will have already been sanitized prior to generating the sorted hierarchy data, so issues like open paths, overlapping, partially overlapping and self-intersecting shouldn't be an issue.
Below is a group of test figures I've been working with that may help to illustrate my question.
As a human, I can see that the yellow shape is not within the blue one, nor is the blue within the yellow. They are both within the green shape, which is within the red... and so on. (Apologies to those who are color blind)
The resultant tree would be as follows:
I'm working in C# but don't figure it's relevant to the question.
Thank you
EDIT 1
A more concise question might be "How do I generate this tree with the correct order?" (given data in no particular order). Is this just your basic textbook "binary search tree insertion" that I'm over-thinking maybe?
EDIT 2
Attempting to convert Norlesh's pseudo-code into c# and tie it into my existing code, I ended up with the following:
// Return list of shapes contained within container contour but no other
private List<NPContour> DirectlyContained(NPContour container, List<NPContour> contours)
{
List<NPContour> result = new List<NPContour>();
foreach (NPContour contour in contours)
{
if (container.Contains(contour))
{
foreach (NPContour other in contours)
{
if (other.Contains(contour))
break;
result.Add(contour);
}
}
}
return result;
}
// Recursively build tree structure with each node being a list of its children
private void BuildTree(NPContourTreeNode2 parent, List<NPContour> contours)
{
List<NPContour> children = DirectlyContained(parent.Contour, contours);
if (children.Count > 0)
{
// TODO: There's probably a faster or more elegant way to do this but this is clear and good enough for now
foreach (NPContour child in children)
{
contours.Remove(child);
parent.Children.Add(new NPContourTreeNode2(child));
}
foreach (NPContourTreeNode2 child in parent.Children)
{
BuildTree(child, contours);
}
}
}
... And the calling code ....
List<NPContour> contours = new List<NPContour>();
List<NPContour> _topLevelContours = new List<NPContour>();
bool contained = false;
foreach (NPChain chain in _chains)
{
if (chain.Closed)
{
NPContour newContour = new NPContour(chain);
contours.Add(newContour);
}
}
//foreach (NPContour contour in contours)
for (int i = 0; i < contours.Count(); i++)
{
contained = false;
foreach (NPContour container in contours)
{
if (container.Contains(contours[i]))
{
contained = true;
continue;
}
}
if (contained == false)
{
_topLevelContours.Add(contours[i]);
contours.Remove(contours[i]);
}
}
foreach (NPContour topLevelContour in _topLevelContours)
{
NPContourTreeNode2 topLevelNode = new NPContourTreeNode2(topLevelContour);
BuildTree(topLevelNode, contours);
}
I'm thinking I must have misinterpreted something in the translation because it isn't working. I'm going to keep plugging away at it but thought I'd post the code here in hopes someone may help point out my error.
Note that there was a discrepancy in the pseudocode in that buildTree didn't return anything, but in the calling code a return value is appended to ... well, I got a bit confused where exactly it was supposed to be going there anyway. I got the general idea of the example but I think there may have been some important points that were lost on me.
So far in my brief debugging, I seem to get more than one top level shape from the example below (whereas there should only be one) and multiples of the various children (something like 55?). I hope to be able to give more debugging information later.
Here is some pseudo code that should achieve what your trying to do:
// return true if shape is enclosed completely inside container
function contains(container, shape);
// return list of shapes contained within container shape but no other.
function directlyContained(container, shapes) {
result = []
for (shape in shapes) {
if (contains(container, shape)) {
// check its not further down hierarchy
for (other in shapes) {
if (contains(other, shape)) {
break // not the top level container
}
result.append(shape)
}
}
}
return result;
}
// recursively build tree structure with each node being a list of its children
// - removes members of shapes list as they are claimed.
function buildTree(parent, shapes) {
children = directlyContained(parent, shapes)
if (children.length > 0) {
shapes.remove(children);
parent.append(children);
for (child in children) { // recall on each child
buildTree(child, shapes);
}
}
}
function findTopLevel(shapes) {
result = []
// find the one or more top level shapes that are not contained
for shape in shapes {
contained = false;
for (container in shapes) {
if (contains(container, shape)) {
contained = true;
continue;
}
}
if (contained = false) {
scene.append(shape);
shapes.remove(shape);
}
}
return result;
}
shapes = <...>; // list initialized with the unsorted shapes
scene = findTopLevel(shapes);
shapes.remove(scene);
for (top in scene) {
buildTree(top, shapes);
}
I am using the A* pathfinding algorithm taken from here http://arongranberg.com/astar/docs/ and I am trying to make an object move from a random point to another random point in a loop system in unity.This is the code used to move the object: I tried to put the points into an array but it didnt work. The author says that If I want additional behaviour after the AI reached its destination I should write the code in the OnTargetReached() method, but I am not sure exactly how to. If you've got any ideas, even the smallest would be very helpful.
public virtual void SearchPath () {
//if (target == null)
//{ Debug.LogError ("Target is null, aborting all search"); canSearch = false; return; }
lastRepath = Time.time;
//This is where we should search to
//Vector3 [] position = new Vector3[2];
//position[0] = new Vector3(Random.Range(-2,-7), 0, Random.Range(21,26));
//position[1] = new Vector3(Random.Range(19,23), 0, Random.Range (28,31));
//position[2] =
canSearchAgain = false;
//Alternative way of requesting the path
//Path p = PathPool<Path>.GetPath().Setup(GetFeetPosition(),targetPoint,null);
//seeker.StartPath (p);
//We should search from the current position
seeker.StartPath (GetFeetPosition(),targetPosition);
}
public virtual void OnTargetReached () {
//End of path has been reached
//If you want custom logic for when the AI has reached it's destination
//add it here
//You can also create a new script which inherits from this one
//and override the function in that script
//Vector3 new_targetPosition = new Vector3(Random.Range(19,23), 0, Random.Range (28,31));
//Vector3 new_targetPosition = new Vector3(19,0,28);
seeker.StartPath (GetFeetPosition(),new_targetPosition);
}
stick a bunch of nodes in your scene (just empty unity objects)
name them node1,2,3,4,5 make your loop / path and number them in order.
Make an pathManager script that has a public transform[] nodeLoop; array and drag your nodes onto the array in order.
Now you have a list of node/postions.
Now jsut hook it up to your existing OnTargetReached()
make a function that just gets the next node position...
something like this
void OnTargetReached ()
{
new_targetPosition = pathManager.m.getNextPathPoint()
}
pathmanager has something like this...
int pathPoint=0;
Vector3 getNextPathPoint()
{
pathPoint++;
if(pathPoint >= nodeLoop.length)
pathPoint=0;
return nodeLoop[pathPoint];
}
sorry for the hasty pseudocode, but you should get the idea