示例#1
0
collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
		f32 pos_max_d, const aabb3f &box_0,
		f32 stepheight, f32 dtime,
		v3f *pos_f, v3f *speed_f,
		v3f accel_f, ActiveObject *self,
		bool collideWithObjects)
{
	static bool time_notification_done = false;
	Map *map = &env->getMap();
	//TimeTaker tt("collisionMoveSimple");
/*
	ScopeProfiler sp(g_profiler, "collisionMoveSimple avg", SPT_AVG);
*/

	collisionMoveResult result;

	/*
		Calculate new velocity
	*/
	if (dtime > 1) {
		if (!time_notification_done) {
			time_notification_done = true;
			infostream << "collisionMoveSimple: maximum step interval exceeded,"
					" lost movement details!"<<std::endl;
		}
		dtime = 1;
	} else {
		time_notification_done = false;
	}
	*speed_f += accel_f * dtime;

	// If there is no speed, there are no collisions
	if (speed_f->getLength() == 0)
		return result;

	// Limit speed for avoiding hangs
	speed_f->Y = rangelim(speed_f->Y, -1000, 1000);
	speed_f->X = rangelim(speed_f->X, -1000, 1000);
	speed_f->Z = rangelim(speed_f->Z, -1000, 1000);

	/*
		Collect node boxes in movement range
	*/
	std::vector<aabb3f> cboxes;
	std::vector<bool> is_unloaded;
	std::vector<bool> is_step_up;
	std::vector<bool> is_object;
	std::vector<int> bouncy_values;
	std::vector<v3s16> node_positions;
	{
	//TimeTaker tt2("collisionMoveSimple collect boxes");
/*
	ScopeProfiler sp(g_profiler, "collisionMoveSimple collect boxes avg", 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;

	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);

		bool is_position_valid;
		MapNode n = map->getNodeNoEx(p, &is_position_valid);

		if (is_position_valid) {
			// Object collides into walkable nodes

			any_position_valid = true;
			INodeDefManager *nodedef = gamedef->getNodeDefManager();
			const ContentFeatures &f = nodedef->get(n);
			if(f.walkable == false)
				continue;
			int n_bouncy_value = itemgroup_get(f.groups, "bouncy");

			int neighbors = 0;
			if (f.drawtype == NDT_NODEBOX && f.node_box.type == NODEBOX_CONNECTED) {
				v3s16 p2 = p;

				p2.Y++;
				getNeighborConnectingFace(p2, nodedef, map, n, 1, &neighbors);

				p2 = p;
				p2.Y--;
				getNeighborConnectingFace(p2, nodedef, map, n, 2, &neighbors);

				p2 = p;
				p2.Z--;
				getNeighborConnectingFace(p2, nodedef, map, n, 4, &neighbors);

				p2 = p;
				p2.X--;
				getNeighborConnectingFace(p2, nodedef, map, n, 8, &neighbors);

				p2 = p;
				p2.Z++;
				getNeighborConnectingFace(p2, nodedef, map, n, 16, &neighbors);

				p2 = p;
				p2.X++;
				getNeighborConnectingFace(p2, nodedef, map, n, 32, &neighbors);
			}
			std::vector<aabb3f> nodeboxes;
			n.getCollisionBoxes(gamedef->ndef(), &nodeboxes, neighbors);
			for(std::vector<aabb3f>::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);
			}
		}
		else {
			// Collide with unloaded nodes
			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);
		}
	}

	// Do not move if world has not loaded yet, since custom node boxes
	// are not available for collision detection.
	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 */

		std::vector<ActiveObject*> objects;
#ifndef SERVER
		ClientEnvironment *c_env = dynamic_cast<ClientEnvironment*>(env);
		if (c_env != 0) {
			f32 distance = speed_f->getLength();
			std::vector<DistanceSortedActiveObject> 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);
				}
			}
		}
		else
