#!/usr/bin/env python3
"""Dump the three sub-geometries of each PUNCH patch separately:
chunk1 (dmg_set mask), chunk2 (undmg2enbl DRAWN), chunk3 (dmg_clear).
Check: are chunk2 and chunk3 identical? bbox/plane relationships chunk1 vs chunk2."""
import struct, sys
import numpy as np

VSTRIDE = {0x80:12, 0x81:24, 0x82:28, 0x88:20, 0x89:32, 0x8A:36}
CONTAINERS = {0x40,0x41,0x42,0x46,0x48,0x70,0x10,0x20,0x30,0x3}

def parse(path):
    d = open(path,"rb").read()
    patches = []
    def read_chunk(p, end):
        if p+2 > end: return None
        tw = struct.unpack_from("<H", d, p)[0]
        cid = tw & 0x2fff
        lw = 4 if (tw & 0x8000) else (2 if (tw & 0x4000) else 1)
        if p+2+lw > end: return None
        if lw==1: ln = d[p+2]
        elif lw==2: ln = struct.unpack_from("<H", d, p+2)[0]
        else: ln = struct.unpack_from("<I", d, p+2)[0]
        body = p+2+lw
        return cid, body, ln, body+ln
    def walk(p, end, patch):
        while p < end:
            r = read_chunk(p, end)
            if not r: break
            cid, body, ln, nxt = r
            if cid == 0x42:
                patch = {"mtl":"", "punch":False, "geoms":[]}
                patches.append(patch)
                walk(body, nxt, patch)
            elif cid == 0x2030 and patch is not None:
                patch["mtl"] = d[body:body+ln].split(b'\0')[0].decode('latin1','ignore')
            elif cid == 0x2037 and patch is not None:
                if b"PUNCH" in d[body:body+ln]: patch["punch"] = True
            elif cid in VSTRIDE and patch is not None:
                st = VSTRIDE[cid]
                n = ln // st
                verts = []
                for i in range(n):
                    o = body + i*st
                    verts.append(struct.unpack_from("<fff", d, o))
                patch["geoms"].append({"verts":verts, "faces":[], "vt":cid})
            elif cid == 0x47 and patch is not None and patch["geoms"]:
                g = patch["geoms"][-1]
                n = ln//4
                idx = [struct.unpack_from("<I", d, body+i*4)[0] for i in range(n)]
                for f in range(0,(n//3)*3,3):
                    g["faces"].append(tuple(idx[f:f+3]))
            elif cid == 0x4d and patch is not None and patch["geoms"]:
                g = patch["geoms"][-1]
                ppf = d[body]; n=(ln-1)//4
                idx=[struct.unpack_from("<I",d,body+1+i*4)[0] for i in range(n)]
                for f in range(0,(n//ppf)*ppf,ppf):
                    poly=[idx[f+j] for j in range(ppf)]
                    for k in range(1,ppf-1):
                        g["faces"].append((poly[0],poly[k],poly[k+1]))
            elif cid in CONTAINERS:
                walk(body, nxt, patch)
            p = nxt
    walk(8, len(d), None)
    return patches

def bbox(verts):
    V = np.array(verts)
    return V.min(0), V.max(0)

for path in sys.argv[1:]:
    print("==", path)
    for pi, pt in enumerate(parse(path)):
        if not pt["punch"] or not pt["geoms"]: continue
        print(" patch%d mtl=%s  %d sub-geometries" % (pi, pt["mtl"][:40], len(pt["geoms"])))
        for gi, g in enumerate(pt["geoms"]):
            lo, hi = bbox(g["verts"])
            print("   g%d: %d verts %d tris  bbox [%7.3f %7.3f %7.3f] .. [%7.3f %7.3f %7.3f]"
                  % (gi, len(g["verts"]), len(g["faces"]), *lo, *hi))
        if len(pt["geoms"]) == 3:
            v2 = np.array(pt["geoms"][1]["verts"]); v3 = np.array(pt["geoms"][2]["verts"])
            same = v2.shape == v3.shape and np.allclose(v2, v3, atol=1e-6)
            f2 = pt["geoms"][1]["faces"]; f3 = pt["geoms"][2]["faces"]
            print("   g1 vs g2(twin?): verts identical=%s faces identical=%s" % (same, f2==f3))
            if not same and v2.shape == v3.shape:
                dd = np.abs(v2-v3).max()
                print("     max coord delta = %g" % dd)
            # g0 vs g1 proximity: distance of each g1 vert to g0 bbox
            v1 = np.array(pt["geoms"][0]["verts"])
            lo0, hi0 = v1.min(0), v1.max(0)
            lo1, hi1 = v2.min(0), v2.max(0)
            print("   mask(g0) bbox vs drawn(g1) bbox: g0 contains g1: %s ; g1 contains g0: %s"
                  % (bool(np.all(lo0 <= lo1+1e-4) and np.all(hi0 >= hi1-1e-4)),
                     bool(np.all(lo1 <= lo0+1e-4) and np.all(hi1 >= hi0-1e-4))))
