201 lines
6.5 KiB
Python

import sys
import struct
from pprint import pprint
from itertools import chain
from collections import defaultdict
import functools
import mcregion
import vec3
import vertex_buffer
import data
def wrap_n(nc, chunk_c):
if nc < 0:
nc = 15
chunk_c = chunk_c - 1
if nc > 15:
nc = 0
chunk_c = chunk_c + 1
return nc, chunk_c
non_solid_blocks = {
data.BlockID.TALL_GRASS,
data.BlockID.MUSHROOM_1,
data.BlockID.MUSHROOM_2,
data.BlockID.FLOWER,
data.BlockID.ROSE,
data.BlockID.SAPLING,
}
def neighbor_exists(level_table, chunk_x, chunk_z, nx, ny, nz):
if ny > 127 or ny < 0:
return False
nx, n_chunk_x = wrap_n(nx, chunk_x)
nz, n_chunk_z = wrap_n(nz, chunk_z)
assert nx <= 15 and nx >= 0
assert nz <= 15 and nz >= 0
key = (n_chunk_x, n_chunk_z)
if key not in level_table:
return True
n_block_index = mcregion.block_index_from_xyz(nx, ny, nz)
n_block_id = level_table[key].blocks[n_block_index]
has_neighbor = (n_block_id != data.BlockID.AIR) and (n_block_id not in non_solid_blocks)
return has_neighbor
def block_neighbors(level_table, chunk_x, chunk_z, block_index):
block_id = level_table[(chunk_x, chunk_z)].blocks[block_index]
if block_id == data.BlockID.AIR:
return
xyz = mcregion.xyz_from_block_index(block_index)
center_position = vec3.add(xyz, (chunk_x * 16, 0, chunk_z * 16))
def find_non_neighbors():
for i, normal in enumerate(vertex_buffer.normals):
neighbor = vec3.add(normal, xyz)
if not neighbor_exists(level_table, chunk_x, chunk_z, *neighbor):
yield i
normal_indices = list(find_non_neighbors())
if block_id in non_solid_blocks or normal_indices:
yield center_position, block_id, normal_indices
def devoxelize_region(level_table, level_table_keys):
for chunk_x, chunk_z in level_table_keys:
for block_index in range(128 * 16 * 16):
yield from block_neighbors(level_table, chunk_x, chunk_z, block_index)
def build_level_table(level_table, mem, locations):
for location in locations:
try:
level = mcregion.parse_location(mem, location)
except CountZeroException:
continue
x, z = level.x_pos, level.z_pos
level_table[(x, z)] = level
return level_table
def normal_indices_as_block_configuration(normal_indices):
acc = 0
for i in normal_indices:
acc |= (1 << i)
return acc
def build_block_configuration_table():
for i in range(64):
indices = []
for j in range(6):
if ((i >> j) & 1) != 0:
indices.extend(vertex_buffer.faces_by_normal[vertex_buffer.normals[j]])
yield indices
non_cube_blocks = {
data.BlockID.TALL_GRASS,
data.BlockID.MUSHROOM_1,
data.BlockID.MUSHROOM_2,
data.BlockID.FLOWER,
data.BlockID.ROSE,
data.BlockID.SAPLING,
}
def pack_instance_data(position, block_id):
packed = struct.pack("<hhhBB",
position[0], position[1], position[2],
block_id,
0)
return packed
def build_block_instances(blocks):
by_configuration = defaultdict(list)
deferred_blocks = []
for position, block_id, normal_indices in blocks:
if block_id in non_cube_blocks:
deferred_blocks.append((position, block_id))
continue
configuration = normal_indices_as_block_configuration(normal_indices)
by_configuration[configuration].append((position, block_id))
offset = 0
configuration_instance_count_offset = []
with open(f"{data_path}.instance.vtx", "wb") as f:
######################################################################
# cubes
######################################################################
for configuration in range(64):
if configuration not in by_configuration:
configuration_instance_count_offset.append((0, 0))
continue
_blocks = by_configuration[configuration]
configuration_instance_count_offset.append((len(_blocks), offset))
for position, block_id in _blocks:
assert block_id not in non_cube_blocks, block_id
packed = pack_instance_data(position, block_id)
f.write(packed)
offset += len(packed)
######################################################################
# non-cubes
######################################################################
nc_offset = offset
nc_instance_count = 0
for position, block_id in deferred_blocks:
if block_id not in non_cube_blocks:
continue
packed = pack_instance_data(position, block_id)
f.write(packed)
offset += len(packed)
nc_instance_count += 1
configuration_instance_count_offset.append((nc_instance_count, nc_offset))
with open(f"{data_path}.instance.cfg", "wb") as f:
for instance_count, offset in configuration_instance_count_offset:
#print(instance_count, offset)
f.write(struct.pack("<ii", instance_count, offset))
def level_table_from_path(level_table, path):
with open(path, "rb") as f:
buf = f.read()
mem = memoryview(buf)
offset = 0
offset, locations = mcregion.parse_locations(mem, offset)
offset, timestamps = mcregion.parse_timestamps(mem, offset)
assert offset == 0x2000
build_level_table(level_table, mem, locations)
all_paths = [
"/home/bilbo/Love2DWorld/region/r.0.0.mcr",
"/home/bilbo/Love2DWorld/region/r.-1.-1.mcr",
"/home/bilbo/Love2DWorld/region/r.0.-1.mcr",
"/home/bilbo/Love2DWorld/region/r.-1.0.mcr",
]
def main2(level_table, level_table_keys):
blocks = devoxelize_region(level_table, level_table_keys)
build_block_instances(blocks)
def main(mcr_path, data_path):
assert mcr_path in all_paths
level_table = {}
level_table_from_path(level_table, mcr_path)
level_table_keys = list(level_table.keys())
for path in all_paths:
if path == mcr_path:
continue
level_table_from_path(level_table, path)
main2(level_table, level_table_keys)
#import cProfile
#cProfile.runctx("main2(level_table, level_table_keys)", {},
# {"level_table_keys": level_table_keys, "level_table": level_table, "main2": main2})
mcr_path = sys.argv[1]
data_path = sys.argv[2]
main(mcr_path, data_path)