use bevy_asset::RenderAssetUsages;
use bevy_math::{DVec3, Vec3};
use bevy_mesh::{Indices, Mesh, PrimitiveTopology};
use crate::cell::GeoCell;
use crate::coord;
pub fn cell_boundary_3d(cell: u64, radius: f64) -> Option<Vec<Vec3>> {
let boundary = a5::cell_to_boundary(
cell,
Some(a5::core::cell::CellToBoundaryOptions {
closed_ring: false,
segments: Some(1),
}),
)
.ok()?;
Some(
boundary
.iter()
.map(|ll| coord::lonlat_to_vec3(ll, radius))
.collect(),
)
}
pub fn cell_boundary_3d_f64(cell: u64, radius: f64) -> Option<Vec<DVec3>> {
let boundary = a5::cell_to_boundary(cell, None).ok()?;
Some(
boundary
.iter()
.map(|ll| coord::lonlat_to_dvec3(ll, radius))
.collect(),
)
}
pub fn cell_boundary_local(cell: u64, radius: f64) -> Option<Vec<Vec3>> {
let center_ll = a5::cell_to_lonlat(cell).ok()?;
let center = coord::lonlat_to_dvec3(¢er_ll, radius);
let boundary = a5::cell_to_boundary(cell, None).ok()?;
Some(
boundary
.iter()
.map(|ll| (coord::lonlat_to_dvec3(ll, radius) - center).as_vec3())
.collect(),
)
}
pub fn cell_edges_3d(cell: u64, radius: f64) -> Option<Vec<(Vec3, Vec3)>> {
let verts = cell_boundary_3d(cell, radius)?;
if verts.is_empty() {
return Some(Vec::new());
}
let mut edges = Vec::with_capacity(verts.len());
for i in 0..verts.len() {
let next = (i + 1) % verts.len();
edges.push((verts[i], verts[next]));
}
Some(edges)
}
pub fn cell_triangle_fan(cell: u64, radius: f64) -> Option<(Vec<Vec3>, Vec<u32>)> {
let local_verts = cell_boundary_local(cell, radius)?;
let n = local_verts.len();
if n < 3 {
return Some((Vec::new(), Vec::new()));
}
let mut vertices = Vec::with_capacity(n + 1);
vertices.push(Vec3::ZERO); vertices.extend_from_slice(&local_verts);
let mut indices = Vec::with_capacity(n * 3);
for i in 0..n {
indices.push(0); indices.push((i + 1) as u32);
indices.push(((i + 1) % n + 1) as u32);
}
Some((vertices, indices))
}
pub fn batch_boundaries_3d(cells: &[u64], radius: f64) -> Vec<(Vec3, u64)> {
let mut result = Vec::new();
for &cell in cells {
if let Ok(boundary) = a5::cell_to_boundary(cell, None) {
for ll in &boundary {
result.push((coord::lonlat_to_vec3(ll, radius), cell));
}
}
}
result
}
pub fn cell_boundary_relative(cell: u64, origin: u64, radius: f64) -> Option<Vec<Vec3>> {
let origin_ll = a5::cell_to_lonlat(origin).ok()?;
let origin_pos = coord::lonlat_to_dvec3(&origin_ll, radius);
let boundary = a5::cell_to_boundary(cell, None).ok()?;
Some(
boundary
.iter()
.map(|ll| (coord::lonlat_to_dvec3(ll, radius) - origin_pos).as_vec3())
.collect(),
)
}
pub fn build_grid_line_mesh(cells: &[GeoCell], radius: f64) -> Mesh {
let mut positions: Vec<[f32; 3]> = Vec::new();
let mut indices: Vec<u32> = Vec::new();
let mut offset: u32 = 0;
for cell in cells {
let Some(verts) = cell_boundary_3d(cell.raw(), radius) else {
continue;
};
if verts.is_empty() {
continue;
}
for v in &verts {
positions.push([v.x, v.y, v.z]);
}
let n = verts.len() as u32;
for i in 0..n {
indices.push(offset + i);
indices.push(offset + (i + 1) % n);
}
offset += n;
}
let mut mesh = Mesh::new(
PrimitiveTopology::LineList,
RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD,
);
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions);
mesh.insert_indices(Indices::U32(indices));
mesh
}
pub fn cell_boundary_smooth(cell: u64, radius: f64, segments: i32) -> Option<Vec<Vec3>> {
let boundary = a5::cell_to_boundary(
cell,
Some(a5::core::cell::CellToBoundaryOptions {
closed_ring: true,
segments: Some(segments),
}),
)
.ok()?;
Some(
boundary
.iter()
.map(|ll| coord::lonlat_to_vec3(ll, radius))
.collect(),
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hex_has_vertices() {
let cell = a5::lonlat_to_cell(a5::LonLat::new(2.3522, 48.8566), 5).unwrap();
let verts = cell_boundary_3d(cell, 6_371_000.0).unwrap();
assert!(verts.len() >= 5);
}
#[test]
fn edges_match_vertices() {
let cell = a5::lonlat_to_cell(a5::LonLat::new(2.3522, 48.8566), 5).unwrap();
let verts = cell_boundary_3d(cell, 6_371_000.0).unwrap();
let edges = cell_edges_3d(cell, 6_371_000.0).unwrap();
assert_eq!(verts.len(), edges.len());
}
#[test]
fn triangle_fan_valid() {
let cell = a5::lonlat_to_cell(a5::LonLat::new(2.3522, 48.8566), 5).unwrap();
let (verts, indices) = cell_triangle_fan(cell, 6_371_000.0).unwrap();
assert!(!verts.is_empty());
assert!(!indices.is_empty());
assert_eq!(indices.len() % 3, 0);
}
}