"""Hard watertight test shapes in ../meshes: deep concavities, interleaved sheets, many holes, fine detail.

  knot    trefoil knot tube (self-wrapping: several rings per layer, crossings along every ray)
  spring  coil spring, 8 turns (a deep, open concavity; 16 rings per layer)
  sponge  Menger sponge, level 2 (square holes through every axis, genus 72)
  gear    24-tooth gear with a bore (boundary detail beyond 16 harmonics: an expected FCP failure case)

    env -u PYTHONPATH .venv/bin/python scripts/make_shapes.py
"""
import sys
from pathlib import Path

import manifold3d as mf
import numpy as np
import trimesh
from shapely.geometry import Point, Polygon

sys.path.insert(0, str(Path(__file__).parent))
from common import ROOT  # noqa: E402

OUT = ROOT / "meshes"


def tube(curve, r, n_seg=24):
    """Watertight tube of radius r along the closed (closed=True) or open polyline curve (n, 3), with caps if open."""
    return trimesh.creation.sweep_polygon(Point(0, 0).buffer(r, quad_segs=n_seg // 4), curve)


def knot(n=600):
    t = np.linspace(0, 2 * np.pi, n, endpoint=False)
    c = np.column_stack([np.sin(t) + 2 * np.sin(2 * t), np.cos(t) - 2 * np.cos(2 * t), -np.sin(3 * t)]) * 30
    c = np.vstack([c, c[:1]])
    return tube(c, 9.0)


def spring(turns=8, n=1600):
    t = np.linspace(0, 2 * np.pi * turns, n)
    c = np.column_stack([40 * np.cos(t), 40 * np.sin(t), 12 * t / (2 * np.pi)])
    return tube(c, 4.0)


def sponge(level=2, size=90.0):
    cube = mf.Manifold.cube([size] * 3, center=True)
    holes = []
    for lev in range(1, level + 1):
        s = size / 3 ** lev
        k = 3 ** (lev - 1)
        for i in range(k):
            for j in range(k):
                a = -size / 2 + (3 * i + 1.5) * s
                b = -size / 2 + (3 * j + 1.5) * s
                for ax in range(3):
                    dims = [s, s, s]
                    dims[ax] = size * 1.2
                    pos = [a, b]
                    pos.insert(ax, 0.0)
                    holes.append(mf.Manifold.cube(dims, center=True).translate(pos))
    out = cube
    for h in holes:
        out = out - h
    m = out.to_mesh()
    return trimesh.Trimesh(m.vert_properties[:, :3], m.tri_verts)


def gear(teeth=24, r=45.0, depth=6.0, height=25.0, bore=12.0):
    pts = []
    for i in range(teeth):
        a = 2 * np.pi * i / teeth
        da = 2 * np.pi / teeth
        for f, rr in ((0.0, r - depth), (0.15, r - depth), (0.3, r), (0.7, r), (0.85, r - depth)):
            pts.append(((rr) * np.cos(a + f * da), (rr) * np.sin(a + f * da)))
    poly = Polygon(pts).difference(Point(0, 0).buffer(bore, quad_segs=32))
    return trimesh.creation.extrude_polygon(poly, height)


def main():
    OUT.mkdir(exist_ok=True)
    for name, fn in (("knot", knot), ("spring", spring), ("sponge", sponge), ("gear", gear)):
        m = fn()
        m.merge_vertices()
        m.fix_normals()
        if m.volume < 0:
            m.invert()
        m.export(OUT / f"{name}.ply")
        print(name, len(m.faces), "faces, watertight", m.is_watertight, "genus", int(1 - m.euler_number / 2), flush=True)


if __name__ == "__main__":
    main()
