
com.tvd12.gamebox.octree.OcTreeNode Maven / Gradle / Ivy
The newest version!
package com.tvd12.gamebox.octree;
import com.tvd12.gamebox.entity.PositionAware;
import com.tvd12.gamebox.math.Bounds;
import com.tvd12.gamebox.math.Vec3;
import lombok.Setter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
public class OcTreeNode {
@Setter
private OcTreeNode parentNode = null;
private final int maxItems;
private final float minNodeSize;
private final Bounds bounds;
private final Set items = new HashSet<>();
private final List> children = new ArrayList<>();
private static final int NUM_CHILDREN = 8;
public OcTreeNode(Bounds bounds, int maxItems, float minNodeSize) {
if (minNodeSize <= 0) {
throw new IllegalArgumentException(
"minNodeSize must > 0 to avoid StackOverflow"
);
}
this.bounds = bounds;
this.maxItems = maxItems;
this.minNodeSize = minNodeSize;
}
public OcTreeNode insert(T newItem) {
if (!this.bounds.containsPosition(newItem.getPosition())) {
return null;
}
if (isLeaf()) {
if (this.items.size() < maxItems || this.bounds.getMaxDimension() < 2 * minNodeSize) {
this.items.add(newItem);
return this;
}
createChildren();
passItemsToChildren();
}
return insertItemToChildren(newItem);
}
private void createChildren() {
for (int i = 0; i < NUM_CHILDREN; ++i) {
Bounds bounds = this.bounds.getOctant(i);
OcTreeNode child = new OcTreeNode<>(bounds, maxItems, minNodeSize);
this.children.add(child);
child.setParentNode(this);
}
}
private void passItemsToChildren() {
this.items.forEach(
this::insertItemToChildren
);
this.items.clear();
}
private OcTreeNode insertItemToChildren(T item) {
for (OcTreeNode child : this.children) {
OcTreeNode nodeContainingInsertedItem = child.insert(item);
if (nodeContainingInsertedItem != null) {
return nodeContainingInsertedItem;
}
}
return null;
}
public boolean remove(T item) {
if (!this.bounds.containsPosition(item.getPosition())) {
return false;
}
if (isLeaf()) {
return removeItemFromThisLeaf(item);
}
return removeFromChildren(item);
}
private boolean removeItemFromThisLeaf(T item) {
if (!this.items.contains(item)) {
return false;
}
this.items.remove(item);
tryMergingChildrenOfParentNode();
return true;
}
private boolean removeFromChildren(T item) {
for (OcTreeNode child : this.children) {
boolean isPlayerRemoved = child.remove(item);
if (isPlayerRemoved) {
return true;
}
}
return false;
}
private void tryMergingChildrenOfParentNode() {
if (this.parentNode != null && this.parentNode.countItems() <= maxItems) {
this.parentNode.mergeChildren();
}
}
private void mergeChildren() {
List itemsInChildren = new ArrayList<>();
getItemsInChildren(itemsInChildren);
this.items.addAll(itemsInChildren);
this.children.clear();
tryMergingChildrenOfParentNode();
}
private void getItemsInChildren(List players) {
if (isLeaf()) {
players.addAll(this.items);
this.items.clear();
return;
}
for (OcTreeNode child : this.children) {
child.getItemsInChildren(players);
}
}
public int countItems() {
if (isLeaf()) {
return this.items.size();
}
return countItemsFromChildren();
}
private int countItemsFromChildren() {
int count = 0;
for (OcTreeNode child : this.children) {
count += child.countItems();
}
return count;
}
public List search(Bounds searchBounds, List matches) {
if (!this.bounds.doesOverlap(searchBounds)) {
return matches;
}
if (isLeaf()) {
return searchFromThisLeaf(searchBounds, matches);
}
return searchFromChildren(searchBounds, matches);
}
private List searchFromThisLeaf(Bounds searchBounds, List matches) {
for (T item : this.items) {
if (searchBounds.containsPosition(item.getPosition())) {
matches.add(item);
}
}
return matches;
}
private List searchFromChildren(Bounds searchBounds, List matches) {
for (OcTreeNode child : this.children) {
child.search(searchBounds, matches);
}
return matches;
}
protected OcTreeNode findNodeContainingPosition(Vec3 position) {
if (!this.bounds.containsPosition(position)) {
return null;
}
if (isLeaf()) {
return this;
}
return findNodeContainingPositionFromChildren(position);
}
private OcTreeNode findNodeContainingPositionFromChildren(Vec3 position) {
for (OcTreeNode child : this.children) {
OcTreeNode node = child.findNodeContainingPosition(position);
if (node != null) {
return node;
}
}
return null;
}
public boolean isLeaf() {
return this.children.isEmpty();
}
@Override
public String toString() {
return '(' +
"bounds=" + bounds +
", items=" + items +
", children=" + children +
')';
}
public String toPrettyString(int level) {
String spaces = level <= 0
? ""
: String.format("%" + level * 2 + 's', "");
return spaces + "(\n" +
spaces + " bounds=" + bounds + ",\n" +
spaces + " items=" + items + ",\n" +
spaces + (
children.isEmpty()
? " children=[]\n"
: " children=[\n" + children.stream()
.map(it -> it.toPrettyString(level + 1))
.collect(Collectors.joining(",\n")) +
'\n' + spaces + " ]\n"
) + spaces + ')';
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy