try new approach based on interval maps

they resolve the problems and the redundancies of interval trees, while giving a serius memory usage benefit (20% of the one used by interval trees in test scene)

test scene is a world made of a cube of sides RENDER_DISTANCE*2 with player at the center
treemaps-queues-iteration
EmaMaker 2022-08-16 21:45:26 +02:00
parent f8a31093cd
commit a96c09df4b
4 changed files with 140 additions and 53 deletions

View File

View File

@ -0,0 +1,74 @@
package com.emamaker.voxeltest.intervaltrees.data;
import java.util.ArrayList;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
public class IntervalMap <V>{
public TreeMap<Integer, V> treemap = new TreeMap<>();
int terminator = Integer.MAX_VALUE;
ArrayList<Integer> toDelete = new ArrayList<>();
public void insert(int start, int end, V value){
// if(c.compare(start, end) > 0 || c.compare(start, end) == 0)return;
if(start >= end) return;
// take the last key before end
Map.Entry<Integer,V> upper_bound = treemap.lowerEntry(end); //'higherEntry' makes sure that there's no equal case, otherwise the node could be removed as soon as it is inserted
Map.Entry<Integer, V> upper_entry = treemap.higherEntry(end);
Map.Entry<Integer, V> lower_entry = treemap.lowerEntry(start);
// insert the start key. Replaces whatever value is already there. Do not place if the lowerEntry is of the same value
if(lower_entry == null || ( lower_entry.getValue() != null && !lower_entry.getValue().equals(value))) treemap.put(start, value);
if(upper_entry == null){
if(upper_bound != null ) treemap.put(end+1, upper_bound.getKey() > start ? upper_bound.getValue():null);
else treemap.put(end+1, null);
}else{
if(upper_entry.getValue() != null && !upper_entry.getValue().equals(value) && upper_entry.getKey() > end + 1) treemap.put(end+1, upper_bound.getValue());
else treemap.put(end+1, upper_bound.getValue());
}
for(Integer k : treemap.subMap(start, lower_entry != null && value == lower_entry.getValue(), end, true).keySet()){
treemap.remove(k);
}
}
public void insertTerminator(int start){
treemap.put(start, null);
terminator=start;
}
public Integer begin(){
return treemap.firstKey();
}
public Integer end(){
return treemap.lastKey();
}
Map.Entry<Integer,V> e;
public V valueAtKey(Integer key){
return treemap.floorEntry(key).getValue();
}
public SortedMap<Integer, V> valuesBetween(Integer start, boolean startInclusive, Integer end, boolean endInclusive){
return treemap.subMap(start, startInclusive, end, endInclusive);
}
public SortedMap<Integer, V> valuesBetween(Integer start , Integer end){
return treemap.subMap(start, true, end, false);
}
V oldValue = null;
Integer oldK = null;
public void print() {
System.out.println("-------");
for (Integer k : treemap.keySet()) {
if (oldK != null) {
System.out.println("[" + oldK + ", " + k + ") --> " + oldValue);
}
oldK = k;
oldValue = treemap.get(k);
}
oldK = null;
}
}

View File

