use hyperreal::{Real, RealSign};
use crate::classify::{is_zero, real_sign};
use crate::contour::Contour2;
use crate::curve_string::CurveString2;
use crate::segment::{CircularArc2, LineSeg2, Segment2};
use crate::{Classification, CurveError, CurvePolicy, CurveResult, Point2, UncertaintyReason};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum OffsetCap {
Round,
Butt,
Square,
}
impl LineSeg2 {
pub fn offset_left(&self, distance: Real) -> CurveResult<Self> {
let length = self.length_squared().sqrt()?;
let (dx, dy) = self.delta();
let normal_x = ((-dy) / &length)?;
let normal_y = (dx / &length)?;
let offset_x = &normal_x * &distance;
let offset_y = &normal_y * &distance;
Self::try_new(
self.start().translated(offset_x.clone(), offset_y.clone()),
self.end().translated(offset_x, offset_y),
)
}
}
impl CircularArc2 {
pub fn offset_left(
&self,
distance: Real,
policy: &CurvePolicy,
) -> CurveResult<Classification<Self>> {
let radius = self.radius_squared().sqrt()?;
let offset_radius = if self.is_clockwise() {
&radius + &distance
} else {
&radius - &distance
};
match real_sign(&offset_radius, policy) {
Some(RealSign::Positive) => {}
Some(RealSign::Zero | RealSign::Negative) => {
return Ok(Classification::Uncertain(UncertaintyReason::Unsupported));
}
None => return Ok(Classification::Uncertain(UncertaintyReason::RealSign)),
}
let radius_scale = (offset_radius / &radius)?;
let offset = Self::try_from_center_with_bulge(
scale_from_center(self.start(), self.center(), &radius_scale),
scale_from_center(self.end(), self.center(), &radius_scale),
self.center().clone(),
self.is_clockwise(),
self.bulge().cloned(),
)?;
Ok(Classification::Decided(offset))
}
}
impl Segment2 {
pub fn offset_left(
&self,
distance: Real,
policy: &CurvePolicy,
) -> CurveResult<Classification<Self>> {
match self {
Self::Line(line) => line
.offset_left(distance)
.map(Self::Line)
.map(Classification::Decided),
Self::Arc(arc) => arc
.offset_left(distance, policy)
.map(|arc| arc.map(Segment2::Arc)),
}
}
}
impl CurveString2 {
pub fn offset_left_with_line_joins(
&self,
distance: Real,
policy: &CurvePolicy,
) -> CurveResult<Classification<Self>> {
if is_zero(&distance, policy) == Some(true) {
return Ok(Classification::Decided(self.clone()));
}
let offsets = match offset_segments_left(self.segments(), &distance, policy)? {
Classification::Decided(offsets) => offsets,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
let joined = match joined_offset_segments(self.segments(), &offsets, false, policy)? {
Classification::Decided(joined) => joined,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
Ok(Classification::Decided(CurveString2::new_unchecked(joined)))
}
pub fn offset_left_checked(
&self,
distance: Real,
policy: &CurvePolicy,
) -> CurveResult<Classification<Self>> {
let offset = match self.offset_left_with_line_joins(distance, policy)? {
Classification::Decided(offset) => offset,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
match offset.has_self_contacts(policy)? {
Classification::Decided(false) => Ok(Classification::Decided(offset)),
Classification::Decided(true) => {
Ok(Classification::Uncertain(UncertaintyReason::Unsupported))
}
Classification::Uncertain(reason) => Ok(Classification::Uncertain(reason)),
}
}
pub fn offset_outline(
&self,
distance: Real,
cap: OffsetCap,
policy: &CurvePolicy,
) -> CurveResult<Classification<Contour2>> {
match cap {
OffsetCap::Round => self.offset_outline_round_caps(distance, policy),
OffsetCap::Butt => self.offset_outline_butt_caps(distance, policy),
OffsetCap::Square => self.offset_outline_square_caps(distance, policy),
}
}
pub fn offset_outline_round_caps(
&self,
distance: Real,
policy: &CurvePolicy,
) -> CurveResult<Classification<Contour2>> {
let offsets = match checked_outline_offsets(self, distance, policy)? {
Classification::Decided(offsets) => offsets,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
let OutlineOffsets {
left,
right,
start_center,
end_center,
left_start,
left_end,
right_start,
right_end,
} = offsets;
let mut segments = Vec::with_capacity(left.len() + right.len() + 2);
segments.extend(left.into_segments());
match round_cap_arc(&left_end, &right_end, &end_center, policy)? {
Classification::Decided(cap) => segments.push(Segment2::Arc(cap)),
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
}
segments.extend(reversed_segments(right.into_segments()));
match round_cap_arc(&right_start, &left_start, &start_center, policy)? {
Classification::Decided(cap) => segments.push(Segment2::Arc(cap)),
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
}
checked_outline_contour(segments, policy)
}
pub fn offset_outline_butt_caps(
&self,
distance: Real,
policy: &CurvePolicy,
) -> CurveResult<Classification<Contour2>> {
let offsets = match checked_outline_offsets(self, distance, policy)? {
Classification::Decided(offsets) => offsets,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
let OutlineOffsets {
left,
right,
left_start,
left_end,
right_start,
right_end,
..
} = offsets;
let mut segments = Vec::with_capacity(left.len() + right.len() + 2);
segments.extend(left.into_segments());
match cap_line(&left_end, &right_end)? {
Classification::Decided(cap) => segments.push(Segment2::Line(cap)),
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
}
segments.extend(reversed_segments(right.into_segments()));
match cap_line(&right_start, &left_start)? {
Classification::Decided(cap) => segments.push(Segment2::Line(cap)),
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
}
checked_outline_contour(segments, policy)
}
pub fn offset_outline_square_caps(
&self,
distance: Real,
policy: &CurvePolicy,
) -> CurveResult<Classification<Contour2>> {
let offsets = match checked_outline_offsets(self, distance.clone(), policy)? {
Classification::Decided(offsets) => offsets,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
let OutlineOffsets {
left,
right,
left_start,
left_end,
right_start,
right_end,
..
} = offsets;
let start_tangent = unit_tangent_at_segment_start(
self.segments()
.first()
.ok_or(CurveError::EmptyCurveString)?,
)?;
let end_tangent = unit_tangent_at_segment_end(
self.segments().last().ok_or(CurveError::EmptyCurveString)?,
)?;
let start_dx = &start_tangent.0 * &distance;
let start_dy = &start_tangent.1 * &distance;
let end_dx = &end_tangent.0 * &distance;
let end_dy = &end_tangent.1 * &distance;
let left_start_square = left_start.translated(-start_dx.clone(), -start_dy.clone());
let right_start_square = right_start.translated(-start_dx, -start_dy);
let left_end_square = left_end.translated(end_dx.clone(), end_dy.clone());
let right_end_square = right_end.translated(end_dx, end_dy);
let left = match extend_square_cap_trace(
left.into_segments(),
left_start_square.clone(),
left_end_square.clone(),
)? {
Classification::Decided(left) => left,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
let right = match extend_square_cap_trace(
right.into_segments(),
right_start_square.clone(),
right_end_square.clone(),
)? {
Classification::Decided(right) => right,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
let mut segments = Vec::with_capacity(left.len() + right.len() + 2);
segments.extend(left);
match cap_line(&left_end_square, &right_end_square)? {
Classification::Decided(cap) => segments.push(Segment2::Line(cap)),
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
}
segments.extend(reversed_segments(right));
match cap_line(&right_start_square, &left_start_square)? {
Classification::Decided(cap) => segments.push(Segment2::Line(cap)),
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
}
checked_outline_contour(segments, policy)
}
}
impl Contour2 {
pub fn offset_left_with_line_joins(
&self,
distance: Real,
policy: &CurvePolicy,
) -> CurveResult<Classification<Self>> {
if is_zero(&distance, policy) == Some(true) {
return Ok(Classification::Decided(self.clone()));
}
let offsets = match offset_segments_left(self.segments(), &distance, policy)? {
Classification::Decided(offsets) => offsets,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
let joined = match joined_offset_segments(self.segments(), &offsets, true, policy)? {
Classification::Decided(joined) => joined,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
Ok(Classification::Decided(Contour2::new_unchecked(
CurveString2::new_unchecked(joined),
self.fill_rule(),
)))
}
pub fn offset_left_checked(
&self,
distance: Real,
policy: &CurvePolicy,
) -> CurveResult<Classification<Self>> {
let offset = match self.offset_left_with_line_joins(distance, policy)? {
Classification::Decided(offset) => offset,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
match offset.has_self_contacts(policy)? {
Classification::Decided(false) => Ok(Classification::Decided(offset)),
Classification::Decided(true) => {
Ok(Classification::Uncertain(UncertaintyReason::Unsupported))
}
Classification::Uncertain(reason) => Ok(Classification::Uncertain(reason)),
}
}
}
fn scale_from_center(point: &Point2, center: &Point2, scale: &Real) -> Point2 {
let radius = point.delta_from(center);
Point2::new(
center.x() + (&radius.0 * scale),
center.y() + (&radius.1 * scale),
)
}
struct OutlineOffsets {
left: CurveString2,
right: CurveString2,
start_center: Point2,
end_center: Point2,
left_start: Point2,
left_end: Point2,
right_start: Point2,
right_end: Point2,
}
fn checked_outline_offsets(
source: &CurveString2,
distance: Real,
policy: &CurvePolicy,
) -> CurveResult<Classification<OutlineOffsets>> {
match real_sign(&distance, policy) {
Some(RealSign::Positive) => {}
Some(RealSign::Zero | RealSign::Negative) => {
return Ok(Classification::Uncertain(UncertaintyReason::Unsupported));
}
None => return Ok(Classification::Uncertain(UncertaintyReason::RealSign)),
}
match source.has_self_contacts(policy)? {
Classification::Decided(false) => {}
Classification::Decided(true) => {
return Ok(Classification::Uncertain(UncertaintyReason::Unsupported));
}
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
}
let left = match source.offset_left_with_line_joins(distance.clone(), policy)? {
Classification::Decided(left) => left,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
let right = match source.offset_left_with_line_joins(-distance, policy)? {
Classification::Decided(right) => right,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
Ok(Classification::Decided(OutlineOffsets {
start_center: source.start().ok_or(CurveError::EmptyCurveString)?.clone(),
end_center: source.end().ok_or(CurveError::EmptyCurveString)?.clone(),
left_start: left.start().ok_or(CurveError::EmptyCurveString)?.clone(),
left_end: left.end().ok_or(CurveError::EmptyCurveString)?.clone(),
right_start: right.start().ok_or(CurveError::EmptyCurveString)?.clone(),
right_end: right.end().ok_or(CurveError::EmptyCurveString)?.clone(),
left,
right,
}))
}
fn checked_outline_contour(
segments: Vec<Segment2>,
policy: &CurvePolicy,
) -> CurveResult<Classification<Contour2>> {
let outline = match Contour2::try_new(segments) {
Ok(outline) => outline,
Err(CurveError::DisconnectedCurveString) => {
return Ok(Classification::Uncertain(UncertaintyReason::Unsupported));
}
Err(CurveError::AmbiguousCurveStringConnection) => {
return Ok(Classification::Uncertain(UncertaintyReason::RealSign));
}
Err(error) => return Err(error),
};
match outline.has_self_contacts(policy)? {
Classification::Decided(false) => Ok(Classification::Decided(outline)),
Classification::Decided(true) => {
Ok(Classification::Uncertain(UncertaintyReason::Unsupported))
}
Classification::Uncertain(reason) => Ok(Classification::Uncertain(reason)),
}
}
fn extend_square_cap_trace(
mut segments: Vec<Segment2>,
extended_start: Point2,
extended_end: Point2,
) -> CurveResult<Classification<Vec<Segment2>>> {
if segments.is_empty() {
return Err(CurveError::EmptyCurveString);
}
let original_start = segments[0].start().clone();
match &segments[0] {
Segment2::Line(line) => {
segments[0] = match cap_line(&extended_start, line.end())? {
Classification::Decided(line) => Segment2::Line(line),
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
}
Segment2::Arc(_) => match cap_line(&extended_start, &original_start)? {
Classification::Decided(line) => segments.insert(0, Segment2::Line(line)),
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
},
}
let last_index = segments.len() - 1;
let original_end = segments[last_index].end().clone();
match &segments[last_index] {
Segment2::Line(line) => {
segments[last_index] = match cap_line(line.start(), &extended_end)? {
Classification::Decided(line) => Segment2::Line(line),
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
}
Segment2::Arc(_) => match cap_line(&original_end, &extended_end)? {
Classification::Decided(line) => segments.push(Segment2::Line(line)),
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
},
}
Ok(Classification::Decided(segments))
}
fn unit_tangent_at_segment_start(segment: &Segment2) -> CurveResult<(Real, Real)> {
match segment {
Segment2::Line(line) => unit_tangent_for_line(line),
Segment2::Arc(arc) => unit_tangent_for_arc_at_point(arc, arc.start()),
}
}
fn unit_tangent_at_segment_end(segment: &Segment2) -> CurveResult<(Real, Real)> {
match segment {
Segment2::Line(line) => unit_tangent_for_line(line),
Segment2::Arc(arc) => unit_tangent_for_arc_at_point(arc, arc.end()),
}
}
fn unit_tangent_for_line(line: &LineSeg2) -> CurveResult<(Real, Real)> {
let length = line.length_squared().sqrt()?;
let (dx, dy) = line.delta();
Ok(((dx / &length)?, (dy / &length)?))
}
fn unit_tangent_for_arc_at_point(arc: &CircularArc2, point: &Point2) -> CurveResult<(Real, Real)> {
let radius = arc.radius_squared().sqrt()?;
let (rx, ry) = point.delta_from(arc.center());
if arc.is_clockwise() {
Ok(((ry / &radius)?, ((-rx) / &radius)?))
} else {
Ok(((-ry / &radius)?, (rx / &radius)?))
}
}
fn offset_segments_left(
segments: &[Segment2],
distance: &Real,
policy: &CurvePolicy,
) -> CurveResult<Classification<Vec<Segment2>>> {
let mut offsets = Vec::with_capacity(segments.len());
for segment in segments {
match segment.offset_left(distance.clone(), policy)? {
Classification::Decided(offset) => offsets.push(offset),
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
}
}
Ok(Classification::Decided(offsets))
}
#[derive(Clone, Debug, PartialEq)]
enum OffsetJoin {
Miter(Point2),
Round { center: Point2 },
}
fn joined_offset_segments(
source: &[Segment2],
offsets: &[Segment2],
closed: bool,
policy: &CurvePolicy,
) -> CurveResult<Classification<Vec<Segment2>>> {
if offsets.is_empty() {
return Err(CurveError::EmptyCurveString);
}
if source.len() != offsets.len() {
return Err(CurveError::Topology(
"source and offset segment counts differ".into(),
));
}
let join_count = if closed {
offsets.len()
} else {
offsets.len().saturating_sub(1)
};
let mut joins = Vec::with_capacity(join_count);
for index in 0..join_count {
let next_index = (index + 1) % offsets.len();
match classify_offset_join(
&source[index],
&source[next_index],
&offsets[index],
&offsets[next_index],
policy,
)? {
Classification::Decided(join) => joins.push(join),
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
}
}
let mut joined = Vec::with_capacity(offsets.len() + join_count);
for index in 0..offsets.len() {
let start_override = start_miter_for_segment(index, offsets.len(), closed, &joins);
let end_override = end_miter_for_segment(index, &joins);
let adjusted = match adjust_offset_segment(
&offsets[index],
start_override.as_ref(),
end_override.as_ref(),
)? {
Classification::Decided(segment) => segment,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
joined.push(adjusted);
if let Some(OffsetJoin::Round { center }) = joins.get(index) {
let from = joined
.last()
.expect("current adjusted offset segment was just pushed")
.end()
.clone();
let to = offsets[(index + 1) % offsets.len()].start().clone();
match append_round_join_if_needed(&mut joined, &from, &to, center, policy)? {
Classification::Decided(()) => {}
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
}
}
}
Ok(Classification::Decided(joined))
}
fn classify_offset_join(
source_previous: &Segment2,
source_next: &Segment2,
offset_previous: &Segment2,
offset_next: &Segment2,
policy: &CurvePolicy,
) -> CurveResult<Classification<OffsetJoin>> {
match (offset_previous, offset_next) {
(Segment2::Line(previous), Segment2::Line(next)) => {
match line_support_intersection(previous, next, policy)? {
Classification::Decided(Some(point)) => {
Ok(Classification::Decided(OffsetJoin::Miter(point)))
}
Classification::Decided(None) => Ok(Classification::Decided(round_join(
source_previous,
source_next,
))),
Classification::Uncertain(reason) => Ok(Classification::Uncertain(reason)),
}
}
_ => Ok(Classification::Decided(round_join(
source_previous,
source_next,
))),
}
}
fn round_join(previous: &Segment2, next: &Segment2) -> OffsetJoin {
let _ = next;
OffsetJoin::Round {
center: previous.end().clone(),
}
}
fn line_support_intersection(
previous: &LineSeg2,
next: &LineSeg2,
policy: &CurvePolicy,
) -> CurveResult<Classification<Option<Point2>>> {
let (rx, ry) = previous.delta();
let (sx, sy) = next.delta();
let denominator = cross(&rx, &ry, &sx, &sy);
match real_sign(&denominator, policy) {
Some(RealSign::Zero) => Ok(Classification::Decided(None)),
Some(RealSign::Positive | RealSign::Negative) => {
let qmp = next.start().delta_from(previous.start());
let numerator = cross(&qmp.0, &qmp.1, &sx, &sy);
let t = (numerator / &denominator)?;
Ok(Classification::Decided(Some(previous.point_at(t))))
}
None => Ok(Classification::Uncertain(UncertaintyReason::RealSign)),
}
}
fn start_miter_for_segment(
index: usize,
segment_count: usize,
closed: bool,
joins: &[OffsetJoin],
) -> Option<Point2> {
if !closed && index == 0 {
return None;
}
let join_index = if index == 0 {
segment_count - 1
} else {
index - 1
};
match joins.get(join_index) {
Some(OffsetJoin::Miter(point)) => Some(point.clone()),
_ => None,
}
}
fn end_miter_for_segment(index: usize, joins: &[OffsetJoin]) -> Option<Point2> {
match joins.get(index) {
Some(OffsetJoin::Miter(point)) => Some(point.clone()),
_ => None,
}
}
fn adjust_offset_segment(
segment: &Segment2,
start_override: Option<&Point2>,
end_override: Option<&Point2>,
) -> CurveResult<Classification<Segment2>> {
match segment {
Segment2::Line(line) => {
let start = start_override
.cloned()
.unwrap_or_else(|| line.start().clone());
let end = end_override.cloned().unwrap_or_else(|| line.end().clone());
match LineSeg2::try_new(start, end) {
Ok(line) => Ok(Classification::Decided(Segment2::Line(line))),
Err(CurveError::ZeroLengthLine) => {
Ok(Classification::Uncertain(UncertaintyReason::Unsupported))
}
Err(error) => Err(error),
}
}
Segment2::Arc(_) if start_override.is_some() || end_override.is_some() => {
Ok(Classification::Uncertain(UncertaintyReason::Unsupported))
}
Segment2::Arc(arc) => Ok(Classification::Decided(Segment2::Arc(arc.clone()))),
}
}
fn append_round_join_if_needed(
joined: &mut Vec<Segment2>,
from: &Point2,
to: &Point2,
center: &Point2,
policy: &CurvePolicy,
) -> CurveResult<Classification<()>> {
let distance = from.distance_squared(to);
match is_zero(&distance, policy) {
Some(true) => Ok(Classification::Decided(())),
Some(false) => {
let clockwise = match round_join_clockwise(center, from, to, policy) {
Classification::Decided(clockwise) => clockwise,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
match round_join_arc(from, to, center, clockwise) {
Classification::Decided(arc) => {
joined.push(Segment2::Arc(arc));
Ok(Classification::Decided(()))
}
Classification::Uncertain(reason) => Ok(Classification::Uncertain(reason)),
}
}
None => Ok(Classification::Uncertain(UncertaintyReason::RealSign)),
}
}
fn round_cap_arc(
from: &Point2,
to: &Point2,
center: &Point2,
policy: &CurvePolicy,
) -> CurveResult<Classification<CircularArc2>> {
match is_zero(&from.distance_squared(to), policy) {
Some(true) => Ok(Classification::Uncertain(UncertaintyReason::Unsupported)),
Some(false) => {
let clockwise = match round_join_clockwise(center, from, to, policy) {
Classification::Decided(clockwise) => clockwise,
Classification::Uncertain(reason) => return Ok(Classification::Uncertain(reason)),
};
Ok(round_join_arc(from, to, center, clockwise))
}
None => Ok(Classification::Uncertain(UncertaintyReason::RealSign)),
}
}
fn round_join_arc(
from: &Point2,
to: &Point2,
center: &Point2,
clockwise: bool,
) -> Classification<CircularArc2> {
match CircularArc2::try_from_center(from.clone(), to.clone(), center.clone(), clockwise) {
Ok(arc) => Classification::Decided(arc),
Err(CurveError::ZeroRadiusArc | CurveError::RadiusMismatch) => {
Classification::Uncertain(UncertaintyReason::Unsupported)
}
Err(error) => {
unreachable!("round join arc construction returned unexpected error: {error}")
}
}
}
fn cap_line(from: &Point2, to: &Point2) -> CurveResult<Classification<LineSeg2>> {
match LineSeg2::try_new(from.clone(), to.clone()) {
Ok(line) => Ok(Classification::Decided(line)),
Err(CurveError::ZeroLengthLine) => {
Ok(Classification::Uncertain(UncertaintyReason::Unsupported))
}
Err(error) => Err(error),
}
}
fn reversed_segments(segments: Vec<Segment2>) -> impl Iterator<Item = Segment2> {
segments.into_iter().rev().map(|segment| segment.reversed())
}
fn round_join_clockwise(
center: &Point2,
from: &Point2,
to: &Point2,
policy: &CurvePolicy,
) -> Classification<bool> {
let from_radius = from.delta_from(center);
let to_radius = to.delta_from(center);
let turn = cross(&from_radius.0, &from_radius.1, &to_radius.0, &to_radius.1);
match real_sign(&turn, policy) {
Some(RealSign::Positive) => Classification::Decided(false),
Some(RealSign::Negative) => Classification::Decided(true),
Some(RealSign::Zero) => Classification::Decided(true),
None => Classification::Uncertain(UncertaintyReason::RealSign),
}
}
fn cross(ax: &Real, ay: &Real, bx: &Real, by: &Real) -> Real {
(ax * by) - (ay * bx)
}