1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
use fj_math::{Line, Point, Scalar, Vector};
use crate::{
objects::{
Curve, GlobalCurve, GlobalEdge, GlobalVertex, HalfEdge, Surface,
SurfaceVertex, Vertex,
},
path::SurfacePath,
};
use super::Sweep;
impl Sweep for (Vertex, Surface) {
type Swept = HalfEdge;
fn sweep(self, path: impl Into<Vector<3>>) -> Self::Swept {
let (vertex, surface) = self;
let path = path.into();
// The result of sweeping a `Vertex` is an `Edge`. Seems
// straight-forward at first, but there are some subtleties we need to
// understand:
//
// 1. To create an `Edge`, we need the `Curve` that defines it. A
// `Curve` is defined in a `Surface`, and we're going to need that to
// create the `Curve`. Which is why this `Sweep` implementation is
// for `(Vertex, Surface)`, and not just for `Vertex`.
// 2. Please note that, while the output `Edge` has two vertices, our
// input `Vertex` is not one of them! It can't be, unless the `Curve`
// of the output `Edge` happens to be the same `Curve` that the input
// `Vertex` is defined on. That would be an edge case that probably
// can't result in anything valid, and we're going to ignore it for
// now.
// 3. This means, we have to compute everything that defines the
// output `Edge`: The `Curve`, the vertices, and the `GlobalCurve`.
//
// Before we get to that though, let's make sure that whoever called
// this didn't give us bad input.
// So, we're supposed to create the `Edge` by sweeping a `Vertex` using
// `path`. Unless `path` is identical to the path that created the
// `Surface`, this doesn't make any sense.
//
// Further, the `Curve` that was swept to create the `Surface` needs to
// be the same `Curve` that the input `Vertex` is defined on. If it's
// not, we have no way of knowing the surface coordinates of the input
// `Vertex` on the `Surface`, and we're going to need to do that further
// down.
//
// Let's make sure that these requirements are met.
{
assert_eq!(vertex.curve().global_form().path(), surface.u());
assert_eq!(path, surface.v());
}
// With that out of the way, let's start by creating the `GlobalEdge`,
// as that is the most straight-forward part of this operations, and
// we're going to need it soon anyway.
let edge_global = vertex.global_form().sweep(path);
// Next, let's compute the surface coordinates of the two vertices of
// the output `Edge`, as we're going to need these for the rest of this
// operation.
//
// They both share a u-coordinate, which is the t-coordinate of our
// input `Vertex`. Remember, we validated above, that the `Curve` of the
// `Surface` and the curve of the input `Vertex` are the same, so we can
// do that.
//
// Now remember what we also validated above: That `path`, which we're
// using to create the output `Edge`, also created the `Surface`, and
// thereby defined its coordinate system. That makes the v-coordinates
// straight-forward: The start of the edge is at zero, the end is at
// one.
let points_surface = [
Point::from([vertex.position().t, Scalar::ZERO]),
Point::from([vertex.position().t, Scalar::ONE]),
];
// Armed with those coordinates, creating the `Curve` of the output
// `Edge` is straight-forward.
let curve = {
let line = Line::from_points(points_surface);
Curve::new(surface, SurfacePath::Line(line), *edge_global.curve())
};
// And now the vertices. Again, nothing wild here.
let vertices = {
let vertices_global = edge_global.vertices();
// Can be cleaned up, once `zip` is stable:
// https://doc.rust-lang.org/std/primitive.array.html#method.zip
let [a_surface, b_surface] = points_surface;
let [a_global, b_global] = vertices_global;
let vertices_surface =
[(a_surface, a_global), (b_surface, b_global)].map(
|(point_surface, &vertex_global)| {
SurfaceVertex::new(
point_surface,
surface,
vertex_global,
)
},
);
// Can be cleaned up, once `zip` is stable:
// https://doc.rust-lang.org/std/primitive.array.html#method.zip
let [a_surface, b_surface] = vertices_surface;
let [a_global, b_global] = vertices_global;
let vertices = [(a_surface, a_global), (b_surface, b_global)];
vertices.map(|(vertex_surface, &vertex_global)| {
Vertex::new(
[vertex_surface.position().v],
curve.clone(),
vertex_surface,
vertex_global,
)
})
};
// And finally, creating the output `Edge` is just a matter of
// assembling the pieces we've already created.
HalfEdge::new(curve, vertices, edge_global)
}
}
impl Sweep for GlobalVertex {
type Swept = GlobalEdge;
fn sweep(self, path: impl Into<Vector<3>>) -> Self::Swept {
let a = self;
let b = GlobalVertex::from_position(self.position() + path.into());
let curve =
GlobalCurve::build().line_from_points([a.position(), b.position()]);
GlobalEdge::new(curve, [a, b])
}
}
#[cfg(test)]
mod tests {
use crate::{
algorithms::sweep::Sweep,
objects::{
Curve, GlobalCurve, GlobalEdge, GlobalVertex, HalfEdge, Surface,
Vertex,
},
};
#[test]
fn vertex_surface() {
let surface = Surface::xz_plane();
let curve = Curve::build(surface).u_axis();
let vertex = Vertex::build(curve).from_point([0.]);
let half_edge = (vertex, surface).sweep([0., 0., 1.]);
let expected_half_edge = HalfEdge::build(surface)
.line_segment_from_points([[0., 0.], [0., 1.]]);
assert_eq!(half_edge, expected_half_edge);
}
#[test]
fn global_vertex() {
let edge =
GlobalVertex::from_position([0., 0., 0.]).sweep([0., 0., 1.]);
let expected_edge = GlobalEdge::new(
GlobalCurve::build().z_axis(),
[[0., 0., 0.], [0., 0., 1.]].map(GlobalVertex::from_position),
);
assert_eq!(edge, expected_edge);
}
}