Fleshed out samplers, segments, and the rail graph.

This commit is contained in:
Patrick Marsee 2025-01-03 17:39:08 -05:00
parent 98e8cc418b
commit 600fd0c276
12 changed files with 1783 additions and 0 deletions

379
src/simple_segment.rs Normal file
View file

@ -0,0 +1,379 @@
use crate::prelude::*;
use crate::samplers::sampler::*;
use crate::rail_segment;
use crate::rail_segment::RailSegment;
use crate::rail_connector::RailConnector;
/**
* Pure rust implementation of Basis::new_looking_at intended to allow unit tests.
*/
fn new_looking_at(direction: Vector3, up: Vector3, _positive_z_forward: bool) -> Basis {
let z = -direction.normalized();
let x = up.cross(z).normalized();
let y = z.cross(x);
Basis::from_cols(x, y, z)
}
pub struct SimpleSegment<A: AlignmentSampler, E: ElevationSampler> {
alignment: A,
elevation: E,
lower_connection: Option<RailConnector>,
upper_connection: Option<RailConnector>,
//superelevation: Box<dyn SuperElevationSampler>, // Sometime I may get around to making this.
// cache some stuff
//endpoint0: Option<Transform3D>,
//endpoint1: Option<Transform3D>,
//length: Option<f32>,
}
impl<A: AlignmentSampler, E: ElevationSampler> SimpleSegment<A, E> {
pub fn new(alignment: A, elevation: E) -> Self {
Self {
alignment,
elevation,
lower_connection: Option::None,
upper_connection: Option::None,
//endpoint0: None,
//endpoint1: None,
//length: None,
}
}
}
impl<A: AlignmentSampler, E: ElevationSampler> RailSegment for SimpleSegment<A, E> {
fn get_length(&self, _index: usize) -> Result<f32, rail_segment::Error> {
/*match self.length {
Some(length) => Ok(length),
None => {
let result = self.elevation.length();
self.length = Some(result);
Ok(result)
},
}*/
Ok(self.elevation.length())
}
fn sample(&self, _index: usize, offset: f32) -> Result<Vector3, rail_segment::Error> {
if offset < 0.0 || offset > self.get_length(1).unwrap() {
return Err(rail_segment::Error::OffsetError);
}
let top_down_t = self.elevation.to_top_down_normalized(offset / self.elevation.length());
let xz = self.alignment.sample_normalized(top_down_t);
let y = self.elevation.sample_normalized(top_down_t);
Ok(Vector3::new(xz.x, y, xz.y))
}
fn sample_with_rotation(&self, _index: usize, offset: f32) -> Result<Transform3D, rail_segment::Error> {
if offset < 0.0 || offset > self.get_length(1).unwrap() {
return Err(rail_segment::Error::OffsetError);
}
let top_down_t = self.elevation.to_top_down_normalized(offset / self.elevation.length());
let direction_xz = self.alignment.tangent_normalized(top_down_t);
let direction_y = self.elevation.grade_normalized(top_down_t);
let direction = Vector3::new(direction_xz.x, direction_y, direction_xz.y);
let basis = new_looking_at(direction, Vector3::UP, false);
let xz = self.alignment.sample_normalized(top_down_t);
let y = self.elevation.sample_normalized(top_down_t);
Ok(Transform3D::new(basis, Vector3::new(xz.x, y, xz.y)))
}
fn sample_with_rotation_reverse(&self, _index: usize, offset: f32) -> Result<Transform3D, rail_segment::Error> {
if offset < 0.0 || offset > self.get_length(1).unwrap() {
return Err(rail_segment::Error::OffsetError);
}
let top_down_t = self.elevation.to_top_down_normalized(offset / self.elevation.length());
let direction_zx = self.alignment.tangent_normalized(top_down_t);
let direction_y = self.elevation.grade_normalized(top_down_t);
let direction = Vector3::new(-direction_zx.x, -direction_y, -direction_zx.y);
let basis = new_looking_at(direction, Vector3::UP, false);
let xz = self.alignment.sample_normalized(top_down_t);
let y = self.elevation.sample_normalized(top_down_t);
Ok(Transform3D::new(basis, Vector3::new(xz.x, y, xz.y)))
}
fn sample_up_vector(&self, _index: usize, offset: f32) -> Result<Vector3, rail_segment::Error> {
if offset < 0.0 || offset > self.get_length(1).unwrap() {
return Err(rail_segment::Error::OffsetError);
}
let top_down_t = self.elevation.to_top_down_normalized(offset / self.elevation.length());
let direction_xz = self.alignment.tangent_normalized(top_down_t);
let direction_y = self.elevation.grade_normalized(top_down_t);
let direction = Vector3::new(direction_xz.x, direction_y, direction_xz.y);
let normal = Vector3::new(direction_xz.y, 0.0, -direction_xz.x);
Ok(direction.cross(normal).normalized())
}
fn segment_from_endpoint(&self, _endpoint: usize) -> Result<usize, rail_segment::Error> {
Ok(1)
}
fn get_endpoint_transform(&self, endpoint: usize) -> Result<Transform3D, rail_segment::Error> {
match endpoint {
/*0 => {
match self.endpoint0 {
Some(result) => Ok(result),
None => {
let result = self.sample_with_rotation_reverse(0, 0.0).unwrap();
self.endpoint0 = Some(result);
Ok(result)
},
}
},
1 => {
match self.endpoint1 {
Some(result) => Ok(result),
None => {
let result = self.sample_with_rotation(0, self.get_length(1).unwrap()).unwrap();
self.endpoint1 = Some(result);
Ok(result)
},
}
},*/
0 => self.sample_with_rotation_reverse(0, 0.0),
1 => self.sample_with_rotation(0, self.get_length(1).unwrap()),
_ => Err(rail_segment::Error::EndpointError),
}
}
fn is_endpoint_upper(&self, endpoint: usize) -> Result<bool, rail_segment::Error> {
match endpoint {
0 => Ok(false),
1 => Ok(true),
_ => Err(rail_segment::Error::EndpointError),
}
}
fn num_endpoints(&self) -> usize {
2
}
fn borrow_connection(&self, endpoint: usize) -> Result<&RailConnector, rail_segment::Error> {
match endpoint {
0 => {
if let Some(ret) = &self.lower_connection {
Ok(ret)
} else {
Err(rail_segment::Error::NeighborError)
}
},
1 => {
if let Some(ret) = &self.upper_connection {
Ok(ret)
} else {
Err(rail_segment::Error::NeighborError)
}
},
_ => Err(rail_segment::Error::EndpointError),
}
}
fn check_connection(&self, endpoint: usize) -> Result<bool, rail_segment::Error> {
match endpoint {
0 => {
match &self.lower_connection {
Some(_) => Ok(true),
None => Ok(false),
}
},
1 => {
match &self.upper_connection {
Some(_) => Ok(true),
None => Ok(false),
}
},
_ => Err(rail_segment::Error::EndpointError),
}
}
fn connection_iter(&self) -> rail_segment::ConnectionIter {
rail_segment::ConnectionIter::new(self)
}
fn add_connection(&mut self, endpoint: usize, connector: RailConnector) -> Result<bool, rail_segment::Error> {
match endpoint {
0 => {
self.lower_connection = Option::Some(connector);
Ok(true)
},
1 => {
self.upper_connection = Option::Some(connector);
Ok(true)
},
_ => Err(rail_segment::Error::EndpointError),
}
}
fn remove_connection(&mut self, endpoint: usize) -> Result<bool, rail_segment::Error> {
match endpoint {
0 => {
self.lower_connection = Option::None;
Ok(true)
},
1 => {
self.upper_connection = Option::None;
Ok(true)
},
_ => Err(rail_segment::Error::EndpointError),
}
}
fn remove_all_connections(&mut self) {
self.lower_connection = Option::None;
self.upper_connection = Option::None;
}
fn get_state(&self) -> f32 {
0.0
}
fn set_state(&mut self, _: f32) {
// This does nothing for simple segments.
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::samplers::tangent_sampler::*;
fn compare_vectors(vec1: Vector3, vec2: Vector3, epsilon: f32, message: &str) {
assert!((vec1.x - vec2.x).abs() <= epsilon && (vec1.y - vec2.y).abs() <= epsilon,
"{message} Expected {vec2}, received {vec1}.")
}
/*fn compare_floats(float1: f32, float2: f32, epsilon: f32, message: &str) {
assert!((float1 - float2).abs() <= epsilon,
"{message} Expected {float2}, received {float1}.")
}*/
#[test]
fn test_basis_new_looking_at() {
let test_basis = new_looking_at(Vector3::FORWARD, Vector3::UP, false);
assert_eq!(test_basis.col_a(), Vector3::RIGHT,
"Column A was incorrect.");
assert_eq!(test_basis.col_b(), Vector3::UP,
"Column B was incorrect.");
assert_eq!(test_basis.col_c(), Vector3::BACK,
"Column C was incorrect.");
}
#[test]
fn test_get_length() {
let alignment = TangentAlignmentSampler::new(Vector2::new(1.0, 1.0), Vector2::new(10.0, 13.0));
let elevation = TangentElevationSampler::new(1.0, 9.0, 15.0);
let test_segment = SimpleSegment::new(alignment, elevation);
assert_eq!(test_segment.get_length(1).unwrap(), 17.0,
"Segment length was incorrect.");
}
#[test]
fn test_sample() {
let alignment = TangentAlignmentSampler::new(Vector2::new(1.0, 1.0), Vector2::new(10.0, 13.0));
let elevation = TangentElevationSampler::new(1.0, 9.0, 15.0);
let test_segment = SimpleSegment::new(alignment, elevation);
assert_eq!(test_segment.sample(1, 0.0).unwrap(), Vector3::new(1.0, 1.0, 1.0),
"Segment begins at the wrong location.");
assert_eq!(test_segment.sample(1, 17.0).unwrap(), Vector3::new(10.0, 9.0, 13.0),
"Segment ends at the wrong location.");
assert_eq!(test_segment.sample(1, 8.5).unwrap(), Vector3::new(5.5, 5.0, 7.0),
"Segment passes through the wrong location.");
}
#[test]
fn test_sample_with_rotation() {
let alignment = TangentAlignmentSampler::new(Vector2::new(1.0, 1.0), Vector2::new(10.0, 13.0));
let elevation = TangentElevationSampler::new(1.0, 9.0, 15.0);
let test_segment = SimpleSegment::new(alignment, elevation);
let direction_forward = Vector3::new(-9.0 / 17.0, -8.0 / 17.0, -12.0 / 17.0);
let direction_right = Vector3::new(-0.8, 0.0, 0.6);
let direction_up = direction_forward.cross(direction_right);
let transform1 = test_segment.sample_with_rotation(1, 0.0).unwrap();
let epsilon = 1e-6;
assert_eq!(transform1.origin, Vector3::new(1.0, 1.0, 1.0),
"Segment begins at the wrong location.");
compare_vectors(transform1.basis.col_c(), direction_forward, epsilon,
"Segment begins facing the wrong direction.");
compare_vectors(transform1.basis.col_a(), direction_right, epsilon,
"Segment begins with the wrong right direction.");
compare_vectors(transform1.basis.col_b(), direction_up, epsilon,
"Segment begins with the wrong up direction.");
let transform2 = test_segment.sample_with_rotation(1, 17.0).unwrap();
assert_eq!(transform2.origin, Vector3::new(10.0, 9.0, 13.0),
"Segment ends at the wrong location.");
compare_vectors(transform2.basis.col_c(), direction_forward, epsilon,
"Segment ends facing the wrong direction.");
compare_vectors(transform2.basis.col_a(), direction_right, epsilon,
"Segment ends with the wrong righ direction.");
compare_vectors(transform2.basis.col_b(), direction_up, epsilon,
"Segment ends with the wrong up direction.");
let transform3 = test_segment.sample_with_rotation(1, 8.5).unwrap();
assert_eq!(transform3.origin, Vector3::new(5.5, 5.0, 7.0),
"Segment passes through the wrong location.");
compare_vectors(transform3.basis.col_c(), direction_forward, epsilon,
"Segment passes through facing the wrong direction.");
compare_vectors(transform3.basis.col_a(), direction_right, epsilon,
"Segment passes through with the wrong righ direction.");
compare_vectors(transform3.basis.col_b(), direction_up, epsilon,
"Segment passes through with the wrong up direction.");
}
#[test]
fn test_sample_with_rotation_reverse() {
let alignment = TangentAlignmentSampler::new(Vector2::new(1.0, 1.0), Vector2::new(10.0, 13.0));
let elevation = TangentElevationSampler::new(1.0, 9.0, 15.0);
let test_segment = SimpleSegment::new(alignment, elevation);
let direction_forward = Vector3::new(9.0 / 17.0, 8.0 / 17.0, 12.0 / 17.0);
let direction_right = Vector3::new(0.8, 0.0, -0.6);
let direction_up = direction_forward.cross(direction_right);
let transform1 = test_segment.sample_with_rotation_reverse(1, 0.0).unwrap();
let epsilon = 1e-6;
assert_eq!(transform1.origin, Vector3::new(1.0, 1.0, 1.0),
"Segment begins at the wrong location.");
compare_vectors(transform1.basis.col_c(), direction_forward, epsilon,
"Segment begins facing the wrong direction.");
compare_vectors(transform1.basis.col_a(), direction_right, epsilon,
"Segment begins with the wrong right direction.");
compare_vectors(transform1.basis.col_b(), direction_up, epsilon,
"Segment begins with the wrong up direction.");
let transform2 = test_segment.sample_with_rotation_reverse(1, 17.0).unwrap();
assert_eq!(transform2.origin, Vector3::new(10.0, 9.0, 13.0),
"Segment ends at the wrong location.");
compare_vectors(transform2.basis.col_c(), direction_forward, epsilon,
"Segment ends facing the wrong direction.");
compare_vectors(transform2.basis.col_a(), direction_right, epsilon,
"Segment ends with the wrong righ direction.");
compare_vectors(transform2.basis.col_b(), direction_up, epsilon,
"Segment ends with the wrong up direction.");
let transform3 = test_segment.sample_with_rotation_reverse(1, 8.5).unwrap();
assert_eq!(transform3.origin, Vector3::new(5.5, 5.0, 7.0),
"Segment passes through the wrong location.");
compare_vectors(transform3.basis.col_c(), direction_forward, epsilon,
"Segment passes through facing the wrong direction.");
compare_vectors(transform3.basis.col_a(), direction_right, epsilon,
"Segment passes through with the wrong righ direction.");
compare_vectors(transform3.basis.col_b(), direction_up, epsilon,
"Segment passes through with the wrong up direction.");
}
#[test]
fn test_sample_up_vector() {
let alignment = TangentAlignmentSampler::new(Vector2::new(1.0, 1.0), Vector2::new(10.0, 13.0));
let elevation = TangentElevationSampler::new(1.0, 9.0, 15.0);
let test_segment = SimpleSegment::new(alignment, elevation);
let direction_forward = Vector3::new(-9.0 / 17.0, -8.0 / 17.0, -12.0 / 17.0);
let direction_right = Vector3::new(-0.8, 0.0, 0.6);
let direction_up = direction_forward.cross(direction_right);
let up1 = test_segment.sample_up_vector(1, 0.0).unwrap();
let epsilon = 1e-6;
compare_vectors(up1, direction_up, epsilon,
"Segment begins with the wrong up direction.");
let up2 = test_segment.sample_up_vector(1, 17.0).unwrap();
compare_vectors(up2, direction_up, epsilon,
"Segment ends with the wrong up direction.");
let up3 = test_segment.sample_up_vector(1, 8.5).unwrap();
compare_vectors(up3, direction_up, epsilon,
"Segment passes through with the wrong up direction.");
}
}