#include "PhysicsSolver.hpp" #include "Hitbox.hpp" #include #include #include #include #include #include #define GLM_ENABLE_EXPERIMENTAL #include const float E = 0.03f; const float MAX_FIX = 0.1f; PhysicsSolver::PhysicsSolver(glm::vec3 gravity) : gravity(gravity) { } void PhysicsSolver::step( Chunks* chunks, Hitbox* hitbox, float delta, uint substeps, entityid_t entity ) { float dt = delta / static_cast(substeps); float linearDamping = hitbox->linearDamping; float s = 2.0f/BLOCK_AABB_GRID; const glm::vec3& half = hitbox->halfsize; glm::vec3& pos = hitbox->position; glm::vec3& vel = hitbox->velocity; float gravityScale = hitbox->gravityScale; bool prevGrounded = hitbox->grounded; hitbox->grounded = false; for (uint i = 0; i < substeps; i++) { float px = pos.x; float py = pos.y; float pz = pos.z; vel += gravity * dt * gravityScale; if (hitbox->type == BodyType::DYNAMIC) { colisionCalc(chunks, hitbox, vel, pos, half, (prevGrounded && gravityScale > 0.0f) ? 0.5f : 0.0f); } vel.x *= glm::max(0.0f, 1.0f - dt * linearDamping); if (hitbox->verticalDamping) { vel.y *= glm::max(0.0f, 1.0f - dt * linearDamping); } vel.z *= glm::max(0.0f, 1.0f - dt * linearDamping); pos += vel * dt + gravity * gravityScale * dt * dt * 0.5f; if (hitbox->grounded && pos.y < py) { pos.y = py; } if (hitbox->crouching && hitbox->grounded){ float y = (pos.y-half.y-E); hitbox->grounded = false; for (int ix = 0; ix <= (half.x-E)*2/s; ix++) { float x = (px-half.x+E) + ix * s; for (int iz = 0; iz <= (half.z-E)*2/s; iz++){ float z = (pos.z-half.z+E) + iz * s; if (chunks->isObstacleAt(x,y,z)){ hitbox->grounded = true; break; } } } if (!hitbox->grounded) { pos.z = pz; } hitbox->grounded = false; for (int ix = 0; ix <= (half.x-E)*2/s; ix++) { float x = (pos.x-half.x+E) + ix * s; for (int iz = 0; iz <= (half.z-E)*2/s; iz++){ float z = (pz-half.z+E) + iz * s; if (chunks->isObstacleAt(x,y,z)){ hitbox->grounded = true; break; } } } if (!hitbox->grounded) { pos.x = px; } hitbox->grounded = true; } } AABB aabb; aabb.a = hitbox->position - hitbox->halfsize; aabb.b = hitbox->position + hitbox->halfsize; for (size_t i = 0; i < sensors.size(); i++) { auto& sensor = *sensors[i]; if (sensor.entity == entity) { continue; } bool triggered = false; switch (sensor.type) { case SensorType::AABB: triggered = aabb.intersect(sensor.calculated.aabb); break; case SensorType::RADIUS: triggered = glm::distance2( hitbox->position, glm::vec3(sensor.calculated.radial)) < sensor.calculated.radial.w; break; } if (triggered) { if (sensor.prevEntered.find(entity) == sensor.prevEntered.end()) { sensor.enterCallback(sensor.entity, sensor.index, entity); } sensor.nextEntered.insert(entity); } } } static float calc_step_height( Chunks* chunks, glm::vec3& pos, const glm::vec3& half, float stepHeight, float s ) { if (stepHeight > 0.0f) { for (int ix = 0; ix <= (half.x-E)*2/s; ix++) { float x = (pos.x-half.x+E) + ix * s; for (int iz = 0; iz <= (half.z-E)*2/s; iz++) { float z = (pos.z-half.z+E) + iz * s; if (chunks->isObstacleAt(x, pos.y+half.y+stepHeight, z)) { return 0.0f; } } } } return stepHeight; } template static bool calc_collision_neg( Chunks* chunks, glm::vec3& pos, glm::vec3& vel, const glm::vec3& half, float stepHeight, float s ) { if (vel[nx] >= 0.0f) { return false; } glm::vec3 offset(0.0f, stepHeight, 0.0f); for (int iy = 0; iy <= (half[ny]-E)*2/s; iy++) { glm::vec3 coord; coord[ny] = ((pos+offset)[ny]-half[ny]+E) + iy * s; for (int iz = 0; iz <= (half[nz]-E)*2/s; iz++){ coord[nz] = (pos[nz]-half[nz]+E) + iz * s; coord[nx] = (pos[nx]-half[nx]-E); if (const auto aabb = chunks->isObstacleAt(coord.x, coord.y, coord.z)) { vel[nx] = 0.0f; float newx = std::floor(coord[nx]) + aabb->max()[nx] + half[nx] + E; if (std::abs(newx-pos[nx]) <= MAX_FIX) { pos[nx] = newx; } return true; } } } return false; } template static void calc_collision_pos( Chunks* chunks, glm::vec3& pos, glm::vec3& vel, const glm::vec3& half, float stepHeight, float s ) { if (vel[nx] <= 0.0f) { return; } glm::vec3 offset(0.0f, stepHeight, 0.0f); for (int iy = 0; iy <= (half[ny]-E)*2/s; iy++) { glm::vec3 coord; coord[ny] = ((pos+offset)[ny]-half[ny]+E) + iy * s; for (int iz = 0; iz <= (half[nz]-E)*2/s; iz++) { coord[nz] = (pos[nz]-half[nz]+E) + iz * s; coord[nx] = (pos[nx]+half[nx]+E); if (const auto aabb = chunks->isObstacleAt(coord.x, coord.y, coord.z)) { vel[nx] = 0.0f; float newx = std::floor(coord[nx]) - half[nx] + aabb->min()[nx] - E; if (std::abs(newx-pos[nx]) <= MAX_FIX) { pos[nx] = newx; } return; } } } } void PhysicsSolver::colisionCalc( Chunks* chunks, Hitbox* hitbox, glm::vec3& vel, glm::vec3& pos, const glm::vec3 half, float stepHeight ) { // step size (smaller - more accurate, but slower) float s = 2.0f/BLOCK_AABB_GRID; stepHeight = calc_step_height(chunks, pos, half, stepHeight, s); const AABB* aabb; calc_collision_neg<0, 1, 2>(chunks, pos, vel, half, stepHeight, s); calc_collision_pos<0, 1, 2>(chunks, pos, vel, half, stepHeight, s); calc_collision_neg<2, 1, 0>(chunks, pos, vel, half, stepHeight, s); calc_collision_pos<2, 1, 0>(chunks, pos, vel, half, stepHeight, s); if (calc_collision_neg<1, 0, 2>(chunks, pos, vel, half, stepHeight, s)) { hitbox->grounded = true; } if (stepHeight > 0.0 && vel.y <= 0.0f){ for (int ix = 0; ix <= (half.x-E)*2/s; ix++) { float x = (pos.x-half.x+E) + ix * s; for (int iz = 0; iz <= (half.z-E)*2/s; iz++) { float z = (pos.z-half.z+E) + iz * s; float y = (pos.y-half.y+E); if ((aabb = chunks->isObstacleAt(x,y,z))){ vel.y = 0.0f; float newy = std::floor(y) + aabb->max().y + half.y; if (std::abs(newy-pos.y) <= MAX_FIX+stepHeight) { pos.y = newy; } break; } } } } if (vel.y > 0.0f){ for (int ix = 0; ix <= (half.x-E)*2/s; ix++) { float x = (pos.x-half.x+E) + ix * s; for (int iz = 0; iz <= (half.z-E)*2/s; iz++) { float z = (pos.z-half.z+E) + iz * s; float y = (pos.y+half.y+E); if ((aabb = chunks->isObstacleAt(x,y,z))){ vel.y = 0.0f; float newy = std::floor(y) - half.y + aabb->min().y - E; if (std::abs(newy-pos.y) <= MAX_FIX) { pos.y = newy; } break; } } } } } bool PhysicsSolver::isBlockInside(int x, int y, int z, Hitbox* hitbox) { const glm::vec3& pos = hitbox->position; const glm::vec3& half = hitbox->halfsize; return x >= floor(pos.x-half.x) && x <= floor(pos.x+half.x) && z >= floor(pos.z-half.z) && z <= floor(pos.z+half.z) && y >= floor(pos.y-half.y) && y <= floor(pos.y+half.y); } bool PhysicsSolver::isBlockInside(int x, int y, int z, Block* def, blockstate state, Hitbox* hitbox) { const float E = 0.001f; // inaccuracy const glm::vec3& pos = hitbox->position; const glm::vec3& half = hitbox->halfsize; const auto& boxes = def->rotatable ? def->rt.hitboxes[state.rotation] : def->hitboxes; for (const auto& block_hitbox : boxes) { glm::vec3 min = block_hitbox.min(); glm::vec3 max = block_hitbox.max(); if (min.x < pos.x+half.x-x-E && max.x > pos.x-half.x-x+E && min.z < pos.z+half.z-z-E && max.z > pos.z-half.z-z+E && min.y < pos.y+half.y-y-E && max.y > pos.y-half.y-y+E) return true; } return false; } void PhysicsSolver::removeSensor(Sensor* sensor) { sensors.erase(std::remove(sensors.begin(), sensors.end(), sensor), sensors.end()); }