mirror of https://codeberg.org/topola/topola.git
feat(topola): Add navmesher to maintain navmeshes
This commit is contained in:
parent
d589266df5
commit
e9778fd653
|
|
@ -9,6 +9,7 @@ version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
dearcut = { version = "0.1", features = ["undoredo"] }
|
||||||
derive-getters.workspace = true
|
derive-getters.workspace = true
|
||||||
derive_more.workspace = true
|
derive_more.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,16 @@
|
||||||
use derive_getters::{Dissolve, Getters};
|
use derive_getters::{Dissolve, Getters};
|
||||||
use undoredo::{ApplyDelta, Delta, FlushDelta};
|
use undoredo::{ApplyDelta, Delta, FlushDelta};
|
||||||
|
|
||||||
use crate::layout::{Layout, LayoutHalfDelta};
|
use crate::layout::{
|
||||||
|
Arc, ArcId, Joint, JointId, Layout, LayoutHalfDelta, Segment, SegmentId, Via, ViaId,
|
||||||
|
};
|
||||||
|
|
||||||
struct Layer {
|
struct Layer {
|
||||||
name: String,
|
name: String,
|
||||||
index: usize,
|
index: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Getters)]
|
#[derive(Clone, Debug, Getters)]
|
||||||
pub struct Board {
|
pub struct Board {
|
||||||
layout: Layout,
|
layout: Layout,
|
||||||
}
|
}
|
||||||
|
|
@ -23,6 +25,22 @@ impl Board {
|
||||||
layout: Layout::new(boundary),
|
layout: Layout::new(boundary),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn add_joint(&mut self, joint: Joint) -> JointId {
|
||||||
|
self.layout.add_joint(joint)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_segment(&mut self, segment: Segment) -> SegmentId {
|
||||||
|
self.layout.add_segment(segment)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_arc(&mut self, arc: Arc) -> ArcId {
|
||||||
|
self.layout.add_arc(arc)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_via(&mut self, via: Via) -> ViaId {
|
||||||
|
self.layout.add_via(via)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Dissolve)]
|
#[derive(Clone, Debug, Dissolve)]
|
||||||
|
|
|
||||||
|
|
@ -25,10 +25,11 @@ impl JointId {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct Joint {
|
pub struct Joint {
|
||||||
position: [i64; 2],
|
pub position: [i64; 2],
|
||||||
radius: u64,
|
pub layer: usize,
|
||||||
|
pub radius: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||||
|
|
@ -48,10 +49,11 @@ impl SegmentId {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct Segment {
|
pub struct Segment {
|
||||||
endpoints: [JointId; 2],
|
pub endpoints: [JointId; 2],
|
||||||
half_width: u64,
|
pub layer: usize,
|
||||||
|
pub half_width: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||||
|
|
@ -71,11 +73,12 @@ impl ArcId {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct Arc {
|
pub struct Arc {
|
||||||
endpoints: [JointId; 2],
|
pub endpoints: [JointId; 2],
|
||||||
focus: [i64; 2],
|
pub focus: [i64; 2],
|
||||||
half_width: u64,
|
pub layer: usize,
|
||||||
|
pub half_width: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||||
|
|
@ -95,10 +98,11 @@ impl ViaId {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct Via {
|
pub struct Via {
|
||||||
endpoints: [JointId; 2],
|
pub endpoints: [JointId; 2],
|
||||||
radius: u64,
|
pub layer: usize,
|
||||||
|
pub radius: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Getters)]
|
#[derive(Clone, Debug, Getters)]
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
mod board;
|
mod board;
|
||||||
mod layout;
|
mod layout;
|
||||||
|
mod navmesher;
|
||||||
mod specctra;
|
mod specctra;
|
||||||
|
|
||||||
pub use crate::board::Board;
|
pub use crate::board::Board;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,126 @@
|
||||||
|
// SPDX-FileCopyrightText: 2026 Topola contributors
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
|
||||||
|
use dearcut::RecordingTriangulator;
|
||||||
|
use derive_getters::Getters;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
Board,
|
||||||
|
layout::{Arc, ArcId, Joint, JointId, Segment, SegmentId, Via, ViaId},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Getters)]
|
||||||
|
pub struct LayerNavmesher {
|
||||||
|
navmeshes: Vec<RecordingTriangulator<i64>>,
|
||||||
|
inflation_factors: Vec<f64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LayerNavmesher {
|
||||||
|
pub fn insert_polygon(&mut self, polygon: impl IntoIterator<Item = [i64; 2]>) {
|
||||||
|
let polygon: Vec<[i64; 2]> = polygon.into_iter().collect();
|
||||||
|
|
||||||
|
for i in 0..self.navmeshes.len() {
|
||||||
|
self.navmeshes[i].insert_polygon(Self::inflate_polygon(
|
||||||
|
polygon.clone(),
|
||||||
|
self.inflation_factors[i],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inflate_polygon(
|
||||||
|
polygon: impl IntoIterator<Item = [i64; 2]>,
|
||||||
|
inflation_factor: f64,
|
||||||
|
) -> impl IntoIterator<Item = [i64; 2]> {
|
||||||
|
let polygon: Vec<[i64; 2]> = polygon.into_iter().collect();
|
||||||
|
|
||||||
|
// Centroid.
|
||||||
|
let cx = polygon.iter().map(|p| p[0] as f64).sum::<f64>() / polygon.len() as f64;
|
||||||
|
let cy = polygon.iter().map(|p| p[1] as f64).sum::<f64>() / polygon.len() as f64;
|
||||||
|
|
||||||
|
polygon.into_iter().map(move |[px, py]| {
|
||||||
|
// Delta.
|
||||||
|
let dx = px as f64 - cx;
|
||||||
|
let dy = py as f64 - cy;
|
||||||
|
let d = (dx * dx + dy * dy).sqrt();
|
||||||
|
|
||||||
|
// Normalize delta.
|
||||||
|
let nx = dx / d;
|
||||||
|
let ny = dy / d;
|
||||||
|
|
||||||
|
// Shift away from centroid.
|
||||||
|
let fx = px as f64 + nx * inflation_factor;
|
||||||
|
let fy = py as f64 + ny * inflation_factor;
|
||||||
|
|
||||||
|
// Round away from centroid.
|
||||||
|
let rx = if fx >= cx { fx.ceil() } else { fx.floor() };
|
||||||
|
let ry = if fy >= cy { fy.ceil() } else { fy.floor() };
|
||||||
|
|
||||||
|
[rx as i64, ry as i64]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Getters)]
|
||||||
|
pub struct Navmesher {
|
||||||
|
layers: Vec<LayerNavmesher>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Navmesher {
|
||||||
|
pub fn insert_polygon(&mut self, layer: usize, polygon: impl IntoIterator<Item = [i64; 2]>) {
|
||||||
|
self.layers[layer].insert_polygon(polygon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Getters)]
|
||||||
|
pub struct NavmesherBoard {
|
||||||
|
navmesher: Navmesher,
|
||||||
|
board: Board,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NavmesherBoard {
|
||||||
|
pub fn insert_joint(&mut self, joint: Joint) -> JointId {
|
||||||
|
self.navmesher
|
||||||
|
.insert_polygon(joint.layer, Self::joint_circumscribed_octagon(joint));
|
||||||
|
self.board.add_joint(joint)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn joint_circumscribed_octagon(joint: Joint) -> [[i64; 2]; 8] {
|
||||||
|
let cx = joint.position[0];
|
||||||
|
let cy = joint.position[1];
|
||||||
|
let r = joint.radius as i64;
|
||||||
|
|
||||||
|
// 1.082392... = 1 / cos(π/8)
|
||||||
|
// 0.414213... = tan(π/8)
|
||||||
|
|
||||||
|
// Approximate multipliers as fractions.
|
||||||
|
let r1 = (r * 277 + 128) / 256; // round(r * 1.0823922)
|
||||||
|
let r2 = (r * 106 + 128) / 256; // round(r * 0.41421356)
|
||||||
|
|
||||||
|
[
|
||||||
|
[cx + r1, cy], // right
|
||||||
|
[cx + r2, cy + r2], // top-right
|
||||||
|
[cx, cy + r1], // top
|
||||||
|
[cx - r2, cy + r2], // top-left
|
||||||
|
[cx - r1, cy], // left
|
||||||
|
[cx - r2, cy - r2], // bottom-left
|
||||||
|
[cx, cy - r1], // bottom
|
||||||
|
[cx + r2, cy - r2], // bottom-right
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert_segment(&mut self, segment: Segment) -> SegmentId {
|
||||||
|
// TODO: Insert into navmesh.
|
||||||
|
self.board.add_segment(segment)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert_arc(&mut self, arc: Arc) -> ArcId {
|
||||||
|
// TODO: Insert into navmesh.
|
||||||
|
self.board.add_arc(arc)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert_via(&mut self, via: Via) -> ViaId {
|
||||||
|
// TODO: Insert into navmesh.
|
||||||
|
self.board.add_via(via)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue