capsule-block collision correction
All checks were successful
studiorailgun/Renderer/pipeline/head This commit looks good

This commit is contained in:
austin 2025-05-16 10:29:41 -04:00
parent 9566bf2720
commit cafc5f25c8
17 changed files with 484 additions and 45 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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