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 centertreemaps-queues-iteration
parent
f8a31093cd
commit
a96c09df4b
0
src/main/java/com/emamaker/voxeltest/intervaltrees/data/Interval.java
Normal file → Executable file
0
src/main/java/com/emamaker/voxeltest/intervaltrees/data/Interval.java
Normal file → Executable 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;
|
||||||
|
}
|
||||||
|
}
|
0
src/main/java/com/emamaker/voxeltest/intervaltrees/data/IntervalTree.java
Normal file → Executable file
0
src/main/java/com/emamaker/voxeltest/intervaltrees/data/IntervalTree.java
Normal file → Executable 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue