#!/usr/bin/env python3 """Stream one VFMSH tile at a time to locate LOD/interface patches.""" import argparse import array import resource import struct import sys def main(): parser = argparse.ArgumentParser() parser.add_argument("scene") parser.add_argument("--lod", type=int, required=True) parser.add_argument("--surface", choices=("floor", "ceiling"), required=True) parser.add_argument("--height-min-m", type=float, required=True) parser.add_argument("--height-max-m", type=float, required=True) args = parser.parse_args() candidates = [] scanned = 0 with open(args.scene, "rb") as stream: if stream.read(8) != b"VFMSH001": raise SystemExit("invalid VFMSH001 scene") version, voxel_um, tile_count = struct.unpack("<3I", stream.read(12)) stream.seek(40, 1) if version != 1: raise SystemExit("unsupported mesh scene") for _ in range(tile_count): key = struct.unpack("<4i", stream.read(16)) surface_count, _, vertex_count, index_count, mask_count = struct.unpack( "<5I", stream.read(20)) payload = vertex_count * 24 + index_count * 4 + mask_count if key[3] != args.lod: stream.seek(payload, 1) continue scanned += 1 positions = array.array("f") positions.frombytes(stream.read(vertex_count * 12)) normals = array.array("f") normals.frombytes(stream.read(vertex_count * 12)) indices = array.array("I") indices.frombytes(stream.read(index_count * 4)) masks = stream.read(mask_count) if sys.byteorder != "little": positions.byteswap() normals.byteswap() indices.byteswap() hits = 0 sum_x = sum_y = sum_z = 0.0 min_z, max_z = float("inf"), float("-inf") want_up = args.surface == "floor" for offset in range(0, index_count, 3): if masks and masks[offset // 3]: continue a, b, c = indices[offset:offset + 3] if max(a, b, c) >= surface_count: continue nz = (normals[a * 3 + 2] + normals[b * 3 + 2] + normals[c * 3 + 2]) / 3.0 if (nz > 0) != want_up: continue za, zb, zc = positions[a * 3 + 2], positions[b * 3 + 2], positions[c * 3 + 2] if ((za + zb + zc) / 3.0 < args.height_min_m or (za + zb + zc) / 3.0 > args.height_max_m): continue hits += 1 sum_x += (positions[a * 3] + positions[b * 3] + positions[c * 3]) / 3.0 sum_y += (positions[a * 3 + 1] + positions[b * 3 + 1] + positions[c * 3 + 1]) / 3.0 z = (za + zb + zc) / 3.0 sum_z += z min_z, max_z = min(min_z, za, zb, zc), max(max_z, za, zb, zc) if hits: extent = 32.0 * (1 << key[3]) * voxel_um / 1_000_000.0 candidates.append((hits, key, sum_x / hits, sum_y / hits, sum_z / hits, min_z, max_z, extent)) candidates.sort(reverse=True, key=lambda item: item[0]) print(f"[VFMeshTileScan] lod={args.lod} surface={args.surface} scanned_tiles={scanned} " f"candidate_tiles={len(candidates)} peak_rss_kib=" f"{resource.getrusage(resource.RUSAGE_SELF).ru_maxrss}") for hits, key, x, y, z, low, high, extent in candidates[:40]: print(f"[VFMeshTile] key={key} triangles={hits} centroid_m=({x:.3f},{y:.3f},{z:.3f}) " f"z_m={low:.3f}..{high:.3f} extent_m={extent:.3f}") if __name__ == "__main__": main()