diff options
author | Tiger Wang <ziwei.tiger@outlook.com> | 2022-02-12 15:57:26 +0100 |
---|---|---|
committer | Tiger Wang <ziwei.tiger@outlook.com> | 2022-07-19 23:33:09 +0200 |
commit | 9d0623a6653af85e139addb3d95aeb4f3f3dcfff (patch) | |
tree | 9858a908b5fb2b6c67f614cd4adbd38568fe919e /src/Physics/Tracers/LineBlockTracer.cpp | |
parent | Pickups: improve coalescing implementation (diff) | |
download | cuberite-9d0623a6653af85e139addb3d95aeb4f3f3dcfff.tar cuberite-9d0623a6653af85e139addb3d95aeb4f3f3dcfff.tar.gz cuberite-9d0623a6653af85e139addb3d95aeb4f3f3dcfff.tar.bz2 cuberite-9d0623a6653af85e139addb3d95aeb4f3f3dcfff.tar.lz cuberite-9d0623a6653af85e139addb3d95aeb4f3f3dcfff.tar.xz cuberite-9d0623a6653af85e139addb3d95aeb4f3f3dcfff.tar.zst cuberite-9d0623a6653af85e139addb3d95aeb4f3f3dcfff.zip |
Diffstat (limited to 'src/Physics/Tracers/LineBlockTracer.cpp')
-rw-r--r-- | src/Physics/Tracers/LineBlockTracer.cpp | 346 |
1 files changed, 346 insertions, 0 deletions
diff --git a/src/Physics/Tracers/LineBlockTracer.cpp b/src/Physics/Tracers/LineBlockTracer.cpp new file mode 100644 index 000000000..49eba9ac8 --- /dev/null +++ b/src/Physics/Tracers/LineBlockTracer.cpp @@ -0,0 +1,346 @@ + +// LineBlockTracer.cpp + +// Implements the cLineBlockTracer class representing a cBlockTracer that traces along a straight line between two points + +#include "Globals.h" +#include "LineBlockTracer.h" +#include "BlockInfo.h" +#include "World.h" +#include "Chunk.h" +#include "BoundingBox.h" + + + + + +cLineBlockTracer::cLineBlockTracer(cWorld & a_World, cCallbacks & a_Callbacks) : + Super(a_World, a_Callbacks), + m_Start(), + m_End(), + m_Diff(), + m_Dir(), + m_Current(), + m_CurrentFace(BLOCK_FACE_NONE) +{ +} + + + + + +bool cLineBlockTracer::Trace(cWorld & a_World, cBlockTracer::cCallbacks & a_Callbacks, const Vector3d a_Start, const Vector3d a_End) +{ + cLineBlockTracer Tracer(a_World, a_Callbacks); + return Tracer.Trace(a_Start, a_End); +} + + + + + +bool cLineBlockTracer::LineOfSightTrace(cWorld & a_World, const Vector3d & a_Start, const Vector3d & a_End, int a_Sight) +{ + static class LineOfSightCallbacks: + public cLineBlockTracer::cCallbacks + { + bool m_IsAirOpaque; + bool m_IsWaterOpaque; + bool m_IsLavaOpaque; + public: + LineOfSightCallbacks(bool a_IsAirOpaque, bool a_IsWaterOpaque, bool a_IsLavaOpaque): + m_IsAirOpaque(a_IsAirOpaque), + m_IsWaterOpaque(a_IsWaterOpaque), + m_IsLavaOpaque(a_IsLavaOpaque) + {} + + virtual bool OnNextBlock(Vector3i a_BlockPos, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, eBlockFace a_EntryFace) override + { + switch (a_BlockType) + { + case E_BLOCK_AIR: return m_IsAirOpaque; + case E_BLOCK_LAVA: return m_IsLavaOpaque; + case E_BLOCK_STATIONARY_LAVA: return m_IsLavaOpaque; + case E_BLOCK_STATIONARY_WATER: return m_IsWaterOpaque; + case E_BLOCK_WATER: return m_IsWaterOpaque; + default: return true; + } + } + } callbacks( + (a_Sight & losAir) == 0, + (a_Sight & losWater) == 0, + (a_Sight & losLava) == 0 + ); + return Trace(a_World, callbacks, a_Start, a_End); +} + + + + + +bool cLineBlockTracer::FirstSolidHitTrace( + cWorld & a_World, + const Vector3d & a_Start, const Vector3d & a_End, + Vector3d & a_HitCoords, + Vector3i & a_HitBlockCoords, eBlockFace & a_HitBlockFace +) +{ + class cSolidHitCallbacks: + public cCallbacks + { + public: + cSolidHitCallbacks(const Vector3d & a_CBStart, const Vector3d & a_CBEnd, Vector3d & a_CBHitCoords, Vector3i & a_CBHitBlockCoords, eBlockFace & a_CBHitBlockFace): + m_Start(a_CBStart), + m_End(a_CBEnd), + m_HitCoords(a_CBHitCoords), + m_HitBlockCoords(a_CBHitBlockCoords), + m_HitBlockFace(a_CBHitBlockFace) + { + } + + virtual bool OnNextBlock(Vector3i a_BlockPos, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, eBlockFace a_EntryFace) override + { + if (!cBlockInfo::IsSolid(a_BlockType)) + { + return false; + } + + // We hit a solid block, calculate the exact hit coords and abort trace: + m_HitBlockCoords = a_BlockPos; + m_HitBlockFace = a_EntryFace; + cBoundingBox bb(a_BlockPos, a_BlockPos + Vector3i(1, 1, 1)); // Bounding box of the block hit + double LineCoeff = 0; // Used to calculate where along the line an intersection with the bounding box occurs + eBlockFace Face; // Face hit + if (!bb.CalcLineIntersection(m_Start, m_End, LineCoeff, Face)) + { + // Math rounding errors have caused the calculation to miss the block completely, assume immediate hit + LineCoeff = 0; + } + m_HitCoords = m_Start + (m_End - m_Start) * LineCoeff; // Point where projectile goes into the hit block + return true; + } + + protected: + const Vector3d & m_Start; + const Vector3d & m_End; + Vector3d & m_HitCoords; + Vector3i & m_HitBlockCoords; + eBlockFace & m_HitBlockFace; + } callbacks(a_Start, a_End, a_HitCoords, a_HitBlockCoords, a_HitBlockFace); + return !Trace(a_World, callbacks, a_Start, a_End); +} + + + + + +bool cLineBlockTracer::Trace(const Vector3d a_Start, const Vector3d a_End) +{ + // Initialize the member veriables: + m_Start = a_Start; + m_End = a_End; + m_Dir.x = (m_Start.x < m_End.x) ? 1 : -1; + m_Dir.y = (m_Start.y < m_End.y) ? 1 : -1; + m_Dir.z = (m_Start.z < m_End.z) ? 1 : -1; + m_CurrentFace = BLOCK_FACE_NONE; + + // Check the start coords, adjust into the world: + if (m_Start.y < 0) + { + if (m_End.y < 0) + { + // Nothing to trace + m_Callbacks->OnNoMoreHits(); + return true; + } + FixStartBelowWorld(); + m_Callbacks->OnIntoWorld(m_Start); + } + else if (m_Start.y >= cChunkDef::Height) + { + if (m_End.y >= cChunkDef::Height) + { + m_Callbacks->OnNoMoreHits(); + return true; + } + FixStartAboveWorld(); + m_Callbacks->OnIntoWorld(m_Start); + } + + m_Current = m_Start.Floor(); + + m_Diff = m_End - m_Start; + + // The actual trace is handled with ChunkMapCS locked by calling our ChunkCallback for the specified chunk + int BlockX = FloorC(m_Start.x); + int BlockZ = FloorC(m_Start.z); + int ChunkX, ChunkZ; + cChunkDef::BlockToChunk(BlockX, BlockZ, ChunkX, ChunkZ); + return m_World->DoWithChunk(ChunkX, ChunkZ, [this](cChunk & a_Chunk) { return ChunkCallback(&a_Chunk); }); +} + + + + + +void cLineBlockTracer::FixStartAboveWorld(void) +{ + // We must set the start Y to less than cChunkDef::Height so that it is considered inside the world later on + // Therefore we use an EPS-offset from the height, as small as reasonably possible. + const double Height = static_cast<double>(cChunkDef::Height) - 0.00001; + CalcXZIntersection(Height, m_Start.x, m_Start.z); + m_Start.y = Height; +} + + + + + +void cLineBlockTracer::FixStartBelowWorld(void) +{ + CalcXZIntersection(0, m_Start.x, m_Start.z); + m_Start.y = 0; +} + + + + + +void cLineBlockTracer::CalcXZIntersection(double a_Y, double & a_IntersectX, double & a_IntersectZ) +{ + double Ratio = (m_Start.y - a_Y) / (m_Start.y - m_End.y); + a_IntersectX = m_Start.x + (m_End.x - m_Start.x) * Ratio; + a_IntersectZ = m_Start.z + (m_End.z - m_Start.z) * Ratio; +} + + + + + +bool cLineBlockTracer::MoveToNextBlock(void) +{ + // Find out which of the current block's walls gets hit by the path: + static const double EPS = 0.00001; + enum + { + dirNONE, + dirX, + dirY, + dirZ, + } Direction = dirNONE; + + // Calculate the next YZ wall hit: + double Coeff = 1; + if (std::abs(m_Diff.x) > EPS) + { + double DestX = (m_Dir.x > 0) ? (m_Current.x + 1) : m_Current.x; + double CoeffX = (DestX - m_Start.x) / m_Diff.x; + if (CoeffX <= 1) // We need to include equality for the last block in the trace + { + Coeff = CoeffX; + Direction = dirX; + } + } + + // If the next XZ wall hit is closer, use it instead: + if (std::abs(m_Diff.y) > EPS) + { + double DestY = (m_Dir.y > 0) ? (m_Current.y + 1) : m_Current.y; + double CoeffY = (DestY - m_Start.y) / m_Diff.y; + if (CoeffY <= Coeff) // We need to include equality for the last block in the trace + { + Coeff = CoeffY; + Direction = dirY; + } + } + + // If the next XY wall hit is closer, use it instead: + if (std::abs(m_Diff.z) > EPS) + { + double DestZ = (m_Dir.z > 0) ? (m_Current.z + 1) : m_Current.z; + double CoeffZ = (DestZ - m_Start.z) / m_Diff.z; + if (CoeffZ <= Coeff) // We need to include equality for the last block in the trace + { + Direction = dirZ; + } + } + + // Based on the wall hit, adjust the current coords + switch (Direction) + { + case dirX: m_Current.x += m_Dir.x; m_CurrentFace = (m_Dir.x > 0) ? BLOCK_FACE_XM : BLOCK_FACE_XP; break; + case dirY: m_Current.y += m_Dir.y; m_CurrentFace = (m_Dir.y > 0) ? BLOCK_FACE_YM : BLOCK_FACE_YP; break; + case dirZ: m_Current.z += m_Dir.z; m_CurrentFace = (m_Dir.z > 0) ? BLOCK_FACE_ZM : BLOCK_FACE_ZP; break; + case dirNONE: return false; + } + return true; +} + + + + + +bool cLineBlockTracer::ChunkCallback(cChunk * a_Chunk) +{ + ASSERT((m_Current.y >= 0) && (m_Current.y < cChunkDef::Height)); // This should be provided by FixStartAboveWorld() / FixStartBelowWorld() + + // This is the actual line tracing loop. + for (;;) + { + // Our caller (DoWithChunk callback) should never give nothing: + ASSERT(a_Chunk != nullptr); + + // Move to next block + if (!MoveToNextBlock()) + { + // We've reached the end + m_Callbacks->OnNoMoreHits(); + return true; + } + + if ((m_Current.y < 0) || (m_Current.y >= cChunkDef::Height)) + { + // We've gone out of the world, that's the end of this trace + double IntersectX, IntersectZ; + CalcXZIntersection(m_Current.y, IntersectX, IntersectZ); + if (m_Callbacks->OnOutOfWorld({IntersectX, double(m_Current.y), IntersectZ})) + { + // The callback terminated the trace + return false; + } + m_Callbacks->OnNoMoreHits(); + return true; + } + + // Update the current chunk + a_Chunk = a_Chunk->GetNeighborChunk(m_Current.x, m_Current.z); + if (a_Chunk == nullptr) + { + m_Callbacks->OnNoChunk(); + return false; + } + + // Report the current block through the callbacks: + if (a_Chunk->IsValid()) + { + BLOCKTYPE BlockType; + NIBBLETYPE BlockMeta; + int RelX = m_Current.x - a_Chunk->GetPosX() * cChunkDef::Width; + int RelZ = m_Current.z - a_Chunk->GetPosZ() * cChunkDef::Width; + a_Chunk->GetBlockTypeMeta(RelX, m_Current.y, RelZ, BlockType, BlockMeta); + if (m_Callbacks->OnNextBlock(m_Current, BlockType, BlockMeta, m_CurrentFace)) + { + // The callback terminated the trace + return false; + } + } + else if (m_Callbacks->OnNextBlockNoData(m_Current, m_CurrentFace)) + { + // The callback terminated the trace + return false; + } + } +} + + + |