Added render distance slider and implementation
This commit is contained in:
@@ -3,7 +3,6 @@ package me.cortex.voxy.client.config;
|
||||
import com.google.gson.FieldNamingPolicy;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import me.cortex.voxy.client.saver.ContextSelectionSystem;
|
||||
import me.cortex.voxy.common.Logger;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
|
||||
@@ -24,7 +23,7 @@ public class VoxyConfig {
|
||||
public boolean enabled = true;
|
||||
public boolean enableRendering = true;
|
||||
public boolean ingestEnabled = true;
|
||||
//public int renderDistance = 128;
|
||||
public int sectionRenderDistance = 16;
|
||||
public int serviceThreads = Math.max(Runtime.getRuntime().availableProcessors()/2, 1);
|
||||
public float subDivisionSize = 128;
|
||||
public int secondaryLruCacheSize = 1024;
|
||||
|
||||
@@ -112,6 +112,18 @@ public class VoxyConfigScreenFactory implements ModMenuApi {
|
||||
.setDefaultValue((int) DEFAULT.subDivisionSize)
|
||||
.build());
|
||||
|
||||
category.addEntry(entryBuilder.startIntSlider(Text.translatable("voxy.config.general.renderDistance"), config.sectionRenderDistance, 2, 64)
|
||||
.setTooltip(Text.translatable("voxy.config.general.renderDistance.tooltip"))
|
||||
.setSaveConsumer(val -> {
|
||||
config.sectionRenderDistance = val;
|
||||
var wrenderer =((IGetVoxyRenderSystem)(MinecraftClient.getInstance().worldRenderer));
|
||||
if (wrenderer != null && wrenderer.getVoxyRenderSystem() != null) {
|
||||
wrenderer.getVoxyRenderSystem().setRenderDistance(val);
|
||||
}
|
||||
})
|
||||
.setDefaultValue(DEFAULT.sectionRenderDistance)
|
||||
.build());
|
||||
|
||||
//category.addEntry(entryBuilder.startIntSlider(Text.translatable("voxy.config.general.lruCacheSize"), config.secondaryLruCacheSize, 16, 1<<13)
|
||||
// .setTooltip(Text.translatable("voxy.config.general.lruCacheSize.tooltip"))
|
||||
// .setSaveConsumer(val ->{if (config.secondaryLruCacheSize != val) reload(); config.secondaryLruCacheSize = val;})
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package me.cortex.voxy.client.core.rendering;
|
||||
|
||||
import me.cortex.voxy.client.core.util.RingTracker;
|
||||
import me.cortex.voxy.common.world.WorldEngine;
|
||||
|
||||
import java.util.function.LongConsumer;
|
||||
|
||||
public class RenderDistanceTracker {
|
||||
private static final int CHECK_DISTANCE_BLOCKS = 128;
|
||||
private final LongConsumer addTopLevelNode;
|
||||
private final LongConsumer removeTopLevelNode;
|
||||
private final int processRate;
|
||||
private final int minSec;
|
||||
private final int maxSec;
|
||||
private RingTracker tracker;
|
||||
private int renderDistance;
|
||||
private double posX;
|
||||
private double posZ;
|
||||
public RenderDistanceTracker(int rate, int minSec, int maxSec, LongConsumer addTopLevelNode, LongConsumer removeTopLevelNode) {
|
||||
this.addTopLevelNode = addTopLevelNode;
|
||||
this.removeTopLevelNode = removeTopLevelNode;
|
||||
this.renderDistance = 2;
|
||||
this.tracker = new RingTracker(this.renderDistance, 0, 0, true);
|
||||
this.processRate = rate;
|
||||
this.minSec = minSec;
|
||||
this.maxSec = maxSec;
|
||||
}
|
||||
|
||||
public void setRenderDistance(int renderDistance) {
|
||||
this.tracker.unload();
|
||||
this.tracker.process(Integer.MAX_VALUE, this::add, this::rem);
|
||||
this.renderDistance = renderDistance;
|
||||
this.tracker = new RingTracker(renderDistance, ((int)this.posX)>>9, ((int)this.posZ)>>9, true);
|
||||
}
|
||||
|
||||
public void setCenterAndProcess(double x, double z) {
|
||||
double dx = this.posX-x;
|
||||
double dz = this.posZ-z;
|
||||
if (CHECK_DISTANCE_BLOCKS*CHECK_DISTANCE_BLOCKS<dx*dx+dz*dz) {
|
||||
this.posX = x;
|
||||
this.posZ = z;
|
||||
this.tracker.moveCenter(((int)x)>>9, ((int)z)>>9);
|
||||
}
|
||||
this.tracker.process(this.processRate, this::add, this::rem);
|
||||
}
|
||||
|
||||
private void add(int x, int z) {
|
||||
for (int y = this.minSec; y <= this.maxSec; y++) {
|
||||
this.addTopLevelNode.accept(WorldEngine.getWorldSectionId(4, x, y, z));
|
||||
}
|
||||
}
|
||||
|
||||
private void rem(int x, int z) {
|
||||
for (int y = this.minSec; y <= this.maxSec; y++) {
|
||||
this.removeTopLevelNode.accept(WorldEngine.getWorldSectionId(4, x, y, z));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -86,18 +86,15 @@ public class RenderService<T extends AbstractSectionRenderer<J, ?>, J extends Vi
|
||||
world.getMapper().setBiomeCallback(this.modelService::addBiome);
|
||||
}
|
||||
|
||||
private int q = -60;
|
||||
public void addTopLevelNode(long pos) {
|
||||
this.nodeManager.insertTopLevelNode(pos);
|
||||
}
|
||||
|
||||
public void removeTopLevelNode(long pos) {
|
||||
this.nodeManager.removeTopLevelNode(pos);
|
||||
}
|
||||
|
||||
public void setup(Camera camera) {
|
||||
final int W = 32;
|
||||
final int H = 2;
|
||||
boolean SIDED = false;
|
||||
for (int i = 0; i<64 && q<((W*2+1)*(W*2+1)*H)&&q++>=0;i++) {
|
||||
this.nodeManager.insertTopLevelNode(WorldEngine.getWorldSectionId(4, (q%(W*2+1))-(SIDED?0:W), ((q/(W*2+1))/(W*2+1))-1, ((q/(W*2+1))%(W*2+1))-(SIDED?0:W)));
|
||||
}
|
||||
if (q==((W*2+1)*(W*2+1)*H)) {
|
||||
q++;
|
||||
Logger.info("Finished loading render distance");
|
||||
}
|
||||
this.modelService.tick();
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import me.cortex.voxy.client.core.rendering.post.PostProcessing;
|
||||
import me.cortex.voxy.client.core.rendering.util.DownloadStream;
|
||||
import me.cortex.voxy.client.core.rendering.util.RawDownloadStream;
|
||||
import me.cortex.voxy.client.core.util.IrisUtil;
|
||||
import me.cortex.voxy.client.core.util.RingTracker;
|
||||
import me.cortex.voxy.common.Logger;
|
||||
import me.cortex.voxy.common.thread.ServiceThreadPool;
|
||||
import me.cortex.voxy.common.world.WorldEngine;
|
||||
@@ -40,6 +41,7 @@ public class VoxyRenderSystem {
|
||||
private final RenderService renderer;
|
||||
private final PostProcessing postProcessing;
|
||||
private final WorldEngine worldIn;
|
||||
private final RenderDistanceTracker renderDistanceTracker;
|
||||
|
||||
public VoxyRenderSystem(WorldEngine world, ServiceThreadPool threadPool) {
|
||||
//Trigger the shared index buffer loading
|
||||
@@ -49,10 +51,23 @@ public class VoxyRenderSystem {
|
||||
this.worldIn = world;
|
||||
this.renderer = new RenderService(world, threadPool);
|
||||
this.postProcessing = new PostProcessing();
|
||||
|
||||
this.renderDistanceTracker = new RenderDistanceTracker(10,
|
||||
MinecraftClient.getInstance().world.getBottomSectionCoord()>>5,
|
||||
(MinecraftClient.getInstance().world.getTopSectionCoord()-1)>>5,
|
||||
this.renderer::addTopLevelNode,
|
||||
this.renderer::removeTopLevelNode);
|
||||
|
||||
this.renderDistanceTracker.setRenderDistance(VoxyConfig.CONFIG.sectionRenderDistance);
|
||||
}
|
||||
|
||||
public void setRenderDistance(int renderDistance) {
|
||||
this.renderDistanceTracker.setRenderDistance(renderDistance);
|
||||
}
|
||||
|
||||
public void renderSetup(Frustum frustum, Camera camera) {
|
||||
this.renderDistanceTracker.setCenterAndProcess(camera.getBlockPos().getX(), camera.getBlockPos().getZ());
|
||||
|
||||
this.renderer.setup(camera);
|
||||
PrintfDebugUtil.tick();
|
||||
}
|
||||
|
||||
@@ -216,8 +216,9 @@ public class HierarchicalOcclusionTraverser {
|
||||
//TODO: Move the first queue to a persistent list so its not updated every frame
|
||||
|
||||
ptr = UploadStream.INSTANCE.upload(this.scratchQueueA, 0, 4L*initialQueueSize);
|
||||
for (int i = 0; i < initialQueueSize; i++) {
|
||||
MemoryUtil.memPutInt(ptr + 4L*i, this.nodeManager.getTopLevelNodeIds().getInt(i));
|
||||
int i = 0;
|
||||
for (int node : this.nodeManager.getTopLevelNodeIds()) {
|
||||
MemoryUtil.memPutInt(ptr + 4L*(i++), node);
|
||||
}
|
||||
|
||||
UploadStream.INSTANCE.commit();
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package me.cortex.voxy.client.core.rendering.hierachical;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.ints.IntSet;
|
||||
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
||||
@@ -87,7 +86,7 @@ public class NodeManager {
|
||||
private final Long2IntOpenHashMap activeSectionMap = new Long2IntOpenHashMap();
|
||||
private final NodeStore nodeData;
|
||||
public final int maxNodeCount;
|
||||
private final IntArrayList topLevelNodeIds = new IntArrayList();
|
||||
private final IntOpenHashSet topLevelNodeIds = new IntOpenHashSet();
|
||||
private final LongOpenHashSet topLevelNodes = new LongOpenHashSet();
|
||||
private int activeNodeRequestCount;
|
||||
|
||||
@@ -133,24 +132,26 @@ public class NodeManager {
|
||||
}
|
||||
|
||||
public void removeTopLevelNode(long pos) {
|
||||
if (!this.topLevelNodes.remove(pos)) {
|
||||
throw new IllegalStateException("Position not in top level map");
|
||||
}
|
||||
int nodeId = this.activeSectionMap.get(pos);
|
||||
if (nodeId == -1) {
|
||||
Logger.error("Tried removing top level pos " + WorldEngine.pprintPos(pos) + " but it was not in active map, discarding!");
|
||||
return;
|
||||
throw new IllegalStateException("Tried removing top level pos " + WorldEngine.pprintPos(pos) + " but it was not in active map, discarding!");
|
||||
}
|
||||
if ((nodeId&NODE_TYPE_MSK)!=NODE_TYPE_REQUEST) {
|
||||
int id = nodeId&NODE_ID_MSK;
|
||||
if (!this.topLevelNodeIds.remove(id)) {
|
||||
throw new IllegalStateException("Node id was not in top level node ids: " + nodeId + " pos: " + WorldEngine.pprintPos(pos));
|
||||
}
|
||||
}
|
||||
//TODO: assert is top level node
|
||||
|
||||
//TODO:FIXME augment topLevelNodeIds with a hashmap from node id to array index
|
||||
// OR!! just ensure the list is always ordered?? maybe? idk i think hashmap is best
|
||||
// since the array list might get shuffled as nodes are removed
|
||||
// since need to move the entry at the end of the array to fill a hole made
|
||||
|
||||
// remove from topLevelNodes aswell
|
||||
|
||||
//Remove the entire thing
|
||||
this.recurseRemoveNode(pos);
|
||||
}
|
||||
|
||||
|
||||
IntArrayList getTopLevelNodeIds() {
|
||||
IntOpenHashSet getTopLevelNodeIds() {
|
||||
return this.topLevelNodeIds;
|
||||
}
|
||||
|
||||
@@ -599,6 +600,35 @@ public class NodeManager {
|
||||
this._recurseRemoveNode(pos, false);
|
||||
}
|
||||
|
||||
|
||||
private void _removeRequest(int reqId, NodeChildRequest req, long pos) {
|
||||
//Delete all the request data
|
||||
for (int i = 0; i < 8; i++) {
|
||||
if ((req.getMsk()&(1<<i))==0) continue;
|
||||
|
||||
int mesh = req.getChildMesh(i);
|
||||
if (mesh != EMPTY_GEOMETRY_ID && mesh != NULL_GEOMETRY_ID)
|
||||
this.geometryManager.removeSection(mesh);
|
||||
|
||||
//Unwatch the request position
|
||||
long childPos = makeChildPos(pos, i);
|
||||
//Remove from section tracker
|
||||
int cId = this.activeSectionMap.remove(childPos);
|
||||
if (cId == -1) {
|
||||
throw new IllegalStateException("Child not in activeMap");
|
||||
}
|
||||
if ((cId&NODE_TYPE_MSK) != NODE_TYPE_REQUEST || (cId&REQUEST_TYPE_MSK) != REQUEST_TYPE_CHILD || (cId&NODE_ID_MSK) != reqId) {
|
||||
throw new IllegalStateException("Invalid child active state map: " + cId);
|
||||
}
|
||||
if (!this.watcher.unwatch(childPos, WorldEngine.UPDATE_FLAGS)) {
|
||||
throw new IllegalStateException("Pos was not being watched");
|
||||
}
|
||||
}
|
||||
|
||||
this.childRequests.release(reqId);//Release the request
|
||||
this.activeNodeRequestCount--;
|
||||
}
|
||||
|
||||
//Recursivly fully removes all nodes and children
|
||||
private void _recurseRemoveNode(long pos, boolean onlyRemoveChildren) {
|
||||
//NOTE: this also removes from the section map
|
||||
@@ -612,8 +642,8 @@ public class NodeManager {
|
||||
throw new IllegalStateException("Cannot remove pos that doesnt exist");
|
||||
}
|
||||
int type = nodeId&NODE_TYPE_MSK;
|
||||
nodeId &= NODE_ID_MSK;
|
||||
if (type == NODE_TYPE_INNER || type == NODE_TYPE_LEAF) {
|
||||
nodeId &= NODE_ID_MSK;
|
||||
if (!this.nodeData.nodeExists(nodeId)) {
|
||||
throw new IllegalStateException("Node exists in section map but not in nodeData");
|
||||
}
|
||||
@@ -628,34 +658,8 @@ public class NodeManager {
|
||||
var req = this.childRequests.get(reqId);
|
||||
childExistence ^= req.getMsk();
|
||||
|
||||
//TODO FINISH
|
||||
this._removeRequest(reqId, req, pos);
|
||||
|
||||
//Delete all the request data
|
||||
for (int i = 0; i < 8; i++) {
|
||||
if ((req.getMsk()&(1<<i))==0) continue;
|
||||
|
||||
int mesh = req.getChildMesh(i);
|
||||
if (mesh != EMPTY_GEOMETRY_ID && mesh != NULL_GEOMETRY_ID)
|
||||
this.geometryManager.removeSection(mesh);
|
||||
|
||||
//Unwatch the request position
|
||||
long childPos = makeChildPos(pos, i);
|
||||
//Remove from section tracker
|
||||
int cId = this.activeSectionMap.remove(childPos);
|
||||
if (cId == -1) {
|
||||
throw new IllegalStateException("Child not in activeMap");
|
||||
}
|
||||
if ((cId&NODE_TYPE_MSK) != NODE_TYPE_REQUEST || (cId&REQUEST_TYPE_MSK) != REQUEST_TYPE_CHILD || (cId&NODE_ID_MSK) != reqId) {
|
||||
throw new IllegalStateException("Invalid child active state map: " + cId);
|
||||
}
|
||||
if (!this.watcher.unwatch(childPos, WorldEngine.UPDATE_FLAGS)) {
|
||||
throw new IllegalStateException("Pos was not being watched");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
this.childRequests.release(reqId);//Release the request
|
||||
this.activeNodeRequestCount--;
|
||||
if (onlyRemoveChildren) {
|
||||
this.nodeData.unmarkRequestInFlight(nodeId);
|
||||
this.nodeData.setNodeRequest(nodeId, NULL_REQUEST_ID);
|
||||
@@ -745,10 +749,33 @@ public class NodeManager {
|
||||
//TODO: probably need this.clearId(nodeId);
|
||||
this.invalidateNode(nodeId);
|
||||
}
|
||||
} else {
|
||||
} else if (type == NODE_TYPE_REQUEST) {
|
||||
if (!this.watcher.unwatch(pos, WorldEngine.UPDATE_FLAGS)) {
|
||||
throw new IllegalStateException("Pos was not being watched");
|
||||
}
|
||||
if ((nodeId&REQUEST_TYPE_MSK) == REQUEST_TYPE_SINGLE) {
|
||||
nodeId &= NODE_ID_MSK;
|
||||
|
||||
Logger.error("UNFINISHED OPERATION TODO: FIXME3");
|
||||
//NOTE: There are request type singles and request type child!!!!
|
||||
var req = this.singleRequests.get(nodeId);
|
||||
if (req.getPosition() != pos)
|
||||
throw new IllegalStateException();
|
||||
|
||||
this.singleRequests.release(nodeId);
|
||||
if (req.hasMeshSet()) {
|
||||
int mesh = req.getMesh();
|
||||
if (mesh != EMPTY_GEOMETRY_ID && mesh != NULL_GEOMETRY_ID)
|
||||
this.geometryManager.removeSection(mesh);
|
||||
}
|
||||
|
||||
} else {
|
||||
nodeId &= NODE_ID_MSK;
|
||||
var req = this.childRequests.get(nodeId);
|
||||
if (req.getPosition() != pos)
|
||||
throw new IllegalStateException();
|
||||
this._removeRequest(nodeId, req, pos);
|
||||
}
|
||||
} else {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,4 +42,7 @@ class SingleNodeRequest {
|
||||
public boolean hasChildExistenceSet() {
|
||||
return (this.setMsk&2)!=0;
|
||||
}
|
||||
public boolean hasMeshSet() {
|
||||
return (this.setMsk&1)!=0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,7 @@ package me.cortex.voxy.client.core.rendering.hierachical;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2IntFunction;
|
||||
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.*;
|
||||
import me.cortex.voxy.client.core.rendering.ISectionWatcher;
|
||||
import me.cortex.voxy.client.core.rendering.building.BuiltSection;
|
||||
import me.cortex.voxy.client.core.rendering.section.AbstractSectionGeometryManager;
|
||||
@@ -236,6 +234,10 @@ public class TestNodeManager {
|
||||
public void verifyIntegrity() {
|
||||
this.nodeManager.verifyIntegrity(this.watcher.updateTypes.keySet(), this.cleaner.active);
|
||||
}
|
||||
|
||||
public void remTopPos(long pos) {
|
||||
this.nodeManager.removeTopLevelNode(pos);
|
||||
}
|
||||
}
|
||||
|
||||
private static void fillInALl(TestBase test, long pos, Long2IntFunction converter) {
|
||||
@@ -274,8 +276,8 @@ public class TestNodeManager {
|
||||
|
||||
public static void main(String[] args) {
|
||||
Logger.INSERT_CLASS = false;
|
||||
int ITER_COUNT = 50_000;
|
||||
int INNER_ITER_COUNT = 500_000;
|
||||
int ITER_COUNT = 5_000;
|
||||
int INNER_ITER_COUNT = 100_000;
|
||||
boolean GEO_REM = true;
|
||||
|
||||
AtomicInteger finished = new AtomicInteger();
|
||||
@@ -299,13 +301,14 @@ public class TestNodeManager {
|
||||
}
|
||||
System.out.println("Finished " + finished.get() + " iterations out of " + ITER_COUNT);
|
||||
}
|
||||
private static long rPos(Random r) {
|
||||
private static long rPos(Random r, LongList tops) {
|
||||
int lvl = r.nextInt(5);
|
||||
long top = tops.getLong(r.nextInt(tops.size()));
|
||||
if (lvl==4) {
|
||||
return WorldEngine.getWorldSectionId(4,0,0,0);
|
||||
return top;
|
||||
}
|
||||
int bound = 16>>lvl;
|
||||
return WorldEngine.getWorldSectionId(lvl, r.nextInt(bound), r.nextInt(bound), r.nextInt(bound));
|
||||
return WorldEngine.getWorldSectionId(lvl, r.nextInt(bound)+(WorldEngine.getX(top)<<4), r.nextInt(bound)+(WorldEngine.getY(top)<<4), r.nextInt(bound)+(WorldEngine.getZ(top)<<4));
|
||||
}
|
||||
|
||||
private static boolean runTest(int ITERS, int testIdx, Set<List<StackTraceElement>> traces, boolean geoRemoval) {
|
||||
@@ -314,14 +317,30 @@ public class TestNodeManager {
|
||||
Random r = new Random(testIdx * 1234L);
|
||||
try {
|
||||
var test = new TestBase();
|
||||
LongList tops = new LongArrayList();
|
||||
//Fuzzy bruteforce everything
|
||||
test.putTopPos(POS_A);
|
||||
tops.add(POS_A);
|
||||
for (int i = 0; i < ITERS; i++) {
|
||||
long pos = rPos(r);
|
||||
int op = r.nextInt(4);
|
||||
long pos = rPos(r, tops);
|
||||
int op = r.nextInt(5);
|
||||
int extra = r.nextInt(256);
|
||||
boolean hasGeometry = r.nextBoolean();
|
||||
if (op == 0) {
|
||||
boolean addRemTLN = r.nextInt(512) == 0;
|
||||
boolean extraBool = r.nextBoolean();
|
||||
if (op == 0 && addRemTLN) {
|
||||
pos = WorldEngine.getWorldSectionId(4, r.nextInt(5)-2, r.nextInt(5)-2, r.nextInt(5)-2);
|
||||
boolean cont = tops.contains(pos);
|
||||
if (cont&&extraBool&&tops.size()>1) {
|
||||
extraBool = true;
|
||||
test.remTopPos(pos);
|
||||
tops.rem(pos);
|
||||
} else if (!cont) {
|
||||
extraBool = false;
|
||||
test.putTopPos(pos);
|
||||
tops.add(pos);
|
||||
}
|
||||
} else if (op == 0) {
|
||||
test.request(pos);
|
||||
}
|
||||
if (op == 1) {
|
||||
@@ -336,8 +355,20 @@ public class TestNodeManager {
|
||||
test.printNodeChanges();
|
||||
test.verifyIntegrity();
|
||||
}
|
||||
test.childUpdate(POS_A, 0);
|
||||
test.meshUpdate(POS_A, 0, 0);
|
||||
for (long top : tops) {
|
||||
test.remTopPos(top);
|
||||
}
|
||||
test.printNodeChanges();
|
||||
test.verifyIntegrity();
|
||||
if (test.nodeManager.getCurrentMaxNodeId() != -1) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (!test.cleaner.active.isEmpty()) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (!test.watcher.updateTypes.isEmpty()) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (test.geometryManager.memoryInUse != 0) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
204
src/main/java/me/cortex/voxy/client/core/util/RingTracker.java
Normal file
204
src/main/java/me/cortex/voxy/client/core/util/RingTracker.java
Normal file
@@ -0,0 +1,204 @@
|
||||
package me.cortex.voxy.client.core.util;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap;
|
||||
import me.cortex.voxy.common.Logger;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
//Tracks a ring and load/unload positions
|
||||
// can process N of these load/unload positions
|
||||
public class RingTracker {
|
||||
//TODO: replace with custom map that removes elements if its mapped to 0
|
||||
private final Long2ByteOpenHashMap operations = new Long2ByteOpenHashMap(1<<13);
|
||||
private final int[] boundDist;
|
||||
private final int radius;
|
||||
private int centerX;
|
||||
private int centerZ;
|
||||
|
||||
public RingTracker(int radius, int centerX, int centerZ, boolean fill) {
|
||||
this.centerX = centerX;
|
||||
this.centerZ = centerZ;
|
||||
this.radius = radius;
|
||||
this.boundDist = generateBoundingHalfCircleDistance(radius);
|
||||
if (fill) {
|
||||
this.fillRing(true);
|
||||
}
|
||||
}
|
||||
|
||||
private static long pack(int x, int z) {
|
||||
return Integer.toUnsignedLong(x)|(Integer.toUnsignedLong(z)<<32);
|
||||
}
|
||||
|
||||
private void fillRing(boolean load) {
|
||||
for (int i = 0; i <= this.radius*2; i++) {
|
||||
int x = this.centerX + i - this.radius;
|
||||
int d = this.boundDist[i];
|
||||
for (int z = this.centerZ-d; z <= this.centerZ+d; z++) {
|
||||
int res = this.operations.addTo(pack(x, z), (byte) (load?1:-1));
|
||||
if ((load&&0<res)||(((!load)&&res<0))) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void unload() {
|
||||
this.fillRing(false);
|
||||
}
|
||||
|
||||
//Moves the center from old to new and updates the operations map
|
||||
public void moveCenter(int x, int z) {
|
||||
//TODO, if the new center is greater than radius from current, unload all current and load all at new
|
||||
if (this.radius+1<Math.abs(x-this.centerX) || this.radius+1<Math.abs(z-this.centerZ)) {
|
||||
this.fillRing(false);
|
||||
this.centerX = x;
|
||||
this.centerZ = z;
|
||||
this.fillRing(true);
|
||||
} else {
|
||||
if (x != this.centerX) {
|
||||
moveX(x - this.centerX);
|
||||
}
|
||||
if (z != this.centerZ) {
|
||||
moveZ(z - this.centerZ);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void moveZ(int delta) {
|
||||
if (delta == 0) return;
|
||||
//Since +- 1 is the most common operation, fastpath it
|
||||
if (delta == -1 || delta == 1) {
|
||||
for (int i = 0; i <= this.radius * 2; i++) {
|
||||
int x = this.centerX + i - this.radius;
|
||||
//Multiply by the delta since its +-1 it also then makes it the correct orientation
|
||||
int d = this.boundDist[i]*delta;
|
||||
int pz = this.centerZ+d+delta;//Point to add (we need to offset by 1 in the mov direction)
|
||||
int nz = this.centerZ-d;//Point to rem
|
||||
if (0<this.operations.addTo(pack(x, pz), (byte) 1))//Load point
|
||||
throw new IllegalStateException("x: "+x+", z: "+pz+" state: "+this.operations.get(pack(x, pz)));
|
||||
if (this.operations.addTo(pack(x, nz), (byte) -1)<0)//Unload point
|
||||
throw new IllegalStateException("x: "+x+", z: "+nz+" state: "+this.operations.get(pack(x, nz)));
|
||||
}
|
||||
this.centerZ += delta;
|
||||
} else {
|
||||
int sDelta = Integer.signum(delta);
|
||||
for (int i = 0; i <= this.radius * 2; i++) {
|
||||
int x = this.centerX + i - this.radius;
|
||||
//Multiply by the delta since its +-1 it also then makes it the correct orientation
|
||||
int d = this.boundDist[i]*sDelta;
|
||||
int pz = this.centerZ+d;//Point to add (we need to offset by 1 in the mov direction)
|
||||
for (int z = pz + (sDelta<0?delta:1); z <= pz + (sDelta<0?-1:delta); z++) {
|
||||
if (0<this.operations.addTo(pack(x, z), (byte) 1))//Load point
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
int nz = this.centerZ-d;//Point to rem
|
||||
for (int z = nz + (sDelta<0?(delta+1):0); z < nz + (sDelta<0?1:delta); z++) {
|
||||
if (this.operations.addTo(pack(x, z), (byte) -1)<0)//Unload point
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
this.centerZ += delta;
|
||||
}
|
||||
}
|
||||
|
||||
private void moveX(int delta) {
|
||||
if (delta == 0) return;
|
||||
//Since +- 1 is the most common operation, fastpath it
|
||||
if (delta == -1 || delta == 1) {
|
||||
for (int i = 0; i <= this.radius * 2; i++) {
|
||||
int z = this.centerZ + i - this.radius;
|
||||
//Multiply by the delta since its +-1 it also then makes it the correct orientation
|
||||
int d = this.boundDist[i]*delta;
|
||||
int px = this.centerX+d+delta;//Point to add (we need to offset by 1 in the mov direction)
|
||||
int nx = this.centerX-d;//Point to rem
|
||||
if (0<this.operations.addTo(pack(px, z), (byte) 1))//Load point
|
||||
throw new IllegalStateException();
|
||||
if (this.operations.addTo(pack(nx, z), (byte) -1)<0)//Unload point
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
this.centerX += delta;
|
||||
} else {
|
||||
int sDelta = Integer.signum(delta);
|
||||
for (int i = 0; i <= this.radius * 2; i++) {
|
||||
int z = this.centerZ + i - this.radius;
|
||||
//Multiply by the delta since its +-1 it also then makes it the correct orientation
|
||||
int d = this.boundDist[i]*sDelta;
|
||||
int px = this.centerX+d;//Point to add (we need to offset by 1 in the mov direction)
|
||||
for (int x = px + (sDelta<0?delta:1); x <= px + (sDelta<0?-1:delta); x++) {
|
||||
if (0<this.operations.addTo(pack(x, z), (byte) 1))//Load point
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
int nx = this.centerX-d;//Point to rem
|
||||
for (int x = nx + (sDelta<0?(delta+1):0); x < nx + (sDelta<0?1:delta); x++) {
|
||||
if (this.operations.addTo(pack(x, z), (byte) -1)<0)//Unload point
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
this.centerX += delta;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IUpdateConsumer {
|
||||
void accept(int x, int z);
|
||||
}
|
||||
|
||||
//Processes N operations from the operations map
|
||||
public int process(int N, IUpdateConsumer onAdd, IUpdateConsumer onRemove) {
|
||||
if (this.operations.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
var iter = this.operations.long2ByteEntrySet().fastIterator();
|
||||
int i = 0;
|
||||
while (iter.hasNext() && N--!=0) {
|
||||
var entry = iter.next();
|
||||
if (entry.getByteValue()==0) {
|
||||
iter.remove(); N++;
|
||||
continue;
|
||||
}
|
||||
i++;
|
||||
byte op = entry.getByteValue();
|
||||
if (op != 1 && op != -1) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
boolean isAdd = op == 1;
|
||||
long pos = entry.getLongKey();
|
||||
int x = (int) (pos&0xFFFFFFFFL);
|
||||
int z = (int) ((pos>>>32)&0xFFFFFFFFL);
|
||||
if (isAdd) {
|
||||
onAdd.accept(x, z);
|
||||
} else {
|
||||
onRemove.accept(x, z);
|
||||
}
|
||||
iter.remove();
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
private int[] generateBoundingHalfCircleDistance(int radius) {
|
||||
var ret = new int[radius*2+1];
|
||||
for (int i = -radius; i <= radius; i++) {
|
||||
ret[i+radius] = (int)Math.sqrt(radius*radius - i*i);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
for (int j = 0; j < 50; j++) {
|
||||
Random r = new Random((j+18723)*1234);
|
||||
var tracker = new RingTracker(r.nextInt(100)+1, 0, 0, true);
|
||||
int R = r.nextInt(500);
|
||||
for (int i = 0; i < 50_000; i++) {
|
||||
int x = r.nextInt(R*2+1)-R;
|
||||
int z = r.nextInt(R*2+1)-R;
|
||||
tracker.moveCenter(x, z);
|
||||
}
|
||||
tracker.fillRing(false);
|
||||
tracker.process(64, (x,z)->{
|
||||
Logger.info("Add:", x,",",z);
|
||||
}, (x,z)->{
|
||||
Logger.info("Remove:", x,",",z);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -65,7 +65,7 @@ public class RingUtil {
|
||||
public static int[] generatingBoundingCorner2D(int radius) {
|
||||
IntOpenHashSet points = new IntOpenHashSet();
|
||||
//Do 2 pass (x and y) to generate and cover all points
|
||||
for (int i = 0; i <= radius; i++) {
|
||||
for (int i = 1; i <= radius; i++) {
|
||||
int other = (int) Math.floor(Math.sqrt(radius*radius - i*i));
|
||||
//add points (x,other) and (other,x) as it covers the full spectrum
|
||||
points.add((i<<16)|other);
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
"voxy.config.title": "Voxy config",
|
||||
|
||||
"voxy.config.general": "General",
|
||||
"voxy.config.threads": "Threads",
|
||||
"voxy.config.storage": "Storage",
|
||||
|
||||
"voxy.config.general.enabled": "Enable Voxy",
|
||||
"voxy.config.general.enabled.tooltip": "Fully enables or disables voxy",
|
||||
@@ -18,5 +16,8 @@
|
||||
"voxy.config.general.serviceThreads.tooltip": "Number of threads the ServiceThreadPool can use",
|
||||
|
||||
"voxy.config.general.subDivisionSize": "Pixels^2 of subdivision size",
|
||||
"voxy.config.general.subDivisionSize.tooltip": "Maximum size in pixels (squared) of screenspace AABB before subdiving to smaller LoDs (Smaller being higher quality)"
|
||||
"voxy.config.general.subDivisionSize.tooltip": "Maximum size in pixels (squared) of screenspace AABB before subdiving to smaller LoDs (Smaller being higher quality)",
|
||||
|
||||
"voxy.config.general.renderDistance": "Render distance",
|
||||
"voxy.config.general.renderDistance.tooltip": "Render distance of voxy, each unit is equivalent to 32 chunks in vanilla (i.e. to get to vanilla render distance multiply by 32)"
|
||||
}
|
||||
Reference in New Issue
Block a user