#!/usr/bin/env python3
"""Draw a top-view silhouette from the ACTUAL exported GLB vertices.
Run beside inspect_glb.py: python compare_table_silhouettes.py table-8.glb table-32.glb --out comparison-rebuilt.svg --report comparison-rebuilt.json
Python3.9+ standard library. Read-only inputs; outputs must be new paths.
This is an orthographic geometry diagram, not a lighting render or frame-rate test.
"""
import argparse
import html
import json
import math
from pathlib import Path
import sys
from inspect_glb import read_model,accessor_values,inspect,need

def profile(path):
    report=inspect(path)
    need(report['meshNodes']==report['meshDefinitions']==1,'Expected one comparison mesh/node')
    node=report['nodes'][0]
    need(node['translation']==[0,0,0] and node['scale']==[1,1,1] and node['rotation']==[0,0,0,1] and node['matrix'] is None,'Comparison node must be at origin without transforms')
    model,binary,_=read_model(path)
    primitive=model['meshes'][0]['primitives'][0]
    points=accessor_values(model,binary,primitive['attributes']['POSITION'])
    highest=max(p[1] for p in points)  # glTF Y-up, so top-view plane is XZ.
    ring=list({(round(p[0],9),round(p[2],9)) for p in points if abs(p[1]-highest)<1e-6})
    ring.sort(key=lambda p:math.atan2(p[1],p[0]))
    need(len(ring) in (8,32),'Expected8 or32 top-rim vertices')
    radius=max(math.hypot(x,z) for x,z in ring)
    need(all(abs(math.hypot(x,z)-radius)<1e-6 for x,z in ring),'Expected regular ring on one radius')
    need(abs(radius-.3)<1e-6,'Expected0.30m radius')
    gaps=[]
    for a,b in zip(ring,ring[1:]+ring[:1]):
        edge=math.hypot(b[0]-a[0],b[1]-a[1])
        apothem=abs(a[0]*b[1]-a[1]*b[0])/edge
        gaps.append(radius-apothem)
    gap=max(gaps)
    return {'file':Path(path).name,'sides':len(ring),'triangles':report['trianglesInMeshDefinitions'],
            'bytes':report['bytes'],'radiusM':radius,'maxRadialShortfallMm':gap*1000,
            'projectedShortfallAt300px':gap/(2*radius)*300,
            'projectedShortfallAt48px':gap/(2*radius)*48,
            'ringXZ':ring,'degenerateTriangles':report['degenerateTriangles'],'nonUnitNormals':report['nonUnitNormals']}

def draw(profiles):
    parts=['<svg xmlns="http://www.w3.org/2000/svg" width="960" height="650" viewBox="0 0 960 650" role="img" aria-labelledby="title desc">',
    '<title id="title">実際のGLBから取り出した8角と32角の天板輪郭</title><desc id="desc">同じ直径60センチ。上は図の原寸で直径300ピクセル、下は48ピクセル。破線は理想の円。8角は28三角形、32角は124三角形。</desc>',
    '<rect width="960" height="650" rx="20" fill="#eef0e8"/><g font-family="sans-serif" fill="#263b32">',
    '<text x="36" y="40" font-size="25" font-weight="700">分割数だけを変えると、輪郭はどう変わる？</text>',
    '<text x="36" y="70" font-size="17">実GLBの頂点から描画。青い破線は同じ半径の円。照明や動作速度の比較ではありません。</text>']
    for report,cx in zip(profiles,(255,705)):
        sides=report['sides'];radius=report['radiusM'];ring=report['ringXZ']
        parts.append(f'<text x="{cx}" y="112" text-anchor="middle" font-size="23" font-weight="700">{sides}角 / {report["triangles"]}三角形</text>')
        for cy,diam in [(290,300),(522,48)]:
            vertices=' '.join(f'{cx+x/radius*diam/2:.3f},{cy-z/radius*diam/2:.3f}' for x,z in ring)
            parts.append(f'<polygon points="{vertices}" fill="#e7ca89" stroke="#755d36" stroke-width="1.5"/><circle cx="{cx}" cy="{cy}" r="{diam/2}" fill="none" stroke="#28798a" stroke-width="1" stroke-dasharray="4 3"/>')
        parts.append(f'<text x="{cx}" y="466" text-anchor="middle" font-size="16">直径300px: 最大の内側へのずれ 約{report["projectedShortfallAt300px"]:.2f}px</text>')
        parts.append(f'<text x="{cx}" y="569" text-anchor="middle" font-size="16">直径48px: 約{report["projectedShortfallAt48px"]:.2f}px</text>')
    parts.extend(['<text x="36" y="612" font-size="18">寸法・材質は同じ。近くで見せる物か、小さく表示する物かで選びます。</text>',
                  '<text x="36" y="637" font-size="15">pxはこの比較図の原寸上の値。ページの縮小表示では実際の画面上の大きさも変わります。</text></g></svg>'])
    return ''.join(parts)

def main():
    parser=argparse.ArgumentParser(description=__doc__)
    parser.add_argument('inputs',nargs=2,type=Path);parser.add_argument('--out',required=True,type=Path);parser.add_argument('--report',required=True,type=Path)
    args=parser.parse_args()
    targets=[args.out.resolve(),args.report.resolve()]
    need(len(set(targets))==2 and all(not p.exists() for p in targets),'Choose two new output filenames; existing files are not overwritten')
    need(not any(p in [x.resolve() for x in args.inputs] for p in targets),'Outputs must not replace inputs')
    reports=[profile(path) for path in args.inputs]
    need([p['sides'] for p in reports]==[8,32],'Supply8-side model first,32-side model second')
    svg=draw(reports)
    data={'models':reports,'method':'Top rim vertices read from exported GLB POSITION accessors; fixed orthographic scale, ideal circle with equal radius; no invented service output or performance measurement.'}
    # All parsing, checks and rendering finish before files are opened.
    with args.out.open('x',encoding='utf-8') as out:out.write(svg)
    with args.report.open('x',encoding='utf-8') as out:json.dump(data,out,ensure_ascii=False,indent=2);out.write('\n')
    print('Created',args.out,'and',args.report)
if __name__=='__main__':
    try:main()
    except (ValueError,OSError,KeyError,IndexError,TypeError) as error:
        print('ERROR: '+str(error),file=sys.stderr);sys.exit(1)
