aboutsummaryrefslogtreecommitdiff
Commit message (Expand)AuthorAge
...
| * Bump version to 0.4.7PilzAdam2013-06-06
| * Revert "Fix multiple texture support for animated meshnodes." (broke existing...PilzAdam2013-06-06
| * Close console when it loses focus but it is still on screenEsteban I. Ruiz Moreno2013-06-05
| * Only add ^[forcesingle to get raw texture if atlas is usedKahrl2013-06-04
| * Fix multiple texture support for animated meshnodes.stujones112013-06-03
| * Corrected segfault when registering new biomes.sweetbomber2013-06-03
| * Add and implement setting max_clearobjects_extra_loaded_blocks.Kahrl2013-06-03
| * Delay deleting replaced textures (m_texture_trash)Kahrl2013-06-03
| * Call m_menumgr->deletingMenu from quitMenu in addition to destructorKahrl2013-06-03
| * Add Code::Blocks project and build files to .gitignorebcnjr52013-06-03
| * Replace c55.me linksKahrl2013-06-03
| * Fix missing find_path and line_of_sightsapier2013-06-01
| * Fix some nullptr exceptions when handling invalid node inventoriesMetaDucky2013-06-01
* | Translated using Weblate (Kirghiz)Chyngyz Dzhumaliev2013-06-21
* | Translated using Weblate (French)Dany Cuartiella2013-06-02
|/
* Translated using Weblate (French)Ilya Zhuravlev2013-06-01
* Merge remote-tracking branch 'origin/master'Weblate2013-06-01
|\
| * Fix minetest.get_inventory(loc) always returning nil for "node" type locMetaDucky2013-06-01
| * Fix editbox default text being pre-selected in some casesMetaDucky2013-06-01
| * Generate debug HUD text with ostringstream instead of snprintf.Kahrl2013-06-01
| * Liquid adjusting: continue to dropproller2013-05-31
| * Bugfix: Update formname on showing new formspec while already one shownsapier2013-05-26
| * Fixed wrongly named invref:get_location() table members for nodes.MetaDucky2013-05-26
| * Add ObjectRef.hud_set_hotbar_itemcount and add TOCLIENT_HUD_SET_PARAMKahrl2013-05-26
| * Higher resolution menuheaderPilzAdam2013-05-25
| * Fix itemstack:add item not working correctsapier2013-05-25
| * Move scriptapi to separate folder (by sapier)sapier2013-05-25
| * Predict param2 of facedir nodes and attachment of attached_node nodesPilzAdam2013-05-24
| * Make raillike nodes connect to any other raillike nodes if both are in the gr...Jeija2013-05-22
| * Fix disappearing of wielditemPilzAdam2013-05-21
| * Log non-fatal mod name conflict to actionstream instead of errorstreamKahrl2013-05-21
| * Dont animate changing wielditem from hand to handPilzAdam2013-05-20
| * Add menu_header.png and remove old, unused texturesPilzAdam2013-05-20
| * Fix typo in particle spawningMetaDucky2013-05-20
| * Save settings after dropping main menuelagin2013-05-20
| * Fix link in minetest.conf.exampleRuss2013-05-20
| * Use the group "soil" for nodes that saplings grow onShadowNinja2013-05-20
| * Fix lava damage on player's upper bodykwolekr2013-05-19
| * Fix nearly all warningskwolekr2013-05-19
| * Allow nil as puncher e.g. to do damage by tntsapier2013-05-20
| * Add a little animation when changing the wielded itemPilzAdam2013-05-20
| * Dont teleport back if holding sneak when detaching/turn freemove offPilzAdam2013-05-19
| * Fix EmergeThread names in logKahrl2013-05-19
| * Write mods that are not in world.mt into it at world startupPilzAdam2013-05-19
| * Dont load mods that have no entry in world.mtPilzAdam2013-05-19
| * Install mods/mods_here.txt instead of mods/minetest/mods_here.txtPilzAdam2013-05-19
| * Remove no virtual dtor warnings, make MapgenParams contain actual NoiseParamskwolekr2013-05-19
| * Fix regression of world loading caused by 81c863ackwolekr2013-05-19
| * New modsystemPilzAdam2013-05-19
| * Remove survival and build from CMakeLists.txtPilzAdam2013-05-18
' href='#n475'>475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include "collision.h"
#include "mapblock.h"
#include "map.h"
#include "nodedef.h"
#include "gamedef.h"
#include "log.h"
#include "environment.h"
#include "serverobject.h"
#include <vector>
#include <set>
#include "util/timetaker.h"
#include "main.h" // g_profiler
#include "profiler.h"

// float error is 10 - 9.96875 = 0.03125
//#define COLL_ZERO 0.032 // broken unit tests
#define COLL_ZERO 0

// 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(
		const aabb3f &staticbox, const aabb3f &movingbox,
		const v3f &speed, f32 d, 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
	);

	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;
		}
		else if(relbox.MaxEdge.X < 0)
		{
			return -1;
		}
	}

	// 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;
		}
	}

	// 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;
		}
		//else if(relbox.MaxEdge.Z < 0)
		//{
		//	return -1;
		//}
	}

	return -1;
}

// Helper function:
// Checks if moving the movingbox up by the given distance would hit a ceiling.
bool wouldCollideWithCeiling(
		const std::vector<aabb3f> &staticboxes,
		const aabb3f &movingbox,
		f32 y_increase, f32 d)
{
	//TimeTaker tt("wouldCollideWithCeiling");

	assert(y_increase >= 0);

	for(std::vector<aabb3f>::const_iterator
			i = staticboxes.begin();
			i != staticboxes.end(); i++)
	{
		const aabb3f& staticbox = *i;
		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) &&
				(movingbox.MinEdge.Z < staticbox.MaxEdge.Z) &&
				(movingbox.MaxEdge.Z > staticbox.MinEdge.Z))
			return true;
	}

	return false;
}


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)
{
	Map *map = &env->getMap();
	//TimeTaker tt("collisionMoveSimple");
    ScopeProfiler sp(g_profiler, "collisionMoveSimple avg", SPT_AVG);

	collisionMoveResult result;

	/*
		Calculate new velocity
	*/
	if( dtime > 0.5 ) {
		infostream<<"collisionMoveSimple: WARNING: maximum step interval exceeded, lost movement details!"<<std::endl;
		dtime = 0.5;
	}
	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<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;

	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

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

			std::vector<aabb3f> nodeboxes = n.getCollisionBoxes(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);
				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);
		}
	}
	} // tt2

	if(collideWithObjects)
	{
		ScopeProfiler sp(g_profiler, "collisionMoveSimple objects avg", SPT_AVG);
		//TimeTaker tt3("collisionMoveSimple collect object boxes");

		/* add object boxes to cboxes */


		std::list<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::set<u16> s_objects = s_env->getObjectsInsideRadius(pos_f,distance * 1.5);
				for (std::set<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::list<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());
	assert(cboxes.size() == is_step_up.size());
	assert(cboxes.size() == bouncy_values.size());
	assert(cboxes.size() == node_positions.size());
	assert(cboxes.size() == is_object.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");
        ScopeProfiler sp(g_profiler, "collisionMoveSimple dtime loop avg", SPT_AVG);

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

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