@ -1,33 +1,37 @@
package com.emamaker.voxeltest.intervaltrees.world;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Queue;
import java.util.TreeMap;
import com.emamaker.voxeltest.intervaltrees.data.Interval;
import com.emamaker.voxeltest.intervaltrees.data.IntervalMap;
import com.emamaker.voxeltest.intervaltrees.data.IntervalTree;
import com.emamaker.voxeltest.intervaltrees.utils.Config;
import com.emamaker.voxeltest.intervaltrees.utils.Utils;
import com.emamaker.voxeltest.intervaltrees.world.blocks.Blocks;
import com.jme3.material.Material;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
public class Chunk {
Vector3f pos;
public IntervalTree<Blocks> blocks = new IntervalTree<Blocks>();
public Vector3f pos;
public IntervalMap<Blocks> blocks = new IntervalMap<>();
public Node chunkNode = new Node();
public boolean loaded = false;
public Chunk() {
this(Vector3f.ZERO);
}
public Chunk(Vector3f pos_) {
this.pos = pos_;
chunkNode.setLocalTranslation(pos.mult(Config.CHUNK_SIZE));
// I still have to decided if this is necessary. With an empty tree this
// takes O(1)
blocks.insertValue(Blocks.AIR, new Interval(0, Config.CHUNK_3DCOORD_MAX_INDEX));
//blocks.insertTerminator(Config.CHUNK_3DCOORD_MAX_INDEX);
blocks.insert(0, Config.CHUNK_3DCOORD_MAX_INDEX, Blocks.AIR);
}
public static int coord3DTo1D(int x, int y, int z) {
@ -39,34 +43,44 @@ public class Chunk {
}
public Blocks getBlock(int x, int y, int z) {
return blocks.getSingleValue(Chunk.coord3DTo1D(x, y, z));
return blocks.valueAtKey(Chunk.coord3DTo1D(x, y, z));
}
public void generatePlane() {
// blocks.print();
// this.setBlocks(Blocks.STONE, 0,
// Config.CHUNK_3DCOORD_MAX_INDEX));
for (int i = 0; i < Config.CHUNK_SIZE; i++) this.setBlocks(Blocks.STONE, coord3DTo1D(0, 0, i), coord3DTo1D(Config.CHUNK_SIZE - 1, 0, i));
for (int i = 0; i < Config.CHUNK_SIZE; i++) this.setBlocks(Blocks.DIRT, coord3DTo1D(0, 1, i), coord3DTo1D(Config.CHUNK_SIZE - 1, 1, i));
for (int i = 0; i < Config.CHUNK_SIZE; i++) this.setBlocks(Blocks.GRASS, coord3DTo1D(0, 2, i), coord3DTo1D(Config.CHUNK_SIZE - 1, 2, i));
for (int i = 0; i < Config.CHUNK_SIZE; i++)
this.setBlocks(Blocks.STONE, coord3DTo1D(0, 0, i), coord3DTo1D(Config.CHUNK_SIZE - 1, 0, i));
for (int i = 0; i < Config.CHUNK_SIZE; i++)
this.setBlocks(Blocks.DIRT, coord3DTo1D(0, 1, i), coord3DTo1D(Config.CHUNK_SIZE - 1, 1, i));
for (int i = 0; i < Config.CHUNK_SIZE; i++)
this.setBlocks(Blocks.GRASS, coord3DTo1D(0, 2, i), coord3DTo1D(Config.CHUNK_SIZE - 1, 2, i));
for (int i = 0; i < Config.CHUNK_SIZE; i++) this.setBlocks(Blocks.STONE, coord3DTo1D(0, 3, i), coord3DTo1D(Config.CHUNK_SIZE - 1, 3, i));
for (int i = 0; i < Config.CHUNK_SIZE; i++) this.setBlocks(Blocks.DIRT, coord3DTo1D(0, 4, i), coord3DTo1D(Config.CHUNK_SIZE - 1, 4, i));
for (int i = 0; i < Config.CHUNK_SIZE; i++) this.setBlocks(Blocks.GRASS, coord3DTo1D(0, 5, i), coord3DTo1D(Config.CHUNK_SIZE - 1, 5, i));
for (int i = 0; i < Config.CHUNK_SIZE; i++)
this.setBlocks(Blocks.STONE, coord3DTo1D(0, 3, i), coord3DTo1D(Config.CHUNK_SIZE - 1, 3, i));
for (int i = 0; i < Config.CHUNK_SIZE; i++)
this.setBlocks(Blocks.DIRT, coord3DTo1D(0, 4, i), coord3DTo1D(Config.CHUNK_SIZE - 1, 4, i));
for (int i = 0; i < Config.CHUNK_SIZE; i++)
this.setBlocks(Blocks.GRASS, coord3DTo1D(0, 5, i), coord3DTo1D(Config.CHUNK_SIZE - 1, 5, i));
}
public void generateStair() {
for (int i = 0; i < Config.CHUNK_SIZE; i++) {
for (int j = 0; j < Config.CHUNK_SIZE; j++) {
this.setBlocks(j < Config.CHUNK_SIZE - i - 1 ? Blocks.STONE : Blocks.GRASS, coord3DTo1D(0, i, j), coord3DTo1D(Config.CHUNK_SIZE - 1, i, j));
}
public void generateCube() {
this.setBlocks(Blocks.STONE, 0,
Config.CHUNK_3DCOORD_MAX_INDEX);
}
public void generateStair() {
for (int i = 0; i < Config.CHUNK_SIZE; i++) {
for (int j = 0; j < Config.CHUNK_SIZE; j++) {
this.setBlocks(j < Config.CHUNK_SIZE - i - 1 ? Blocks.STONE : Blocks.GRASS, coord3DTo1D(0, i, j),
coord3DTo1D(Config.CHUNK_SIZE - 1, i, j));
}
this.setBlocks(Blocks.DIRT, Chunk.coord3DTo1D(0, 0, 0), Chunk.coord3DTo1D(2, 0, 0));
this.setBlocks(Blocks.DIRT, Chunk.coord3DTo1D(1, 0, 2), Chunk.coord3DTo1D(2, 0, 2));
}
this.setBlocks(Blocks.DIRT, Chunk.coord3DTo1D(0, 0, 0), Chunk.coord3DTo1D(2, 0, 0));
this.setBlocks(Blocks.DIRT, Chunk.coord3DTo1D(1, 0, 2), Chunk.coord3DTo1D(2, 0, 2));
}
public void arrayGenerateCorner() {
Blocks[] array = new Blocks[Config.CHUNK_3DCOORD_MAX_INDEX + 1];
@ -78,11 +92,14 @@ public class Chunk {
// Distance from 0,0,0
dist = (int) Math.sqrt((i * i) + (j * j) + (k * k));
if (dist <= (int) (Config.CHUNK_SIZE / 3)) array[Chunk.coord3DTo1D(i, j, k)] = Blocks.STONE;
else if (dist > (int) (Config.CHUNK_SIZE / 3) && dist <= (int) (2 * Config.CHUNK_SIZE / 3)) array[Chunk.coord3DTo1D(i, j, k)] = Blocks.DIRT;
else if (dist > (int) (2 * Config.CHUNK_SIZE / 3) && dist <= (int) (Config.CHUNK_SIZE)) array[Chunk.coord3DTo1D(i, j, k)] = Blocks.GRASS;
else array[Chunk.coord3DTo1D(i, j, k)] = Blocks.AIR;
if (dist <= (int) (Config.CHUNK_SIZE / 3))
array[Chunk.coord3DTo1D(i, j, k)] = Blocks.STONE;
else if (dist > (int) (Config.CHUNK_SIZE / 3) && dist <= (int) (2 * Config.CHUNK_SIZE / 3))
array[Chunk.coord3DTo1D(i, j, k)] = Blocks.DIRT;
else if (dist > (int) (2 * Config.CHUNK_SIZE / 3) && dist <= (int) (Config.CHUNK_SIZE))
array[Chunk.coord3DTo1D(i, j, k)] = Blocks.GRASS;
else
array[Chunk.coord3DTo1D(i, j, k)] = Blocks.AIR;
}
}
@ -91,52 +108,48 @@ public class Chunk {
this.treeFrom1DArray(array);
}
/* Set blocks. Interval to be intended in the same way as in interval tree (flatten 1d index of 3d coords) */
public void setBlocks(Blocks block, Interval interval){
this.blocks.insertValue(block, new Interval(Math.max(0, interval.getLow()), Math.min(Config.CHUNK_3DCOORD_MAX_INDEX, interval.getHigh())));
}
public void setBlocks(Blocks block, int intLow, int intHigh){
this.blocks.insertValue(block, new Interval(Math.max(0, intLow), Math.min(Config.CHUNK_3DCOORD_MAX_INDEX, intHigh)));
/*
* Set blocks. Interval to be intended in the same way as in interval tree
* (flatten 1d index of 3d coords)
*/
public void setBlocks(Blocks block, int intLow, int intHigh) {
this.blocks.insert( Math.max(0,intLow), Math.min(Config.CHUNK_3DCOORD_MAX_INDEX+1, intHigh),block);
}
public Blocks[] treeTo1DArray() {
// System.out.println(Config.CHUNK_3DCOORD_MAX_INDEX);
Blocks[] result = new Blocks[Config.CHUNK_3DCOORD_MAX_INDEX + 1];
Queue<IntervalTree<Blocks>.TreeNode> queue = new ArrayDeque();
if(blocks.getRoot() != null) queue.add(blocks.getRoot());
IntervalTree<Blocks>.TreeNode t;
while (!queue.isEmpty()) {
t = queue.poll();
if (t.getLeft() != null) queue.add(t.getLeft());
if (t.getRight() != null) queue.add(t.getRight());
for (int i = t.getInterval().getLow(); i <= t.getInterval().getHigh(); i++) result[i] = t.getValue();
Blocks v;
Integer oldK = null;
for(Integer k : blocks.treemap.keySet()){
if(oldK != null){
v = blocks.valueAtKey(oldK);
//System.out.println("From " +oldK+ " to " + k + ": " + v);
for(int i = oldK; i < k; i++) result[i] = v;
}
oldK = k;
}
v = blocks.valueAtKey(oldK);
for(int i = oldK; i < result.length; i++) result[i] = v;
return result;
}
public void treeFrom1DArray(Blocks[] array) {
this.blocks = new IntervalTree<>();
this.blocks = new IntervalMap<>();
int start = 0;
Blocks prev = array[0];
for (int i = 0; i < array.length; i++) {
if (array[i] != prev) {
// System.out.println("From " + start + " to " + i + ": " + prev);
this.setBlocks(prev == null ? Blocks.AIR : prev, start, i);
start = i;
}
prev = array[i];
}
// System.out.println("From " + start + " to end :" + prev);
this.setBlocks(prev, start, array.length - 1);
this.setBlocks(prev, start, array.length-1);
}
}