# -*- coding: utf-8 -*-
r"""Constroi a grelha de colisao de um mapa a partir do .nav do CLIENT.

    python tools/extrair_colisao.py [berneo]  ->  Data/Maps/Colisao_<mapa>.bin

FORMATO DO .nav, decifrado em 29/08 (nao havia documentacao nenhuma):

    [0:4]    versao (10)
    [4:8]    numero de registos da seccao 1 (8049 no berneo)
    seccao1  8049 x 24 bytes: seis u32. Os quatro primeiros correlacionam-se aos pares com
             a posicao dos poligonos, mas nao sao adjacencia -- ficou por decifrar.
    seccao2  GRUPOS de poligonos, ate ao fim do ficheiro. Cada poligono e uma contagem u32
             seguida dessa quantidade de pares u16 (x, y) em celulas. Uma contagem ZERO
             separa um grupo do seguinte. O berneo tem 47 grupos e o ficheiro decifra-se
             ate ao ultimo byte, sem sobra -- foi assim que se soube que o formato estava
             certo.

GEOMETRIA. Confirmada por dois caminhos independentes: o cabecalho dos .sat diz 5120
unidades por bloco e ha 9 blocos de 512 celulas numa grelha 3x3, o que da 10 unidades por
celula e 1536x1536 celulas no total; e os vertices do .nav vao ate 1533, no mesmo intervalo.

QUAIS GRUPOS BLOQUEIAM. Nao esta escrito em lado nenhum, entao mediu-se: para cada grupo,
com que frequencia as 89 mil posicoes REAIS de monstro caem la dentro, contra pontos ao
acaso na mesma regiao. Um grupo de obstaculos tem de ter os monstros a evita-lo:

    grupo 14  617 poligonos  real 0,10%  acaso 1,45%  -> 14x menos que o acaso
    grupo  2  189 poligonos  real 0,20%  acaso 1,19%  ->  6x menos
    grupo 29  576 poligonos  real 0,18%  acaso 0,65%
    grupo 19  972 poligonos  real 1,35%  acaso 3,15%

Os restantes 43 grupos tem os monstros a passar por cima tanto ou mais do que o acaso, logo
nao bloqueiam. O que cada grupo significa continua por saber; o que se mede e se bloqueia.
"""
import io
import math
import os
import struct
import sys

LADO_CEL = 10.0
CELULAS = 1536
LIMIAR = 0.30      # razao real/acaso abaixo da qual se considera obstaculo
# So os dois com sinal FORTE. Os grupos 19 e 29 tinham razoes de 0,43 e 0,27 --
# os monstros evitam-nos, mas nao o suficiente para serem parede, e inclui-los
# encheu o mapa de bloqueios falsos: a validacao passou de 0,5 para 5,0 por cento
# de posicoes reais em celula bloqueada.
GRUPOS_BLOQUEIO = (2, 14)


def ler_nav(caminho):
    b = io.open(caminho, "rb").read()
    n1 = struct.unpack_from("<I", b, 4)[0]
    p = 8 + n1 * 24
    grupos, atual = [], []
    while p + 4 <= len(b):
        n = struct.unpack_from("<I", b, p)[0]
        if n == 0:
            if atual:
                grupos.append(atual)
                atual = []
            p += 4
            continue
        if n > 5000 or p + 4 + n * 4 > len(b):
            break
        v = struct.unpack_from("<%dH" % (n * 2), b, p + 4)
        atual.append([(v[i * 2], v[i * 2 + 1]) for i in range(n)])
        p += 4 + n * 4
    if atual:
        grupos.append(atual)
    return grupos, len(b) - p


def rasterizar(polis, lado=CELULAS):
    """Marca as celulas cobertas pelos poligonos. Varredura por linha, sem bibliotecas."""
    g = bytearray(lado * lado)
    for pol in polis:
        ys = [q[1] for q in pol]
        for y in range(max(0, min(ys)), min(lado - 1, max(ys)) + 1):
            xs = []
            n = len(pol)
            for i in range(n):
                x1, y1 = pol[i]
                x2, y2 = pol[(i + 1) % n]
                if (y1 > y) != (y2 > y):
                    xs.append(x1 + (y - y1) * (x2 - x1) / float(y2 - y1))
            xs.sort()
            # PELO CENTRO DA CELULA, nao pelas bordas. Marcar de int(inicio) a int(fim)
            # engorda cada obstaculo em quase uma celula de cada lado -- com celulas de 10
            # unidades isso e um metro de parede a mais em todo o contorno, e foi o que pos
            # 5 por cento das posicoes reais dentro de bloqueio.
            for k in range(0, len(xs) - 1, 2):
                x0 = int(math.ceil(xs[k] - 0.5))
                x1 = int(math.floor(xs[k + 1] - 0.5))
                for x in range(max(0, x0), min(lado - 1, x1) + 1):
                    g[y * lado + x] = 1
    return g


def main() -> int:
    mapa = sys.argv[1] if len(sys.argv) > 1 else "berneo"
    nav = os.path.join(r"C:\Games\Karos\ResUnpak\Map", mapa, mapa + ".nav")
    if not os.path.exists(nav):
        print("nao encontrei %s" % nav)
        return 1
    grupos, sobra = ler_nav(nav)
    print("%s: %d grupos, %d bytes por ler no fim" % (mapa, len(grupos), sobra))
    polis = [p for k in GRUPOS_BLOQUEIO if k < len(grupos) for p in grupos[k]]
    print("grupos de bloqueio %s -> %d poligonos" % (list(GRUPOS_BLOQUEIO), len(polis)))
    g = rasterizar(polis)
    bloq = sum(g)
    print("celulas bloqueadas: %d de %d  (%.1f%%)" % (bloq, len(g), 100.0 * bloq / len(g)))

    destino = os.path.join(os.path.dirname(os.path.abspath(__file__)),
                           "..", "server", "Data", "Maps", "Colisao_%s.bin" % mapa)
    with open(destino, "wb") as f:
        f.write(struct.pack("<3I", CELULAS, CELULAS, int(LADO_CEL)))
        f.write(bytes(g))
    print("escrito em %s" % os.path.normpath(destino))
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
