diff --git a/src/pocketmine/entity/Living.php b/src/pocketmine/entity/Living.php index 5c968b2f0..c5092fe8a 100644 --- a/src/pocketmine/entity/Living.php +++ b/src/pocketmine/entity/Living.php @@ -35,6 +35,7 @@ use pocketmine\event\Timings; use pocketmine\item\Consumable; use pocketmine\item\Item as ItemItem; use pocketmine\math\Vector3; +use pocketmine\math\VoxelRayTrace; use pocketmine\nbt\tag\ByteTag; use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\FloatTag; @@ -44,7 +45,6 @@ use pocketmine\network\mcpe\protocol\EntityEventPacket; use pocketmine\network\mcpe\protocol\MobEffectPacket; use pocketmine\Player; use pocketmine\utils\Binary; -use pocketmine\utils\BlockIterator; use pocketmine\utils\Color; abstract class Living extends Entity implements Damageable{ @@ -689,11 +689,8 @@ abstract class Living extends Entity implements Damageable{ $blocks = []; $nextIndex = 0; - $itr = new BlockIterator($this->level, $this->getPosition(), $this->getDirectionVector(), $this->getEyeHeight(), $maxDistance); - - while($itr->valid()){ - $itr->next(); - $block = $itr->current(); + foreach(VoxelRayTrace::inDirection($this->add(0, $this->eyeHeight, 0), $this->getDirectionVector(), $maxDistance) as $vector3){ + $block = $this->level->getBlockAt($vector3->x, $vector3->y, $vector3->z); $blocks[$nextIndex++] = $block; if($maxLength !== 0 and count($blocks) > $maxLength){ diff --git a/src/pocketmine/level/Level.php b/src/pocketmine/level/Level.php index b1334c996..86cb5c718 100644 --- a/src/pocketmine/level/Level.php +++ b/src/pocketmine/level/Level.php @@ -1245,78 +1245,6 @@ class Level implements ChunkManager, Metadatable{ return $collides; } - /* - public function rayTraceBlocks(Vector3 $pos1, Vector3 $pos2, $flag = false, $flag1 = false, $flag2 = false){ - if(!is_nan($pos1->x) and !is_nan($pos1->y) and !is_nan($pos1->z)){ - if(!is_nan($pos2->x) and !is_nan($pos2->y) and !is_nan($pos2->z)){ - $x1 = (int) $pos1->x; - $y1 = (int) $pos1->y; - $z1 = (int) $pos1->z; - $x2 = (int) $pos2->x; - $y2 = (int) $pos2->y; - $z2 = (int) $pos2->z; - - $block = $this->getBlock(Vector3::createVector($x1, $y1, $z1)); - - if(!$flag1 or $block->getBoundingBox() !== null){ - $ob = $block->calculateIntercept($pos1, $pos2); - if($ob !== null){ - return $ob; - } - } - - $movingObjectPosition = null; - - $k = 200; - - while($k-- >= 0){ - if(is_nan($pos1->x) or is_nan($pos1->y) or is_nan($pos1->z)){ - return null; - } - - if($x1 === $x2 and $y1 === $y2 and $z1 === $z2){ - return $flag2 ? $movingObjectPosition : null; - } - - $flag3 = true; - $flag4 = true; - $flag5 = true; - - $i = 999; - $j = 999; - $k = 999; - - if($x1 > $x2){ - $i = $x2 + 1; - }elseif($x1 < $x2){ - $i = $x2; - }else{ - $flag3 = false; - } - - if($y1 > $y2){ - $j = $y2 + 1; - }elseif($y1 < $y2){ - $j = $y2; - }else{ - $flag4 = false; - } - - if($z1 > $z2){ - $k = $z2 + 1; - }elseif($z1 < $z2){ - $k = $z2; - }else{ - $flag5 = false; - } - - //TODO - } - } - } - } - */ - public function getFullLight(Vector3 $pos) : int{ return $this->getFullLightAt($pos->x, $pos->y, $pos->z); } diff --git a/src/pocketmine/math/VoxelRayTrace.php b/src/pocketmine/math/VoxelRayTrace.php new file mode 100644 index 000000000..9383c36b5 --- /dev/null +++ b/src/pocketmine/math/VoxelRayTrace.php @@ -0,0 +1,138 @@ +add($directionVector->multiply($maxDistance))); + } + + /** + * Performs a ray trace between the start and end coordinates. This returns a Generator which yields Vector3s + * containing the coordinates of voxels it passes through. + * + * This is an implementation of the algorithm described in the link below. + * @link http://www.cse.yorku.ca/~amana/research/grid.pdf + * + * @param Vector3 $start + * @param Vector3 $end + * + * @return \Generator|Vector3[] + */ + public static function betweenPoints(Vector3 $start, Vector3 $end) : \Generator{ + $currentBlock = $start->floor(); + + $directionVector = $end->subtract($start)->normalize(); + if($directionVector->lengthSquared() <= 0){ + throw new \InvalidArgumentException("Start and end points are the same, giving a zero direction vector"); + } + + $radius = $start->distance($end); + + $stepX = $directionVector->x <=> 0; + $stepY = $directionVector->y <=> 0; + $stepZ = $directionVector->z <=> 0; + + //Initialize the step accumulation variables depending how far into the current block the start position is. If + //the start position is on the corner of the block, these will be zero. + $tMaxX = self::rayTraceDistanceToBoundary($start->x, $directionVector->x); + $tMaxY = self::rayTraceDistanceToBoundary($start->y, $directionVector->y); + $tMaxZ = self::rayTraceDistanceToBoundary($start->z, $directionVector->z); + + //The change in t on each axis when taking a step on that axis (always positive). + $tDeltaX = $directionVector->x == 0 ? 0 : $stepX / $directionVector->x; + $tDeltaY = $directionVector->y == 0 ? 0 : $stepY / $directionVector->y; + $tDeltaZ = $directionVector->z == 0 ? 0 : $stepZ / $directionVector->z; + + while(true){ + yield $currentBlock; + + // tMaxX stores the t-value at which we cross a cube boundary along the + // X axis, and similarly for Y and Z. Therefore, choosing the least tMax + // chooses the closest cube boundary. + if($tMaxX < $tMaxY and $tMaxX < $tMaxZ){ + if($tMaxX > $radius){ + break; + } + $currentBlock->x += $stepX; + $tMaxX += $tDeltaX; + }elseif($tMaxY < $tMaxZ){ + if($tMaxY > $radius){ + break; + } + $currentBlock->y += $stepY; + $tMaxY += $tDeltaY; + }else{ + if($tMaxZ > $radius){ + break; + } + $currentBlock->z += $stepZ; + $tMaxZ += $tDeltaZ; + } + } + } + + /** + * Returns the distance that must be travelled on an axis from the start point with the direction vector component to + * cross a block boundary. + * + * For example, given an X coordinate inside a block and the X component of a direction vector, will return the distance + * travelled by that direction component to reach a block with a different X coordinate. + * + * Find the smallest positive t such that s+t*ds is an integer. + * + * @param float $s Starting coordinate + * @param float $ds Direction vector component of the relevant axis + * + * @return float Distance along the ray trace that must be travelled to cross a boundary. + */ + private static function rayTraceDistanceToBoundary(float $s, float $ds) : float{ + if($ds == 0){ + return INF; + } + + if($ds < 0){ + $s = -$s; + $ds = -$ds; + + if(floor($s) == $s){ //exactly at coordinate, will leave the coordinate immediately by moving negatively + return 0; + } + } + + // problem is now s+t*ds = 1 + return (1 - ($s - floor($s))) / $ds; + } +} diff --git a/src/pocketmine/utils/BlockIterator.php b/src/pocketmine/utils/BlockIterator.php deleted file mode 100644 index d5174ba2a..000000000 --- a/src/pocketmine/utils/BlockIterator.php +++ /dev/null @@ -1,304 +0,0 @@ -[3] */ - private $blockQueue; - private $currentBlock = 0; - /** @var Block */ - private $currentBlockObject = null; - private $currentDistance = 0; - private $maxDistanceInt = 0; - - private $secondError; - private $thirdError; - - private $secondStep; - private $thirdStep; - - private $mainFace; - private $secondFace; - private $thirdFace; - - public function __construct(Level $level, Vector3 $start, Vector3 $direction, $yOffset = 0, $maxDistance = 0){ - $this->level = $level; - $this->maxDistance = (int) $maxDistance; - $this->blockQueue = new \SplFixedArray(3); - - $startClone = new Vector3($start->x, $start->y, $start->z); - $startClone->y += $yOffset; - - $this->currentDistance = 0; - - $mainDirection = 0; - $secondDirection = 0; - $thirdDirection = 0; - - $mainPosition = 0; - $secondPosition = 0; - $thirdPosition = 0; - - $pos = new Vector3($startClone->x, $startClone->y, $startClone->z); - $startBlock = $this->level->getBlock(new Vector3(floor($pos->x), floor($pos->y), floor($pos->z))); - - if($this->getXLength($direction) > $mainDirection){ - $this->mainFace = $this->getXFace($direction); - $mainDirection = $this->getXLength($direction); - $mainPosition = $this->getXPosition($direction, $startClone, $startBlock); - - $this->secondFace = $this->getYFace($direction); - $secondDirection = $this->getYLength($direction); - $secondPosition = $this->getYPosition($direction, $startClone, $startBlock); - - $this->thirdFace = $this->getZFace($direction); - $thirdDirection = $this->getZLength($direction); - $thirdPosition = $this->getZPosition($direction, $startClone, $startBlock); - } - if($this->getYLength($direction) > $mainDirection){ - $this->mainFace = $this->getYFace($direction); - $mainDirection = $this->getYLength($direction); - $mainPosition = $this->getYPosition($direction, $startClone, $startBlock); - - $this->secondFace = $this->getZFace($direction); - $secondDirection = $this->getZLength($direction); - $secondPosition = $this->getZPosition($direction, $startClone, $startBlock); - - $this->thirdFace = $this->getXFace($direction); - $thirdDirection = $this->getXLength($direction); - $thirdPosition = $this->getXPosition($direction, $startClone, $startBlock); - } - if($this->getZLength($direction) > $mainDirection){ - $this->mainFace = $this->getZFace($direction); - $mainDirection = $this->getZLength($direction); - $mainPosition = $this->getZPosition($direction, $startClone, $startBlock); - - $this->secondFace = $this->getXFace($direction); - $secondDirection = $this->getXLength($direction); - $secondPosition = $this->getXPosition($direction, $startClone, $startBlock); - - $this->thirdFace = $this->getYFace($direction); - $thirdDirection = $this->getYLength($direction); - $thirdPosition = $this->getYPosition($direction, $startClone, $startBlock); - } - - $d = $mainPosition / $mainDirection; - $secondd = $secondPosition - $secondDirection * $d; - $thirdd = $thirdPosition - $thirdDirection * $d; - - $this->secondError = floor($secondd * self::$gridSize); - $this->secondStep = round($secondDirection / $mainDirection * self::$gridSize); - $this->thirdError = floor($thirdd * self::$gridSize); - $this->thirdStep = round($thirdDirection / $mainDirection * self::$gridSize); - - if($this->secondError + $this->secondStep <= 0){ - $this->secondError = -$this->secondStep + 1; - } - - if($this->thirdError + $this->thirdStep <= 0){ - $this->thirdError = -$this->thirdStep + 1; - } - - $lastBlock = $startBlock->getSide(Vector3::getOppositeSide($this->mainFace)); - - if($this->secondError < 0){ - $this->secondError += self::$gridSize; - $lastBlock = $lastBlock->getSide(Vector3::getOppositeSide($this->secondFace)); - } - - if($this->thirdError < 0){ - $this->thirdError += self::$gridSize; - $lastBlock = $lastBlock->getSide(Vector3::getOppositeSide($this->thirdFace)); - } - - $this->secondError -= self::$gridSize; - $this->thirdError -= self::$gridSize; - - $this->blockQueue[0] = $lastBlock; - - $this->currentBlock = -1; - - $this->scan(); - - $startBlockFound = false; - - for($cnt = $this->currentBlock; $cnt >= 0; --$cnt){ - if($this->blockEquals($this->blockQueue[$cnt], $startBlock)){ - $this->currentBlock = $cnt; - $startBlockFound = true; - break; - } - } - - if(!$startBlockFound){ - throw new \InvalidStateException("Start block missed in BlockIterator"); - } - - $this->maxDistanceInt = round($maxDistance / (sqrt($mainDirection ** 2 + $secondDirection ** 2 + $thirdDirection ** 2) / $mainDirection)); - } - - private function blockEquals(Block $a, Block $b){ - return $a->x === $b->x and $a->y === $b->y and $a->z === $b->z; - } - - private function getXFace(Vector3 $direction){ - return ($direction->x > 0) ? Vector3::SIDE_EAST : Vector3::SIDE_WEST; - } - - private function getYFace(Vector3 $direction){ - return ($direction->y > 0) ? Vector3::SIDE_UP : Vector3::SIDE_DOWN; - } - - private function getZFace(Vector3 $direction){ - return ($direction->z > 0) ? Vector3::SIDE_SOUTH : Vector3::SIDE_NORTH; - } - - private function getXLength(Vector3 $direction){ - return abs($direction->x); - } - - private function getYLength(Vector3 $direction){ - return abs($direction->y); - } - - private function getZLength(Vector3 $direction){ - return abs($direction->z); - } - - private function getPosition($direction, $position, $blockPosition){ - return $direction > 0 ? ($position - $blockPosition) : ($blockPosition + 1 - $position); - } - - private function getXPosition(Vector3 $direction, Vector3 $position, Block $block){ - return $this->getPosition($direction->x, $position->x, $block->x); - } - - private function getYPosition(Vector3 $direction, Vector3 $position, Block $block){ - return $this->getPosition($direction->y, $position->y, $block->y); - } - - private function getZPosition(Vector3 $direction, Vector3 $position, Block $block){ - return $this->getPosition($direction->z, $position->z, $block->z); - } - - public function next(){ - $this->scan(); - - if($this->currentBlock <= -1){ - throw new \OutOfBoundsException; - }else{ - $this->currentBlockObject = $this->blockQueue[$this->currentBlock--]; - } - } - - /** - * @return Block - * - * @throws \OutOfBoundsException - */ - public function current(){ - if($this->currentBlockObject === null){ - throw new \OutOfBoundsException; - } - return $this->currentBlockObject; - } - - public function rewind(){ - throw new \InvalidStateException("BlockIterator doesn't support rewind()"); - } - - public function key(){ - return $this->currentBlock - 1; - } - - public function valid(){ - $this->scan(); - return $this->currentBlock !== -1; - } - - private function scan(){ - if($this->currentBlock >= 0){ - return; - } - - if($this->maxDistance !== 0 and $this->currentDistance > $this->maxDistanceInt){ - $this->end = true; - return; - } - - if($this->end){ - return; - } - - ++$this->currentDistance; - - $this->secondError += $this->secondStep; - $this->thirdError += $this->thirdStep; - - if($this->secondError > 0 and $this->thirdError > 0){ - $this->blockQueue[2] = $this->blockQueue[0]->getSide($this->mainFace); - - if(($this->secondStep * $this->thirdError) < ($this->thirdStep * $this->secondError)){ - $this->blockQueue[1] = $this->blockQueue[2]->getSide($this->secondFace); - $this->blockQueue[0] = $this->blockQueue[1]->getSide($this->thirdFace); - }else{ - $this->blockQueue[1] = $this->blockQueue[2]->getSide($this->thirdFace); - $this->blockQueue[0] = $this->blockQueue[1]->getSide($this->secondFace); - } - - $this->thirdError -= self::$gridSize; - $this->secondError -= self::$gridSize; - $this->currentBlock = 2; - }elseif($this->secondError > 0){ - $this->blockQueue[1] = $this->blockQueue[0]->getSide($this->mainFace); - $this->blockQueue[0] = $this->blockQueue[1]->getSide($this->secondFace); - $this->secondError -= self::$gridSize; - $this->currentBlock = 1; - }elseif($this->thirdError > 0){ - $this->blockQueue[1] = $this->blockQueue[0]->getSide($this->mainFace); - $this->blockQueue[0] = $this->blockQueue[1]->getSide($this->thirdFace); - $this->thirdError -= self::$gridSize; - $this->currentBlock = 1; - }else{ - $this->blockQueue[0] = $this->blockQueue[0]->getSide($this->mainFace); - $this->currentBlock = 0; - } - } -}