exact-poly 0.3.0

Integer polygon geometry library — exact arithmetic, no float errors
Documentation
use serde::{Deserialize, Serialize};
use wasm_bindgen::prelude::*;

#[wasm_bindgen(typescript_custom_section)]
const TS_APPEND_CONTENT: &str = r#"
export interface ProtocolConfig {
  max_parts: number;
  max_vertices_per_part: number;
  min_edge_length_squared: bigint;
  min_compactness_ppm: bigint;
  area_divisor: bigint;
}

export type PolygonRing = bigint[];
export type PolygonParts = PolygonRing[];

export type DecomposeStrategy =
  | "AlreadyConvex"
  | "ExactPartition"
  | "Bayazit"
  | "EarClipMerge"
  | { Rotation: { offset: number; inner: DecomposeStrategy } };

export type DecomposeOutcome =
  | { Success: { part_count: number } }
  | { "TooManyParts": { count: number } }
  | { ValidationFailed: { errors: string[] } }
  | { AlgorithmFailed: { error: string } };

export interface DecomposeAttempt {
  strategy: DecomposeStrategy;
  rotation: number;
  outcome: DecomposeOutcome;
}

export interface DecomposeResult {
  parts: PolygonParts;
  steiner_points: PolygonRing;
  strategy: DecomposeStrategy;
  trace?: DecomposeAttempt[];
}

export interface ValidationCheck {
  name: string;
  passed: boolean;
  detail: string;
  severity: "ok" | "warn" | "error";
}

export interface ValidationReport {
  checks: ValidationCheck[];
  valid: boolean;
  error_count: number;
  warn_count: number;
  original_twice_area: string;
  parts_twice_area_sum: string;
  part_areas: string[];
}

export interface IndexPair {
  a_index: number;
  b_index: number;
}

export type TopologyError =
  | { NotConnected: { disconnected_parts: number[] } }
  | { HasHoles: { boundary_components: number } }
  | { VertexOnlyContact: { part_a: number; part_b: number } }
  | { UnsupportedContact: { part_a: number; part_b: number; reason: string } }
  | { "TooManyParts": { count: number; max: number } }
  | { NotCompact: { compactness_ppm: bigint; min_ppm: bigint } };

export function area_display_from_twice_area(twice_area: string, config?: ProtocolConfig | null): bigint;
export function areas_conserved_values(original: string, part_areas: string[]): boolean;
export function bayazit_decompose_polygon(ring_flat: BigInt64Array, allow_steiner: boolean): PolygonParts;
export function collect_steiner_points(ring_flat: BigInt64Array, parts_flat: PolygonParts): PolygonRing;
export function contains_polygon(outer_parts_flat: PolygonParts, inner_parts_flat: PolygonParts): boolean;
export function decompose_polygon(ring_flat: BigInt64Array, allow_steiner: boolean, collect_trace?: boolean | null, minimize_parts?: boolean | null, config?: ProtocolConfig | null): DecomposeResult;
export function ear_clip_triangulate_polygon(ring_flat: BigInt64Array): PolygonParts;
export function ensure_ccw(ring_flat: BigInt64Array): PolygonRing;
export function exact_partition_only_original_vertices(ring_flat: BigInt64Array, parts_flat: PolygonParts): boolean;
export function exact_vertex_partition_polygon(ring_flat: BigInt64Array): PolygonParts;
export function find_overlapping_parts(a_parts_flat: PolygonParts, b_parts_flat: PolygonParts): IndexPair[];
export function merge_convex_pair(a_flat: BigInt64Array, b_flat: BigInt64Array): PolygonRing | undefined;
export function normalize_polygon(ring_flat: BigInt64Array): PolygonRing | undefined;
export function optimize_partition(parts_flat: PolygonParts): PolygonParts;
export function parts_overlap(a_parts_flat: PolygonParts, b_parts_flat: PolygonParts): boolean;
export function point_inside_any_part(parts_flat: PolygonParts, x: bigint, y: bigint): boolean;
export function remove_collinear(ring_flat: BigInt64Array): PolygonRing;
export function rotate_polygon(ring_flat: BigInt64Array, start: number): PolygonRing;
export function validate_compactness(twice_area: string, perimeter: string, config?: ProtocolConfig | null): string | undefined;
export function validate_decomposition(ring_flat: BigInt64Array, parts_flat: PolygonParts, config?: ProtocolConfig | null): ValidationReport;
export function validate_edge_lengths(ring_flat: BigInt64Array, config?: ProtocolConfig | null): string | undefined;
export function validate_multipart_topology(parts_flat: PolygonParts, allow_vertex_contact?: boolean | null, config?: ProtocolConfig | null): TopologyError | undefined;
export function validate_part(ring_flat: BigInt64Array, config?: ProtocolConfig | null): string | undefined;
"#;

#[derive(Serialize, Deserialize)]
pub struct WasmDecomposeResult {
    pub parts: Vec<Vec<i64>>,
    pub steiner_points: Vec<i64>,
    pub strategy: crate::types::Strategy,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub trace: Option<Vec<crate::types::Attempt>>,
}

#[derive(Serialize, Deserialize)]
pub struct WasmIndexPair {
    pub a_index: usize,
    pub b_index: usize,
}