aboutsummaryrefslogtreecommitdiff
path: root/src/objdef.cpp
blob: 08d6844fc135487818ffc73f8a33bcba49e90b32 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
/*
Minetest
Copyright (C) 2010-2015 kwolekr, Ryan Kwolek <kwolekr@minetest.net>

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 "objdef.h"
#include "util/numeric.h"
#include "log.h"
#include "gamedef.h"

ObjDefManager::ObjDefManager(IGameDef *gamedef, ObjDefType type)
{
	m_objtype = type;
	m_ndef = gamedef ? gamedef->getNodeDefManager() : NULL;
}


ObjDefManager::~ObjDefManager()
{
	for (size_t i = 0; i != m_objects.size(); i++)
		delete m_objects[i];
}


ObjDefHandle ObjDefManager::add(ObjDef *obj)
{
	assert(obj);

	if (obj->name.length() && getByName(obj->name))
		return OBJDEF_INVALID_HANDLE;

	u32 index = addRaw(obj);
	if (index == OBJDEF_INVALID_INDEX)
		return OBJDEF_INVALID_HANDLE;

	obj->handle = createHandle(index, m_objtype, obj->uid);
	return obj->handle;
}


ObjDef *ObjDefManager::get(ObjDefHandle handle) const
{
	u32 index = validateHandle(handle);
	return (index != OBJDEF_INVALID_INDEX) ? getRaw(index) : NULL;
}


ObjDef *ObjDefManager::set(ObjDefHandle handle, ObjDef *obj)
{
	u32 index = validateHandle(handle);
	if (index == OBJDEF_INVALID_INDEX)
		return NULL;

	ObjDef *oldobj = setRaw(index, obj);

	obj->uid    = oldobj->uid;
	obj->index  = oldobj->index;
	obj->handle = oldobj->handle;

	return oldobj;
}


u32 ObjDefManager::addRaw(ObjDef *obj)
{
	size_t nobjects = m_objects.size();
	if (nobjects >= OBJDEF_MAX_ITEMS)
		return -1;

	obj->index = nobjects;

	// Ensure UID is nonzero so that a valid handle == OBJDEF_INVALID_HANDLE
	// is not possible.  The slight randomness bias isn't very significant.
	obj->uid = myrand() & OBJDEF_UID_MASK;
	if (obj->uid == 0)
		obj->uid = 1;

	m_objects.push_back(obj);

	infostream << "ObjDefManager: added " << getObjectTitle()
		<< ": name=\"" << obj->name
		<< "\" index=" << obj->index
		<< " uid="     << obj->uid
		<< std::endl;

	return nobjects;
}


ObjDef *ObjDefManager::getRaw(u32 index) const
{
	return m_objects[index];
}


ObjDef *ObjDefManager::setRaw(u32 index, ObjDef *obj)
{
	ObjDef *old_obj = m_objects[index];
	m_objects[index] = obj;
	return old_obj;
}


ObjDef *ObjDefManager::getByName(const std::string &name) const
{
	for (size_t i = 0; i != m_objects.size(); i++) {
		ObjDef *obj = m_objects[i];
		if (obj && !strcasecmp(name.c_str(), obj->name.c_str()))
			return obj;
	}

	return NULL;
}


void ObjDefManager::clear()
{
	for (size_t i = 0; i != m_objects.size(); i++)
		delete m_objects[i];

	m_objects.clear();
}


u32 ObjDefManager::validateHandle(ObjDefHandle handle) const
{
	ObjDefType type;
	u32 index;
	u32 uid;

	bool is_valid =
		(handle != OBJDEF_INVALID_HANDLE)         &&
		decodeHandle(handle, &index, &type, &uid) &&
		(type == m_objtype)                       &&
		(index < m_objects.size())                &&
		(m_objects[index]->uid == uid);

	return is_valid ? index : -1;
}


ObjDefHandle ObjDefManager::createHandle(u32 index, ObjDefType type, u32 uid)
{
	ObjDefHandle handle = 0;
	set_bits(&handle, 0, 18, index);
	set_bits(&handle, 18, 6, type);
	set_bits(&handle, 24, 7, uid);

	u32 parity = calc_parity(handle);
	set_bits(&handle, 31, 1, parity);

	return handle ^ OBJDEF_HANDLE_SALT;
}


bool ObjDefManager::decodeHandle(ObjDefHandle handle, u32 *index,
	ObjDefType *type, u32 *uid)
{
	handle ^= OBJDEF_HANDLE_SALT;

	u32 parity = get_bits(handle, 31, 1);
	set_bits(&handle, 31, 1, 0);
	if (parity != calc_parity(handle))
		return false;

	*index = get_bits(handle, 0, 18);
	*type  = (ObjDefType)get_bits(handle, 18, 6);
	*uid   = get_bits(handle, 24, 7);
	return true;
}
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. CollisionAxis axisAlignedCollision( const aabb3f &staticbox, const aabb3f &movingbox, const v3f &speed, f32 *dtime) { //TimeTaker tt("axisAlignedCollision"); aabb3f relbox( (movingbox.MaxEdge.X - movingbox.MinEdge.X) + (staticbox.MaxEdge.X - staticbox.MinEdge.X), // sum of the widths (movingbox.MaxEdge.Y - movingbox.MinEdge.Y) + (staticbox.MaxEdge.Y - staticbox.MinEdge.Y), (movingbox.MaxEdge.Z - movingbox.MinEdge.Z) + (staticbox.MaxEdge.Z - staticbox.MinEdge.Z), std::max(movingbox.MaxEdge.X, staticbox.MaxEdge.X) - std::min(movingbox.MinEdge.X, staticbox.MinEdge.X), //outer bounding 'box' dimensions std::max(movingbox.MaxEdge.Y, staticbox.MaxEdge.Y) - std::min(movingbox.MinEdge.Y, staticbox.MinEdge.Y), std::max(movingbox.MaxEdge.Z, staticbox.MaxEdge.Z) - std::min(movingbox.MinEdge.Z, staticbox.MinEdge.Z) ); const f32 dtime_max = *dtime; 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 { return COLLISION_AXIS_NONE; } } // NO else if here 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) { 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; } } } 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<NearbyCollisionInfo> &cinfo, const aabb3f &movingbox, f32 y_increase, f32 d) { //TimeTaker tt("wouldCollideWithCeiling"); assert(y_increase >= 0); // pre-condition 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) && (movingbox.MinEdge.Z < staticbox.MaxEdge.Z) && (movingbox.MaxEdge.Z > staticbox.MinEdge.Z)) return true; } return false; } static inline void getNeighborConnectingFace(const v3s16 &p, const NodeDefManager *nodedef, Map *map, MapNode n, int v, int *neighbors) { MapNode n2 = map->getNode(p); if (nodedef->nodeboxConnects(n, n2, v)) *neighbors |= v; } 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(); ScopeProfiler sp(g_profiler, "collisionMoveSimple()", 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); *speed_f = truncate(*speed_f, 10000.0f); /* Collect node boxes in movement range */ std::vector<NearbyCollisionInfo> cinfo; { //TimeTaker tt2("collisionMoveSimple collect boxes"); ScopeProfiler sp2(g_profiler, "collisionMoveSimple(): collect boxes", SPT_AVG); v3f newpos_f = *pos_f + *speed_f * dtime; v3f minpos_f( MYMIN(pos_f->X, newpos_f.X), MYMIN(pos_f->Y, newpos_f.Y) + 0.01f * BS, // bias rounding, player often at +/-n.5 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->getNode(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, 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, 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) { /* add object boxes to cinfo */ std::vector<ActiveObject*> objects; #ifndef SERVER ClientEnvironment *c_env = dynamic_cast<ClientEnvironment*>(env); if (c_env != 0) { // 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<DistanceSortedActiveObject> clientobjects; 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<ServerEnvironment*>(env); 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) {