diff --git a/assets/Data/entity/creatures/human.json b/assets/Data/entity/creatures/human.json index 5a8ebc65..904e0c9b 100644 --- a/assets/Data/entity/creatures/human.json +++ b/assets/Data/entity/creatures/human.json @@ -388,9 +388,9 @@ ] }, "collidable" : { - "type" : "CYLINDER", + "type" : "CAPSULE", "dimension1" : 0.35, - "dimension2" : 1.6, + "dimension2" : 0.7, "dimension3" : 0.35, "linearFriction": 0.001, "mass": 0.3, @@ -399,7 +399,7 @@ "rotZ": 0, "rotW": 1, "offsetX" : 0, - "offsetY" : 0.8, + "offsetY" : 0.7, "offsetZ" : 0, "angularlyStatic" : true }, diff --git a/docs/src/progress/renderertodo.md b/docs/src/progress/renderertodo.md index 4dffba14..17f65a3a 100644 --- a/docs/src/progress/renderertodo.md +++ b/docs/src/progress/renderertodo.md @@ -1838,6 +1838,9 @@ Fix virtual scrollable working with certain panels Humans use capsule shape now Physics numbers reworked +(05/16/2025) +Capsule-BlockChunk collision correction in collidable trees + diff --git a/src/main/java/electrosphere/client/block/ClientBlockSelection.java b/src/main/java/electrosphere/client/block/ClientBlockSelection.java index 1bccc672..af8c2c57 100644 --- a/src/main/java/electrosphere/client/block/ClientBlockSelection.java +++ b/src/main/java/electrosphere/client/block/ClientBlockSelection.java @@ -6,6 +6,7 @@ import org.joml.Vector3d; import org.joml.Vector3i; import electrosphere.client.interact.select.AreaSelection; +import electrosphere.client.scene.ClientWorldData; import electrosphere.data.block.BlockFab; import electrosphere.engine.Globals; @@ -95,8 +96,8 @@ public class ClientBlockSelection { if(!startChunk.equals(endChunk)){ throw new Error("Unsupported case! Selected are coverts multiple chunks.. " + startChunk + " " + endChunk); } - Vector3i blockStart = Globals.clientState.clientWorldData.convertRealToBlockSpace(selection.getRectStart()); - Vector3i blockEnd = Globals.clientState.clientWorldData.convertRealToBlockSpace(selection.getRectEnd()); + Vector3i blockStart = ClientWorldData.convertRealToLocalBlockSpace(selection.getRectStart()); + Vector3i blockEnd = ClientWorldData.convertRealToLocalBlockSpace(selection.getRectEnd()); BlockChunkData chunk = Globals.clientState.clientBlockManager.getChunkDataAtWorldPoint(startChunk, 0); if(chunk == null){ diff --git a/src/main/java/electrosphere/client/fluid/cells/FluidCellManager.java b/src/main/java/electrosphere/client/fluid/cells/FluidCellManager.java index 4c736a51..c488697e 100644 --- a/src/main/java/electrosphere/client/fluid/cells/FluidCellManager.java +++ b/src/main/java/electrosphere/client/fluid/cells/FluidCellManager.java @@ -8,6 +8,7 @@ import java.util.Set; import org.joml.Vector3d; import org.joml.Vector3i; +import electrosphere.client.scene.ClientWorldData; import electrosphere.client.terrain.cache.ChunkData; import electrosphere.client.terrain.manager.ClientTerrainManager; import electrosphere.engine.Globals; @@ -322,14 +323,14 @@ public class FluidCellManager { for(int z = -(int)drawRadius; z < drawRadius; z = z + ChunkData.CHUNK_DATA_SIZE){ Vector3d newPos = new Vector3d(playerPos.x + x, playerPos.y + y, playerPos.z + z); Vector3i worldPos = new Vector3i( - Globals.clientState.clientWorldData.convertRealToChunkSpace(newPos.x), - Globals.clientState.clientWorldData.convertRealToChunkSpace(newPos.y), - Globals.clientState.clientWorldData.convertRealToChunkSpace(newPos.z) + ClientWorldData.convertRealToChunkSpace(newPos.x), + ClientWorldData.convertRealToChunkSpace(newPos.y), + ClientWorldData.convertRealToChunkSpace(newPos.z) ); Vector3d chunkRealSpace = new Vector3d( - Globals.clientState.clientWorldData.convertChunkToRealSpace(worldPos.x), - Globals.clientState.clientWorldData.convertChunkToRealSpace(worldPos.y), - Globals.clientState.clientWorldData.convertChunkToRealSpace(worldPos.z) + ClientWorldData.convertChunkToRealSpace(worldPos.x), + ClientWorldData.convertChunkToRealSpace(worldPos.y), + ClientWorldData.convertChunkToRealSpace(worldPos.z) ); if( playerPos.distance(chunkRealSpace) < drawRadius && diff --git a/src/main/java/electrosphere/client/fluid/manager/ClientFluidManager.java b/src/main/java/electrosphere/client/fluid/manager/ClientFluidManager.java index dcb0191a..400c6e87 100644 --- a/src/main/java/electrosphere/client/fluid/manager/ClientFluidManager.java +++ b/src/main/java/electrosphere/client/fluid/manager/ClientFluidManager.java @@ -113,9 +113,9 @@ public class ClientFluidManager { public boolean containsChunkDataAtRealPoint(double x, double y, double z){ assert clientWorldData != null; return fluidCache.containsChunkDataAtWorldPoint( - clientWorldData.convertRealToChunkSpace(x), - clientWorldData.convertRealToChunkSpace(y), - clientWorldData.convertRealToChunkSpace(z) + ClientWorldData.convertRealToChunkSpace(x), + ClientWorldData.convertRealToChunkSpace(y), + ClientWorldData.convertRealToChunkSpace(z) ); } diff --git a/src/main/java/electrosphere/client/interact/ClientInteractionEngine.java b/src/main/java/electrosphere/client/interact/ClientInteractionEngine.java index cf190c4a..dd289931 100644 --- a/src/main/java/electrosphere/client/interact/ClientInteractionEngine.java +++ b/src/main/java/electrosphere/client/interact/ClientInteractionEngine.java @@ -12,6 +12,7 @@ import org.ode4j.ode.DBody; import electrosphere.client.block.BlockChunkData; import electrosphere.client.entity.camera.CameraEntityUtils; +import electrosphere.client.scene.ClientWorldData; import electrosphere.client.terrain.cache.ChunkData; import electrosphere.client.ui.menu.ingame.InteractionTargetMenu; import electrosphere.collision.CollisionBodyCreation; @@ -279,7 +280,7 @@ public class ClientInteractionEngine { //grab block at point BlockChunkData blockChunkData = Globals.clientState.clientBlockManager.getChunkDataAtWorldPoint(Globals.clientState.clientWorldData.convertRealToWorldSpace(collisionPosition), 0); if(blockChunkData != null){ - Vector3i blockPos = Globals.clientState.clientWorldData.convertRealToBlockSpace(new Vector3d(collisionPosition).add(new Vector3d(eyePos).mul(-BlockChunkData.BLOCK_SIZE_MULTIPLIER / 2.0f))); + Vector3i blockPos = ClientWorldData.convertRealToLocalBlockSpace(new Vector3d(collisionPosition).add(new Vector3d(eyePos).mul(-BlockChunkData.BLOCK_SIZE_MULTIPLIER / 2.0f))); if(!blockChunkData.isEmpty(blockPos.x, blockPos.y, blockPos.z)){ short type = blockChunkData.getType(blockPos.x, blockPos.y, blockPos.z); String text = Globals.gameConfigCurrent.getBlockData().getTypeFromId(type).getName(); @@ -293,7 +294,7 @@ public class ClientInteractionEngine { if(!set){ ChunkData chunkData = Globals.clientState.clientTerrainManager.getChunkDataAtWorldPoint(Globals.clientState.clientWorldData.convertRealToWorldSpace(collisionPosition), 0); if(chunkData != null){ - int voxelType = chunkData.getType(Globals.clientState.clientWorldData.convertRealToVoxelSpace(new Vector3d(collisionPosition).add(new Vector3d(ServerTerrainChunk.VOXEL_SIZE / 2.0f)))); + int voxelType = chunkData.getType(ClientWorldData.convertRealToVoxelSpace(new Vector3d(collisionPosition).add(new Vector3d(ServerTerrainChunk.VOXEL_SIZE / 2.0f)))); if(voxelType != ServerTerrainChunk.VOXEL_TYPE_AIR){ String text = Globals.gameConfigCurrent.getVoxelData().getTypeFromId(voxelType).getName(); InteractionTargetMenu.setInteractionTargetString(text); diff --git a/src/main/java/electrosphere/client/scene/ClientWorldData.java b/src/main/java/electrosphere/client/scene/ClientWorldData.java index feed7eb3..67d50d5c 100644 --- a/src/main/java/electrosphere/client/scene/ClientWorldData.java +++ b/src/main/java/electrosphere/client/scene/ClientWorldData.java @@ -64,11 +64,19 @@ public class ClientWorldData { } - public int convertRealToChunkSpace(double real){ + public static int convertRealToChunkSpace(double real){ return (int)Math.floor(real / ServerTerrainChunk.CHUNK_DIMENSION); } - public float convertChunkToRealSpace(int chunk){ + public static Vector3i convertRealToChunkSpace(Vector3d real){ + return new Vector3i( + ClientWorldData.convertRealToChunkSpace(real.x), + ClientWorldData.convertRealToChunkSpace(real.y), + ClientWorldData.convertRealToChunkSpace(real.z) + ); + } + + public static float convertChunkToRealSpace(int chunk){ return chunk * ServerTerrainChunk.CHUNK_DIMENSION; } @@ -185,11 +193,11 @@ public class ClientWorldData { ); } - public Vector3i convertRealToVoxelSpace(Vector3d position){ + public static Vector3i convertRealToVoxelSpace(Vector3d position){ return new Vector3i( - (int)Math.floor(position.x - this.convertChunkToRealSpace(this.convertRealToChunkSpace(position.x))), - (int)Math.floor(position.y - this.convertChunkToRealSpace(this.convertRealToChunkSpace(position.y))), - (int)Math.floor(position.z - this.convertChunkToRealSpace(this.convertRealToChunkSpace(position.z))) + (int)Math.floor(position.x - ClientWorldData.convertChunkToRealSpace(ClientWorldData.convertRealToChunkSpace(position.x))), + (int)Math.floor(position.y - ClientWorldData.convertChunkToRealSpace(ClientWorldData.convertRealToChunkSpace(position.y))), + (int)Math.floor(position.z - ClientWorldData.convertChunkToRealSpace(ClientWorldData.convertRealToChunkSpace(position.z))) ); } @@ -198,7 +206,7 @@ public class ClientWorldData { * @param real The real position * @return The closest block position */ - public int convertRealToLocalBlockSpace(double real){ + public static int convertRealToLocalBlockSpace(double real){ return (int)Math.floor(real * BlockChunkData.BLOCKS_PER_UNIT_DISTANCE % BlockChunkData.CHUNK_DATA_WIDTH); } @@ -207,11 +215,11 @@ public class ClientWorldData { * @param position The real-space position * @return The nearest block-space position */ - public Vector3i convertRealToBlockSpace(Vector3d position){ + public static Vector3i convertRealToLocalBlockSpace(Vector3d position){ return new Vector3i( - this.convertRealToLocalBlockSpace(position.x), - this.convertRealToLocalBlockSpace(position.y), - this.convertRealToLocalBlockSpace(position.z) + ClientWorldData.convertRealToLocalBlockSpace(position.x), + ClientWorldData.convertRealToLocalBlockSpace(position.y), + ClientWorldData.convertRealToLocalBlockSpace(position.z) ); } @@ -249,4 +257,17 @@ public class ClientWorldData { chunkPos.x < this.worldDiscreteSize && chunkPos.y < this.worldDiscreteSize && chunkPos.z < this.worldDiscreteSize; } + /** + * Clamps a real space position to the closest block space position + * @param realPos The real space position + * @return The real space position that is clamped to the closest block space position + */ + public static Vector3d clampRealToBlock(Vector3d realPos){ + return new Vector3d( + realPos.x - realPos.x % BlockChunkData.BLOCK_SIZE_MULTIPLIER, + realPos.y - realPos.y % BlockChunkData.BLOCK_SIZE_MULTIPLIER, + realPos.z - realPos.z % BlockChunkData.BLOCK_SIZE_MULTIPLIER + ); + } + } diff --git a/src/main/java/electrosphere/client/script/ScriptClientAreaUtils.java b/src/main/java/electrosphere/client/script/ScriptClientAreaUtils.java index 0944a0c9..69204619 100644 --- a/src/main/java/electrosphere/client/script/ScriptClientAreaUtils.java +++ b/src/main/java/electrosphere/client/script/ScriptClientAreaUtils.java @@ -5,6 +5,7 @@ import org.joml.Vector3d; import org.joml.Vector3i; import electrosphere.client.interact.select.AreaSelection; +import electrosphere.client.scene.ClientWorldData; import electrosphere.controls.cursor.CursorState; import electrosphere.engine.Globals; import electrosphere.entity.EntityUtils; @@ -22,7 +23,7 @@ public class ScriptClientAreaUtils { // Vector3d blockCursorPos = Globals.cursorState.getBlockCursorPos(); Vector3d cursorPos = new Vector3d(EntityUtils.getPosition(Globals.cursorState.playerCursor)); Vector3i chunkPos = Globals.clientState.clientWorldData.convertRealToWorldSpace(cursorPos); - Vector3i blockPos = Globals.clientState.clientWorldData.convertRealToBlockSpace(cursorPos); + Vector3i blockPos = ClientWorldData.convertRealToLocalBlockSpace(cursorPos); AreaSelection selection = AreaSelection.selectRectangularBlockCavity(chunkPos, blockPos, AreaSelection.DEFAULT_SELECTION_RADIUS); Globals.cursorState.selectRectangularArea(selection); CursorState.makeAreaVisible(); diff --git a/src/main/java/electrosphere/client/script/ScriptClientVoxelUtils.java b/src/main/java/electrosphere/client/script/ScriptClientVoxelUtils.java index c5ef4944..a544f632 100644 --- a/src/main/java/electrosphere/client/script/ScriptClientVoxelUtils.java +++ b/src/main/java/electrosphere/client/script/ScriptClientVoxelUtils.java @@ -7,6 +7,7 @@ import org.joml.Vector3i; import electrosphere.audio.VirtualAudioSourceManager.VirtualAudioSourceType; import electrosphere.audio.movement.MovementAudioService.InteractionType; import electrosphere.client.entity.camera.CameraEntityUtils; +import electrosphere.client.scene.ClientWorldData; import electrosphere.client.terrain.editing.TerrainEditing; import electrosphere.collision.CollisionEngine; import electrosphere.controls.cursor.CursorState; @@ -145,7 +146,7 @@ public class ScriptClientVoxelUtils { String fabPath = Globals.cursorState.getSelectedFabPath(); Vector3d fabCursorPos = EntityUtils.getPosition(CursorState.getFabCursor()); Vector3i chunkPos = Globals.clientState.clientWorldData.convertRealToWorldSpace(fabCursorPos); - Vector3i voxelPos = Globals.clientState.clientWorldData.convertRealToBlockSpace(fabCursorPos); + Vector3i voxelPos = ClientWorldData.convertRealToLocalBlockSpace(fabCursorPos); int rotation = Globals.cursorState.getFabCursorRotation(); Globals.clientState.clientConnection.queueOutgoingMessage(TerrainMessage.constructRequestPlaceFabMessage( chunkPos.x, chunkPos.y, chunkPos.z, diff --git a/src/main/java/electrosphere/client/terrain/editing/BlockEditing.java b/src/main/java/electrosphere/client/terrain/editing/BlockEditing.java index d86510cb..9bdd310e 100644 --- a/src/main/java/electrosphere/client/terrain/editing/BlockEditing.java +++ b/src/main/java/electrosphere/client/terrain/editing/BlockEditing.java @@ -2,6 +2,7 @@ package electrosphere.client.terrain.editing; import org.joml.Vector3i; +import electrosphere.client.scene.ClientWorldData; import electrosphere.client.script.ScriptClientVoxelUtils; import electrosphere.engine.Globals; @@ -16,7 +17,7 @@ public class BlockEditing { * @param metadata The metadata of the block */ public static void editBlock(short type, short metadata){ - Vector3i cornerVoxel = Globals.clientState.clientWorldData.convertRealToBlockSpace(Globals.cursorState.getBlockCursorPos()); + Vector3i cornerVoxel = ClientWorldData.convertRealToLocalBlockSpace(Globals.cursorState.getBlockCursorPos()); int blockSize = Globals.cursorState.getBlockSize(); Vector3i chunkPos = Globals.clientState.clientWorldData.convertRealToWorldSpace(Globals.cursorState.getBlockCursorPos()); ScriptClientVoxelUtils.clientRequestEditBlock(chunkPos, cornerVoxel, type, metadata, blockSize); @@ -26,7 +27,7 @@ public class BlockEditing { * Destroy blocks */ public static void destroyBlock(){ - Vector3i cornerVoxel = Globals.clientState.clientWorldData.convertRealToBlockSpace(Globals.cursorState.getBlockCursorPos()); + Vector3i cornerVoxel = ClientWorldData.convertRealToLocalBlockSpace(Globals.cursorState.getBlockCursorPos()); int blockSize = Globals.cursorState.getBlockSize(); Vector3i chunkPos = Globals.clientState.clientWorldData.convertRealToWorldSpace(Globals.cursorState.getBlockCursorPos()); ScriptClientVoxelUtils.clientRequestEditBlock(chunkPos, cornerVoxel, (short)0, (short)0, blockSize); diff --git a/src/main/java/electrosphere/client/terrain/sampling/ClientVoxelSampler.java b/src/main/java/electrosphere/client/terrain/sampling/ClientVoxelSampler.java index 6b7986e8..a93953ce 100644 --- a/src/main/java/electrosphere/client/terrain/sampling/ClientVoxelSampler.java +++ b/src/main/java/electrosphere/client/terrain/sampling/ClientVoxelSampler.java @@ -3,6 +3,7 @@ package electrosphere.client.terrain.sampling; import org.joml.Vector3d; import org.joml.Vector3i; +import electrosphere.client.scene.ClientWorldData; import electrosphere.client.terrain.cache.ChunkData; import electrosphere.engine.Globals; import electrosphere.entity.Entity; @@ -36,7 +37,7 @@ public class ClientVoxelSampler { public static int getVoxelType(Vector3d realPos){ int voxelId = 0; Vector3i chunkSpacePos = Globals.clientState.clientWorldData.convertRealToWorldSpace(realPos); - Vector3i voxelSpacePos = Globals.clientState.clientWorldData.convertRealToVoxelSpace(realPos); + Vector3i voxelSpacePos = ClientWorldData.convertRealToVoxelSpace(realPos); if(Globals.clientState.clientTerrainManager.containsChunkDataAtWorldPoint(chunkSpacePos, ChunkData.NO_STRIDE)){ ChunkData chunkData = Globals.clientState.clientTerrainManager.getChunkDataAtWorldPoint(chunkSpacePos, ChunkData.NO_STRIDE); voxelId = chunkData.getType(voxelSpacePos); diff --git a/src/main/java/electrosphere/collision/CollisionWorldData.java b/src/main/java/electrosphere/collision/CollisionWorldData.java index f25b8d78..43f1963a 100644 --- a/src/main/java/electrosphere/collision/CollisionWorldData.java +++ b/src/main/java/electrosphere/collision/CollisionWorldData.java @@ -1,5 +1,6 @@ package electrosphere.collision; +import electrosphere.client.scene.ClientWorldData; import electrosphere.client.terrain.manager.ClientTerrainManager; import electrosphere.engine.Globals; import electrosphere.server.datacell.ServerWorldData; @@ -45,7 +46,7 @@ public class CollisionWorldData { public int convertRealToWorld(double real){ if(Globals.clientState.clientWorldData != null){ - return Globals.clientState.clientWorldData.convertRealToChunkSpace(real); + return ClientWorldData.convertRealToChunkSpace(real); } else { return ServerWorldData.convertRealToChunkSpace(real); } @@ -53,7 +54,7 @@ public class CollisionWorldData { public double convertWorldToReal(int world){ if(Globals.clientState.clientWorldData != null){ - return Globals.clientState.clientWorldData.convertChunkToRealSpace(world); + return ClientWorldData.convertChunkToRealSpace(world); } else { return ServerWorldData.convertChunkToRealSpace(world); } diff --git a/src/main/java/electrosphere/collision/physics/CollisionResult.java b/src/main/java/electrosphere/collision/physics/CollisionResult.java new file mode 100644 index 00000000..3014495c --- /dev/null +++ b/src/main/java/electrosphere/collision/physics/CollisionResult.java @@ -0,0 +1,54 @@ +package electrosphere.collision.physics; + +import org.joml.Vector3d; + +/** + * The result of a collision + */ +public class CollisionResult { + + /** + * The penetration of two bodies + */ + double penetration; + + /** + * The normal of the collision + */ + Vector3d normal; + + /** + * Gets the penetration of the collision result + * @return The penetration + */ + public double getPenetration() { + return penetration; + } + + /** + * Sets the penetration of the collision result + * @param penetration The penetration + */ + public void setPenetration(double penetration) { + this.penetration = penetration; + } + + /** + * Gets the normal of the collision result + * @return The normal + */ + public Vector3d getNormal() { + return normal; + } + + /** + * Sets the normal of the collision result + * @param normal The normal + */ + public void setNormal(Vector3d normal) { + this.normal = normal; + } + + + +} diff --git a/src/main/java/electrosphere/entity/state/collidable/ClientCollidableTree.java b/src/main/java/electrosphere/entity/state/collidable/ClientCollidableTree.java index ebf36495..17455378 100644 --- a/src/main/java/electrosphere/entity/state/collidable/ClientCollidableTree.java +++ b/src/main/java/electrosphere/entity/state/collidable/ClientCollidableTree.java @@ -1,16 +1,24 @@ package electrosphere.entity.state.collidable; +import org.joml.AABBd; import org.joml.Quaterniond; import org.joml.Vector3d; +import org.joml.Vector3i; import org.ode4j.ode.DBody; +import org.ode4j.ode.DCapsule; +import electrosphere.client.block.BlockChunkData; +import electrosphere.client.scene.ClientWorldData; import electrosphere.collision.PhysicsUtils; import electrosphere.collision.collidable.Collidable; +import electrosphere.collision.physics.CollisionResult; import electrosphere.engine.Globals; import electrosphere.entity.Entity; import electrosphere.entity.EntityDataStrings; import electrosphere.entity.EntityUtils; import electrosphere.entity.btree.BehaviorTree; +import electrosphere.entity.types.collision.CollisionObjUtils; +import electrosphere.util.math.CollisionUtils; /** * Client collidable tree @@ -65,6 +73,91 @@ public class ClientCollidableTree implements BehaviorTree { } } PhysicsUtils.setRigidBodyTransform(Globals.clientState.clientSceneWrapper.getCollisionEngine(), newPosition, rotation, body); + + //capsule-specific block collision logic + if(body.isEnabled() && body.getFirstGeom() != null && (body.getFirstGeom() instanceof DCapsule)){ + //get capsule params + DCapsule capsuleGeom = (DCapsule)body.getFirstGeom(); + double length = capsuleGeom.getLength(); + double halfLength = length / 2.0; + double radius = capsuleGeom.getRadius(); + Vector3d bodyOffset = PhysicsUtils.odeVecToJomlVec(body.getFirstGeom().getOffsetPosition()); + + //entity spatial transforms + Vector3d entRealPos = EntityUtils.getPosition(parent); + Quaterniond entRot = EntityUtils.getRotation(parent); + + //start and end of capsule + Vector3d realStart = new Vector3d(0,-halfLength,0).rotate(entRot).add(entRealPos).add(bodyOffset); + Vector3d realEnd = new Vector3d(0,halfLength,0).rotate(entRot).add(entRealPos).add(bodyOffset); + + + //block position of body + Vector3d blockPos = ClientWorldData.clampRealToBlock(entRealPos); + Vector3d currBlockPos = new Vector3d(); + + //get dims to scan along (ceil to overcompensate -- better to over scan than underscan) + int halfRadBlockLen = (int)Math.ceil(halfLength / BlockChunkData.BLOCK_SIZE_MULTIPLIER); + int radBlockLen = (int)Math.ceil(radius / BlockChunkData.BLOCK_SIZE_MULTIPLIER); + + //final corrected position + Vector3d corrected = new Vector3d(entRealPos); + + //scan for all potential blocks + for(int x = -radBlockLen; x <= radBlockLen; x++){ + for(int z = -radBlockLen; z <= radBlockLen; z++){ + for(int y = -halfRadBlockLen; y <= halfRadBlockLen; y++){ + currBlockPos.set(blockPos).add( + x * BlockChunkData.BLOCK_SIZE_MULTIPLIER, + y * BlockChunkData.BLOCK_SIZE_MULTIPLIER, + z * BlockChunkData.BLOCK_SIZE_MULTIPLIER + ); + Vector3i chunkPos = ClientWorldData.convertRealToChunkSpace(currBlockPos); + Vector3i entBlockPos = ClientWorldData.convertRealToLocalBlockSpace(currBlockPos); + + //error check bounds + if(chunkPos.x < 0 || chunkPos.y < 0 || chunkPos.z < 0){ + continue; + } + + //get block data for block to check + BlockChunkData data = Globals.clientState.clientBlockManager.getChunkDataAtWorldPoint(chunkPos, BlockChunkData.LOD_FULL_RES); + if(data != null){ + //check type of voxel to skip math + short type = data.getType(entBlockPos.x, entBlockPos.y, entBlockPos.z); + if(type != BlockChunkData.BLOCK_TYPE_EMPTY){ + + //AABB for the voxel + AABBd voxelBox = new AABBd( + currBlockPos.x, + currBlockPos.y, + currBlockPos.z, + currBlockPos.x + BlockChunkData.BLOCK_SIZE_MULTIPLIER, + currBlockPos.y + BlockChunkData.BLOCK_SIZE_MULTIPLIER, + currBlockPos.z + BlockChunkData.BLOCK_SIZE_MULTIPLIER + ); + + //actually collision check + CollisionResult collisionResult = CollisionUtils.collideCapsuleAABB(realStart, realEnd, radius, voxelBox); + if(collisionResult != null){ + double pen = collisionResult.getPenetration(); + double forceMul = pen * 0.3; + Vector3d normal = collisionResult.getNormal().mul(forceMul); + if(normal != null){ + // body.addForce(normal.x, normal.y, normal.z); + //correct the position of the capsule + corrected.add(normal); + } + } + } + } + } + } + } + + //apply correction + CollisionObjUtils.clientPositionCharacter(parent, newPosition, entRot); + } } /** diff --git a/src/main/java/electrosphere/entity/state/collidable/ServerCollidableTree.java b/src/main/java/electrosphere/entity/state/collidable/ServerCollidableTree.java index 79b87ffc..7483dd2a 100644 --- a/src/main/java/electrosphere/entity/state/collidable/ServerCollidableTree.java +++ b/src/main/java/electrosphere/entity/state/collidable/ServerCollidableTree.java @@ -1,40 +1,66 @@ package electrosphere.entity.state.collidable; +import electrosphere.client.block.BlockChunkData; +import electrosphere.collision.PhysicsUtils; import electrosphere.collision.collidable.Collidable; +import electrosphere.collision.physics.CollisionResult; +import electrosphere.engine.Globals; import electrosphere.entity.Entity; import electrosphere.entity.EntityDataStrings; +import electrosphere.entity.EntityUtils; import electrosphere.entity.btree.BehaviorTree; import electrosphere.entity.state.gravity.ServerGravityTree; import electrosphere.entity.state.movement.fall.ServerFallTree; +import electrosphere.entity.types.collision.CollisionObjUtils; +import electrosphere.server.datacell.Realm; +import electrosphere.server.datacell.ServerWorldData; +import electrosphere.server.datacell.interfaces.VoxelCellManager; +import electrosphere.util.math.CollisionUtils; +import org.joml.AABBd; +import org.joml.Quaterniond; +import org.joml.Vector3d; +import org.joml.Vector3i; import org.ode4j.ode.DBody; +import org.ode4j.ode.DCapsule; /** * Server collidable tree */ public class ServerCollidableTree implements BehaviorTree { + /** + * The parent of the collidable + */ Entity parent; + + /** + * The body + */ DBody body; + + /** + * The collidable + */ Collidable collidable; - boolean applyImpulses = true; + /** + * Constructor + * @param e The entity + * @param collidable The collidable + * @param body The body + */ public ServerCollidableTree(Entity e, Collidable collidable, DBody body){ parent = e; this.collidable = collidable; this.body = body; } - public ServerCollidableTree(Entity e, Collidable collidable, DBody body, boolean applyImpulses){ - parent = e; - this.collidable = collidable; - this.body = body; - this.applyImpulses = applyImpulses; - } - - static int incrementer = 0; - + /** + * Simulates the collidable tree + * @param deltaTime The amount of time to simulate by + */ public void simulate(float deltaTime){ //have we hit a terrain impulse? //handle impulses @@ -48,6 +74,94 @@ public class ServerCollidableTree implements BehaviorTree { this.resetGravityFall(); } } + + //capsule-specific block collision logic + if(body.isEnabled() && body.getFirstGeom() != null && (body.getFirstGeom() instanceof DCapsule)){ + Realm realm = Globals.serverState.realmManager.getEntityRealm(parent); + if(realm.getDataCellManager() instanceof VoxelCellManager){ + VoxelCellManager voxelCellManager = (VoxelCellManager)realm.getDataCellManager(); + + //get capsule params + DCapsule capsuleGeom = (DCapsule)body.getFirstGeom(); + double length = capsuleGeom.getLength(); + double halfLength = length / 2.0; + double radius = capsuleGeom.getRadius(); + Vector3d bodyOffset = PhysicsUtils.odeVecToJomlVec(body.getFirstGeom().getOffsetPosition()); + + //entity spatial transforms + Vector3d entRealPos = EntityUtils.getPosition(parent); + Quaterniond entRot = EntityUtils.getRotation(parent); + + //start and end of capsule + Vector3d realStart = new Vector3d(0,-halfLength,0).rotate(entRot).add(entRealPos).add(bodyOffset); + Vector3d realEnd = new Vector3d(0,halfLength,0).rotate(entRot).add(entRealPos).add(bodyOffset); + + + //block position of body + Vector3d blockPos = ServerWorldData.clampRealToBlock(entRealPos); + Vector3d currBlockPos = new Vector3d(); + + //get dims to scan along (ceil to overcompensate -- better to over scan than underscan) + int halfRadBlockLen = (int)Math.ceil(halfLength / BlockChunkData.BLOCK_SIZE_MULTIPLIER); + int radBlockLen = (int)Math.ceil(radius / BlockChunkData.BLOCK_SIZE_MULTIPLIER); + + //final corrected position + Vector3d corrected = new Vector3d(entRealPos); + + //scan for all potential blocks + for(int x = -radBlockLen; x <= radBlockLen; x++){ + for(int z = -radBlockLen; z <= radBlockLen; z++){ + for(int y = -halfRadBlockLen; y <= halfRadBlockLen; y++){ + currBlockPos.set(blockPos).add( + x * BlockChunkData.BLOCK_SIZE_MULTIPLIER, + y * BlockChunkData.BLOCK_SIZE_MULTIPLIER, + z * BlockChunkData.BLOCK_SIZE_MULTIPLIER + ); + Vector3i chunkPos = ServerWorldData.convertRealToChunkSpace(currBlockPos); + Vector3i entBlockPos = ServerWorldData.convertRealToLocalBlockSpace(currBlockPos); + + //error check bounds + if(chunkPos.x < 0 || chunkPos.y < 0 || chunkPos.z < 0){ + continue; + } + + //get block data for block to check + BlockChunkData data = voxelCellManager.getBlocksAtPosition(chunkPos); + short type = data.getType(entBlockPos.x, entBlockPos.y, entBlockPos.z); + + if(type != BlockChunkData.BLOCK_TYPE_EMPTY){ + + //AABB for the voxel + AABBd voxelBox = new AABBd( + currBlockPos.x, + currBlockPos.y, + currBlockPos.z, + currBlockPos.x + BlockChunkData.BLOCK_SIZE_MULTIPLIER, + currBlockPos.y + BlockChunkData.BLOCK_SIZE_MULTIPLIER, + currBlockPos.z + BlockChunkData.BLOCK_SIZE_MULTIPLIER + ); + + //actually collision check + CollisionResult collisionResult = CollisionUtils.collideCapsuleAABB(realStart, realEnd, radius, voxelBox); + if(collisionResult != null){ + double pen = collisionResult.getPenetration(); + double forceMul = pen * 0.3; + Vector3d normal = collisionResult.getNormal().mul(forceMul); + if(normal != null){ + // body.addForce(normal.x, normal.y, normal.z); + //correct the position of the capsule + corrected.add(normal); + } + } + } + } + } + } + + //apply correction + CollisionObjUtils.serverPositionCharacter(parent, corrected); + } + } } @@ -61,10 +175,20 @@ public class ServerCollidableTree implements BehaviorTree { this.collidable = collidable; } + /** + * Checks if the entity has a server collidable tree + * @param e The entity + * @return true if it has a collidable tree, false otherwise + */ public static boolean hasServerCollidableTree(Entity e){ return e.containsKey(EntityDataStrings.SERVER_COLLIDABLE_TREE); } + /** + * Gets the server collidable tree on an entity + * @param e The entity + * @return The tree if it exists, false otherwise + */ public static ServerCollidableTree getServerCollidableTree(Entity e){ return (ServerCollidableTree)e.getData(EntityDataStrings.SERVER_COLLIDABLE_TREE); } diff --git a/src/main/java/electrosphere/server/datacell/ServerWorldData.java b/src/main/java/electrosphere/server/datacell/ServerWorldData.java index 8e95f1b6..74dd6ad3 100644 --- a/src/main/java/electrosphere/server/datacell/ServerWorldData.java +++ b/src/main/java/electrosphere/server/datacell/ServerWorldData.java @@ -423,7 +423,7 @@ public class ServerWorldData { * @param realPos The real space position * @return The real space position that is clamped to the closest block space position */ - public Vector3d clampRealToBlock(Vector3d realPos){ + public static Vector3d clampRealToBlock(Vector3d realPos){ return new Vector3d( realPos.x - realPos.x % BlockChunkData.BLOCK_SIZE_MULTIPLIER, realPos.y - realPos.y % BlockChunkData.BLOCK_SIZE_MULTIPLIER, diff --git a/src/main/java/electrosphere/util/math/CollisionUtils.java b/src/main/java/electrosphere/util/math/CollisionUtils.java new file mode 100644 index 00000000..9baf7c83 --- /dev/null +++ b/src/main/java/electrosphere/util/math/CollisionUtils.java @@ -0,0 +1,136 @@ +package electrosphere.util.math; + +import org.joml.AABBd; +import org.joml.Vector3d; + +import electrosphere.collision.physics.CollisionResult; +import io.github.studiorailgun.MathUtils; + +/** + * Utilities for performing collisions of geometry + */ +public class CollisionUtils { + + /** + * Checks if a capsule intersects an AABB + * @param capsuleStart The start of the capsule + * @param capsuleEnd The end of the capsule + * @param radius The radius of the capsule + * @param box The box + * @return true if they intersect, false otherwise + */ + public static boolean capsuleIntersectsAABB(Vector3d start, Vector3d end, double radius, AABBd box){ + // Step 1: Find closest point on capsule segment to the AABB + // Approximate by projecting the center of the AABB onto the segment + double boxCenterX = (box.minX + box.maxX) * 0.5; + double boxCenterY = (box.minY + box.maxY) * 0.5; + double boxCenterZ = (box.minZ + box.maxZ) * 0.5; + + double abX = end.x - start.x; + double abY = end.y - start.y; + double abZ = end.z - start.z; + + double lenSquared = (boxCenterX - abX) * (boxCenterX - abX) + (boxCenterY - abY) * (boxCenterY - abY) + (boxCenterZ - abZ) * (boxCenterZ - abZ); + + double t = MathUtils.dot( + boxCenterX - start.x, + boxCenterY - start.y, + boxCenterZ - start.z, + abX, + abY, + abZ + ) / lenSquared; + t = Math.max(0f, Math.min(1f, t)); // clamp to [0,1] + + double segClosesX = start.x + (abX * t); + double segClosesY = start.y + (abY * t); + double segClosesZ = start.z + (abZ * t); + + // Step 2: Find closest point on AABB to that segment point + double boxClosestX = MathUtils.clamp(segClosesX, box.minX, box.maxX); + double boxClosestY = MathUtils.clamp(segClosesY, box.minY, box.maxY); + double boxClosestZ = MathUtils.clamp(segClosesZ, box.minZ, box.maxZ); + + // Step 3: Compute distance squared + double diffX = segClosesX - boxClosestX; + double diffY = segClosesY - boxClosestY; + double diffZ = segClosesZ - boxClosestZ; + + double distSq = (diffX * diffX) + (diffY * diffY) + (diffZ * diffZ); + + return distSq <= radius * radius; + } + + /** + * Checks if a capsule intersects an AABB + * @param capsuleStart The start of the capsule + * @param capsuleEnd The end of the capsule + * @param radius The radius of the capsule + * @param box The box + * @return true if they intersect, false otherwise + */ + public static CollisionResult collideCapsuleAABB(Vector3d start, Vector3d end, double radius, AABBd box){ + CollisionResult rVal = new CollisionResult(); + // Step 1: Find closest point on capsule segment to the AABB + // Approximate by projecting the center of the AABB onto the segment + double boxCenterX = (box.minX + box.maxX) * 0.5; + double boxCenterY = (box.minY + box.maxY) * 0.5; + double boxCenterZ = (box.minZ + box.maxZ) * 0.5; + + double abX = end.x - start.x; + double abY = end.y - start.y; + double abZ = end.z - start.z; + + double lenSquared = (boxCenterX - abX) * (boxCenterX - abX) + (boxCenterY - abY) * (boxCenterY - abY) + (boxCenterZ - abZ) * (boxCenterZ - abZ); + + double t = MathUtils.dot( + boxCenterX - start.x, + boxCenterY - start.y, + boxCenterZ - start.z, + abX, + abY, + abZ + ) / lenSquared; + t = Math.max(0f, Math.min(1f, t)); // clamp to [0,1] + + double segClosesX = start.x + (abX * t); + double segClosesY = start.y + (abY * t); + double segClosesZ = start.z + (abZ * t); + + // Step 2: Find closest point on AABB to that segment point + double boxClosestX = MathUtils.clamp(segClosesX, box.minX, box.maxX); + double boxClosestY = MathUtils.clamp(segClosesY, box.minY, box.maxY); + double boxClosestZ = MathUtils.clamp(segClosesZ, box.minZ, box.maxZ); + + // Step 3: Compute distance squared + double diffX = segClosesX - boxClosestX; + double diffY = segClosesY - boxClosestY; + double diffZ = segClosesZ - boxClosestZ; + + //compute distance squared + double distSq = (diffX * diffX) + (diffY * diffY) + (diffZ * diffZ); + + //early return if no collision occurred + if(distSq > radius * radius){ + return null; + } + + //compute distance + double dist = Math.sqrt(distSq); + + //compute penetration + double penetration = radius - dist; + rVal.setPenetration(penetration); + + //compute normal + Vector3d normal = null; + if(dist > 1e-6){ + normal = new Vector3d(diffX,diffY,diffZ).mul(1 / dist); + } + rVal.setNormal(normal); + + + return rVal; + } + +}