client hitbox body destruction based on dist
Some checks failed
studiorailgun/Renderer/pipeline/head There was a failure building this commit

This commit is contained in:
austin 2025-04-04 17:44:52 -04:00
parent 86c867fe31
commit bf35da4529
7 changed files with 322 additions and 138 deletions

View File

@ -1436,6 +1436,7 @@ Unarmed combat
GriddedDataCellTrackingData created
Reduce allocations in GriddedDataCellManager methods that loop cells
Implement max distance for queueing for simulation
Client hitbox body destruction based on distance from player (for performance)

View File

@ -777,6 +777,50 @@ public class CollisionEngine {
}
}
/**
* Destroys a body + collidable pair
* @param body The body
* @param collidable The collidable
*/
protected void destroyPhysicsPair(DBody body, Collidable collidable){
spaceLock.lock();
this.deregisterCollisionObject(body, collidable);
this.destroyDBody(body);
spaceLock.unlock();
}
/**
* Disables a body
* @param body The body
*/
protected void disable(DBody body){
spaceLock.lock();
body.disable();
spaceLock.unlock();
}
/**
* Enables a body
* @param body The body
*/
protected void enable(DBody body){
spaceLock.lock();
body.enable();
spaceLock.unlock();
}
/**
* Checks if a body is enabled
* @param body The body
* @return true if it is enabled, false otherwise
*/
protected boolean isEnabled(DBody body){
spaceLock.lock();
boolean rVal = body.isEnabled();
spaceLock.unlock();
return rVal;
}
/**
* Creates a trimesh from a given set of vertices and indices
* @param verts The vertices

View File

@ -8,6 +8,7 @@ import org.ode4j.math.DQuaternion;
import org.ode4j.math.DQuaternionC;
import org.ode4j.ode.DBody;
import electrosphere.collision.collidable.Collidable;
import electrosphere.game.data.collidable.CollidableTemplate;
/**
@ -120,5 +121,42 @@ public class PhysicsUtils {
collisionEngine.destroyDBody(body);
}
/**
* Destroys a body + collidable pair
* @param body The body
* @param collidable The collidable
*/
public static void destroyPhysicsPair(CollisionEngine collisionEngine, DBody body, Collidable collidable){
collisionEngine.destroyPhysicsPair(body, collidable);
}
/**
* Disables a body
* @param collisionEngine The collision engine
* @param body The body
*/
public static void disableBody(CollisionEngine collisionEngine, DBody body){
collisionEngine.disable(body);
}
/**
* Enables a body
* @param collisionEngine The collision engine
* @param body The body
*/
public static void enableBody(CollisionEngine collisionEngine, DBody body){
collisionEngine.enable(body);
}
/**
* Checks if a body is enabled
* @param collisionEngine The collision engine
* @param body The body
* @return true if the body is enabled, false otherwise
*/
public static boolean isBodyEnabled(CollisionEngine collisionEngine, DBody body){
return collisionEngine.isEnabled(body);
}
}

View File

