Cuberite
A lightweight, fast and extensible game server for Minecraft
LineBlockTracer.cpp
Go to the documentation of this file.
1 
2 // LineBlockTracer.cpp
3 
4 // Implements the cLineBlockTracer class representing a cBlockTracer that traces along a straight line between two points
5 
6 #include "Globals.h"
7 #include "LineBlockTracer.h"
8 #include "BlockInfo.h"
9 #include "World.h"
10 #include "Chunk.h"
11 #include "BoundingBox.h"
12 
13 
14 
15 
16 
17 cLineBlockTracer::cLineBlockTracer(cWorld & a_World, cCallbacks & a_Callbacks) :
18  Super(a_World, a_Callbacks),
19  m_Start(),
20  m_End(),
21  m_Diff(),
22  m_Dir(),
23  m_Current(),
24  m_CurrentFace(BLOCK_FACE_NONE)
25 {
26 }
27 
28 
29 
30 
31 
32 bool cLineBlockTracer::Trace(cWorld & a_World, cBlockTracer::cCallbacks & a_Callbacks, const Vector3d a_Start, const Vector3d a_End)
33 {
34  cLineBlockTracer Tracer(a_World, a_Callbacks);
35  return Tracer.Trace(a_Start, a_End);
36 }
37 
38 
39 
40 
41 
42 bool cLineBlockTracer::LineOfSightTrace(cWorld & a_World, const Vector3d & a_Start, const Vector3d & a_End, int a_Sight)
43 {
44  static class LineOfSightCallbacks:
45  public cLineBlockTracer::cCallbacks
46  {
47  bool m_IsAirOpaque;
48  bool m_IsWaterOpaque;
49  bool m_IsLavaOpaque;
50  public:
51  LineOfSightCallbacks(bool a_IsAirOpaque, bool a_IsWaterOpaque, bool a_IsLavaOpaque):
52  m_IsAirOpaque(a_IsAirOpaque),
53  m_IsWaterOpaque(a_IsWaterOpaque),
54  m_IsLavaOpaque(a_IsLavaOpaque)
55  {}
56 
57  virtual bool OnNextBlock(Vector3i a_BlockPos, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, eBlockFace a_EntryFace) override
58  {
59  switch (a_BlockType)
60  {
61  case E_BLOCK_AIR: return m_IsAirOpaque;
62  case E_BLOCK_LAVA: return m_IsLavaOpaque;
63  case E_BLOCK_STATIONARY_LAVA: return m_IsLavaOpaque;
64  case E_BLOCK_STATIONARY_WATER: return m_IsWaterOpaque;
65  case E_BLOCK_WATER: return m_IsWaterOpaque;
66  default: return true;
67  }
68  }
69  } callbacks(
70  (a_Sight & losAir) == 0,
71  (a_Sight & losWater) == 0,
72  (a_Sight & losLava) == 0
73  );
74  return Trace(a_World, callbacks, a_Start, a_End);
75 }
76 
77 
78 
79 
80 
82  cWorld & a_World,
83  const Vector3d & a_Start, const Vector3d & a_End,
84  Vector3d & a_HitCoords,
85  Vector3i & a_HitBlockCoords, eBlockFace & a_HitBlockFace
86 )
87 {
88  class cSolidHitCallbacks:
89  public cCallbacks
90  {
91  public:
92  cSolidHitCallbacks(const Vector3d & a_CBStart, const Vector3d & a_CBEnd, Vector3d & a_CBHitCoords, Vector3i & a_CBHitBlockCoords, eBlockFace & a_CBHitBlockFace):
93  m_Start(a_CBStart),
94  m_End(a_CBEnd),
95  m_HitCoords(a_CBHitCoords),
96  m_HitBlockCoords(a_CBHitBlockCoords),
97  m_HitBlockFace(a_CBHitBlockFace)
98  {
99  }
100 
101  virtual bool OnNextBlock(Vector3i a_BlockPos, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, eBlockFace a_EntryFace) override
102  {
103  if (!cBlockInfo::IsSolid(a_BlockType))
104  {
105  return false;
106  }
107 
108  // We hit a solid block, calculate the exact hit coords and abort trace:
109  m_HitBlockCoords = a_BlockPos;
110  m_HitBlockFace = a_EntryFace;
111  cBoundingBox bb(a_BlockPos, a_BlockPos + Vector3i(1, 1, 1)); // Bounding box of the block hit
112  double LineCoeff = 0; // Used to calculate where along the line an intersection with the bounding box occurs
113  eBlockFace Face; // Face hit
114  if (!bb.CalcLineIntersection(m_Start, m_End, LineCoeff, Face))
115  {
116  // Math rounding errors have caused the calculation to miss the block completely, assume immediate hit
117  LineCoeff = 0;
118  }
119  m_HitCoords = m_Start + (m_End - m_Start) * LineCoeff; // Point where projectile goes into the hit block
120  return true;
121  }
122 
123  protected:
124  const Vector3d & m_Start;
125  const Vector3d & m_End;
126  Vector3d & m_HitCoords;
127  Vector3i & m_HitBlockCoords;
128  eBlockFace & m_HitBlockFace;
129  } callbacks(a_Start, a_End, a_HitCoords, a_HitBlockCoords, a_HitBlockFace);
130  return !Trace(a_World, callbacks, a_Start, a_End);
131 }
132 
133 
134 
135 
136 
137 bool cLineBlockTracer::Trace(const Vector3d a_Start, const Vector3d a_End)
138 {
139  // Initialize the member veriables:
140  m_Start = a_Start;
141  m_End = a_End;
142  m_Dir.x = (m_Start.x < m_End.x) ? 1 : -1;
143  m_Dir.y = (m_Start.y < m_End.y) ? 1 : -1;
144  m_Dir.z = (m_Start.z < m_End.z) ? 1 : -1;
146 
147  // Check the start coords, adjust into the world:
148  if (m_Start.y < 0)
149  {
150  if (m_End.y < 0)
151  {
152  // Nothing to trace
153  m_Callbacks->OnNoMoreHits();
154  return true;
155  }
157  m_Callbacks->OnIntoWorld(m_Start);
158  }
159  else if (m_Start.y >= cChunkDef::Height)
160  {
161  if (m_End.y >= cChunkDef::Height)
162  {
163  m_Callbacks->OnNoMoreHits();
164  return true;
165  }
167  m_Callbacks->OnIntoWorld(m_Start);
168  }
169 
170  m_Current = m_Start.Floor();
171 
172  m_Diff = m_End - m_Start;
173 
174  // The actual trace is handled with ChunkMapCS locked by calling our ChunkCallback for the specified chunk
175  int BlockX = FloorC(m_Start.x);
176  int BlockZ = FloorC(m_Start.z);
177  int ChunkX, ChunkZ;
178  cChunkDef::BlockToChunk(BlockX, BlockZ, ChunkX, ChunkZ);
179  return m_World->DoWithChunk(ChunkX, ChunkZ, [this](cChunk & a_Chunk) { return ChunkCallback(&a_Chunk); });
180 }
181 
182 
183 
184 
185 
187 {
188  // We must set the start Y to less than cChunkDef::Height so that it is considered inside the world later on
189  // Therefore we use an EPS-offset from the height, as small as reasonably possible.
190  const double Height = static_cast<double>(cChunkDef::Height) - 0.00001;
192  m_Start.y = Height;
193 }
194 
195 
196 
197 
198 
200 {
202  m_Start.y = 0;
203 }
204 
205 
206 
207 
208 
209 void cLineBlockTracer::CalcXZIntersection(double a_Y, double & a_IntersectX, double & a_IntersectZ)
210 {
211  double Ratio = (m_Start.y - a_Y) / (m_Start.y - m_End.y);
212  a_IntersectX = m_Start.x + (m_End.x - m_Start.x) * Ratio;
213  a_IntersectZ = m_Start.z + (m_End.z - m_Start.z) * Ratio;
214 }
215 
216 
217 
218 
219 
221 {
222  // Find out which of the current block's walls gets hit by the path:
223  static const double EPS = 0.00001;
224  enum
225  {
226  dirNONE,
227  dirX,
228  dirY,
229  dirZ,
230  } Direction = dirNONE;
231 
232  // Calculate the next YZ wall hit:
233  double Coeff = 1;
234  if (std::abs(m_Diff.x) > EPS)
235  {
236  double DestX = (m_Dir.x > 0) ? (m_Current.x + 1) : m_Current.x;
237  double CoeffX = (DestX - m_Start.x) / m_Diff.x;
238  if (CoeffX <= 1) // We need to include equality for the last block in the trace
239  {
240  Coeff = CoeffX;
241  Direction = dirX;
242  }
243  }
244 
245  // If the next XZ wall hit is closer, use it instead:
246  if (std::abs(m_Diff.y) > EPS)
247  {
248  double DestY = (m_Dir.y > 0) ? (m_Current.y + 1) : m_Current.y;
249  double CoeffY = (DestY - m_Start.y) / m_Diff.y;
250  if (CoeffY <= Coeff) // We need to include equality for the last block in the trace
251  {
252  Coeff = CoeffY;
253  Direction = dirY;
254  }
255  }
256 
257  // If the next XY wall hit is closer, use it instead:
258  if (std::abs(m_Diff.z) > EPS)
259  {
260  double DestZ = (m_Dir.z > 0) ? (m_Current.z + 1) : m_Current.z;
261  double CoeffZ = (DestZ - m_Start.z) / m_Diff.z;
262  if (CoeffZ <= Coeff) // We need to include equality for the last block in the trace
263  {
264  Direction = dirZ;
265  }
266  }
267 
268  // Based on the wall hit, adjust the current coords
269  switch (Direction)
270  {
271  case dirX: m_Current.x += m_Dir.x; m_CurrentFace = (m_Dir.x > 0) ? BLOCK_FACE_XM : BLOCK_FACE_XP; break;
272  case dirY: m_Current.y += m_Dir.y; m_CurrentFace = (m_Dir.y > 0) ? BLOCK_FACE_YM : BLOCK_FACE_YP; break;
273  case dirZ: m_Current.z += m_Dir.z; m_CurrentFace = (m_Dir.z > 0) ? BLOCK_FACE_ZM : BLOCK_FACE_ZP; break;
274  case dirNONE: return false;
275  }
276  return true;
277 }
278 
279 
280 
281 
282 
284 {
285  ASSERT((m_Current.y >= 0) && (m_Current.y < cChunkDef::Height)); // This should be provided by FixStartAboveWorld() / FixStartBelowWorld()
286 
287  // This is the actual line tracing loop.
288  for (;;)
289  {
290  // Our caller (DoWithChunk callback) should never give nothing:
291  ASSERT(a_Chunk != nullptr);
292 
293  // Move to next block
294  if (!MoveToNextBlock())
295  {
296  // We've reached the end
297  m_Callbacks->OnNoMoreHits();
298  return true;
299  }
300 
301  if ((m_Current.y < 0) || (m_Current.y >= cChunkDef::Height))
302  {
303  // We've gone out of the world, that's the end of this trace
304  double IntersectX, IntersectZ;
305  CalcXZIntersection(m_Current.y, IntersectX, IntersectZ);
306  if (m_Callbacks->OnOutOfWorld({IntersectX, double(m_Current.y), IntersectZ}))
307  {
308  // The callback terminated the trace
309  return false;
310  }
311  m_Callbacks->OnNoMoreHits();
312  return true;
313  }
314 
315  // Update the current chunk
316  a_Chunk = a_Chunk->GetNeighborChunk(m_Current.x, m_Current.z);
317  if (a_Chunk == nullptr)
318  {
319  m_Callbacks->OnNoChunk();
320  return false;
321  }
322 
323  // Report the current block through the callbacks:
324  if (a_Chunk->IsValid())
325  {
327  NIBBLETYPE BlockMeta;
328  int RelX = m_Current.x - a_Chunk->GetPosX() * cChunkDef::Width;
329  int RelZ = m_Current.z - a_Chunk->GetPosZ() * cChunkDef::Width;
330  a_Chunk->GetBlockTypeMeta(RelX, m_Current.y, RelZ, BlockType, BlockMeta);
331  if (m_Callbacks->OnNextBlock(m_Current, BlockType, BlockMeta, m_CurrentFace))
332  {
333  // The callback terminated the trace
334  return false;
335  }
336  }
337  else if (m_Callbacks->OnNextBlockNoData(m_Current, m_CurrentFace))
338  {
339  // The callback terminated the trace
340  return false;
341  }
342  }
343 }
344 
345 
346 
@ E_BLOCK_WATER
Definition: BlockType.h:18
@ E_BLOCK_STATIONARY_LAVA
Definition: BlockType.h:21
@ E_BLOCK_AIR
Definition: BlockType.h:10
@ E_BLOCK_STATIONARY_WATER
Definition: BlockType.h:19
@ E_BLOCK_LAVA
Definition: BlockType.h:20
unsigned char NIBBLETYPE
The datatype used by nibbledata (meta, light, skylight)
Definition: ChunkDef.h:44
unsigned char BLOCKTYPE
The datatype used by blockdata.
Definition: ChunkDef.h:41
eBlockFace
Block face constants, used in PlayerDigging and PlayerBlockPlacement packets and bbox collision calc.
Definition: Defines.h:38
@ BLOCK_FACE_XP
Definition: Defines.h:41
@ BLOCK_FACE_YP
Definition: Defines.h:43
@ BLOCK_FACE_YM
Definition: Defines.h:42
@ BLOCK_FACE_ZM
Definition: Defines.h:44
@ BLOCK_FACE_ZP
Definition: Defines.h:45
@ BLOCK_FACE_XM
Definition: Defines.h:40
@ BLOCK_FACE_NONE
Definition: Defines.h:39
#define ASSERT(x)
Definition: Globals.h:276
std::enable_if< std::is_arithmetic< T >::value, C >::type FloorC(T a_Value)
Floors a value, then casts it to C (an int by default).
Definition: Globals.h:347
BlockType
Definition: BlockTypes.h:4
Direction
Vector3< int > Vector3i
Definition: Vector3.h:487
static bool IsSolid(BLOCKTYPE Block)
Is this block solid (player cannot walk through)?
Definition: BlockInfo.cpp:892
Represents two sets of coords, minimum and maximum for each direction.
Definition: BoundingBox.h:24
bool CalcLineIntersection(Vector3d a_LinePoint1, Vector3d a_LinePoint2, double &a_LineCoeff, eBlockFace &a_Face) const
Returns true if this bounding box is intersected by the line specified by its two points Also calcula...
Definition: Chunk.h:36
int GetPosX(void) const
Definition: Chunk.h:131
cChunk * GetNeighborChunk(int a_BlockX, int a_BlockZ)
Returns the chunk into which the specified block belongs, by walking the neighbors.
Definition: Chunk.cpp:1806
bool IsValid(void) const
Returns true iff the chunk block data is valid (loaded / generated)
Definition: Chunk.h:58
int GetPosZ(void) const
Definition: Chunk.h:132
void GetBlockTypeMeta(Vector3i a_RelPos, BLOCKTYPE &a_BlockType, NIBBLETYPE &a_BlockMeta) const
Definition: Chunk.cpp:1757
static void BlockToChunk(int a_X, int a_Z, int &a_ChunkX, int &a_ChunkZ)
Converts absolute block coords to chunk coords:
Definition: ChunkDef.h:210
static const int Width
Definition: ChunkDef.h:124
static const int Height
Definition: ChunkDef.h:125
cBlockTracer Super
void FixStartBelowWorld(void)
Adjusts the start point below the world to just at the world's bottom.
void CalcXZIntersection(double a_Y, double &a_IntersectX, double &a_IntersectZ)
Calculates the XZ coords of an intersection with the specified Yconst plane; assumes that such an int...
Vector3i m_Dir
The increment at which the block coords are going from Start to End; either +1 or -1.
static bool LineOfSightTrace(cWorld &a_World, const Vector3d &a_Start, const Vector3d &a_End, int a_Sight)
Returns true if the two positions are within line of sight (not obscured by blocks).
Vector3d m_Diff
The difference in coords, End - Start.
bool Trace(Vector3d a_Start, Vector3d a_End)
Traces one line between Start and End; returns true if the entire line was traced (until OnNoMoreHits...
bool ChunkCallback(cChunk *a_Chunk)
Vector3d m_End
The end point of the trace.
Vector3d m_Start
The start point of the trace.
bool MoveToNextBlock(void)
Moves m_Current to the next block on the line; returns false if no move is possible (reached the end)
static bool FirstSolidHitTrace(cWorld &a_World, const Vector3d &a_Start, const Vector3d &a_End, Vector3d &a_HitCoords, Vector3i &a_HitBlockCoords, eBlockFace &a_HitBlockFace)
Traces until the first solid block is hit (or until end, whichever comes first.
Vector3i m_Current
The current block.
eBlockFace m_CurrentFace
The face through which the current block has been entered.
void FixStartAboveWorld(void)
Adjusts the start point above the world to just at the world's top.
cLineBlockTracer(cWorld &a_World, cCallbacks &a_Callbacks)
T x
Definition: Vector3.h:17
Vector3< int > Floor(void) const
Returns a new Vector3i with coords set to std::floor() of this vector's coords.
Definition: Vector3.h:177
T y
Definition: Vector3.h:17
T z
Definition: Vector3.h:17
Definition: World.h:53