amulet-core 1.9.19__py3-none-any.whl → 1.9.20__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of amulet-core might be problematic. Click here for more details.
- amulet/__init__.py +27 -27
- amulet/__pyinstaller/__init__.py +2 -2
- amulet/__pyinstaller/hook-amulet.py +4 -4
- amulet/_version.py +21 -21
- amulet/api/__init__.py +2 -2
- amulet/api/abstract_base_entity.py +128 -128
- amulet/api/block.py +630 -630
- amulet/api/block_entity.py +71 -71
- amulet/api/cache.py +107 -107
- amulet/api/chunk/__init__.py +6 -6
- amulet/api/chunk/biomes.py +207 -207
- amulet/api/chunk/block_entity_dict.py +175 -175
- amulet/api/chunk/blocks.py +46 -46
- amulet/api/chunk/chunk.py +389 -389
- amulet/api/chunk/entity_list.py +75 -75
- amulet/api/chunk/status.py +167 -167
- amulet/api/data_types/__init__.py +4 -4
- amulet/api/data_types/generic_types.py +4 -4
- amulet/api/data_types/operation_types.py +16 -16
- amulet/api/data_types/world_types.py +49 -49
- amulet/api/data_types/wrapper_types.py +71 -71
- amulet/api/entity.py +74 -74
- amulet/api/errors.py +119 -119
- amulet/api/history/__init__.py +36 -36
- amulet/api/history/base/__init__.py +3 -3
- amulet/api/history/base/base_history.py +26 -26
- amulet/api/history/base/history_manager.py +63 -63
- amulet/api/history/base/revision_manager.py +73 -73
- amulet/api/history/changeable.py +15 -15
- amulet/api/history/data_types.py +7 -7
- amulet/api/history/history_manager/__init__.py +3 -3
- amulet/api/history/history_manager/container.py +102 -102
- amulet/api/history/history_manager/database.py +279 -279
- amulet/api/history/history_manager/meta.py +93 -93
- amulet/api/history/history_manager/object.py +116 -116
- amulet/api/history/revision_manager/__init__.py +2 -2
- amulet/api/history/revision_manager/disk.py +33 -33
- amulet/api/history/revision_manager/ram.py +12 -12
- amulet/api/item.py +75 -75
- amulet/api/level/__init__.py +4 -4
- amulet/api/level/base_level/__init__.py +1 -1
- amulet/api/level/base_level/base_level.py +1035 -1026
- amulet/api/level/base_level/chunk_manager.py +227 -227
- amulet/api/level/base_level/clone.py +389 -389
- amulet/api/level/base_level/player_manager.py +101 -101
- amulet/api/level/immutable_structure/__init__.py +1 -1
- amulet/api/level/immutable_structure/immutable_structure.py +94 -94
- amulet/api/level/immutable_structure/void_format_wrapper.py +117 -117
- amulet/api/level/structure.py +22 -22
- amulet/api/level/world.py +19 -19
- amulet/api/partial_3d_array/__init__.py +2 -2
- amulet/api/partial_3d_array/base_partial_3d_array.py +263 -263
- amulet/api/partial_3d_array/bounded_partial_3d_array.py +528 -528
- amulet/api/partial_3d_array/data_types.py +15 -15
- amulet/api/partial_3d_array/unbounded_partial_3d_array.py +229 -229
- amulet/api/partial_3d_array/util.py +152 -152
- amulet/api/player.py +65 -65
- amulet/api/registry/__init__.py +2 -2
- amulet/api/registry/base_registry.py +34 -34
- amulet/api/registry/biome_manager.py +153 -153
- amulet/api/registry/block_manager.py +156 -156
- amulet/api/selection/__init__.py +2 -2
- amulet/api/selection/abstract_selection.py +315 -315
- amulet/api/selection/box.py +805 -805
- amulet/api/selection/group.py +488 -488
- amulet/api/structure.py +37 -37
- amulet/api/wrapper/__init__.py +8 -8
- amulet/api/wrapper/chunk/interface.py +441 -441
- amulet/api/wrapper/chunk/translator.py +567 -567
- amulet/api/wrapper/format_wrapper.py +772 -772
- amulet/api/wrapper/structure_format_wrapper.py +116 -116
- amulet/api/wrapper/world_format_wrapper.py +63 -63
- amulet/level/__init__.py +1 -1
- amulet/level/formats/anvil_forge_world.py +40 -40
- amulet/level/formats/anvil_world/__init__.py +3 -3
- amulet/level/formats/anvil_world/_sector_manager.py +291 -384
- amulet/level/formats/anvil_world/data_pack/__init__.py +2 -2
- amulet/level/formats/anvil_world/data_pack/data_pack.py +224 -224
- amulet/level/formats/anvil_world/data_pack/data_pack_manager.py +77 -77
- amulet/level/formats/anvil_world/dimension.py +177 -177
- amulet/level/formats/anvil_world/format.py +769 -769
- amulet/level/formats/anvil_world/region.py +384 -384
- amulet/level/formats/construction/__init__.py +3 -3
- amulet/level/formats/construction/format_wrapper.py +515 -515
- amulet/level/formats/construction/interface.py +134 -134
- amulet/level/formats/construction/section.py +60 -60
- amulet/level/formats/construction/util.py +165 -165
- amulet/level/formats/leveldb_world/__init__.py +3 -3
- amulet/level/formats/leveldb_world/chunk.py +33 -33
- amulet/level/formats/leveldb_world/dimension.py +385 -419
- amulet/level/formats/leveldb_world/format.py +659 -641
- amulet/level/formats/leveldb_world/interface/chunk/__init__.py +36 -36
- amulet/level/formats/leveldb_world/interface/chunk/base_leveldb_interface.py +836 -836
- amulet/level/formats/leveldb_world/interface/chunk/generate_interface.py +31 -31
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_0.py +30 -30
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_1.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_10.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_11.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_12.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_13.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_14.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_15.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_16.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_17.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_18.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_19.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_2.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_20.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_21.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_22.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_23.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_24.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_25.py +24 -24
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_26.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_27.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_28.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_29.py +33 -33
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_3.py +57 -57
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_30.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_31.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_32.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_33.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_34.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_35.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_36.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_37.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_38.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_39.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_4.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_40.py +16 -16
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_5.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_6.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_7.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_8.py +180 -180
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_9.py +18 -18
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_chunk_versions.py +79 -79
- amulet/level/formats/mcstructure/__init__.py +3 -3
- amulet/level/formats/mcstructure/chunk.py +50 -50
- amulet/level/formats/mcstructure/format_wrapper.py +408 -408
- amulet/level/formats/mcstructure/interface.py +175 -175
- amulet/level/formats/schematic/__init__.py +3 -3
- amulet/level/formats/schematic/chunk.py +55 -55
- amulet/level/formats/schematic/data_types.py +4 -4
- amulet/level/formats/schematic/format_wrapper.py +373 -373
- amulet/level/formats/schematic/interface.py +142 -142
- amulet/level/formats/sponge_schem/__init__.py +4 -4
- amulet/level/formats/sponge_schem/chunk.py +62 -62
- amulet/level/formats/sponge_schem/format_wrapper.py +463 -463
- amulet/level/formats/sponge_schem/interface.py +118 -118
- amulet/level/formats/sponge_schem/varint/__init__.py +1 -1
- amulet/level/formats/sponge_schem/varint/varint.py +87 -87
- amulet/level/interfaces/chunk/anvil/anvil_0.py +72 -72
- amulet/level/interfaces/chunk/anvil/anvil_1444.py +336 -336
- amulet/level/interfaces/chunk/anvil/anvil_1466.py +94 -94
- amulet/level/interfaces/chunk/anvil/anvil_1467.py +37 -37
- amulet/level/interfaces/chunk/anvil/anvil_1484.py +20 -20
- amulet/level/interfaces/chunk/anvil/anvil_1503.py +20 -20
- amulet/level/interfaces/chunk/anvil/anvil_1519.py +34 -34
- amulet/level/interfaces/chunk/anvil/anvil_1901.py +20 -20
- amulet/level/interfaces/chunk/anvil/anvil_1908.py +20 -20
- amulet/level/interfaces/chunk/anvil/anvil_1912.py +21 -21
- amulet/level/interfaces/chunk/anvil/anvil_1934.py +20 -20
- amulet/level/interfaces/chunk/anvil/anvil_2203.py +69 -69
- amulet/level/interfaces/chunk/anvil/anvil_2529.py +19 -19
- amulet/level/interfaces/chunk/anvil/anvil_2681.py +76 -76
- amulet/level/interfaces/chunk/anvil/anvil_2709.py +19 -19
- amulet/level/interfaces/chunk/anvil/anvil_2844.py +267 -267
- amulet/level/interfaces/chunk/anvil/anvil_3463.py +19 -19
- amulet/level/interfaces/chunk/anvil/anvil_na.py +607 -607
- amulet/level/interfaces/chunk/anvil/base_anvil_interface.py +326 -326
- amulet/level/load.py +59 -59
- amulet/level/loader.py +95 -95
- amulet/level/translators/chunk/bedrock/__init__.py +267 -267
- amulet/level/translators/chunk/bedrock/bedrock_nbt_blockstate_translator.py +46 -46
- amulet/level/translators/chunk/bedrock/bedrock_numerical_translator.py +39 -39
- amulet/level/translators/chunk/bedrock/bedrock_psudo_numerical_translator.py +37 -37
- amulet/level/translators/chunk/java/java_1_18_translator.py +40 -40
- amulet/level/translators/chunk/java/java_blockstate_translator.py +94 -94
- amulet/level/translators/chunk/java/java_numerical_translator.py +62 -62
- amulet/libs/leveldb/__init__.py +7 -7
- amulet/operations/__init__.py +5 -5
- amulet/operations/clone.py +18 -18
- amulet/operations/delete_chunk.py +32 -32
- amulet/operations/fill.py +30 -30
- amulet/operations/paste.py +65 -65
- amulet/operations/replace.py +58 -58
- amulet/utils/__init__.py +14 -14
- amulet/utils/format_utils.py +41 -41
- amulet/utils/generator.py +15 -15
- amulet/utils/matrix.py +243 -243
- amulet/utils/numpy_helpers.py +46 -46
- amulet/utils/world_utils.py +349 -349
- {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/METADATA +97 -97
- amulet_core-1.9.20.dist-info/RECORD +208 -0
- amulet_core-1.9.19.dist-info/RECORD +0 -208
- {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/WHEEL +0 -0
- {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/entry_points.txt +0 -0
- {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/top_level.txt +0 -0
amulet/utils/world_utils.py
CHANGED
|
@@ -1,349 +1,349 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import math
|
|
4
|
-
import sys
|
|
5
|
-
import gzip
|
|
6
|
-
from io import StringIO
|
|
7
|
-
from typing import Tuple, Optional
|
|
8
|
-
import numpy
|
|
9
|
-
from numpy import ndarray, zeros, uint8
|
|
10
|
-
from amulet.api.data_types import ChunkCoordinates
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
# depreciated and will be removed
|
|
14
|
-
SECTOR_BYTES = 4096
|
|
15
|
-
SECTOR_INTS = SECTOR_BYTES / 4
|
|
16
|
-
CHUNK_HEADER_SIZE = 5
|
|
17
|
-
VERSION_GZIP = 1
|
|
18
|
-
VERSION_DEFLATE = 2
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def block_coords_to_chunk_coords(
|
|
22
|
-
*args: int, sub_chunk_size: int = 16
|
|
23
|
-
) -> Tuple[int, ...]:
|
|
24
|
-
"""
|
|
25
|
-
Converts the supplied block coordinates into chunk coordinates
|
|
26
|
-
|
|
27
|
-
:param args: The coordinate of the block(s)
|
|
28
|
-
:param sub_chunk_size: The dimension of the chunk (Optional. Default 16)
|
|
29
|
-
:return: The resulting chunk coordinates in (x, z) order
|
|
30
|
-
"""
|
|
31
|
-
return tuple(int(math.floor(coord / sub_chunk_size)) for coord in args)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def chunk_coords_to_block_coords(
|
|
35
|
-
x: int, z: int, chunk_x_size: int = 16, chunk_z_size: int = 16
|
|
36
|
-
) -> ChunkCoordinates:
|
|
37
|
-
"""
|
|
38
|
-
Converts the supplied chunk coordinates into block coordinates
|
|
39
|
-
|
|
40
|
-
:param x: The x coordinate of the chunk
|
|
41
|
-
:param z: The z coordinate of the chunk
|
|
42
|
-
:param chunk_x_size: The dimension of the chunk in the x direction (Optional. Default 16)
|
|
43
|
-
:param chunk_z_size: The dimension of the chunk in the z direction (Optional. Default 16)
|
|
44
|
-
:return: The resulting block coordinates in (x, z) order
|
|
45
|
-
"""
|
|
46
|
-
return x * chunk_x_size, z * chunk_z_size
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def chunk_coords_to_region_coords(cx: int, cz: int) -> ChunkCoordinates:
|
|
50
|
-
"""
|
|
51
|
-
Converts the supplied chunk coordinates into region coordinates
|
|
52
|
-
|
|
53
|
-
:param cx: The x coordinate of the chunk
|
|
54
|
-
:param cz: The z coordinate of the chunk
|
|
55
|
-
:return: The resulting region coordinates in (x, z) order
|
|
56
|
-
"""
|
|
57
|
-
return cx >> 5, cz >> 5
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def region_coords_to_chunk_coords(rx: int, rz: int) -> ChunkCoordinates:
|
|
61
|
-
"""
|
|
62
|
-
Converts the supplied region coordinates into the minimum chunk coordinates of that region
|
|
63
|
-
|
|
64
|
-
:param rx: The x coordinate of the region
|
|
65
|
-
:param rz: The y coordinate of the region
|
|
66
|
-
:return: The resulting minimum chunk coordinates of that region in (x, z) order
|
|
67
|
-
"""
|
|
68
|
-
return rx << 5, rz << 5
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
def blocks_slice_to_chunk_slice(
|
|
72
|
-
blocks_slice: slice, chunk_shape: int, chunk_coord: int
|
|
73
|
-
) -> slice:
|
|
74
|
-
"""
|
|
75
|
-
Converts the supplied blocks slice into chunk slice
|
|
76
|
-
|
|
77
|
-
:param blocks_slice: The slice of the blocks
|
|
78
|
-
:param chunk_shape: The shape of the chunk in this direction
|
|
79
|
-
:param chunk_coord: The coordinate of the chunk in this direction
|
|
80
|
-
:return: The resulting chunk slice
|
|
81
|
-
"""
|
|
82
|
-
return slice(
|
|
83
|
-
min(max(0, blocks_slice.start - chunk_coord * chunk_shape), chunk_shape),
|
|
84
|
-
min(max(0, blocks_slice.stop - chunk_coord * chunk_shape), chunk_shape),
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
def gunzip(data):
|
|
89
|
-
"""
|
|
90
|
-
Decompresses data that is in Gzip format
|
|
91
|
-
"""
|
|
92
|
-
return gzip.GzipFile(fileobj=StringIO(data)).read()
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
def from_nibble_array(arr: ndarray) -> ndarray:
|
|
96
|
-
"""
|
|
97
|
-
Unpacks a flat nibble array into a full size numpy array
|
|
98
|
-
|
|
99
|
-
:param arr: The nibble array
|
|
100
|
-
:return: The resulting array
|
|
101
|
-
"""
|
|
102
|
-
shape = arr.size
|
|
103
|
-
|
|
104
|
-
new_arr = zeros((shape * 2), dtype=uint8)
|
|
105
|
-
|
|
106
|
-
new_arr[::2] = arr & 0xF
|
|
107
|
-
new_arr[1::2] = arr >> 4
|
|
108
|
-
|
|
109
|
-
return new_arr
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
def to_nibble_array(arr: ndarray) -> ndarray:
|
|
113
|
-
"""
|
|
114
|
-
packs a full size numpy array into a nibble array.
|
|
115
|
-
|
|
116
|
-
:param arr: Full numpy array
|
|
117
|
-
:return: The nibble array
|
|
118
|
-
"""
|
|
119
|
-
arr = arr.ravel()
|
|
120
|
-
return (arr[::2] + (arr[1::2] << 4)).astype("uint8")
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
"""
|
|
124
|
-
Minecraft Java edition stores the block and height arrays in a compacted long array format.
|
|
125
|
-
The format stores one or more entries per long, using the fewest number of bits required to store the data.
|
|
126
|
-
There are two storage methods, the compact version was used prior to 1.16 and the less compact version in 1.16 and above.
|
|
127
|
-
Apparently the less compact version is quicker to pack and unpack.
|
|
128
|
-
The compact version effectively stores the values as a bit array spanning one or more values in the long array.
|
|
129
|
-
There may be some padding if the bit array does not fill all the long values. (The letter "P" signifies an unused padding bit)
|
|
130
|
-
PPAAAAAAAAABBBBBBBBBCCCCCCCCCDDDDDDDDDEEEEEEEEEFFFFFFFFFGGGGGGGG GHHHHHHHHHIIIIIIIIIJJJJJJJJJKKKKKKKKKLLLLLLLLLMMMMMMMMMOOOOOOOOO
|
|
131
|
-
The less compact version does not allow entries to straddle long values. Instead, if required, there is padding within each long.
|
|
132
|
-
PAAAAAAAAABBBBBBBBBCCCCCCCCCDDDDDDDDDEEEEEEEEEFFFFFFFFFGGGGGGGGG PHHHHHHHHHIIIIIIIIIJJJJJJJJJKKKKKKKKKLLLLLLLLLMMMMMMMMMOOOOOOOOO
|
|
133
|
-
|
|
134
|
-
The functions below can be used to pack and unpack both formats.
|
|
135
|
-
"""
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
def decode_long_array(
|
|
139
|
-
long_array: numpy.ndarray,
|
|
140
|
-
size: int,
|
|
141
|
-
bits_per_entry: int,
|
|
142
|
-
dense=True,
|
|
143
|
-
signed: bool = False,
|
|
144
|
-
) -> numpy.ndarray:
|
|
145
|
-
"""
|
|
146
|
-
Decode a long array (from BlockStates or Heightmaps)
|
|
147
|
-
|
|
148
|
-
:param long_array: Encoded long array
|
|
149
|
-
:param size: The expected size of the returned array
|
|
150
|
-
:param bits_per_entry: The number of bits per entry in the encoded array.
|
|
151
|
-
:param dense: If true the long arrays will be treated as a bit stream. If false they are distinct values with padding
|
|
152
|
-
:param signed: Should the returned array be signed.
|
|
153
|
-
:return: Decoded array as numpy array
|
|
154
|
-
"""
|
|
155
|
-
# validate the inputs and throw an error if there is a problem
|
|
156
|
-
if not isinstance(bits_per_entry, int):
|
|
157
|
-
raise ValueError(f"The bits_per_entry input must be an int.")
|
|
158
|
-
|
|
159
|
-
assert (
|
|
160
|
-
1 <= bits_per_entry <= 64
|
|
161
|
-
), f"bits_per_entry must be between 1 and 64 inclusive. Got {bits_per_entry}"
|
|
162
|
-
|
|
163
|
-
# force the array to be a signed long array
|
|
164
|
-
long_array = long_array.astype(">q")
|
|
165
|
-
|
|
166
|
-
if dense:
|
|
167
|
-
expected_len = math.ceil(size * bits_per_entry / 64)
|
|
168
|
-
else:
|
|
169
|
-
expected_len = math.ceil(size / (64 // bits_per_entry))
|
|
170
|
-
if len(long_array) != expected_len:
|
|
171
|
-
raise Exception(
|
|
172
|
-
f"{'Dense e' if dense else 'E'}ncoded long array with {bits_per_entry} bits per entry should contain {expected_len} longs but got {len(long_array)}."
|
|
173
|
-
)
|
|
174
|
-
|
|
175
|
-
# unpack the long array into a bit array
|
|
176
|
-
bits = numpy.unpackbits(long_array[::-1].astype(">i8").view("uint8"))
|
|
177
|
-
if dense:
|
|
178
|
-
if bits.size % bits_per_entry:
|
|
179
|
-
# if the array is densely packed and there is extra padding, remove it
|
|
180
|
-
bits = bits[bits.size % bits_per_entry :]
|
|
181
|
-
else:
|
|
182
|
-
# if not densely packed remove the padding per long
|
|
183
|
-
entry_per_long = 64 // bits_per_entry
|
|
184
|
-
bits = bits.reshape(-1, 64)[:, -entry_per_long * bits_per_entry :]
|
|
185
|
-
|
|
186
|
-
byte_length = 2 ** math.ceil(math.log(math.ceil(bits_per_entry / 8), 2))
|
|
187
|
-
dtype = {1: "B", 2: ">H", 4: ">I", 8: ">Q"}[byte_length]
|
|
188
|
-
|
|
189
|
-
# pad the bits to fill one of the above data types
|
|
190
|
-
arr = numpy.packbits(
|
|
191
|
-
numpy.pad(
|
|
192
|
-
bits.reshape(-1, bits_per_entry)[-size:, :],
|
|
193
|
-
[(0, 0), (byte_length * 8 - bits_per_entry, 0)],
|
|
194
|
-
"constant",
|
|
195
|
-
)
|
|
196
|
-
).view(dtype=dtype)[::-1]
|
|
197
|
-
if signed:
|
|
198
|
-
# convert to a signed array if requested
|
|
199
|
-
sarray = arr.astype({1: "b", 2: ">h", 4: ">i", 8: ">q"}[byte_length])
|
|
200
|
-
if bits_per_entry < 64:
|
|
201
|
-
mask = arr >= 2 ** (bits_per_entry - 1)
|
|
202
|
-
sarray[mask] = numpy.subtract(
|
|
203
|
-
arr[mask], 2**bits_per_entry, dtype=numpy.int64
|
|
204
|
-
)
|
|
205
|
-
arr = sarray
|
|
206
|
-
return arr
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
def encode_long_array(
|
|
210
|
-
array: numpy.ndarray,
|
|
211
|
-
bits_per_entry: Optional[int] = None,
|
|
212
|
-
dense: bool = True,
|
|
213
|
-
min_bits_per_entry=1,
|
|
214
|
-
) -> numpy.ndarray:
|
|
215
|
-
"""
|
|
216
|
-
Encode a long array (from BlockStates or Heightmaps)
|
|
217
|
-
|
|
218
|
-
:param array: A numpy array of the data to be encoded.
|
|
219
|
-
:param bits_per_entry: The number of bits to use to store each value. If left as None will use the smallest bits per entry.
|
|
220
|
-
:param dense: If true the long arrays will be treated as a bit stream. If false they are distinct values with padding
|
|
221
|
-
:param min_bits_per_entry: The mimimum value that bits_per_entry can be. If it is less than this it will be capped at this value.
|
|
222
|
-
:return: Encoded array as numpy array
|
|
223
|
-
"""
|
|
224
|
-
assert (
|
|
225
|
-
1 <= min_bits_per_entry <= 64
|
|
226
|
-
), f"min_bits_per_entry must be between 1 and 64 inclusive. Got {bits_per_entry}"
|
|
227
|
-
# cast to a signed longlong array
|
|
228
|
-
array = array.astype(">q")
|
|
229
|
-
# work out how many bits are required to store the
|
|
230
|
-
required_bits_per_entry = max(
|
|
231
|
-
max(
|
|
232
|
-
int(numpy.amin(array)).bit_length(),
|
|
233
|
-
int(numpy.amax(array)).bit_length(),
|
|
234
|
-
),
|
|
235
|
-
min_bits_per_entry,
|
|
236
|
-
)
|
|
237
|
-
if bits_per_entry is None:
|
|
238
|
-
# if a bit depth has not been requested use the minimum required
|
|
239
|
-
bits_per_entry = required_bits_per_entry
|
|
240
|
-
elif isinstance(bits_per_entry, int):
|
|
241
|
-
assert (
|
|
242
|
-
1 <= bits_per_entry <= 64
|
|
243
|
-
), f"bits_per_entry must be between 1 and 64 inclusive. Got {bits_per_entry}"
|
|
244
|
-
# if a bit depth has been set and it is smaller than what is required throw an error
|
|
245
|
-
if required_bits_per_entry > bits_per_entry:
|
|
246
|
-
raise Exception(
|
|
247
|
-
f"The array requires at least {required_bits_per_entry} bits per value which is more than the specified {bits_per_entry} bits"
|
|
248
|
-
)
|
|
249
|
-
else:
|
|
250
|
-
raise ValueError(
|
|
251
|
-
"bits_per_entry must be an int between 1 and 64 inclusive or None."
|
|
252
|
-
)
|
|
253
|
-
# make the negative values positive to make bit storage easier
|
|
254
|
-
uarray = array.astype(">Q")
|
|
255
|
-
if bits_per_entry < 64:
|
|
256
|
-
mask = array < 0
|
|
257
|
-
uarray[mask] = numpy.add(
|
|
258
|
-
array[mask], 2**bits_per_entry, dtype=numpy.uint64, casting="unsafe"
|
|
259
|
-
)
|
|
260
|
-
array = uarray
|
|
261
|
-
|
|
262
|
-
# unpack the individual values into a bit array
|
|
263
|
-
bits: numpy.ndarray = numpy.unpackbits(
|
|
264
|
-
numpy.ascontiguousarray(array[::-1]).view("uint8")
|
|
265
|
-
).reshape(-1, 64)[:, -bits_per_entry:]
|
|
266
|
-
if dense:
|
|
267
|
-
if bits.size % 64:
|
|
268
|
-
# if the bit array does not fill a whole long
|
|
269
|
-
# add padding to the last long if required
|
|
270
|
-
bits = numpy.pad(
|
|
271
|
-
bits.ravel(),
|
|
272
|
-
[(64 - (bits.size % 64), 0)],
|
|
273
|
-
"constant",
|
|
274
|
-
)
|
|
275
|
-
else:
|
|
276
|
-
# if the array is not dense add padding
|
|
277
|
-
entry_per_long = 64 // bits_per_entry
|
|
278
|
-
if bits.shape[0] % entry_per_long:
|
|
279
|
-
# add padding to the last long if required
|
|
280
|
-
bits = numpy.pad(
|
|
281
|
-
bits,
|
|
282
|
-
[(entry_per_long - (bits.shape[0] % entry_per_long), 0), (0, 0)],
|
|
283
|
-
"constant",
|
|
284
|
-
)
|
|
285
|
-
# add padding for each long
|
|
286
|
-
bits = numpy.pad(
|
|
287
|
-
bits.reshape(-1, bits_per_entry * entry_per_long),
|
|
288
|
-
[(0, 0), (64 - bits_per_entry * entry_per_long, 0)],
|
|
289
|
-
"constant",
|
|
290
|
-
)
|
|
291
|
-
|
|
292
|
-
# pack the bits into a long array
|
|
293
|
-
return numpy.packbits(bits).view(dtype=">q")[::-1]
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
def get_size(obj, seen=None):
|
|
297
|
-
"""Recursively finds size of objects"""
|
|
298
|
-
size = sys.getsizeof(obj)
|
|
299
|
-
if seen is None:
|
|
300
|
-
seen = set()
|
|
301
|
-
obj_id = id(obj)
|
|
302
|
-
if obj_id in seen:
|
|
303
|
-
return 0
|
|
304
|
-
|
|
305
|
-
# Important mark as seen *before* entering recursion to gracefully handle
|
|
306
|
-
# self-referential objects
|
|
307
|
-
seen.add(obj_id)
|
|
308
|
-
if isinstance(obj, dict):
|
|
309
|
-
size += sum([get_size(v, seen) for v in obj.values()])
|
|
310
|
-
size += sum([get_size(k, seen) for k in obj.keys()])
|
|
311
|
-
elif hasattr(obj, "__dict__"):
|
|
312
|
-
size += get_size(obj.__dict__, seen)
|
|
313
|
-
elif hasattr(obj, "__iter__") and not isinstance(obj, (str, bytes, bytearray)):
|
|
314
|
-
size += sum([get_size(i, seen) for i in obj])
|
|
315
|
-
return size
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
def get_smallest_dtype(arr: ndarray, uint: bool = True) -> int:
|
|
319
|
-
"""
|
|
320
|
-
Returns the smallest dtype (number) that the array can afford
|
|
321
|
-
|
|
322
|
-
:param arr: The array to check on
|
|
323
|
-
:param uint: Should the array fit in uint or not (default: True)
|
|
324
|
-
:return: The number of bits all the elements can be represented with
|
|
325
|
-
"""
|
|
326
|
-
possible_dtypes = (2**x for x in range(3, 8))
|
|
327
|
-
max_number = numpy.amax(arr)
|
|
328
|
-
if not uint:
|
|
329
|
-
max_number = max_number * 2
|
|
330
|
-
if max_number == 0:
|
|
331
|
-
max_number = 1
|
|
332
|
-
return next(dtype for dtype in possible_dtypes if dtype > math.log(max_number, 2))
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
def entity_position_to_chunk_coordinates(
|
|
336
|
-
entity_coordinates: Tuple[float, float, float]
|
|
337
|
-
):
|
|
338
|
-
return (
|
|
339
|
-
int(math.floor(entity_coordinates[0])) >> 4,
|
|
340
|
-
int(math.floor(entity_coordinates[2])) >> 4,
|
|
341
|
-
)
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
def fast_unique(array: numpy.ndarray) -> Tuple[numpy.ndarray, numpy.ndarray]:
|
|
345
|
-
uni = numpy.unique(array)
|
|
346
|
-
lut = numpy.zeros(numpy.amax(uni) + 1, dtype=numpy.uint32)
|
|
347
|
-
lut[uni] = numpy.arange(uni.size)
|
|
348
|
-
inv = lut[array]
|
|
349
|
-
return uni, inv
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
import sys
|
|
5
|
+
import gzip
|
|
6
|
+
from io import StringIO
|
|
7
|
+
from typing import Tuple, Optional
|
|
8
|
+
import numpy
|
|
9
|
+
from numpy import ndarray, zeros, uint8
|
|
10
|
+
from amulet.api.data_types import ChunkCoordinates
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# depreciated and will be removed
|
|
14
|
+
SECTOR_BYTES = 4096
|
|
15
|
+
SECTOR_INTS = SECTOR_BYTES / 4
|
|
16
|
+
CHUNK_HEADER_SIZE = 5
|
|
17
|
+
VERSION_GZIP = 1
|
|
18
|
+
VERSION_DEFLATE = 2
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def block_coords_to_chunk_coords(
|
|
22
|
+
*args: int, sub_chunk_size: int = 16
|
|
23
|
+
) -> Tuple[int, ...]:
|
|
24
|
+
"""
|
|
25
|
+
Converts the supplied block coordinates into chunk coordinates
|
|
26
|
+
|
|
27
|
+
:param args: The coordinate of the block(s)
|
|
28
|
+
:param sub_chunk_size: The dimension of the chunk (Optional. Default 16)
|
|
29
|
+
:return: The resulting chunk coordinates in (x, z) order
|
|
30
|
+
"""
|
|
31
|
+
return tuple(int(math.floor(coord / sub_chunk_size)) for coord in args)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def chunk_coords_to_block_coords(
|
|
35
|
+
x: int, z: int, chunk_x_size: int = 16, chunk_z_size: int = 16
|
|
36
|
+
) -> ChunkCoordinates:
|
|
37
|
+
"""
|
|
38
|
+
Converts the supplied chunk coordinates into block coordinates
|
|
39
|
+
|
|
40
|
+
:param x: The x coordinate of the chunk
|
|
41
|
+
:param z: The z coordinate of the chunk
|
|
42
|
+
:param chunk_x_size: The dimension of the chunk in the x direction (Optional. Default 16)
|
|
43
|
+
:param chunk_z_size: The dimension of the chunk in the z direction (Optional. Default 16)
|
|
44
|
+
:return: The resulting block coordinates in (x, z) order
|
|
45
|
+
"""
|
|
46
|
+
return x * chunk_x_size, z * chunk_z_size
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def chunk_coords_to_region_coords(cx: int, cz: int) -> ChunkCoordinates:
|
|
50
|
+
"""
|
|
51
|
+
Converts the supplied chunk coordinates into region coordinates
|
|
52
|
+
|
|
53
|
+
:param cx: The x coordinate of the chunk
|
|
54
|
+
:param cz: The z coordinate of the chunk
|
|
55
|
+
:return: The resulting region coordinates in (x, z) order
|
|
56
|
+
"""
|
|
57
|
+
return cx >> 5, cz >> 5
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def region_coords_to_chunk_coords(rx: int, rz: int) -> ChunkCoordinates:
|
|
61
|
+
"""
|
|
62
|
+
Converts the supplied region coordinates into the minimum chunk coordinates of that region
|
|
63
|
+
|
|
64
|
+
:param rx: The x coordinate of the region
|
|
65
|
+
:param rz: The y coordinate of the region
|
|
66
|
+
:return: The resulting minimum chunk coordinates of that region in (x, z) order
|
|
67
|
+
"""
|
|
68
|
+
return rx << 5, rz << 5
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def blocks_slice_to_chunk_slice(
|
|
72
|
+
blocks_slice: slice, chunk_shape: int, chunk_coord: int
|
|
73
|
+
) -> slice:
|
|
74
|
+
"""
|
|
75
|
+
Converts the supplied blocks slice into chunk slice
|
|
76
|
+
|
|
77
|
+
:param blocks_slice: The slice of the blocks
|
|
78
|
+
:param chunk_shape: The shape of the chunk in this direction
|
|
79
|
+
:param chunk_coord: The coordinate of the chunk in this direction
|
|
80
|
+
:return: The resulting chunk slice
|
|
81
|
+
"""
|
|
82
|
+
return slice(
|
|
83
|
+
min(max(0, blocks_slice.start - chunk_coord * chunk_shape), chunk_shape),
|
|
84
|
+
min(max(0, blocks_slice.stop - chunk_coord * chunk_shape), chunk_shape),
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def gunzip(data):
|
|
89
|
+
"""
|
|
90
|
+
Decompresses data that is in Gzip format
|
|
91
|
+
"""
|
|
92
|
+
return gzip.GzipFile(fileobj=StringIO(data)).read()
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def from_nibble_array(arr: ndarray) -> ndarray:
|
|
96
|
+
"""
|
|
97
|
+
Unpacks a flat nibble array into a full size numpy array
|
|
98
|
+
|
|
99
|
+
:param arr: The nibble array
|
|
100
|
+
:return: The resulting array
|
|
101
|
+
"""
|
|
102
|
+
shape = arr.size
|
|
103
|
+
|
|
104
|
+
new_arr = zeros((shape * 2), dtype=uint8)
|
|
105
|
+
|
|
106
|
+
new_arr[::2] = arr & 0xF
|
|
107
|
+
new_arr[1::2] = arr >> 4
|
|
108
|
+
|
|
109
|
+
return new_arr
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def to_nibble_array(arr: ndarray) -> ndarray:
|
|
113
|
+
"""
|
|
114
|
+
packs a full size numpy array into a nibble array.
|
|
115
|
+
|
|
116
|
+
:param arr: Full numpy array
|
|
117
|
+
:return: The nibble array
|
|
118
|
+
"""
|
|
119
|
+
arr = arr.ravel()
|
|
120
|
+
return (arr[::2] + (arr[1::2] << 4)).astype("uint8")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
"""
|
|
124
|
+
Minecraft Java edition stores the block and height arrays in a compacted long array format.
|
|
125
|
+
The format stores one or more entries per long, using the fewest number of bits required to store the data.
|
|
126
|
+
There are two storage methods, the compact version was used prior to 1.16 and the less compact version in 1.16 and above.
|
|
127
|
+
Apparently the less compact version is quicker to pack and unpack.
|
|
128
|
+
The compact version effectively stores the values as a bit array spanning one or more values in the long array.
|
|
129
|
+
There may be some padding if the bit array does not fill all the long values. (The letter "P" signifies an unused padding bit)
|
|
130
|
+
PPAAAAAAAAABBBBBBBBBCCCCCCCCCDDDDDDDDDEEEEEEEEEFFFFFFFFFGGGGGGGG GHHHHHHHHHIIIIIIIIIJJJJJJJJJKKKKKKKKKLLLLLLLLLMMMMMMMMMOOOOOOOOO
|
|
131
|
+
The less compact version does not allow entries to straddle long values. Instead, if required, there is padding within each long.
|
|
132
|
+
PAAAAAAAAABBBBBBBBBCCCCCCCCCDDDDDDDDDEEEEEEEEEFFFFFFFFFGGGGGGGGG PHHHHHHHHHIIIIIIIIIJJJJJJJJJKKKKKKKKKLLLLLLLLLMMMMMMMMMOOOOOOOOO
|
|
133
|
+
|
|
134
|
+
The functions below can be used to pack and unpack both formats.
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def decode_long_array(
|
|
139
|
+
long_array: numpy.ndarray,
|
|
140
|
+
size: int,
|
|
141
|
+
bits_per_entry: int,
|
|
142
|
+
dense=True,
|
|
143
|
+
signed: bool = False,
|
|
144
|
+
) -> numpy.ndarray:
|
|
145
|
+
"""
|
|
146
|
+
Decode a long array (from BlockStates or Heightmaps)
|
|
147
|
+
|
|
148
|
+
:param long_array: Encoded long array
|
|
149
|
+
:param size: The expected size of the returned array
|
|
150
|
+
:param bits_per_entry: The number of bits per entry in the encoded array.
|
|
151
|
+
:param dense: If true the long arrays will be treated as a bit stream. If false they are distinct values with padding
|
|
152
|
+
:param signed: Should the returned array be signed.
|
|
153
|
+
:return: Decoded array as numpy array
|
|
154
|
+
"""
|
|
155
|
+
# validate the inputs and throw an error if there is a problem
|
|
156
|
+
if not isinstance(bits_per_entry, int):
|
|
157
|
+
raise ValueError(f"The bits_per_entry input must be an int.")
|
|
158
|
+
|
|
159
|
+
assert (
|
|
160
|
+
1 <= bits_per_entry <= 64
|
|
161
|
+
), f"bits_per_entry must be between 1 and 64 inclusive. Got {bits_per_entry}"
|
|
162
|
+
|
|
163
|
+
# force the array to be a signed long array
|
|
164
|
+
long_array = long_array.astype(">q")
|
|
165
|
+
|
|
166
|
+
if dense:
|
|
167
|
+
expected_len = math.ceil(size * bits_per_entry / 64)
|
|
168
|
+
else:
|
|
169
|
+
expected_len = math.ceil(size / (64 // bits_per_entry))
|
|
170
|
+
if len(long_array) != expected_len:
|
|
171
|
+
raise Exception(
|
|
172
|
+
f"{'Dense e' if dense else 'E'}ncoded long array with {bits_per_entry} bits per entry should contain {expected_len} longs but got {len(long_array)}."
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
# unpack the long array into a bit array
|
|
176
|
+
bits = numpy.unpackbits(long_array[::-1].astype(">i8").view("uint8"))
|
|
177
|
+
if dense:
|
|
178
|
+
if bits.size % bits_per_entry:
|
|
179
|
+
# if the array is densely packed and there is extra padding, remove it
|
|
180
|
+
bits = bits[bits.size % bits_per_entry :]
|
|
181
|
+
else:
|
|
182
|
+
# if not densely packed remove the padding per long
|
|
183
|
+
entry_per_long = 64 // bits_per_entry
|
|
184
|
+
bits = bits.reshape(-1, 64)[:, -entry_per_long * bits_per_entry :]
|
|
185
|
+
|
|
186
|
+
byte_length = 2 ** math.ceil(math.log(math.ceil(bits_per_entry / 8), 2))
|
|
187
|
+
dtype = {1: "B", 2: ">H", 4: ">I", 8: ">Q"}[byte_length]
|
|
188
|
+
|
|
189
|
+
# pad the bits to fill one of the above data types
|
|
190
|
+
arr = numpy.packbits(
|
|
191
|
+
numpy.pad(
|
|
192
|
+
bits.reshape(-1, bits_per_entry)[-size:, :],
|
|
193
|
+
[(0, 0), (byte_length * 8 - bits_per_entry, 0)],
|
|
194
|
+
"constant",
|
|
195
|
+
)
|
|
196
|
+
).view(dtype=dtype)[::-1]
|
|
197
|
+
if signed:
|
|
198
|
+
# convert to a signed array if requested
|
|
199
|
+
sarray = arr.astype({1: "b", 2: ">h", 4: ">i", 8: ">q"}[byte_length])
|
|
200
|
+
if bits_per_entry < 64:
|
|
201
|
+
mask = arr >= 2 ** (bits_per_entry - 1)
|
|
202
|
+
sarray[mask] = numpy.subtract(
|
|
203
|
+
arr[mask], 2**bits_per_entry, dtype=numpy.int64
|
|
204
|
+
)
|
|
205
|
+
arr = sarray
|
|
206
|
+
return arr
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def encode_long_array(
|
|
210
|
+
array: numpy.ndarray,
|
|
211
|
+
bits_per_entry: Optional[int] = None,
|
|
212
|
+
dense: bool = True,
|
|
213
|
+
min_bits_per_entry=1,
|
|
214
|
+
) -> numpy.ndarray:
|
|
215
|
+
"""
|
|
216
|
+
Encode a long array (from BlockStates or Heightmaps)
|
|
217
|
+
|
|
218
|
+
:param array: A numpy array of the data to be encoded.
|
|
219
|
+
:param bits_per_entry: The number of bits to use to store each value. If left as None will use the smallest bits per entry.
|
|
220
|
+
:param dense: If true the long arrays will be treated as a bit stream. If false they are distinct values with padding
|
|
221
|
+
:param min_bits_per_entry: The mimimum value that bits_per_entry can be. If it is less than this it will be capped at this value.
|
|
222
|
+
:return: Encoded array as numpy array
|
|
223
|
+
"""
|
|
224
|
+
assert (
|
|
225
|
+
1 <= min_bits_per_entry <= 64
|
|
226
|
+
), f"min_bits_per_entry must be between 1 and 64 inclusive. Got {bits_per_entry}"
|
|
227
|
+
# cast to a signed longlong array
|
|
228
|
+
array = array.astype(">q")
|
|
229
|
+
# work out how many bits are required to store the
|
|
230
|
+
required_bits_per_entry = max(
|
|
231
|
+
max(
|
|
232
|
+
int(numpy.amin(array)).bit_length(),
|
|
233
|
+
int(numpy.amax(array)).bit_length(),
|
|
234
|
+
),
|
|
235
|
+
min_bits_per_entry,
|
|
236
|
+
)
|
|
237
|
+
if bits_per_entry is None:
|
|
238
|
+
# if a bit depth has not been requested use the minimum required
|
|
239
|
+
bits_per_entry = required_bits_per_entry
|
|
240
|
+
elif isinstance(bits_per_entry, int):
|
|
241
|
+
assert (
|
|
242
|
+
1 <= bits_per_entry <= 64
|
|
243
|
+
), f"bits_per_entry must be between 1 and 64 inclusive. Got {bits_per_entry}"
|
|
244
|
+
# if a bit depth has been set and it is smaller than what is required throw an error
|
|
245
|
+
if required_bits_per_entry > bits_per_entry:
|
|
246
|
+
raise Exception(
|
|
247
|
+
f"The array requires at least {required_bits_per_entry} bits per value which is more than the specified {bits_per_entry} bits"
|
|
248
|
+
)
|
|
249
|
+
else:
|
|
250
|
+
raise ValueError(
|
|
251
|
+
"bits_per_entry must be an int between 1 and 64 inclusive or None."
|
|
252
|
+
)
|
|
253
|
+
# make the negative values positive to make bit storage easier
|
|
254
|
+
uarray = array.astype(">Q")
|
|
255
|
+
if bits_per_entry < 64:
|
|
256
|
+
mask = array < 0
|
|
257
|
+
uarray[mask] = numpy.add(
|
|
258
|
+
array[mask], 2**bits_per_entry, dtype=numpy.uint64, casting="unsafe"
|
|
259
|
+
)
|
|
260
|
+
array = uarray
|
|
261
|
+
|
|
262
|
+
# unpack the individual values into a bit array
|
|
263
|
+
bits: numpy.ndarray = numpy.unpackbits(
|
|
264
|
+
numpy.ascontiguousarray(array[::-1]).view("uint8")
|
|
265
|
+
).reshape(-1, 64)[:, -bits_per_entry:]
|
|
266
|
+
if dense:
|
|
267
|
+
if bits.size % 64:
|
|
268
|
+
# if the bit array does not fill a whole long
|
|
269
|
+
# add padding to the last long if required
|
|
270
|
+
bits = numpy.pad(
|
|
271
|
+
bits.ravel(),
|
|
272
|
+
[(64 - (bits.size % 64), 0)],
|
|
273
|
+
"constant",
|
|
274
|
+
)
|
|
275
|
+
else:
|
|
276
|
+
# if the array is not dense add padding
|
|
277
|
+
entry_per_long = 64 // bits_per_entry
|
|
278
|
+
if bits.shape[0] % entry_per_long:
|
|
279
|
+
# add padding to the last long if required
|
|
280
|
+
bits = numpy.pad(
|
|
281
|
+
bits,
|
|
282
|
+
[(entry_per_long - (bits.shape[0] % entry_per_long), 0), (0, 0)],
|
|
283
|
+
"constant",
|
|
284
|
+
)
|
|
285
|
+
# add padding for each long
|
|
286
|
+
bits = numpy.pad(
|
|
287
|
+
bits.reshape(-1, bits_per_entry * entry_per_long),
|
|
288
|
+
[(0, 0), (64 - bits_per_entry * entry_per_long, 0)],
|
|
289
|
+
"constant",
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
# pack the bits into a long array
|
|
293
|
+
return numpy.packbits(bits).view(dtype=">q")[::-1]
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def get_size(obj, seen=None):
|
|
297
|
+
"""Recursively finds size of objects"""
|
|
298
|
+
size = sys.getsizeof(obj)
|
|
299
|
+
if seen is None:
|
|
300
|
+
seen = set()
|
|
301
|
+
obj_id = id(obj)
|
|
302
|
+
if obj_id in seen:
|
|
303
|
+
return 0
|
|
304
|
+
|
|
305
|
+
# Important mark as seen *before* entering recursion to gracefully handle
|
|
306
|
+
# self-referential objects
|
|
307
|
+
seen.add(obj_id)
|
|
308
|
+
if isinstance(obj, dict):
|
|
309
|
+
size += sum([get_size(v, seen) for v in obj.values()])
|
|
310
|
+
size += sum([get_size(k, seen) for k in obj.keys()])
|
|
311
|
+
elif hasattr(obj, "__dict__"):
|
|
312
|
+
size += get_size(obj.__dict__, seen)
|
|
313
|
+
elif hasattr(obj, "__iter__") and not isinstance(obj, (str, bytes, bytearray)):
|
|
314
|
+
size += sum([get_size(i, seen) for i in obj])
|
|
315
|
+
return size
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def get_smallest_dtype(arr: ndarray, uint: bool = True) -> int:
|
|
319
|
+
"""
|
|
320
|
+
Returns the smallest dtype (number) that the array can afford
|
|
321
|
+
|
|
322
|
+
:param arr: The array to check on
|
|
323
|
+
:param uint: Should the array fit in uint or not (default: True)
|
|
324
|
+
:return: The number of bits all the elements can be represented with
|
|
325
|
+
"""
|
|
326
|
+
possible_dtypes = (2**x for x in range(3, 8))
|
|
327
|
+
max_number = numpy.amax(arr)
|
|
328
|
+
if not uint:
|
|
329
|
+
max_number = max_number * 2
|
|
330
|
+
if max_number == 0:
|
|
331
|
+
max_number = 1
|
|
332
|
+
return next(dtype for dtype in possible_dtypes if dtype > math.log(max_number, 2))
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def entity_position_to_chunk_coordinates(
|
|
336
|
+
entity_coordinates: Tuple[float, float, float]
|
|
337
|
+
):
|
|
338
|
+
return (
|
|
339
|
+
int(math.floor(entity_coordinates[0])) >> 4,
|
|
340
|
+
int(math.floor(entity_coordinates[2])) >> 4,
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def fast_unique(array: numpy.ndarray) -> Tuple[numpy.ndarray, numpy.ndarray]:
|
|
345
|
+
uni = numpy.unique(array)
|
|
346
|
+
lut = numpy.zeros(numpy.amax(uni) + 1, dtype=numpy.uint32)
|
|
347
|
+
lut[uni] = numpy.arange(uni.size)
|
|
348
|
+
inv = lut[array]
|
|
349
|
+
return uni, inv
|