@ -30,7 +30,9 @@ public class HitboxManager {
*/
ReentrantLock lock = new ReentrantLock();
//an id incrementer for hitboxes
/**
* An incrementer for hitbox IDs
*/
long idIncrementer = 0;
/**

View File

@ -17,28 +17,37 @@ import electrosphere.entity.btree.BehaviorTree;
*/
public class ClientCollidableTree implements BehaviorTree {
/**
* The parent entity of this tree
*/
Entity parent;
//the ode body for this collidable tree
/**
* The body for this tree
*/
DBody body;
/**
* The collidable for this tree
*/
Collidable collidable;
boolean applyImpulses = true;
/**
* Constructor
* @param e The entity
* @param collidable The collidable
* @param body The body
*/
public ClientCollidableTree(Entity e, Collidable collidable, DBody body){
parent = e;
this.collidable = collidable;
this.body = body;
}
public ClientCollidableTree(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 component
*/
public void simulate(float deltaTime){
Vector3d position = EntityUtils.getPosition(parent);
Quaterniond rotation = EntityUtils.getRotation(parent);
@ -68,10 +77,20 @@ public class ClientCollidableTree implements BehaviorTree {
this.collidable = collidable;
}
/**
* Checks if the entity has a collidable tree
* @param e The entity
* @return true if the entity contains a collidable tree, false otherwise
*/
public static boolean hasClientCollidableTree(Entity e){
return e.containsKey(EntityDataStrings.CLIENT_COLLIDABLE_TREE);
}
/**
* Gets the collidable tree on the entity
* @param e The entity
* @return The tree
*/
public static ClientCollidableTree getClientCollidableTree(Entity e){
return (ClientCollidableTree)e.getData(EntityDataStrings.CLIENT_COLLIDABLE_TREE);
}

View File

@ -54,22 +54,49 @@ public class HitboxCollectionState {
SOUR, //less damage
}
//the parent entity of the hitbox state
/**
* Distance after which hitboxes are disabled on the client
*/
public static final double CLIENT_DISABLE_DISTANCE = 40;
/**
* Distance within which hitboxes are enabled on the client
*/
public static final double CLIENT_ENABLE_DISTANCE = 35;
/**
* The parent entity of the hitbox state
*/
Entity parent;
//the body that contains all the hitbox shapes
/**
* The body that contains all the hitbox shapes
*/
DBody body;
//The collidable associated with the body
/**
* The collidable associated with the body
*/
Collidable collidable;
//the list of all geoms in the collection state
/**
* The list of all geoms in the collection state
*/
List<DGeom> geoms = new LinkedList<DGeom>();
//the map of bone -> list of states of individual shapes attached to that bone
/**
* The map of bone -> list of states of individual shapes attached to that bone
*/
Map<String,List<HitboxState>> boneHitboxMap = new HashMap<String,List<HitboxState>>();
//map of hitbox state -> geometry
/**
* Map of hitbox state -> geometry
*/
Map<HitboxState,DGeom> stateGeomMap = new HashMap<HitboxState,DGeom>();
//the map of geometry -> hitbox shape status, useful for finding data about a given hitbox during collision
/**
* The map of geometry -> hitbox shape status, useful for finding data about a given hitbox during collision
*/
Map<DGeom,HitboxState> geomStateMap = new HashMap<DGeom,HitboxState>();
/**
@ -82,19 +109,34 @@ public class HitboxCollectionState {
*/
List<HitboxState> nonBoneHitboxes = new LinkedList<HitboxState>();
//callback to provide a position for the hitbox each frame
/**
* Callback to provide a position for the hitbox each frame
*/
HitboxPositionCallback positionCallback;
//controls whether the hitbox state is active or not
/**
* The raw data to pattern hitboxes off of
*/
List<HitboxData> rawData;
/**
* Controls whether the hitbox state is active or not
*/
boolean active = true;
//controls whether active hitboxes should be overwritten with block boxes
/**
* Controls whether active hitboxes should be overwritten with block boxes
*/
boolean blockOverride = false;
//the associated manager
/**
* The associated manager
*/
HitboxManager manager;
//controls whether this hitbox collection thinks its on the server or client
/**
* Controls whether this hitbox collection thinks its on the server or client
*/
boolean isServer = true;
@ -107,94 +149,15 @@ public class HitboxCollectionState {
*/
public static HitboxCollectionState attachHitboxState(HitboxManager manager, boolean isServer, Entity entity, List<HitboxData> hitboxListRaw){
HitboxCollectionState rVal = new HitboxCollectionState();
rVal.rawData = hitboxListRaw;
rVal.manager = manager;
rVal.isServer = isServer;
//create the shapes
for(HitboxData hitboxDataRaw : hitboxListRaw){
DGeom geom = null;
HitboxType type = HitboxType.HIT;
HitboxShapeType shapeType = HitboxShapeType.SPHERE;
//
//Get the type as an enum
//
switch(hitboxDataRaw.getType()){
case HitboxData.HITBOX_TYPE_HIT: {
type = HitboxType.HIT;
shapeType = HitboxShapeType.SPHERE;
geom = CollisionBodyCreation.createShapeSphere(manager.getCollisionEngine(), hitboxDataRaw.getRadius(), Collidable.TYPE_OBJECT_BIT);
} break;
case HitboxData.HITBOX_TYPE_HURT: {
type = HitboxType.HURT;
shapeType = HitboxShapeType.SPHERE;
geom = CollisionBodyCreation.createShapeSphere(manager.getCollisionEngine(), hitboxDataRaw.getRadius(), Collidable.TYPE_OBJECT_BIT);
} break;
case HitboxData.HITBOX_TYPE_HIT_CONNECTED: {
type = HitboxType.HIT;
shapeType = HitboxShapeType.CAPSULE;
geom = CollisionBodyCreation.createShapeSphere(manager.getCollisionEngine(), hitboxDataRaw.getRadius(), Collidable.TYPE_OBJECT_BIT);
} break;
case HitboxData.HITBOX_TYPE_HURT_CONNECTED: {
type = HitboxType.HURT;
shapeType = HitboxShapeType.CAPSULE;
geom = CollisionBodyCreation.createShapeSphere(manager.getCollisionEngine(), hitboxDataRaw.getRadius(), Collidable.TYPE_OBJECT_BIT);
} break;
case HitboxData.HITBOX_TYPE_STATIC_CAPSULE: {
type = HitboxType.HURT;
shapeType = HitboxShapeType.STATIC_CAPSULE;
geom = CollisionBodyCreation.createCapsuleShape(manager.getCollisionEngine(), hitboxDataRaw.getRadius(), hitboxDataRaw.getLength(), Collidable.TYPE_OBJECT_BIT);
} break;
}
//
//Get the subtype as an enum
//
HitboxSubtype subType;
String subTypeRaw = hitboxDataRaw.getSubType();
if(subTypeRaw == null){
subTypeRaw = HitboxData.HITBOX_SUBTYPE_REUGLAR;
}
switch(subTypeRaw){
case HitboxData.HITBOX_SUBTYPE_SWEET: {
subType = HitboxSubtype.SWEET;
} break;
case HitboxData.HITBOX_SUBTYPE_REUGLAR: {
subType = HitboxSubtype.REGULAR;
} break;
case HitboxData.HITBOX_SUBTYPE_SOUR: {
subType = HitboxSubtype.SOUR;
} break;
default: {
subType = HitboxSubtype.REGULAR;
} break;
}
//create the state for the individual shape
HitboxState state = new HitboxState(hitboxDataRaw.getBone(), hitboxDataRaw, type, subType, shapeType, false);
//add to structures
if(hitboxDataRaw.getBone() != null){
rVal.addHitbox(hitboxDataRaw.getBone(),state);
} else {
rVal.addHitbox(state);
}
rVal.registerGeom(geom,state);
}
//create body with all the shapes
DGeom[] geomArray = rVal.geoms.toArray(new DGeom[rVal.geoms.size()]);
rVal.body = CollisionBodyCreation.createBodyWithShapes(manager.getCollisionEngine(), geomArray);
CollisionBodyCreation.setKinematic(manager.getCollisionEngine(), rVal.body);
//register collidable with collision engine
Collidable collidable = new Collidable(entity, Collidable.TYPE_OBJECT, true);
manager.getCollisionEngine().registerCollisionObject(rVal.body, collidable);
//attach
entity.putData(EntityDataStrings.HITBOX_DATA, rVal);
rVal.parent = entity;
//register
rVal.createBody();
manager.registerHitbox(rVal);
rVal.manager = manager;
return rVal;
}
@ -238,6 +201,110 @@ public class HitboxCollectionState {
// return rVal;
}
/**
* Creates the body
*/
private void createBody(){
CollisionEngine collisionEngine = this.manager.getCollisionEngine();
//create the shapes
for(HitboxData hitboxDataRaw : this.rawData){
DGeom geom = null;
HitboxType type = HitboxType.HIT;
HitboxShapeType shapeType = HitboxShapeType.SPHERE;
//
//Get the type as an enum
//
switch(hitboxDataRaw.getType()){
case HitboxData.HITBOX_TYPE_HIT: {
type = HitboxType.HIT;
shapeType = HitboxShapeType.SPHERE;
geom = CollisionBodyCreation.createShapeSphere(collisionEngine, hitboxDataRaw.getRadius(), Collidable.TYPE_OBJECT_BIT);
} break;
case HitboxData.HITBOX_TYPE_HURT: {
type = HitboxType.HURT;
shapeType = HitboxShapeType.SPHERE;
geom = CollisionBodyCreation.createShapeSphere(collisionEngine, hitboxDataRaw.getRadius(), Collidable.TYPE_OBJECT_BIT);
} break;
case HitboxData.HITBOX_TYPE_HIT_CONNECTED: {
type = HitboxType.HIT;
shapeType = HitboxShapeType.CAPSULE;
geom = CollisionBodyCreation.createShapeSphere(collisionEngine, hitboxDataRaw.getRadius(), Collidable.TYPE_OBJECT_BIT);
} break;
case HitboxData.HITBOX_TYPE_HURT_CONNECTED: {
type = HitboxType.HURT;
shapeType = HitboxShapeType.CAPSULE;
geom = CollisionBodyCreation.createShapeSphere(collisionEngine, hitboxDataRaw.getRadius(), Collidable.TYPE_OBJECT_BIT);
} break;
case HitboxData.HITBOX_TYPE_STATIC_CAPSULE: {
type = HitboxType.HURT;
shapeType = HitboxShapeType.STATIC_CAPSULE;
geom = CollisionBodyCreation.createCapsuleShape(collisionEngine, hitboxDataRaw.getRadius(), hitboxDataRaw.getLength(), Collidable.TYPE_OBJECT_BIT);
} break;
}
//
//Get the subtype as an enum
//
HitboxSubtype subType;
String subTypeRaw = hitboxDataRaw.getSubType();
if(subTypeRaw == null){
subTypeRaw = HitboxData.HITBOX_SUBTYPE_REUGLAR;
}
switch(subTypeRaw){
case HitboxData.HITBOX_SUBTYPE_SWEET: {
subType = HitboxSubtype.SWEET;
} break;
case HitboxData.HITBOX_SUBTYPE_REUGLAR: {
subType = HitboxSubtype.REGULAR;
} break;
case HitboxData.HITBOX_SUBTYPE_SOUR: {
subType = HitboxSubtype.SOUR;
} break;
default: {
subType = HitboxSubtype.REGULAR;
} break;
}
//create the state for the individual shape
HitboxState state = new HitboxState(hitboxDataRaw.getBone(), hitboxDataRaw, type, subType, shapeType, false);
//add to structures
if(hitboxDataRaw.getBone() != null){
this.addHitbox(hitboxDataRaw.getBone(),state);
} else {
this.addHitbox(state);
}
this.registerGeom(geom,state);
}
//create body with all the shapes
DGeom[] geomArray = this.geoms.toArray(new DGeom[this.geoms.size()]);
this.body = CollisionBodyCreation.createBodyWithShapes(collisionEngine, geomArray);
CollisionBodyCreation.setKinematic(collisionEngine, this.body);
//register collidable with collision engine
this.collidable = new Collidable(this.parent, Collidable.TYPE_OBJECT, true);
collisionEngine.registerCollisionObject(this.body, this.collidable);
}
/**
* Destroys the body
*/
private void destroyBody(){
//destroy body
if(this.body != null){
PhysicsUtils.destroyPhysicsPair(this.manager.getCollisionEngine(), this.body, this.collidable);
//clear tracking structures
this.geoms.clear();
this.boneHitboxMap.clear();
this.stateGeomMap.clear();
this.geomStateMap.clear();
this.allStates.clear();
this.nonBoneHitboxes.clear();
this.body = null;
this.collidable = null;
}
}
/**
* Clears the collision status of all shapes
*/
@ -255,38 +322,49 @@ public class HitboxCollectionState {
if(parent != null && !isServer && EntityUtils.getActor(parent) != null){
if(!this.geoms.isEmpty()){
Vector3d entityPosition = EntityUtils.getPosition(parent);
PhysicsUtils.setRigidBodyTransform(collisionEngine, entityPosition, new Quaterniond(), body);
for(String boneName : this.boneHitboxMap.keySet()){
if(EntityUtils.getActor(parent).containsBone(boneName)){
Vector3d bonePosition = EntityUtils.getActor(parent).getBonePosition(boneName);
for(HitboxState state : this.boneHitboxMap.get(boneName)){
DGeom geom = this.stateGeomMap.get(state);
HitboxState shapeStatus = this.geomStateMap.get(geom);
switch(shapeStatus.shapeType){
case SPHERE: {
this.updateSphereShapePosition(collisionEngine,boneName,shapeStatus,bonePosition);
} break;
case CAPSULE: {
this.updateCapsuleShapePosition(collisionEngine,boneName,shapeStatus,bonePosition);
} break;
case STATIC_CAPSULE: {
} break;
Vector3d playerPos = EntityUtils.getPosition(Globals.playerEntity);
double distanceToPlayer = entityPosition.distance(playerPos);
if(distanceToPlayer > CLIENT_DISABLE_DISTANCE){
if(this.body != null){
this.destroyBody();
}
} else if(distanceToPlayer < CLIENT_ENABLE_DISTANCE) {
if(this.body == null){
this.createBody();
}
PhysicsUtils.setRigidBodyTransform(collisionEngine, entityPosition, new Quaterniond(), body);
for(String boneName : this.boneHitboxMap.keySet()){
if(EntityUtils.getActor(parent).containsBone(boneName)){
Vector3d bonePosition = EntityUtils.getActor(parent).getBonePosition(boneName);
for(HitboxState state : this.boneHitboxMap.get(boneName)){
DGeom geom = this.stateGeomMap.get(state);
HitboxState shapeStatus = this.geomStateMap.get(geom);
switch(shapeStatus.shapeType){
case SPHERE: {
this.updateSphereShapePosition(collisionEngine,boneName,shapeStatus,bonePosition);
} break;
case CAPSULE: {
this.updateCapsuleShapePosition(collisionEngine,boneName,shapeStatus,bonePosition);
} break;
case STATIC_CAPSULE: {
} break;
}
}
}
}
}
for(HitboxState state : this.nonBoneHitboxes){
DGeom geom = this.stateGeomMap.get(state);
HitboxState shapeStatus = this.geomStateMap.get(geom);
switch(shapeStatus.shapeType){
case SPHERE: {
this.updateSphereShapePosition(collisionEngine,null,shapeStatus,null);
} break;
case CAPSULE: {
this.updateCapsuleShapePosition(collisionEngine,null,shapeStatus,null);
} break;
case STATIC_CAPSULE: {
} break;
for(HitboxState state : this.nonBoneHitboxes){
DGeom geom = this.stateGeomMap.get(state);
HitboxState shapeStatus = this.geomStateMap.get(geom);
switch(shapeStatus.shapeType){
case SPHERE: {
this.updateSphereShapePosition(collisionEngine,null,shapeStatus,null);
} break;
case CAPSULE: {
this.updateCapsuleShapePosition(collisionEngine,null,shapeStatus,null);
} break;
case STATIC_CAPSULE: {
} break;
}
}
}
}
@ -506,6 +584,9 @@ public class HitboxCollectionState {
* @return the hitbox state if it exists
*/
public static HitboxCollectionState getHitboxState(Entity entity){
if(!entity.containsKey(EntityDataStrings.HITBOX_DATA)){
return null;
}
return (HitboxCollectionState)entity.getData(EntityDataStrings.HITBOX_DATA);
}
@ -549,7 +630,7 @@ public class HitboxCollectionState {
} else {
engine = Globals.clientSceneWrapper.getHitboxManager().getCollisionEngine();
}
if(engine != null){
if(engine != null && body != null){
PhysicsUtils.destroyBody(engine, body);
}
}

View File

@ -361,7 +361,6 @@ public class GriddedDataCellManager implements DataCellManager, VoxelCellManager
public boolean updatePlayerPositions(){
Globals.profiler.beginCpuSample("GriddedDataCellManager.updatePlayerPositions - Reset chunk distances");
loadedCellsLock.lock();
Globals.profiler.beginCpuSample("GriddedDataCellManager.updatePlayerPositions - Remove from old cells");
for(ServerDataCell cell : this.groundDataCells.values()){
GriddedDataCellTrackingData trackingData = this.cellTrackingMap.get(cell);
trackingData.setClosestPlayer(GriddedDataCellTrackingData.REALLY_LARGE_DISTANCE);