Greatly improved bullet collision physics performance and accuracy with true ray tracing.
This commit is contained in:
parent
4aba806610
commit
4198a18a70
|
@ -0,0 +1,27 @@
|
|||
package nl.andrewl.aos_core.model;
|
||||
|
||||
/**
|
||||
* Represents the different modes that a player can be in.
|
||||
* <ul>
|
||||
* <li>
|
||||
* In normal mode, the player acts as a usual competitive player that
|
||||
* has an inventory, can shoot weapons, and must traverse the world by
|
||||
* walking around.
|
||||
* </li>
|
||||
* <li>
|
||||
* In creative mode, the player can fly, but still collides with the
|
||||
* world's objects. The player also has unlimited ammunition and
|
||||
* blocks.
|
||||
* </li>
|
||||
* <li>
|
||||
* In spectator mode, the player can fly freely throughout the world,
|
||||
* limited by nothing. The player can't interact with the world in any
|
||||
* way other than simply observing it through sight and sound.
|
||||
* </li>
|
||||
* </ul>
|
||||
*/
|
||||
public enum PlayerMode {
|
||||
NORMAL,
|
||||
CREATIVE,
|
||||
SPECTATOR
|
||||
}
|
|
@ -13,6 +13,7 @@ import nl.andrewl.aos_core.net.client.SoundMessage;
|
|||
import nl.andrewl.aos_core.net.world.ChunkUpdateMessage;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector3f;
|
||||
import org.joml.Vector3fc;
|
||||
import org.joml.Vector3i;
|
||||
|
||||
import java.util.*;
|
||||
|
@ -103,10 +104,9 @@ public class ProjectileManager {
|
|||
|
||||
// Check for if the bullet will move close enough to a player to hit them.
|
||||
Vector3f movement = new Vector3f(projectile.getVelocity()).mul(dt);
|
||||
Vector3f direction = new Vector3f(projectile.getVelocity()).normalize();
|
||||
|
||||
// Check first to see if we'll hit a player this tick.
|
||||
Vector3f testPos = new Vector3f();
|
||||
Vector3f testMovement = new Vector3f(movement).normalize(0.1f);
|
||||
Vector3f playerHit = null;
|
||||
ServerPlayer hitPlayer = null;
|
||||
int playerHitType = -1;
|
||||
|
@ -121,23 +121,25 @@ public class ProjectileManager {
|
|||
) continue;
|
||||
|
||||
Vector3f headPos = player.getEyePosition();
|
||||
Vector3f bodyPos = new Vector3f(player.getPosition());
|
||||
bodyPos.y += 1.0f;
|
||||
float headRadius = Player.RADIUS;
|
||||
Vector3f bodyPos = new Vector3f(player.getPosition().x, player.getPosition().y + 1, player.getPosition().z);
|
||||
float bodyRadius = Player.RADIUS;
|
||||
|
||||
// Do a really shitty collision detection... check in 10cm increments if we're close to the player.
|
||||
// TODO: Come up with a better collision system.
|
||||
testPos.set(projectile.getPosition());
|
||||
while (testPos.distanceSquared(projectile.getPosition()) < movement.lengthSquared() && playerHit == null) {
|
||||
if (testPos.distanceSquared(headPos) < Player.RADIUS * Player.RADIUS) {
|
||||
playerHitType = 1;
|
||||
playerHit = new Vector3f(testPos);
|
||||
hitPlayer = player;
|
||||
} else if (testPos.distanceSquared(bodyPos) < Player.RADIUS * Player.RADIUS) {
|
||||
playerHitType = 2;
|
||||
playerHit = new Vector3f(testPos);
|
||||
hitPlayer = player;
|
||||
}
|
||||
testPos.add(testMovement);
|
||||
Vector3f headIntersect = checkSphereIntersection(projectile.getPosition(), direction, headPos, headRadius);
|
||||
if (headIntersect != null) {
|
||||
playerHitType = 1;
|
||||
playerHit = headIntersect;
|
||||
hitPlayer = player;
|
||||
break;
|
||||
}
|
||||
|
||||
// Check body intersection.
|
||||
Vector3f bodyIntersect = checkSphereIntersection(projectile.getPosition(), direction, bodyPos, bodyRadius);
|
||||
if (bodyIntersect != null) {
|
||||
playerHitType = 2;
|
||||
playerHit = bodyIntersect;
|
||||
hitPlayer = player;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -168,6 +170,37 @@ public class ProjectileManager {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for and returns the point at which a ray with origin p and
|
||||
* normalized direction d intersects with a sphere whose center is c, and
|
||||
* whose radius is r.
|
||||
* @param p The ray origin.
|
||||
* @param d The ray's normalized direction.
|
||||
* @param c The sphere's center.
|
||||
* @param r The radius of the sphere.
|
||||
* @return The point of intersection, if one exists.
|
||||
*/
|
||||
private Vector3f checkSphereIntersection(Vector3fc p, Vector3fc d, Vector3fc c, float r) {
|
||||
Vector3f vpc = new Vector3f(c).sub(p);
|
||||
if (vpc.dot(d) >= 0) {// Only check for intersections ahead of us.
|
||||
Vector3f puv = new Vector3f(d).mul(d.dot(vpc) / d.length());
|
||||
Vector3f pc = new Vector3f(p).add(puv);
|
||||
Vector3f diff = new Vector3f(c).sub(pc);
|
||||
if (diff.length() <= r) {
|
||||
Vector3f pcToC = new Vector3f(pc).sub(c);
|
||||
float dist = (float) Math.sqrt(r * r - pcToC.lengthSquared());
|
||||
float distanceToFirstIntersection;
|
||||
if (vpc.length() > r) {
|
||||
distanceToFirstIntersection = pcToC.length() - dist;
|
||||
} else {
|
||||
distanceToFirstIntersection = pcToC.length() + dist;
|
||||
}
|
||||
return new Vector3f(d).mul(distanceToFirstIntersection).add(p);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void handleProjectileBlockHit(Hit hit, ServerProjectile projectile, long now) {
|
||||
if (!server.getTeamManager().isProtected(hit.pos())) {
|
||||
Gun gun = (Gun) projectile.getSourceItem();
|
||||
|
|
|
@ -102,7 +102,7 @@ public class PlayerActionManager {
|
|||
}
|
||||
|
||||
tickMovement(dt, server, world, server.getConfig().physics);
|
||||
input.reset();
|
||||
input.reset(); // Reset our input state after processing this tick's player input.
|
||||
}
|
||||
|
||||
private void tickGunAction(long now, Server server, GunItemStack g) {
|
||||
|
@ -115,58 +115,17 @@ public class PlayerActionManager {
|
|||
(gun.isAutomatic() || !gunNeedsReCock) &&
|
||||
!server.getTeamManager().isProtected(player) // Don't allow players to shoot from within their own team's protected zones.
|
||||
) {
|
||||
server.getProjectileManager().spawnBullets(player, gun);
|
||||
g.setBulletCount(g.getBulletCount() - 1);
|
||||
gunLastShotAt = now;
|
||||
if (!gun.isAutomatic()) {
|
||||
gunNeedsReCock = true;
|
||||
}
|
||||
server.getPlayerManager().getHandler(player.getId()).sendDatagramPacket(new ItemStackMessage(player.getInventory()));
|
||||
// Apply recoil!
|
||||
float recoilFactor = 10f; // Maximum number of degrees to recoil.
|
||||
if (isScopeEnabled()) recoilFactor *= 0.1f;
|
||||
float recoil = recoilFactor * gun.getRecoil() + (float) ThreadLocalRandom.current().nextGaussian(0, 0.01);
|
||||
server.getPlayerManager().getHandler(player.getId()).sendDatagramPacket(new ClientRecoilMessage(0, Math.toRadians(recoil)));
|
||||
// Play sound!
|
||||
String shotSound = null;
|
||||
if (gun instanceof Rifle) {
|
||||
shotSound = "shot_m1-garand_1";
|
||||
} else if (gun instanceof Ak47) {
|
||||
shotSound = "shot_ak-47_1";
|
||||
} else if (gun instanceof Winchester) {
|
||||
shotSound = "shot_winchester_1";
|
||||
}
|
||||
Vector3f soundLocation = new Vector3f(player.getPosition());
|
||||
soundLocation.y += 1.4f;
|
||||
soundLocation.add(player.getViewVector());
|
||||
server.getPlayerManager().broadcastUdpMessage(new SoundMessage(shotSound, 1, soundLocation, player.getVelocity()));
|
||||
shootGun(now, server, gun, g);
|
||||
}
|
||||
|
||||
if (// Check to see if the player is reloading.
|
||||
input.reloading() &&
|
||||
!gunReloading &&
|
||||
g.getClipCount() > 0
|
||||
) {
|
||||
g.setClipCount(g.getClipCount() - 1);
|
||||
gunReloadingStartedAt = now;
|
||||
gunReloading = true;
|
||||
reloadingItemStack = g;
|
||||
server.getPlayerManager().getHandler(player.getId()).sendDatagramPacket(new ItemStackMessage(player.getInventory()));
|
||||
server.getPlayerManager().broadcastUdpMessage(new SoundMessage("reload", 1, player.getPosition(), player.getVelocity()));
|
||||
// Check to see if the player is reloading.
|
||||
if (input.reloading() && !gunReloading && g.getClipCount() > 0) {
|
||||
reloadGun(now, server, g);
|
||||
}
|
||||
|
||||
if (// Check to see if reloading is done.
|
||||
gunReloading &&
|
||||
reloadingItemStack != null &&
|
||||
now - gunReloadingStartedAt > gun.getReloadTime() * 1000
|
||||
) {
|
||||
reloadingItemStack.setBulletCount(gun.getMaxBulletCount());
|
||||
int idx = player.getInventory().getIndex(reloadingItemStack);
|
||||
if (idx != -1) {
|
||||
server.getPlayerManager().getHandler(player.getId()).sendDatagramPacket(new ItemStackMessage(idx, reloadingItemStack));
|
||||
}
|
||||
gunReloading = false;
|
||||
reloadingItemStack = null;
|
||||
// Check to see if reloading is done.
|
||||
if (gunReloading && reloadingItemStack != null && now - gunReloadingStartedAt > gun.getReloadTime() * 1000) {
|
||||
reloadingComplete(server, gun);
|
||||
}
|
||||
|
||||
// Check to see if the player released the trigger, for non-automatic weapons.
|
||||
|
@ -334,26 +293,6 @@ public class PlayerActionManager {
|
|||
return points;
|
||||
}
|
||||
|
||||
private boolean isSpaceOccupied(Vector3i pos) {
|
||||
var playerPos = player.getPosition();
|
||||
float playerBodyMinZ = playerPos.z - RADIUS;
|
||||
float playerBodyMaxZ = playerPos.z + RADIUS;
|
||||
float playerBodyMinX = playerPos.x - RADIUS;
|
||||
float playerBodyMaxX = playerPos.x + RADIUS;
|
||||
float playerBodyMinY = playerPos.y;
|
||||
float playerBodyMaxY = playerPos.y + player.getCurrentHeight();
|
||||
|
||||
// Compute the bounds of all blocks the player is intersecting with.
|
||||
int minX = (int) Math.floor(playerBodyMinX);
|
||||
int minZ = (int) Math.floor(playerBodyMinZ);
|
||||
int minY = (int) Math.floor(playerBodyMinY);
|
||||
int maxX = (int) Math.floor(playerBodyMaxX);
|
||||
int maxZ = (int) Math.floor(playerBodyMaxZ);
|
||||
int maxY = (int) Math.floor(playerBodyMaxY);
|
||||
|
||||
return pos.x >= minX && pos.x <= maxX && pos.y >= minY && pos.y <= maxY && pos.z >= minZ && pos.z <= maxZ;
|
||||
}
|
||||
|
||||
private void checkBlockCollisions(Vector3f movement, Server server, World world) {
|
||||
var position = player.getPosition();
|
||||
var velocity = player.getVelocity();
|
||||
|
@ -501,4 +440,51 @@ public class PlayerActionManager {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void shootGun(long now, Server server, Gun gun, GunItemStack g) {
|
||||
server.getProjectileManager().spawnBullets(player, gun);
|
||||
g.setBulletCount(g.getBulletCount() - 1);
|
||||
gunLastShotAt = now;
|
||||
if (!gun.isAutomatic()) {
|
||||
gunNeedsReCock = true;
|
||||
}
|
||||
server.getPlayerManager().getHandler(player.getId()).sendDatagramPacket(new ItemStackMessage(player.getInventory()));
|
||||
// Apply recoil!
|
||||
float recoilFactor = 10f; // Maximum number of degrees to recoil.
|
||||
if (isScopeEnabled()) recoilFactor *= 0.1f;
|
||||
float recoil = recoilFactor * gun.getRecoil() + (float) ThreadLocalRandom.current().nextGaussian(0, 0.01);
|
||||
server.getPlayerManager().getHandler(player.getId()).sendDatagramPacket(new ClientRecoilMessage(0, Math.toRadians(recoil)));
|
||||
// Play sound!
|
||||
String shotSound = null;
|
||||
if (gun instanceof Rifle) {
|
||||
shotSound = "shot_m1-garand_1";
|
||||
} else if (gun instanceof Ak47) {
|
||||
shotSound = "shot_ak-47_1";
|
||||
} else if (gun instanceof Winchester) {
|
||||
shotSound = "shot_winchester_1";
|
||||
}
|
||||
Vector3f soundLocation = new Vector3f(player.getPosition());
|
||||
soundLocation.y += 1.4f;
|
||||
soundLocation.add(player.getViewVector());
|
||||
server.getPlayerManager().broadcastUdpMessage(new SoundMessage(shotSound, 1, soundLocation, player.getVelocity()));
|
||||
}
|
||||
|
||||
private void reloadGun(long now, Server server, GunItemStack g) {
|
||||
g.setClipCount(g.getClipCount() - 1);
|
||||
gunReloadingStartedAt = now;
|
||||
gunReloading = true;
|
||||
reloadingItemStack = g;
|
||||
server.getPlayerManager().getHandler(player.getId()).sendDatagramPacket(new ItemStackMessage(player.getInventory()));
|
||||
server.getPlayerManager().broadcastUdpMessage(new SoundMessage("reload", 1, player.getPosition(), player.getVelocity()));
|
||||
}
|
||||
|
||||
private void reloadingComplete(Server server, Gun gun) {
|
||||
reloadingItemStack.setBulletCount(gun.getMaxBulletCount());
|
||||
int idx = player.getInventory().getIndex(reloadingItemStack);
|
||||
if (idx != -1) {
|
||||
server.getPlayerManager().getHandler(player.getId()).sendDatagramPacket(new ItemStackMessage(idx, reloadingItemStack));
|
||||
}
|
||||
gunReloading = false;
|
||||
reloadingItemStack = null;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue