]> git.lizzy.rs Git - minetest.git/blob - src/collision.cpp
Random Lua tweaks/fixes
[minetest.git] / src / collision.cpp
1 /*
2 Minetest-c55
3 Copyright (C) 2010 celeron55, Perttu Ahola <celeron55@gmail.com>
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include "collision.h"
21 #include "mapblock.h"
22 #include "map.h"
23 #include "nodedef.h"
24 #include "gamedef.h"
25
26 collisionMoveResult collisionMoveSimple(Map *map, IGameDef *gamedef,
27                 f32 pos_max_d, const core::aabbox3d<f32> &box_0,
28                 f32 dtime, v3f &pos_f, v3f &speed_f)
29 {
30         collisionMoveResult result;
31
32         v3f oldpos_f = pos_f;
33         v3s16 oldpos_i = floatToInt(oldpos_f, BS);
34
35         /*
36                 Calculate new position
37         */
38         pos_f += speed_f * dtime;
39
40         /*
41                 Collision detection
42         */
43         
44         // position in nodes
45         v3s16 pos_i = floatToInt(pos_f, BS);
46         
47         /*
48                 Collision uncertainty radius
49                 Make it a bit larger than the maximum distance of movement
50         */
51         f32 d = pos_max_d * 1.1;
52         // A fairly large value in here makes moving smoother
53         //f32 d = 0.15*BS;
54
55         // This should always apply, otherwise there are glitches
56         assert(d > pos_max_d);
57         
58         /*
59                 Calculate collision box
60         */
61         core::aabbox3d<f32> box = box_0;
62         box.MaxEdge += pos_f;
63         box.MinEdge += pos_f;
64         core::aabbox3d<f32> oldbox = box_0;
65         oldbox.MaxEdge += oldpos_f;
66         oldbox.MinEdge += oldpos_f;
67
68         /*
69                 If the object lies on a walkable node, this is set to true.
70         */
71         result.touching_ground = false;
72         
73         /*
74                 Go through every node around the object
75                 TODO: Calculate the range of nodes that need to be checked
76         */
77         for(s16 y = oldpos_i.Y - 1; y <= oldpos_i.Y + 2; y++)
78         for(s16 z = oldpos_i.Z - 1; z <= oldpos_i.Z + 1; z++)
79         for(s16 x = oldpos_i.X - 1; x <= oldpos_i.X + 1; x++)
80         {
81                 try{
82                         // Object collides into walkable nodes
83                         MapNode n = map->getNode(v3s16(x,y,z));
84                         if(gamedef->getNodeDefManager()->get(n).walkable == false)
85                                 continue;
86                 }
87                 catch(InvalidPositionException &e)
88                 {
89                         // Doing nothing here will block the object from
90                         // walking over map borders
91                 }
92
93                 core::aabbox3d<f32> nodebox = getNodeBox(v3s16(x,y,z), BS);
94                 
95                 /*
96                         See if the object is touching ground.
97
98                         Object touches ground if object's minimum Y is near node's
99                         maximum Y and object's X-Z-area overlaps with the node's
100                         X-Z-area.
101
102                         Use 0.15*BS so that it is easier to get on a node.
103                 */
104                 if(
105                                 //fabs(nodebox.MaxEdge.Y-box.MinEdge.Y) < d
106                                 fabs(nodebox.MaxEdge.Y-box.MinEdge.Y) < 0.15*BS
107                                 && nodebox.MaxEdge.X-d > box.MinEdge.X
108                                 && nodebox.MinEdge.X+d < box.MaxEdge.X
109                                 && nodebox.MaxEdge.Z-d > box.MinEdge.Z
110                                 && nodebox.MinEdge.Z+d < box.MaxEdge.Z
111                 ){
112                         result.touching_ground = true;
113                 }
114                 
115                 // If object doesn't intersect with node, ignore node.
116                 if(box.intersectsWithBox(nodebox) == false)
117                         continue;
118                 
119                 /*
120                         Go through every axis
121                 */
122                 v3f dirs[3] = {
123                         v3f(0,0,1), // back-front
124                         v3f(0,1,0), // top-bottom
125                         v3f(1,0,0), // right-left
126                 };
127                 for(u16 i=0; i<3; i++)
128                 {
129                         /*
130                                 Calculate values along the axis
131                         */
132                         f32 nodemax = nodebox.MaxEdge.dotProduct(dirs[i]);
133                         f32 nodemin = nodebox.MinEdge.dotProduct(dirs[i]);
134                         f32 objectmax = box.MaxEdge.dotProduct(dirs[i]);
135                         f32 objectmin = box.MinEdge.dotProduct(dirs[i]);
136                         f32 objectmax_old = oldbox.MaxEdge.dotProduct(dirs[i]);
137                         f32 objectmin_old = oldbox.MinEdge.dotProduct(dirs[i]);
138                         
139                         /*
140                                 Check collision for the axis.
141                                 Collision happens when object is going through a surface.
142                         */
143                         bool negative_axis_collides =
144                                 (nodemax > objectmin && nodemax <= objectmin_old + d
145                                         && speed_f.dotProduct(dirs[i]) < 0);
146                         bool positive_axis_collides =
147                                 (nodemin < objectmax && nodemin >= objectmax_old - d
148                                         && speed_f.dotProduct(dirs[i]) > 0);
149                         bool main_axis_collides =
150                                         negative_axis_collides || positive_axis_collides;
151                         
152                         /*
153                                 Check overlap of object and node in other axes
154                         */
155                         bool other_axes_overlap = true;
156                         for(u16 j=0; j<3; j++)
157                         {
158                                 if(j == i)
159                                         continue;
160                                 f32 nodemax = nodebox.MaxEdge.dotProduct(dirs[j]);
161                                 f32 nodemin = nodebox.MinEdge.dotProduct(dirs[j]);
162                                 f32 objectmax = box.MaxEdge.dotProduct(dirs[j]);
163                                 f32 objectmin = box.MinEdge.dotProduct(dirs[j]);
164                                 if(!(nodemax - d > objectmin && nodemin + d < objectmax))
165                                 {
166                                         other_axes_overlap = false;
167                                         break;
168                                 }
169                         }
170                         
171                         /*
172                                 If this is a collision, revert the pos_f in the main
173                                 direction.
174                         */
175                         if(other_axes_overlap && main_axis_collides)
176                         {
177                                 speed_f -= speed_f.dotProduct(dirs[i]) * dirs[i];
178                                 pos_f -= pos_f.dotProduct(dirs[i]) * dirs[i];
179                                 pos_f += oldpos_f.dotProduct(dirs[i]) * dirs[i];
180                                 result.collides = true;
181                         }
182                 
183                 }
184         } // xyz
185         
186         return result;
187 }
188
189 collisionMoveResult collisionMovePrecise(Map *map, IGameDef *gamedef,
190                 f32 pos_max_d, const core::aabbox3d<f32> &box_0,
191                 f32 dtime, v3f &pos_f, v3f &speed_f)
192 {
193         collisionMoveResult final_result;
194
195         // Maximum time increment (for collision detection etc)
196         // time = distance / speed
197         f32 dtime_max_increment = pos_max_d / speed_f.getLength();
198         
199         // Maximum time increment is 10ms or lower
200         if(dtime_max_increment > 0.01)
201                 dtime_max_increment = 0.01;
202         
203         // Don't allow overly huge dtime
204         if(dtime > 2.0)
205                 dtime = 2.0;
206         
207         f32 dtime_downcount = dtime;
208
209         u32 loopcount = 0;
210         do
211         {
212                 loopcount++;
213
214                 f32 dtime_part;
215                 if(dtime_downcount > dtime_max_increment)
216                 {
217                         dtime_part = dtime_max_increment;
218                         dtime_downcount -= dtime_part;
219                 }
220                 else
221                 {
222                         dtime_part = dtime_downcount;
223                         /*
224                                 Setting this to 0 (no -=dtime_part) disables an infinite loop
225                                 when dtime_part is so small that dtime_downcount -= dtime_part
226                                 does nothing
227                         */
228                         dtime_downcount = 0;
229                 }
230
231                 collisionMoveResult result = collisionMoveSimple(map, gamedef,
232                                 pos_max_d, box_0, dtime_part, pos_f, speed_f);
233
234                 if(result.touching_ground)
235                         final_result.touching_ground = true;
236                 if(result.collides)
237                         final_result.collides = true;
238         }
239         while(dtime_downcount > 0.001);
240                 
241
242         return final_result;
243 }
244
245