hitbox collision debugging
All checks were successful
studiorailgun/Renderer/pipeline/head This commit looks good

This commit is contained in:
austin 2024-09-02 18:30:26 -04:00
parent bff1b62659
commit b1612cc943
9 changed files with 192 additions and 66 deletions

View File

@ -649,6 +649,24 @@ Fix Yoga double free bug
Explicit error on setting uniform to unsupported type
Methods for getting buffers from mesh
(08/29/2024)
use STBImage instead of ImageIO
Signal passing architecture
Services architecture
(09/01/2024)
Lots of jenkins pipeline work (finally got it mostly consistent!)
(09/02/2024)
Engine mostly building reproducibly/consistently/testably
Framebuffer fixes
Attack tree integration test
Shadowmap fixes
Shadowmap pipeline debug menu
Shader storage refactor
Unit definition/spawning
Hitbox updates for katana 2H
# TODO

View File

@ -5,6 +5,7 @@ import java.util.concurrent.ConcurrentHashMap;
import org.joml.Vector3d;
import org.ode4j.ode.DContactGeom;
import org.ode4j.ode.DGeom;
import electrosphere.collision.CollisionEngine;
import electrosphere.collision.CollisionEngine.CollisionResolutionCallback;
@ -220,8 +221,8 @@ public class ClientSceneWrapper {
*/
CollisionResolutionCallback resolutionCallback = new CollisionResolutionCallback() {
@Override
public void resolve(DContactGeom geom, Collidable impactor, Collidable receiver, Vector3d normal, Vector3d localPosition, Vector3d worldPos, float magnitude) {
HitboxUtils.clientDamageHitboxColision(geom, impactor, receiver, normal, localPosition, worldPos, magnitude);
public void resolve(DContactGeom geom, DGeom impactorGeom, DGeom receiverGeom, Collidable impactor, Collidable receiver, Vector3d normal, Vector3d localPosition, Vector3d worldPos, float magnitude) {
HitboxUtils.clientDamageHitboxColision(geom, impactorGeom, receiverGeom, impactor, receiver, normal, localPosition, worldPos, magnitude);
}
};

View File

@ -353,6 +353,8 @@ public class CollisionEngine {
//use custom collision resolution
collisionResolutionCallback.resolve(
contact.geom,
o1,
o2,
bodyPointerMap.get(o1.getBody()),
bodyPointerMap.get(o2.getBody()),
PhysicsUtils.odeVecToJomlVec(contact.geom.normal).mul(-1.0),
@ -362,6 +364,8 @@ public class CollisionEngine {
);
collisionResolutionCallback.resolve(
contact.geom,
o2,
o1,
bodyPointerMap.get(o2.getBody()),
bodyPointerMap.get(o1.getBody()),
PhysicsUtils.odeVecToJomlVec(contact.geom.normal),
@ -931,6 +935,8 @@ public class CollisionEngine {
/**
* Resolves a collision between two collidables in the engine
* @param contactGeom the ode4j contact geom
* @param geom1 The first geometry
* @param geom2 The second geometry
* @param impactor The collidable initiating the contact
* @param receiver The collidable recieving the contact
* @param normal The normal of the collision
@ -938,7 +944,7 @@ public class CollisionEngine {
* @param worldPos The world position of the collision
* @param magnitude The magnitude of the collision
*/
public void resolve(DContactGeom contactGeom, Collidable impactor, Collidable receiver, Vector3d normal, Vector3d localPosition, Vector3d worldPos, float magnitude);
public void resolve(DContactGeom contactGeom, DGeom geom1, DGeom geom2, Collidable impactor, Collidable receiver, Vector3d normal, Vector3d localPosition, Vector3d worldPos, float magnitude);
}
}

View File

@ -25,8 +25,16 @@ import electrosphere.logger.LoggerInterface;
public class RayCastCallback implements DNearCallback {
/**
* Maximum number of contacts allowed
*/
static final int MAX_CONTACTS = 5;
/**
* Really far away from the ray origin point
*/
static final double REALLY_LONG_DISTANCE = 1000000;
/**
* // Check ray collision against a space
void RayCallback(void *Data, dGeomID Geometry1, dGeomID Geometry2) {
@ -49,7 +57,7 @@ void RayCallback(void *Data, dGeomID Geometry1, dGeomID Geometry2) {
//For the current execution, this stores the shortest length that has currently been encountered.
//This is used to keep track of the closest body so that there doesn't need to be contact join creation.
//This should be reset every time a ray cast is called in collision engine by calling setLength in this object.
double shortestLength = 1000000;
double shortestLength = REALLY_LONG_DISTANCE;
@Override
public void call(Object data, DGeom o1, DGeom o2) {
@ -111,6 +119,14 @@ void RayCallback(void *Data, dGeomID Geometry1, dGeomID Geometry2) {
}
}
}
if(rayCastData.collisionPosition == null){
String errorMessage = "Collision error!\n" +
"body1: " + b1 + "\n" +
"body2: " + b2 + "\n" +
"collidable1: " + collidable1 + "\n" +
"collidable2: " + collidable2 + "\n";
LoggerInterface.loggerEngine.ERROR(new IllegalStateException(errorMessage));
}
}
}
}

View File

@ -24,14 +24,12 @@ public class HitboxUtils {
* @param impactor the entity initiating the collision
* @param receiver the entity receiving the collision
*/
public static void clientDamageHitboxColision(DContactGeom contactGeom, Collidable impactor, Collidable receiver, Vector3d normal, Vector3d localPosition, Vector3d worldPos, float magnitude){
public static void clientDamageHitboxColision(DContactGeom contactGeom, DGeom impactorGeom, DGeom receiverGeom, Collidable impactor, Collidable receiver, Vector3d normal, Vector3d localPosition, Vector3d worldPos, float magnitude){
Entity impactorParent = impactor.getParent();
Entity receiverParent = receiver.getParent();
HitboxCollectionState impactorState = HitboxCollectionState.getHitboxState(impactorParent);
HitboxCollectionState receiverState = HitboxCollectionState.getHitboxState(receiverParent);
DGeom impactorGeom = contactGeom.g1;
DGeom receiverGeom = contactGeom.g2;
HitboxState impactorShapeStatus = impactorState.getShapeStatus(impactorGeom);
HitboxState receiverShapeStatus = receiverState.getShapeStatus(receiverGeom);

View File

@ -1,6 +1,5 @@
package electrosphere.entity.state.hitbox;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
@ -268,21 +267,27 @@ public class HitboxCollectionState {
PhysicsEntityUtils.setGeometryPosition(collisionEngine, geom, worldPosition, rotation);
}
//update bone-attached hitboxes on server
} else if(parent != null && isServer && EntityUtils.getPoseActor(parent) != null){
if(!this.geoms.isEmpty()){
Vector3d entityPosition = EntityUtils.getPosition(parent);
this.body.setPosition(PhysicsUtils.jomlVecToOdeVec(entityPosition));
//
for(String boneName : this.boneHitboxMap.keySet()){
Vector3f bonePosition = EntityUtils.getPoseActor(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){
if(state == null){
throw new IllegalStateException("Geometry not assigned to a hitbox state!");
}
//
switch(state.shapeType){
case SPHERE: {
this.updateSphereShapePosition(collisionEngine,boneName,shapeStatus,bonePosition);
this.updateSphereShapePosition(collisionEngine,boneName,state,bonePosition);
} break;
case CAPSULE: {
this.updateCapsuleShapePosition(collisionEngine,boneName,shapeStatus,bonePosition);
this.updateCapsuleShapePosition(collisionEngine,boneName,state,bonePosition);
} break;
case STATIC_CAPSULE: {
} break;
@ -387,9 +392,7 @@ public class HitboxCollectionState {
//called all subsequent updates to hitbox position
//destroy old capsule
this.geomStateMap.remove(geom);
this.geoms.remove(geom);
CollisionBodyCreation.destroyShape(collisionEngine, geom);
this.destroyGeom(collisionEngine, geom);
//calculate position between new world point and old world point
Vector3d bodyPosition = new Vector3d(worldPosition).lerp(previousWorldPos, 0.5);
@ -426,18 +429,14 @@ public class HitboxCollectionState {
PhysicsEntityUtils.setGeometryPosition(collisionEngine, geom, bodyPosition, worldRotation);
} else {
//called first time the hitbox updates position
this.geomStateMap.remove(geom);
this.geoms.remove(geom);
CollisionBodyCreation.destroyShape(collisionEngine, geom);
this.destroyGeom(collisionEngine, geom);
//create new capsule
geom = CollisionBodyCreation.createCapsuleShape(manager.getCollisionEngine(), hitboxState.getHitboxData().getRadius(), length, Collidable.TYPE_OBJECT_BIT);
CollisionBodyCreation.attachGeomToBody(collisionEngine,body,geom);
}
//update maps and other variables for next frame
this.stateGeomMap.put(hitboxState,geom);
this.geomStateMap.put(geom,hitboxState);
this.geoms.add(geom);
this.registerGeom(geom, hitboxState);
hitboxState.setPreviousWorldPos(worldPosition);
}
@ -560,11 +559,26 @@ public class HitboxCollectionState {
* @param state The state associated with the geom
*/
private void registerGeom(DGeom geom, HitboxState state){
if(state == null){
throw new IllegalArgumentException("Null hitbox state provided to geometry register!");
}
this.geoms.add(geom);
this.geomStateMap.put(geom,state);
this.stateGeomMap.put(state,geom);
}
/**
* Destroys a geometry
* @param collisionEngine The collision engine for the body
* @param geom The geometry
*/
private void destroyGeom(CollisionEngine collisionEngine, DGeom geom){
HitboxState state = this.geomStateMap.remove(geom);
this.stateGeomMap.remove(state);
this.geoms.remove(geom);
CollisionBodyCreation.destroyShape(collisionEngine, geom);
}
/**
* Gets the hitbox type of a given hitbox data
* @param data The data

View File

@ -203,7 +203,11 @@ public class MenuGeneratorsLevelEditor {
Vector3d eyePos = new Vector3d(CameraEntityUtils.getCameraEye(Globals.playerCamera));
Vector3d centerPos = new Vector3d(CameraEntityUtils.getCameraCenter(Globals.playerCamera));
Realm realm = Globals.realmManager.getRealms().iterator().next();
Vector3d cursorPos = realm.getCollisionEngine().rayCastPosition(new Vector3d(centerPos), new Vector3d(eyePos).mul(-1.0), 5.0).add(cursorVerticalOffset);
Vector3d cursorPos = realm.getCollisionEngine().rayCastPosition(new Vector3d(centerPos), new Vector3d(eyePos).mul(-1.0), 5.0);
if(cursorPos == null){
cursorPos = new Vector3d(centerPos).add(new Vector3d(eyePos).mul(-5.0));
}
cursorPos = cursorPos.add(cursorVerticalOffset);
UnitUtils.spawnUnit(realm, cursorPos, unitDefinition.getId());
}));
}

View File

@ -1,4 +1,4 @@
package electrosphere.server.datacell.physics;
package electrosphere.server.collision;
import org.joml.Vector3d;
import org.ode4j.ode.DContactGeom;
@ -14,6 +14,7 @@ import electrosphere.entity.state.hitbox.HitboxCollectionState.HitboxType;
import electrosphere.entity.state.life.ServerLifeTree;
import electrosphere.entity.types.attach.AttachUtils;
import electrosphere.entity.types.item.ItemUtils;
import electrosphere.logger.LoggerInterface;
/**
* Callback for managing collisions on the server
@ -21,48 +22,106 @@ import electrosphere.entity.types.item.ItemUtils;
public class ServerHitboxResolutionCallback implements CollisionResolutionCallback {
@Override
public void resolve(DContactGeom contactGeom, Collidable impactor, Collidable receiver, Vector3d normal, Vector3d localPosition, Vector3d worldPos, float magnitude) {
Entity impactorParent = impactor.getParent();
Entity receiverParent = receiver.getParent();
HitboxCollectionState impactorState = HitboxCollectionState.getHitboxState(impactorParent);
HitboxCollectionState receiverState = HitboxCollectionState.getHitboxState(receiverParent);
DGeom impactorGeom = contactGeom.g1;
DGeom receiverGeom = contactGeom.g2;
public void resolve(DContactGeom contactGeom, DGeom impactorGeom, DGeom receiverGeom, Collidable impactor, Collidable receiver, Vector3d normal, Vector3d localPosition, Vector3d worldPos, float magnitude) {
Entity impactorEntity = impactor.getParent();
Entity receiverEntity = receiver.getParent();
HitboxCollectionState impactorState = HitboxCollectionState.getHitboxState(impactorEntity);
HitboxCollectionState receiverState = HitboxCollectionState.getHitboxState(receiverEntity);
HitboxState impactorShapeStatus = impactorState.getShapeStatus(impactorGeom);
HitboxState receiverShapeStatus = receiverState.getShapeStatus(receiverGeom);
//basic error checking
if(impactorEntity == null){
throw new IllegalStateException("Impactor's entity is null");
}
if(receiverEntity == null){
throw new IllegalStateException("Receiver's entity is null");
}
if(!HitboxCollectionState.hasHitboxState(impactorEntity)){
throw new IllegalStateException("Impactor state is null");
}
if(!HitboxCollectionState.hasHitboxState(receiverEntity)){
throw new IllegalStateException("Receiver state is null");
}
if(impactorGeom == null){
throw new IllegalStateException("Impactor geom is null");
}
if(receiverGeom == null){
throw new IllegalStateException("Receiver geom is null");
}
if(!impactorState.getGeometries().contains(impactorGeom)){
String message = "Impactor geom has wrong parent assigned!\n" +
"Problem geom: " + impactorGeom + "\n" +
"All geometries tracked: " + impactorState.getGeometries() + "n\""
;
throw new IllegalStateException(message);
}
if(impactorShapeStatus == null){
String message = "Impactor shape status is null\n" +
"Problem geom: " + impactorGeom + "\n" +
"All geometries tracked: " + impactorState.getGeometries() + "n\""
;
throw new IllegalStateException(message);
}
if(receiverShapeStatus == null){
String message = "Receiver shape status is null\n" +
"Problem geom: " + receiverGeom + "\n" +
"All geometries tracked: " + receiverState.getGeometries() + "n\""
;
throw new IllegalStateException(message);
}
boolean impactorShapeStatusIsNull = impactorShapeStatus == null;
boolean receiverShapeStatusIsNull = receiverShapeStatus == null;
boolean impactorIsHit = impactorShapeStatus != null && impactorShapeStatus.getType() == HitboxType.HIT;
boolean receiverIsHurt = receiverShapeStatus != null && receiverShapeStatus.getType() == HitboxType.HURT;
boolean receiverIsBlock = receiverShapeStatus != null && (receiverShapeStatus.getType() == HitboxType.BLOCK || (receiverShapeStatus.getType() == HitboxType.HIT && receiverShapeStatus.isBlockOverride()));
boolean parentsAreDifferent = AttachUtils.getParent(impactorParent) != receiverParent;
boolean parentsAreDifferent = AttachUtils.getParent(impactorEntity) != receiverEntity;
boolean impactorCollisionBlocked = false;
//check if the impactor thinks it can collide with the receiver
if(impactorParent != null && ServerAttackTree.getServerAttackTree(impactorParent) != null){
ServerAttackTree impactorAttackTree = ServerAttackTree.getServerAttackTree(impactorParent);
//
//sword has attack tree
if(impactorEntity != null && ServerAttackTree.getServerAttackTree(impactorEntity) != null){
ServerAttackTree impactorAttackTree = ServerAttackTree.getServerAttackTree(impactorEntity);
//
//sword-on-sword check
//if we collide with the creature directly
if(!impactorAttackTree.canCollideEntity(receiverParent)){
if(!impactorAttackTree.canCollideEntity(receiverEntity)){
impactorCollisionBlocked = true;
}
//
//sword-on-creature check
//if we collide with an item attached to the creature
if(AttachUtils.hasParent(receiverParent) && !impactorAttackTree.canCollideEntity(AttachUtils.getParent(receiverParent))){
if(AttachUtils.hasParent(receiverEntity) && !impactorAttackTree.canCollideEntity(AttachUtils.getParent(receiverEntity))){
impactorCollisionBlocked = true;
}
} else if(impactorParent != null && AttachUtils.hasParent(impactorParent) && AttachUtils.getParent(impactorParent) != null && ServerAttackTree.getServerAttackTree(AttachUtils.getParent(impactorParent)) != null){
ServerAttackTree impactorAttackTree = ServerAttackTree.getServerAttackTree(AttachUtils.getParent(impactorParent));
//
//creature has attack tree
} else if(impactorEntity != null && AttachUtils.hasParent(impactorEntity) && AttachUtils.getParent(impactorEntity) != null && ServerAttackTree.getServerAttackTree(AttachUtils.getParent(impactorEntity)) != null){
ServerAttackTree impactorAttackTree = ServerAttackTree.getServerAttackTree(AttachUtils.getParent(impactorEntity));
//
//creature-on-sword check
//if we collide with the creature directly
if(!impactorAttackTree.canCollideEntity(receiverParent)){
if(!impactorAttackTree.canCollideEntity(receiverEntity)){
impactorCollisionBlocked = true;
}
//
//creature-on-creature check
//if we collide with an item attached to the creature
if(AttachUtils.hasParent(receiverParent) && !impactorAttackTree.canCollideEntity(AttachUtils.getParent(receiverParent))){
if(AttachUtils.hasParent(receiverEntity) && !impactorAttackTree.canCollideEntity(AttachUtils.getParent(receiverEntity))){
impactorCollisionBlocked = true;
}
}
//
//check if is damage event
boolean isDamageEvent =
!impactorShapeStatusIsNull &&
@ -73,6 +132,7 @@ public class ServerHitboxResolutionCallback implements CollisionResolutionCallba
!impactorCollisionBlocked
;
//
//check if is block event
boolean isBlockEvent =
!impactorShapeStatusIsNull &&
@ -94,70 +154,79 @@ public class ServerHitboxResolutionCallback implements CollisionResolutionCallba
if(isDamageEvent){
//if the entity is attached to is an item, we need to compare with the parent of the item
//to make sure you don't stab yourself for instance
boolean isItem = ItemUtils.isItem(impactorParent);//hitboxParent.containsKey(EntityDataStrings.ITEM_IS_ITEM);
Entity hitboxAttachParent = AttachUtils.getParent(impactorParent);
boolean isItem = ItemUtils.isItem(impactorEntity);
Entity hitboxAttachParent = AttachUtils.getParent(impactorEntity);
//
//handle receiver
if(isItem){
if(hitboxAttachParent != receiverParent){
ServerLifeTree serverLifeTree = ServerLifeTree.getServerLifeTree(receiverParent);
serverLifeTree.addCollisionEvent(impactorParent, impactorShapeStatus, receiverShapeStatus, worldPos, isDamageEvent, isBlockEvent);
if(hitboxAttachParent != receiverEntity){
ServerLifeTree serverLifeTree = ServerLifeTree.getServerLifeTree(receiverEntity);
serverLifeTree.addCollisionEvent(impactorEntity, impactorShapeStatus, receiverShapeStatus, worldPos, isDamageEvent, isBlockEvent);
}
}
//
//handle attacker
this.handleAttackerCollision(impactorParent,receiverParent);
this.handleAttackerCollision(impactorEntity,receiverEntity);
}
if(isBlockEvent){
//
//handle receiver
boolean receiverIsItem = ItemUtils.isItem(receiverParent);
boolean receiverHasParent = AttachUtils.hasParent(receiverParent);
boolean receiverIsItem = ItemUtils.isItem(receiverEntity);
boolean receiverHasParent = AttachUtils.hasParent(receiverEntity);
if(receiverIsItem && receiverHasParent){
//item is equipped to something
ServerLifeTree serverLifeTree = ServerLifeTree.getServerLifeTree(AttachUtils.getParent(receiverParent));
ServerLifeTree serverLifeTree = ServerLifeTree.getServerLifeTree(AttachUtils.getParent(receiverEntity));
if(serverLifeTree != null){
serverLifeTree.addCollisionEvent(impactorParent, impactorShapeStatus, receiverShapeStatus, worldPos, isDamageEvent, isBlockEvent);
serverLifeTree.addCollisionEvent(impactorEntity, impactorShapeStatus, receiverShapeStatus, worldPos, isDamageEvent, isBlockEvent);
}
} else {
//attacking an item that is not equipped to anything
ServerLifeTree serverLifeTree = ServerLifeTree.getServerLifeTree(receiverParent);
ServerLifeTree serverLifeTree = ServerLifeTree.getServerLifeTree(receiverEntity);
if(serverLifeTree != null){
serverLifeTree.addCollisionEvent(impactorParent, impactorShapeStatus, receiverShapeStatus, worldPos, isDamageEvent, isBlockEvent);
serverLifeTree.addCollisionEvent(impactorEntity, impactorShapeStatus, receiverShapeStatus, worldPos, isDamageEvent, isBlockEvent);
}
}
//
//handle attacker
this.handleAttackerCollision(impactorParent,receiverParent);
this.handleAttackerCollision(impactorEntity,receiverEntity);
}
}
/**
* Handles collision tracking from the impactor's side
* @param impactorParent The impactor hitbox's parent entity
* @param impactorEntity The impactor hitbox's parent entity
* @param receiverParent The receiver hitbox's parent entity
*/
private void handleAttackerCollision(Entity impactorParent, Entity receiverParent){
boolean receiverIsItem = ItemUtils.isItem(receiverParent);
boolean receiverHasParent = AttachUtils.hasParent(receiverParent);
private void handleAttackerCollision(Entity impactorEntity, Entity receiverEntity){
boolean receiverIsItem = ItemUtils.isItem(receiverEntity);
boolean receiverHasParent = AttachUtils.hasParent(receiverEntity);
if(impactorParent != null && ServerAttackTree.getServerAttackTree(impactorParent) != null){
ServerAttackTree impactorAttackTree = ServerAttackTree.getServerAttackTree(impactorParent);
impactorAttackTree.collideEntity(receiverParent);
//
//The sword has the attack tree
if(impactorEntity != null && ServerAttackTree.getServerAttackTree(impactorEntity) != null){
ServerAttackTree impactorAttackTree = ServerAttackTree.getServerAttackTree(impactorEntity);
impactorAttackTree.collideEntity(receiverEntity);
//if the receiver is an item that is equipped, collide with parent too
if(receiverIsItem && receiverHasParent){
impactorAttackTree.collideEntity(AttachUtils.getParent(receiverParent));
impactorAttackTree.collideEntity(AttachUtils.getParent(receiverEntity));
} else if(receiverHasParent){
LoggerInterface.loggerEngine.WARNING("Potentially unhandled case with server collision!");
}
} else if(impactorParent != null && AttachUtils.hasParent(impactorParent) && AttachUtils.getParent(impactorParent) != null && ServerAttackTree.getServerAttackTree(AttachUtils.getParent(impactorParent)) != null){
ServerAttackTree impactorAttackTree = ServerAttackTree.getServerAttackTree(AttachUtils.getParent(impactorParent));
impactorAttackTree.collideEntity(receiverParent);
//
//The parent of the sword has the attack tree
} else if(impactorEntity != null && AttachUtils.hasParent(impactorEntity) && AttachUtils.getParent(impactorEntity) != null && ServerAttackTree.getServerAttackTree(AttachUtils.getParent(impactorEntity)) != null){
ServerAttackTree impactorAttackTree = ServerAttackTree.getServerAttackTree(AttachUtils.getParent(impactorEntity));
impactorAttackTree.collideEntity(receiverEntity);
//if the receiver is an item that is equipped, collide with parent too
if(receiverIsItem && receiverHasParent){
impactorAttackTree.collideEntity(AttachUtils.getParent(receiverParent));
impactorAttackTree.collideEntity(AttachUtils.getParent(receiverEntity));
} else if(receiverHasParent){
LoggerInterface.loggerEngine.WARNING("Potentially unhandled case with server collision!");
}
}
}

View File

@ -11,8 +11,8 @@ import electrosphere.collision.hitbox.HitboxManager;
import electrosphere.entity.Entity;
import electrosphere.game.server.world.ServerWorldData;
import electrosphere.net.server.player.Player;
import electrosphere.server.collision.ServerHitboxResolutionCallback;
import electrosphere.server.content.ServerContentManager;
import electrosphere.server.datacell.physics.ServerHitboxResolutionCallback;
/**
* Manages all realms for the engine. Should be a singleton