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 nl.andrewl.aos_core.net.world.ChunkUpdateMessage;
|
||||||
import org.joml.Matrix4f;
|
import org.joml.Matrix4f;
|
||||||
import org.joml.Vector3f;
|
import org.joml.Vector3f;
|
||||||
|
import org.joml.Vector3fc;
|
||||||
import org.joml.Vector3i;
|
import org.joml.Vector3i;
|
||||||
|
|
||||||
import java.util.*;
|
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.
|
// Check for if the bullet will move close enough to a player to hit them.
|
||||||
Vector3f movement = new Vector3f(projectile.getVelocity()).mul(dt);
|
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.
|
// 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;
|
Vector3f playerHit = null;
|
||||||
ServerPlayer hitPlayer = null;
|
ServerPlayer hitPlayer = null;
|
||||||
int playerHitType = -1;
|
int playerHitType = -1;
|
||||||
|
@ -121,23 +121,25 @@ public class ProjectileManager {
|
||||||
) continue;
|
) continue;
|
||||||
|
|
||||||
Vector3f headPos = player.getEyePosition();
|
Vector3f headPos = player.getEyePosition();
|
||||||
Vector3f bodyPos = new Vector3f(player.getPosition());
|
float headRadius = Player.RADIUS;
|
||||||
bodyPos.y += 1.0f;
|
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.
|
Vector3f headIntersect = checkSphereIntersection(projectile.getPosition(), direction, headPos, headRadius);
|
||||||
// TODO: Come up with a better collision system.
|
if (headIntersect != null) {
|
||||||
testPos.set(projectile.getPosition());
|
|
||||||
while (testPos.distanceSquared(projectile.getPosition()) < movement.lengthSquared() && playerHit == null) {
|
|
||||||
if (testPos.distanceSquared(headPos) < Player.RADIUS * Player.RADIUS) {
|
|
||||||
playerHitType = 1;
|
playerHitType = 1;
|
||||||
playerHit = new Vector3f(testPos);
|
playerHit = headIntersect;
|
||||||
hitPlayer = player;
|
|
||||||
} else if (testPos.distanceSquared(bodyPos) < Player.RADIUS * Player.RADIUS) {
|
|
||||||
playerHitType = 2;
|
|
||||||
playerHit = new Vector3f(testPos);
|
|
||||||
hitPlayer = player;
|
hitPlayer = player;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
testPos.add(testMovement);
|
|
||||||
|
// 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) {
|
private void handleProjectileBlockHit(Hit hit, ServerProjectile projectile, long now) {
|
||||||
if (!server.getTeamManager().isProtected(hit.pos())) {
|
if (!server.getTeamManager().isProtected(hit.pos())) {
|
||||||
Gun gun = (Gun) projectile.getSourceItem();
|
Gun gun = (Gun) projectile.getSourceItem();
|
||||||
|
|
|
@ -102,7 +102,7 @@ public class PlayerActionManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
tickMovement(dt, server, world, server.getConfig().physics);
|
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) {
|
private void tickGunAction(long now, Server server, GunItemStack g) {
|
||||||
|
@ -115,58 +115,17 @@ public class PlayerActionManager {
|
||||||
(gun.isAutomatic() || !gunNeedsReCock) &&
|
(gun.isAutomatic() || !gunNeedsReCock) &&
|
||||||
!server.getTeamManager().isProtected(player) // Don't allow players to shoot from within their own team's protected zones.
|
!server.getTeamManager().isProtected(player) // Don't allow players to shoot from within their own team's protected zones.
|
||||||
) {
|
) {
|
||||||
server.getProjectileManager().spawnBullets(player, gun);
|
shootGun(now, server, gun, g);
|
||||||
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()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (// Check to see if the player is reloading.
|
// Check to see if the player is reloading.
|
||||||
input.reloading() &&
|
if (input.reloading() && !gunReloading && g.getClipCount() > 0) {
|
||||||
!gunReloading &&
|
reloadGun(now, server, g);
|
||||||
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()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (// Check to see if reloading is done.
|
// Check to see if reloading is done.
|
||||||
gunReloading &&
|
if (gunReloading && reloadingItemStack != null && now - gunReloadingStartedAt > gun.getReloadTime() * 1000) {
|
||||||
reloadingItemStack != null &&
|
reloadingComplete(server, gun);
|
||||||
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 the player released the trigger, for non-automatic weapons.
|
// Check to see if the player released the trigger, for non-automatic weapons.
|
||||||
|
@ -334,26 +293,6 @@ public class PlayerActionManager {
|
||||||
return points;
|
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) {
|
private void checkBlockCollisions(Vector3f movement, Server server, World world) {
|
||||||
var position = player.getPosition();
|
var position = player.getPosition();
|
||||||
var velocity = player.getVelocity();
|
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