summaryrefslogtreecommitdiffstats
path: root/src/Physics
diff options
context:
space:
mode:
Diffstat (limited to 'src/Physics')
-rw-r--r--src/Physics/Explodinator.cpp159
-rw-r--r--src/Physics/Tracers/BlockTracer.h133
-rw-r--r--src/Physics/Tracers/LineBlockTracer.cpp402
-rw-r--r--src/Physics/Tracers/LineBlockTracer.h78
4 files changed, 299 insertions, 473 deletions
diff --git a/src/Physics/Explodinator.cpp b/src/Physics/Explodinator.cpp
index ba1a8cbcf..058078cdc 100644
--- a/src/Physics/Explodinator.cpp
+++ b/src/Physics/Explodinator.cpp
@@ -7,7 +7,7 @@
#include "Chunk.h"
#include "ClientHandle.h"
#include "Entities/FallingBlock.h"
-#include "LineBlockTracer.h"
+#include "Physics/Tracers/LineBlockTracer.h"
#include "Simulator/SandSimulator.h"
@@ -22,27 +22,10 @@ namespace Explodinator
static const auto TraceCubeSideLength = 16U;
static const auto BoundingBoxStepUnit = 0.5;
- /** Converts an absolute floating-point Position into a Chunk-relative one. */
- static Vector3f AbsoluteToRelative(const Vector3f a_Position, const cChunkCoords a_ChunkPosition)
- {
- return { a_Position.x - a_ChunkPosition.m_ChunkX * cChunkDef::Width, a_Position.y, a_Position.z - a_ChunkPosition.m_ChunkZ * cChunkDef::Width };
- }
-
- /** Make a From Chunk-relative Position into a To Chunk-relative position. */
- static Vector3f RebaseRelativePosition(const cChunkCoords a_From, const cChunkCoords a_To, const Vector3f a_Position)
- {
- return
- {
- a_Position.x + (a_From.m_ChunkX - a_To.m_ChunkX) * cChunkDef::Width,
- a_Position.y,
- a_Position.z + (a_From.m_ChunkZ - a_To.m_ChunkZ) * cChunkDef::Width
- };
- }
-
/** Returns how much of an explosion Destruction Lazor's (tm) intensity the given block attenuates.
Values are scaled as 0.3 * (0.3 + Wiki) since some compilers miss the constant folding optimisation.
Wiki values are https://minecraft.gamepedia.com/Explosion#Blast_resistance as of 2021-02-06. */
- static float GetExplosionAbsorption(const BLOCKTYPE Block)
+ static constexpr float GetExplosionAbsorption(const BLOCKTYPE Block)
{
switch (Block)
{
@@ -162,7 +145,7 @@ namespace Explodinator
/** Calculates the approximate percentage of an Entity's bounding box that is exposed to an explosion centred at Position. */
static float CalculateEntityExposure(const cChunk & a_Chunk, const cEntity & a_Entity, const Vector3f a_Position, const int a_SquareRadius)
{
- class LineOfSightCallbacks final : public cLineBlockTracer::cCallbacks
+ class LineOfSightCallbacks final : public BlockTracerCallbacks
{
virtual bool OnNextBlock(Vector3i a_BlockPos, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, eBlockFace a_EntryFace) override
{
@@ -173,7 +156,6 @@ namespace Explodinator
const Vector3d Position = a_Position;
unsigned Unobstructed = 0, Total = 0;
const auto Box = a_Entity.GetBoundingBox();
- cLineBlockTracer Tracer(*a_Chunk.GetWorld(), Callback);
for (double X = Box.GetMinX(); X < Box.GetMaxX(); X += BoundingBoxStepUnit)
{
@@ -182,6 +164,7 @@ namespace Explodinator
for (double Z = Box.GetMinZ(); Z < Box.GetMaxZ(); Z += BoundingBoxStepUnit)
{
const Vector3d Destination{X, Y, Z};
+
if ((Destination - Position).SqrLength() > a_SquareRadius)
{
// Don't bother with points outside our designated area-of-effect
@@ -189,7 +172,7 @@ namespace Explodinator
continue;
}
- if (Tracer.Trace(a_Position, Destination))
+ if (LineBlockTracer::Trace(a_Chunk, Callback, Position, Destination))
{
Unobstructed++;
}
@@ -257,134 +240,146 @@ namespace Explodinator
}
/** Sets the block at the given position, updating surroundings. */
- static void SetBlock(cWorld & a_World, cChunk & a_Chunk, const Vector3i a_AbsolutePosition, const Vector3i a_RelativePosition, const BLOCKTYPE a_DestroyedBlock, const BLOCKTYPE a_NewBlock, const cEntity * const a_ExplodingEntity)
+ static void SetBlock(cChunk & a_Chunk, const Vector3i a_AbsolutePosition, const Vector3i a_RelativePosition, const BLOCKTYPE a_CurrentBlock, const NIBBLETYPE a_CurrentMeta, const BLOCKTYPE a_NewBlock, const cEntity * const a_ExplodingEntity)
{
- const auto DestroyedMeta = a_Chunk.GetMeta(a_RelativePosition);
-
// SetBlock wakes up all simulators for the area, so that water and lava flows and sand falls into the blasted holes
// It also is responsible for calling cBlockHandler::OnNeighborChanged to pop off blocks that fail CanBeAt
// An explicit call to cBlockHandler::OnBroken handles the destruction of multiblock structures
// References at (FS #391, GH #4418):
a_Chunk.SetBlock(a_RelativePosition, a_NewBlock, 0);
- cChunkInterface Interface(a_World.GetChunkMap());
- cBlockHandler::For(a_DestroyedBlock).OnBroken(Interface, a_World, a_AbsolutePosition, a_DestroyedBlock, DestroyedMeta, a_ExplodingEntity);
+ auto & World = *a_Chunk.GetWorld();
+ cChunkInterface Interface(World.GetChunkMap());
+ cBlockHandler::For(a_CurrentBlock).OnBroken(Interface, World, a_AbsolutePosition, a_CurrentBlock, a_CurrentMeta, a_ExplodingEntity);
}
/** Work out what should happen when an explosion destroys the given block.
Tasks include lighting TNT, dropping pickups, setting fire and flinging shrapnel according to Minecraft rules.
OK, _mostly_ Minecraft rules. */
- static void DestroyBlock(cChunk & a_Chunk, const Vector3i a_Position, const int a_Power, const bool a_Fiery, const cEntity * const a_ExplodingEntity)
+ static void DestroyBlock(MTRand & a_Random, cChunk & a_Chunk, const Vector3i a_AbsolutePosition, const Vector3i a_RelativePosition, const BLOCKTYPE a_CurrentBlock, const NIBBLETYPE a_CurrentMeta, const cBoundingBox a_ExplosionBounds, const int a_Power, const bool a_Fiery, const cEntity * const a_ExplodingEntity)
{
- const auto DestroyedBlock = a_Chunk.GetBlock(a_Position);
- if (DestroyedBlock == E_BLOCK_AIR)
- {
- // There's nothing left for us here, but a barren and empty land
- // Let's go.
- return;
- }
-
auto & World = *a_Chunk.GetWorld();
- auto & Random = GetRandomProvider();
- const auto Absolute = cChunkDef::RelativeToAbsolute(a_Position, a_Chunk.GetPos());
- if (DestroyedBlock == E_BLOCK_TNT) // If the block is TNT we should set it off
+ if (a_CurrentBlock == E_BLOCK_TNT) // If the block is TNT we should set it off
{
// Random fuse between 10 to 30 game ticks.
- const int FuseTime = Random.RandInt(10, 30);
+ const int FuseTime = a_Random.RandInt(10, 30);
// Activate the TNT, with initial velocity and no fuse sound:
- World.SpawnPrimedTNT(Vector3d(0.5, 0, 0.5) + Absolute, FuseTime, 1, false);
+ World.SpawnPrimedTNT(Vector3d(0.5, 0, 0.5) + a_AbsolutePosition, FuseTime, 1, false);
}
- else if ((a_ExplodingEntity != nullptr) && (a_ExplodingEntity->IsTNT() || BlockAlwaysDrops(DestroyedBlock) || Random.RandBool(1.f / a_Power))) // For TNT explosions, destroying a block that always drops, or if RandBool, drop pickups
+ else if ((a_ExplodingEntity != nullptr) && (a_ExplodingEntity->IsTNT() || BlockAlwaysDrops(a_CurrentBlock) || a_Random.RandBool(1.f / a_Power))) // For TNT explosions, destroying a block that always drops, or if RandBool, drop pickups
{
- const auto DestroyedMeta = a_Chunk.GetMeta(a_Position);
- a_Chunk.GetWorld()->SpawnItemPickups(cBlockHandler::For(DestroyedBlock).ConvertToPickups(DestroyedMeta), Absolute);
+ for (auto & Item : cBlockHandler::For(a_CurrentBlock).ConvertToPickups(a_CurrentMeta))
+ {
+ World.SpawnItemPickup(Vector3d(0.5, 0, 0.5) + a_AbsolutePosition, std::move(Item), Vector3d(), a_ExplosionBounds);
+ }
}
- else if (a_Fiery && Random.RandBool(1 / 3.0)) // 33% chance of starting fires if it can start fires
+ else if (a_Fiery && a_Random.RandBool(1 / 3.0)) // 33% chance of starting fires if it can start fires
{
- const auto Below = a_Position.addedY(-1);
+ const auto Below = a_AbsolutePosition.addedY(-1);
if ((Below.y >= 0) && cBlockInfo::FullyOccupiesVoxel(a_Chunk.GetBlock(Below)))
{
// Start a fire:
- SetBlock(World, a_Chunk, Absolute, a_Position, DestroyedBlock, E_BLOCK_FIRE, a_ExplodingEntity);
+ SetBlock(a_Chunk, a_AbsolutePosition, a_RelativePosition, a_CurrentBlock, a_CurrentMeta, E_BLOCK_FIRE, a_ExplodingEntity);
return;
}
}
- else if (const auto Shrapnel = World.GetTNTShrapnelLevel(); (Shrapnel > slNone) && Random.RandBool(0)) // Currently 0% chance of flinging stuff around
+ else if (const auto Shrapnel = World.GetTNTShrapnelLevel(); (Shrapnel > slNone) && a_Random.RandBool(0)) // Currently 0% chance of flinging stuff around
{
// If the block is shrapnel-able, make a falling block entity out of it:
if (
- ((Shrapnel == slAll) && cBlockInfo::FullyOccupiesVoxel(DestroyedBlock)) ||
- ((Shrapnel == slGravityAffectedOnly) && cSandSimulator::IsAllowedBlock(DestroyedBlock))
+ ((Shrapnel == slAll) && cBlockInfo::FullyOccupiesVoxel(a_CurrentBlock)) ||
+ ((Shrapnel == slGravityAffectedOnly) && cSandSimulator::IsAllowedBlock(a_CurrentBlock))
)
{
- const auto DestroyedMeta = a_Chunk.GetMeta(a_Position);
- auto FallingBlock = std::make_unique<cFallingBlock>(Vector3d(0.5, 0, 0.5) + Absolute, DestroyedBlock, DestroyedMeta);
+ auto FallingBlock = std::make_unique<cFallingBlock>(Vector3d(0.5, 0, 0.5) + a_AbsolutePosition, a_CurrentBlock, a_CurrentMeta);
// TODO: correct velocity FallingBlock->SetSpeedY(40);
FallingBlock->Initialize(std::move(FallingBlock), World);
}
}
- SetBlock(World, a_Chunk, Absolute, a_Position, DestroyedBlock, E_BLOCK_AIR, a_ExplodingEntity);
+ SetBlock(a_Chunk, a_AbsolutePosition, a_RelativePosition, a_CurrentBlock, a_CurrentMeta, E_BLOCK_AIR, a_ExplodingEntity);
}
- /** Traces the path taken by one Explosion Lazor (tm) with given direction and intensity, that will destroy blocks until it is exhausted. */
- static void DestructionTrace(cChunk * a_Chunk, Vector3f a_Origin, const Vector3f a_Direction, const int a_Power, const bool a_Fiery, float a_Intensity, const cEntity * const a_ExplodingEntity)
+ /** Returns a random intensity for an Explosion Lazor (tm) as a function of the explosion's power. */
+ static float RandomIntensity(MTRand & a_Random, const int a_Power)
+ {
+ return a_Power * (0.7f + a_Random.RandReal(0.6f));
+ }
+
+ /** Traces the path taken by one Explosion Lazor (tm) with given direction and random intensity, that will destroy blocks until it is exhausted. */
+ static void DestructionTrace(MTRand & a_Random, cChunk * a_Chunk, Vector3f a_Origin, const Vector3f a_Direction, const cBoundingBox a_ExplosionBounds, const int a_Power, const bool a_Fiery, const cEntity * const a_ExplodingEntity)
{
// The current position the ray is at.
auto Checkpoint = a_Origin;
+ auto Position = Checkpoint.Floor();
+
+ auto Intensity = RandomIntensity(a_Random, a_Power);
+
// The displacement that the ray in one iteration step should travel.
const auto Step = a_Direction.NormalizeCopy() * StepUnit;
// Loop until intensity runs out:
- while (a_Intensity > 0)
+ while (Intensity > 0)
{
- auto Position = Checkpoint.Floor();
if (!cChunkDef::IsValidHeight(Position))
{
break;
}
- const auto Neighbour = a_Chunk->GetRelNeighborChunkAdjustCoords(Position);
- if ((Neighbour == nullptr) || !Neighbour->IsValid())
+ Vector3i RelativePosition;
+
+ if (!a_Chunk->GetChunkAndRelByAbsolute(Position, &a_Chunk, RelativePosition))
{
break;
}
- a_Intensity -= GetExplosionAbsorption(Neighbour->GetBlock(Position));
- if (a_Intensity <= 0)
+ BLOCKTYPE CurrentBlock;
+ NIBBLETYPE CurrentMeta;
+ a_Chunk->GetBlockTypeMeta(RelativePosition, CurrentBlock, CurrentMeta);
+
+ Intensity -= GetExplosionAbsorption(CurrentBlock);
+ if (Intensity <= 0)
{
// The ray is exhausted:
break;
}
- DestroyBlock(*Neighbour, Position, a_Power, a_Fiery, a_ExplodingEntity);
-
- // Adjust coordinates to be relative to the neighbour chunk:
- Checkpoint = RebaseRelativePosition(a_Chunk->GetPos(), Neighbour->GetPos(), Checkpoint);
- a_Origin = RebaseRelativePosition(a_Chunk->GetPos(), Neighbour->GetPos(), a_Origin);
- a_Chunk = Neighbour;
+ if (CurrentBlock != E_BLOCK_AIR)
+ {
+ DestroyBlock(a_Random, *a_Chunk, Position, RelativePosition, CurrentBlock, CurrentMeta, a_ExplosionBounds, a_Power, a_Fiery, a_ExplodingEntity);
+ }
// Increment the simulation, weaken the ray:
Checkpoint += Step;
- a_Intensity -= StepAttenuation;
- }
- }
+ Intensity -= StepAttenuation;
- /** Returns a random intensity for an Explosion Lazor (tm) as a function of the explosion's power. */
- static float RandomIntensity(MTRand & a_Random, const int a_Power)
- {
- return a_Power * (0.7f + a_Random.RandReal(0.6f));
+ for (int i = 0; i != 2; i++)
+ {
+ const auto PreviousPosition = Position;
+ Position = Checkpoint.Floor();
+
+ if (Position != PreviousPosition)
+ {
+ break;
+ }
+
+ Checkpoint += Step;
+ Intensity -= StepAttenuation + GetExplosionAbsorption(E_BLOCK_AIR);
+ }
+ }
}
/** Sends out Explosion Lazors (tm) originating from the given position that destroy blocks. */
static void DamageBlocks(cChunk & a_Chunk, const Vector3f a_Position, const int a_Power, const bool a_Fiery, const cEntity * const a_ExplodingEntity)
{
// Oh boy... Better hope you have a hot cache, 'cos this little manoeuvre's gonna cost us 1352 raytraces in one tick...
- const int HalfSide = TraceCubeSideLength / 2;
+
auto & Random = GetRandomProvider();
+ const int HalfSide = TraceCubeSideLength / 2;
+ const cBoundingBox ExplosionBounds(a_Position, TraceCubeSideLength);
// The following loops implement the tracing algorithm described in http://minecraft.gamepedia.com/Explosion
@@ -394,8 +389,8 @@ namespace Explodinator
{
for (float OffsetZ = -HalfSide; OffsetZ < HalfSide; OffsetZ++)
{
- DestructionTrace(&a_Chunk, a_Position, Vector3f(OffsetX, +HalfSide, OffsetZ), a_Power, a_Fiery, RandomIntensity(Random, a_Power), a_ExplodingEntity);
- DestructionTrace(&a_Chunk, a_Position, Vector3f(OffsetX, -HalfSide, OffsetZ), a_Power, a_Fiery, RandomIntensity(Random, a_Power), a_ExplodingEntity);
+ DestructionTrace(Random, &a_Chunk, a_Position, Vector3f(OffsetX, +HalfSide, OffsetZ), ExplosionBounds, a_Power, a_Fiery, a_ExplodingEntity);
+ DestructionTrace(Random, &a_Chunk, a_Position, Vector3f(OffsetX, -HalfSide, OffsetZ), ExplosionBounds, a_Power, a_Fiery, a_ExplodingEntity);
}
}
@@ -404,8 +399,8 @@ namespace Explodinator
{
for (float OffsetY = -HalfSide + 1; OffsetY < HalfSide - 1; OffsetY++)
{
- DestructionTrace(&a_Chunk, a_Position, Vector3f(OffsetX, OffsetY, +HalfSide), a_Power, a_Fiery, RandomIntensity(Random, a_Power), a_ExplodingEntity);
- DestructionTrace(&a_Chunk, a_Position, Vector3f(OffsetX, OffsetY, -HalfSide), a_Power, a_Fiery, RandomIntensity(Random, a_Power), a_ExplodingEntity);
+ DestructionTrace(Random, &a_Chunk, a_Position, Vector3f(OffsetX, OffsetY, +HalfSide), ExplosionBounds, a_Power, a_Fiery, a_ExplodingEntity);
+ DestructionTrace(Random, &a_Chunk, a_Position, Vector3f(OffsetX, OffsetY, -HalfSide), ExplosionBounds, a_Power, a_Fiery, a_ExplodingEntity);
}
}
@@ -414,8 +409,8 @@ namespace Explodinator
{
for (float OffsetY = -HalfSide + 1; OffsetY < HalfSide - 1; OffsetY++)
{
- DestructionTrace(&a_Chunk, a_Position, Vector3f(+HalfSide, OffsetY, OffsetZ), a_Power, a_Fiery, RandomIntensity(Random, a_Power), a_ExplodingEntity);
- DestructionTrace(&a_Chunk, a_Position, Vector3f(-HalfSide, OffsetY, OffsetZ), a_Power, a_Fiery, RandomIntensity(Random, a_Power), a_ExplodingEntity);
+ DestructionTrace(Random, &a_Chunk, a_Position, Vector3f(+HalfSide, OffsetY, OffsetZ), ExplosionBounds, a_Power, a_Fiery, a_ExplodingEntity);
+ DestructionTrace(Random, &a_Chunk, a_Position, Vector3f(-HalfSide, OffsetY, OffsetZ), ExplosionBounds, a_Power, a_Fiery, a_ExplodingEntity);
}
}
}
@@ -435,7 +430,7 @@ namespace Explodinator
{
LagTheClient(a_Chunk, a_Position, a_Power);
DamageEntities(a_Chunk, a_Position, a_Power);
- DamageBlocks(a_Chunk, AbsoluteToRelative(a_Position, a_Chunk.GetPos()), a_Power, a_Fiery, a_ExplodingEntity);
+ DamageBlocks(a_Chunk, a_Position, a_Power, a_Fiery, a_ExplodingEntity);
return false;
});
diff --git a/src/Physics/Tracers/BlockTracer.h b/src/Physics/Tracers/BlockTracer.h
index 62ae1636e..e32ef3b21 100644
--- a/src/Physics/Tracers/BlockTracer.h
+++ b/src/Physics/Tracers/BlockTracer.h
@@ -1,7 +1,7 @@
// BlockTracer.h
-// Declares the classes common for all blocktracers
+// Declares the callback common to all blocktracers
@@ -9,118 +9,61 @@
#pragma once
-
-
-
-
#include "Defines.h"
#include "ChunkDef.h"
-// fwd: World.h
-class cWorld;
-
+/** The callback class is used to notify the caller of individual events that are being traced. */
+class BlockTracerCallbacks abstract
+{
+public:
+ // Force a virtual destructor in descendants:
+ virtual ~BlockTracerCallbacks() {}
+ /** Called on each block encountered along the path, including the first block (path start)
+ When this callback returns true, the tracing is aborted. */
+ virtual bool OnNextBlock(Vector3i a_BlockPos, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, eBlockFace a_EntryFace) = 0;
-class cBlockTracer abstract
-{
-public:
- /** The callback class is used to notify the caller of individual events that are being traced.
- */
- class cCallbacks abstract
- {
- public:
- // Force a virtual destructor in descendants:
- virtual ~cCallbacks() {}
-
- /** Called on each block encountered along the path, including the first block (path start)
- When this callback returns true, the tracing is aborted.
- */
- virtual bool OnNextBlock(Vector3i a_BlockPos, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, eBlockFace a_EntryFace) = 0;
-
- /** Called on each block encountered along the path, including the first block (path start), if chunk data is not loaded
- When this callback returns true, the tracing is aborted.
- */
- virtual bool OnNextBlockNoData(Vector3i a_BlockPos, eBlockFace a_EntryFace)
- {
- UNUSED(a_BlockPos);
- UNUSED(a_EntryFace);
- return false;
- }
-
- /** Called when the path goes out of world, either below (a_BlockPos.y < 0) or above (a_BlockPos.y >= cChunkDef::Height)
- The coords specify the exact point at which the path exited the world.
- If this callback returns true, the tracing is aborted.
- Note that some paths can go out of the world and come back again (parabola),
- in such a case this callback is followed by OnIntoWorld() and further OnNextBlock() calls
- */
- virtual bool OnOutOfWorld(Vector3d a_BlockPos)
- {
- UNUSED(a_BlockPos);
- return false;
- }
-
- /** Called when the path goes into the world, from either below (a_BlockPos.y < 0) or above (a_BlockPos.y >= cChunkDef::Height)
- The coords specify the exact point at which the path entered the world.
- If this callback returns true, the tracing is aborted.
- Note that some paths can go out of the world and come back again (parabola),
- in such a case this callback is followed by further OnNextBlock() calls
- */
- virtual bool OnIntoWorld(Vector3d a_BlockPos)
- {
- UNUSED(a_BlockPos);
- return false;
- }
-
- /** Called when the path is sure not to hit any more blocks.
- Note that for some shapes this might never happen (line with constant Y)
- */
- virtual void OnNoMoreHits(void) {}
-
- /** Called when the block tracing walks into a chunk that is not allocated.
- This usually means that the tracing is aborted.
- */
- virtual void OnNoChunk(void) {}
- } ;
-
-
- /** Creates the BlockTracer parent with the specified callbacks */
- cBlockTracer(cWorld & a_World, cCallbacks & a_Callbacks) :
- m_World(&a_World),
- m_Callbacks(&a_Callbacks)
+ /** Called on each block encountered along the path, including the first block (path start), if chunk data is not loaded
+ When this callback returns true, the tracing is aborted. */
+ virtual bool OnNextBlockNoData(Vector3i a_BlockPos, eBlockFace a_EntryFace)
{
+ UNUSED(a_BlockPos);
+ UNUSED(a_EntryFace);
+ return false;
}
-
- /** Sets new world, returns the old one. Note that both need to be valid */
- cWorld & SetWorld(cWorld & a_World)
+ /** Called when the path goes out of world, either below (a_BlockPos.y < 0) or above (a_BlockPos.y >= cChunkDef::Height)
+ The coords specify the exact point at which the path exited the world.
+ If this callback returns true, the tracing is aborted.
+ Note that some paths can go out of the world and come back again (parabola),
+ in such a case this callback is followed by OnIntoWorld() and further OnNextBlock() calls. */
+ virtual bool OnOutOfWorld(Vector3d a_BlockPos)
{
- cWorld & Old = *m_World;
- m_World = &a_World;
- return Old;
+ UNUSED(a_BlockPos);
+ return false;
}
-
- /** Sets new callbacks, returns the old ones. Note that both need to be valid */
- cCallbacks & SetCallbacks(cCallbacks & a_NewCallbacks)
+ /** Called when the path goes into the world, from either below (a_BlockPos.y < 0) or above (a_BlockPos.y >= cChunkDef::Height)
+ The coords specify the exact point at which the path entered the world.
+ If this callback returns true, the tracing is aborted.
+ Note that some paths can go out of the world and come back again (parabola),
+ in such a case this callback is followed by further OnNextBlock() calls. */
+ virtual bool OnIntoWorld(Vector3d a_BlockPos)
{
- cCallbacks & Old = *m_Callbacks;
- m_Callbacks = &a_NewCallbacks;
- return Old;
+ UNUSED(a_BlockPos);
+ return false;
}
-protected:
- /** The world upon which to operate */
- cWorld * m_World;
+ /** Called when the path is sure not to hit any more blocks.
+ Note that for some shapes this might never happen (line with constant Y). */
+ virtual void OnNoMoreHits(void) {}
- /** The callback to use for reporting */
- cCallbacks * m_Callbacks;
+ /** Called when the block tracing walks into a chunk that is not allocated.
+ This usually means that the tracing is aborted. */
+ virtual void OnNoChunk(void) {}
} ;
-
-
-
-
diff --git a/src/Physics/Tracers/LineBlockTracer.cpp b/src/Physics/Tracers/LineBlockTracer.cpp
index 49eba9ac8..265d97dab 100644
--- a/src/Physics/Tracers/LineBlockTracer.cpp
+++ b/src/Physics/Tracers/LineBlockTracer.cpp
@@ -14,35 +14,186 @@
-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)
+static Vector3d CalcXZIntersection(double a_Y, const Vector3d a_Start, const Vector3d a_End)
{
+ const double Ratio = (a_Start.y - a_Y) / (a_Start.y - a_End.y);
+ return { a_Start.x + (a_End.x - a_Start.x) * Ratio, a_Y, a_Start.z + (a_End.z - a_Start.z) * Ratio };
}
-bool cLineBlockTracer::Trace(cWorld & a_World, cBlockTracer::cCallbacks & a_Callbacks, const Vector3d a_Start, const Vector3d a_End)
+static Vector3d FixStartAboveWorld(const Vector3d a_Start, const Vector3d a_End)
{
- cLineBlockTracer Tracer(a_World, a_Callbacks);
- return Tracer.Trace(a_Start, a_End);
+ // 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;
+ return CalcXZIntersection(Height, a_Start, a_End);
+}
+
+
+
+
+
+static Vector3d FixStartBelowWorld(const Vector3d a_Start, const Vector3d a_End)
+{
+ return CalcXZIntersection(0, a_Start, a_End);
+}
+
+
+
+
+
+static eBlockFace MoveToNextBlock(Vector3i & a_CurrentBlock, const Vector3i a_Adjustment, const Vector3d a_Direction, const Vector3d a_Start, const Vector3i a_StepOffset, const eBlockFace a_StepXFace, const eBlockFace a_StepYFace, const eBlockFace a_StepZFace)
+{
+ const auto Coeff = (Vector3d(a_CurrentBlock + a_Adjustment) - a_Start) / a_Direction;
+
+ if (Coeff.x <= Coeff.y)
+ {
+ if (Coeff.x <= Coeff.z)
+ {
+ a_CurrentBlock.x += a_StepOffset.x;
+ return a_StepXFace;
+ }
+ }
+ else if (Coeff.y <= Coeff.z)
+ {
+ a_CurrentBlock.y += a_StepOffset.y;
+ return a_StepYFace;
+ }
+
+ a_CurrentBlock.z += a_StepOffset.z;
+ return a_StepZFace;
}
-bool cLineBlockTracer::LineOfSightTrace(cWorld & a_World, const Vector3d & a_Start, const Vector3d & a_End, int a_Sight)
+bool LineBlockTracer::Trace(const cChunk & a_Chunk, BlockTracerCallbacks & a_Callbacks, Vector3d a_Start, Vector3d a_End)
+{
+ // Clamp the start coords into the world by advancing them along the line:
+ if (a_Start.y < 0)
+ {
+ if (a_End.y < 0)
+ {
+ // Nothing to trace:
+ a_Callbacks.OnNoMoreHits();
+ return true;
+ }
+
+ a_Start = FixStartBelowWorld(a_Start, a_End);
+ a_Callbacks.OnIntoWorld(a_Start);
+ }
+ else if (a_Start.y >= cChunkDef::Height)
+ {
+ if (a_End.y >= cChunkDef::Height)
+ {
+ a_Callbacks.OnNoMoreHits();
+ return true;
+ }
+
+ a_Start = FixStartAboveWorld(a_Start, a_End);
+ a_Callbacks.OnIntoWorld(a_Start);
+ }
+
+ const auto EndPosition = a_End.Floor();
+ const auto Direction = a_End - a_Start;
+ const bool XPositive = a_Start.x <= a_End.x;
+ const bool YPositive = a_Start.y <= a_End.y;
+ const bool ZPositive = a_Start.z <= a_End.z;
+ const auto StepXFace = XPositive ? BLOCK_FACE_XM : BLOCK_FACE_XP;
+ const auto StepYFace = YPositive ? BLOCK_FACE_YM : BLOCK_FACE_YP;
+ const auto StepZFace = ZPositive ? BLOCK_FACE_ZM : BLOCK_FACE_ZP;
+ const Vector3i Adjustment(XPositive ? 1 : 0, YPositive ? 1 : 0, ZPositive ? 1 : 0);
+ const Vector3i StepOffset(XPositive ? 1 : -1, YPositive ? 1 : -1, ZPositive ? 1 : -1);
+
+ auto Position = a_Start.Floor();
+ auto Chunk = const_cast<cChunk &>(a_Chunk).GetNeighborChunk(Position.x, Position.z);
+
+ // We should always start in a valid chunk:
+ ASSERT(Chunk != nullptr);
+
+ // This is guaranteed by FixStartAboveWorld() / FixStartBelowWorld():
+ ASSERT(cChunkDef::IsValidHeight(Position));
+
+ // This is the actual line tracing loop.
+ for (;;)
+ {
+ if (Position == EndPosition)
+ {
+ // We've reached the end
+ a_Callbacks.OnNoMoreHits();
+ return true;
+ }
+
+ // The face of the next block the line just entered.
+ const auto CurrentFace = MoveToNextBlock(Position, Adjustment, Direction, a_Start, StepOffset, StepXFace, StepYFace, StepZFace);
+
+ if (!cChunkDef::IsValidHeight(Position))
+ {
+ // We've gone out of the world, that's the end of this trace.
+ if (a_Callbacks.OnOutOfWorld(CalcXZIntersection(static_cast<double>(Position.y), a_Start, a_End)))
+ {
+ // The callback terminated the trace
+ return false;
+ }
+ a_Callbacks.OnNoMoreHits();
+ return true;
+ }
+
+ // Update the current chunk:
+ Chunk = Chunk->GetNeighborChunk(Position.x, Position.z);
+ if (Chunk == nullptr)
+ {
+ a_Callbacks.OnNoChunk();
+ return false;
+ }
+
+ // Report the current block through the callbacks:
+ if (Chunk->IsValid())
+ {
+ BLOCKTYPE BlockType;
+ NIBBLETYPE BlockMeta;
+ int RelX = Position.x - Chunk->GetPosX() * cChunkDef::Width;
+ int RelZ = Position.z - Chunk->GetPosZ() * cChunkDef::Width;
+ Chunk->GetBlockTypeMeta(RelX, Position.y, RelZ, BlockType, BlockMeta);
+ if (a_Callbacks.OnNextBlock(Position, BlockType, BlockMeta, CurrentFace))
+ {
+ // The callback terminated the trace.
+ return false;
+ }
+ }
+ else if (a_Callbacks.OnNextBlockNoData(Position, CurrentFace))
+ {
+ // The callback terminated the trace.
+ return false;
+ }
+ }
+}
+
+
+
+
+
+bool LineBlockTracer::Trace(cWorld & a_World, BlockTracerCallbacks & a_Callbacks, const Vector3d a_Start, const Vector3d a_End)
+{
+ int BlockX = FloorC(a_Start.x);
+ int BlockZ = FloorC(a_Start.z);
+ int ChunkX, ChunkZ;
+ cChunkDef::BlockToChunk(BlockX, BlockZ, ChunkX, ChunkZ);
+ return a_World.DoWithChunk(ChunkX, ChunkZ, [&a_Callbacks, a_Start, a_End](cChunk & a_Chunk) { return Trace(a_Chunk, a_Callbacks, a_Start, a_End); });
+}
+
+
+
+
+
+bool LineBlockTracer::LineOfSightTrace(cWorld & a_World, const Vector3d & a_Start, const Vector3d & a_End, LineOfSight a_Sight)
{
static class LineOfSightCallbacks:
- public cLineBlockTracer::cCallbacks
+ public BlockTracerCallbacks
{
bool m_IsAirOpaque;
bool m_IsWaterOpaque;
@@ -67,9 +218,9 @@ bool cLineBlockTracer::LineOfSightTrace(cWorld & a_World, const Vector3d & a_Sta
}
}
} callbacks(
- (a_Sight & losAir) == 0,
- (a_Sight & losWater) == 0,
- (a_Sight & losLava) == 0
+ (a_Sight & LineOfSight::Air) == 0,
+ (a_Sight & LineOfSight::Water) == 0,
+ (a_Sight & LineOfSight::Lava) == 0
);
return Trace(a_World, callbacks, a_Start, a_End);
}
@@ -78,7 +229,7 @@ bool cLineBlockTracer::LineOfSightTrace(cWorld & a_World, const Vector3d & a_Sta
-bool cLineBlockTracer::FirstSolidHitTrace(
+bool LineBlockTracer::FirstSolidHitTrace(
cWorld & a_World,
const Vector3d & a_Start, const Vector3d & a_End,
Vector3d & a_HitCoords,
@@ -86,7 +237,7 @@ bool cLineBlockTracer::FirstSolidHitTrace(
)
{
class cSolidHitCallbacks:
- public cCallbacks
+ public BlockTracerCallbacks
{
public:
cSolidHitCallbacks(const Vector3d & a_CBStart, const Vector3d & a_CBEnd, Vector3d & a_CBHitCoords, Vector3i & a_CBHitBlockCoords, eBlockFace & a_CBHitBlockFace):
@@ -129,218 +280,3 @@ bool cLineBlockTracer::FirstSolidHitTrace(
} 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;
- }
- }
-}
-
-
-
diff --git a/src/Physics/Tracers/LineBlockTracer.h b/src/Physics/Tracers/LineBlockTracer.h
index b1dacee20..41b26c354 100644
--- a/src/Physics/Tracers/LineBlockTracer.h
+++ b/src/Physics/Tracers/LineBlockTracer.h
@@ -1,7 +1,7 @@
// LineBlockTracer.h
-// Declares the cLineBlockTracer class representing a cBlockTracer that traces along a straight line between two points
+// Declares the LineBlockTracer namespace representing a tracer that visits every block along a straight line between two points
@@ -15,47 +15,36 @@
-// fwd: Chunk.h
class cChunk;
+class cWorld;
-class cLineBlockTracer:
- public cBlockTracer
+namespace LineBlockTracer
{
- using Super = cBlockTracer;
-
-public:
-
- enum eLineOfSight
+ /* Bit flags used for LineOfSightTrace's Sight parameter. */
+ enum LineOfSight
{
- // Bit flags used for LineOfSightTrace's Sight parameter:
- losAir = 1, // Can see through air
- losWater = 2, // Can see through water
- losLava = 4, // Can see through lava
+ Air = 1, // Can see through air.
+ Water = 2, // Can see through water.
+ Lava = 4, // Can see through lava.
// Common combinations:
- losAirWaterLava = losAir | losWater | losLava,
- losAirWater = losAir | losWater,
+ AirWaterLava = Air | Water | Lava,
+ AirWater = Air | Water,
};
-
- cLineBlockTracer(cWorld & a_World, cCallbacks & a_Callbacks);
-
/** Traces one line between Start and End; returns true if the entire line was traced (until OnNoMoreHits()) */
- bool Trace(Vector3d a_Start, Vector3d a_End);
-
-
- // Utility functions for simple one-line usage:
+ bool Trace(const cChunk & a_Chunk, BlockTracerCallbacks & a_Callbacks, Vector3d a_Start, Vector3d a_End);
/** Traces one line between Start and End; returns true if the entire line was traced (until OnNoMoreHits()) */
- static bool Trace(cWorld & a_World, cCallbacks & a_Callbacks, const Vector3d a_Start, const Vector3d a_End);
+ bool Trace(cWorld & a_World, BlockTracerCallbacks & a_Callbacks, const Vector3d a_Start, const Vector3d a_End);
/** Returns true if the two positions are within line of sight (not obscured by blocks).
a_Sight specifies which blocks are considered transparent for the trace, is an OR-combination of eLineOfSight constants. */
- static bool LineOfSightTrace(cWorld & a_World, const Vector3d & a_Start, const Vector3d & a_End, int a_Sight);
+ bool LineOfSightTrace(cWorld & a_World, const Vector3d & a_Start, const Vector3d & a_End, LineOfSight a_Sight);
/** Traces until the first solid block is hit (or until end, whichever comes first.
If a solid block was hit, returns true and fills a_HitCoords, a_HitBlockCoords and a_HitBlockFace.
@@ -63,48 +52,11 @@ public:
a_HitCoords is the exact coords of the hit,
a_HitBlockCoords are the coords of the solid block that was hit,
a_HitBlockFace is the face of the solid block that was hit. */
- static bool FirstSolidHitTrace(
+ bool FirstSolidHitTrace(
cWorld & a_World,
const Vector3d & a_Start, const Vector3d & a_End,
Vector3d & a_HitCoords,
Vector3i & a_HitBlockCoords,
eBlockFace & a_HitBlockFace
);
-
-protected:
- /** The start point of the trace */
- Vector3d m_Start;
-
- /** The end point of the trace */
- Vector3d m_End;
-
- /** The difference in coords, End - Start */
- Vector3d m_Diff;
-
- /** The increment at which the block coords are going from Start to End; either +1 or -1 */
- Vector3i m_Dir;
-
- /** The current block */
- Vector3i m_Current;
-
- /** The face through which the current block has been entered */
- eBlockFace m_CurrentFace;
-
-
- /** Adjusts the start point above the world to just at the world's top */
- void FixStartAboveWorld(void);
-
- /** Adjusts the start point below the world to just at the world's bottom */
- void FixStartBelowWorld(void);
-
- /** Calculates the XZ coords of an intersection with the specified Yconst plane; assumes that such an intersection exists */
- void CalcXZIntersection(double a_Y, double & a_IntersectX, double & a_IntersectZ);
-
- /** Moves m_Current to the next block on the line; returns false if no move is possible (reached the end) */
- bool MoveToNextBlock(void);
-
- bool ChunkCallback(cChunk * a_Chunk);
-} ;
-
-
-
+}