diff --git a/docs/src/progress/renderertodo.md b/docs/src/progress/renderertodo.md index ddf062a7..fbd681d8 100644 --- a/docs/src/progress/renderertodo.md +++ b/docs/src/progress/renderertodo.md @@ -1539,6 +1539,9 @@ Variable block editing size Increase block cursor size Block area selection +(04/26/2025) +Exporting block prefabs to compressed files + diff --git a/src/main/java/electrosphere/client/block/BlockChunkData.java b/src/main/java/electrosphere/client/block/BlockChunkData.java index 384edad6..d84b8e26 100644 --- a/src/main/java/electrosphere/client/block/BlockChunkData.java +++ b/src/main/java/electrosphere/client/block/BlockChunkData.java @@ -19,10 +19,16 @@ public class BlockChunkData { */ public static final int TOTAL_DATA_WIDTH = CHUNK_DATA_WIDTH * CHUNK_DATA_WIDTH * CHUNK_DATA_WIDTH; + /** + * 2 - block type + * 2 - metadata + */ + public static final int BYTES_PER_BLOCK = 2 * 2; + /** * Size of a buffer that stores this chunk's data */ - public static final int BUFFER_SIZE = TOTAL_DATA_WIDTH * 2 * 2; + public static final int BUFFER_SIZE = TOTAL_DATA_WIDTH * BYTES_PER_BLOCK; /** * The number of blocks to place within each unit of distance diff --git a/src/main/java/electrosphere/client/block/ClientBlockSelection.java b/src/main/java/electrosphere/client/block/ClientBlockSelection.java index 00598939..65ef71ad 100644 --- a/src/main/java/electrosphere/client/block/ClientBlockSelection.java +++ b/src/main/java/electrosphere/client/block/ClientBlockSelection.java @@ -1,10 +1,13 @@ package electrosphere.client.block; +import java.io.File; + import org.joml.Vector3d; import org.joml.Vector3i; -import electrosphere.controls.cursor.CursorState; +import electrosphere.client.interact.select.AreaSelection; import electrosphere.engine.Globals; +import electrosphere.game.data.block.BlockFab; /** * Class for selecting blocks on the client @@ -68,8 +71,51 @@ public class ClientBlockSelection { } } } + AreaSelection selection = AreaSelection.createRect(minPos, maxPos); + Globals.cursorState.selectRectangularArea(selection); + } - CursorState.selectRectangularArea(minPos, maxPos); + /** + * Exports currently selected area of voxels + */ + public static void exportSelection(){ + AreaSelection selection = Globals.cursorState.getAreaSelection(); + Vector3i startChunk = Globals.clientWorldData.convertRealToWorldSpace(selection.getRectStart()); + Vector3i endChunk = Globals.clientWorldData.convertRealToWorldSpace(selection.getRectEnd()); + if(!startChunk.equals(endChunk)){ + throw new Error("Unsupported case! Selected are coverts multiple chunks.. " + startChunk + " " + endChunk); + } + Vector3i blockStart = Globals.clientWorldData.convertRealToBlockSpace(selection.getRectStart()); + Vector3i blockEnd = Globals.clientWorldData.convertRealToBlockSpace(selection.getRectEnd()); + + BlockChunkData chunk = Globals.clientBlockManager.getChunkDataAtWorldPoint(startChunk, 0); + if(chunk == null){ + throw new Error("Failed to grab chunk at " + startChunk); + } + + int blockCount = (blockEnd.x - blockStart.x) * (blockEnd.y - blockStart.y) * (blockEnd.z - blockStart.z); + short[] types = new short[blockCount]; + short[] metadata = new short[blockCount]; + int i = 0; + for(int x = blockStart.x; x < blockEnd.x; x++){ + for(int y = blockStart.y; y < blockEnd.y; y++){ + for(int z = blockStart.z; z < blockEnd.z; z++){ + types[i] = chunk.getType(x, y, z); + metadata[i] = chunk.getMetadata(x, y, z); + i++; + } + } + } + + Vector3i dimensions = new Vector3i( + (blockEnd.x - blockStart.x), + (blockEnd.y - blockStart.y), + (blockEnd.z - blockStart.z) + ); + + BlockFab fab = BlockFab.create(dimensions, types, metadata); + File exportLoc = new File("./struct.block"); + fab.save(exportLoc); } } diff --git a/src/main/java/electrosphere/client/interact/select/AreaSelection.java b/src/main/java/electrosphere/client/interact/select/AreaSelection.java new file mode 100644 index 00000000..082b1a28 --- /dev/null +++ b/src/main/java/electrosphere/client/interact/select/AreaSelection.java @@ -0,0 +1,75 @@ +package electrosphere.client.interact.select; + +import org.joml.Vector3d; + +/** + * An area of space that is selected by the client + */ +public class AreaSelection { + + /** + * The type of selection + */ + public static enum AreaSelectionType { + /** + * A rectangle + */ + RECTANGULAR, + } + + /** + * The type of selection + */ + private AreaSelectionType type; + + /** + * The start point of the rectangular selection + */ + private Vector3d rectStart; + + /** + * The end point of the rectangular selection + */ + private Vector3d rectEnd; + + /** + * Creates a rectangular selection + * @param start The start point + * @param end The end point + * @return The selection + */ + public static AreaSelection createRect(Vector3d start, Vector3d end){ + AreaSelection rVal = new AreaSelection(); + rVal.type = AreaSelectionType.RECTANGULAR; + rVal.rectStart = start; + rVal.rectEnd = end; + return rVal; + } + + /** + * Gets the type of area + * @return The type of area + */ + public AreaSelectionType getType() { + return type; + } + + /** + * Gets the start point of the rectangular selection + * @return The start point + */ + public Vector3d getRectStart() { + return rectStart; + } + + /** + * Gets the end point of the rectangular selection + * @return The end point + */ + public Vector3d getRectEnd() { + return rectEnd; + } + + + +} diff --git a/src/main/java/electrosphere/client/ui/menu/editor/ImGuiAreaTab.java b/src/main/java/electrosphere/client/ui/menu/editor/ImGuiAreaTab.java index a2811f43..0e82c4f2 100644 --- a/src/main/java/electrosphere/client/ui/menu/editor/ImGuiAreaTab.java +++ b/src/main/java/electrosphere/client/ui/menu/editor/ImGuiAreaTab.java @@ -15,6 +15,9 @@ public class ImGuiAreaTab { if(ImGui.button("Select all voxels")){ ClientBlockSelection.selectAllBlocks(); } + if(ImGui.button("Export Selected Blocks")){ + ClientBlockSelection.exportSelection(); + } } } diff --git a/src/main/java/electrosphere/controls/cursor/CursorState.java b/src/main/java/electrosphere/controls/cursor/CursorState.java index 7366349d..054aefd7 100644 --- a/src/main/java/electrosphere/controls/cursor/CursorState.java +++ b/src/main/java/electrosphere/controls/cursor/CursorState.java @@ -7,6 +7,7 @@ import org.joml.Vector3i; import electrosphere.client.block.BlockChunkData; import electrosphere.client.entity.camera.CameraEntityUtils; +import electrosphere.client.interact.select.AreaSelection; import electrosphere.collision.CollisionEngine; import electrosphere.engine.Globals; import electrosphere.engine.assetmanager.AssetDataStrings; @@ -59,6 +60,11 @@ public class CursorState { */ private int blockSize = MIN_BLOCK_SIZE; + /** + * The current selection of the area selector + */ + private AreaSelection areaCursorSelection = null; + /** * Creates the cursor entities */ @@ -154,12 +160,12 @@ public class CursorState { /** * Selects a rectangular area - * @param startPos The start position of the area - * @param endPos The end position of the area + * @param selection The area selection to encompass the cursor */ - public static void selectRectangularArea(Vector3d startPos, Vector3d endPos){ - Vector3d center = new Vector3d(startPos).add(endPos).mul(0.5f); - Vector3d scale = new Vector3d(startPos).sub(endPos).absolute(); + public void selectRectangularArea(AreaSelection selection){ + this.areaCursorSelection = selection; + Vector3d center = new Vector3d(areaCursorSelection.getRectStart()).add(areaCursorSelection.getRectEnd()).mul(0.5f); + Vector3d scale = new Vector3d(areaCursorSelection.getRectStart()).sub(areaCursorSelection.getRectEnd()).absolute(); EntityCreationUtils.makeEntityDrawable(Globals.playerAreaCursor, AssetDataStrings.UNITCUBE); Actor areaCursorActor = EntityUtils.getActor(Globals.playerAreaCursor); areaCursorActor.addTextureMask(new ActorTextureMask("cube", Arrays.asList(new String[]{"Textures/transparent_red.png"}))); @@ -260,5 +266,13 @@ public class CursorState { public int getBlockSize(){ return blockSize; } + + /** + * Gets the currently selected area + * @return The currently selected area + */ + public AreaSelection getAreaSelection(){ + return this.areaCursorSelection; + } } diff --git a/src/main/java/electrosphere/game/data/block/BlockFab.java b/src/main/java/electrosphere/game/data/block/BlockFab.java new file mode 100644 index 00000000..0eb5c94e --- /dev/null +++ b/src/main/java/electrosphere/game/data/block/BlockFab.java @@ -0,0 +1,85 @@ +package electrosphere.game.data.block; + +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; + +import org.joml.Vector3i; + +import electrosphere.client.block.BlockChunkData; +import electrosphere.util.FileUtils; + +/** + * A collection of blocks + */ +public class BlockFab { + + /** + * Size of the header for a block fab file + * 3 * 4 for the dimensions at the front of the file + */ + public static final int HEADER_SIZE = 3 * 4; + + /** + * Dimensions of the block fab + */ + Vector3i dimensions; + + /** + * Block type data + */ + short[] types; + + /** + * Block metadata + */ + short[] metadata; + + /** + * Creates a block fab + * @param dimensions The dimensions of the fab + * @param types The block types + * @param metadata The block metadata + * @return The block fab + */ + public static BlockFab create(Vector3i dimensions, short[] types, short[] metadata){ + BlockFab rVal = new BlockFab(); + rVal.dimensions = dimensions; + rVal.types = types; + rVal.metadata = metadata; + return rVal; + } + + /** + * Saves this fab to a file + * @param file The file + */ + public void save(File file){ + int blockCount = dimensions.x * dimensions.y * dimensions.z; + + + ByteBuffer buff = ByteBuffer.allocate(HEADER_SIZE + blockCount * BlockChunkData.BYTES_PER_BLOCK); + + IntBuffer intView = buff.asIntBuffer(); + intView.put(dimensions.x); + intView.put(dimensions.y); + intView.put(dimensions.z); + buff.position(HEADER_SIZE); + + ShortBuffer shortView = buff.asShortBuffer(); + + shortView.put(types); + shortView.put(metadata); + shortView.flip(); + + File exportLoc = new File("./struct.block"); + try { + FileUtils.writeBufferToCompressedFile(exportLoc, buff); + } catch (IOException e) { + throw new Error("Failed to export selected blocks to a file!"); + } + } + +} diff --git a/src/main/java/electrosphere/util/FileUtils.java b/src/main/java/electrosphere/util/FileUtils.java index d912fbf8..95a45fe5 100644 --- a/src/main/java/electrosphere/util/FileUtils.java +++ b/src/main/java/electrosphere/util/FileUtils.java @@ -33,6 +33,8 @@ import java.security.NoSuchAlgorithmException; import java.util.LinkedList; import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; import javax.imageio.ImageIO; @@ -526,6 +528,32 @@ public class FileUtils { } } + /** + * Writes a ByteBuffer to a file and compresses it + * @param file The file + * @param buff The buffer + */ + public static void writeBufferToCompressedFile(File file, ByteBuffer buff) throws IOException { + try (GZIPOutputStream outStream = new GZIPOutputStream(Files.newOutputStream(file.toPath()))){ + outStream.write(buff.array()); + } + } + + /** + * Writes a ByteBuffer to a file and compresses it + * @param file The file + * @param buff The buffer + */ + public static ByteBuffer readBufferFromCompressedFile(File file) throws IOException { + ByteBuffer buff = null; + try (GZIPInputStream inStream = new GZIPInputStream(Files.newInputStream(file.toPath()))){ + byte[] bytes = inStream.readAllBytes(); + buff = ByteBuffer.allocate(bytes.length); + buff.put(bytes); + buff.flip(); + } + return buff; + }