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

13
src/baked_segment.rs Normal file
View file

@ -0,0 +1,13 @@
use crate::prelude::*;
use std::string::String;
use std::vec::Vec;
pub struct BakedSegment {
// Connections
lower_segment: String,
upper_segment: String,
// Shape
bake_interval: f32,
shape: Vec<Transform3D>,
}

View file

@ -1,4 +1,9 @@
mod rail_graph; mod rail_graph;
mod rail_segment;
mod rail_connector;
mod samplers;
mod segment_endpoint;
mod simple_segment;
mod prelude { mod prelude {
pub use godot::prelude::*; pub use godot::prelude::*;
@ -10,3 +15,7 @@ struct RailNetworkExtension;
#[gdextension] #[gdextension]
unsafe impl ExtensionLibrary for RailNetworkExtension {} unsafe impl ExtensionLibrary for RailNetworkExtension {}
//mod tests {
// use super::samplers::tests;
//}

14
src/rail_connector.rs Normal file
View file

@ -0,0 +1,14 @@
#[derive(Copy, Clone)]
pub struct RailConnector {
pub segment_index: usize,
pub endpoint_index: usize,
}
impl RailConnector {
pub fn new(segment_index: usize, endpoint_index: usize) -> Self {
Self {
segment_index,
endpoint_index,
}
}
}

View file

@ -1 +1,276 @@
use crate::prelude::*; use crate::prelude::*;
use crate::rail_segment::RailSegment;
use crate::rail_connector::RailConnector;
//use std::collections::HashMap;
use std::vec::Vec;
//use std::string::String;
pub struct RailGraph {
segments: Vec<Option<Box<dyn RailSegment>>>,
// segment_names: HashMap<String, usize>,
min_available_index: usize,
}
impl RailGraph {
pub fn new() -> Self {
Self {
segments: Vec::<Option::<Box::<dyn RailSegment>>>::new(),
//segment_names: {},
min_available_index: 0,
}
}
pub fn get_segment(&self, index: usize) -> Option<&Box<dyn RailSegment>> {
match &self.segments.get(index) {
Some(ret) => ret.as_ref(),
None => None,
}
}
pub fn add_segment(&mut self, segment: Box<dyn RailSegment>) -> usize {
let ret = self.min_available_index;
if self.segments.len() == self.min_available_index {
self.segments.push(Some(segment));
self.min_available_index = self.segments.len();
} else {
self.segments[self.min_available_index] = Some(segment);
self.update_min_available_index();
}
ret
}
pub fn remove_segment(&mut self, index: usize) {
self.isolate_segment(index);
self.segments[index] = Option::None;
if index < self.min_available_index {
self.min_available_index = index;
}
}
pub fn connect_endpoints(&mut self, index1: usize, endpoint1: usize, index2: usize, endpoint2: usize) {
// Needs improvement: silently fails with invalid endpoints.
let connector1 = RailConnector::new(index2, endpoint2);
let connector2 = RailConnector::new(index1, endpoint1);
// Before we continue, make sure that anything that these endpoints are already connected to are disconnected.
if let Some(segment1) = &self.segments[index1] {
if let Ok(old_connection) = segment1.borrow_connection(endpoint1) {
let old_index = old_connection.segment_index;
let old_endpoint = old_connection.endpoint_index;
if let Some(old_segment) = &mut self.segments[old_index] {
let _ = old_segment.remove_connection(old_endpoint);
}
}
}
if let Some(segment2) = &self.segments[index2] {
if let Ok(old_connection) = segment2.borrow_connection(endpoint2) {
let old_index = old_connection.segment_index;
let old_endpoint = old_connection.endpoint_index;
if let Some(old_segment) = &mut self.segments[old_index] {
let _ = old_segment.remove_connection(old_endpoint);
}
}
}
// Connect them now
if let Some(segment1) = &mut self.segments[index1] {
let _ = segment1.add_connection(endpoint1, connector1);
}
if let Some(segment2) = &mut self.segments[index2] {
let _ = segment2.add_connection(endpoint2, connector2);
}
}
pub fn disconnect_endpoint(&mut self, index: usize, endpoint: usize) {
if let Some(segment) = &self.segments[index] {
if let Ok(old_connection) = segment.borrow_connection(endpoint) {
let old_index = old_connection.segment_index;
let old_endpoint = old_connection.endpoint_index;
if let Some(old_segment) = &mut self.segments[old_index] {
let _ = old_segment.remove_connection(old_endpoint);
}
}
}
if let Some(segment) = &mut self.segments[index] {
let _ = segment.remove_connection(endpoint);
}
}
pub fn append_segment(&mut self,
new_segment: Box<dyn RailSegment>,
new_segment_endpoint: usize,
onto_segment: usize,
ont_segment_endpoint: usize) {
let index = self.add_segment(new_segment);
self.connect_endpoints(index, new_segment_endpoint, onto_segment, ont_segment_endpoint);
}
fn isolate_segment(&mut self, index: usize) {
// Get the old connections
let mut connections = Vec::<RailConnector>::new();
if let Some(segment) = &self.segments[index] {
for connection in segment.connection_iter() {
connections.push(*connection);
}
}
// disconnect neighbors
for connection in connections {
if let Some(old_segment) = &mut self.segments[connection.segment_index] {
let _ = old_segment.remove_connection(connection.endpoint_index);
}
}
// reflect the same change in the original segment.
if let Some(segment) = &mut self.segments[index] {
segment.remove_all_connections();
}
}
fn update_min_available_index(&mut self) {
while let Some(_segment) = &self.segments[self.min_available_index] {
self.min_available_index += 1;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::samplers::tangent_sampler::*;
use crate::samplers::circular_sampler::*;
use crate::samplers::sampler::*;
use crate::simple_segment::*;
use std::f32::consts::PI;
/*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}.")
}*/
fn create_test_graph() -> RailGraph {
let alignment1 = TangentAlignmentSampler::new(Vector2::new(0.0, 0.0), Vector2::new(1.0, 0.0));
let elevation1 = TangentElevationSampler::new(0.0, 0.0, 1.0);
let test_segment1 = Box::new(SimpleSegment::new(alignment1, elevation1));
let alignment2 = CircularAlignmentSampler::new(0.0, PI * 0.5, Vector2::new(1.0, 1.0), 1.0, true);
let elevation2 = TangentElevationSampler::new(0.0, 0.0, alignment2.length());
let test_segment2 = Box::new(SimpleSegment::new(alignment2, elevation2));
let alignment3 = TangentAlignmentSampler::new(Vector2::new(2.0, 1.0), Vector2::new(2.0, 2.0));
let elevation3 = TangentElevationSampler::new(0.0, 0.0, 1.0);
let test_segment3 = Box::new(SimpleSegment::new(alignment3, elevation3));
let mut test_graph = RailGraph::new();
test_graph.add_segment(test_segment1);
test_graph.append_segment(test_segment2, 0, 0, 1);
test_graph.append_segment(test_segment3, 0, 1, 1);
return test_graph;
}
#[test]
fn test_get_segment() {
let test_graph = create_test_graph();
assert!(match test_graph.get_segment(0) { Option::Some(_)=>true, _=>false },
"Segment 0 could not be retrieved.");
assert!(match test_graph.get_segment(1) { Option::Some(_)=>true, _=>false },
"Segment 1 could not be retrieved.");
assert!(match test_graph.get_segment(2) { Option::Some(_)=>true, _=>false },
"Segment 2 could not be retrieved.");
assert!(match test_graph.get_segment(3) { Option::None=>true, _=>false },
"Segment that didn't exist was retrieved.");
}
#[test]
fn test_add_segment() {
let alignment = TangentAlignmentSampler::new(Vector2::new(2.0, 2.0), Vector2::new(2.0, 3.0));
let elevation = TangentElevationSampler::new(0.0, 0.0, 1.0);
let test_segment = Box::new(SimpleSegment::new(alignment, elevation));
let mut test_graph = create_test_graph();
let index = test_graph.add_segment(test_segment);
assert_eq!(index, 3, "Segment was added with incorrect index.");
assert_eq!(test_graph.segments.len(), 4, "Graph has incorrect number of segments.");
}
#[test]
fn test_remove_segment() {
let mut test_graph = create_test_graph();
test_graph.remove_segment(1);
assert_eq!(test_graph.segments.len(), 3, "Graph has incorrect number of segments.");
assert_eq!(test_graph.min_available_index, 1, "Incorrect min_available_index.");
let first_segment = test_graph.get_segment(0).unwrap();
assert!(!first_segment.check_connection(1).unwrap(), "Connection 1 wasn't broken.");
let second_segment = test_graph.get_segment(2).unwrap();
assert!(!second_segment.check_connection(0).unwrap(), "Connection 2 wasn't broken.");
assert!(match test_graph.get_segment(1) { Option::None=>true, _=>false },
"Segment that didn't exist was retrieved.");
}
#[test]
fn test_connect_endpoints() {
let mut test_graph = create_test_graph();
test_graph.connect_endpoints(0, 0, 2, 1);
let first_segment = test_graph.get_segment(0).unwrap();
let first_connection = first_segment.borrow_connection(0).unwrap();
assert_eq!(first_connection.segment_index, 2, "Connection(0, 0) had the incorrect index.");
assert_eq!(first_connection.endpoint_index, 1, "Connection(0, 0) had the incorrect endpoint.");
let second_segment = test_graph.get_segment(2).unwrap();
let second_connection = second_segment.borrow_connection(1).unwrap();
assert_eq!(second_connection.segment_index, 0, "Connection(2, 1) had the incorrect index.");
assert_eq!(second_connection.endpoint_index, 0, "Connection(2, 1) had the incorrect endpoint.");
test_graph.connect_endpoints(0, 1, 2, 0);
let first_segment = test_graph.get_segment(0).unwrap();
let first_connection = first_segment.borrow_connection(1).unwrap();
assert_eq!(first_connection.segment_index, 2, "Connection(0, 1) had the incorrect index.");
assert_eq!(first_connection.endpoint_index, 0, "Connection(0, 1) had the incorrect endpoint.");
let second_segment = test_graph.get_segment(2).unwrap();
let second_connection = second_segment.borrow_connection(0).unwrap();
assert_eq!(second_connection.segment_index, 0, "Connection(2, 0) had the incorrect index.");
assert_eq!(second_connection.endpoint_index, 1, "Connection(2, 0) had the incorrect endpoint.");
let middle_segment = test_graph.get_segment(1).unwrap();
let first_connection = middle_segment.borrow_connection(0);
assert!(match first_connection { Result::Err(_)=>true, _=>false },
"Connection(1, 0) still exists.");
let second_connection = middle_segment.borrow_connection(1);
assert!(match second_connection { Result::Err(_)=>true, _=>false },
"Connection(1, 1) still exists.")
}
#[test]
fn test_disconnect_endpoint() {
let mut test_graph = create_test_graph();
test_graph.disconnect_endpoint(0, 1);
let first_segment = test_graph.get_segment(0).unwrap();
let first_connection = first_segment.borrow_connection(1);
assert!(match first_connection { Result::Err(_)=>true, _=>false },
"Connection(0, 1) still exists.");
let middle_segment = test_graph.get_segment(1).unwrap();
let middle_connection = middle_segment.borrow_connection(0);
assert!(match middle_connection { Result::Err(_)=>true, _=>false },
"Connection(1, 0) still exists.");
}
#[test]
fn test_append_segment() {
let test_graph = create_test_graph();
let first_segment = test_graph.get_segment(0).unwrap();
let second_segment = test_graph.get_segment(1).unwrap();
let third_segment = test_graph.get_segment(2).unwrap();
let connection01 = first_segment.borrow_connection(1).unwrap();
assert_eq!(connection01.segment_index, 1, "Connection(0, 1) had the incorrect index.");
assert_eq!(connection01.endpoint_index, 0, "Connection(0, 1) had the incorrect endpoint.");
let connection10 = second_segment.borrow_connection(0).unwrap();
assert_eq!(connection10.segment_index, 0, "Connection(1, 0) had the incorrect index.");
assert_eq!(connection10.endpoint_index, 1, "Connection(1, 0) had the incorrect endpoint.");
let connection11 = second_segment.borrow_connection(1).unwrap();
assert_eq!(connection11.segment_index, 2, "Connection(1, 1) had the incorrect index.");
assert_eq!(connection11.endpoint_index, 0, "Connection(1, 1) had the incorrect endpoint.");
let connection20 = third_segment.borrow_connection(0).unwrap();
assert_eq!(connection20.segment_index, 1, "Connection(2, 0) had the incorrect index.");
assert_eq!(connection20.endpoint_index, 1, "Connection(2, 0) had the incorrect endpoint.");
}
}

136
src/rail_segment.rs Normal file
View file

@ -0,0 +1,136 @@
use crate::prelude::*;
use crate::rail_connector::RailConnector;
use std::error;
use std::fmt;
use std::iter::Iterator;
#[derive(Debug)]
pub enum Error {
IndexError,
EndpointError,
OffsetError,
NeighborError,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::IndexError => write!(f, "Nonexistent internal curve index"),
Self::EndpointError => write!(f, "Nonexistent endpoint index"),
Self::OffsetError => write!(f, "Invalid offset"),
Self::NeighborError => write!(f, "Invalid Neighbor"),
}
}
}
impl error::Error for Error {}
pub struct ConnectionIter<'a> {
segment: &'a dyn RailSegment,
index: usize,
}
impl<'a> ConnectionIter<'a> {
pub fn new(segment: &'a impl RailSegment) -> Self {
Self {
segment,
index: 0,
}
}
}
impl<'a> Iterator for ConnectionIter<'a> {
type Item = &'a RailConnector;
fn next(&mut self) -> Option<Self::Item> {
while self.index < self.segment.num_endpoints() {
if let Ok(connection) = self.segment.borrow_connection(self.index) {
self.index += 1;
return Some(connection);
} else {
self.index += 1;
}
}
return None;
}
}
/** A RailSegment represents a section of track.
* They contain at least one internal curve which is addressibe via an integer
* index. Valid indecies start at 0, though a negative index may be used to
* indicate that a point does not exist on any internal curve. Offsets are in
* Godot units (which shall be interpreted as meters).
*/
pub trait RailSegment {
/** Get the overall length of the internal curve represented by the given
* `index`.
*/
fn get_length(&self, index: usize) -> Result<f32, Error>;
/** Get the location defined by the given `offset` on the internal curve
* represented by the given `index`.
*/
fn sample(&self, index: usize, offset: f32) -> Result<Vector3, Error>;
/** Get the location defined by the given `offset` on the internal curve
* represented by the given `index`, but with additional rotation data.
*/
fn sample_with_rotation(&self, index: usize, offset: f32) -> Result<Transform3D, Error>;
/** Similar to sample_with_rotation, but the rotation is facing the opposite direction.
*/
fn sample_with_rotation_reverse(&self, index: usize, offset: f32) -> Result<Transform3D, Error>;
/** Get the direction that constitutes "up" for the given `offset` on the
* internal curve represented by the given `index`.
*/
fn sample_up_vector(&self, index: usize, offset: f32) -> Result<Vector3, Error>;
// endpoint functions
/** Get the segment index associated with an endpoint. */
fn segment_from_endpoint(&self, endpoint: usize) -> Result<usize, Error>;
/** Get the location of an endpoint. */
fn get_endpoint_transform(&self, endpoint: usize) -> Result<Transform3D, Error>;
/** Get whether an endpoint represents the upper or lower end of a segment. */
fn is_endpoint_upper(&self, endpoint: usize) -> Result<bool, Error>;
/** Get the number of endpoins this segment has. */
fn num_endpoints(&self) -> usize;
// connection functions
/** Borrow the connector associated with the given endpoint. */
fn borrow_connection(&self, endpoint: usize) -> Result<&RailConnector, Error>;
/** Check if a connection is established on the given endpoint. */
fn check_connection(&self, endpoint: usize) -> Result<bool, Error>;
/** Get an iterator of the connections on this segment.
* If an endpoint is not connected, then it will not be represented by this iterator.
* This iterator keeps an immutable reference to the segment from which it was created.
*/
fn connection_iter(&self) -> ConnectionIter;
/** Add a connection to the given endpoint.
* Any previous connection on it is overwritten.
*/
fn add_connection(&mut self, endpoint: usize, connector: RailConnector) -> Result<bool, Error>;
/** Remove the given endpoint's connection if it has one. */
fn remove_connection(&mut self, endpoint: usize) -> Result<bool, Error>;
/** Remove all connections from the segment. */
fn remove_all_connections(&mut self);
// internal variable state functions
/** Get the internal state float value. */
fn get_state(&self) -> f32;
/** Set the internal state float value. */
fn set_state(&mut self, value: f32);
}

View file

@ -0,0 +1,461 @@
use crate::prelude::*;
use crate::samplers::sampler::*;
use std::f32::consts::PI;
/**
* Samples a segment of a circle.
* Angles are as on a compass, starting with 0 at North and increasing clockwise.
* North is Vector2(0.0, -1.0), East is Vector2(1.0, 0.0)
*/
pub struct CircularAlignmentSampler {
lower_end_rad: f32,
upper_end_rad: f32,
center: Vector2,
radius_meters: f32,
clockwise: bool,
}
impl CircularAlignmentSampler {
pub fn new(mut lower_end_rad: f32, mut upper_end_rad: f32, center: Vector2, radius_meters: f32, clockwise: bool) -> Self {
if clockwise && upper_end_rad < lower_end_rad {
upper_end_rad += 2.0 * PI;
} else if !clockwise && upper_end_rad > lower_end_rad {
lower_end_rad += 2.0 * PI;
}
Self {
lower_end_rad,
upper_end_rad,
center,
radius_meters,
clockwise,
}
}
fn angle_from_normalized(&self, t: f32) -> f32 {
self.lower_end_rad * (1.0 - t) + self.upper_end_rad * t
}
}
impl AlignmentSampler for CircularAlignmentSampler {
fn sample_normalized(&self, t: f32) -> Vector2 {
let t_rad = self.angle_from_normalized(t);
return Vector2::new(t_rad.sin() * self.radius_meters, -t_rad.cos() * self.radius_meters) + self.center;
}
fn tangent_normalized(&self, t: f32) -> Vector2 {
let t_rad = self.angle_from_normalized(t);
if self.clockwise {
return Vector2::new(t_rad.cos(), t_rad.sin());
} else {
return Vector2::new(-t_rad.cos(), -t_rad.sin());
}
}
fn sample_offset(&self, offset: f32) -> Vector2 {
self.sample_normalized(offset / self.length())
}
fn tangent_offset(&self, offset: f32) -> Vector2 {
self.tangent_normalized(offset / self.length())
}
fn length(&self) -> f32 {
(self.upper_end_rad - self.lower_end_rad).abs() * self.radius_meters
}
}
pub struct CircularElevationSampler {
lower_end: f32,
upper_end: f32,
center_meters: f32,
radius_meters: f32,
top_down_length: f32,
peak: bool, // as opposed to valley
}
impl CircularElevationSampler {
pub fn new(lower_end: f32,
upper_end: f32,
center_meters: f32,
radius_meters: f32,
peak: bool,) -> Self {
let top_down_length = (upper_end - lower_end) * radius_meters;
Self {
lower_end,
upper_end,
center_meters,
radius_meters,
top_down_length,
peak,
}
}
}
fn circle_function(x: f32) -> f32 {
let x = x.clamp(-1.0, 1.0);
(1.0 - x * x).sqrt()
}
impl ElevationSampler for CircularElevationSampler {
fn sample_normalized(&self, top_down_t: f32) -> f32 {
let actual_t = self.lower_end * (1.0 - top_down_t) + self.upper_end * top_down_t;
if self.peak {
return circle_function(actual_t) * self.radius_meters + self.center_meters;
} else {
return -circle_function(actual_t) * self.radius_meters + self.center_meters;
}
}
fn grade_normalized(&self, top_down_t: f32) -> f32 {
let actual_t = self.lower_end * (1.0 - top_down_t) + self.upper_end * top_down_t;
if self.peak {
return -actual_t / circle_function(actual_t);
} else {
return actual_t / circle_function(actual_t);
}
}
fn sample_offset(&self, offset: f32) -> f32 {
self.sample_normalized(offset / self.top_down_length)
}
fn grade_offset(&self, offset: f32) -> f32 {
self.grade_normalized(offset / self.top_down_length)
}
fn length(&self) -> f32 {
(self.upper_end.asin() - self.lower_end.asin()) * self.radius_meters
}
fn to_top_down_normalized(&self, actual_t: f32) -> f32 {
let actual_t = self.lower_end.asin() * (1.0 - actual_t) + self.upper_end.asin() * actual_t;
(actual_t.sin() - self.lower_end) / (self.upper_end - self.lower_end)
}
fn to_top_down_offset(&self, actual_offset: f32) -> f32 {
self.to_top_down_normalized(actual_offset / self.length()) * self.top_down_length
}
}
#[cfg(test)]
mod tests {
use super::*;
fn compare_vectors(vec1: Vector2, vec2: Vector2, message: &str) {
let epsilon = 1e-5; // This is really bad tolerance, but f32 sucks so I have no choice.
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, message: &str) {
let epsilon = 1e-5;
assert!((float1 - float2).abs() <= epsilon,
"{message} Expected {float2}, received {float1}.")
}
#[test]
fn test_circular_alignment_sample_normalized() {
let test_alignment_1 = CircularAlignmentSampler::new(0.0, PI, Vector2::new(1.0, 1.0), 2.0, true);
compare_vectors(test_alignment_1.sample_normalized(0.0), Vector2::new(1.0, -1.0),
"test_alignment_1 begins in the wrong location.");
compare_vectors(test_alignment_1.sample_normalized(1.0), Vector2::new(1.0, 3.0),
"test_alignment_1 ends in the wrong location.");
compare_vectors(test_alignment_1.sample_normalized(0.5), Vector2::new(3.0, 1.0),
"test_alignment_1 passes through the wrong location.");
compare_vectors(test_alignment_1.sample_normalized(-1.0), Vector2::new(1.0, 3.0),
"test_alignment_1 negative offset ends in the wrong location.");
compare_vectors(test_alignment_1.sample_normalized(2.0), Vector2::new(1.0, -1.0),
"test_alignment_1 double offset ends in the wrong location.");
let test_alignment_2 = CircularAlignmentSampler::new(3.0 * PI / 2.0, PI / 2.0, Vector2::new(1.0, 1.0), 2.0, true);
compare_vectors(test_alignment_2.sample_normalized(0.0), Vector2::new(-1.0, 1.0),
"test_alignment_2 begins in the wrong location.");
compare_vectors(test_alignment_2.sample_normalized(1.0), Vector2::new(3.0, 1.0),
"test_alignment_2 ends in the wrong location.");
compare_vectors(test_alignment_2.sample_normalized(0.5), Vector2::new(1.0, -1.0),
"test_alignment_2 passes through the wrong location.");
compare_vectors(test_alignment_2.sample_normalized(-1.0), Vector2::new(3.0, 1.0),
"test_alignment_2 negative offset ends in the wrong location.");
compare_vectors(test_alignment_2.sample_normalized(2.0), Vector2::new(-1.0, 1.0),
"test_alignment_2 double offset ends in the wrong location.");
let test_alignment_3 = CircularAlignmentSampler::new(0.0, PI, Vector2::new(1.0, 1.0), 2.0, false);
compare_vectors(test_alignment_3.sample_normalized(0.0), Vector2::new(1.0, -1.0),
"test_alignment_3 begins in the wrong location.");
compare_vectors(test_alignment_3.sample_normalized(1.0), Vector2::new(1.0, 3.0),
"test_alignment_3 ends in the wrong location.");
compare_vectors(test_alignment_3.sample_normalized(0.5), Vector2::new(-1.0, 1.0),
"test_alignment_3 passes through the wrong location.");
compare_vectors(test_alignment_3.sample_normalized(-1.0), Vector2::new(1.0, 3.0),
"test_alignment_3 negative offset ends in the wrong location.");
compare_vectors(test_alignment_3.sample_normalized(2.0), Vector2::new(1.0, -1.0),
"test_alignment_3 double offset ends in the wrong location.");
let test_alignment_4 = CircularAlignmentSampler::new(3.0 * PI / 2.0, PI / 2.0, Vector2::new(1.0, 1.0), 2.0, false);
compare_vectors(test_alignment_4.sample_normalized(0.0), Vector2::new(-1.0, 1.0),
"test_alignment_4 begins in the wrong location.");
compare_vectors(test_alignment_4.sample_normalized(1.0), Vector2::new(3.0, 1.0),
"test_alignment_4 ends in the wrong location.");
compare_vectors(test_alignment_4.sample_normalized(0.5), Vector2::new(1.0, 3.0),
"test_alignment_4 passes through the wrong location.");
compare_vectors(test_alignment_4.sample_normalized(-1.0), Vector2::new(3.0, 1.0),
"test_alignment_4 negative offset ends in the wrong location.");
compare_vectors(test_alignment_4.sample_normalized(2.0), Vector2::new(-1.0, 1.0),
"test_alignment_4 double offset ends in the wrong location.");
}
#[test]
fn test_circular_alignment_tangent_normalized() {
let test_alignment_1 = CircularAlignmentSampler::new(0.0, PI, Vector2::new(1.0, 1.0), 2.0, true);
compare_vectors(test_alignment_1.tangent_normalized(0.0), Vector2::new(1.0, 0.0),
"test_alignment_1 begins in the wrong direction.");
compare_vectors(test_alignment_1.tangent_normalized(1.0), Vector2::new(-1.0, 0.0),
"test_alignment_1 ends in the wrong direction.");
compare_vectors(test_alignment_1.tangent_normalized(0.5), Vector2::new(0.0, 1.0),
"test_alignment_1 passes through the wrong direction.");
compare_vectors(test_alignment_1.tangent_normalized(-1.0), Vector2::new(-1.0, 0.0),
"test_alignment_1 negative offset ends in the wrong direction.");
compare_vectors(test_alignment_1.tangent_normalized(2.0), Vector2::new(1.0, 0.0),
"test_alignment_1 double offset ends in the wrong direction.");
let test_alignment_2 = CircularAlignmentSampler::new(3.0 * PI / 2.0, PI / 2.0, Vector2::new(1.0, 1.0), 2.0, true);
compare_vectors(test_alignment_2.tangent_normalized(0.0), Vector2::new(0.0, -1.0),
"test_alignment_2 begins in the wrong direction.");
compare_vectors(test_alignment_2.tangent_normalized(1.0), Vector2::new(0.0, 1.0),
"test_alignment_2 ends in the wrong direction.");
compare_vectors(test_alignment_2.tangent_normalized(0.5), Vector2::new(1.0, 0.0),
"test_alignment_2 passes through the wrong direction.");
compare_vectors(test_alignment_2.tangent_normalized(-1.0), Vector2::new(0.0, 1.0),
"test_alignment_2 negative offset ends in the wrong direction.");
compare_vectors(test_alignment_2.tangent_normalized(2.0), Vector2::new(0.0, -1.0),
"test_alignment_2 double offset ends in the wrong direction.");
let test_alignment_3 = CircularAlignmentSampler::new(0.0, PI, Vector2::new(1.0, 1.0), 2.0, false);
compare_vectors(test_alignment_3.tangent_normalized(0.0), Vector2::new(-1.0, 0.0),
"test_alignment_3 begins in the wrong direction.");
compare_vectors(test_alignment_3.tangent_normalized(1.0), Vector2::new(1.0, 0.0),
"test_alignment_3 ends in the wrong direction.");
compare_vectors(test_alignment_3.tangent_normalized(0.5), Vector2::new(0.0, 1.0),
"test_alignment_3 passes through the wrong direction.");
compare_vectors(test_alignment_3.tangent_normalized(-1.0), Vector2::new(1.0, 0.),
"test_alignment_3 negative offset ends in the wrong direction.");
compare_vectors(test_alignment_3.tangent_normalized(2.0), Vector2::new(-1.0, 0.0),
"test_alignment_3 double offset ends in the wrong direction.");
let test_alignment_4 = CircularAlignmentSampler::new(3.0 * PI / 2.0, PI / 2.0, Vector2::new(1.0, 1.0), 2.0, false);
compare_vectors(test_alignment_4.tangent_normalized(0.0), Vector2::new(0.0, 1.0),
"test_alignment_4 begins in the wrong direction.");
compare_vectors(test_alignment_4.tangent_normalized(1.0), Vector2::new(0.0, -1.0),
"test_alignment_4 ends in the wrong direction.");
compare_vectors(test_alignment_4.tangent_normalized(0.5), Vector2::new(1.0, 0.0),
"test_alignment_4 passes through the wrong direction.");
compare_vectors(test_alignment_4.tangent_normalized(-1.0), Vector2::new(0.0, -1.0),
"test_alignment_4 negative offset ends in the wrong direction.");
compare_vectors(test_alignment_4.tangent_normalized(2.0), Vector2::new(0.0, 1.0),
"test_alignment_4 double offset ends in the wrong direction.");
}
#[test]
fn test_circular_alignment_sample_offset() {
let test_alignment_1 = CircularAlignmentSampler::new(0.0, PI, Vector2::new(1.0, 1.0), 2.0, true);
compare_vectors(test_alignment_1.sample_offset(0.0), Vector2::new(1.0, -1.0),
"test_alignment_1 begins in the wrong location.");
compare_vectors(test_alignment_1.sample_offset(2.0 * PI), Vector2::new(1.0, 3.0),
"test_alignment_1 ends in the wrong location.");
compare_vectors(test_alignment_1.sample_offset(PI), Vector2::new(3.0, 1.0),
"test_alignment_1 passes through the wrong location.");
compare_vectors(test_alignment_1.sample_offset(-2.0 * PI), Vector2::new(1.0, 3.0),
"test_alignment_1 negative offset ends in the wrong location.");
compare_vectors(test_alignment_1.sample_offset(4.0 * PI), Vector2::new(1.0, -1.0),
"test_alignment_1 double offset ends in the wrong location.");
let test_alignment_2 = CircularAlignmentSampler::new(3.0 * PI / 2.0, PI / 2.0, Vector2::new(1.0, 1.0), 2.0, true);
compare_vectors(test_alignment_2.sample_offset(0.0), Vector2::new(-1.0, 1.0),
"test_alignment_2 begins in the wrong location.");
compare_vectors(test_alignment_2.sample_offset(2.0 * PI), Vector2::new(3.0, 1.0),
"test_alignment_2 ends in the wrong location.");
compare_vectors(test_alignment_2.sample_offset(PI), Vector2::new(1.0, -1.0),
"test_alignment_2 passes through the wrong location.");
compare_vectors(test_alignment_2.sample_offset(-2.0 * PI), Vector2::new(3.0, 1.0),
"test_alignment_2 negative offset ends in the wrong location.");
compare_vectors(test_alignment_2.sample_offset(4.0 * PI), Vector2::new(-1.0, 1.0),
"test_alignment_2 double offset ends in the wrong location.");
let test_alignment_3 = CircularAlignmentSampler::new(0.0, PI, Vector2::new(1.0, 1.0), 2.0, false);
compare_vectors(test_alignment_3.sample_offset(0.0), Vector2::new(1.0, -1.0),
"test_alignment_3 begins in the wrong location.");
compare_vectors(test_alignment_3.sample_offset(2.0 * PI), Vector2::new(1.0, 3.0),
"test_alignment_3 ends in the wrong location.");
compare_vectors(test_alignment_3.sample_offset(PI), Vector2::new(-1.0, 1.0),
"test_alignment_3 passes through the wrong location.");
compare_vectors(test_alignment_3.sample_offset(-2.0 * PI), Vector2::new(1.0, 3.0),
"test_alignment_3 negative offset ends in the wrong location.");
compare_vectors(test_alignment_3.sample_offset(4.0 * PI), Vector2::new(1.0, -1.0),
"test_alignment_3 double offset ends in the wrong location.");
let test_alignment_4 = CircularAlignmentSampler::new(3.0 * PI / 2.0, PI / 2.0, Vector2::new(1.0, 1.0), 2.0, false);
compare_vectors(test_alignment_4.sample_offset(0.0), Vector2::new(-1.0, 1.0),
"test_alignment_4 begins in the wrong location.");
compare_vectors(test_alignment_4.sample_offset(2.0 * PI), Vector2::new(3.0, 1.0),
"test_alignment_4 ends in the wrong location.");
compare_vectors(test_alignment_4.sample_offset(PI), Vector2::new(1.0, 3.0),
"test_alignment_4 passes through the wrong location.");
compare_vectors(test_alignment_4.sample_offset(-2.0 * PI), Vector2::new(3.0, 1.0),
"test_alignment_4 negative offset ends in the wrong location.");
compare_vectors(test_alignment_4.sample_offset(4.0 * PI), Vector2::new(-1.0, 1.0),
"test_alignment_4 double offset ends in the wrong location.");
}
#[test]
fn test_circular_alignment_tangent_offset() {
let test_alignment_1 = CircularAlignmentSampler::new(0.0, PI, Vector2::new(1.0, 1.0), 2.0, true);
compare_vectors(test_alignment_1.tangent_offset(0.0), Vector2::new(1.0, 0.0),
"test_alignment_1 begins in the wrong direction.");
compare_vectors(test_alignment_1.tangent_offset(2.0 * PI), Vector2::new(-1.0, 0.0),
"test_alignment_1 ends in the wrong direction.");
compare_vectors(test_alignment_1.tangent_offset(PI), Vector2::new(0.0, 1.0),
"test_alignment_1 passes through the wrong direction.");
compare_vectors(test_alignment_1.tangent_offset(-2.0 * PI), Vector2::new(-1.0, 0.0),
"test_alignment_1 negative offset ends in the wrong direction.");
compare_vectors(test_alignment_1.tangent_offset(4.0 * PI), Vector2::new(1.0, 0.0),
"test_alignment_1 double offset ends in the wrong direction.");
let test_alignment_2 = CircularAlignmentSampler::new(3.0 * PI / 2.0, PI / 2.0, Vector2::new(1.0, 1.0), 2.0, true);
compare_vectors(test_alignment_2.tangent_offset(0.0), Vector2::new(0.0, -1.0),
"test_alignment_2 begins in the wrong direction.");
compare_vectors(test_alignment_2.tangent_offset(2.0 * PI), Vector2::new(0.0, 1.0),
"test_alignment_2 ends in the wrong direction.");
compare_vectors(test_alignment_2.tangent_offset(PI), Vector2::new(1.0, 0.0),
"test_alignment_2 passes through the wrong direction.");
compare_vectors(test_alignment_2.tangent_offset(-2.0 * PI), Vector2::new(0.0, 1.0),
"test_alignment_2 negative offset ends in the wrong direction.");
compare_vectors(test_alignment_2.tangent_offset(4.0 * PI), Vector2::new(0.0, -1.0),
"test_alignment_2 double offset ends in the wrong direction.");
let test_alignment_3 = CircularAlignmentSampler::new(0.0, PI, Vector2::new(1.0, 1.0), 2.0, false);
compare_vectors(test_alignment_3.tangent_offset(0.0), Vector2::new(-1.0, 0.0),
"test_alignment_3 begins in the wrong direction.");
compare_vectors(test_alignment_3.tangent_offset(2.0 * PI), Vector2::new(1.0, 0.0),
"test_alignment_3 ends in the wrong direction.");
compare_vectors(test_alignment_3.tangent_offset(PI), Vector2::new(0.0, 1.0),
"test_alignment_3 passes through the wrong direction.");
compare_vectors(test_alignment_3.tangent_offset(-2.0 * PI), Vector2::new(1.0, 0.),
"test_alignment_3 negative offset ends in the wrong direction.");
compare_vectors(test_alignment_3.tangent_offset(4.0 * PI), Vector2::new(-1.0, 0.0),
"test_alignment_3 double offset ends in the wrong direction.");
let test_alignment_4 = CircularAlignmentSampler::new(3.0 * PI / 2.0, PI / 2.0, Vector2::new(1.0, 1.0), 2.0, false);
compare_vectors(test_alignment_4.tangent_offset(0.0), Vector2::new(0.0, 1.0),
"test_alignment_4 begins in the wrong direction.");
compare_vectors(test_alignment_4.tangent_offset(2.0 * PI), Vector2::new(0.0, -1.0),
"test_alignment_4 ends in the wrong direction.");
compare_vectors(test_alignment_4.tangent_offset(PI), Vector2::new(1.0, 0.0),
"test_alignment_4 passes through the wrong direction.");
compare_vectors(test_alignment_4.tangent_offset(-2.0 * PI), Vector2::new(0.0, -1.0),
"test_alignment_4 negative offset ends in the wrong direction.");
compare_vectors(test_alignment_4.tangent_offset(4.0 * PI), Vector2::new(0.0, 1.0),
"test_alignment_4 double offset ends in the wrong direction.");
}
#[test]
fn test_circular_alignment_length() {
let test_alignment_1 = CircularAlignmentSampler::new(0.0, PI, Vector2::new(1.0, 1.0), 2.0, true);
compare_floats(test_alignment_1.length(), PI * 2.0, "test_alignment_1 had an incorrect length.");
}
#[test]
fn test_circular_elevation_sample_normalized() {
let test_elevation_1 = CircularElevationSampler::new(-0.5, 0.5, 1.0, 2.0, true);
compare_floats(test_elevation_1.sample_normalized(0.0), 1.0 + 3.0_f32.sqrt(),
"test_elevation_1 begins in the wrong location.");
compare_floats(test_elevation_1.sample_normalized(1.0), 1.0 + 3.0_f32.sqrt(),
"test_elevation_1 ends in the wrong location.");
compare_floats(test_elevation_1.sample_normalized(0.5), 3.0,
"test_elevation_1 passes through the wrong location.");
let test_elevation_2 = CircularElevationSampler::new(-0.5, 0.5, 1.0, 2.0, false);
compare_floats(test_elevation_2.sample_normalized(0.0), 1.0 - 3.0_f32.sqrt(),
"test_elevation_2 begins in the wrong location.");
compare_floats(test_elevation_2.sample_normalized(1.0), 1.0 - 3.0_f32.sqrt(),
"test_elevation_2 ends in the wrong location.");
compare_floats(test_elevation_2.sample_normalized(0.5), -1.0,
"test_elevation_2 passes through the wrong location.");
}
#[test]
fn test_circular_elevation_grade_normalized() {
let test_elevation_1 = CircularElevationSampler::new(-0.5, 0.5, 1.0, 2.0, true);
compare_floats(test_elevation_1.grade_normalized(0.0), 1.0 / 3.0_f32.sqrt(),
"test_elevation_1 begins in the wrong grade.");
compare_floats(test_elevation_1.grade_normalized(1.0), -1.0 / 3.0_f32.sqrt(),
"test_elevation_1 ends in the wrong grade.");
compare_floats(test_elevation_1.grade_normalized(0.5), 0.0,
"test_elevation_1 passes through the wrong grade.");
let test_elevation_2 = CircularElevationSampler::new(-0.5, 0.5, 1.0, 2.0, false);
compare_floats(test_elevation_2.grade_normalized(0.0), -1.0 / 3.0_f32.sqrt(),
"test_elevation_2 begins in the wrong grade.");
compare_floats(test_elevation_2.grade_normalized(1.0), 1.0 / 3.0_f32.sqrt(),
"test_elevation_2 ends in the wrong grade.");
compare_floats(test_elevation_2.grade_normalized(0.5), 0.0,
"test_elevation_2 passes through the wrong grade.");
}
#[test]
fn test_circular_elevation_sample_offset() {
let test_elevation_1 = CircularElevationSampler::new(-0.5, 0.5, 1.0, 2.0, true);
compare_floats(test_elevation_1.sample_offset(0.0), 1.0 + 3.0_f32.sqrt(),
"test_elevation_1 begins in the wrong location.");
compare_floats(test_elevation_1.sample_offset(2.0), 1.0 + 3.0_f32.sqrt(),
"test_elevation_1 ends in the wrong location.");
compare_floats(test_elevation_1.sample_offset(1.0), 3.0,
"test_elevation_1 passes through the wrong location.");
let test_elevation_2 = CircularElevationSampler::new(-0.5, 0.5, 1.0, 2.0, false);
compare_floats(test_elevation_2.sample_offset(0.0), 1.0 - 3.0_f32.sqrt(),
"test_elevation_2 begins in the wrong location.");
compare_floats(test_elevation_2.sample_offset(2.0), 1.0 - 3.0_f32.sqrt(),
"test_elevation_2 ends in the wrong location.");
compare_floats(test_elevation_2.sample_offset(1.0), -1.0,
"test_elevation_2 passes through the wrong location.");
}
#[test]
fn test_circular_elevation_grade_offset() {
let test_elevation_1 = CircularElevationSampler::new(-0.5, 0.5, 1.0, 2.0, true);
compare_floats(test_elevation_1.grade_offset(0.0), 1.0 / 3.0_f32.sqrt(),
"test_elevation_1 begins in the wrong grade.");
compare_floats(test_elevation_1.grade_offset(2.0), -1.0 / 3.0_f32.sqrt(),
"test_elevation_1 ends in the wrong grade.");
compare_floats(test_elevation_1.grade_offset(1.0), 0.0,
"test_elevation_1 passes through the wrong grade.");
let test_elevation_2 = CircularElevationSampler::new(-0.5, 0.5, 1.0, 2.0, false);
compare_floats(test_elevation_2.grade_offset(0.0), -1.0 / 3.0_f32.sqrt(),
"test_elevation_2 begins in the wrong grade.");
compare_floats(test_elevation_2.grade_offset(2.0), 1.0 / 3.0_f32.sqrt(),
"test_elevation_2 ends in the wrong grade.");
compare_floats(test_elevation_2.grade_offset(1.0), 0.0,
"test_elevation_2 passes through the wrong grade.");
}
#[test]
fn test_circular_elevation_length() {
let test_elevation_1 = CircularElevationSampler::new(-0.5, 0.5, 1.0, 2.0, true);
compare_floats(test_elevation_1.length(), 2.0 * PI / 3.0, "test_elevation_1 had an incorrect length.");
}
#[test]
fn test_circular_elevation_to_top_down_normalized() {
let test_elevation_1 = CircularElevationSampler::new(-1.0, 1.0, 1.0, 2.0, true);
compare_floats(test_elevation_1.to_top_down_normalized(0.0), 0.0,
"test_elevation_1 begins in the wrong location.");
compare_floats(test_elevation_1.to_top_down_normalized(1.0), 1.0,
"test_elevation_1 ends in the wrong location.");
compare_floats(test_elevation_1.to_top_down_normalized(0.5), 0.5,
"test_elevation_1 passes through the wrong location.");
compare_floats(test_elevation_1.to_top_down_normalized(0.25), (1.0 - (2.0_f32.sqrt() / 2.0)) / 2.0,
"test_elevation_1 quarter offset ends in the wrong location.");
compare_floats(test_elevation_1.to_top_down_normalized(0.75), ((2.0_f32.sqrt() / 2.0) + 1.0) / 2.0,
"test_elevation_1 three quarter offset ends in the wrong location.");
}
#[test]
fn test_circular_elevation_to_top_down_offset() {
let test_elevation_1 = CircularElevationSampler::new(-1.0, 1.0, 1.0, 2.0, true);
compare_floats(test_elevation_1.to_top_down_offset(0.0), 0.0,
"test_elevation_1 begins in the wrong location.");
compare_floats(test_elevation_1.to_top_down_offset(2.0 * PI), 4.0,
"test_elevation_1 ends in the wrong location.");
compare_floats(test_elevation_1.to_top_down_offset(PI), 2.0,
"test_elevation_1 passes through the wrong location.");
compare_floats(test_elevation_1.to_top_down_offset(PI / 2.0), (1.0 - (2.0_f32.sqrt() / 2.0)) * 2.0,
"test_elevation_1 quarter offset ends in the wrong location.");
compare_floats(test_elevation_1.to_top_down_offset(3.0 * PI / 2.0), ((2.0_f32.sqrt() / 2.0) + 1.0) * 2.0,
"test_elevation_1 three quarter offset ends in the wrong location.");
}
}

3
src/samplers/mod.rs Normal file
View file

@ -0,0 +1,3 @@
pub mod sampler;
pub mod tangent_sampler;
pub mod circular_sampler;

78
src/samplers/sampler.rs Normal file
View file

@ -0,0 +1,78 @@
use crate::prelude::*;
/**
* Implement this for 2D top-down point samplers.
* These samplers will be used for a segment's top-down shape.
*/
pub trait AlignmentSampler {
/**
* Input distance from lower end in percent, get the top-down coordinates.
*/
fn sample_normalized(&self, t: f32) -> Vector2;
/**
* Input distance from lower end in percent, get the normalized direction of the curve at that point.
*/
fn tangent_normalized(&self, t: f32) -> Vector2;
/**
* Input distance from lower end in meters, get the top-down coordinates.
*/
fn sample_offset(&self, offset: f32) -> Vector2;
/**
* Input distance from lower end in meters, get the normalized direction of the curve at that point.
*/
fn tangent_offset(&self, offset: f32) -> Vector2;
/**
* Get the length as seen from above. This does not account for elevation changes.
*/
fn length(&self) -> f32;
}
/**
* Implement this for 1D elevation point samplers.
* These samplers will be used for a segment's elevation or superelevation.
* Structs that implement this should keep track of their top-down length.
*/
pub trait ElevationSampler {
/**
* Input normalized distance from lower end, get elevation at that point.
*/
fn sample_normalized(&self, top_down_t: f32) -> f32;
/**
* Input normalized distance from lower end, get the derivative of elevation at that point.
* This is equivalent to the track's grade at this point.
*/
fn grade_normalized(&self, top_down_t: f32) -> f32;
/**
* Input actual distance in meters from lower end, get elevation at that point.
*/
fn sample_offset(&self, top_down_offset: f32) -> f32;
/**
* Input actual distance in meters from lower end, get the derivative of elevation at that point.
* This is equivalent to the track's grade at this point.
*/
fn grade_offset(&self, top_down_offset: f32) -> f32;
/**
* Get the actual length accounting for elevation.
*/
fn length(&self) -> f32;
/**
* Given an offset relative to the total length of the segment, return the associated distance.
*/
fn to_top_down_normalized(&self, actual_t: f32) -> f32;
/**
* Given an offset relative to the total length of the segment, return the associated distance.
*/
fn to_top_down_offset(&self, actual_offset: f32) -> f32;
}

View file

@ -0,0 +1,34 @@
use crate::prelude::*;
use crate::samplers::sampler::Sampler3D;
pub struct SplineSampler3D {
lower_end: Vector3,
lower_control: Vector3,
upper_control: Vector3,
upper_end: Vector3,
bake_interval: f32,
}
impl Sampler3D {
fn sample(&self, t: f32) -> Vector3 {
let q0 = lower_end + lower_control * t;
let q1 = (lower_end + lower_control).lerp((upper_end + upper_control), t);
let q2 = upper_end + upper_control * (1-t);
let r0 = q0.lerp(q1, t);
let r1 = q1.lerp(q2, t);
return r0.lerp(r1, t);
}
fn tangent(&self, t: f32) -> Vector3 {
let q0 = lower_end + lower_control * t;
let q1 = (lower_end + lower_control).lerp((upper_end + upper_control), t);
let q2 = upper_end + upper_control * (1-t);
let r0 = q0.lerp(q1, t);
let r1 = q1.lerp(q2, t);
return r1 - r0;
}
}

View file

@ -0,0 +1,368 @@
use crate::prelude::*;
use crate::samplers::sampler::*;
pub struct TangentAlignmentSampler {
lower_end: Vector2,
upper_end: Vector2,
}
impl TangentAlignmentSampler {
pub fn new(lower_end: Vector2, upper_end: Vector2) -> Self {
Self {lower_end, upper_end}
}
}
impl AlignmentSampler for TangentAlignmentSampler {
fn sample_normalized(&self, t: f32) -> Vector2 {
self.lower_end.lerp(self.upper_end, t)
}
fn tangent_normalized(&self, _t: f32) -> Vector2 {
(self.upper_end - self.lower_end).normalized()
}
fn sample_offset(&self, offset: f32) -> Vector2 {
self.sample_normalized(offset / self.length())
}
fn tangent_offset(&self, _offset: f32) -> Vector2 {
self.tangent_normalized(0.0) // tangent is constant for this type of alignment
}
fn length(&self) -> f32 {
(self.upper_end - self.lower_end).length()
}
}
pub struct TangentElevationSampler {
lower_end: f32,
upper_end: f32,
top_down_length: f32,
}
impl TangentElevationSampler {
pub fn new(lower_end: f32, upper_end: f32, top_down_length: f32) -> Self {
if top_down_length <= 0.0 {
panic!("TangentElevationSampler cannot have 0 length.");
}
Self {
lower_end,
upper_end,
top_down_length,
}
}
}
impl ElevationSampler for TangentElevationSampler {
fn sample_normalized(&self, top_down_t: f32) -> f32 {
self.lower_end * (1.0 - top_down_t) + self.upper_end * top_down_t
}
fn grade_normalized(&self, _top_down_t: f32) -> f32 {
(self.upper_end - self.lower_end) / self.top_down_length
}
fn sample_offset(&self, top_down_offset: f32) -> f32 {
self.sample_normalized(top_down_offset / self.top_down_length)
}
fn grade_offset(&self, _top_down_offset: f32) -> f32 {
self.grade_normalized(0.0)
}
fn length(&self) -> f32 {
let rise = self.upper_end - self.lower_end;
return (rise * rise + self.top_down_length * self.top_down_length).sqrt();
}
fn to_top_down_normalized(&self, actual_t: f32) -> f32 {
actual_t
}
fn to_top_down_offset(&self, actual_offset: f32) -> f32 {
self.top_down_length / self.length() * actual_offset
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tangent_alignment_sample_offset() {
let test_alignment_1 = TangentAlignmentSampler::new(Vector2::new(1.0, 1.0), Vector2::new(4.0, 5.0));
assert_eq!(test_alignment_1.sample_offset(0.0), Vector2::new(1.0, 1.0),
"test_alignment_1 begins in the wrong location.");
assert_eq!(test_alignment_1.sample_offset(5.0), Vector2::new(4.0, 5.0),
"test_alignment_1 ends in the wrong location.");
assert_eq!(test_alignment_1.sample_offset(2.5), Vector2::new(2.5, 3.0),
"test_alignment_1 passes through the wrong location.");
assert_eq!(test_alignment_1.sample_offset(-5.0), Vector2::new(-2.0, -3.0),
"test_alignment_1 negative offset ends in the wrong location.");
assert_eq!(test_alignment_1.sample_offset(10.0), Vector2::new(7.0, 9.0),
"test_alignment_1 double offset ends in the wrong location.");
let test_alignment_2 = TangentAlignmentSampler::new(Vector2::new(-1.0, -1.0), Vector2::new(-4.0, -5.0));
assert_eq!(test_alignment_2.sample_offset(0.0), Vector2::new(-1.0, -1.0),
"test_alignment_2 begins in the wrong location.");
assert_eq!(test_alignment_2.sample_offset(5.0), Vector2::new(-4.0, -5.0),
"test_alignment_2 ends in the wrong location.");
assert_eq!(test_alignment_2.sample_offset(2.5), Vector2::new(-2.5, -3.0),
"test_alignment_2 passes through the wrong location.");
assert_eq!(test_alignment_2.sample_offset(-5.0), Vector2::new(2.0, 3.0),
"test_alignment_2 negative offset ends in the wrong location.");
assert_eq!(test_alignment_2.sample_offset(10.0), Vector2::new(-7.0, -9.0),
"test_alignment_2 double offset ends in the wrong location.");
}
#[test]
fn test_tangent_alignment_tangent_offset() {
let test_alignment_1 = TangentAlignmentSampler::new(Vector2::new(0.0, 0.0), Vector2::new(1.0, 0.0));
let correct_tangent_1 = Vector2::new(1.0, 0.0);
assert_eq!(test_alignment_1.tangent_offset(0.0), correct_tangent_1,
"test_alignment_1 begins with an incorrect tangent.");
assert_eq!(test_alignment_1.tangent_offset(1.0), correct_tangent_1,
"test_alignment_1 ends with an incorrect tangent.");
assert_eq!(test_alignment_1.tangent_offset(0.5), correct_tangent_1,
"test_alignment_1 has an incorrect tangent in the middle.");
assert_eq!(test_alignment_1.tangent_offset(-1.0),correct_tangent_1,
"test_alignment_1 negative offset has an incorrect tangent.");
assert_eq!(test_alignment_1.tangent_offset(2.0), correct_tangent_1,
"test_alignment_1 double offset has an incorrect tangent.");
let test_alignment_2 = TangentAlignmentSampler::new(Vector2::new(0.0, 0.0), Vector2::new(0.0, -1.0));
let correct_tangent_2 = Vector2::new(0.0, -1.0);
assert_eq!(test_alignment_2.tangent_offset(0.0), correct_tangent_2,
"test_alignment_2 begins with an incorrect tangent.");
assert_eq!(test_alignment_2.tangent_offset(1.0), correct_tangent_2,
"test_alignment_2 ends with an incorrect tangent.");
assert_eq!(test_alignment_2.tangent_offset(0.5), correct_tangent_2,
"test_alignment_2 has an incorrect tangent in the middle.");
assert_eq!(test_alignment_2.tangent_offset(-1.0), correct_tangent_2,
"test_alignment_2 negative offset has an incorrect tangent.");
assert_eq!(test_alignment_2.tangent_offset(2.0), correct_tangent_2,
"test_alignment_2 double offset has an incorrect tangent.");
let test_alignment_3 = TangentAlignmentSampler::new(Vector2::new(1.0, 1.0), Vector2::new(4.0, 5.0));
let correct_tangent_3 = Vector2::new(3.0, 4.0).normalized();
assert_eq!(test_alignment_3.tangent_offset(0.0), correct_tangent_3,
"test_alignment_3 begins with an incorrect tangent.");
assert_eq!(test_alignment_3.tangent_offset(5.0), correct_tangent_3,
"test_alignment_3 ends with an incorrect tangent.");
assert_eq!(test_alignment_3.tangent_offset(2.5), correct_tangent_3,
"test_alignment_3 has an incorrect tangent in the middle.");
assert_eq!(test_alignment_3.tangent_offset(-5.0), correct_tangent_3,
"test_alignment_3 negative offset has an incorrect tangent.");
assert_eq!(test_alignment_3.tangent_offset(10.0), correct_tangent_3,
"test_alignment_3 double offset has an incorrect tangent.");
}
#[test]
fn test_tangent_alignment_sample_normalized() {
let test_alignment_1 = TangentAlignmentSampler::new(Vector2::new(1.0, 1.0), Vector2::new(4.0, 5.0));
assert_eq!(test_alignment_1.sample_normalized(0.0), Vector2::new(1.0, 1.0),
"test_alignment_1 begins in the wrong location.");
assert_eq!(test_alignment_1.sample_normalized(1.0), Vector2::new(4.0, 5.0),
"test_alignment_1 ends in the wrong location.");
assert_eq!(test_alignment_1.sample_normalized(0.5), Vector2::new(2.5, 3.0),
"test_alignment_1 passes through the wrong location.");
assert_eq!(test_alignment_1.sample_normalized(-1.0), Vector2::new(-2.0, -3.0),
"test_alignment_1 negative offset ends in the wrong location.");
assert_eq!(test_alignment_1.sample_normalized(2.0), Vector2::new(7.0, 9.0),
"test_alignment_1 double offset ends in the wrong location.");
let test_alignment_2 = TangentAlignmentSampler::new(Vector2::new(-1.0, -1.0), Vector2::new(-4.0, -5.0));
assert_eq!(test_alignment_2.sample_normalized(0.0), Vector2::new(-1.0, -1.0),
"test_alignment_2 begins in the wrong location.");
assert_eq!(test_alignment_2.sample_normalized(1.0), Vector2::new(-4.0, -5.0),
"test_alignment_2 ends in the wrong location.");
assert_eq!(test_alignment_2.sample_normalized(0.5), Vector2::new(-2.5, -3.0),
"test_alignment_2 passes through the wrong location.");
assert_eq!(test_alignment_2.sample_normalized(-1.0), Vector2::new(2.0, 3.0),
"test_alignment_2 negative offset ends in the wrong location.");
assert_eq!(test_alignment_2.sample_normalized(2.0), Vector2::new(-7.0, -9.0),
"test_alignment_2 double offset ends in the wrong location.");
}
#[test]
fn test_tangent_alignment_tangent_normalized() {
let test_alignment_1 = TangentAlignmentSampler::new(Vector2::new(0.0, 0.0), Vector2::new(1.0, 0.0));
let correct_tangent_1 = Vector2::new(1.0, 0.0);
assert_eq!(test_alignment_1.tangent_normalized(0.0),correct_tangent_1,
"test_alignment_1 begins with an incorrect tangent.");
assert_eq!(test_alignment_1.tangent_normalized(1.0), correct_tangent_1,
"test_alignment_1 ends with an incorrect tangent.");
assert_eq!(test_alignment_1.tangent_normalized(0.5), correct_tangent_1,
"test_alignment_1 has an incorrect tangent in the middle.");
assert_eq!(test_alignment_1.tangent_normalized(-1.0), correct_tangent_1,
"test_alignment_1 negative offset has an incorrect tangent.");
assert_eq!(test_alignment_1.tangent_normalized(2.0), correct_tangent_1,
"test_alignment_1 double offset has an incorrect tangent.");
let test_alignment_2 = TangentAlignmentSampler::new(Vector2::new(0.0, 0.0), Vector2::new(0.0, -1.0));
let correct_tangent_2 = Vector2::new(0.0, -1.0);
assert_eq!(test_alignment_2.tangent_normalized(0.0), correct_tangent_2,
"test_alignment_2 begins with an incorrect tangent.");
assert_eq!(test_alignment_2.tangent_normalized(1.0), correct_tangent_2,
"test_alignment_2 ends with an incorrect tangent.");
assert_eq!(test_alignment_2.tangent_normalized(0.5), correct_tangent_2,
"test_alignment_2 has an incorrect tangent in the middle.");
assert_eq!(test_alignment_2.tangent_normalized(-1.0), correct_tangent_2,
"test_alignment_2 negative offset has an incorrect tangent.");
assert_eq!(test_alignment_2.tangent_normalized(2.0), correct_tangent_2,
"test_alignment_2 double offset has an incorrect tangent.");
let test_alignment_3 = TangentAlignmentSampler::new(Vector2::new(1.0, 1.0), Vector2::new(4.0, 5.0));
let correct_tangent_3 = Vector2::new(3.0, 4.0).normalized();
assert_eq!(test_alignment_3.tangent_normalized(0.0), correct_tangent_3,
"test_alignment_3 begins with an incorrect tangent.");
assert_eq!(test_alignment_3.tangent_normalized(1.0), correct_tangent_3,
"test_alignment_3 ends with an incorrect tangent.");
assert_eq!(test_alignment_3.tangent_normalized(0.5), correct_tangent_3,
"test_alignment_3 has an incorrect tangent in the middle.");
assert_eq!(test_alignment_3.tangent_normalized(-1.0), correct_tangent_3,
"test_alignment_3 negative offset has an incorrect tangent.");
assert_eq!(test_alignment_3.tangent_normalized(2.0), correct_tangent_3,
"test_alignment_3 double offset has an incorrect tangent.");
}
#[test]
fn test_tangent_alignment_length() {
let test_alignment_1 = TangentAlignmentSampler::new(Vector2::new(1.0, 1.0), Vector2::new(4.0, 5.0));
assert_eq!(test_alignment_1.length(), 5.0, "test_alignment_1 had an incorrect length.");
}
#[test]
fn test_tangent_elevation_sample_offset() {
let test_elevation_1 = TangentElevationSampler::new(1.0, 2.0, 5.0);
assert_eq!(test_elevation_1.sample_offset(0.0), 1.0,
"test_elevation_1 begins in the wrong location.");
assert_eq!(test_elevation_1.sample_offset(5.0), 2.0,
"test_elevation_1 ends in the wrong location.");
assert_eq!(test_elevation_1.sample_offset(2.5), 1.5,
"test_elevation_1 passes through the wrong location.");
assert_eq!(test_elevation_1.sample_offset(-5.0), 0.0,
"test_elevation_1 negative offset ends in the wrong location.");
assert_eq!(test_elevation_1.sample_offset(10.0), 3.0,
"test_elevation_1 double offset ends in the wrong location.");
let test_elevation_2 = TangentElevationSampler::new(2.0, 2.0, 5.0);
assert_eq!(test_elevation_2.sample_offset(0.0), 2.0,
"test_elevation_2 begins in the wrong location.");
assert_eq!(test_elevation_2.sample_offset(5.0), 2.0,
"test_elevation_2 ends in the wrong location.");
assert_eq!(test_elevation_2.sample_offset(2.5), 2.0,
"test_elevation_2 passes through the wrong location.");
assert_eq!(test_elevation_2.sample_offset(-5.0), 2.0,
"test_elevation_2 negative offset ends in the wrong location.");
assert_eq!(test_elevation_2.sample_offset(10.0), 2.0,
"test_elevation_2 double offset ends in the wrong location.");
}
#[test]
fn test_tangent_elevation_grade_offset() {
let test_elevation_1 = TangentElevationSampler::new(1.0, 2.0, 5.0);
assert_eq!(test_elevation_1.grade_offset(0.0), 0.2,
"test_elevation_1 begins in the wrong location.");
assert_eq!(test_elevation_1.grade_offset(5.0), 0.2,
"test_elevation_1 ends in the wrong location.");
assert_eq!(test_elevation_1.grade_offset(2.5), 0.2,
"test_elevation_1 passes through the wrong location.");
assert_eq!(test_elevation_1.grade_offset(-5.0), 0.2,
"test_elevation_1 negative offset ends in the wrong location.");
assert_eq!(test_elevation_1.grade_offset(10.0), 0.2,
"test_elevation_1 double offset ends in the wrong location.");
let test_elevation_2 = TangentElevationSampler::new(2.0, 2.0, 5.0);
assert_eq!(test_elevation_2.grade_offset(0.0), 0.0,
"test_elevation_2 begins in the wrong location.");
assert_eq!(test_elevation_2.grade_offset(5.0), 0.0,
"test_elevation_2 ends in the wrong location.");
assert_eq!(test_elevation_2.grade_offset(2.5), 0.0,
"test_elevation_2 passes through the wrong location.");
assert_eq!(test_elevation_2.grade_offset(-5.0), 0.0,
"test_elevation_2 negative offset ends in the wrong location.");
assert_eq!(test_elevation_2.grade_offset(10.0), 0.0,
"test_elevation_2 double offset ends in the wrong location.");
}
#[test]
fn test_tangent_elevation_sample_normalized() {
let test_elevation_1 = TangentElevationSampler::new(1.0, 2.0, 5.0);
assert_eq!(test_elevation_1.sample_normalized(0.0), 1.0,
"test_elevation_1 begins in the wrong location.");
assert_eq!(test_elevation_1.sample_normalized(1.0), 2.0,
"test_elevation_1 ends in the wrong location.");
assert_eq!(test_elevation_1.sample_normalized(0.5), 1.5,
"test_elevation_1 passes through the wrong location.");
assert_eq!(test_elevation_1.sample_normalized(-1.0), 0.0,
"test_elevation_1 negative offset ends in the wrong location.");
assert_eq!(test_elevation_1.sample_normalized(2.0), 3.0,
"test_elevation_1 double offset ends in the wrong location.");
let test_elevation_2 = TangentElevationSampler::new(2.0, 2.0, 5.0);
assert_eq!(test_elevation_2.sample_normalized(0.0), 2.0,
"test_elevation_2 begins in the wrong location.");
assert_eq!(test_elevation_2.sample_normalized(1.0), 2.0,
"test_elevation_2 ends in the wrong location.");
assert_eq!(test_elevation_2.sample_normalized(0.5), 2.0,
"test_elevation_2 passes through the wrong location.");
assert_eq!(test_elevation_2.sample_normalized(-1.0), 2.0,
"test_elevation_2 negative offset ends in the wrong location.");
assert_eq!(test_elevation_2.sample_normalized(2.0), 2.0,
"test_elevation_2 double offset ends in the wrong location.");
}
#[test]
fn test_tangent_elevation_grade_normalized() {
let test_elevation_1 = TangentElevationSampler::new(1.0, 2.0, 5.0);
assert_eq!(test_elevation_1.grade_normalized(0.0), 0.2,
"test_elevation_1 begins in the wrong location.");
assert_eq!(test_elevation_1.grade_normalized(1.0), 0.2,
"test_elevation_1 ends in the wrong location.");
assert_eq!(test_elevation_1.grade_normalized(0.5), 0.2,
"test_elevation_1 passes through the wrong location.");
assert_eq!(test_elevation_1.grade_normalized(-1.0), 0.2,
"test_elevation_1 negative offset ends in the wrong location.");
assert_eq!(test_elevation_1.grade_normalized(2.0), 0.2,
"test_elevation_1 double offset ends in the wrong location.");
let test_elevation_2 = TangentElevationSampler::new(2.0, 2.0, 5.0);
assert_eq!(test_elevation_2.grade_normalized(0.0), 0.0,
"test_elevation_2 begins in the wrong location.");
assert_eq!(test_elevation_2.grade_normalized(1.0), 0.0,
"test_elevation_2 ends in the wrong location.");
assert_eq!(test_elevation_2.grade_normalized(0.5), 0.0,
"test_elevation_2 passes through the wrong location.");
assert_eq!(test_elevation_2.grade_normalized(-1.0), 0.0,
"test_elevation_2 negative offset ends in the wrong location.");
assert_eq!(test_elevation_2.grade_normalized(2.0), 0.0,
"test_elevation_2 double offset ends in the wrong location.");
}
#[test]
fn test_tangent_elevation_length() {
let test_elevation_1 = TangentElevationSampler::new(1.0, 4.0, 4.0);
assert_eq!(test_elevation_1.length(), 5.0, "test_elevation_1 had an incorrect length.");
}
#[test]
fn test_tangent_elevation_to_top_down_normalized() {
let test_elevation_1 = TangentElevationSampler::new(1.0, 4.0, 4.0);
assert_eq!(test_elevation_1.to_top_down_normalized(0.0), 0.0,
"test_elevation_1 begins in the wrong location.");
assert_eq!(test_elevation_1.to_top_down_normalized(1.0), 1.0,
"test_elevation_1 ends in the wrong location.");
assert_eq!(test_elevation_1.to_top_down_normalized(0.5), 0.5,
"test_elevation_1 passes through the wrong location.");
assert_eq!(test_elevation_1.to_top_down_normalized(-1.0), -1.0,
"test_elevation_1 negative offset ends in the wrong location.");
assert_eq!(test_elevation_1.to_top_down_normalized(2.0), 2.0,
"test_elevation_1 double offset ends in the wrong location.");
}
#[test]
fn test_tangent_elevation_to_top_down_offset() {
let test_elevation_1 = TangentElevationSampler::new(1.0, 4.0, 4.0);
assert_eq!(test_elevation_1.to_top_down_offset(0.0), 0.0,
"test_elevation_1 begins in the wrong location.");
assert_eq!(test_elevation_1.to_top_down_offset(5.0), 4.0,
"test_elevation_1 ends in the wrong location.");
assert_eq!(test_elevation_1.to_top_down_offset(2.5), 2.0,
"test_elevation_1 passes through the wrong location.");
assert_eq!(test_elevation_1.to_top_down_offset(-5.0), -4.0,
"test_elevation_1 negative offset ends in the wrong location.");
assert_eq!(test_elevation_1.to_top_down_offset(10.0), 8.0,
"test_elevation_1 double offset ends in the wrong location.");
}
}

13
src/segment_endpoint.rs Normal file
View file

@ -0,0 +1,13 @@
pub struct SegmentEndpoint {
internal_segment_index: usize,
is_upper: bool,
}
impl SegmentEndpoint {
fn new(internal_segment_index: usize, is_upper: bool) -> Self {
Self {
internal_segment_index,
is_upper,
}
}
}

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.");
}
}