Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions chunky/src/java/se/llbit/chunky/chunk/biome/BiomeData2d.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package se.llbit.chunky.chunk.biome;

import se.llbit.chunky.world.JavaChunk;
import se.llbit.chunky.world.biome.BiomePalette;
import se.llbit.chunky.world.biome.Biomes;
import se.llbit.nbt.Tag;
Expand Down Expand Up @@ -34,7 +35,7 @@ public void clear() {
}

public static void loadBiomeDataByteArray(Tag chunkData, BiomeData2d biomeData, BiomePalette biomePalette) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be moved to the world? Seems to be world format / chunk data dependant.

byte[] data = chunkData.get(LEVEL_BIOMES).byteArray();
byte[] data = chunkData.get(JavaChunk.LEVEL_BIOMES).byteArray();
int i = 0;
for(int z = 0; z < Z_MAX; z++) {
for(int x = 0; x < X_MAX; x++) {
Expand All @@ -46,7 +47,7 @@ public static void loadBiomeDataByteArray(Tag chunkData, BiomeData2d biomeData,

public static void loadBiomeDataIntArray(Tag chunkData, BiomeData2d biomeData, BiomePalette biomePalette) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, ie. let the biome data classes only contain the data and add per world format factories to create them?

// Since Minecraft 1.13, biome IDs are stored in an int vector with 256 entries (one for each XZ position).
int[] data = chunkData.get(LEVEL_BIOMES).intArray();
int[] data = chunkData.get(JavaChunk.LEVEL_BIOMES).intArray();
int i = 0;
for(int z = 0; z < Z_MAX; z++) {
for(int x = 0; x < X_MAX; x++) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package se.llbit.chunky.chunk.biome;

import se.llbit.chunky.chunk.ChunkData;
import se.llbit.chunky.world.JavaChunk;
import se.llbit.chunky.world.biome.Biome;
import se.llbit.chunky.world.biome.BiomePalette;
import se.llbit.chunky.world.biome.Biomes;
Expand All @@ -19,7 +20,7 @@ public class BiomeDataFactory {
//TODO: Ideally we would have registered factory impls with an isValidFor(Tag chunkTag), but this messy if chain works for now
public static void loadBiomeData(ChunkData chunkData, Tag chunkTag, BiomePalette biomePalette, int yMin, int yMax) {
BiomeData biomeData = chunkData.getBiomeData();
Tag biomeTagsPre21w39a = chunkTag.get(LEVEL_BIOMES);
Tag biomeTagsPre21w39a = chunkTag.get(JavaChunk.LEVEL_BIOMES);
if (!biomeTagsPre21w39a.isError()) { // pre21w39a tag exists
if (biomeTagsPre21w39a.isByteArray(X_MAX * Z_MAX)) {
if (!(biomeData instanceof BiomeData2d)) {
Expand All @@ -43,7 +44,7 @@ public static void loadBiomeData(ChunkData chunkData, Tag chunkTag, BiomePalette
chunkData.setBiomeData(biomeData);
return;
}
} else if (!chunkTag.get(SECTIONS_POST_21W39A).isError()) { // in 21w39a biome tags moved into the sections array
} else if (!chunkTag.get(JavaChunk.SECTIONS_POST_21W39A).isError()) { // in 21w39a biome tags moved into the sections array
if(!(biomeData instanceof GenericQuartBiomeData3d)) {
biomeData = new GenericQuartBiomeData3d();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package se.llbit.chunky.chunk.biome;

import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import se.llbit.chunky.world.JavaChunk;
import se.llbit.chunky.world.biome.Biome;
import se.llbit.chunky.world.biome.BiomePalette;
import se.llbit.chunky.world.biome.Biomes;
Expand Down Expand Up @@ -60,7 +61,7 @@ public void clear() {
}

public static void loadBiomeDataPost21w39a(Tag chunkData, GenericQuartBiomeData3d biomeData, BiomePalette biomePalette, int yMin, int yMax) {
Tag sections = chunkData.get(SECTIONS_POST_21W39A);
Tag sections = chunkData.get(JavaChunk.SECTIONS_POST_21W39A);
if (sections.isList()) {
for (SpecificTag section : sections.asList()) {
Tag yTag = section.get("Y");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import java.util.Arrays;

import static se.llbit.chunky.world.Chunk.LEVEL_BIOMES;
import static se.llbit.chunky.world.JavaChunk.LEVEL_BIOMES;

/**
* Implementation of a 3D biome grid where the smallest size of a biome is 4x4x4 blocks
Expand Down
9 changes: 4 additions & 5 deletions chunky/src/java/se/llbit/chunky/map/MapTile.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@

import se.llbit.chunky.resources.BitmapImage;
import se.llbit.chunky.world.*;
import se.llbit.chunky.world.region.Region;

/**
* A tile in the 2D world map or minimap. The tile contains either a chunk or a region.
Expand Down Expand Up @@ -71,14 +70,14 @@ public void draw(MapBuffer buffer, WorldMapLoader mapLoader, ChunkView view,
}
} else {
RegionPosition regionPos = new RegionPosition(pos.x, pos.z); // intentionally don't convert, this position represented a region already.

boolean isValid = mapLoader.getWorld().currentDimension().regionExistsWithinRange(regionPos, view.yMin, view.yMax);
Region region = mapLoader.getWorld().currentDimension().getRegionWithinRange(regionPos, view.yMin, view.yMax);

int pixelOffset = 0;
for (int z = 0; z < 32; ++z) {
for (int x = 0; x < 32; ++x) {
Chunk chunk = region.getChunk(x, z);
//Calculate the chunk position as empty chunks are (0, 0)
ChunkPosition pos = region.getPosition().asChunkPosition(x, z);
ChunkPosition pos = regionPos.asChunkPosition(x, z);
Chunk chunk = mapLoader.getWorld().currentDimension().getChunk(pos);

pixels[pixelOffset] = chunk.biomeColor();
if (isValid && !(chunk instanceof EmptyRegionChunk) && selection.isSelected(pos)) {
Expand Down
74 changes: 46 additions & 28 deletions chunky/src/java/se/llbit/chunky/map/WorldMapLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/
package se.llbit.chunky.map;

import java.util.Optional;
import java.util.function.BiConsumer;
import se.llbit.chunky.PersistentSettings;
import se.llbit.chunky.renderer.ChunkViewListener;
Expand All @@ -25,6 +26,8 @@
import se.llbit.chunky.world.region.RegionParser;
import se.llbit.chunky.world.region.RegionQueue;
import se.llbit.chunky.world.listeners.ChunkTopographyListener;
import se.llbit.chunky.world.worldformat.WorldFormats;
import se.llbit.log.Log;

import java.io.File;
import java.util.ArrayList;
Expand All @@ -46,7 +49,7 @@ public class WorldMapLoader implements ChunkTopographyListener, ChunkViewListene
private final ChunkTopographyUpdater topographyUpdater = new ChunkTopographyUpdater();

/** The dimension to load in the current world. */
private int currentDimensionId = PersistentSettings.getDimension();
private String currentDimensionId = PersistentSettings.getDimension();

private List<BiConsumer<World, Boolean>> worldLoadListeners = new ArrayList<>();

Expand All @@ -64,28 +67,44 @@ public WorldMapLoader(ChunkyFxController controller, MapView mapView) {
topographyUpdater.start();
}

public void loadWorldFromDirectory(File worldLocation) {
if (worldLocation == null) {
return;
}
this.loadWorld(WorldFormats.createWorld(worldLocation).orElse(EmptyWorld.INSTANCE));
}
/**
* This is called when a new world is loaded
*/
public void loadWorld(File worldDir) {
if (World.isWorldDir(worldDir)) {
if (world != null) {
world.currentDimension().removeChunkTopographyListener(this);
}
boolean isSameWorld = !(world instanceof EmptyWorld) && worldDir.equals(world.getWorldDirectory());
World newWorld = World.loadWorld(worldDir, currentDimensionId, World.LoggedWarnings.NORMAL);
newWorld.currentDimension().addChunkTopographyListener(this);
synchronized (this) {
world = newWorld;
updateRegionChangeWatcher(newWorld.currentDimension());

File newWorldDir = world.getWorldDirectory();
if (newWorldDir != null && !newWorldDir.equals(PersistentSettings.getLastWorld())) {
PersistentSettings.setLastWorld(newWorldDir);
}
public void loadWorld(World newWorld) {
if (this.world != null) {
this.world.currentDimension().removeChunkTopographyListener(this);
}
boolean isSameWorld = !(this.world instanceof EmptyWorld) && newWorld.getWorldDirectory().equals(this.world.getWorldDirectory());

Optional<String> dimensionToLoad = Optional.of(world.currentDimension())
.map(Dimension::getId)
.filter(dimension -> newWorld.availableDimensions().contains(dimension))
.or(newWorld::defaultDimension)
.or(() -> newWorld.availableDimensions().stream().findFirst());

if (dimensionToLoad.isEmpty()) {
Log.infof("No dimension loaded for world %s", newWorld.toString());
return;
}

Dimension loadedDim = newWorld.loadDimension(dimensionToLoad.get());
loadedDim.addChunkTopographyListener(this);
synchronized (this) {
this.world = newWorld;
updateRegionChangeWatcher(loadedDim);

File newWorldDir = this.world.getWorldDirectory();
if (!newWorldDir.equals(PersistentSettings.getLastWorld())) {
PersistentSettings.setLastWorld(newWorldDir);
}
worldLoadListeners.forEach(listener -> listener.accept(newWorld, isSameWorld));
}
worldLoadListeners.forEach(listener -> listener.accept(newWorld, isSameWorld));
}

/**
Expand Down Expand Up @@ -151,14 +170,12 @@ public void regionUpdated(RegionPosition region) {
public void reloadWorld() {
topographyUpdater.clearQueue();
world.currentDimension().removeChunkTopographyListener(this);
World newWorld = World.loadWorld(world.getWorldDirectory(), currentDimensionId,
World.LoggedWarnings.NORMAL);
newWorld.currentDimension().addChunkTopographyListener(this);
world.loadDimension(currentDimensionId);
world.currentDimension().addChunkTopographyListener(this);
synchronized (this) {
world = newWorld;
updateRegionChangeWatcher(newWorld.currentDimension());
updateRegionChangeWatcher(world.currentDimension());
}
worldLoadListeners.forEach(listener -> listener.accept(newWorld, true));
worldLoadListeners.forEach(listener -> listener.accept(world, true));
viewUpdated(mapView.getMapView()); // update visible chunks immediately
}

Expand All @@ -174,10 +191,11 @@ private void updateRegionChangeWatcher(Dimension dimension) {
/**
* Set the current dimension.
*
* @param value Must be a valid dimension index (0, -1, 1)
* @param value Must be a valid dimension see {@link World#availableDimensions()}
*/
public void setDimension(int value) {
if (value != currentDimensionId) {
// TODO: change this to show the available dimensions in the UI.
public void setDimension(String value) {
if (!value.equals(currentDimensionId)) {
currentDimensionId = value;
PersistentSettings.setDimension(currentDimensionId);

Expand All @@ -187,7 +205,7 @@ public void setDimension(int value) {
}

/** Get the currently loaded dimension. */
public int getDimension() {
public String getDimension() {
return currentDimensionId;
}
}
28 changes: 17 additions & 11 deletions chunky/src/java/se/llbit/chunky/renderer/scene/Scene.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
import se.llbit.chunky.world.biome.Biome;
import se.llbit.chunky.world.biome.BiomePalette;
import se.llbit.chunky.world.biome.Biomes;
import se.llbit.chunky.world.worldformat.WorldFormats;
import se.llbit.json.*;
import se.llbit.log.Log;
import se.llbit.math.*;
Expand Down Expand Up @@ -190,7 +191,7 @@ public class Scene implements JsonSerializable, Refreshable {
*/
protected int rayDepth = PersistentSettings.getRayDepthDefault();
protected String worldPath = "";
protected int worldDimension = 0;
protected String worldDimension = PersistentSettings.DEFAULT_DIMENSION;
protected RenderMode mode = RenderMode.PREVIEW;
protected int dumpFrequency = DEFAULT_DUMP_FREQUENCY;
protected boolean saveSnapshots = false;
Expand Down Expand Up @@ -543,11 +544,8 @@ public synchronized void loadScene(RenderContext context, String sceneName, Task
loadedWorld = EmptyWorld.INSTANCE;
if (!worldPath.isEmpty()) {
File worldDirectory = new File(worldPath);
if (World.isWorldDir(worldDirectory)) {
loadedWorld = World.loadWorld(worldDirectory, worldDimension, World.LoggedWarnings.NORMAL);
} else {
Log.info("Could not load world: " + worldPath);
}
loadedWorld = WorldFormats.createWorld(worldDirectory).orElse(EmptyWorld.INSTANCE);
loadedWorld.loadDimension(this.worldDimension);
}

loadDump(context, taskTracker);
Expand Down Expand Up @@ -761,7 +759,7 @@ public synchronized void reloadChunks(TaskTracker taskTracker) {
Log.warn("Can not reload chunks for scene - world directory not found!");
return;
}
loadedWorld = World.loadWorld(loadedWorld.getWorldDirectory(), worldDimension, World.LoggedWarnings.NORMAL);
loadedWorld.loadDimension(worldDimension);
loadChunks(taskTracker, loadedWorld, chunks);
refresh();
}
Expand Down Expand Up @@ -794,7 +792,7 @@ public synchronized void loadChunks(TaskTracker taskTracker, World world, Collec

loadedWorld = world;
worldPath = loadedWorld.getWorldDirectory().getAbsolutePath();
worldDimension = world.currentDimensionId();
worldDimension = world.currentDimension().getId();

if (chunksToLoad.isEmpty()) {
return;
Expand All @@ -821,7 +819,7 @@ public synchronized void loadChunks(TaskTracker taskTracker, World world, Collec
}

for (RegionPosition region : regions) {
dimension.getRegion(region).parse(yMin, yMax);
((JavaDimension) dimension).getRegion(region).parse(yMin, yMax);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since regions are handy for selecting many chunks, could we add virtual regions (= squares of many chunks) to Bedrock? As a UI feature, not as in actually loading regions, if the world format doesn't actually have regions.

Copy link
Member Author

@NotStirred NotStirred Jul 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea being abstract out a virtual region that also happens to be the same size as a java region?

}
}

Expand Down Expand Up @@ -1192,7 +1190,8 @@ public synchronized void loadChunks(TaskTracker taskTracker, World world, Collec

if (!chunkData.isEmpty()){
nonEmptyChunks.add(cp);
if (dimension.getChunk(cp).getVersion() == ChunkVersion.PRE_FLATTENING) {
Chunk chunk = dimension.getChunk(cp);
if (chunk instanceof JavaChunk javaChunk && javaChunk.getVersion() == ChunkVersion.PRE_FLATTENING) {
legacyChunks.add(cp);
}
}
Expand Down Expand Up @@ -2907,7 +2906,14 @@ else if(waterShader.equals("SIMPLEX"))
if (json.get("world").isObject()) {
JsonObject world = json.get("world").object();
worldPath = world.get("path").stringValue(worldPath);
worldDimension = world.get("dimension").intValue(worldDimension);

String dimensionString = world.get("dimension").stringValue("");
if (dimensionString.isEmpty()) {
// legacy int-based dimension indices
worldDimension = JavaWorld.VANILLA_DIMENSION_IDX_TO_ID.get(world.get("dimension").intValue(0));
} else {
worldDimension = dimensionString;
}
}

if (json.get("camera").isObject()) {
Expand Down
10 changes: 1 addition & 9 deletions chunky/src/java/se/llbit/chunky/ui/ChunkMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,16 @@

import javafx.application.Platform;
import javafx.geometry.Point2D;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.*;
import javafx.scene.control.Button;
import javafx.scene.control.Dialog;
import javafx.scene.control.MenuItem;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.Border;
import javafx.scene.layout.GridPane;
import javafx.stage.PopupWindow;
import se.llbit.chunky.map.MapBuffer;
import se.llbit.chunky.map.MapView;
Expand All @@ -45,7 +39,6 @@
import se.llbit.chunky.renderer.scene.SceneManager;
import se.llbit.chunky.ui.controller.ChunkyFxController;
import se.llbit.chunky.ui.dialogs.SelectChunksInRadiusDialog;
import se.llbit.chunky.ui.elements.TextFieldLabelWrapper;
import se.llbit.chunky.world.*;
import se.llbit.chunky.world.Dimension;
import se.llbit.chunky.world.listeners.ChunkUpdateListener;
Expand All @@ -57,7 +50,6 @@
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Optional;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
Expand Down Expand Up @@ -593,7 +585,7 @@ private void drawPlayers(GraphicsContext gc) {
World world = mapLoader.getWorld();
double blockScale = mapView.scale / 16.;
for (PlayerEntityData player : world.currentDimension().getPlayerPositions()) {
if (player.dimension == world.currentDimensionId()) {
if (player.dimension.equals(world.currentDimension().getId())) {
int px = (int) QuickMath.floor(player.x * blockScale);
int py = (int) QuickMath.floor(player.y);
int pz = (int) QuickMath.floor(player.z * blockScale);
Expand Down
Loading