Skip to content

Commit

Permalink
bricks implemented into quad tree
Browse files Browse the repository at this point in the history
  • Loading branch information
Goby56 committed Jul 22, 2024
1 parent 770ccab commit e28bb97
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 267 deletions.
15 changes: 12 additions & 3 deletions src/main/java/com/goby56/wakes/simulation/Brick.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.goby56.wakes.simulation;

import net.minecraft.util.math.Vec2f;

import java.util.*;

public class Brick<T extends Position<T> & Age<T>> implements Iterable<T> {
Expand All @@ -9,13 +11,20 @@ public class Brick<T extends Position<T> & Age<T>> implements Iterable<T> {

public int occupied = 0;

public Brick(int capacity) {
assert (int) (Math.log(capacity) / Math.log(2)) % 2 == 0;
this.dim = (int) Math.sqrt(capacity);
public Brick(int width) {
this.dim = width;
this.capacity = dim * dim;
this.nodes = new ArrayList<>(capacity);
}

public boolean tick() {
return true;
}

public T get_global(int x, int z) {
return this.get(x % this.dim, z % this.dim);
}

public T get(int x, int z) {
return this.get(dim * z + x);
}
Expand Down
254 changes: 79 additions & 175 deletions src/main/java/com/goby56/wakes/simulation/QuadTree.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,245 +2,149 @@

import net.minecraft.client.render.Frustum;
import net.minecraft.util.math.Box;
import org.apache.commons.lang3.NotImplementedException;

import java.util.ArrayList;
import java.util.Stack;
import java.util.stream.Stream;
import java.util.*;

public class QuadTree<T extends Position<T> & Age<T>> {
private static final int CAPACITY = 4;
private static final int MAX_DEPTH = 21;
private static final int ROOT_X = (int) - Math.pow(2, 25);
private static final int ROOT_Z = (int) - Math.pow(2, 25);
private static final int ROOT_WIDTH = (int) Math.pow(2, 26);

private final QuadTree<T> ROOT;
private QuadTree<T> NE;
private QuadTree<T> NW;
private QuadTree<T> SW;
private QuadTree<T> SE;
private final QuadTree<T> PARENT;
private List<QuadTree<T>> children;

private final AABB bounds;
private final DecentralizedBounds bounds;
private final int depth;
private final ArrayList<T> nodes = new ArrayList<>(CAPACITY);
private Brick<T> brick;

public QuadTree(int x, int z, int width) {
this(x, z, width, 0, null);
public QuadTree() {
this(ROOT_X, ROOT_Z, ROOT_WIDTH, 0, null, null);
}

private QuadTree(int x, int z, int width, int depth, QuadTree<T> root) {
this.bounds = new AABB(x, z, width);
private QuadTree(int x, int z, int width, int depth, QuadTree<T> root, QuadTree<T> parent) {
this.bounds = new DecentralizedBounds(x, z, width);
this.depth = depth;
if (depth >= MAX_DEPTH) {
this.brick = new Brick<>(bounds.width());
}
this.ROOT = root == null ? this : root;
this.PARENT = parent;
}

public Stream<QuadTree<T>> iterateChildren() {
return Stream.of(this.NE, this.NW, this.SW, this.SE);
}

public void tick() {
Stack<Integer> indicesToDelete = new Stack<>();
int i = 0;
for (T node : nodes) {
if (!node.isDead()) {
node.tick();
} else {
indicesToDelete.add(i);
}
i++;
public boolean tick() {
if (brick != null) {
return brick.tick();
}
for (i = 0; i < indicesToDelete.size(); i++) {
this.nodes.remove((int) indicesToDelete.pop());
}

if (this.NE == null) {
return;
}

this.iterateChildren().forEach(QuadTree::tick);
if (this.iterateChildren().allMatch(t -> t.nodes.isEmpty()) && this.nodes.isEmpty()) {
this.prune();
}
}

private void tryAdd(T node) {
if (!node.inValidPos()) {
return;
}
ArrayList<T> nodesNearby = new ArrayList<>();
this.ROOT.query(new AABB(node.x(), node.z(), 1), nodesNearby);

boolean nodeAlreadyExists = false;
for (T otherNode : nodesNearby) {
if (node.equals(otherNode)) {
nodeAlreadyExists = true;
otherNode.revive(node);
}
}


if (!nodeAlreadyExists) {
this.nodes.add(node);
nodesNearby.forEach(node::updateAdjacency);
if (children == null) return false;
int aliveChildren = 0;
for (var tree : children) {
if (tree.tick()) aliveChildren++;
}
if (aliveChildren == 0) this.prune();
return aliveChildren > 0;
}

public boolean insert(T node) {
// TODO DUPLICATE INSERTIONS MAY STILL BE PRESENT. FIX
if (!this.bounds.contains(node.x(), node.z())) {
return false;
}

if (this.nodes.size() < CAPACITY) {
// TODO MAYBE FIX DUPLICATE INSERTIONS BY MAKING tryAdd RETURN BOOLEAN
this.tryAdd(node);
if (this.brick != null) {
brick.insert(node);
return true;
}

if (this.NE == null) {
if (!this.subdivide()) return false;
}

if (this.NE.insert(node)) return true;
if (this.NW.insert(node)) return true;
if (this.SW.insert(node)) return true;
return this.SE.insert(node);
}

public void query(AABB range, ArrayList<T> output) {
if (!this.bounds.intersects(range)) {
return;
}
for (T node : this.nodes) {
if (range.contains(node.x(), node.z())) {
output.add(node);
}
}
if (this.NE == null) {
return;
}

this.iterateChildren().forEach(t -> t.query(range, output));
}

public void query(Circle range, ArrayList<T> output) {
// TODO UNIFY query(AABB) AND query(Circle) TO ONE METHOD
if (!this.bounds.intersects(range)) {
return;
}
for (T node : this.nodes) {
if (range.contains(node.x(), node.z())) {
output.add(node);
}
}
if (this.NE == null) {
return;
if (children == null) this.subdivide();
for (var tree : children) {
if (tree.insert(node)) return true;
}
this.iterateChildren().forEach(t -> t.query(range, output));
return false;
}

public void query(Frustum frustum, int y, ArrayList<T> output) {
public void query(Frustum frustum, int y, ArrayList<Brick<T>> output) {
if (!frustum.isVisible(this.bounds.toBox(y))) {
return;
}
for (T node : this.nodes) {
if (frustum.isVisible(node.toBox())) {
output.add(node);
}
}
if (this.NE == null) {
if (brick != null) {
output.add(brick);
return;
}

this.iterateChildren().forEach(t -> t.query(frustum, y, output));
for (var tree : children) {
tree.query(frustum, y, output);
}
}

private boolean subdivide() {
if (this.bounds.width <= Math.sqrt(CAPACITY)) return false;
private void subdivide() {
if (brick != null) return;
int x = this.bounds.x;
int z = this.bounds.z;
int w = this.bounds.width >> 1;
this.NE = new QuadTree<>(x + w, z - w, w, depth + 1, this.ROOT);
this.NW = new QuadTree<>(x - w, z - w, w, depth + 1, this.ROOT);
this.SW = new QuadTree<>(x - w, z + w, w, depth + 1, this.ROOT);
this.SE = new QuadTree<>(x + w, z + w, w, depth + 1, this.ROOT);
return true;
}

public int count() {
int n = this.nodes.size();
if (this.NE == null) {
return n;
for (int i = 0; i < 4; i++) {
children.add(i, new QuadTree<>());
}
return n + this.NE.count() + this.NW.count() + this.SW.count() + this.SE.count();
children.add(0, new QuadTree<>(x, z, w, depth + 1, this.ROOT, this)); // NW
children.add(1, new QuadTree<>(x + w, z, w, depth + 1, this.ROOT, this)); // NE
children.add(2, new QuadTree<>(x, z + w, w, depth + 1, this.ROOT, this)); // SW
children.add(3, new QuadTree<>(x + w, z + w, w, depth + 1, this.ROOT, this)); // SE
}

public int getDepth() {
if (this.NE == null) {
return this.depth;
public int count() {
if (brick != null) {
return brick.occupied;
}
return this.iterateChildren().mapToInt(QuadTree::getDepth).max().getAsInt();
if (children == null) return 0;
return children.stream().reduce(0, (tot, tree) -> tot + tree.count(), Integer::sum);
}

public void prune() {
this.nodes.forEach(T::markDead);
this.nodes.clear();

if (this.NE == null) {
return;
if (children != null) {
for (var tree : children) {
tree.prune();
}
children.set(0, null);
children.set(1, null);
children.set(2, null);
children.set(3, null);
}
this.iterateChildren().forEach(QuadTree::prune);
this.NE = this.NW = this.SW = this.SE = null;
children = null;
}

private void distribute() {
// TODO METHOD THAT DISTRIBUTES METHOD CALLS TO ALL BRANCHES
throw new NotImplementedException();
}

public void getBBs(ArrayList<DebugBB> bbs, int height) {
bbs.add(new DebugBB(this.bounds.toDebugBox(height), this.depth));
if (this.NE == null) {
return;
public void getBrickBBs(ArrayList<DebugBB> bbs, int height) {
if (brick != null) {
bbs.add(new DebugBB(bounds.toDebugBox(height), depth));
}
if (children == null) return;
for (var tree : children) {
tree.getBrickBBs(bbs, height);
}
this.iterateChildren().forEach(t -> t.getBBs(bbs, height));
}

public record DebugBB(Box bb, int depth) {
}

public record AABB(int x, int z, int width) {
public record DecentralizedBounds(int x, int z, int width) {
public boolean contains(int x, int z) {
return this.x - this.width <= x && x <= this.x + this.width &&
this.z - this.width <= z && z <= this.z + this.width;
}

public boolean intersects(AABB other) {
return !(this.x - this.width > other.x + other.width ||
this.x + this.width < other.x - other.width ||
this.z - this.width > other.z + other.width ||
this.z + this.width < other.z - other.width);
return this.x <= x && x <= this.x + this.width &&
this.z <= z && z <= this.z + this.width;
}

public boolean intersects(Circle other) {
if (this.contains(other.x, other.z)) return true;
return !(this.x - this.width > other.x + other.radius ||
this.x + this.width < other.x - other.radius ||
this.z - this.width > other.z + other.radius ||
this.z + this.width < other.z - other.radius);
public boolean intersects(DecentralizedBounds other) {
return !(this.x > other.x + other.width ||
this.x + this.width < other.x ||
this.z > other.z + other.width ||
this.z + this.width < other.z);
}

public Box toBox(int y) {
return new Box(this.x - this.width, y - 0.5, this.z - this.width,
this.x + this.width, y + 0.5, this.z + this.width);
return new Box(this.x, y - 0.5, this.z,
this.x + this.width, y + 0.5, this.z + this.width);
}

public Box toDebugBox(int y) {
return new Box(this.x - this.width + 0.1, y - 1.2, this.z - this.width + 0.1,
this.x + this.width - 0.1, y - 1.23, this.z + this.width - 0.1);
}
}

public record Circle(int x, int z, int radius) {
public boolean contains(int x, int z) {
return Math.sqrt(Math.pow(this.x - x, 2) + Math.pow(this.z - z, 2)) <= radius;
return new Box(this.x + 0.1, y - 1.2, this.z + 0.1,
this.x + this.width - 0.1, y - 1.23, this.z + this.width - 0.1);
}

}
Expand Down
Loading

0 comments on commit e28bb97

Please sign in to comment.