mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-04-23 00:55:57 +00:00
Rework World::blockHash() to use morton3d
this uses the unused bits on the Y component to expand the X/Z axes to 2^27 blocks long instead of 2^21 permitted by a regular morton3d code. It does require the sacrifice of an additional bit on the Y axis, but the performance advantages are more than worth it. I'm exploring how realistic it would be to just eliminate blockHash global usage (currently in lighting updates and explosions). This would allow scaling up to 2^32 without larger hashes (morton2d for chunks).
This commit is contained in:
parent
4684333dee
commit
286ac2a975
@ -98,6 +98,7 @@ use function microtime;
|
||||
use function min;
|
||||
use function morton2d_decode;
|
||||
use function morton2d_encode;
|
||||
use function morton3d_decode;
|
||||
use function morton3d_encode;
|
||||
use function mt_rand;
|
||||
use function spl_object_id;
|
||||
@ -274,13 +275,31 @@ class World implements ChunkManager{
|
||||
return morton2d_encode($x, $z);
|
||||
}
|
||||
|
||||
private const MORTON3D_BIT_SIZE = 21;
|
||||
private const BLOCKHASH_Y_BITS = 9;
|
||||
private const BLOCKHASH_Y_MASK = (1 << self::BLOCKHASH_Y_BITS) - 1;
|
||||
private const BLOCKHASH_XZ_MASK = (1 << self::MORTON3D_BIT_SIZE) - 1;
|
||||
private const BLOCKHASH_XZ_EXTRA_BITS = 6;
|
||||
private const BLOCKHASH_XZ_EXTRA_MASK = (1 << self::BLOCKHASH_XZ_EXTRA_BITS) - 1;
|
||||
private const BLOCKHASH_XZ_SIGN_SHIFT = 64 - self::MORTON3D_BIT_SIZE - self::BLOCKHASH_XZ_EXTRA_BITS;
|
||||
private const BLOCKHASH_X_SHIFT = self::BLOCKHASH_Y_BITS;
|
||||
private const BLOCKHASH_Z_SHIFT = self::BLOCKHASH_X_SHIFT + self::BLOCKHASH_XZ_EXTRA_BITS;
|
||||
|
||||
public static function blockHash(int $x, int $y, int $z) : int{
|
||||
//TODO: switch this to use morton3d (21 bits each only allows for 2M blocks, but Y would have 12 spare bits)
|
||||
$shiftedY = $y - self::HALF_Y_MAX;
|
||||
if($shiftedY < -512 or $shiftedY >= 512){
|
||||
$shiftedY = $y + self::HALF_Y_MAX;
|
||||
if(($shiftedY & (~0 << self::BLOCKHASH_Y_BITS)) !== 0){
|
||||
throw new \InvalidArgumentException("Y coordinate $y is out of range!");
|
||||
}
|
||||
return (($x & 0x7ffffff) << 37) | (($shiftedY & 0x3ff) << 27) | ($z & 0x7ffffff);
|
||||
//morton3d gives us 21 bits on each axis, but the Y axis only requires 9
|
||||
//so we use the extra space on Y (12 bits) and add 6 extra bits from X and Z instead.
|
||||
//if we ever need more space for Y (e.g. due to expansion), take bits from X/Z to compensate.
|
||||
return morton3d_encode(
|
||||
$x & self::BLOCKHASH_XZ_MASK,
|
||||
($shiftedY /* & self::BLOCKHASH_Y_MASK */) |
|
||||
((($x >> self::MORTON3D_BIT_SIZE) & self::BLOCKHASH_XZ_EXTRA_MASK) << self::BLOCKHASH_X_SHIFT) |
|
||||
((($z >> self::MORTON3D_BIT_SIZE) & self::BLOCKHASH_XZ_EXTRA_MASK) << self::BLOCKHASH_Z_SHIFT),
|
||||
$z & self::BLOCKHASH_XZ_MASK
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -291,10 +310,14 @@ class World implements ChunkManager{
|
||||
}
|
||||
|
||||
public static function getBlockXYZ(int $hash, ?int &$x, ?int &$y, ?int &$z) : void{
|
||||
//TODO: switch this to use morton3d
|
||||
$x = $hash >> 37;
|
||||
$y = ($hash << 27 >> 54) + self::HALF_Y_MAX;
|
||||
$z = $hash << 37 >> 37;
|
||||
[$baseX, $baseY, $baseZ] = morton3d_decode($hash);
|
||||
|
||||
$extraX = ((($baseY >> self::BLOCKHASH_X_SHIFT) & self::BLOCKHASH_XZ_EXTRA_MASK) << self::MORTON3D_BIT_SIZE);
|
||||
$extraZ = ((($baseY >> self::BLOCKHASH_Z_SHIFT) & self::BLOCKHASH_XZ_EXTRA_MASK) << self::MORTON3D_BIT_SIZE);
|
||||
|
||||
$x = (($baseX & self::BLOCKHASH_XZ_MASK) | $extraX) << self::BLOCKHASH_XZ_SIGN_SHIFT >> self::BLOCKHASH_XZ_SIGN_SHIFT;
|
||||
$y = ($baseY & self::BLOCKHASH_Y_MASK) - self::HALF_Y_MAX;
|
||||
$z = (($baseZ & self::BLOCKHASH_XZ_MASK) | $extraZ) << self::BLOCKHASH_XZ_SIGN_SHIFT >> self::BLOCKHASH_XZ_SIGN_SHIFT;
|
||||
}
|
||||
|
||||
public static function getXZ(int $hash, ?int &$x, ?int &$z) : void{
|
||||
|
Loading…
x
Reference in New Issue
Block a user