X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=src%2Fcollision.cpp;h=575e70ff9375f263d1eab1ad6357dfd11d4acda7;hb=55804c56e9485659c912bf965761187b9ec0597f;hp=6d24bc699f10fe19acf6d7411b9cedb381a63dad;hpb=e8ac5a31cf12afcfddf8e3ed31e8038930edb06f;p=minetest.git diff --git a/src/collision.cpp b/src/collision.cpp index 6d24bc699..575e70ff9 100644 --- a/src/collision.cpp +++ b/src/collision.cpp @@ -32,25 +32,56 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "util/timetaker.h" #include "profiler.h" +#ifdef __FAST_MATH__ +#warning "-ffast-math is known to cause bugs in collision code, do not use!" +#endif struct NearbyCollisionInfo { - NearbyCollisionInfo(bool is_ul, bool is_obj, int bouncy, - const v3s16 &pos, const aabb3f &box) : + // node + NearbyCollisionInfo(bool is_ul, int bouncy, const v3s16 &pos, + const aabb3f &box) : is_unloaded(is_ul), - is_object(is_obj), + obj(nullptr), bouncy(bouncy), position(pos), box(box) {} + // object + NearbyCollisionInfo(ActiveObject *obj, int bouncy, + const aabb3f &box) : + is_unloaded(false), + obj(obj), + bouncy(bouncy), + box(box) + {} + + inline bool isObject() const { return obj != nullptr; } + bool is_unloaded; bool is_step_up = false; - bool is_object; + ActiveObject *obj; int bouncy; v3s16 position; aabb3f box; }; +// Helper functions: +// Truncate floating point numbers to specified number of decimal places +// in order to move all the floating point error to one side of the correct value +static inline f32 truncate(const f32 val, const f32 factor) +{ + return truncf(val * factor) / factor; +} + +static inline v3f truncate(const v3f& vec, const f32 factor) +{ + return v3f( + truncate(vec.X, factor), + truncate(vec.Y, factor), + truncate(vec.Z, factor) + ); +} // Helper function: // Checks for collision of a moving aabbox with a static aabbox @@ -63,70 +94,70 @@ CollisionAxis axisAlignedCollision( //TimeTaker tt("axisAlignedCollision"); aabb3f relbox( - movingbox.MaxEdge.X - movingbox.MinEdge.X + staticbox.MaxEdge.X - staticbox.MinEdge.X, // sum of the widths - movingbox.MaxEdge.Y - movingbox.MinEdge.Y + staticbox.MaxEdge.Y - staticbox.MinEdge.Y, - movingbox.MaxEdge.Z - movingbox.MinEdge.Z + staticbox.MaxEdge.Z - staticbox.MinEdge.Z, + (movingbox.MaxEdge.X - movingbox.MinEdge.X) + (staticbox.MaxEdge.X - staticbox.MinEdge.X), // sum of the widths + (movingbox.MaxEdge.Y - movingbox.MinEdge.Y) + (staticbox.MaxEdge.Y - staticbox.MinEdge.Y), + (movingbox.MaxEdge.Z - movingbox.MinEdge.Z) + (staticbox.MaxEdge.Z - staticbox.MinEdge.Z), std::max(movingbox.MaxEdge.X, staticbox.MaxEdge.X) - std::min(movingbox.MinEdge.X, staticbox.MinEdge.X), //outer bounding 'box' dimensions std::max(movingbox.MaxEdge.Y, staticbox.MaxEdge.Y) - std::min(movingbox.MinEdge.Y, staticbox.MinEdge.Y), std::max(movingbox.MaxEdge.Z, staticbox.MaxEdge.Z) - std::min(movingbox.MinEdge.Z, staticbox.MinEdge.Z) ); const f32 dtime_max = *dtime; - const f32 inner_margin = -1.5f; + f32 inner_margin; // the distance of clipping recovery f32 distance; f32 time; - if (speed.X) { - distance = relbox.MaxEdge.X - relbox.MinEdge.X; - *dtime = distance >= 0 ? std::abs(distance / speed.X) : -std::abs(distance / speed.X); + if (speed.Y) { + distance = relbox.MaxEdge.Y - relbox.MinEdge.Y; + *dtime = distance / std::abs(speed.Y); time = std::max(*dtime, 0.0f); - if (distance > inner_margin) { - if (*dtime <= dtime_max) { - if ((speed.X > 0 && staticbox.MaxEdge.X > movingbox.MaxEdge.X) || - (speed.X < 0 && staticbox.MinEdge.X < movingbox.MinEdge.X)) { - if ( - (std::max(movingbox.MaxEdge.Y + speed.Y * time, staticbox.MaxEdge.Y) - - std::min(movingbox.MinEdge.Y + speed.Y * time, staticbox.MinEdge.Y) - - relbox.MinEdge.Y < 0) && + if (*dtime <= dtime_max) { + inner_margin = std::max(-0.5f * (staticbox.MaxEdge.Y - staticbox.MinEdge.Y), -2.0f); + + if ((speed.Y > 0 && staticbox.MinEdge.Y - movingbox.MaxEdge.Y > inner_margin) || + (speed.Y < 0 && movingbox.MinEdge.Y - staticbox.MaxEdge.Y > inner_margin)) { + if ( + (std::max(movingbox.MaxEdge.X + speed.X * time, staticbox.MaxEdge.X) + - std::min(movingbox.MinEdge.X + speed.X * time, staticbox.MinEdge.X) + - relbox.MinEdge.X < 0) && (std::max(movingbox.MaxEdge.Z + speed.Z * time, staticbox.MaxEdge.Z) - std::min(movingbox.MinEdge.Z + speed.Z * time, staticbox.MinEdge.Z) - relbox.MinEdge.Z < 0) - ) - return COLLISION_AXIS_X; - } - } else { - return COLLISION_AXIS_NONE; + ) + return COLLISION_AXIS_Y; } } + else { + return COLLISION_AXIS_NONE; + } } // NO else if here - if (speed.Y) { - distance = relbox.MaxEdge.Y - relbox.MinEdge.Y; - - *dtime = distance >= 0 ? std::abs(distance / speed.Y) : -std::abs(distance / speed.Y); + if (speed.X) { + distance = relbox.MaxEdge.X - relbox.MinEdge.X; + *dtime = distance / std::abs(speed.X); time = std::max(*dtime, 0.0f); - if (distance > inner_margin) { - if (*dtime <= dtime_max) { - if ((speed.Y > 0 && staticbox.MaxEdge.Y > movingbox.MaxEdge.Y) || - (speed.Y < 0 && staticbox.MinEdge.Y < movingbox.MinEdge.Y)) { - if ( - (std::max(movingbox.MaxEdge.X + speed.X * time, staticbox.MaxEdge.X) - - std::min(movingbox.MinEdge.X + speed.X * time, staticbox.MinEdge.X) - - relbox.MinEdge.X < 0) && + if (*dtime <= dtime_max) { + inner_margin = std::max(-0.5f * (staticbox.MaxEdge.X - staticbox.MinEdge.X), -2.0f); + + if ((speed.X > 0 && staticbox.MinEdge.X - movingbox.MaxEdge.X > inner_margin) || + (speed.X < 0 && movingbox.MinEdge.X - staticbox.MaxEdge.X > inner_margin)) { + if ( + (std::max(movingbox.MaxEdge.Y + speed.Y * time, staticbox.MaxEdge.Y) + - std::min(movingbox.MinEdge.Y + speed.Y * time, staticbox.MinEdge.Y) + - relbox.MinEdge.Y < 0) && (std::max(movingbox.MaxEdge.Z + speed.Z * time, staticbox.MaxEdge.Z) - std::min(movingbox.MinEdge.Z + speed.Z * time, staticbox.MinEdge.Z) - relbox.MinEdge.Z < 0) - ) - return COLLISION_AXIS_Y; - } - } else { - return COLLISION_AXIS_NONE; + ) + return COLLISION_AXIS_X; } + } else { + return COLLISION_AXIS_NONE; } } @@ -134,24 +165,23 @@ CollisionAxis axisAlignedCollision( if (speed.Z) { distance = relbox.MaxEdge.Z - relbox.MinEdge.Z; - - *dtime = distance >= 0 ? std::abs(distance / speed.Z) : -std::abs(distance / speed.Z); + *dtime = distance / std::abs(speed.Z); time = std::max(*dtime, 0.0f); - if (distance > inner_margin) { - if (*dtime <= dtime_max) { - if ((speed.Z > 0 && staticbox.MaxEdge.Z > movingbox.MaxEdge.Z) || - (speed.Z < 0 && staticbox.MinEdge.Z < movingbox.MinEdge.Z)) { - if ( - (std::max(movingbox.MaxEdge.X + speed.X * time, staticbox.MaxEdge.X) - - std::min(movingbox.MinEdge.X + speed.X * time, staticbox.MinEdge.X) - - relbox.MinEdge.X < 0) && + if (*dtime <= dtime_max) { + inner_margin = std::max(-0.5f * (staticbox.MaxEdge.Z - staticbox.MinEdge.Z), -2.0f); + + if ((speed.Z > 0 && staticbox.MinEdge.Z - movingbox.MaxEdge.Z > inner_margin) || + (speed.Z < 0 && movingbox.MinEdge.Z - staticbox.MaxEdge.Z > inner_margin)) { + if ( + (std::max(movingbox.MaxEdge.X + speed.X * time, staticbox.MaxEdge.X) + - std::min(movingbox.MinEdge.X + speed.X * time, staticbox.MinEdge.X) + - relbox.MinEdge.X < 0) && (std::max(movingbox.MaxEdge.Y + speed.Y * time, staticbox.MaxEdge.Y) - std::min(movingbox.MinEdge.Y + speed.Y * time, staticbox.MinEdge.Y) - relbox.MinEdge.Y < 0) - ) - return COLLISION_AXIS_Z; - } + ) + return COLLISION_AXIS_Z; } } } @@ -199,10 +229,12 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, v3f accel_f, ActiveObject *self, bool collideWithObjects) { + #define PROFILER_NAME(text) (s_env ? ("Server: " text) : ("Client: " text)) static bool time_notification_done = false; Map *map = &env->getMap(); + ServerEnvironment *s_env = dynamic_cast(env); - ScopeProfiler sp(g_profiler, "collisionMoveSimple()", SPT_AVG); + ScopeProfiler sp(g_profiler, PROFILER_NAME("collisionMoveSimple()"), SPT_AVG); collisionMoveResult result; @@ -219,10 +251,13 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, } else { time_notification_done = false; } + + v3f dpos_f = (*speed_f + accel_f * 0.5f * dtime) * dtime; + v3f newpos_f = *pos_f + dpos_f; *speed_f += accel_f * dtime; - // If there is no speed, there are no collisions - if (speed_f->getLength() == 0) + // If the object is static, there are no collisions + if (dpos_f == v3f()) return result; // Limit speed for avoiding hangs @@ -230,15 +265,16 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, speed_f->X = rangelim(speed_f->X, -5000, 5000); speed_f->Z = rangelim(speed_f->Z, -5000, 5000); + *speed_f = truncate(*speed_f, 10000.0f); + /* Collect node boxes in movement range */ std::vector cinfo; { //TimeTaker tt2("collisionMoveSimple collect boxes"); - ScopeProfiler sp2(g_profiler, "collisionMoveSimple(): collect boxes", SPT_AVG); + ScopeProfiler sp2(g_profiler, PROFILER_NAME("collisionMoveSimple(): collect boxes"), SPT_AVG); - v3f newpos_f = *pos_f + *speed_f * dtime; v3f minpos_f( MYMIN(pos_f->X, newpos_f.X), MYMIN(pos_f->Y, newpos_f.Y) + 0.01f * BS, // bias rounding, player often at +/-n.5 @@ -271,7 +307,8 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, if (!f.walkable) continue; - int n_bouncy_value = itemgroup_get(f.groups, "bouncy"); + // Negative bouncy may have a meaning, but we need +value here. + int n_bouncy_value = abs(itemgroup_get(f.groups, "bouncy")); int neighbors = 0; if (f.drawtype == NDT_NODEBOX && @@ -309,13 +346,13 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, for (auto box : nodeboxes) { box.MinEdge += posf; box.MaxEdge += posf; - cinfo.emplace_back(false, false, n_bouncy_value, p, box); + cinfo.emplace_back(false, n_bouncy_value, p, box); } } else { // Collide with unloaded nodes (position invalid) and loaded // CONTENT_IGNORE nodes (position valid) aabb3f box = getNodeBox(p, BS); - cinfo.emplace_back(true, false, 0, p, box); + cinfo.emplace_back(true, 0, p, box); } } @@ -355,7 +392,6 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, else #endif { - ServerEnvironment *s_env = dynamic_cast(env); if (s_env != NULL) { // Calculate distance by speed, add own extent and 1.5m of tolerance f32 distance = speed_f->getLength() * dtime + @@ -365,7 +401,8 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, // we directly use the callback to populate the result to prevent // a useless result loop here auto include_obj_cb = [self, &objects] (ServerActiveObject *obj) { - if (!self || (self != obj && self != obj->getParent())) { + if (!obj->isGone() && + (!self || (self != obj && self != obj->getParent()))) { objects.push_back((ActiveObject *)obj); } return false; @@ -380,12 +417,10 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, iter != objects.end(); ++iter) { ActiveObject *object = *iter; - if (object) { + if (object && object->collideWithObjects()) { aabb3f object_collisionbox; - if (object->getCollisionBox(&object_collisionbox) && - object->collideWithObjects()) { - cinfo.emplace_back(false, true, 0, v3s16(), object_collisionbox); - } + if (object->getCollisionBox(&object_collisionbox)) + cinfo.emplace_back(object, 0, object_collisionbox); } } #ifndef SERVER @@ -396,7 +431,8 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, v3f lplayer_pos = lplayer->getPosition(); lplayer_collisionbox.MinEdge += lplayer_pos; lplayer_collisionbox.MaxEdge += lplayer_pos; - cinfo.emplace_back(false, true, 0, v3s16(), lplayer_collisionbox); + ActiveObject *obj = (ActiveObject*) lplayer->getCAO(); + cinfo.emplace_back(obj, 0, lplayer_collisionbox); } } #endif @@ -450,7 +486,7 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, if (nearest_collided == COLLISION_AXIS_NONE) { // No collision with any collision box. - *pos_f += *speed_f * dtime; + *pos_f += truncate(*speed_f * dtime, 100.0f); dtime = 0; // Set to 0 to avoid "infinite" loop due to small FP numbers } else { // Otherwise, a collision occurred. @@ -486,7 +522,7 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, pos_f->Z += speed_f->Z * nearest_dtime; } } else { - *pos_f += *speed_f * nearest_dtime; + *pos_f += truncate(*speed_f * nearest_dtime, 100.0f); dtime -= nearest_dtime; } @@ -495,12 +531,13 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, is_collision = false; CollisionInfo info; - if (nearest_info.is_object) + if (nearest_info.isObject()) info.type = COLLISION_OBJECT; else info.type = COLLISION_NODE; info.node_p = nearest_info.position; + info.object = nearest_info.obj; info.old_speed = *speed_f; info.plane = nearest_collided; @@ -569,7 +606,7 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, if (std::fabs(cbox.MaxEdge.Y - box.MinEdge.Y) < 0.05f) { result.touching_ground = true; - if (box_info.is_object) + if (box_info.isObject()) result.standing_on_object = true; } }