X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=src%2Fcollision.cpp;h=575e70ff9375f263d1eab1ad6357dfd11d4acda7;hb=55804c56e9485659c912bf965761187b9ec0597f;hp=a3979f1dc03289a70f1e8dd755fc128ca12769bb;hpb=e737b1c271c9d957651ef2201bb820e4465358bf;p=minetest.git diff --git a/src/collision.cpp b/src/collision.cpp index a3979f1dc..575e70ff9 100644 --- a/src/collision.cpp +++ b/src/collision.cpp @@ -18,149 +18,181 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include "collision.h" +#include #include "mapblock.h" #include "map.h" #include "nodedef.h" #include "gamedef.h" -#include "log.h" -#include "environment.h" -#include "serverobject.h" -#include -#include +#ifndef SERVER +#include "client/clientenvironment.h" +#include "client/localplayer.h" +#endif +#include "serverenvironment.h" +#include "server/serveractiveobject.h" #include "util/timetaker.h" #include "profiler.h" -// float error is 10 - 9.96875 = 0.03125 -//#define COLL_ZERO 0.032 // broken unit tests -#define COLL_ZERO 0 +#ifdef __FAST_MATH__ +#warning "-ffast-math is known to cause bugs in collision code, do not use!" +#endif + +struct NearbyCollisionInfo { + // node + NearbyCollisionInfo(bool is_ul, int bouncy, const v3s16 &pos, + const aabb3f &box) : + is_unloaded(is_ul), + 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; + 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 // Returns -1 if no collision, 0 if X collision, 1 if Y collision, 2 if Z collision // The time after which the collision occurs is stored in dtime. -int axisAlignedCollision( +CollisionAxis axisAlignedCollision( const aabb3f &staticbox, const aabb3f &movingbox, - const v3f &speed, f32 d, f32 *dtime) + const v3f &speed, f32 *dtime) { //TimeTaker tt("axisAlignedCollision"); - f32 xsize = (staticbox.MaxEdge.X - staticbox.MinEdge.X) - COLL_ZERO; // reduce box size for solve collision stuck (flying sand) - f32 ysize = (staticbox.MaxEdge.Y - staticbox.MinEdge.Y); // - COLL_ZERO; // Y - no sense for falling, but maybe try later - f32 zsize = (staticbox.MaxEdge.Z - staticbox.MinEdge.Z) - COLL_ZERO; - aabb3f relbox( - movingbox.MinEdge.X - staticbox.MinEdge.X, - movingbox.MinEdge.Y - staticbox.MinEdge.Y, - movingbox.MinEdge.Z - staticbox.MinEdge.Z, - movingbox.MaxEdge.X - staticbox.MinEdge.X, - movingbox.MaxEdge.Y - staticbox.MinEdge.Y, - movingbox.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) ); - if(speed.X > 0) // Check for collision with X- plane - { - if (relbox.MaxEdge.X <= d) { - *dtime = -relbox.MaxEdge.X / speed.X; - if ((relbox.MinEdge.Y + speed.Y * (*dtime) < ysize) && - (relbox.MaxEdge.Y + speed.Y * (*dtime) > COLL_ZERO) && - (relbox.MinEdge.Z + speed.Z * (*dtime) < zsize) && - (relbox.MaxEdge.Z + speed.Z * (*dtime) > COLL_ZERO)) - return 0; - } - else if(relbox.MinEdge.X > xsize) - { - return -1; - } - } - else if(speed.X < 0) // Check for collision with X+ plane - { - if (relbox.MinEdge.X >= xsize - d) { - *dtime = (xsize - relbox.MinEdge.X) / speed.X; - if ((relbox.MinEdge.Y + speed.Y * (*dtime) < ysize) && - (relbox.MaxEdge.Y + speed.Y * (*dtime) > COLL_ZERO) && - (relbox.MinEdge.Z + speed.Z * (*dtime) < zsize) && - (relbox.MaxEdge.Z + speed.Z * (*dtime) > COLL_ZERO)) - return 0; + const f32 dtime_max = *dtime; + f32 inner_margin; // the distance of clipping recovery + f32 distance; + f32 time; + + + if (speed.Y) { + distance = relbox.MaxEdge.Y - relbox.MinEdge.Y; + *dtime = distance / std::abs(speed.Y); + time = std::max(*dtime, 0.0f); + + 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_Y; + } } - else if(relbox.MaxEdge.X < 0) - { - return -1; + else { + return COLLISION_AXIS_NONE; } } // NO else if here - if(speed.Y > 0) // Check for collision with Y- plane - { - if (relbox.MaxEdge.Y <= d) { - *dtime = -relbox.MaxEdge.Y / speed.Y; - if ((relbox.MinEdge.X + speed.X * (*dtime) < xsize) && - (relbox.MaxEdge.X + speed.X * (*dtime) > COLL_ZERO) && - (relbox.MinEdge.Z + speed.Z * (*dtime) < zsize) && - (relbox.MaxEdge.Z + speed.Z * (*dtime) > COLL_ZERO)) - return 1; - } - else if(relbox.MinEdge.Y > ysize) - { - return -1; - } - } - else if(speed.Y < 0) // Check for collision with Y+ plane - { - if (relbox.MinEdge.Y >= ysize - d) { - *dtime = (ysize - relbox.MinEdge.Y) / speed.Y; - if ((relbox.MinEdge.X + speed.X * (*dtime) < xsize) && - (relbox.MaxEdge.X + speed.X * (*dtime) > COLL_ZERO) && - (relbox.MinEdge.Z + speed.Z * (*dtime) < zsize) && - (relbox.MaxEdge.Z + speed.Z * (*dtime) > COLL_ZERO)) - return 1; - } - else if(relbox.MaxEdge.Y < 0) - { - return -1; + if (speed.X) { + distance = relbox.MaxEdge.X - relbox.MinEdge.X; + *dtime = distance / std::abs(speed.X); + time = std::max(*dtime, 0.0f); + + 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_X; + } + } else { + return COLLISION_AXIS_NONE; } } // NO else if here - if(speed.Z > 0) // Check for collision with Z- plane - { - if (relbox.MaxEdge.Z <= d) { - *dtime = -relbox.MaxEdge.Z / speed.Z; - if ((relbox.MinEdge.X + speed.X * (*dtime) < xsize) && - (relbox.MaxEdge.X + speed.X * (*dtime) > COLL_ZERO) && - (relbox.MinEdge.Y + speed.Y * (*dtime) < ysize) && - (relbox.MaxEdge.Y + speed.Y * (*dtime) > COLL_ZERO)) - return 2; - } - //else if(relbox.MinEdge.Z > zsize) - //{ - // return -1; - //} - } - else if(speed.Z < 0) // Check for collision with Z+ plane - { - if (relbox.MinEdge.Z >= zsize - d) { - *dtime = (zsize - relbox.MinEdge.Z) / speed.Z; - if ((relbox.MinEdge.X + speed.X * (*dtime) < xsize) && - (relbox.MaxEdge.X + speed.X * (*dtime) > COLL_ZERO) && - (relbox.MinEdge.Y + speed.Y * (*dtime) < ysize) && - (relbox.MaxEdge.Y + speed.Y * (*dtime) > COLL_ZERO)) - return 2; + if (speed.Z) { + distance = relbox.MaxEdge.Z - relbox.MinEdge.Z; + *dtime = distance / std::abs(speed.Z); + time = std::max(*dtime, 0.0f); + + 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; + } } - //else if(relbox.MaxEdge.Z < 0) - //{ - // return -1; - //} } - return -1; + return COLLISION_AXIS_NONE; } // Helper function: // Checks if moving the movingbox up by the given distance would hit a ceiling. bool wouldCollideWithCeiling( - const std::vector &staticboxes, + const std::vector &cinfo, const aabb3f &movingbox, f32 y_increase, f32 d) { @@ -168,12 +200,9 @@ bool wouldCollideWithCeiling( assert(y_increase >= 0); // pre-condition - for(std::vector::const_iterator - i = staticboxes.begin(); - i != staticboxes.end(); ++i) - { - const aabb3f& staticbox = *i; - if((movingbox.MaxEdge.Y - d <= staticbox.MinEdge.Y) && + for (const auto &it : cinfo) { + const aabb3f &staticbox = it.box; + if ((movingbox.MaxEdge.Y - d <= staticbox.MinEdge.Y) && (movingbox.MaxEdge.Y + y_increase > staticbox.MinEdge.Y) && (movingbox.MinEdge.X < staticbox.MaxEdge.X) && (movingbox.MaxEdge.X > staticbox.MinEdge.X) && @@ -185,11 +214,11 @@ bool wouldCollideWithCeiling( return false; } -static inline void getNeighborConnectingFace(v3s16 p, INodeDefManager *nodedef, - Map *map, MapNode n, int v, int *neighbors) +static inline void getNeighborConnectingFace(const v3s16 &p, + const NodeDefManager *nodedef, Map *map, MapNode n, int v, int *neighbors) { - MapNode n2 = map->getNodeNoEx(p); - if (nodedef->nodeboxConnects(n, n2)) + MapNode n2 = map->getNode(p); + if (nodedef->nodeboxConnects(n, n2, v)) *neighbors |= v; } @@ -200,30 +229,35 @@ 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(); - //TimeTaker tt("collisionMoveSimple"); - ScopeProfiler sp(g_profiler, "collisionMoveSimple avg", SPT_AVG); + ServerEnvironment *s_env = dynamic_cast(env); + + ScopeProfiler sp(g_profiler, PROFILER_NAME("collisionMoveSimple()"), SPT_AVG); collisionMoveResult result; /* Calculate new velocity */ - if (dtime > 0.5) { + if (dtime > 0.5f) { if (!time_notification_done) { time_notification_done = true; infostream << "collisionMoveSimple: maximum step interval exceeded," " lost movement details!"<getLength() == 0) + // If the object is static, there are no collisions + if (dpos_f == v3f()) return result; // Limit speed for avoiding hangs @@ -231,51 +265,54 @@ 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 cboxes; - std::vector is_unloaded; - std::vector is_step_up; - std::vector is_object; - std::vector bouncy_values; - std::vector node_positions; + std::vector cinfo; { //TimeTaker tt2("collisionMoveSimple collect boxes"); - ScopeProfiler sp(g_profiler, "collisionMoveSimple collect boxes avg", SPT_AVG); + ScopeProfiler sp2(g_profiler, PROFILER_NAME("collisionMoveSimple(): collect boxes"), SPT_AVG); - v3s16 oldpos_i = floatToInt(*pos_f, BS); - v3s16 newpos_i = floatToInt(*pos_f + *speed_f * dtime, BS); - s16 min_x = MYMIN(oldpos_i.X, newpos_i.X) + (box_0.MinEdge.X / BS) - 1; - s16 min_y = MYMIN(oldpos_i.Y, newpos_i.Y) + (box_0.MinEdge.Y / BS) - 1; - s16 min_z = MYMIN(oldpos_i.Z, newpos_i.Z) + (box_0.MinEdge.Z / BS) - 1; - s16 max_x = MYMAX(oldpos_i.X, newpos_i.X) + (box_0.MaxEdge.X / BS) + 1; - s16 max_y = MYMAX(oldpos_i.Y, newpos_i.Y) + (box_0.MaxEdge.Y / BS) + 1; - s16 max_z = MYMAX(oldpos_i.Z, newpos_i.Z) + (box_0.MaxEdge.Z / BS) + 1; + 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 + MYMIN(pos_f->Z, newpos_f.Z) + ); + v3f maxpos_f( + MYMAX(pos_f->X, newpos_f.X), + MYMAX(pos_f->Y, newpos_f.Y), + MYMAX(pos_f->Z, newpos_f.Z) + ); + v3s16 min = floatToInt(minpos_f + box_0.MinEdge, BS) - v3s16(1, 1, 1); + v3s16 max = floatToInt(maxpos_f + box_0.MaxEdge, BS) + v3s16(1, 1, 1); bool any_position_valid = false; - for(s16 x = min_x; x <= max_x; x++) - for(s16 y = min_y; y <= max_y; y++) - for(s16 z = min_z; z <= max_z; z++) - { - v3s16 p(x,y,z); - + v3s16 p; + for (p.X = min.X; p.X <= max.X; p.X++) + for (p.Y = min.Y; p.Y <= max.Y; p.Y++) + for (p.Z = min.Z; p.Z <= max.Z; p.Z++) { bool is_position_valid; - MapNode n = map->getNodeNoEx(p, &is_position_valid); + MapNode n = map->getNode(p, &is_position_valid); - if (is_position_valid) { + if (is_position_valid && n.getContent() != CONTENT_IGNORE) { // Object collides into walkable nodes any_position_valid = true; - INodeDefManager *nodedef = gamedef->getNodeDefManager(); + const NodeDefManager *nodedef = gamedef->getNodeDefManager(); const ContentFeatures &f = nodedef->get(n); - if(f.walkable == false) + + 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 && f.node_box.type == NODEBOX_CONNECTED) { + if (f.drawtype == NDT_NODEBOX && + f.node_box.type == NODEBOX_CONNECTED) { v3s16 p2 = p; p2.Y++; @@ -303,74 +340,76 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, } std::vector nodeboxes; n.getCollisionBoxes(gamedef->ndef(), &nodeboxes, neighbors); - for(std::vector::iterator - i = nodeboxes.begin(); - i != nodeboxes.end(); ++i) - { - aabb3f box = *i; - box.MinEdge += v3f(x, y, z)*BS; - box.MaxEdge += v3f(x, y, z)*BS; - cboxes.push_back(box); - is_unloaded.push_back(false); - is_step_up.push_back(false); - bouncy_values.push_back(n_bouncy_value); - node_positions.push_back(p); - is_object.push_back(false); + + // Calculate float position only once + v3f posf = intToFloat(p, BS); + for (auto box : nodeboxes) { + box.MinEdge += posf; + box.MaxEdge += posf; + cinfo.emplace_back(false, n_bouncy_value, p, box); } - } - else { - // Collide with unloaded nodes + } else { + // Collide with unloaded nodes (position invalid) and loaded + // CONTENT_IGNORE nodes (position valid) aabb3f box = getNodeBox(p, BS); - cboxes.push_back(box); - is_unloaded.push_back(true); - is_step_up.push_back(false); - bouncy_values.push_back(0); - node_positions.push_back(p); - is_object.push_back(false); + cinfo.emplace_back(true, 0, p, box); } } // Do not move if world has not loaded yet, since custom node boxes // are not available for collision detection. - if (!any_position_valid) + // This also intentionally occurs in the case of the object being positioned + // solely on loaded CONTENT_IGNORE nodes, no matter where they come from. + if (!any_position_valid) { + *speed_f = v3f(0, 0, 0); return result; + } } // tt2 if(collideWithObjects) { - ScopeProfiler sp(g_profiler, "collisionMoveSimple objects avg", SPT_AVG); - //TimeTaker tt3("collisionMoveSimple collect object boxes"); - - /* add object boxes to cboxes */ + /* add object boxes to cinfo */ std::vector objects; #ifndef SERVER ClientEnvironment *c_env = dynamic_cast(env); if (c_env != 0) { - f32 distance = speed_f->getLength(); + // Calculate distance by speed, add own extent and 1.5m of tolerance + f32 distance = speed_f->getLength() * dtime + + box_0.getExtent().getLength() + 1.5f * BS; std::vector clientobjects; - c_env->getActiveObjects(*pos_f, distance * 1.5, clientobjects); - for (size_t i=0; i < clientobjects.size(); i++) { - if ((self == 0) || (self != clientobjects[i].obj)) { - objects.push_back((ActiveObject*)clientobjects[i].obj); + c_env->getActiveObjects(*pos_f, distance, clientobjects); + + for (auto &clientobject : clientobjects) { + // Do collide with everything but itself and the parent CAO + if (!self || (self != clientobject.obj && + self != clientobject.obj->getParent())) { + objects.push_back((ActiveObject*) clientobject.obj); } } } else #endif { - ServerEnvironment *s_env = dynamic_cast(env); - if (s_env != 0) { - f32 distance = speed_f->getLength(); - std::vector s_objects; - s_env->getObjectsInsideRadius(s_objects, *pos_f, distance * 1.5); - for (std::vector::iterator iter = s_objects.begin(); iter != s_objects.end(); ++iter) { - ServerActiveObject *current = s_env->getActiveObject(*iter); - if ((self == 0) || (self != current)) { - objects.push_back((ActiveObject*)current); + if (s_env != NULL) { + // Calculate distance by speed, add own extent and 1.5m of tolerance + f32 distance = speed_f->getLength() * dtime + + box_0.getExtent().getLength() + 1.5f * BS; + + // search for objects which are not us, or we are not its parent + // 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 (!obj->isGone() && + (!self || (self != obj && self != obj->getParent()))) { + objects.push_back((ActiveObject *)obj); } - } + return false; + }; + + std::vector s_objects; + s_env->getObjectsInsideRadius(s_objects, *pos_f, distance, include_obj_cb); } } @@ -378,48 +417,36 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, iter != objects.end(); ++iter) { ActiveObject *object = *iter; - if (object != NULL) { + if (object && object->collideWithObjects()) { aabb3f object_collisionbox; - if (object->getCollisionBox(&object_collisionbox) && - object->collideWithObjects()) { - cboxes.push_back(object_collisionbox); - is_unloaded.push_back(false); - is_step_up.push_back(false); - bouncy_values.push_back(0); - node_positions.push_back(v3s16(0,0,0)); - is_object.push_back(true); - } + if (object->getCollisionBox(&object_collisionbox)) + cinfo.emplace_back(object, 0, object_collisionbox); } } +#ifndef SERVER + if (self && c_env) { + LocalPlayer *lplayer = c_env->getLocalPlayer(); + if (lplayer->getParent() == nullptr) { + aabb3f lplayer_collisionbox = lplayer->getCollisionbox(); + v3f lplayer_pos = lplayer->getPosition(); + lplayer_collisionbox.MinEdge += lplayer_pos; + lplayer_collisionbox.MaxEdge += lplayer_pos; + ActiveObject *obj = (ActiveObject*) lplayer->getCAO(); + cinfo.emplace_back(obj, 0, lplayer_collisionbox); + } + } +#endif } //tt3 - assert(cboxes.size() == is_unloaded.size()); // post-condition - assert(cboxes.size() == is_step_up.size()); // post-condition - assert(cboxes.size() == bouncy_values.size()); // post-condition - assert(cboxes.size() == node_positions.size()); // post-condition - assert(cboxes.size() == is_object.size()); // post-condition - /* Collision detection */ - /* - Collision uncertainty radius - Make it a bit larger than the maximum distance of movement - */ - f32 d = pos_max_d * 1.1; - // A fairly large value in here makes moving smoother - //f32 d = 0.15*BS; - - // This should always apply, otherwise there are glitches - assert(d > pos_max_d); // invariant + f32 d = 0.0f; int loopcount = 0; - while(dtime > BS * 1e-10) { - //TimeTaker tt3("collisionMoveSimple dtime loop"); - ScopeProfiler sp(g_profiler, "collisionMoveSimple dtime loop avg", SPT_AVG); - + while(dtime > BS * 1e-10f) { // Avoid infinite loop loopcount++; if (loopcount >= 100) { @@ -431,22 +458,23 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, movingbox.MinEdge += *pos_f; movingbox.MaxEdge += *pos_f; - int nearest_collided = -1; + CollisionAxis nearest_collided = COLLISION_AXIS_NONE; f32 nearest_dtime = dtime; int nearest_boxindex = -1; /* Go through every nodebox, find nearest collision */ - for (u32 boxindex = 0; boxindex < cboxes.size(); boxindex++) { + for (u32 boxindex = 0; boxindex < cinfo.size(); boxindex++) { + const NearbyCollisionInfo &box_info = cinfo[boxindex]; // Ignore if already stepped up this nodebox. - if(is_step_up[boxindex]) + if (box_info.is_step_up) continue; // Find nearest collision of the two boxes (raytracing-like) - f32 dtime_tmp; - int collided = axisAlignedCollision( - cboxes[boxindex], movingbox, *speed_f, d, &dtime_tmp); + f32 dtime_tmp = nearest_dtime; + CollisionAxis collided = axisAlignedCollision(box_info.box, + movingbox, *speed_f, &dtime_tmp); if (collided == -1 || dtime_tmp >= nearest_dtime) continue; @@ -456,89 +484,94 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, nearest_boxindex = boxindex; } - if (nearest_collided == -1) { + 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. - - const aabb3f& cbox = cboxes[nearest_boxindex]; + NearbyCollisionInfo &nearest_info = cinfo[nearest_boxindex]; + const aabb3f& cbox = nearest_info.box; + + //movingbox except moved to the horizontal position it would be after step up + aabb3f stepbox = movingbox; + stepbox.MinEdge.X += speed_f->X * dtime; + stepbox.MinEdge.Z += speed_f->Z * dtime; + stepbox.MaxEdge.X += speed_f->X * dtime; + stepbox.MaxEdge.Z += speed_f->Z * dtime; // Check for stairs. - bool step_up = (nearest_collided != 1) && // must not be Y direction + bool step_up = (nearest_collided != COLLISION_AXIS_Y) && // must not be Y direction (movingbox.MinEdge.Y < cbox.MaxEdge.Y) && (movingbox.MinEdge.Y + stepheight > cbox.MaxEdge.Y) && - (!wouldCollideWithCeiling(cboxes, movingbox, + (!wouldCollideWithCeiling(cinfo, stepbox, cbox.MaxEdge.Y - movingbox.MinEdge.Y, d)); // Get bounce multiplier - bool bouncy = (bouncy_values[nearest_boxindex] >= 1); - float bounce = -(float)bouncy_values[nearest_boxindex] / 100.0; + float bounce = -(float)nearest_info.bouncy / 100.0f; // Move to the point of collision and reduce dtime by nearest_dtime if (nearest_dtime < 0) { - // Handle negative nearest_dtime (can be caused by the d allowance) + // Handle negative nearest_dtime if (!step_up) { - if (nearest_collided == 0) + if (nearest_collided == COLLISION_AXIS_X) pos_f->X += speed_f->X * nearest_dtime; - if (nearest_collided == 1) + if (nearest_collided == COLLISION_AXIS_Y) pos_f->Y += speed_f->Y * nearest_dtime; - if (nearest_collided == 2) + if (nearest_collided == COLLISION_AXIS_Z) 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; } bool is_collision = true; - if (is_unloaded[nearest_boxindex]) + if (nearest_info.is_unloaded) is_collision = false; CollisionInfo info; - if (is_object[nearest_boxindex]) + if (nearest_info.isObject()) info.type = COLLISION_OBJECT; else info.type = COLLISION_NODE; - info.node_p = node_positions[nearest_boxindex]; - info.bouncy = bouncy; + info.node_p = nearest_info.position; + info.object = nearest_info.obj; info.old_speed = *speed_f; + info.plane = nearest_collided; // Set the speed component that caused the collision to zero if (step_up) { // Special case: Handle stairs - is_step_up[nearest_boxindex] = true; + nearest_info.is_step_up = true; is_collision = false; - } else if(nearest_collided == 0) { // X + } else if (nearest_collided == COLLISION_AXIS_X) { if (fabs(speed_f->X) > BS * 3) speed_f->X *= bounce; else speed_f->X = 0; result.collides = true; - result.collides_xz = true; - } - else if(nearest_collided == 1) { // Y - if (fabs(speed_f->Y) > BS * 3) + } else if (nearest_collided == COLLISION_AXIS_Y) { + if(fabs(speed_f->Y) > BS * 3) speed_f->Y *= bounce; else speed_f->Y = 0; result.collides = true; - } else if(nearest_collided == 2) { // Z + } else if (nearest_collided == COLLISION_AXIS_Z) { if (fabs(speed_f->Z) > BS * 3) speed_f->Z *= bounce; else speed_f->Z = 0; result.collides = true; - result.collides_xz = true; } info.new_speed = *speed_f; - if (info.new_speed.getDistanceFrom(info.old_speed) < 0.1 * BS) + if (info.new_speed.getDistanceFrom(info.old_speed) < 0.1f * BS) is_collision = false; if (is_collision) { + info.axis = nearest_collided; result.collisions.push_back(info); } } @@ -550,8 +583,8 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, aabb3f box = box_0; box.MinEdge += *pos_f; box.MaxEdge += *pos_f; - for (u32 boxindex = 0; boxindex < cboxes.size(); boxindex++) { - const aabb3f& cbox = cboxes[boxindex]; + for (const auto &box_info : cinfo) { + const aabb3f &cbox = box_info.box; /* See if the object is touching ground. @@ -559,25 +592,22 @@ collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef, Object touches ground if object's minimum Y is near node's maximum Y and object's X-Z-area overlaps with the node's X-Z-area. - - Use 0.15*BS so that it is easier to get on a node. */ + if (cbox.MaxEdge.X - d > box.MinEdge.X && cbox.MinEdge.X + d < box.MaxEdge.X && cbox.MaxEdge.Z - d > box.MinEdge.Z && cbox.MinEdge.Z + d < box.MaxEdge.Z) { - if (is_step_up[boxindex]) { - pos_f->Y += (cbox.MaxEdge.Y - box.MinEdge.Y); + if (box_info.is_step_up) { + pos_f->Y += cbox.MaxEdge.Y - box.MinEdge.Y; box = box_0; box.MinEdge += *pos_f; box.MaxEdge += *pos_f; } - if (fabs(cbox.MaxEdge.Y - box.MinEdge.Y) < 0.15 * BS) { + if (std::fabs(cbox.MaxEdge.Y - box.MinEdge.Y) < 0.05f) { result.touching_ground = true; - if (is_object[boxindex]) + if (box_info.isObject()) result.standing_on_object = true; - if (is_unloaded[boxindex]) - result.standing_on_unloaded = true; } } }