area selection utility
All checks were successful
studiorailgun/Renderer/pipeline/head This commit looks good

This commit is contained in:
austin 2025-04-28 12:46:26 -04:00
parent c96c07334a
commit cad9a994d0
12 changed files with 224 additions and 0 deletions

View File

@ -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" : [

View File

@ -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()
}
}
]

View File

@ -0,0 +1,11 @@
/**
* Utilities for managing areas on the client
*/
export interface ClientAreaUtils {
/**
* Selects a rectangular area
*/
readonly selectAreaRectangular: () => void
}

View File

@ -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<Math>
/**
* Utilities for area management on the client
*/
readonly areaUtils?: Class<ClientAreaUtils>,
}
/**

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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 + ""));

View File

@ -143,6 +143,7 @@ public class CharacterProtocol implements ServerProtocolTemplate<CharacterMessag
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
connectionHandler.setCreatureTemplate(template);
} else {
@ -165,6 +166,7 @@ public class CharacterProtocol implements ServerProtocolTemplate<CharacterMessag
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
connectionHandler.setCreatureTemplate(template);
}

View File

@ -21,6 +21,7 @@ import java.util.Map;
import org.graalvm.polyglot.Source;
import org.graalvm.polyglot.Value;
import electrosphere.client.script.ScriptClientAreaUtils;
import electrosphere.client.script.ScriptClientVoxelUtils;
import electrosphere.client.ui.menu.script.ScriptLevelEditorUtils;
import electrosphere.client.ui.menu.script.ScriptMenuUtils;
@ -121,6 +122,7 @@ public class ScriptEngine extends SignalServiceImpl {
{"voxelUtils",ScriptClientVoxelUtils.class},
{"levelEditorUtils",ScriptLevelEditorUtils.class},
{"math",ScriptMathInterface.class},
{"areaUtils",ScriptClientAreaUtils.class},
};
//singletons from the host that are provided to the javascript context