From 20c53b151bebdc079facc1fbce3bf4dec87fdb60 Mon Sep 17 00:00:00 2001 From: austin Date: Sat, 26 Apr 2025 00:16:36 -0400 Subject: [PATCH] block area selection control --- docs/src/progress/renderertodo.md | 3 + .../client/block/BlockChunkData.java | 5 ++ .../client/block/ClientBlockSelection.java | 75 +++++++++++++++++++ .../client/scene/ClientWorldData.java | 24 ++++++ .../client/ui/menu/editor/ImGuiAreaTab.java | 20 +++++ .../ui/menu/editor/ImGuiEditorWindows.java | 4 + .../controls/cursor/CursorState.java | 43 ++++++++++- .../java/electrosphere/engine/Globals.java | 1 + 8 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 src/main/java/electrosphere/client/block/ClientBlockSelection.java create mode 100644 src/main/java/electrosphere/client/ui/menu/editor/ImGuiAreaTab.java diff --git a/docs/src/progress/renderertodo.md b/docs/src/progress/renderertodo.md index bf283203..ddf062a7 100644 --- a/docs/src/progress/renderertodo.md +++ b/docs/src/progress/renderertodo.md @@ -1536,6 +1536,9 @@ Audio on placing blocks Audio on placing/removing voxels Debounce item usage activations Variable block editing size +Increase block cursor size +Block area selection + diff --git a/src/main/java/electrosphere/client/block/BlockChunkData.java b/src/main/java/electrosphere/client/block/BlockChunkData.java index 8bffdc85..384edad6 100644 --- a/src/main/java/electrosphere/client/block/BlockChunkData.java +++ b/src/main/java/electrosphere/client/block/BlockChunkData.java @@ -69,6 +69,11 @@ public class BlockChunkData { */ public static final int LOD_LOWEST_RES = LOD_SIXTEENTH_RES; + /** + * An empty block + */ + public static final short BLOCK_TYPE_EMPTY = 0; + /** * The type of block at a given position diff --git a/src/main/java/electrosphere/client/block/ClientBlockSelection.java b/src/main/java/electrosphere/client/block/ClientBlockSelection.java new file mode 100644 index 00000000..00598939 --- /dev/null +++ b/src/main/java/electrosphere/client/block/ClientBlockSelection.java @@ -0,0 +1,75 @@ +package electrosphere.client.block; + +import org.joml.Vector3d; +import org.joml.Vector3i; + +import electrosphere.controls.cursor.CursorState; +import electrosphere.engine.Globals; + +/** + * Class for selecting blocks on the client + */ +public class ClientBlockSelection { + + /** + * Selects all blocks on the client + */ + public static void selectAllBlocks(){ + Vector3d minPos = new Vector3d(); + Vector3d maxPos = new Vector3d(); + + Vector3i minVoxelPos = new Vector3i(); + Vector3i maxVoxelPos = new Vector3i(); + + Vector3i chunkPos = new Vector3i(); + + boolean encountered = false; + + for(int x = 0; x < Globals.clientWorldData.getWorldDiscreteSize(); x++){ + for(int y = 0; y < Globals.clientWorldData.getWorldDiscreteSize(); y++){ + for(int z = 0; z < Globals.clientWorldData.getWorldDiscreteSize(); z++){ + chunkPos = new Vector3i(x,y,z); + + BlockChunkData blockChunkData = Globals.clientBlockManager.getChunkDataAtWorldPoint(chunkPos, 0); + if(blockChunkData.getHomogenousValue() == BlockChunkData.BLOCK_TYPE_EMPTY){ + continue; + } + + boolean foundVoxel = false; + for(int i = 0; i < BlockChunkData.CHUNK_DATA_WIDTH; i++){ + for(int j = 0; j < BlockChunkData.CHUNK_DATA_WIDTH; j++){ + for(int k = 0; k < BlockChunkData.CHUNK_DATA_WIDTH; k++){ + if(blockChunkData.getType(i, j, k) == BlockChunkData.BLOCK_TYPE_EMPTY){ + continue; + } + if(!foundVoxel){ + foundVoxel = true; + minVoxelPos.set(i,j,k); + maxVoxelPos.set(i+1,j+1,k+1); + } else { + minVoxelPos.min(new Vector3i(i,j,k)); + maxVoxelPos.max(new Vector3i(i+1,j+1,k+1)); + } + } + } + } + + Vector3d localMin = Globals.clientWorldData.convertBlockToRealSpace(chunkPos, minVoxelPos); + Vector3d localMax = Globals.clientWorldData.convertBlockToRealSpace(chunkPos, maxVoxelPos); + + if(!encountered){ + encountered = true; + minPos.set(localMin); + maxPos.set(localMax); + } else { + minPos.min(localMin); + maxPos.max(localMax); + } + } + } + } + + CursorState.selectRectangularArea(minPos, maxPos); + } + +} diff --git a/src/main/java/electrosphere/client/scene/ClientWorldData.java b/src/main/java/electrosphere/client/scene/ClientWorldData.java index bb39c87e..28b4b507 100644 --- a/src/main/java/electrosphere/client/scene/ClientWorldData.java +++ b/src/main/java/electrosphere/client/scene/ClientWorldData.java @@ -215,4 +215,28 @@ public class ClientWorldData { ); } + /** + * Converts a block position to a real position + * @param chunkPos The position of the chunk + * @param blockPos The position of the block within the chunk + * @return The real position corresponding to the block's position + */ + public double convertBlockToRealSpace(int chunkPos, int blockPos){ + return ServerTerrainChunk.CHUNK_PLACEMENT_OFFSET * chunkPos + BlockChunkData.BLOCK_SIZE_MULTIPLIER * blockPos; + } + + /** + * Converts a block position to a real position + * @param chunkPos The chunk's position + * @param blockPos The block's position + * @return The real position + */ + public Vector3d convertBlockToRealSpace(Vector3i chunkPos, Vector3i blockPos){ + return new Vector3d( + convertBlockToRealSpace(chunkPos.x, blockPos.x), + convertBlockToRealSpace(chunkPos.y, blockPos.y), + convertBlockToRealSpace(chunkPos.z, blockPos.z) + ); + } + } diff --git a/src/main/java/electrosphere/client/ui/menu/editor/ImGuiAreaTab.java b/src/main/java/electrosphere/client/ui/menu/editor/ImGuiAreaTab.java new file mode 100644 index 00000000..a2811f43 --- /dev/null +++ b/src/main/java/electrosphere/client/ui/menu/editor/ImGuiAreaTab.java @@ -0,0 +1,20 @@ +package electrosphere.client.ui.menu.editor; + +import electrosphere.client.block.ClientBlockSelection; +import imgui.ImGui; + +/** + * Class for drawing the area tab of the editor window + */ +public class ImGuiAreaTab { + + /** + * Draws the area tab + */ + protected static void draw(){ + if(ImGui.button("Select all voxels")){ + ClientBlockSelection.selectAllBlocks(); + } + } + +} diff --git a/src/main/java/electrosphere/client/ui/menu/editor/ImGuiEditorWindows.java b/src/main/java/electrosphere/client/ui/menu/editor/ImGuiEditorWindows.java index 0ec59173..771fa567 100644 --- a/src/main/java/electrosphere/client/ui/menu/editor/ImGuiEditorWindows.java +++ b/src/main/java/electrosphere/client/ui/menu/editor/ImGuiEditorWindows.java @@ -64,6 +64,10 @@ public class ImGuiEditorWindows { ImGui.text("hierarchy controls here"); ImGui.endTabItem(); } + if(ImGui.beginTabItem("Areas")){ + ImGuiAreaTab.draw(); + ImGui.endTabItem(); + } ImGui.endTabBar(); } diff --git a/src/main/java/electrosphere/controls/cursor/CursorState.java b/src/main/java/electrosphere/controls/cursor/CursorState.java index 6d4e2af1..7366349d 100644 --- a/src/main/java/electrosphere/controls/cursor/CursorState.java +++ b/src/main/java/electrosphere/controls/cursor/CursorState.java @@ -34,6 +34,11 @@ public class CursorState { */ public static final String CURSOR_BLOCK_TOKEN = "CURSOR_BLOCK"; + /** + * Token for displaying a block area selection cursor + */ + public static final String CURSOR_AREA_TOKEN = "CURSOR_AREA"; + /** * Minimum size of the block cursor */ @@ -47,7 +52,7 @@ public class CursorState { /** * The scale multiplier for scaling the block cursor */ - private static final float BLOCK_CURSOR_SCALE_MULTIPLIER = 0.2f; + private static final float BLOCK_CURSOR_SCALE_MULTIPLIER = 0.25f; /** * Size of blocks to edit @@ -75,6 +80,15 @@ public class CursorState { DrawableUtils.makeEntityTransparent(Globals.playerBlockCursor); EntityUtils.getScale(Globals.playerBlockCursor).set(BLOCK_CURSOR_SCALE_MULTIPLIER); Globals.clientSceneWrapper.getScene().removeEntityFromTag(Globals.playerBlockCursor, EntityTags.DRAWABLE); + + //player's area cursor + Globals.playerAreaCursor = EntityCreationUtils.createClientSpatialEntity(); + 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"}))); + DrawableUtils.makeEntityTransparent(Globals.playerAreaCursor); + EntityUtils.getScale(Globals.playerAreaCursor).set(BLOCK_CURSOR_SCALE_MULTIPLIER); + Globals.clientSceneWrapper.getScene().removeEntityFromTag(Globals.playerAreaCursor, EntityTags.DRAWABLE); } /** @@ -107,6 +121,7 @@ public class CursorState { */ public static void makeRealVisible(){ Globals.clientSceneWrapper.getScene().removeEntityFromTag(Globals.playerBlockCursor, EntityTags.DRAWABLE); + Globals.clientSceneWrapper.getScene().removeEntityFromTag(Globals.playerAreaCursor, EntityTags.DRAWABLE); Globals.clientSceneWrapper.getScene().registerEntityToTag(Globals.playerCursor, EntityTags.DRAWABLE); } @@ -115,15 +130,41 @@ public class CursorState { */ public static void makeBlockVisible(){ Globals.clientSceneWrapper.getScene().removeEntityFromTag(Globals.playerCursor, EntityTags.DRAWABLE); + Globals.clientSceneWrapper.getScene().removeEntityFromTag(Globals.playerAreaCursor, EntityTags.DRAWABLE); Globals.clientSceneWrapper.getScene().registerEntityToTag(Globals.playerBlockCursor, EntityTags.DRAWABLE); } + /** + * Makes the area position cursor visible + */ + public static void makeAreaVisible(){ + Globals.clientSceneWrapper.getScene().removeEntityFromTag(Globals.playerCursor, EntityTags.DRAWABLE); + Globals.clientSceneWrapper.getScene().removeEntityFromTag(Globals.playerBlockCursor, EntityTags.DRAWABLE); + Globals.clientSceneWrapper.getScene().registerEntityToTag(Globals.playerAreaCursor, EntityTags.DRAWABLE); + } + /** * Hides the cursor */ public static void hide(){ Globals.clientSceneWrapper.getScene().removeEntityFromTag(Globals.playerCursor, EntityTags.DRAWABLE); Globals.clientSceneWrapper.getScene().removeEntityFromTag(Globals.playerBlockCursor, EntityTags.DRAWABLE); + Globals.clientSceneWrapper.getScene().removeEntityFromTag(Globals.playerAreaCursor, EntityTags.DRAWABLE); + } + + /** + * Selects a rectangular area + * @param startPos The start position of the area + * @param endPos The end position of the area + */ + 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(); + 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"}))); + EntityUtils.getPosition(Globals.playerAreaCursor).set(center); + EntityUtils.getScale(Globals.playerAreaCursor).set(scale); } /** diff --git a/src/main/java/electrosphere/engine/Globals.java b/src/main/java/electrosphere/engine/Globals.java index 3f83721b..127d389a 100644 --- a/src/main/java/electrosphere/engine/Globals.java +++ b/src/main/java/electrosphere/engine/Globals.java @@ -407,6 +407,7 @@ public class Globals { //the player in world cursor public static Entity playerCursor; public static Entity playerBlockCursor; + public static Entity playerAreaCursor; //the creature the player camera will orbit and will receive controlHandler movementTree updates public static Entity playerEntity;