diff --git a/docs/src/progress/renderertodo.md b/docs/src/progress/renderertodo.md index 76a430bf..8c712fd9 100644 --- a/docs/src/progress/renderertodo.md +++ b/docs/src/progress/renderertodo.md @@ -1566,6 +1566,9 @@ More block types Transparent blocks Fix block meshgen +(04/27/2025) +Fab cursor rotation + actually place rotated fab + diff --git a/src/main/java/electrosphere/client/interact/select/AreaSelection.java b/src/main/java/electrosphere/client/interact/select/AreaSelection.java index 082b1a28..21750515 100644 --- a/src/main/java/electrosphere/client/interact/select/AreaSelection.java +++ b/src/main/java/electrosphere/client/interact/select/AreaSelection.java @@ -1,6 +1,7 @@ package electrosphere.client.interact.select; import org.joml.Vector3d; +import org.joml.Vector3i; /** * An area of space that is selected by the client @@ -46,6 +47,19 @@ public class AreaSelection { return rVal; } + /** + * Calculates the rectangular area of empty voxels in the block chunks starting at a given position + * @param chunkPos The chunk position to start from + * @param blockPos The block position to start from + * @param maxRadius The maximum radius to expand the area to + * @return The AreaSelection + */ + public static AreaSelection selectRectangularBlockCavity(Vector3i chunkPos, Vector3i blockPos, int maxRadius){ + AreaSelection rVal = null; + + return rVal; + } + /** * Gets the type of area * @return The type of area @@ -69,7 +83,6 @@ public class AreaSelection { public Vector3d getRectEnd() { return rectEnd; } - } diff --git a/src/main/java/electrosphere/client/script/ScriptClientVoxelUtils.java b/src/main/java/electrosphere/client/script/ScriptClientVoxelUtils.java index 522a1e97..71063182 100644 --- a/src/main/java/electrosphere/client/script/ScriptClientVoxelUtils.java +++ b/src/main/java/electrosphere/client/script/ScriptClientVoxelUtils.java @@ -146,9 +146,11 @@ public class ScriptClientVoxelUtils { Vector3d fabCursorPos = EntityUtils.getPosition(CursorState.getFabCursor()); Vector3i chunkPos = Globals.clientWorldData.convertRealToWorldSpace(fabCursorPos); Vector3i voxelPos = Globals.clientWorldData.convertRealToBlockSpace(fabCursorPos); + int rotation = Globals.cursorState.getFabCursorRotation(); Globals.clientConnection.queueOutgoingMessage(TerrainMessage.constructRequestPlaceFabMessage( chunkPos.x, chunkPos.y, chunkPos.z, voxelPos.x, voxelPos.y, voxelPos.z, + rotation, fabPath )); } diff --git a/src/main/java/electrosphere/controls/categories/ControlCategoryMainGame.java b/src/main/java/electrosphere/controls/categories/ControlCategoryMainGame.java index cb94d025..205da07c 100644 --- a/src/main/java/electrosphere/controls/categories/ControlCategoryMainGame.java +++ b/src/main/java/electrosphere/controls/categories/ControlCategoryMainGame.java @@ -612,13 +612,28 @@ public class ControlCategoryMainGame { mainGameControlList.add(controlMap.get(TOOLBAR_SCROLL)); controlMap.get(TOOLBAR_SCROLL).setOnScroll(new Control.ScrollCallback() {public void execute(MouseState mouseState, ScrollEvent scrollEvent){ boolean handled = false; - if(Globals.controlCallback.getKey(GLFW.GLFW_KEY_LEFT_CONTROL)){ + if( + Globals.controlCallback.getKey(GLFW.GLFW_KEY_LEFT_CONTROL) && + !Globals.controlCallback.getKey(GLFW.GLFW_KEY_LEFT_SHIFT) + ){ //if the block cursor is visible, capture this input and instead modify block cursor if(Globals.clientScene.getEntitiesWithTag(EntityTags.DRAWABLE).contains(Globals.playerBlockCursor)){ Globals.cursorState.updateCursorSize(scrollEvent); handled = true; } } + + if( + !Globals.controlCallback.getKey(GLFW.GLFW_KEY_LEFT_CONTROL) && + Globals.controlCallback.getKey(GLFW.GLFW_KEY_LEFT_SHIFT) + ){ + //if the fab cursor is visible, capture this input and instead modify fab cursor + if(Globals.clientScene.getEntitiesWithTag(EntityTags.DRAWABLE).contains(CursorState.getFabCursor())){ + Globals.cursorState.rotateBlockCursor(scrollEvent); + handled = true; + } + } + if(!handled){ if(Globals.playerEntity != null && ClientToolbarState.getClientToolbarState(Globals.playerEntity) != null){ diff --git a/src/main/java/electrosphere/controls/cursor/CursorState.java b/src/main/java/electrosphere/controls/cursor/CursorState.java index fff7be7b..4e4a588d 100644 --- a/src/main/java/electrosphere/controls/cursor/CursorState.java +++ b/src/main/java/electrosphere/controls/cursor/CursorState.java @@ -2,6 +2,7 @@ package electrosphere.controls.cursor; import java.util.Arrays; +import org.joml.Quaterniond; import org.joml.Vector3d; import org.joml.Vector3i; @@ -86,6 +87,18 @@ public class CursorState { */ static Entity playerFabCursor; + /** + * Maximum value to rotate to + */ + private static final int MAX_ROTATION_VAL = 15; + + /** + * The rotation of the fab cursor + * The first two bits encode a rotation about the x axis + * The next two bits encode a subsequent rotation about the y axis + */ + private int fabCursorRotation = 0; + /** * The currently selected fab */ @@ -198,6 +211,7 @@ public class CursorState { CursorState.hide(); Globals.cursorState.setClampToExistingBlock(false); Globals.clientSceneWrapper.getScene().registerEntityToTag(CursorState.playerFabCursor, EntityTags.DRAWABLE); + Globals.cursorState.fabCursorRotation = 0; } /** @@ -310,6 +324,81 @@ public class CursorState { } EntityUtils.getScale(Globals.playerBlockCursor).set(BLOCK_CURSOR_SCALE_MULTIPLIER * this.blockSize); } + + /** + * Rotates the block cursor + * @param scrollEvent The scroll event + */ + public void rotateBlockCursor(ScrollEvent scrollEvent){ + if(scrollEvent.getScrollAmount() > 0){ + if(this.fabCursorRotation > 0){ + this.fabCursorRotation--; + } else { + this.fabCursorRotation = MAX_ROTATION_VAL; + } + } else { + if(this.fabCursorRotation < MAX_ROTATION_VAL){ + this.fabCursorRotation++; + } else { + this.fabCursorRotation = 0; + } + } + this.setFabCursorRotation(this.fabCursorRotation);; + } + + /** + * Rotates the fab cursor + * @param rotationVal The value that encodes the rotation + */ + public void setFabCursorRotation(int rotationVal){ + Quaterniond rotations = CursorState.getBlockRotation(rotationVal); + EntityUtils.getRotation(playerFabCursor).set(rotations); + } + + /** + * Gets the rotation of a block based on its int-encoded rotation value + * @param rotationVal The int-encoded rotation value + * @return The corresponding quaterniond + */ + public static Quaterniond getBlockRotation(int rotationVal){ + int tempVal = rotationVal; + int xRotation = tempVal & 0x3; + int yRotation = tempVal >> 2 & 0x3; + Quaterniond rotations = new Quaterniond(); + double xRot = 0; + double yRot = 0; + switch(xRotation){ + case 0: { + //no rotation applied + } break; + case 1: { + xRot = Math.PI / 2.0; + } break; + case 2: { + xRot = Math.PI; + } break; + case 3: { + xRot = 3.0 * Math.PI / 2.0; + } break; + } + switch(yRotation){ + case 0: { + //no rotation applied + } break; + case 1: { + yRot = Math.PI / 2.0; + } break; + case 2: { + yRot = Math.PI; + } break; + case 3: { + yRot = 3.0 * Math.PI / 2.0; + } break; + } + rotations.rotateLocalZ(xRot); + rotations.rotateLocalY(yRot); + return rotations; + } /** * Sets the block fab cursor's current fab @@ -432,5 +521,13 @@ public class CursorState { public static Entity getFabCursor(){ return playerFabCursor; } + + /** + * Gets the rotation of the fab cursor + * @return The rotation of the fab cursor + */ + public int getFabCursorRotation(){ + return this.fabCursorRotation; + } } diff --git a/src/main/java/electrosphere/net/parser/net/message/TerrainMessage.java b/src/main/java/electrosphere/net/parser/net/message/TerrainMessage.java index ce7736da..374de598 100644 --- a/src/main/java/electrosphere/net/parser/net/message/TerrainMessage.java +++ b/src/main/java/electrosphere/net/parser/net/message/TerrainMessage.java @@ -64,6 +64,7 @@ public class TerrainMessage extends NetworkMessage { int blockMetadata; int blockEditSize; String fabPath; + int blockRotation; /** * Constructor @@ -477,6 +478,20 @@ public class TerrainMessage extends NetworkMessage { this.fabPath = fabPath; } + /** + * Gets blockRotation + */ + public int getblockRotation() { + return blockRotation; + } + + /** + * Sets blockRotation + */ + public void setblockRotation(int blockRotation) { + this.blockRotation = blockRotation; + } + /** * Removes the packet header from the buffer * @param byteBuffer The buffer @@ -1304,17 +1319,20 @@ public class TerrainMessage extends NetworkMessage { if(currentStreamLength < 26){ return false; } - int fabPathSize = 0; if(currentStreamLength < 30){ return false; + } + int fabPathSize = 0; + if(currentStreamLength < 34){ + return false; } else { - temporaryByteQueue.add(byteBuffer.peek(26 + 0)); - temporaryByteQueue.add(byteBuffer.peek(26 + 1)); - temporaryByteQueue.add(byteBuffer.peek(26 + 2)); - temporaryByteQueue.add(byteBuffer.peek(26 + 3)); + temporaryByteQueue.add(byteBuffer.peek(30 + 0)); + temporaryByteQueue.add(byteBuffer.peek(30 + 1)); + temporaryByteQueue.add(byteBuffer.peek(30 + 2)); + temporaryByteQueue.add(byteBuffer.peek(30 + 3)); fabPathSize = ByteStreamUtils.popIntFromByteQueue(temporaryByteQueue); } - if(currentStreamLength < 30 + fabPathSize){ + if(currentStreamLength < 34 + fabPathSize){ return false; } return true; @@ -1333,6 +1351,7 @@ public class TerrainMessage extends NetworkMessage { rVal.setvoxelX(ByteStreamUtils.popIntFromByteQueue(byteBuffer)); rVal.setvoxelY(ByteStreamUtils.popIntFromByteQueue(byteBuffer)); rVal.setvoxelZ(ByteStreamUtils.popIntFromByteQueue(byteBuffer)); + rVal.setblockRotation(ByteStreamUtils.popIntFromByteQueue(byteBuffer)); rVal.setfabPath(ByteStreamUtils.popStringFromByteQueue(byteBuffer)); return rVal; } @@ -1340,7 +1359,7 @@ public class TerrainMessage extends NetworkMessage { /** * Constructs a message of type RequestPlaceFab */ - public static TerrainMessage constructRequestPlaceFabMessage(int worldX,int worldY,int worldZ,int voxelX,int voxelY,int voxelZ,String fabPath){ + public static TerrainMessage constructRequestPlaceFabMessage(int worldX,int worldY,int worldZ,int voxelX,int voxelY,int voxelZ,int blockRotation,String fabPath){ TerrainMessage rVal = new TerrainMessage(TerrainMessageType.REQUESTPLACEFAB); rVal.setworldX(worldX); rVal.setworldY(worldY); @@ -1348,6 +1367,7 @@ public class TerrainMessage extends NetworkMessage { rVal.setvoxelX(voxelX); rVal.setvoxelY(voxelY); rVal.setvoxelZ(voxelZ); + rVal.setblockRotation(blockRotation); rVal.setfabPath(fabPath); rVal.serialize(); return rVal; @@ -1862,7 +1882,7 @@ public class TerrainMessage extends NetworkMessage { } break; case REQUESTPLACEFAB: - rawBytes = new byte[2+4+4+4+4+4+4+4+fabPath.length()]; + rawBytes = new byte[2+4+4+4+4+4+4+4+4+fabPath.length()]; //message header rawBytes[0] = TypeBytes.MESSAGE_TYPE_TERRAIN; //entity messaage header @@ -1891,13 +1911,17 @@ public class TerrainMessage extends NetworkMessage { for(int i = 0; i < 4; i++){ rawBytes[22+i] = intValues[i]; } - intValues = ByteStreamUtils.serializeIntToBytes(fabPath.length()); + intValues = ByteStreamUtils.serializeIntToBytes(blockRotation); for(int i = 0; i < 4; i++){ rawBytes[26+i] = intValues[i]; } + intValues = ByteStreamUtils.serializeIntToBytes(fabPath.length()); + for(int i = 0; i < 4; i++){ + rawBytes[30+i] = intValues[i]; + } stringBytes = fabPath.getBytes(); for(int i = 0; i < fabPath.length(); i++){ - rawBytes[30+i] = stringBytes[i]; + rawBytes[34+i] = stringBytes[i]; } break; } diff --git a/src/main/java/electrosphere/net/server/protocol/TerrainProtocol.java b/src/main/java/electrosphere/net/server/protocol/TerrainProtocol.java index 50590cd5..ac33eb43 100644 --- a/src/main/java/electrosphere/net/server/protocol/TerrainProtocol.java +++ b/src/main/java/electrosphere/net/server/protocol/TerrainProtocol.java @@ -97,7 +97,7 @@ public class TerrainProtocol implements ServerProtocolTemplate { Vector3i blockPos = new Vector3i(message.getvoxelX(),message.getvoxelY(),message.getvoxelZ()); Entity targetEntity = EntityLookupUtils.getEntityById(connectionHandler.getPlayerEntityId()); Realm playerRealm = Globals.realmManager.getEntityRealm(targetEntity); - ServerBlockEditing.placeBlockFab(playerRealm, worldPos, blockPos, message.getfabPath()); + ServerBlockEditing.placeBlockFab(playerRealm, worldPos, blockPos, message.getblockRotation(), message.getfabPath()); } break; //all ignored message types case UPDATEFLUIDDATA: diff --git a/src/main/java/electrosphere/server/physics/block/editing/ServerBlockEditing.java b/src/main/java/electrosphere/server/physics/block/editing/ServerBlockEditing.java index 1aa234e2..a01a8101 100644 --- a/src/main/java/electrosphere/server/physics/block/editing/ServerBlockEditing.java +++ b/src/main/java/electrosphere/server/physics/block/editing/ServerBlockEditing.java @@ -2,7 +2,11 @@ package electrosphere.server.physics.block.editing; import java.io.File; +import org.joml.Matrix4f; +import org.joml.Quaterniond; +import org.joml.Quaternionf; import org.joml.Vector3i; +import org.joml.Vector4f; import electrosphere.client.block.BlockChunkData; import electrosphere.controls.cursor.CursorState; @@ -77,18 +81,28 @@ public class ServerBlockEditing { * @param realm The realm * @param chunkPos The chunk position * @param voxelPos The voxel position + * @param rotation The rotation of the fab * @param fabPath The fab */ - public static void placeBlockFab(Realm realm, Vector3i chunkPos, Vector3i voxelPos, String fabPath){ + public static void placeBlockFab(Realm realm, Vector3i chunkPos, Vector3i voxelPos, int rotation, String fabPath){ BlockFab fab = BlockFab.read(new File(fabPath)); Vector3i dims = fab.getDimensions(); Vector3i currChunkPos = new Vector3i(); Vector3i currVoxelPos = new Vector3i(); VoxelCellManager voxelCellManager = (VoxelCellManager) realm.getDataCellManager(); + Quaterniond rotationQuatd = CursorState.getBlockRotation(rotation); + Quaternionf rotationQuatf = new Quaternionf((float)rotationQuatd.x,(float)rotationQuatd.y,(float)rotationQuatd.z,(float)rotationQuatd.w); + Matrix4f rotMat = new Matrix4f().rotate(rotationQuatf); + Vector4f rotationHolder = new Vector4f(); for(int x = 0; x < dims.x; x++){ for(int y = 0; y < dims.y; y++){ for(int z = 0; z < dims.z; z++){ - currVoxelPos.set(voxelPos).add(x,y,z); + + rotationHolder.set(x,y,z,1); + rotMat.transform(rotationHolder); + currVoxelPos.set(voxelPos).add(Math.round(rotationHolder.x),Math.round(rotationHolder.y),Math.round(rotationHolder.z)); + + currChunkPos.set(chunkPos).add( currVoxelPos.x / BlockChunkData.CHUNK_DATA_WIDTH, currVoxelPos.y / BlockChunkData.CHUNK_DATA_WIDTH, diff --git a/src/net/terrain.json b/src/net/terrain.json index ca9741a0..0737e3f4 100644 --- a/src/net/terrain.json +++ b/src/net/terrain.json @@ -132,6 +132,10 @@ { "name" : "fabPath", "type" : "VAR_STRING" + }, + { + "name" : "blockRotation", + "type" : "FIXED_INT" } ], "messageTypes" : [ @@ -345,6 +349,7 @@ "voxelX", "voxelY", "voxelZ", + "blockRotation", "fabPath" ] }