From 1ded83c968d698b62379389acd210ae478f955a3 Mon Sep 17 00:00:00 2001 From: austin Date: Tue, 29 Apr 2025 12:54:07 -0400 Subject: [PATCH] structure editing tab --- docs/src/progress/renderertodo.md | 2 + .../client/block/ClientBlockSelection.java | 13 +++- .../leveledit/ClientLevelEditorData.java | 56 ++++++++++++++ .../client/ui/menu/editor/ImGuiAreaTab.java | 3 + .../menu/editor/ImGuiEditorDetailsWindow.java | 25 ++++++- .../ui/menu/editor/ImGuiEditorWindows.java | 22 ++++++ .../ui/menu/editor/ImGuiStructureTab.java | 75 +++++++++++++++++++ .../controls/cursor/CursorState.java | 6 +- .../java/electrosphere/engine/Globals.java | 4 + .../game/data/block/BlockFab.java | 43 ++++++++++- .../game/data/block/BlockFabMetadata.java | 42 +++++++++++ 11 files changed, 282 insertions(+), 9 deletions(-) create mode 100644 src/main/java/electrosphere/client/leveledit/ClientLevelEditorData.java create mode 100644 src/main/java/electrosphere/client/ui/menu/editor/ImGuiStructureTab.java create mode 100644 src/main/java/electrosphere/game/data/block/BlockFabMetadata.java diff --git a/docs/src/progress/renderertodo.md b/docs/src/progress/renderertodo.md index 2cdcccd0..b721a044 100644 --- a/docs/src/progress/renderertodo.md +++ b/docs/src/progress/renderertodo.md @@ -1581,6 +1581,8 @@ Grid alignment actually aligns entity to grid (04/29/2025) Fix door tree physics +BlockFab metadata +Structure editing tab in editor view diff --git a/src/main/java/electrosphere/client/block/ClientBlockSelection.java b/src/main/java/electrosphere/client/block/ClientBlockSelection.java index 4928b16e..8e662d92 100644 --- a/src/main/java/electrosphere/client/block/ClientBlockSelection.java +++ b/src/main/java/electrosphere/client/block/ClientBlockSelection.java @@ -79,6 +79,16 @@ public class ClientBlockSelection { * Exports currently selected area of voxels */ public static void exportSelection(){ + BlockFab fab = ClientBlockSelection.convertSelectionToFab(); + File exportLoc = new File("./assets/Data/fab/struct.block"); + fab.write(exportLoc); + } + + /** + * Converts the current selection by the player into a fab + * @return The fab + */ + public static BlockFab convertSelectionToFab(){ AreaSelection selection = Globals.cursorState.getAreaSelection(); Vector3i startChunk = Globals.clientWorldData.convertRealToWorldSpace(selection.getRectStart()); Vector3i endChunk = Globals.clientWorldData.convertRealToWorldSpace(selection.getRectEnd()); @@ -114,8 +124,7 @@ public class ClientBlockSelection { ); BlockFab fab = BlockFab.create(dimensions, types, metadata); - File exportLoc = new File("./assets/Data/fab/struct.block"); - fab.write(exportLoc); + return fab; } } diff --git a/src/main/java/electrosphere/client/leveledit/ClientLevelEditorData.java b/src/main/java/electrosphere/client/leveledit/ClientLevelEditorData.java new file mode 100644 index 00000000..84e2b7fe --- /dev/null +++ b/src/main/java/electrosphere/client/leveledit/ClientLevelEditorData.java @@ -0,0 +1,56 @@ +package electrosphere.client.leveledit; + +import org.joml.Vector3d; + +import electrosphere.game.data.block.BlockFab; + +/** + * Stores the data for the client's level edits + */ +public class ClientLevelEditorData { + + /** + * The currently edited fab + */ + private BlockFab currentFab; + + /** + * The origin point of the current fab + */ + private Vector3d currentFabOrigin; + + /** + * Gets the currently edited fab + * @return The fab if it exists, null otherwise + */ + public BlockFab getCurrentFab() { + return currentFab; + } + + /** + * Sets the fab currently being edited + * @param currentFab The fab + */ + public void setCurrentFab(BlockFab currentFab) { + this.currentFab = currentFab; + } + + /** + * Gets the origin point of the current fab + * @return The origin point + */ + public Vector3d getCurrentFabOrigin() { + return currentFabOrigin; + } + + /** + * Sets the origin point of the current fab + * @param currentFabOrigin The origin point + */ + public void setCurrentFabOrigin(Vector3d currentFabOrigin) { + this.currentFabOrigin = currentFabOrigin; + } + + + +} 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 0e82c4f2..06700712 100644 --- a/src/main/java/electrosphere/client/ui/menu/editor/ImGuiAreaTab.java +++ b/src/main/java/electrosphere/client/ui/menu/editor/ImGuiAreaTab.java @@ -18,6 +18,9 @@ public class ImGuiAreaTab { if(ImGui.button("Export Selected Blocks")){ ClientBlockSelection.exportSelection(); } + if(ImGui.button("Add Area Selection As Room")){ + ClientBlockSelection.exportSelection(); + } } } diff --git a/src/main/java/electrosphere/client/ui/menu/editor/ImGuiEditorDetailsWindow.java b/src/main/java/electrosphere/client/ui/menu/editor/ImGuiEditorDetailsWindow.java index a7ce5f22..6980a25e 100644 --- a/src/main/java/electrosphere/client/ui/menu/editor/ImGuiEditorDetailsWindow.java +++ b/src/main/java/electrosphere/client/ui/menu/editor/ImGuiEditorDetailsWindow.java @@ -32,10 +32,27 @@ public class ImGuiEditorDetailsWindow { detailsWindow.setCallback(new ImGuiWindowCallback() { @Override public void exec() { - ImGui.text("Some details or smthn"); - //close button - if(ImGui.button("Close")){ - detailsWindow.setOpen(false); + switch(ImGuiEditorWindows.getCurrentTab()){ + case 0: { + //general tab + ImGui.text("General tab"); + } break; + case 1: { + //assets tab + ImGui.text("Asset tab"); + } break; + case 2: { + //hierarchy tab + ImGui.text("Hierarchy tab"); + } break; + case 3: { + //area tab + ImGui.text("Area tab"); + } break; + case 4: { + //structure tab + ImGuiStructureTab.drawDetails(); + } break; } } }); 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 771fa567..9e18be03 100644 --- a/src/main/java/electrosphere/client/ui/menu/editor/ImGuiEditorWindows.java +++ b/src/main/java/electrosphere/client/ui/menu/editor/ImGuiEditorWindows.java @@ -19,6 +19,11 @@ public class ImGuiEditorWindows { //tracks if the editor menu is open private static boolean editorIsOpen = false; + /** + * The current tab + */ + private static int currentTab = 0; + /** * Initializes imgui windows */ @@ -46,10 +51,12 @@ public class ImGuiEditorWindows { public void exec() { if(ImGui.beginTabBar("Tabs")){ if(ImGui.beginTabItem("General")){ + currentTab = 0; ImGui.text("hello :)"); ImGui.endTabItem(); } if(ImGui.beginTabItem("Assets")){ + currentTab = 1; ImGui.text("asset selector here"); if(ImGui.button("testasset")){ @@ -61,13 +68,20 @@ public class ImGuiEditorWindows { ImGui.endTabItem(); } if(ImGui.beginTabItem("Hierarchy")){ + currentTab = 2; ImGui.text("hierarchy controls here"); ImGui.endTabItem(); } if(ImGui.beginTabItem("Areas")){ + currentTab = 3; ImGuiAreaTab.draw(); ImGui.endTabItem(); } + if(ImGui.beginTabItem("Structure")){ + currentTab = 4; + ImGuiStructureTab.draw(); + ImGui.endTabItem(); + } ImGui.endTabBar(); } @@ -105,4 +119,12 @@ public class ImGuiEditorWindows { } } + /** + * Gets the current tab + * @return The current tab + */ + protected static int getCurrentTab(){ + return currentTab; + } + } diff --git a/src/main/java/electrosphere/client/ui/menu/editor/ImGuiStructureTab.java b/src/main/java/electrosphere/client/ui/menu/editor/ImGuiStructureTab.java new file mode 100644 index 00000000..cf8572dd --- /dev/null +++ b/src/main/java/electrosphere/client/ui/menu/editor/ImGuiStructureTab.java @@ -0,0 +1,75 @@ +package electrosphere.client.ui.menu.editor; + +import java.io.File; + +import org.joml.Vector3d; + +import electrosphere.client.block.ClientBlockSelection; +import electrosphere.client.interact.select.AreaSelection; +import electrosphere.engine.Globals; +import electrosphere.game.data.block.BlockFab; +import electrosphere.game.data.block.BlockFabMetadata; +import imgui.ImGui; + +/** + * Tab for editing structures + */ +public class ImGuiStructureTab { + + /** + * Draws the contents of the structure tab + */ + protected static void draw(){ + if(Globals.clientLevelEditorData.getCurrentFab() == null){ + ImGui.text("No structure currently being edited"); + if(ImGui.button("Discover structure")){ + ClientBlockSelection.selectAllBlocks(); + AreaSelection area = Globals.cursorState.getAreaSelection(); + if(area != null){ + BlockFab blockFab = ClientBlockSelection.convertSelectionToFab(); + Globals.clientLevelEditorData.setCurrentFab(blockFab); + Globals.clientLevelEditorData.setCurrentFabOrigin(area.getRectStart()); + } + } + } else { + BlockFab currentFab = Globals.clientLevelEditorData.getCurrentFab(); + if(ImGui.button("Convert current selection to room")){ + AreaSelection currentSelection = Globals.cursorState.getAreaSelection(); + if(currentSelection != null){ + currentFab.getFabMetadata().getAreas().add(currentSelection); + } + } + if(ImGui.button("Save")){ + File exportLoc = new File("./assets/Data/fab/struct.block"); + currentFab.write(exportLoc); + } + } + } + + /** + * Draws the details tab + */ + protected static void drawDetails(){ + if(Globals.clientLevelEditorData.getCurrentFab() == null){ + ImGui.text("Select a fab to show details here"); + } else { + BlockFab currentFab = Globals.clientLevelEditorData.getCurrentFab(); + ImGui.text("Origin: " + Globals.clientLevelEditorData.getCurrentFabOrigin()); + ImGui.text("Dimensions: " + currentFab.getDimensions()); + BlockFabMetadata fabMetadata = currentFab.getFabMetadata(); + if(fabMetadata.getAreas() != null){ + if(ImGui.collapsingHeader("Areas in fab: " + fabMetadata.getAreas().size())){ + int i = 0; + for(AreaSelection area : fabMetadata.getAreas()){ + Vector3d dims = new Vector3d(area.getRectEnd()).sub(area.getRectStart()); + ImGui.text("Area " + i + " dimensions " + dims); + i++; + } + } + } else { + ImGui.text("Areas undefined in metadata"); + } + } + } + +} diff --git a/src/main/java/electrosphere/controls/cursor/CursorState.java b/src/main/java/electrosphere/controls/cursor/CursorState.java index f1781e3a..9343788a 100644 --- a/src/main/java/electrosphere/controls/cursor/CursorState.java +++ b/src/main/java/electrosphere/controls/cursor/CursorState.java @@ -506,7 +506,11 @@ public class CursorState { * Hints to show the block cursor */ public void hintShowBlockCursor(){ - if(!Globals.clientSceneWrapper.getScene().getEntitiesWithTag(EntityTags.DRAWABLE).contains(CursorState.playerFabCursor)){ + if( + !Globals.clientSceneWrapper.getScene().getEntitiesWithTag(EntityTags.DRAWABLE).contains(CursorState.playerFabCursor) && + !Globals.clientSceneWrapper.getScene().getEntitiesWithTag(EntityTags.DRAWABLE).contains(Globals.playerAreaCursor) && + !Globals.clientSceneWrapper.getScene().getEntitiesWithTag(EntityTags.DRAWABLE).contains(Globals.playerCursor) + ){ CursorState.makeBlockVisible(AssetDataStrings.TEXTURE_RED_TRANSPARENT); } } diff --git a/src/main/java/electrosphere/engine/Globals.java b/src/main/java/electrosphere/engine/Globals.java index 127d389a..086f907b 100644 --- a/src/main/java/electrosphere/engine/Globals.java +++ b/src/main/java/electrosphere/engine/Globals.java @@ -20,6 +20,7 @@ import electrosphere.client.entity.character.ClientCharacterManager; import electrosphere.client.entity.particle.ParticleService; import electrosphere.client.fluid.cells.FluidCellManager; import electrosphere.client.fluid.manager.ClientFluidManager; +import electrosphere.client.leveledit.ClientLevelEditorData; import electrosphere.client.player.ClientPlayerData; import electrosphere.client.scene.ClientSceneWrapper; import electrosphere.client.scene.ClientWorldData; @@ -399,6 +400,9 @@ public class Globals { //structure manager public static StructureManager structureManager; + + //client level editor data management + public static ClientLevelEditorData clientLevelEditorData = new ClientLevelEditorData(); //the player camera entity diff --git a/src/main/java/electrosphere/game/data/block/BlockFab.java b/src/main/java/electrosphere/game/data/block/BlockFab.java index ca35667b..6618cd07 100644 --- a/src/main/java/electrosphere/game/data/block/BlockFab.java +++ b/src/main/java/electrosphere/game/data/block/BlockFab.java @@ -8,6 +8,8 @@ import java.nio.ShortBuffer; import org.joml.Vector3i; +import com.google.gson.Gson; + import electrosphere.client.block.BlockChunkData; import electrosphere.logger.LoggerInterface; import electrosphere.renderer.meshgen.BlockMeshgenData; @@ -21,7 +23,7 @@ public class BlockFab implements BlockMeshgenData { /** * File version format */ - public static final int FILE_VER = 1; + public static final int FILE_VER = 2; /** * Size of the header for a block fab file @@ -47,6 +49,11 @@ public class BlockFab implements BlockMeshgenData { * Block metadata */ short[] metadata; + + /** + * The metadata of the fab + */ + BlockFabMetadata fabMetadata; /** * Creates a block fab @@ -60,6 +67,7 @@ public class BlockFab implements BlockMeshgenData { rVal.dimensions = dimensions; rVal.types = types; rVal.metadata = metadata; + rVal.fabMetadata = new BlockFabMetadata(); return rVal; } @@ -70,8 +78,11 @@ public class BlockFab implements BlockMeshgenData { public void write(File file){ int blockCount = dimensions.x * dimensions.y * dimensions.z; + Gson gson = new Gson(); + String serializedMetadata = gson.toJson(this.fabMetadata); + byte[] serializedMetadataBytes = serializedMetadata.getBytes(); - ByteBuffer buff = ByteBuffer.allocate(HEADER_SIZE + blockCount * BlockChunkData.BYTES_PER_BLOCK); + ByteBuffer buff = ByteBuffer.allocate(HEADER_SIZE + blockCount * BlockChunkData.BYTES_PER_BLOCK + serializedMetadataBytes.length); IntBuffer intView = buff.asIntBuffer(); intView.put(FILE_VER); @@ -84,6 +95,8 @@ public class BlockFab implements BlockMeshgenData { shortView.put(types); shortView.put(metadata); + buff.position(HEADER_SIZE + types.length * 2 + metadata.length * 2); + buff.put(serializedMetadataBytes); shortView.flip(); try { @@ -106,6 +119,9 @@ public class BlockFab implements BlockMeshgenData { IntBuffer intView = buff.asIntBuffer(); int fileVer = intView.get(); LoggerInterface.loggerFileIO.DEBUG("Read fab file with ver " + fileVer); + if(fileVer != FILE_VER){ + LoggerInterface.loggerFileIO.WARNING("Reading unsupported fab file with version: " + fileVer); + } int dimX = intView.get(); int dimY = intView.get(); @@ -137,10 +153,25 @@ public class BlockFab implements BlockMeshgenData { } } + //read the fab metadata + buff.position(HEADER_SIZE + blockCount * 2 + blockCount * 2); + BlockFabMetadata fabMetadata = new BlockFabMetadata(); + if(buff.remaining() > 0){ + byte[] fabMetadataBytes = new byte[buff.remaining()]; + buff.get(fabMetadataBytes); + String fabMetadataString = new String(fabMetadataBytes); + Gson gson = new Gson(); + fabMetadata = gson.fromJson(fabMetadataString, BlockFabMetadata.class); + } else { + LoggerInterface.loggerFileIO.WARNING("Fab file does not have metadata defined! " + file.getAbsolutePath()); + } + + //construct returned object rVal = new BlockFab(); rVal.dimensions = dims; rVal.types = types; rVal.metadata = metadata; + rVal.fabMetadata = fabMetadata; } catch (IOException e) { LoggerInterface.loggerFileIO.ERROR(e); throw new Error("Failed to read BlockFab " + file); @@ -183,4 +214,12 @@ public class BlockFab implements BlockMeshgenData { return this.types[x * dimensions.y * dimensions.z + y * dimensions.z + z]; } + /** + * Gets the fab metadata for the fab + * @return The metadata + */ + public BlockFabMetadata getFabMetadata(){ + return fabMetadata; + } + } diff --git a/src/main/java/electrosphere/game/data/block/BlockFabMetadata.java b/src/main/java/electrosphere/game/data/block/BlockFabMetadata.java new file mode 100644 index 00000000..1b6865dd --- /dev/null +++ b/src/main/java/electrosphere/game/data/block/BlockFabMetadata.java @@ -0,0 +1,42 @@ +package electrosphere.game.data.block; + +import java.util.LinkedList; +import java.util.List; + +import electrosphere.client.interact.select.AreaSelection; + +/** + * Metdata associated with the fab + */ +public class BlockFabMetadata { + + /** + * Area data for the fab + */ + private List areas; + + /** + * Constructor + */ + protected BlockFabMetadata(){ + this.areas = new LinkedList(); + } + + /** + * Gets the areas defined in the metadata + * @return The areas + */ + public List getAreas() { + return areas; + } + + /** + * Sets the areas defined in the metadata + * @param areas The areas + */ + public void setAreas(List areas) { + this.areas = areas; + } + + +}