#!/usr/bin/env python3 """Measure upward mesh faces around the FI/SW stratum interface.""" import array import collections import math import struct import sys def read_exact(stream, size): data = stream.read(size) if len(data) != size: raise EOFError(f"wanted {size} bytes, got {len(data)}") return data path = sys.argv[1] low_z = float(sys.argv[2]) if len(sys.argv) > 2 else -80.0 high_z = float(sys.argv[3]) if len(sys.argv) > 3 else -45.0 with open(path, "rb") as stream: if read_exact(stream, 8) != b"VFMSH001": raise SystemExit("bad VFMSH001 header") version, voxel_um, tile_count = struct.unpack("<3I", read_exact(stream, 12)) camera = struct.unpack("<3d", read_exact(stream, 24)) yaw, pitch = struct.unpack("<2d", read_exact(stream, 16)) per_lod = collections.defaultdict(lambda: {"tiles": 0, "faces": [], "spans": []}) near_tiles = collections.defaultdict(list) for _ in range(tile_count): key = struct.unpack("<4i", read_exact(stream, 16)) surf_vertices, skirt_count, vertex_count, index_count, mask_count = struct.unpack( "<5I", read_exact(stream, 20)) p = array.array("f") p.frombytes(read_exact(stream, vertex_count * 12)) n = array.array("f") n.frombytes(read_exact(stream, vertex_count * 12)) if sys.byteorder != "little": p.byteswap(); n.byteswap() indices = array.array("I") indices.frombytes(read_exact(stream, index_count * 4)) masks = read_exact(stream, mask_count) if sys.byteorder != "little": indices.byteswap() lod = key[3] z_values = [] for tri in range(index_count // 3): if masks and masks[tri] != 0: continue a, b, c = indices[tri * 3:tri * 3 + 3] if max(a, b, c) >= surf_vertices: continue normal_z = (n[a * 3 + 2] + n[b * 3 + 2] + n[c * 3 + 2]) / 3.0 if normal_z < 0.65: continue z = (p[a * 3 + 2] + p[b * 3 + 2] + p[c * 3 + 2]) / 3.0 if low_z <= z <= high_z: z_values.append(z) if not z_values: continue z_values.sort() row = per_lod[lod] row["tiles"] += 1 row["faces"].extend(z_values) span = z_values[math.floor(.95 * (len(z_values) - 1))] - z_values[math.floor(.05 * (len(z_values) - 1))] row["spans"].append(span) extent = 32 * (1 << lod) * voxel_um / 1_000_000.0 cx = (key[0] + 0.5) * extent cy = (key[1] + 0.5) * extent near_tiles[lod].append((math.hypot(cx - camera[0], cy - camera[1]), key, z_values[len(z_values) // 2], span, len(z_values))) def q(values, fraction): return values[math.floor(fraction * (len(values) - 1))] if values else math.nan print(f"camera_m={camera} yaw={yaw} pitch={pitch} boundary_band_m={low_z}..{high_z}") print("lod tiles_with_boundary_faces faces z_m_p05/median/p95 tile_span_m_p50/p95/max") for lod in sorted(per_lod): row = per_lod[lod] spans = sorted(row["spans"]) print(f"{lod} {row['tiles']} {len(row['faces'])} " f"{q(sorted(row['faces']), .05):.4f}/{q(sorted(row['faces']), .5):.4f}/" f"{q(sorted(row['faces']), .95):.4f} " f"{q(spans, .5):.4f}/{q(spans, .95):.4f}/{max(spans):.4f}") print("nearest_xy_boundary_tiles_by_lod:") for lod in sorted(near_tiles): for distance, key, median, span, faces in sorted(near_tiles[lod])[:3]: print(f"lod={lod} key={key} xy_distance_m={distance:.2f} " f"boundary_median_z={median:.4f} within_tile_p05_p95_m={span:.4f} faces={faces}")