#endif
		{
			ServerEnvironment *s_env = dynamic_cast<ServerEnvironment*>(env);
			if (s_env != 0) {
				f32 distance = speed_f->getLength();
				std::vector<u16> s_objects;
				s_env->getObjectsInsideRadius(s_objects, *pos_f, distance * 1.5);
				for (std::vector<u16>::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);
					}
				}
			}
		}

		for (std::vector<ActiveObject*>::const_iterator iter = objects.begin();
				iter != objects.end(); ++iter) {
			ActiveObject *object = *iter;

			if (object != NULL) {
				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);
				}
			}
		}
	} //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
	if(!(d > pos_max_d))
		return result;

	int loopcount = 0;

	while(dtime > BS * 1e-10) {
		//TimeTaker tt3("collisionMoveSimple dtime loop");
/*
        	ScopeProfiler sp(g_profiler, "collisionMoveSimple dtime loop avg", SPT_AVG);
*/

		// Avoid infinite loop
		loopcount++;
		if (loopcount >= 100) {
			warningstream << "collisionMoveSimple: Loop count exceeded, aborting to avoid infiniite loop" << std::endl;
			break;
		}

		aabb3f movingbox = box_0;
		movingbox.MinEdge += *pos_f;
		movingbox.MaxEdge += *pos_f;

		int nearest_collided = -1;
		f32 nearest_dtime = dtime;
		int nearest_boxindex = -1;

		/*
			Go through every nodebox, find nearest collision
		*/
		for (u32 boxindex = 0; boxindex < cboxes.size(); boxindex++) {
			// Ignore if already stepped up this nodebox.
			if(is_step_up[boxindex])
				continue;

			// Find nearest collision of the two boxes (raytracing-like)
			f32 dtime_tmp;
			int collided = axisAlignedCollision(
					cboxes[boxindex], movingbox, *speed_f, d, &dtime_tmp);

			if (collided == -1 || dtime_tmp >= nearest_dtime)
				continue;

			nearest_dtime = dtime_tmp;
			nearest_collided = collided;
			nearest_boxindex = boxindex;
		}

		if (nearest_collided == -1) {
			// No collision with any collision box.
			*pos_f += *speed_f * dtime;
			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];
			// Check for stairs.
			bool step_up = (nearest_collided != 1) && // must not be Y direction
					(movingbox.MinEdge.Y < cbox.MaxEdge.Y) &&
					(movingbox.MinEdge.Y + stepheight > cbox.MaxEdge.Y) &&
					(!wouldCollideWithCeiling(cboxes, movingbox,
							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;

			// 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)
				if (!step_up) {
					if (nearest_collided == 0)
						pos_f->X += speed_f->X * nearest_dtime;
					if (nearest_collided == 1)
						pos_f->Y += speed_f->Y * nearest_dtime;
					if (nearest_collided == 2)
						pos_f->Z += speed_f->Z * nearest_dtime;
				}
			} else {
				*pos_f += *speed_f * nearest_dtime;
				dtime -= nearest_dtime;
			}

			bool is_collision = true;
			if (is_unloaded[nearest_boxindex])
				is_collision = false;

			CollisionInfo info;
			if (is_object[nearest_boxindex])
				info.type = COLLISION_OBJECT;
			else
				info.type = COLLISION_NODE;

			info.node_p = node_positions[nearest_boxindex];
			info.bouncy = bouncy;
			info.old_speed = *speed_f;

			// Set the speed component that caused the collision to zero
			if (step_up) {
				// Special case: Handle stairs
				is_step_up[nearest_boxindex] = true;
				is_collision = false;
			} else if(nearest_collided == 0) { // 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)
					speed_f->Y *= bounce;
				else
					speed_f->Y = 0;
				result.collides = true;
			} else if(nearest_collided == 2) { // 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)
				is_collision = false;

			if (is_collision) {
				result.collisions.push_back(info);
			}
		}
	}

	/*
		Final touches: Check if standing on ground, step up stairs.
	*/
	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];

		/*
			See if the object is touching ground.

			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);
				box = box_0;
				box.MinEdge += *pos_f;
				box.MaxEdge += *pos_f;
			}
			if (fabs(cbox.MaxEdge.Y - box.MinEdge.Y) < 0.15 * BS) {
				result.touching_ground = true;

				if (is_object[boxindex])
					result.standing_on_object = true;
				if (is_unloaded[boxindex])
					result.standing_on_unloaded = true;
			}
		}
	}

	return result;
}
示例#2
0
void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d,
		core::list<CollisionInfo> *collision_info)
{
	v3f position = getPosition();
	v3f oldpos = position;
	v3s16 oldpos_i = floatToInt(oldpos, BS);

	v3f old_speed = m_speed;

	/*std::cout<<"oldpos_i=("<<oldpos_i.X<<","<<oldpos_i.Y<<","
			<<oldpos_i.Z<<")"<<std::endl;*/

	/*
		Calculate new position
	*/
	position += m_speed * dtime;
	
	// Skip collision detection if a special movement mode is used
	bool free_move = g_settings->getBool("free_move");
	if(free_move)
	{
		setPosition(position);
		return;
	}

	/*
		Collision detection
	*/
	
	// Player position in nodes
	v3s16 pos_i = floatToInt(position, BS);
	
	/*
		Check if player is in water (the oscillating value)
	*/
	try{
		// If in water, the threshold of coming out is at higher y
		if(in_water)
		{
			v3s16 pp = floatToInt(position + v3f(0,BS*0.1,0), BS);
			in_water = content_liquid(map.getNode(pp).getContent());
		}
		// If not in water, the threshold of going in is at lower y
		else
		{
			v3s16 pp = floatToInt(position + v3f(0,BS*0.5,0), BS);
			in_water = content_liquid(map.getNode(pp).getContent());
		}
	}
	catch(InvalidPositionException &e)
	{
		in_water = false;
	}

	/*
		Check if player is in water (the stable value)
	*/
	try{
		v3s16 pp = floatToInt(position + v3f(0,0,0), BS);
		in_water_stable = content_liquid(map.getNode(pp).getContent());
	}
	catch(InvalidPositionException &e)
	{
		in_water_stable = false;
	}

	/*
	        Check if player is climbing
	*/

	try {
	        v3s16 pp = floatToInt(position + v3f(0,0.5*BS,0), BS);
		v3s16 pp2 = floatToInt(position + v3f(0,-0.2*BS,0), BS);
		is_climbing = ((content_features(map.getNode(pp).getContent()).climbable ||
				content_features(map.getNode(pp2).getContent()).climbable) && !free_move);
	}
	catch(InvalidPositionException &e)
	{
	        is_climbing = false;
	}

	/*
		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);

	float player_radius = BS*0.35;
	float player_height = BS*1.7;
	
	// Maximum distance over border for sneaking
	f32 sneak_max = BS*0.4;

	/*
		If sneaking, player has larger collision radius to keep from
		falling
	*/
	/*if(control.sneak)
		player_radius = sneak_max + d*1.1;*/
	
	/*
		If sneaking, keep in range from the last walked node and don't
		fall off from it
	*/
	if(control.sneak && m_sneak_node_exists)
	{
		f32 maxd = 0.5*BS + sneak_max;
		v3f lwn_f = intToFloat(m_sneak_node, BS);
		position.X = rangelim(position.X, lwn_f.X-maxd, lwn_f.X+maxd);
		position.Z = rangelim(position.Z, lwn_f.Z-maxd, lwn_f.Z+maxd);
		
		f32 min_y = lwn_f.Y + 0.5*BS;
		if(position.Y < min_y)
		{
			position.Y = min_y;

			//v3f old_speed = m_speed;

			if(m_speed.Y < 0)
				m_speed.Y = 0;

			/*if(collision_info)
			{
				// Report fall collision
				if(old_speed.Y < m_speed.Y - 0.1)
				{
					CollisionInfo info;
					info.t = COLLISION_FALL;
					info.speed = m_speed.Y - old_speed.Y;
					collision_info->push_back(info);
				}
			}*/
		}
	}

	/*
		Calculate player collision box (new and old)
	*/
	core::aabbox3d<f32> playerbox(
		position.X - player_radius,
		position.Y - 0.0,
		position.Z - player_radius,
		position.X + player_radius,
		position.Y + player_height,
		position.Z + player_radius
	);
	core::aabbox3d<f32> playerbox_old(
		oldpos.X - player_radius,
		oldpos.Y - 0.0,
		oldpos.Z - player_radius,
		oldpos.X + player_radius,
		oldpos.Y + player_height,
		oldpos.Z + player_radius
	);

	/*
		If the player's feet touch the topside of any node, this is
		set to true.

		Player is allowed to jump when this is true.
	*/
	touching_ground = false;

	/*std::cout<<"Checking collisions for ("
			<<oldpos_i.X<<","<<oldpos_i.Y<<","<<oldpos_i.Z
			<<") -> ("
			<<pos_i.X<<","<<pos_i.Y<<","<<pos_i.Z
			<<"):"<<std::endl;*/
	
	bool standing_on_unloaded = false;
	
	/*
		Go through every node around the player
	*/
	for(s16 y = oldpos_i.Y - 1; y <= oldpos_i.Y + 2; y++)
	for(s16 z = oldpos_i.Z - 1; z <= oldpos_i.Z + 1; z++)
	for(s16 x = oldpos_i.X - 1; x <= oldpos_i.X + 1; x++)
	{
		bool is_unloaded = false;
		try{
			// Player collides into walkable nodes
			if(content_walkable(map.getNode(v3s16(x,y,z)).getContent()) == false)
				continue;
		}
		catch(InvalidPositionException &e)
		{
			is_unloaded = true;
			// Doing nothing here will block the player from
			// walking over map borders
		}

		core::aabbox3d<f32> nodebox = getNodeBox(v3s16(x,y,z), BS);
		
		/*
			See if the player is touching ground.

			Player touches ground if player's minimum Y is near node's
			maximum Y and player'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(
				//fabs(nodebox.MaxEdge.Y-playerbox.MinEdge.Y) < d
				fabs(nodebox.MaxEdge.Y-playerbox.MinEdge.Y) < 0.15*BS
				&& nodebox.MaxEdge.X-d > playerbox.MinEdge.X
				&& nodebox.MinEdge.X+d < playerbox.MaxEdge.X
				&& nodebox.MaxEdge.Z-d > playerbox.MinEdge.Z
				&& nodebox.MinEdge.Z+d < playerbox.MaxEdge.Z
		){
			touching_ground = true;
			if(is_unloaded)
				standing_on_unloaded = true;
		}
		
		// If player doesn't intersect with node, ignore node.
		if(playerbox.intersectsWithBox(nodebox) == false)
			continue;
		
		/*
			Go through every axis
		*/
		v3f dirs[3] = {
			v3f(0,0,1), // back-front
			v3f(0,1,0), // top-bottom
			v3f(1,0,0), // right-left
		};
		for(u16 i=0; i<3; i++)
		{
			/*
				Calculate values along the axis
			*/
			f32 nodemax = nodebox.MaxEdge.dotProduct(dirs[i]);
			f32 nodemin = nodebox.MinEdge.dotProduct(dirs[i]);
			f32 playermax = playerbox.MaxEdge.dotProduct(dirs[i]);
			f32 playermin = playerbox.MinEdge.dotProduct(dirs[i]);
			f32 playermax_old = playerbox_old.MaxEdge.dotProduct(dirs[i]);
			f32 playermin_old = playerbox_old.MinEdge.dotProduct(dirs[i]);
			
			/*
				Check collision for the axis.
				Collision happens when player is going through a surface.
			*/
			/*f32 neg_d = d;
			f32 pos_d = d;
			// Make it easier to get on top of a node
			if(i == 1)
				neg_d = 0.15*BS;
			bool negative_axis_collides =
				(nodemax > playermin && nodemax <= playermin_old + neg_d
					&& m_speed.dotProduct(dirs[i]) < 0);
			bool positive_axis_collides =
				(nodemin < playermax && nodemin >= playermax_old - pos_d
					&& m_speed.dotProduct(dirs[i]) > 0);*/
			bool negative_axis_collides =
				(nodemax > playermin && nodemax <= playermin_old + d
					&& m_speed.dotProduct(dirs[i]) < 0);
			bool positive_axis_collides =
				(nodemin < playermax && nodemin >= playermax_old - d
					&& m_speed.dotProduct(dirs[i]) > 0);
			bool main_axis_collides =
					negative_axis_collides || positive_axis_collides;
			
			/*
				Check overlap of player and node in other axes
			*/
			bool other_axes_overlap = true;
			for(u16 j=0; j<3; j++)
			{
				if(j == i)
					continue;
				f32 nodemax = nodebox.MaxEdge.dotProduct(dirs[j]);
				f32 nodemin = nodebox.MinEdge.dotProduct(dirs[j]);
				f32 playermax = playerbox.MaxEdge.dotProduct(dirs[j]);
				f32 playermin = playerbox.MinEdge.dotProduct(dirs[j]);
				if(!(nodemax - d > playermin && nodemin + d < playermax))
				{
					other_axes_overlap = false;
					break;
				}
			}
			
			/*
				If this is a collision, revert the position in the main
				direction.
			*/
			if(other_axes_overlap && main_axis_collides)
			{
				//v3f old_speed = m_speed;

				m_speed -= m_speed.dotProduct(dirs[i]) * dirs[i];
				position -= position.dotProduct(dirs[i]) * dirs[i];
				position += oldpos.dotProduct(dirs[i]) * dirs[i];
				
				/*if(collision_info)
				{
					// Report fall collision
					if(old_speed.Y < m_speed.Y - 0.1)
					{
						CollisionInfo info;
						info.t = COLLISION_FALL;
						info.speed = m_speed.Y - old_speed.Y;
						collision_info->push_back(info);
					}
				}*/
			}
		
		}
	} // xyz

	/*
		Check the nodes under the player to see from which node the
		player is sneaking from, if any.
	*/
	{
		v3s16 pos_i_bottom = floatToInt(position - v3f(0,BS/2,0), BS);
		v2f player_p2df(position.X, position.Z);
		f32 min_distance_f = 100000.0*BS;
		// If already seeking from some node, compare to it.
		/*if(m_sneak_node_exists)
		{
			v3f sneaknode_pf = intToFloat(m_sneak_node, BS);
			v2f sneaknode_p2df(sneaknode_pf.X, sneaknode_pf.Z);
			f32 d_horiz_f = player_p2df.getDistanceFrom(sneaknode_p2df);
			f32 d_vert_f = fabs(sneaknode_pf.Y + BS*0.5 - position.Y);
			// Ignore if player is not on the same level (likely dropped)
			if(d_vert_f < 0.15*BS)
				min_distance_f = d_horiz_f;
		}*/
		v3s16 new_sneak_node = m_sneak_node;
		for(s16 x=-1; x<=1; x++)
		for(s16 z=-1; z<=1; z++)
		{
			v3s16 p = pos_i_bottom + v3s16(x,0,z);
			v3f pf = intToFloat(p, BS);
			v2f node_p2df(pf.X, pf.Z);
			f32 distance_f = player_p2df.getDistanceFrom(node_p2df);
			f32 max_axis_distance_f = MYMAX(
					fabs(player_p2df.X-node_p2df.X),
					fabs(player_p2df.Y-node_p2df.Y));
					
			if(distance_f > min_distance_f ||
					max_axis_distance_f > 0.5*BS + sneak_max + 0.1*BS)
				continue;

			try{
				// The node to be sneaked on has to be walkable
				if(content_walkable(map.getNode(p).getContent()) == false)
					continue;
				// And the node above it has to be nonwalkable
				if(content_walkable(map.getNode(p+v3s16(0,1,0)).getContent()) == true)
					continue;
			}
			catch(InvalidPositionException &e)
			{
				continue;
			}

			min_distance_f = distance_f;
			new_sneak_node = p;
		}
		
		bool sneak_node_found = (min_distance_f < 100000.0*BS*0.9);
		
		if(control.sneak && m_sneak_node_exists)
		{
			if(sneak_node_found)
				m_sneak_node = new_sneak_node;
		}
		else
		{
			m_sneak_node = new_sneak_node;
			m_sneak_node_exists = sneak_node_found;
		}

		/*
			If sneaking, the player's collision box can be in air, so
			this has to be set explicitly
		*/
		if(sneak_node_found && control.sneak)
			touching_ground = true;
	}
	
	/*
		Set new position
	*/
	setPosition(position);
	
	/*
		Report collisions
	*/
	if(collision_info)
	{
		// Report fall collision
		if(old_speed.Y < m_speed.Y - 0.1 && !standing_on_unloaded)
		{
			CollisionInfo info;
			info.t = COLLISION_FALL;
			info.speed = m_speed.Y - old_speed.Y;
			collision_info->push_back(info);
		}
	}
}
示例#3
0
collisionMoveResult collisionMoveSimple(Environment *env, IGameDef *gamedef,
		f32 pos_max_d, const aabb3f &box_0,
		f32 stepheight, f32 dtime,
		v3f *pos_f, v3f *speed_f,
		v3f accel_f, ActiveObject *self,
		bool collideWithObjects)
{
	static bool time_notification_done = false;
	Map *map = &env->getMap();
	//TimeTaker tt("collisionMoveSimple");
	ScopeProfiler sp(g_profiler, "collisionMoveSimple avg", SPT_AVG);

	collisionMoveResult result;

	/*
		Calculate new velocity
	*/
	if (dtime > 0.5f) {
		if (!time_notification_done) {
			time_notification_done = true;
			infostream << "collisionMoveSimple: maximum step interval exceeded,"
					" lost movement details!"<<std::endl;
		}
		dtime = 0.5f;
	} else {
		time_notification_done = false;
	}
	*speed_f += accel_f * dtime;

	// If there is no speed, there are no collisions
	if (speed_f->getLength() == 0)
		return result;

	// Limit speed for avoiding hangs
	speed_f->Y = rangelim(speed_f->Y, -5000, 5000);
	speed_f->X = rangelim(speed_f->X, -5000, 5000);
	speed_f->Z = rangelim(speed_f->Z, -5000, 5000);

	/*
		Collect node boxes in movement range
	*/
	std::vector<NearbyCollisionInfo> cinfo;
	{
	//TimeTaker tt2("collisionMoveSimple collect boxes");
	ScopeProfiler sp2(g_profiler, "collisionMoveSimple collect boxes avg", 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
		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;

	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);

		if (is_position_valid && n.getContent() != CONTENT_IGNORE) {
			// Object collides into walkable nodes

			any_position_valid = true;
			const NodeDefManager *nodedef = gamedef->getNodeDefManager();
			const ContentFeatures &f = nodedef->get(n);

			if (!f.walkable)
				continue;

			int n_bouncy_value = itemgroup_get(f.groups, "bouncy");

			int neighbors = 0;
			if (f.drawtype == NDT_NODEBOX &&
				f.node_box.type == NODEBOX_CONNECTED) {
				v3s16 p2 = p;

				p2.Y++;
				getNeighborConnectingFace(p2, nodedef, map, n, 1, &neighbors);

				p2 = p;
				p2.Y--;
				getNeighborConnectingFace(p2, nodedef, map, n, 2, &neighbors);

				p2 = p;
				p2.Z--;
				getNeighborConnectingFace(p2, nodedef, map, n, 4, &neighbors);

				p2 = p;
				p2.X--;
				getNeighborConnectingFace(p2, nodedef, map, n, 8, &neighbors);

				p2 = p;
				p2.Z++;
				getNeighborConnectingFace(p2, nodedef, map, n, 16, &neighbors);

				p2 = p;
				p2.X++;
				getNeighborConnectingFace(p2, nodedef, map, n, 32, &neighbors);
			}
			std::vector<aabb3f> nodeboxes;
			n.getCollisionBoxes(gamedef->ndef(), &nodeboxes, neighbors);

			// Calculate float position only once
			v3f posf = intToFloat(p, BS);
			for (auto box : nodeboxes) {
				box.MinEdge += posf;
				box.MaxEdge += posf;
				cinfo.emplace_back(false, 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);
		}
	}

	// Do not move if world has not loaded yet, since custom node boxes
	// are not available for collision detection.
	// 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 sp2(g_profiler, "collisionMoveSimple objects avg", SPT_AVG);
		//TimeTaker tt3("collisionMoveSimple collect object boxes");

		/* add object boxes to cinfo */

		std::vector<ActiveObject*> objects;
#ifndef SERVER
		ClientEnvironment *c_env = dynamic_cast<ClientEnvironment*>(env);
		if (c_env != 0) {
			f32 distance = speed_f->getLength();
			std::vector<DistanceSortedActiveObject> clientobjects;
			c_env->getActiveObjects(*pos_f, distance * 1.5f, clientobjects);
			for (auto &clientobject : clientobjects) {
				if (!self || (self != clientobject.obj)) {
					objects.push_back((ActiveObject*) clientobject.obj);
				}
			}
		}
		else
#endif
		{
			ServerEnvironment *s_env = dynamic_cast<ServerEnvironment*>(env);
			if (s_env != NULL) {
				f32 distance = speed_f->getLength();
				std::vector<u16> s_objects;
				s_env->getObjectsInsideRadius(s_objects, *pos_f, distance * 1.5f);
				for (u16 obj_id : s_objects) {
					ServerActiveObject *current = s_env->getActiveObject(obj_id);
					if (!self || (self != current)) {
						objects.push_back((ActiveObject*)current);
					}
				}
			}
		}

		for (std::vector<ActiveObject*>::const_iterator iter = objects.begin();
				iter != objects.end(); ++iter) {
			ActiveObject *object = *iter;

			if (object) {
				aabb3f object_collisionbox;
				if (object->getCollisionBox(&object_collisionbox) &&
						object->collideWithObjects()) {
					cinfo.emplace_back(false, true, 0, v3s16(), object_collisionbox);
				}
			}
		}
	} //tt3

	/*
		Collision detection
	*/

	/*
		Collision uncertainty radius
		Make it a bit larger than the maximum distance of movement
	*/
	f32 d = pos_max_d * 1.1f;
	// 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

	int loopcount = 0;

	while(dtime > BS * 1e-10f) {
		//TimeTaker tt3("collisionMoveSimple dtime loop");
        	ScopeProfiler sp2(g_profiler, "collisionMoveSimple dtime loop avg", SPT_AVG);

		// Avoid infinite loop
		loopcount++;
		if (loopcount >= 100) {
			warningstream << "collisionMoveSimple: Loop count exceeded, aborting to avoid infiniite loop" << std::endl;
			break;
		}

		aabb3f movingbox = box_0;
		movingbox.MinEdge += *pos_f;
		movingbox.MaxEdge += *pos_f;

		int nearest_collided = -1;
		f32 nearest_dtime = dtime;
		int nearest_boxindex = -1;

		/*
			Go through every nodebox, find nearest collision
		*/
		for (u32 boxindex = 0; boxindex < cinfo.size(); boxindex++) {
			const NearbyCollisionInfo &box_info = cinfo[boxindex];
			// Ignore if already stepped up this nodebox.
			if (box_info.is_step_up)
				continue;

			// Find nearest collision of the two boxes (raytracing-like)
			f32 dtime_tmp;
			int collided = axisAlignedCollision(box_info.box,
					movingbox, *speed_f, d, &dtime_tmp);

			if (collided == -1 || dtime_tmp >= nearest_dtime)
				continue;

			nearest_dtime = dtime_tmp;
			nearest_collided = collided;
			nearest_boxindex = boxindex;
		}

		if (nearest_collided == -1) {
			// No collision with any collision box.
			*pos_f += *speed_f * dtime;
			dtime = 0;  // Set to 0 to avoid "infinite" loop due to small FP numbers
		} else {
			// Otherwise, a collision occurred.
			NearbyCollisionInfo &nearest_info = cinfo[nearest_boxindex];
			const aabb3f& cbox = nearest_info.box;
			// Check for stairs.
			bool step_up = (nearest_collided != 1) && // must not be Y direction
					(movingbox.MinEdge.Y < cbox.MaxEdge.Y) &&
					(movingbox.MinEdge.Y + stepheight > cbox.MaxEdge.Y) &&
					(!wouldCollideWithCeiling(cinfo, movingbox,
							cbox.MaxEdge.Y - movingbox.MinEdge.Y,
							d));

			// Get bounce multiplier
			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)
				if (!step_up) {
					if (nearest_collided == 0)
						pos_f->X += speed_f->X * nearest_dtime;
					if (nearest_collided == 1)
						pos_f->Y += speed_f->Y * nearest_dtime;
					if (nearest_collided == 2)
						pos_f->Z += speed_f->Z * nearest_dtime;
				}
			} else {
				*pos_f += *speed_f * nearest_dtime;
				dtime -= nearest_dtime;
			}

			bool is_collision = true;
			if (nearest_info.is_unloaded)
				is_collision = false;

			CollisionInfo info;
			if (nearest_info.is_object)
				info.type = COLLISION_OBJECT;
			else
				info.type = COLLISION_NODE;

			info.node_p = nearest_info.position;
			info.old_speed = *speed_f;

			// Set the speed component that caused the collision to zero
			if (step_up) {
				// Special case: Handle stairs
				nearest_info.is_step_up = true;
				is_collision = false;
			} else if (nearest_collided == 0) { // X
				if (fabs(speed_f->X) > BS * 3)
					speed_f->X *= bounce;
				else
					speed_f->X = 0;
				result.collides = true;
			} else if (nearest_collided == 1) { // 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
				if (fabs(speed_f->Z) > BS * 3)
					speed_f->Z *= bounce;
				else
					speed_f->Z = 0;
				result.collides = true;
			}

			info.new_speed = *speed_f;
			if (info.new_speed.getDistanceFrom(info.old_speed) < 0.1f * BS)
				is_collision = false;

			if (is_collision) {
				result.collisions.push_back(info);
			}
		}
	}

	/*
		Final touches: Check if standing on ground, step up stairs.
	*/
	aabb3f box = box_0;
	box.MinEdge += *pos_f;
	box.MaxEdge += *pos_f;
	for (const auto &box_info : cinfo) {
		const aabb3f &cbox = box_info.box;

		/*
			See if the object is touching ground.

			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 (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 (std::fabs(cbox.MaxEdge.Y - box.MinEdge.Y) < 0.15f * BS) {
				result.touching_ground = true;

				if (box_info.is_object)
					result.standing_on_object = true;
			}
		}
	}

	return result;
}
示例#4
0
void MovingObject::move(float dtime, v3f acceleration)
{
	DSTACKF("%s: typeid=%i, pos=(%f,%f,%f), speed=(%f,%f,%f)"
			", dtime=%f, acc=(%f,%f,%f)",
			__FUNCTION_NAME,
			getTypeId(),
			m_pos.X, m_pos.Y, m_pos.Z,
			m_speed.X, m_speed.Y, m_speed.Z,
			dtime,
			acceleration.X, acceleration.Y, acceleration.Z
			);
	
	v3s16 oldpos_i = floatToInt(m_pos, BS);
	
	if(m_block->isValidPosition(oldpos_i) == false)
	{
		// Should have wrapped, cancelling further movement.
		return;
	}

	// No collisions if there is no collision box
	if(m_collision_box == NULL)
	{
		m_speed += dtime * acceleration;
		m_pos += m_speed * dtime;
		return;
	}
	
	// Set insane speed to zero
	// Otherwise there will be divides by zero and other silly stuff
	if(m_speed.getLength() > 1000.0*BS)
		m_speed = v3f(0,0,0);
		
	// Limit speed to a reasonable value
	float speed_limit = 20.0*BS;
	if(m_speed.getLength() > speed_limit)
		m_speed = m_speed * (speed_limit / m_speed.getLength());

	v3f position = m_pos;
	v3f oldpos = position;

	/*std::cout<<"oldpos_i=("<<oldpos_i.X<<","<<oldpos_i.Y<<","
			<<oldpos_i.Z<<")"<<std::endl;*/

	// Maximum time increment (for collision detection etc)
	// Allow 0.1 blocks per increment
	// time = distance / speed
	// NOTE: In the loop below collisions are detected at 0.15*BS radius
	float speedlength = m_speed.getLength();
	f32 dtime_max_increment;
	if(fabs(speedlength) > 0.001)
		dtime_max_increment = 0.05*BS / speedlength;
	else
		dtime_max_increment = 0.5;
	
	m_touching_ground = false;
		
	u32 loopcount = 0;
	do
	{
		loopcount++;

		f32 dtime_part;
		if(dtime > dtime_max_increment)
			dtime_part = dtime_max_increment;
		else
			dtime_part = dtime;
		dtime -= dtime_part;

		// Begin of dtime limited code
		
		m_speed += acceleration * dtime_part;
		position += m_speed * dtime_part;

		/*
			Collision detection
		*/
		
		v3s16 pos_i = floatToInt(position, BS);
		
		// The loop length is limited to the object moving a distance
		f32 d = (float)BS * 0.15;

		core::aabbox3d<f32> objectbox(
				m_collision_box->MinEdge + position,
				m_collision_box->MaxEdge + position
		);
		
		core::aabbox3d<f32> objectbox_old(
				m_collision_box->MinEdge + oldpos,
				m_collision_box->MaxEdge + oldpos
		);
		
		//TODO: Get these ranges from somewhere
		for(s16 y = oldpos_i.Y - 1; y <= oldpos_i.Y + 2; y++)
		for(s16 z = oldpos_i.Z - 1; z <= oldpos_i.Z + 1; z++)
		for(s16 x = oldpos_i.X - 1; x <= oldpos_i.X + 1; x++)
		{
			try{
				MapNode n = m_block->getNodeParent(v3s16(x,y,z));
				if(content_features(n).walkable == false)
					continue;
			}
			catch(InvalidPositionException &e)
			{
				// Doing nothing here will block the object from
				// walking over map borders
			}

			core::aabbox3d<f32> nodebox = getNodeBox(v3s16(x,y,z), BS);
			
			// See if the object is touching ground
			if(
					fabs(nodebox.MaxEdge.Y-objectbox.MinEdge.Y) < d
					&& nodebox.MaxEdge.X-d > objectbox.MinEdge.X
					&& nodebox.MinEdge.X+d < objectbox.MaxEdge.X
					&& nodebox.MaxEdge.Z-d > objectbox.MinEdge.Z
					&& nodebox.MinEdge.Z+d < objectbox.MaxEdge.Z
			){
				m_touching_ground = true;
			}
			
			if(objectbox.intersectsWithBox(nodebox))
			{
					
		v3f dirs[3] = {
			v3f(0,0,1), // back
			v3f(0,1,0), // top
			v3f(1,0,0), // right
		};
		for(u16 i=0; i<3; i++)
		{
			f32 nodemax = nodebox.MaxEdge.dotProduct(dirs[i]);
			f32 nodemin = nodebox.MinEdge.dotProduct(dirs[i]);
			f32 playermax = objectbox.MaxEdge.dotProduct(dirs[i]);
			f32 playermin = objectbox.MinEdge.dotProduct(dirs[i]);
			f32 playermax_old = objectbox_old.MaxEdge.dotProduct(dirs[i]);
			f32 playermin_old = objectbox_old.MinEdge.dotProduct(dirs[i]);

			bool main_edge_collides = 
				((nodemax > playermin && nodemax <= playermin_old + d
					&& m_speed.dotProduct(dirs[i]) < 0)
				||
				(nodemin < playermax && nodemin >= playermax_old - d
					&& m_speed.dotProduct(dirs[i]) > 0));

			bool other_edges_collide = true;
			for(u16 j=0; j<3; j++)
			{
				if(j == i)
					continue;
				f32 nodemax = nodebox.MaxEdge.dotProduct(dirs[j]);
				f32 nodemin = nodebox.MinEdge.dotProduct(dirs[j]);
				f32 playermax = objectbox.MaxEdge.dotProduct(dirs[j]);
				f32 playermin = objectbox.MinEdge.dotProduct(dirs[j]);
				if(!(nodemax - d > playermin && nodemin + d < playermax))
				{
					other_edges_collide = false;
					break;
				}
			}
			
			if(main_edge_collides && other_edges_collide)
			{
				m_speed -= m_speed.dotProduct(dirs[i]) * dirs[i];
				position -= position.dotProduct(dirs[i]) * dirs[i];
				position += oldpos.dotProduct(dirs[i]) * dirs[i];
			}
		
		}
		
			} // if(objectbox.intersectsWithBox(nodebox))
		} // for y

	} // End of dtime limited loop
	while(dtime > 0.001);

	m_pos = position;
}
示例#5
0
collisionMoveResult collisionMoveSimple(Map *map, IGameDef *gamedef,
		f32 pos_max_d, const core::aabbox3d<f32> &box_0,
		f32 dtime, v3f &pos_f, v3f &speed_f)
{
	collisionMoveResult result;

	v3f oldpos_f = pos_f;
	v3s16 oldpos_i = floatToInt(oldpos_f, BS);

	/*
		Calculate new position
	*/
	pos_f += speed_f * dtime;

	/*
		Collision detection
	*/
	
	// position in nodes
	v3s16 pos_i = floatToInt(pos_f, BS);
	
	/*
		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);
	
	/*
		Calculate collision box
	*/
	core::aabbox3d<f32> box = box_0;
	box.MaxEdge += pos_f;
	box.MinEdge += pos_f;
	core::aabbox3d<f32> oldbox = box_0;
	oldbox.MaxEdge += oldpos_f;
	oldbox.MinEdge += oldpos_f;

	/*
		If the object lies on a walkable node, this is set to true.
	*/
	result.touching_ground = false;
	
	/*
		Go through every node around the object
	*/
	s16 min_x = (box_0.MinEdge.X / BS) - 2;
	s16 min_y = (box_0.MinEdge.Y / BS) - 2;
	s16 min_z = (box_0.MinEdge.Z / BS) - 2;
	s16 max_x = (box_0.MaxEdge.X / BS) + 1;
	s16 max_y = (box_0.MaxEdge.Y / BS) + 1;
	s16 max_z = (box_0.MaxEdge.Z / BS) + 1;
	for(s16 y = oldpos_i.Y + min_y; y <= oldpos_i.Y + max_y; y++)
	for(s16 z = oldpos_i.Z + min_z; z <= oldpos_i.Z + max_z; z++)
	for(s16 x = oldpos_i.X + min_x; x <= oldpos_i.X + max_x; x++)
	{
		try{
			// Object collides into walkable nodes
			MapNode n = map->getNode(v3s16(x,y,z));
			if(gamedef->getNodeDefManager()->get(n).walkable == false)
				continue;
		}
		catch(InvalidPositionException &e)
		{
			// Doing nothing here will block the object from
			// walking over map borders
		}

		core::aabbox3d<f32> nodebox = getNodeBox(v3s16(x,y,z), BS);
		
		/*
			See if the object is touching ground.

			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(
				//fabs(nodebox.MaxEdge.Y-box.MinEdge.Y) < d
				fabs(nodebox.MaxEdge.Y-box.MinEdge.Y) < 0.15*BS
				&& nodebox.MaxEdge.X-d > box.MinEdge.X
				&& nodebox.MinEdge.X+d < box.MaxEdge.X
				&& nodebox.MaxEdge.Z-d > box.MinEdge.Z
				&& nodebox.MinEdge.Z+d < box.MaxEdge.Z
		){
			result.touching_ground = true;
		}
		
		// If object doesn't intersect with node, ignore node.
		if(box.intersectsWithBox(nodebox) == false)
			continue;
		
		/*
			Go through every axis
		*/
		v3f dirs[3] = {
			v3f(0,0,1), // back-front
			v3f(0,1,0), // top-bottom
			v3f(1,0,0), // right-left
		};
		for(u16 i=0; i<3; i++)
		{
			/*
				Calculate values along the axis
			*/
			f32 nodemax = nodebox.MaxEdge.dotProduct(dirs[i]);
			f32 nodemin = nodebox.MinEdge.dotProduct(dirs[i]);
			f32 objectmax = box.MaxEdge.dotProduct(dirs[i]);
			f32 objectmin = box.MinEdge.dotProduct(dirs[i]);
			f32 objectmax_old = oldbox.MaxEdge.dotProduct(dirs[i]);
			f32 objectmin_old = oldbox.MinEdge.dotProduct(dirs[i]);
			
			/*
				Check collision for the axis.
				Collision happens when object is going through a surface.
			*/
			bool negative_axis_collides =
				(nodemax > objectmin && nodemax <= objectmin_old + d
					&& speed_f.dotProduct(dirs[i]) < 0);
			bool positive_axis_collides =
				(nodemin < objectmax && nodemin >= objectmax_old - d
					&& speed_f.dotProduct(dirs[i]) > 0);
			bool main_axis_collides =
					negative_axis_collides || positive_axis_collides;
			
			/*
				Check overlap of object and node in other axes
			*/
			bool other_axes_overlap = true;
			for(u16 j=0; j<3; j++)
			{
				if(j == i)
					continue;
				f32 nodemax = nodebox.MaxEdge.dotProduct(dirs[j]);
				f32 nodemin = nodebox.MinEdge.dotProduct(dirs[j]);
				f32 objectmax = box.MaxEdge.dotProduct(dirs[j]);
				f32 objectmin = box.MinEdge.dotProduct(dirs[j]);
				if(!(nodemax - d > objectmin && nodemin + d < objectmax))
				{
					other_axes_overlap = false;
					break;
				}
			}
			
			/*
				If this is a collision, revert the pos_f in the main
				direction.
			*/
			if(other_axes_overlap && main_axis_collides)
			{
				speed_f -= speed_f.dotProduct(dirs[i]) * dirs[i];
				pos_f -= pos_f.dotProduct(dirs[i]) * dirs[i];
				pos_f += oldpos_f.dotProduct(dirs[i]) * dirs[i];
				result.collides = true;
			}
		
		}
	} // xyz
	
	return result;
}
示例#6
0
collisionMoveResult collisionMoveSimple(Map *map, IGameDef *gamedef,
                                        f32 pos_max_d, const aabb3f &box_0,
                                        f32 stepheight, f32 dtime,
                                        v3f &pos_f, v3f &speed_f, v3f &accel_f)
{
    TimeTaker tt("collisionMoveSimple");
    collisionMoveResult result;

    // If there is no speed, there are no collisions
    if(speed_f.getLength() == 0)
        return result;

    /*
    	Calculate new velocity
    */
    speed_f += accel_f * dtime;

    /*
    	Collect node boxes in movement range
    */
    std::vector<aabb3f> cboxes;
    std::vector<bool> is_unloaded;
    std::vector<bool> is_step_up;
    {
        TimeTaker tt2("collisionMoveSimple collect boxes");

        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;

        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++)
                {
                    try {
                        // Object collides into walkable nodes
                        MapNode n = map->getNode(v3s16(x,y,z));
                        if(gamedef->getNodeDefManager()->get(n).walkable == false)
                            continue;

                        std::vector<aabb3f> nodeboxes = n.getNodeBoxes(gamedef->ndef());
                        for(std::vector<aabb3f>::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);
                        }
                    }
                    catch(InvalidPositionException &e)
                    {
                        // Collide with unloaded nodes
                        aabb3f box = getNodeBox(v3s16(x,y,z), BS);
                        cboxes.push_back(box);
                        is_unloaded.push_back(true);
                        is_step_up.push_back(false);
                    }
                }
    } // tt2

    assert(cboxes.size() == is_unloaded.size());
    assert(cboxes.size() == is_step_up.size());


    /*
    	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);

    int loopcount = 0;
    while(dtime > BS*1e-10)
    {
        TimeTaker tt3("collisionMoveSimple dtime loop");

        // Avoid infinite loop
        loopcount++;
        if(loopcount >= 100)
        {
            infostream<<"collisionMoveSimple: WARNING: Loop count exceeded, aborting to avoid infiniite loop"<<std::endl;
            dtime = 0;
            break;
        }

        aabb3f movingbox = box_0;
        movingbox.MinEdge += pos_f;
        movingbox.MaxEdge += pos_f;

        int nearest_collided = -1;
        f32 nearest_dtime = dtime;
        u32 nearest_boxindex = -1;

        /*
        	Go through every nodebox, find nearest collision
        */
        for(u32 boxindex = 0; boxindex < cboxes.size(); boxindex++)
        {
            // Ignore if already stepped up this nodebox.
            if(is_step_up[boxindex])
                continue;

            // Find nearest collision of the two boxes (raytracing-like)
            f32 dtime_tmp;
            int collided = axisAlignedCollision(
                               cboxes[boxindex], movingbox, speed_f, d, dtime_tmp);

            if(collided == -1 || dtime_tmp >= nearest_dtime)
                continue;
            nearest_dtime = dtime_tmp;
            nearest_collided = collided;
            nearest_boxindex = boxindex;
        }
        if(nearest_collided == -1)
        {
            // No collision with any collision box.
            pos_f += speed_f * dtime;
            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];

            // Check for stairs.
            bool step_up = (nearest_collided != 1) && // must not be Y direction
                           (movingbox.MinEdge.Y < cbox.MaxEdge.Y) &&
                           (movingbox.MinEdge.Y + stepheight > cbox.MaxEdge.Y) &&
                           (!wouldCollideWithCeiling(cboxes, movingbox,
                                   cbox.MaxEdge.Y - movingbox.MinEdge.Y,
                                   d));

            // 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)
                if(!step_up)
                {
                    if(nearest_collided == 0)
                        pos_f.X += speed_f.X * nearest_dtime;
                    if(nearest_collided == 1)
                        pos_f.Y += speed_f.Y * nearest_dtime;
                    if(nearest_collided == 2)
                        pos_f.Z += speed_f.Z * nearest_dtime;
                }
            }
            else
            {
                pos_f += speed_f * nearest_dtime;
                dtime -= nearest_dtime;
            }

            // Set the speed component that caused the collision to zero
            if(step_up)
            {
                // Special case: Handle stairs
                is_step_up[nearest_boxindex] = true;
            }
            else if(nearest_collided == 0) // X
            {
                speed_f.X = 0;
                result.collides = true;
                result.collides_xz = true;
            }
            else if(nearest_collided == 1) // Y
            {
                speed_f.Y = 0;
                result.collides = true;
            }
            else if(nearest_collided == 2) // Z
            {
                speed_f.Z = 0;
                result.collides = true;
                result.collides_xz = true;
            }
        }
    }

    /*
    	Final touches: Check if standing on ground, step up stairs.
    */
    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];

        /*
        	See if the object is touching ground.

        	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);
                box = box_0;
                box.MinEdge += pos_f;
                box.MaxEdge += pos_f;
            }

            if(fabs(cbox.MaxEdge.Y-box.MinEdge.Y) < 0.15*BS)
            {
                result.touching_ground = true;
                if(is_unloaded[boxindex])
                    result.standing_on_unloaded = true;
            }
        }
    }
    return result;
}