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