package com.hypixel.hytale.builtin.portals.utils.spatial; import com.hypixel.hytale.math.shape.Box; import com.hypixel.hytale.math.vector.Vector3d; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import java.util.List; import java.util.Map; public class OctTree { private static final int SIZE = 8; private static final int DEFAULT_NODE_CAPACITY = 4; private final OctTree.Node root; private final int nodeCapacity; public OctTree(double inradius) { this(new Box(-inradius, -inradius, -inradius, inradius, inradius, inradius), 4); } public OctTree(Box boundary) { this(boundary, 4); } public OctTree(Box boundary, int nodeCapacity) { this.root = new OctTree.Node(boundary); this.nodeCapacity = nodeCapacity; } public void add(Vector3d pos, T value) { this.add(this.root, pos, value); } private boolean add(OctTree.Node node, Vector3d pos, T value) { if (node != null && node.boundary.containsPosition(pos)) { if (node.size() < this.nodeCapacity) { node.addPoint(pos, value); return true; } else { if (node.dirs.isEmpty()) { this.subdivide(node); } for (int i = 0; i < 8; i++) { if (this.add(node.dirs.get(i), pos, value)) { return true; } } return false; } } else { return false; } } private void subdivide(OctTree.Node node) { Vector3d min = node.boundary.min; double side = node.boundary.width() / 2.0; for (int i = 0; i < 8; i++) { Vector3d subMin = new Vector3d(min.x + ((i & 1) > 0 ? side : 0.0), min.y + ((i & 2) > 0 ? side : 0.0), min.z + ((i & 4) > 0 ? side : 0.0)); Box sub = Box.cube(subMin, side); node.dirs.add(new OctTree.Node(sub)); } } public Map getAllPoints() { return this.queryRange(this.root.boundary); } public Map queryRange(Vector3d position, double inradius) { Box range = Box.centeredCube(position, inradius); return this.queryRange(range); } public Map queryRange(Box range) { Map out = new Object2ObjectOpenHashMap(); this.queryRange(this.root, range, out); return out; } private void queryRange(OctTree.Node node, Box range, Map out) { if (node != null && node.boundary.isIntersecting(range)) { for (int i = 0; i < node.size(); i++) { Vector3d point = node.points[i]; if (range.containsPosition(point)) { T value = node.values.get(i); out.put(value, point); } } for (OctTree.Node dir : node.dirs) { this.queryRange(dir, range, out); } } } private class Node { private final Box boundary; private final List.Node> dirs = new ObjectArrayList(8); private final Vector3d[] points = new Vector3d[OctTree.this.nodeCapacity]; private final List values = new ObjectArrayList(OctTree.this.nodeCapacity); private int count; public Node(Box boundary) { this.boundary = boundary; } public int size() { return this.count; } public void addPoint(Vector3d pos, T value) { this.points[this.count++] = pos; this.values.add(value); } } }