From cad9a994d00bbd3161f769a067612b2979309c48 Mon Sep 17 00:00:00 2001 From: austin Date: Mon, 28 Apr 2025 12:46:26 -0400 Subject: [PATCH] area selection utility --- assets/Data/entity/items/debug_tools.json | 31 +++++ assets/Scripts/client/clienthooks.ts | 6 + .../types/host/client/client-area-utils.ts | 11 ++ assets/Scripts/types/host/static-classes.ts | 6 + docs/src/progress/renderertodo.md | 4 + .../client/block/BlockChunkData.java | 9 ++ .../client/interact/select/AreaSelection.java | 111 ++++++++++++++++++ .../client/scene/ClientWorldData.java | 10 ++ .../client/script/ScriptClientAreaUtils.java | 31 +++++ .../engine/loadingthreads/LoadingUtils.java | 1 + .../server/protocol/CharacterProtocol.java | 2 + .../electrosphere/script/ScriptEngine.java | 2 + 12 files changed, 224 insertions(+) create mode 100644 assets/Scripts/types/host/client/client-area-utils.ts create mode 100644 src/main/java/electrosphere/client/script/ScriptClientAreaUtils.java diff --git a/assets/Data/entity/items/debug_tools.json b/assets/Data/entity/items/debug_tools.json index 4c6b6061..022476b5 100644 --- a/assets/Data/entity/items/debug_tools.json +++ b/assets/Data/entity/items/debug_tools.json @@ -158,6 +158,37 @@ "offsetZ" : 0 }, "iconPath" : "Textures/icons/hammer.png" + }, + { + "id" : "roomTool", + "tokens" : [ + "GRAVITY", + "TARGETABLE", + "CURSOR_FAB" + ], + "equipData": { + "equipClass" : "tool" + }, + "graphicsTemplate": { + "model": { + "path" : "Models/basic/geometry/unitvector.glb" + } + }, + "clientSideSecondary": "SELECT_ROOM", + "collidable": { + "type" : "CUBE", + "dimension1" : 0.1, + "dimension2" : 0.1, + "dimension3" : 0.35, + "rotX": 0, + "rotY": 0, + "rotZ": 0, + "rotW": 1, + "offsetX" : 0, + "offsetY" : 0.05, + "offsetZ" : 0 + }, + "iconPath" : "Textures/icons/hammer.png" } ], "files" : [ diff --git a/assets/Scripts/client/clienthooks.ts b/assets/Scripts/client/clienthooks.ts index 411572e7..476f90bc 100644 --- a/assets/Scripts/client/clienthooks.ts +++ b/assets/Scripts/client/clienthooks.ts @@ -58,5 +58,11 @@ export const clientHooks: Hook[] = [ callback: (engine: Engine) => { engine.classes.voxelUtils.static.placeFab() } + }, + { + signal: "SELECT_ROOM", + callback: (engine: Engine) => { + engine.classes.areaUtils.static.selectAreaRectangular() + } } ] \ No newline at end of file diff --git a/assets/Scripts/types/host/client/client-area-utils.ts b/assets/Scripts/types/host/client/client-area-utils.ts new file mode 100644 index 00000000..0e217d2d --- /dev/null +++ b/assets/Scripts/types/host/client/client-area-utils.ts @@ -0,0 +1,11 @@ +/** + * Utilities for managing areas on the client + */ +export interface ClientAreaUtils { + + /** + * Selects a rectangular area + */ + readonly selectAreaRectangular: () => void + +} \ No newline at end of file diff --git a/assets/Scripts/types/host/static-classes.ts b/assets/Scripts/types/host/static-classes.ts index 6541ddad..56260aa4 100644 --- a/assets/Scripts/types/host/static-classes.ts +++ b/assets/Scripts/types/host/static-classes.ts @@ -1,3 +1,4 @@ +import { ClientAreaUtils } from "/Scripts/types/host/client/client-area-utils"; import { ClientLevelEditorUtils } from "/Scripts/types/host/client/client-level-editor-utils"; import { ClientVoxelUtils } from "/Scripts/types/host/client/client-voxel-utils"; import { Entity } from "/Scripts/types/host/entity/entity"; @@ -51,6 +52,11 @@ export interface StaticClasses { */ readonly math?: Class + /** + * Utilities for area management on the client + */ + readonly areaUtils?: Class, + } /** diff --git a/docs/src/progress/renderertodo.md b/docs/src/progress/renderertodo.md index 3b8de63a..37a1284c 100644 --- a/docs/src/progress/renderertodo.md +++ b/docs/src/progress/renderertodo.md @@ -1574,6 +1574,10 @@ Block cursor work ChunkDiskMap code org Filter procedural worlds out of level select menu +(04/28/2025) +Area selection utility +RoomTool item + diff --git a/src/main/java/electrosphere/client/block/BlockChunkData.java b/src/main/java/electrosphere/client/block/BlockChunkData.java index 56718b46..cf62dfb1 100644 --- a/src/main/java/electrosphere/client/block/BlockChunkData.java +++ b/src/main/java/electrosphere/client/block/BlockChunkData.java @@ -287,6 +287,15 @@ public class BlockChunkData implements BlockMeshgenData { boolean empty = this.getType(x,y,z) == BlockChunkData.BLOCK_TYPE_EMPTY; return empty; } + + /** + * Checks if a given location is empty + * @param vec The position + * @return true if empty, false otherwise + */ + public boolean isEmpty(Vector3i vec){ + return this.isEmpty(vec.x,vec.y,vec.z); + } /** * Checks if this block chunk is homogenous or not diff --git a/src/main/java/electrosphere/client/interact/select/AreaSelection.java b/src/main/java/electrosphere/client/interact/select/AreaSelection.java index 21750515..857750ca 100644 --- a/src/main/java/electrosphere/client/interact/select/AreaSelection.java +++ b/src/main/java/electrosphere/client/interact/select/AreaSelection.java @@ -3,6 +3,9 @@ package electrosphere.client.interact.select; import org.joml.Vector3d; import org.joml.Vector3i; +import electrosphere.client.block.BlockChunkData; +import electrosphere.engine.Globals; + /** * An area of space that is selected by the client */ @@ -18,6 +21,11 @@ public class AreaSelection { RECTANGULAR, } + /** + * Default radius to select + */ + public static final int DEFAULT_SELECTION_RADIUS = 10; + /** * The type of selection */ @@ -40,6 +48,15 @@ public class AreaSelection { * @return The selection */ public static AreaSelection createRect(Vector3d start, Vector3d end){ + if(start.x > end.x){ + throw new Error("Start x is less than end x! " + start.x + " " + end.x); + } + if(start.y > end.y){ + throw new Error("Start y is less than end y! " + start.y + " " + end.y); + } + if(start.z > end.z){ + throw new Error("Start y is less than end y! " + start.z + " " + end.z); + } AreaSelection rVal = new AreaSelection(); rVal.type = AreaSelectionType.RECTANGULAR; rVal.rectStart = start; @@ -57,6 +74,100 @@ public class AreaSelection { public static AreaSelection selectRectangularBlockCavity(Vector3i chunkPos, Vector3i blockPos, int maxRadius){ AreaSelection rVal = null; + int radStart = 0; + int radEnd = 1; + int increment = 0; + boolean expandPositive = true; + boolean expandNegative = true; + Vector3i currVoxelPos = new Vector3i(); + Vector3i currChunkPos = new Vector3i(); + while(radStart > -maxRadius && radEnd < maxRadius && (expandPositive || expandNegative)){ + if(increment % 2 == 0){ + if(!expandPositive){ + continue; + } + } else { + if(!expandNegative){ + continue; + } + } + for(int x = radStart; x < radEnd; x++){ + for(int y = radStart; y < radEnd; y++){ + for(int z = radStart; z < radEnd; z++){ + currVoxelPos.set(blockPos).add(x,y,z); + currChunkPos.set(chunkPos).add( + currVoxelPos.x / BlockChunkData.CHUNK_DATA_WIDTH, + currVoxelPos.y / BlockChunkData.CHUNK_DATA_WIDTH, + currVoxelPos.z / BlockChunkData.CHUNK_DATA_WIDTH + ); + currVoxelPos.set( + currVoxelPos.x % BlockChunkData.CHUNK_DATA_WIDTH, + currVoxelPos.y % BlockChunkData.CHUNK_DATA_WIDTH, + currVoxelPos.z % BlockChunkData.CHUNK_DATA_WIDTH + ); + if(!Globals.clientWorldData.chunkInBounds(currChunkPos)){ + if(increment % 2 == 0){ + expandPositive = false; + } else { + expandNegative = false; + } + continue; + } + BlockChunkData chunkData = Globals.clientBlockManager.getChunkDataAtWorldPoint(currChunkPos, BlockChunkData.LOD_FULL_RES); + if(chunkData == null){ + if(increment % 2 == 0){ + expandPositive = false; + } else { + expandNegative = false; + } + continue; + } + while(currVoxelPos.x < 0){ + currVoxelPos.x = currVoxelPos.x + BlockChunkData.CHUNK_DATA_WIDTH; + } + while(currVoxelPos.y < 0){ + currVoxelPos.y = currVoxelPos.y + BlockChunkData.CHUNK_DATA_WIDTH; + } + while(currVoxelPos.z < 0){ + currVoxelPos.z = currVoxelPos.z + BlockChunkData.CHUNK_DATA_WIDTH; + } + if(!chunkData.isEmpty(currVoxelPos)){ + if(increment % 2 == 0){ + expandPositive = false; + } else { + expandNegative = false; + } + } + } + } + } + if(increment % 2 == 0){ + if(expandPositive){ + radEnd++; + } + } else { + if(expandNegative){ + radStart--; + } + } + increment++; + } + + Vector3d startPos = new Vector3d(Globals.clientWorldData.convertBlockToRealSpace(chunkPos, blockPos)) + .add( + radStart * BlockChunkData.BLOCK_SIZE_MULTIPLIER, + radStart * BlockChunkData.BLOCK_SIZE_MULTIPLIER, + radStart * BlockChunkData.BLOCK_SIZE_MULTIPLIER + ); + Vector3d endPos = new Vector3d(Globals.clientWorldData.convertBlockToRealSpace(chunkPos, blockPos)) + .add( + radEnd * BlockChunkData.BLOCK_SIZE_MULTIPLIER, + radEnd * BlockChunkData.BLOCK_SIZE_MULTIPLIER, + radEnd * BlockChunkData.BLOCK_SIZE_MULTIPLIER + ); + + rVal = AreaSelection.createRect(startPos, endPos); + return rVal; } diff --git a/src/main/java/electrosphere/client/scene/ClientWorldData.java b/src/main/java/electrosphere/client/scene/ClientWorldData.java index 28b4b507..feed7eb3 100644 --- a/src/main/java/electrosphere/client/scene/ClientWorldData.java +++ b/src/main/java/electrosphere/client/scene/ClientWorldData.java @@ -239,4 +239,14 @@ public class ClientWorldData { ); } + /** + * Checks that a chunk position is in bounds + * @param chunkPos The chunk pos + * @return true if it is in bounds, false otherwise + */ + public boolean chunkInBounds(Vector3i chunkPos){ + return chunkPos.x >= 0 && chunkPos.y >= 0 && chunkPos.z >= 0 && + chunkPos.x < this.worldDiscreteSize && chunkPos.y < this.worldDiscreteSize && chunkPos.z < this.worldDiscreteSize; + } + } diff --git a/src/main/java/electrosphere/client/script/ScriptClientAreaUtils.java b/src/main/java/electrosphere/client/script/ScriptClientAreaUtils.java new file mode 100644 index 00000000..8c406817 --- /dev/null +++ b/src/main/java/electrosphere/client/script/ScriptClientAreaUtils.java @@ -0,0 +1,31 @@ +package electrosphere.client.script; + +import org.graalvm.polyglot.HostAccess.Export; +import org.joml.Vector3d; +import org.joml.Vector3i; + +import electrosphere.client.interact.select.AreaSelection; +import electrosphere.controls.cursor.CursorState; +import electrosphere.engine.Globals; +import electrosphere.entity.EntityUtils; + +/** + * Script utils for clients dealing with areas + */ +public class ScriptClientAreaUtils { + + /** + * Tries to select a rectangular area + */ + @Export + public static void selectAreaRectangular(){ + // Vector3d blockCursorPos = Globals.cursorState.getBlockCursorPos(); + Vector3d cursorPos = new Vector3d(EntityUtils.getPosition(Globals.playerCursor)); + Vector3i chunkPos = Globals.clientWorldData.convertRealToWorldSpace(cursorPos); + Vector3i blockPos = Globals.clientWorldData.convertRealToBlockSpace(cursorPos); + AreaSelection selection = AreaSelection.selectRectangularBlockCavity(chunkPos, blockPos, AreaSelection.DEFAULT_SELECTION_RADIUS); + Globals.cursorState.selectRectangularArea(selection); + CursorState.makeAreaVisible(); + } + +} diff --git a/src/main/java/electrosphere/engine/loadingthreads/LoadingUtils.java b/src/main/java/electrosphere/engine/loadingthreads/LoadingUtils.java index 2e201073..0caff4de 100644 --- a/src/main/java/electrosphere/engine/loadingthreads/LoadingUtils.java +++ b/src/main/java/electrosphere/engine/loadingthreads/LoadingUtils.java @@ -169,6 +169,7 @@ public class LoadingUtils { template.getCreatureToolbarData().setSlotItem("2", new ToolbarItem(73, "entityinspector")); template.getCreatureToolbarData().setSlotItem("3", new ToolbarItem(74, "waterSpawner")); template.getCreatureToolbarData().setSlotItem("4", new ToolbarItem(75, "fabTool")); + template.getCreatureToolbarData().setSlotItem("5", new ToolbarItem(76, "roomTool")); //set player character template serverPlayerConnection.setCreatureTemplate(template); Globals.clientConnection.queueOutgoingMessage(CharacterMessage.constructRequestSpawnCharacterMessage(CharacterProtocol.SPAWN_EXISTING_TEMPLATE + "")); diff --git a/src/main/java/electrosphere/net/server/protocol/CharacterProtocol.java b/src/main/java/electrosphere/net/server/protocol/CharacterProtocol.java index 43429a53..93ad5f9b 100644 --- a/src/main/java/electrosphere/net/server/protocol/CharacterProtocol.java +++ b/src/main/java/electrosphere/net/server/protocol/CharacterProtocol.java @@ -143,6 +143,7 @@ public class CharacterProtocol implements ServerProtocolTemplate