mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-04 19:15:57 +00:00
The truck crates leave the workspace; the rig and threads run on SMLib alone
truck-modeling, -topology, -shapeops, -stepio and the vendored truck-geometry, -meshalgo, -shapeops, -stepio and matext4cgmath (with their attribution rows and vendor notes) are removed. The kernel-cases runner has one kernel, SMLib, and no build-mismatch check; a modelled thread is cut by SMLib's exact screw-swept groove (the faceted groove path is gone). (not built: base in progress) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017itGh5fpMEousnZ91cYcVt
This commit is contained in:
@@ -607,11 +607,6 @@ copied into SolidCraft.
|
||||
|
||||
| Directory | Source | Licence | Changes |
|
||||
|---|---|---|---|
|
||||
| vendor/truck-meshalgo | truck-meshalgo 0.4.0, https://github.com/ricosjp/truck (RICOS Co. Ltd.) | Apache-2.0 | meshing: bounded parameter searches, collapsed-boundary projection, subdivision and provisional fixed-grid previews (vendor/README.md) |
|
||||
| vendor/truck-stepio | truck-stepio 0.3.0, https://github.com/ricosjp/truck (RICOS Co. Ltd.) | Apache-2.0 | STEP writer: intersection-curve surfaces written at unique ids (vendor/README.md) |
|
||||
| vendor/truck-shapeops | truck-shapeops 0.4.0, https://github.com/ricosjp/truck (RICOS Co. Ltd.) | Apache-2.0 | robust meshing and nearest-parameter fallback in booleans; degenerate intersection-curve rejection; face and edge history of booleans; trace macro; corner choice at vertices several intersection curves end on; reuse of an operand's meshes (vendor/README.md) |
|
||||
| vendor/truck-geometry | truck-geometry 0.5.0, https://github.com/ricosjp/truck (RICOS Co. Ltd.) | Apache-2.0 | intersection curves divided from their neighbours' surface parameters; surface second derivatives (vendor/README.md) |
|
||||
| vendor/matext4cgmath | matext4cgmath 0.1.0, https://github.com/ricosjp/matext4cgmath (RICOS Co. Ltd.) | Apache-2.0 | root refinement stops after 100 Newton steps (vendor/README.md) |
|
||||
|
||||
## Vendored renderer compatibility fix
|
||||
|
||||
|
||||
+4
-17
@@ -47,13 +47,7 @@ sha2 = "0.10"
|
||||
moxcms = { version = "0.8.1", default-features = false, features = ["extended_range"] }
|
||||
semver = "1"
|
||||
|
||||
# geometry kernel (Apache-2.0; see plan/adr/0001-geometry-kernel.md). Only crates/kernel uses it.
|
||||
truck-modeling = "0.6"
|
||||
truck-shapeops = { version = "0.4", default-features = false }
|
||||
truck-stepio = { version = "0.3", default-features = false }
|
||||
truck-topology = "0.6"
|
||||
# constrained Delaunay + Ruppert refinement for trimmed faces (MIT OR Apache-2.0); already in the
|
||||
# tree through truck-meshalgo.
|
||||
# constrained Delaunay + Ruppert refinement for trimmed faces (MIT OR Apache-2.0).
|
||||
spade = { version = "2.15", default-features = false, features = ["std"] }
|
||||
rayon = "1.12"
|
||||
|
||||
@@ -126,9 +120,7 @@ opt-level = 2
|
||||
opt-level = 1
|
||||
|
||||
# Tests and lints (`cargo xtask ci`, GitHub CI): quick to build, quick enough to run. Light
|
||||
# optimisation, line tables only (file:line in backtraces), no LTO. Debug assertions are off because
|
||||
# truck's debug-only checks reject valid geometry (seam edges, closed edges); that was the only
|
||||
# reason the tests used to build with --release, which cost most of CI's time.
|
||||
# optimisation, line tables only (file:line in backtraces), no LTO, no debug assertions.
|
||||
[profile.ci]
|
||||
inherits = "dev"
|
||||
opt-level = 1
|
||||
@@ -151,14 +143,9 @@ codegen-units = 16
|
||||
# binary). See docs/crash-reports.md.
|
||||
debug = "line-tables-only"
|
||||
|
||||
# truck-shapeops with SolidCraft fixes (Apache-2.0, see vendor/truck-shapeops and ATTRIBUTION.md).
|
||||
[patch.crates-io]
|
||||
truck-geometry = { path = "vendor/truck-geometry" }
|
||||
truck-shapeops = { path = "vendor/truck-shapeops" }
|
||||
truck-stepio = { path = "vendor/truck-stepio" }
|
||||
truck-meshalgo = { path = "vendor/truck-meshalgo" }
|
||||
matext4cgmath = { path = "vendor/matext4cgmath" }
|
||||
|
||||
# Vendored crates with SolidCraft fixes (see vendor/README.md and ATTRIBUTION.md).
|
||||
[patch.crates-io]
|
||||
# Keep OpenGL pipelines, resolve textures and CAD callbacks at the same safe sample count.
|
||||
egui-wgpu = { path = "vendor/egui-wgpu" }
|
||||
|
||||
|
||||
@@ -9,18 +9,9 @@ use serde_json::{Value, json};
|
||||
use crate::oracle::{ABS_FRAC, Checks, REL_TOL, totals, v3};
|
||||
use crate::{read_json, recipe};
|
||||
|
||||
/// The kernel this process runs on (`SOLIDCRAFT_KERNEL`, set by the caller).
|
||||
/// The kernel the cases run on: SMLib, the only one.
|
||||
fn kernel() -> String {
|
||||
kernel_name(std::env::var("SOLIDCRAFT_KERNEL").ok().as_deref())
|
||||
}
|
||||
|
||||
/// The rig's name for a `SOLIDCRAFT_KERNEL` setting: unset, empty or `truck` (all of which the
|
||||
/// kernel runs as truck) is `default`, the name `boolean_kernel` reports for truck.
|
||||
fn kernel_name(setting: Option<&str>) -> String {
|
||||
match setting.map(str::trim) {
|
||||
None | Some("") | Some("truck") => "default".into(),
|
||||
Some(other) => other.into(),
|
||||
}
|
||||
"smlib".into()
|
||||
}
|
||||
|
||||
/// How many cases a chain of `base` recipes may go through (a ladder case builds on the one
|
||||
@@ -165,17 +156,11 @@ fn check_case(dir: &str) -> Value {
|
||||
return out;
|
||||
}
|
||||
};
|
||||
if kernel() == "smlib" && !solidcraft_engine::kernel::smlib_built() {
|
||||
if !solidcraft_engine::kernel::smlib_built() {
|
||||
out["status"] = json!("unsupported");
|
||||
out["error"] = json!("this build has no SMLib: build solidcraft-cli with --features smlib");
|
||||
return out;
|
||||
}
|
||||
let running = solidcraft_engine::kernel::boolean_kernel();
|
||||
if kernel() != running {
|
||||
out["status"] = json!("unsupported");
|
||||
out["error"] = json!(format!("this build runs booleans on the {running} kernel, not {}", kernel()));
|
||||
return out;
|
||||
}
|
||||
// `refused`: only a clean refusal is right; `solid_or_refused`: a clean refusal, or a result
|
||||
// graded as usual (where the geometry has no manifold answer, such as bodies touching along a
|
||||
// curve only).
|
||||
@@ -380,15 +365,6 @@ mod tests {
|
||||
let _ = std::fs::remove_dir_all(&root);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn truck_named_explicitly_is_the_default_kernel() {
|
||||
// `SOLIDCRAFT_KERNEL=truck` runs truck, so the rig must not call it a build mismatch.
|
||||
for setting in [None, Some(""), Some(" "), Some("truck")] {
|
||||
assert_eq!(kernel_name(setting), "default");
|
||||
}
|
||||
assert_eq!(kernel_name(Some("smlib")), "smlib");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_base_cycle_is_an_error_not_a_hang() {
|
||||
let root = std::env::temp_dir().join(format!("kernel-cases-cycle-{}", std::process::id()));
|
||||
|
||||
+5
-16
@@ -7275,22 +7275,11 @@ fn eval_feature(doc: &Document, vals: &BTreeMap<String, Value>, f: &Feature, st:
|
||||
None => (input, cyl.radius),
|
||||
};
|
||||
let open = ((t.start - cyl.start).abs() < tol, (t.end - cyl.end).abs() < tol);
|
||||
let (nb, groove_faces) = match kernel::backend()? {
|
||||
kernel::Backend::Truck => {
|
||||
let groove = thread_groove(&t, radius, open)?;
|
||||
// The B-rep boolean can't take helical walls in reasonable time: the body is cut
|
||||
// faceted (curved faces within a two-hundredth of the pitch).
|
||||
let nb = kernel::faceted_boolean(&input.body, &groove, BoolOp::Cut, t.pitch * 0.005)?;
|
||||
(nb, groove.triangles.len())
|
||||
}
|
||||
kernel::Backend::Smlib => {
|
||||
// SMLib cuts the exact groove: its screw sweep, a B-rep boolean.
|
||||
let g = thread_groove_shape(&t, radius, open)?;
|
||||
let section = Region2 { outer: solidcraft_geom::Loop2::polygon(&g.section), holes: Vec::new() };
|
||||
let groove = kernel::sweep_helix(&g.plane, §ion, g.base, g.axis, g.pitch, g.turns)?;
|
||||
(kernel::boolean(&input.body, &groove, BoolOp::Cut)?, groove.face_count())
|
||||
}
|
||||
};
|
||||
// The exact groove: SMLib's screw sweep, cut by a B-rep boolean.
|
||||
let g = thread_groove_shape(&t, radius, open)?;
|
||||
let section = Region2 { outer: solidcraft_geom::Loop2::polygon(&g.section), holes: Vec::new() };
|
||||
let groove = kernel::sweep_helix(&g.plane, §ion, g.base, g.axis, g.pitch, g.turns)?;
|
||||
let (nb, groove_faces) = (kernel::boolean(&input.body, &groove, BoolOp::Cut)?, groove.face_count());
|
||||
let nb = nb.ok_or_else(|| DocError::Invalid("the thread removed everything".into()))?;
|
||||
// The groove's faces have no role to name them by.
|
||||
let groove = crate::lineage::Lineage::unnamed_faces(groove_faces);
|
||||
|
||||
Vendored
-266
@@ -4,272 +4,6 @@ Copies of third-party crates that SolidCraft builds with local fixes, wired in w
|
||||
`[patch.crates-io]` in the workspace `Cargo.toml`. They keep their own licence; changed lines
|
||||
are marked `SolidCraft:` in the source.
|
||||
|
||||
## truck-shapeops 0.4.0 (Apache-2.0, © RICOS Co. Ltd., https://github.com/ricosjp/truck)
|
||||
|
||||
Changes:
|
||||
- `transversal/intersection_curve/mod.rs`: reject empty/single-point polylines and
|
||||
collapsed or non-finite segments before endpoint indexing and normalization. A hidden
|
||||
constructor re-export lets the kernel workspace tests exercise the actual vendored code.
|
||||
- `transversal/integrate/mod.rs`: mesh both shells with `robust_triangulation` (faces made by
|
||||
earlier booleans have boundaries that lie on their surface only within tolerance).
|
||||
- `transversal/divide_face/mod.rs`: when projecting a loop onto a face's surface, fall back to
|
||||
the nearest parameter when the exact search fails (intersection curves are approximations).
|
||||
- `transversal/integrate/mod.rs`: the result's shells and faces keep the order of the shell
|
||||
they come from (truck's connected components followed hash order of face addresses, so a
|
||||
boolean's faces came out in a different order on every run).
|
||||
- `transversal/loops_store/mod.rs`: a point at an end of a boundary curve (within the point
|
||||
tolerance) is that end, whatever its parameter: a polyline's parameter counts segments, so
|
||||
an intersection curve ending 2e-7 mm from another's end could read as inside it, cut it
|
||||
into a sliver with a vertex of its own, and leave the result's shell open (#391).
|
||||
- `transversal/loops_store/mod.rs`: an intersection edge added to a face at a vertex that more
|
||||
than one wire visits (several curves end on it: the touching point of two equal cylinders) goes
|
||||
into the corner that holds its direction, swept counterclockwise about the face's normal from
|
||||
the wire's outgoing edge to the way back along its incoming one (`add_edge_by_angle`, told on
|
||||
the polygon loops and followed by the geometry loops). Taking the first visit of each end
|
||||
joined wires that are different regions of a face and left a wire visiting a vertex twice.
|
||||
With no direction to go by, the edge goes where both of its ends are in one wire.
|
||||
- `transversal/divide_face/mod.rs`: a face piece whose wire is not simple makes the operation
|
||||
give up (`Face::try_new`) instead of panicking.
|
||||
- `transversal/divide_face/mod.rs`: a loop whose polygon in the face's parameters has no area
|
||||
(a small hole's rim divided at the tolerance of a much larger operand: one chord per half
|
||||
circle) is divided again, finer, before its area's sign tells a hole from a face piece. The
|
||||
sign of a zero area was random: the hole became a face of its own, inside out, and the face it
|
||||
was cut in stayed whole.
|
||||
- `lib.rs`: `tr!` reports where an operation gives up when `SHAPEOPS_TRACE` is set; compiler
|
||||
warnings no longer fail the build.
|
||||
- Face and edge history (`transversal/history.rs`, new): `and_with_history` / `or_with_history`
|
||||
also return, for each face of the result (in `face_iter` order), the operand face it is a
|
||||
region of and whether it is that face unchanged, and for each edge (distinct edges in order
|
||||
of first use along the faces) the operand edge it is (a piece of) or the two operand faces
|
||||
whose intersection made it. The code that creates faces and edges records them as it goes:
|
||||
`divide_face` (face pieces), `loops_store` (intersection edges, split edges, edges rebuilt on
|
||||
a moved vertex), across the passes that fold in the operands' extra shells. `and` / `or` are
|
||||
wrappers that drop the history; the `*_with_history` forms return the same solid with the
|
||||
history as an `Option` (`None` where a face or edge went unrecorded).
|
||||
- `transversal/integrate/mod.rs`: `meshes` and `and_with_history_meshed` take the first
|
||||
operand's shell meshes made beforehand (copied for each use, since the loops stores cut their
|
||||
edges): a large body cut down to the regions of many small ones is meshed once, which was most
|
||||
of each operation's time.
|
||||
- `transversal/integrate/mod.rs`: in an AND, a face no intersection divides (a shell that meets
|
||||
nothing of the other) is placed by the crossings of a ray with the other shell counted as each
|
||||
face is oriented, not by its surface alone, and an inward shell (a void, a cut's inverted tool)
|
||||
holds what lies outside it. Faces with reversed surfaces (a revolved body's) counted the wrong
|
||||
way, and a void dropped every face outside it: a cut beside a sealed void came out empty or
|
||||
with the body gone. An OR keeps truck's count.
|
||||
|
||||
## truck-geometry 0.5.0 (Apache-2.0, © RICOS Co. Ltd., https://github.com/ricosjp/truck)
|
||||
|
||||
Changes:
|
||||
- `decorators/intersection_curve.rs`: `IntersectionCurve::parameter_division` divides as
|
||||
`algo::curve::parameter_division` does (the same split points, in the same order), but each
|
||||
point's projection onto the two surfaces starts from the parameters of the point that begins
|
||||
its piece, instead of a search of the whole surfaces (a grid pre-search of a B-spline surface
|
||||
for every point: a small hole on a large curved face took seconds, #1038). A hinted result
|
||||
farther from the leader's point than the curve's tolerance (the projection settled on another
|
||||
part of a surface, such as the far side of a small hole) is dropped for the search from
|
||||
nothing, as before. `crates/kernel/src/issue_1038_tests.rs` holds it to that search.
|
||||
- `decorators/revolved_curve.rs`: a point on a revolved surface's seam (angle 0, the same place
|
||||
as 2π) takes the end of the period nearer the hinted angle. The angle was decided by the sign of
|
||||
a cross product that is rounding noise there, so a boundary followed point by point could come
|
||||
back to the seam at the other end of the period and fold its parameter polygon over itself (no
|
||||
intersection loop then lay inside the face). `crates/kernel/src/seam_faces_tests.rs` holds it
|
||||
to a cone read from STEP.
|
||||
- `nurbs/bspsurface.rs`: `uuder` and `vvder` multiply by p·(p − 1), as `BSplineCurve::der2`
|
||||
does. They multiplied by p only, so the second derivatives of every B-spline and NURBS surface
|
||||
of degree p ≥ 3 were 1/(p − 1) of their value (a cubic surface's curvature read as half, #763).
|
||||
`crates/kernel/src/surface_second_derivative_tests.rs` holds them to finite differences.
|
||||
- `decorators/intersection_curve.rs`: `IntersectionCurve::parameter_division` with a tolerance
|
||||
below truck's `TOLERANCE` (1e-6), or one that is not a number, divides at `TOLERANCE` instead
|
||||
of panicking in truck's tolerance assertion; a join failed on that panic (#4623).
|
||||
`crates/kernel/src/issue_1038_tests.rs` holds it to the division at 1e-6.
|
||||
- `decorators/intersection_curve.rs`: where the hinted projection behind `parameter_division`
|
||||
and the search from nothing both fail, the leader's point stands in, the division stops, and
|
||||
the failure is recorded for `take_projection_failure`. It fell back to `subs`, which unwraps
|
||||
that same failed search: a revolve cut whose sphere's pole sat on a box's edge panicked
|
||||
dividing an edge (#5562).
|
||||
- truck-shapeops `transversal/divide_face/mod.rs`: an edge whose division recorded such a
|
||||
failure gives up the face division (`None`), so the Boolean attempt fails as quickly as the
|
||||
panic made it, and the kernel goes on to its other tries. Carrying the approximate points on
|
||||
made one test's Boolean run for minutes.
|
||||
|
||||
## truck-stepio 0.3.0 (Apache-2.0, © RICOS Co. Ltd., https://github.com/ricosjp/truck)
|
||||
|
||||
Only the writer (`out`) is used; SolidCraft reads STEP with its own reader.
|
||||
|
||||
Changes:
|
||||
- `out/geometry.rs`: an `INTERSECTION_CURVE`'s second surface was written at the first
|
||||
surface's entity index, so every boolean-made edge produced duplicate entity ids.
|
||||
- `out/geometry.rs`: surfaces of revolution carry the right `same_sense` for inverted and
|
||||
mirrored revolutions (ISO 10303-42 parameterises them by angle then profile, so their normal
|
||||
is the opposite of truck's; a mirroring transform reverses it again), and the modeling-surface
|
||||
wrapper passes the revolution's sense through instead of always `true`.
|
||||
- `out/geometry.rs`: a surface of revolution under a transform that commutes with turning about
|
||||
its axis (a scale across the axis that differs from the scale along it, as a shell or offset
|
||||
makes of a revolved face) is written as the transformed profile revolved about the
|
||||
transformed axis. Only uniform scales were taken. Other transforms are still refused: the
|
||||
writer stops with a formatting error, which SolidCraft's `step.rs` now reports as an export
|
||||
error (`to_string` panicked on it).
|
||||
- `lib.rs`: compiler warnings no longer fail the build. Example and test targets are dropped
|
||||
(their sources are not vendored).
|
||||
|
||||
## truck-meshalgo 0.4.0 (Apache-2.0, © RICOS Co. Ltd., https://github.com/ricosjp/truck)
|
||||
|
||||
Changes:
|
||||
- `tessellation/triangulation.rs`: a boundary segment that crosses constraints already added
|
||||
is split where it crosses by `add_split_constraint` (a vertex at each crossing, the pieces
|
||||
added where they can be), not by spade's `add_constraint_and_split`, which asserts when a
|
||||
split lands so near another vertex or constraint that the piece up to it is blocked. That
|
||||
assertion lost whole bodies and operations (#4031, #4598); a blocked piece is now left out, as
|
||||
truck did before #1653.
|
||||
- `tessellation/triangulation.rs`, `closed.rs`: retry nearest trim projections outside a
|
||||
spline's knot domain without the preceding point's hint, preferring a bounded fit within
|
||||
the caller's chord tolerance, with a bounded damped least-squares fallback where
|
||||
Newton's method diverges. Affine plane charts remain unbounded; modelling surfaces may
|
||||
retain genuine continuation when no bounded fit is accurate enough. Geometrically closed,
|
||||
smooth spline seams get a local periodic chart during meshing, with wrapped evaluation,
|
||||
so a trim crossing the seam stays a small patch. The stored geometry is unchanged (#1974).
|
||||
- `tessellation/mod.rs`, `triangulation.rs`: a separate provisional `preview_face`
|
||||
path accepts at most 4,096 already sampled boundary points and a fixed grid of
|
||||
at most 16 subdivisions per direction, including periodic seam connections.
|
||||
It does not run adaptive subdivision or grid balancing. The kernel caller bounds
|
||||
projection complexity and total allocation before using it; ordinary modelling
|
||||
tessellation keeps its existing accuracy policy.
|
||||
- `tessellation/triangulation.rs`: exact, robust and provisional-preview projections prefer an
|
||||
equivalent in-domain parameter at collapsed surface edges, and snap to exact bounded
|
||||
endpoints only at collapsed edges when that improves the spatial fit within the existing
|
||||
search tolerance. Closed spline seams retain their distinct parameter endpoints.
|
||||
This prevents huge arbitrary pole parameters from corrupting trimmed meshes (#796),
|
||||
while retaining genuine extrapolation for unbounded planes. Work is bounded to four
|
||||
endpoint evaluations per projected point; no search or tessellation tolerance is relaxed.
|
||||
- `tessellation/triangulation.rs`: a boundary loop is projected onto its surface point by point,
|
||||
each searched from the one before; the first has no hint. When that walk fails, it is tried
|
||||
once more from the first point an unhinted search finds. An unhinted search can miss a point
|
||||
that lies on the surface, such as the corner of a fillet strip on a surface extended past it,
|
||||
and the face was then left unmeshed, which also failed every boolean with the body (#882). A
|
||||
walk that succeeds is kept as it was: where a loop starts decides how it is laid out across a
|
||||
periodic surface's seam.
|
||||
- `tessellation/mod.rs`, `triangulation.rs`: edges are sampled through a new `TrySubs` trait
|
||||
(`try_subs`, `approx_subs`), a bound of `PolylineableCurve`, instead of `subs`. truck's
|
||||
`IntersectionCurve::subs` unwraps a projection onto its two surfaces that can fail to
|
||||
converge, which panicked while meshing (#1252). A failed sample at an interval's end uses the
|
||||
intersection curve's leader point, so the polyline stays finite; a failed interior probe is
|
||||
NaN, which already counts as flat. `truck-shapeops`' `ShapeOpsCurve` carries the same bound,
|
||||
and `truck-modeling` is now a regular dependency (it was a dev-dependency).
|
||||
- `tessellation/triangulation.rs`: the inside test of a face's boundary skips loops whose
|
||||
parameter box is clear of the point (it tested every segment of every loop, so a face with
|
||||
hundreds of holes meshed in quadratic time).
|
||||
- `tessellation/triangulation.rs`: meshed faces and edges are built unchecked: faces whose loop
|
||||
runs along a seam edge both ways and closed edges (a full circle on one vertex) are valid,
|
||||
but the debug-build checks rejected them (debug builds could not mesh them).
|
||||
- `tessellation/triangulation.rs`: on a periodic surface, a boundary point's projected parameter
|
||||
is moved by whole periods to within half a period of the point before it, however many periods
|
||||
away the search put it (it was moved by two periods at most). A hinted search on a revolved
|
||||
surface can return an angle millions of turns from its hint; the loop then spanned those turns,
|
||||
the polyline closing it across the seam had millions of points, and meshing never finished,
|
||||
which hung the STEP export that reads its own file back (#1985).
|
||||
- `tessellation/triangulation.rs`: parameter searches that land outside a surface's bounded,
|
||||
non-periodic domain are rejected (a hinted B-spline search can run off and extrapolate to
|
||||
thousands of parameter units, and meshing that grid never finishes); the last-resort nearest
|
||||
search is clamped into the domain.
|
||||
- `tessellation/triangulation.rs`, bounded work (hostile files must not hang): edge polylines
|
||||
and surface grids come from our own bounded divisions (at most 20 000 points per edge, a
|
||||
grid budget per face shared out of 10 million points per shell, 24 refinement rounds; a
|
||||
point that does not evaluate counts as flat instead of recursing). Surface grids refine only
|
||||
the direction that bends, and a ruling left undivided gets cells comparable to the other
|
||||
direction (long triangles from a trim loop to the grid's far ends cut through cylinders).
|
||||
- `tessellation/triangulation.rs`: an edge's division evaluates each parameter once. A piece
|
||||
carries the points of its ends and its midpoint is an end of both halves: 2 evaluations per
|
||||
piece instead of 4, and the same polyline (a point of an intersection curve costs two grid
|
||||
searches of its surfaces). `bounded_polyline` and `Deflection::turn2`/`turns` are public but
|
||||
hidden, for the kernel's regression against the old division.
|
||||
- `tessellation/mod.rs`, `tessellation/triangulation.rs`, for display: a `Deflection` is the
|
||||
chord tolerance plus an optional bound on the angle one facet spans, and
|
||||
`RoundMeshableShape::robust_triangulation_within` divides by it: an edge or a grid direction
|
||||
within the chord tolerance is still halved while a piece turns more than the angle, until its
|
||||
sag is under a floor (a radius that is small beside the tolerance was one flat facet).
|
||||
Bounded work: a face that would be bounded by more than 4096 segments has its curved edges
|
||||
divided at twice the angle, and again, down to the chord tolerance alone, and the angle
|
||||
stops refining a face's grid at 16 384 points. Each edge is still divided once for both its
|
||||
faces. `triangulation` and `robust_triangulation` divide by the chord tolerance alone, as
|
||||
before.
|
||||
- `analyzers/collision.rs`: the collision search builds each triangle's box once, filters the
|
||||
sweep's open list by box overlap as it goes (it cloned the list for every triangle and built
|
||||
both boxes for every pair), and runs the exact triangle tests on the rayon pool, in order
|
||||
(sequential on wasm). Same collisions in the same order. The old search is kept as
|
||||
`analyzers::collision_test_hooks`, behind a `test-hooks` feature that only
|
||||
`solidcraft-kernel`'s tests turn on, and `crates/kernel/tests/mesh_collision_equivalence.rs`
|
||||
compares the two.
|
||||
- `tessellation/triangulation.rs`: the inside test of a face's boundary also skips runs of 32
|
||||
segments whose box, grown by the tolerance, the test's ray cannot meet. Same answers. The
|
||||
old test is kept as `tessellation::include_test_hooks`, behind a `test-hooks` feature that
|
||||
only `solidcraft-kernel`'s tests turn on, and `crates/kernel/tests/tess_include_equivalence.rs`
|
||||
compares the two.
|
||||
- `tessellation/triangulation.rs`: the constrained Delaunay triangulation of a trimmed face
|
||||
works in (u, v) scaled by the surface's mean speed along each (a power of two, so exact both
|
||||
ways). In raw parameters, a loft side ruled across (about 30 mm) and long along the loft
|
||||
(about 600 mm) is a strip stretched twentyfold, and a trimming node was fanned to boundary
|
||||
nodes hundreds of millimetres away: a flat sheet through a joined tube.
|
||||
- `tessellation/triangulation.rs`: every segment of a face's boundary becomes a constraint; one
|
||||
that crosses or overlaps a constraint already added (an intersection edge's polyline can hook
|
||||
back a few micrometres past its vertex) is split where they meet. truck dropped such a
|
||||
segment and joined its start to the first later node it could reach, which on a joined tube
|
||||
left a trimming curve's 130 nodes behind one constraint across the face: slivers from it
|
||||
sagged millimetres off the surface, and a face pinched to a point (#1648) got no triangles.
|
||||
All meshes; faces whose segments all go in mesh as before.
|
||||
- `tessellation/mod.rs`: `Deflection::rulings` is now `Deflection::display`: a mesh to display
|
||||
or export (the kernel's `tessellate` and display meshes), which gets the ruling grid lines,
|
||||
and the speed-scaled triangulation above. The meshes a Boolean operation intersects
|
||||
(`Deflection::chord`) keep truck's (u, v) and insertion order.
|
||||
- `lib.rs`: compiler warnings no longer fail the build. Example, test and bench targets are
|
||||
dropped (their sources are not vendored).
|
||||
- `tessellation/triangulation.rs`: adaptive division also splits a cell that bows off its
|
||||
corners' plane by more than the chord tolerance (a quarter of its twist `p00 - p01 - p10 +
|
||||
p11`, across that plane, at the middle). A bilinear patch equals the blend of its corners the
|
||||
division compared against, so a twisted ruled face (a loft between polylines) went out as two
|
||||
triangles per face (#1666); a flat cell such as a cone's trapezoid between rulings is not split.
|
||||
- `tessellation/triangulation.rs`: a shell's face meshes share their edges' points exactly. A
|
||||
face's mesh puts each point at its parameters on the face's own surface, so a point of an edge
|
||||
that lies off a surface within tolerance (an intersection-curve sample whose projection did not
|
||||
converge, an imported edge) came out in a different place on each of the edge's faces, and the
|
||||
exported mesh had a crack there. In display and export meshes (`Deflection::display`), a
|
||||
vertex at a point of a face's boundary loops that is farther than `TOLERANCE` from the nearest
|
||||
point of the face's edges now goes to that point, when it is within the chord tolerance
|
||||
(`onto_edges`). Points within `TOLERANCE` stay on the surface, so exact geometry meshes as
|
||||
before. The meshes Boolean operations intersect keep truck's points.
|
||||
- `tessellation/triangulation.rs`: in display and export meshes (`Deflection::display`), a
|
||||
direction left undivided along the rulings of a face ruled one way also gets a grid line
|
||||
through each point of the face's boundary that lies inside the face along it
|
||||
(`with_boundary_lines`), when there are at most 16 such points: corners standing into the
|
||||
face, not a boundary curve running through it, which the triangulation already follows. A hole in a cone or cylinder smaller than one cell (a notch into a
|
||||
chamfer) or a trim crossing the rulings of a narrow band (an oblique plane through a bore's
|
||||
chamfer) had no grid point beside it, and the triangulation fanned from it to the far ends of
|
||||
the face, many degrees round: those triangles cut through the surface and folded over each
|
||||
other (a cone face with a small hole meshed several mm² too large, and the boolean that made
|
||||
it was refused for its volume).
|
||||
- `tessellation/triangulation.rs`: a face that comes out of the triangulation with no triangle
|
||||
at all, though the points of its one boundary loop enclose an area in space, is given the
|
||||
triangles of that loop (ears clipped on the plane of the loop, no point added, so the faces on
|
||||
either side of its edges still meet it). Its two edges can be different curves that, sampled
|
||||
as they are, run along the same line in the face's parameters (a lens thinner than the
|
||||
tolerance), and the loop then has no area there. The shell's mesh had a hole. Faces that
|
||||
mesh are untouched, and so are preview meshes (they have their own step grid). Only a face
|
||||
with one loop of at most 256 points is filled, and not one whose loop crosses itself, whose
|
||||
ears would overlap (the pinched face of #1648 is one): it stays as it was.
|
||||
`crates/kernel/src/thin_face_tests.rs` holds it, `crates/kernel/tests/tess_loop_fill.rs` the
|
||||
ear clipping (through the `test-hooks` feature).
|
||||
|
||||
## matext4cgmath 0.1.0 (Apache-2.0, © RICOS Co. Ltd., https://github.com/ricosjp/matext4cgmath)
|
||||
|
||||
truck-geometry uses it for a matrix's operator norm (an intersection curve scales its tolerance
|
||||
by it whenever it is moved).
|
||||
|
||||
Changes:
|
||||
- `solver.rs`: the Newton refinement of cubic and quartic roots stops after 100 steps. Near a
|
||||
double root it could cycle forever: moving a body whose edges a boolean made (the retries
|
||||
move both bodies and the result back) hung the app.
|
||||
- `lib.rs`: compiler warnings no longer fail the build. Test targets are dropped (their sources
|
||||
are not vendored).
|
||||
|
||||
## egui-wgpu 0.36.2 (MIT OR Apache-2.0, Emil Ernerfeldt and contributors)
|
||||
|
||||
Original package sources from https://github.com/emilk/egui/tree/0.36.2/crates/egui-wgpu,
|
||||
|
||||
Vendored
-40
@@ -1,40 +0,0 @@
|
||||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
||||
#
|
||||
# When uploading crates to the registry Cargo will automatically
|
||||
# "normalize" Cargo.toml files for maximal compatibility
|
||||
# with all versions of Cargo and also rewrite `path` dependencies
|
||||
# to registry (e.g., crates.io) dependencies.
|
||||
#
|
||||
# If you are reading this file be aware that the original Cargo.toml
|
||||
# will likely look very different (and much more reasonable).
|
||||
# See Cargo.toml.orig for the original contents.
|
||||
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "matext4cgmath"
|
||||
version = "0.1.0"
|
||||
authors = ["Yoshinori Tanimura <tanimura@ricos.co.jp>"]
|
||||
description = "Unofficial third-party cgmath extensions for calculate eigenvalues, operator norms and Iwasawa decomposition."
|
||||
homepage = "https://github.com/ricosjp/matext4cgmath"
|
||||
keywords = [
|
||||
"cgmath",
|
||||
"graphics",
|
||||
"linear-algebra",
|
||||
]
|
||||
categories = ["graphics"]
|
||||
license = "Apache-2.0"
|
||||
repository = "https://github.com/ricosjp/matext4cgmath"
|
||||
resolver = "2"
|
||||
autotests = false
|
||||
autoexamples = false
|
||||
autobenches = false
|
||||
|
||||
[dependencies.cgmath]
|
||||
version = "0.18.0"
|
||||
|
||||
[dependencies.katexit]
|
||||
version = "0.1.2"
|
||||
|
||||
[dependencies.num-complex]
|
||||
version = "0.4.0"
|
||||
|
||||
Vendored
-201
@@ -1,201 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2022 Research Institute for Computational Science Co. Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
Vendored
-5
@@ -1,5 +0,0 @@
|
||||
# matext4cgmath
|
||||
|
||||
[](https://crates.io/crates/matext4cgmath) [](https://docs.rs/matext4cgmath)
|
||||
|
||||
Unofficial third-party cgmath extensions for calculate eigenvalues, operator norms and Iwasawa decomposition.
|
||||
Vendored
-153
@@ -1,153 +0,0 @@
|
||||
use crate::*;
|
||||
|
||||
impl<F: BaseFloat> EigenValues for Matrix2<F> {
|
||||
type EigenValues = [Complex<F>; 2];
|
||||
fn eigenvalues(self) -> [Complex<F>; 2] {
|
||||
solver::solve_quadratic(-self.trace(), self.determinant())
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: BaseFloat> EigenValues for Matrix3<F> {
|
||||
type EigenValues = [Complex<F>; 3];
|
||||
fn eigenvalues(self) -> [Complex<F>; 3] {
|
||||
let poly2 = self[0][0] * self[1][1] + self[0][0] * self[2][2] + self[1][1] * self[2][2]
|
||||
- self[0][1] * self[1][0]
|
||||
- self[1][2] * self[2][1]
|
||||
- self[0][2] * self[2][0];
|
||||
solver::solve_cubic(-self.trace(), poly2, -self.determinant())
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: BaseFloat> EigenValues for Matrix4<F> {
|
||||
type EigenValues = [Complex<F>; 4];
|
||||
fn eigenvalues(self) -> [Complex<F>; 4] {
|
||||
let poly2 = self[0][0] * self[1][1]
|
||||
+ self[0][0] * self[2][2]
|
||||
+ self[0][0] * self[3][3]
|
||||
+ self[1][1] * self[2][2]
|
||||
+ self[1][1] * self[3][3]
|
||||
+ self[2][2] * self[3][3]
|
||||
- self[0][1] * self[1][0]
|
||||
- self[0][2] * self[2][0]
|
||||
- self[0][3] * self[3][0]
|
||||
- self[1][2] * self[2][1]
|
||||
- self[1][3] * self[3][1]
|
||||
- self[2][3] * self[3][2];
|
||||
let poly3 = (self[0][0] * self[1][1] + self[0][0] * self[2][2] + self[1][1] * self[2][2]
|
||||
- self[0][1] * self[1][0]
|
||||
- self[0][2] * self[2][0]
|
||||
- self[1][2] * self[2][1])
|
||||
* self[3][3]
|
||||
+ (self[2][0] * self[3][2] - self[2][2] * self[3][0] + self[1][0] * self[3][1]
|
||||
- self[1][1] * self[3][0])
|
||||
* self[0][3]
|
||||
+ (self[2][1] * self[3][2] - self[2][2] * self[3][1] + self[3][0] * self[0][1]
|
||||
- self[3][1] * self[0][0])
|
||||
* self[1][3]
|
||||
+ (self[3][1] * self[1][2] - self[3][2] * self[1][1] + self[3][0] * self[0][2]
|
||||
- self[3][2] * self[0][0])
|
||||
* self[2][3]
|
||||
+ Matrix3::from_cols(self[0].truncate(), self[1].truncate(), self[2].truncate())
|
||||
.determinant();
|
||||
solver::solve_quartic(-self.trace(), poly2, -poly3, self.determinant())
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: BaseFloat> OperatorNorm for Matrix2<F> {
|
||||
#[inline]
|
||||
fn norm_l1(self) -> F {
|
||||
F::max(
|
||||
F::abs(self[0][0]) + F::abs(self[0][1]),
|
||||
F::abs(self[1][0]) + F::abs(self[1][1]),
|
||||
)
|
||||
}
|
||||
#[inline]
|
||||
fn norm_linf(self) -> F {
|
||||
F::max(
|
||||
F::abs(self[0][0]) + F::abs(self[1][0]),
|
||||
F::abs(self[0][1]) + F::abs(self[1][1]),
|
||||
)
|
||||
}
|
||||
#[inline]
|
||||
fn norm_l2(self) -> F {
|
||||
let eigens = (self.transpose() * self).eigenvalues();
|
||||
F::sqrt(F::max(eigens[0].re, eigens[1].re))
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: BaseFloat> OperatorNorm for Matrix3<F> {
|
||||
#[inline]
|
||||
fn norm_l1(self) -> F {
|
||||
F::max(
|
||||
F::max(
|
||||
F::abs(self[0][0]) + F::abs(self[0][1]) + F::abs(self[0][2]),
|
||||
F::abs(self[1][0]) + F::abs(self[1][1]) + F::abs(self[1][2]),
|
||||
),
|
||||
F::abs(self[2][0]) + F::abs(self[2][1]) + F::abs(self[2][2]),
|
||||
)
|
||||
}
|
||||
#[inline]
|
||||
fn norm_linf(self) -> F {
|
||||
F::max(
|
||||
F::max(
|
||||
F::abs(self[0][0]) + F::abs(self[1][0]) + F::abs(self[2][0]),
|
||||
F::abs(self[0][1]) + F::abs(self[1][1]) + F::abs(self[2][1]),
|
||||
),
|
||||
F::abs(self[0][2]) + F::abs(self[1][2]) + F::abs(self[2][2]),
|
||||
)
|
||||
}
|
||||
#[inline]
|
||||
fn norm_l2(self) -> F {
|
||||
let eigens = (self.transpose() * self).eigenvalues();
|
||||
F::sqrt(F::max(F::max(eigens[0].re, eigens[1].re), eigens[2].re))
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: BaseFloat> OperatorNorm for Matrix4<F> {
|
||||
#[inline]
|
||||
fn norm_l1(self) -> F {
|
||||
F::max(
|
||||
F::max(
|
||||
F::max(
|
||||
F::abs(self[0][0])
|
||||
+ F::abs(self[0][1])
|
||||
+ F::abs(self[0][2])
|
||||
+ F::abs(self[0][3]),
|
||||
F::abs(self[1][0])
|
||||
+ F::abs(self[1][1])
|
||||
+ F::abs(self[1][2])
|
||||
+ F::abs(self[1][3]),
|
||||
),
|
||||
F::abs(self[2][0]) + F::abs(self[2][1]) + F::abs(self[2][2]) + F::abs(self[2][3]),
|
||||
),
|
||||
F::abs(self[3][0]) + F::abs(self[3][1]) + F::abs(self[3][2]) + F::abs(self[3][3]),
|
||||
)
|
||||
}
|
||||
#[inline]
|
||||
fn norm_linf(self) -> F {
|
||||
F::max(
|
||||
F::max(
|
||||
F::max(
|
||||
F::abs(self[0][0])
|
||||
+ F::abs(self[1][0])
|
||||
+ F::abs(self[2][0])
|
||||
+ F::abs(self[3][0]),
|
||||
F::abs(self[0][1])
|
||||
+ F::abs(self[1][1])
|
||||
+ F::abs(self[2][1])
|
||||
+ F::abs(self[3][1]),
|
||||
),
|
||||
F::abs(self[0][2]) + F::abs(self[1][2]) + F::abs(self[2][2]) + F::abs(self[3][2]),
|
||||
),
|
||||
F::abs(self[0][3]) + F::abs(self[1][3]) + F::abs(self[2][3]) + F::abs(self[3][3]),
|
||||
)
|
||||
}
|
||||
#[inline]
|
||||
fn norm_l2(self) -> F {
|
||||
let eigens = (self.transpose() * self).eigenvalues();
|
||||
F::sqrt(F::max(
|
||||
F::max(F::max(eigens[0].re, eigens[1].re), eigens[2].re),
|
||||
eigens[3].re,
|
||||
))
|
||||
}
|
||||
}
|
||||
-106
@@ -1,106 +0,0 @@
|
||||
use crate::*;
|
||||
|
||||
impl<F: BaseFloat> Exponential for Matrix2<F> {}
|
||||
impl<F: BaseFloat> Exponential for Matrix3<F> {}
|
||||
impl<F: BaseFloat> Exponential for Matrix4<F> {}
|
||||
|
||||
impl<F: BaseFloat> Decomposition for Matrix2<F> {
|
||||
fn iwasawa_decomposition(self) -> Option<(Self, Self, Self)> {
|
||||
let v0 = self[0];
|
||||
let mag0 = dot(v0, v0);
|
||||
if mag0 <= F::zero() {
|
||||
return None;
|
||||
}
|
||||
let n0 = dot(self[1], v0) / mag0;
|
||||
let v1 = self[1] - v0 * n0;
|
||||
let mag1 = dot(v1, v1);
|
||||
if mag1 <= F::zero() {
|
||||
return None;
|
||||
}
|
||||
let (a0, a1) = (F::sqrt(mag0), F::sqrt(mag1));
|
||||
Some((
|
||||
Matrix2::from_cols(v0 / a0, v1 / a1),
|
||||
Matrix2::from_diagonal(Vector2::new(a0, a1)),
|
||||
Matrix2::new(F::one(), F::zero(), n0, F::one()),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: BaseFloat> Decomposition for Matrix3<F> {
|
||||
fn iwasawa_decomposition(self) -> Option<(Self, Self, Self)> {
|
||||
let v0 = self[0];
|
||||
let mag0 = dot(v0, v0);
|
||||
if mag0 <= F::zero() {
|
||||
return None;
|
||||
}
|
||||
let n0 = dot(self[1], v0) / mag0;
|
||||
let v1 = self[1] - v0 * n0;
|
||||
let mag1 = dot(v1, v1);
|
||||
if mag1 <= F::zero() {
|
||||
return None;
|
||||
}
|
||||
let n1 = dot(self[2], v0) / mag0;
|
||||
let n2 = dot(self[2], v1) / mag1;
|
||||
let v2 = self[2] - v0 * n1 - v1 * n2;
|
||||
let mag2 = dot(v2, v2);
|
||||
if mag2 <= F::zero() {
|
||||
return None;
|
||||
}
|
||||
let (a0, a1, a2) = (F::sqrt(mag0), F::sqrt(mag1), F::sqrt(mag2));
|
||||
#[rustfmt::skip]
|
||||
let res = Some((
|
||||
Matrix3::from_cols(v0 / a0, v1 / a1, v2 / a2),
|
||||
Matrix3::from_diagonal(Vector3::new(a0, a1, a2)),
|
||||
Matrix3::new(
|
||||
F::one(), F::zero(), F::zero(),
|
||||
n0, F::one(), F::zero(),
|
||||
n1, n2, F::one(),
|
||||
),
|
||||
));
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: BaseFloat> Decomposition for Matrix4<F> {
|
||||
fn iwasawa_decomposition(self) -> Option<(Self, Self, Self)> {
|
||||
let v0 = self[0];
|
||||
let mag0 = dot(v0, v0);
|
||||
if mag0 <= F::zero() {
|
||||
return None;
|
||||
}
|
||||
let n0 = dot(self[1], v0) / mag0;
|
||||
let v1 = self[1] - v0 * n0;
|
||||
let mag1 = dot(v1, v1);
|
||||
if mag1 <= F::zero() {
|
||||
return None;
|
||||
}
|
||||
let n1 = dot(self[2], v0) / mag0;
|
||||
let n2 = dot(self[2], v1) / mag1;
|
||||
let v2 = self[2] - v0 * n1 - v1 * n2;
|
||||
let mag2 = dot(v2, v2);
|
||||
if mag2 <= F::zero() {
|
||||
return None;
|
||||
}
|
||||
let n3 = dot(self[3], v0) / mag0;
|
||||
let n4 = dot(self[3], v1) / mag1;
|
||||
let n5 = dot(self[3], v2) / mag2;
|
||||
let v3 = self[3] - v0 * n3 - v1 * n4 - v2 * n5;
|
||||
let mag3 = dot(v3, v3);
|
||||
if mag3 <= F::zero() {
|
||||
return None;
|
||||
}
|
||||
let (a0, a1, a2, a3) = (F::sqrt(mag0), F::sqrt(mag1), F::sqrt(mag2), F::sqrt(mag3));
|
||||
#[rustfmt::skip]
|
||||
let res = Some((
|
||||
Matrix4::from_cols(v0 / a0, v1 / a1, v2 / a2, v3 / a3),
|
||||
Matrix4::from_diagonal(Vector4::new(a0, a1, a2, a3)),
|
||||
Matrix4::new(
|
||||
F::one(), F::zero(), F::zero(), F::zero(),
|
||||
n0, F::one(), F::zero(), F::zero(),
|
||||
n1, n2, F::one(), F::zero(),
|
||||
n3, n4, n5, F::one(),
|
||||
),
|
||||
));
|
||||
res
|
||||
}
|
||||
}
|
||||
Vendored
-120
@@ -1,120 +0,0 @@
|
||||
//! Unofficial third-party cgmath extensions for calculate eigenvalues, operator norms and Iwasawa decomposition.
|
||||
|
||||
// SolidCraft: compiler warnings no longer fail the build.
|
||||
#![deny(clippy::all, rust_2018_idioms)]
|
||||
#![warn(
|
||||
missing_docs,
|
||||
missing_debug_implementations,
|
||||
trivial_casts,
|
||||
trivial_numeric_casts,
|
||||
unsafe_code,
|
||||
unstable_features,
|
||||
unused_import_braces,
|
||||
unused_qualifications
|
||||
)]
|
||||
#![allow(clippy::many_single_char_names)]
|
||||
|
||||
pub use cgmath;
|
||||
use cgmath::*;
|
||||
use num_complex::Complex;
|
||||
|
||||
mod eigens;
|
||||
mod exp_decomp;
|
||||
/// solvers for low dimensional algebraic equations.
|
||||
pub mod solver;
|
||||
|
||||
/// extension for eigen values
|
||||
pub trait EigenValues: VectorSpace {
|
||||
/// the type of the array of eigen values
|
||||
type EigenValues;
|
||||
/// calculate eigen values.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use cgmath::*;
|
||||
/// use matext4cgmath::*;
|
||||
/// use num_complex::Complex;
|
||||
/// const EPS: f64 = 1.0e-10;
|
||||
///
|
||||
/// let mat = Matrix2::new(4.0, -2.0, 3.0, -1.0);
|
||||
/// let mut eigens = mat.eigenvalues();
|
||||
/// // Even in the case of real solutions, the order in the array is not guaranteed.
|
||||
/// eigens.sort_by(|x, y| x.re.partial_cmp(&y.re).unwrap());
|
||||
/// assert!(Complex::norm(eigens[0] - 1.0) < EPS);
|
||||
/// assert!(Complex::norm(eigens[1] - 2.0) < EPS);
|
||||
/// ```
|
||||
fn eigenvalues(self) -> Self::EigenValues;
|
||||
}
|
||||
|
||||
#[cfg_attr(doc, katexit::katexit)]
|
||||
/// [operator norms](https://en.wikipedia.org/wiki/Matrix_norm): $L^1$, $L^2$, and $L^\infty$.
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use cgmath::*;
|
||||
/// use matext4cgmath::*;
|
||||
///
|
||||
/// let mat = Matrix2::new(1.0, 3.0, -2.0, 4.0);
|
||||
/// // L^1 operator norm is the maximum column absolute sumation.
|
||||
/// assert_eq!(mat.norm_l1(), 6.0);
|
||||
/// // L^2 operator norm is the maximum singular value.
|
||||
/// let ans_norm_l2 = (5.0 + f64::sqrt(5.0)) / f64::sqrt(2.0);
|
||||
/// assert!(f64::abs(mat.norm_l2() - ans_norm_l2) < 1.0e-10);
|
||||
/// // L^∞ operator norm is the maximum row absolute sumation.
|
||||
/// assert_eq!(mat.norm_linf(), 7.0);
|
||||
/// ```
|
||||
pub trait OperatorNorm: VectorSpace {
|
||||
/// operator norm for absolute value sumation: $L^1$.
|
||||
fn norm_l1(self) -> Self::Scalar;
|
||||
/// operator norm for Euclidean norm: $L^2$.
|
||||
fn norm_l2(self) -> Self::Scalar;
|
||||
/// operator norm for absolute value maximum: $L^{\infty}$.
|
||||
fn norm_linf(self) -> Self::Scalar;
|
||||
}
|
||||
|
||||
/// calculate exponential value
|
||||
pub trait Exponential: OperatorNorm + std::ops::AddAssign<Self> + One
|
||||
where
|
||||
Self::Scalar: BaseFloat,
|
||||
{
|
||||
/// calculate exponential
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use cgmath::*;
|
||||
/// use matext4cgmath::*;
|
||||
///
|
||||
/// let x = Matrix2::new(0.0, 1.0, -1.0, 0.0);
|
||||
/// let res = x.exp();
|
||||
/// let ans = Matrix2::from_angle(Rad(1.0));
|
||||
/// assert!((res - ans).norm_l1() < 1.0e-10);
|
||||
/// ```
|
||||
fn exp(self) -> Self {
|
||||
use num_traits::{Float, NumCast};
|
||||
let eps = <Self::Scalar as Float>::epsilon();
|
||||
let mut a = self;
|
||||
let mut res = Self::one();
|
||||
for i in 2_u16..=64 {
|
||||
res += a;
|
||||
a = a * self / <Self::Scalar as NumCast>::from(i).unwrap();
|
||||
if a.norm_linf() < eps * self.norm_linf() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(doc, katexit::katexit)]
|
||||
/// some decompositions of matrix
|
||||
pub trait Decomposition: VectorSpace {
|
||||
#[cfg_attr(doc, katexit::katexit)]
|
||||
/// Returns $(K, A, N)$ of the Iwasawa decomposition: $M = KAN$.
|
||||
///
|
||||
/// - $K$: orthonormal matrix
|
||||
/// - $A$: diagonal matrix
|
||||
/// - $N$: upper-half unipotent matrix
|
||||
fn iwasawa_decomposition(self) -> Option<(Self, Self, Self)>;
|
||||
}
|
||||
Vendored
-246
@@ -1,246 +0,0 @@
|
||||
use crate::*;
|
||||
|
||||
#[cfg_attr(doc, katexit::katexit)]
|
||||
/// Solve equation: $x^2 + ax + b = 0$.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use matext4cgmath::solver;
|
||||
/// use num_complex::Complex;
|
||||
/// const EPS: f64 = 1.0e-10;
|
||||
///
|
||||
/// let res = solver::solve_quadratic(3.0, -4.0);
|
||||
/// // Real solutions are always sorted in order of solution size.
|
||||
/// assert!(Complex::norm(res[0] + 4.0) < EPS);
|
||||
/// assert!(Complex::norm(res[1] - 1.0) < EPS);
|
||||
/// ```
|
||||
pub fn solve_quadratic<F: BaseFloat>(a: F, b: F) -> [Complex<F>; 2] {
|
||||
let two = F::one() + F::one();
|
||||
let four = two + two;
|
||||
let det = a * a - four * b;
|
||||
match det >= F::zero() {
|
||||
true => {
|
||||
let h = F::sqrt(det);
|
||||
[
|
||||
Complex::new((-a - h) / two, F::zero()),
|
||||
Complex::new((-a + h) / two, F::zero()),
|
||||
]
|
||||
}
|
||||
false => {
|
||||
let h = F::sqrt(-det);
|
||||
[Complex::new(-a, h) / two, Complex::new(-a, -h) / two]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(doc, katexit::katexit)]
|
||||
/// Solve equation: $x^3 + px + q = 0$.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use matext4cgmath::solver;
|
||||
/// use num_complex::Complex;
|
||||
/// const EPS: f64 = 1.0e-10;
|
||||
///
|
||||
/// let mut res = solver::pre_solve_cubic(-7.0, -6.0);
|
||||
/// // Even in the case of real solutions, the order in the array is not guaranteed.
|
||||
/// res.sort_by(|x, y| x.re.partial_cmp(&y.re).unwrap());
|
||||
/// let ans = [Complex::from(-2.0), Complex::from(-1.0), Complex::from(3.0)];
|
||||
/// res.iter().zip(ans).for_each(|(x, y)| {
|
||||
/// assert!(Complex::norm(x - y) < EPS);
|
||||
/// });
|
||||
/// ```
|
||||
pub fn pre_solve_cubic<F: BaseFloat>(p: F, q: F) -> [Complex<F>; 3] {
|
||||
let two = F::one() + F::one();
|
||||
let three = two + F::one();
|
||||
let sqrt3_2 = F::sqrt(three) / two;
|
||||
let omega = Complex::new(-F::one() / two, sqrt3_2);
|
||||
let omega2 = Complex::new(-F::one() / two, -sqrt3_2);
|
||||
let eps_2 = F::sqrt(F::epsilon());
|
||||
|
||||
let p_3 = p / three;
|
||||
let q_2 = q / two;
|
||||
let alpha2 = q_2 * q_2 + p_3 * p_3 * p_3;
|
||||
let (x, y) = match alpha2 >= F::zero() {
|
||||
true => {
|
||||
let alpha = F::sqrt(alpha2);
|
||||
let tmpx = -q_2 - alpha;
|
||||
let tmpy = -q_2 + alpha;
|
||||
(
|
||||
Complex::new(
|
||||
F::signum(tmpx) * F::powf(F::abs(tmpx), F::one() / three),
|
||||
F::zero(),
|
||||
),
|
||||
Complex::new(
|
||||
F::signum(tmpy) * F::powf(F::abs(tmpy), F::one() / three),
|
||||
F::zero(),
|
||||
),
|
||||
)
|
||||
}
|
||||
false => {
|
||||
let alphai = F::sqrt(-alpha2);
|
||||
(
|
||||
Complex::powf(Complex::new(-q_2, alphai), F::one() / three),
|
||||
Complex::powf(Complex::new(-q_2, -alphai), F::one() / three),
|
||||
)
|
||||
}
|
||||
};
|
||||
let mut res = [x + y, omega * x + omega2 * y, omega2 * x + omega * y];
|
||||
// precision by Newton method
|
||||
res.iter_mut().for_each(|x| {
|
||||
let mut f = *x * *x * *x + *x * p + q;
|
||||
let mut f_prime = *x * *x * three + p;
|
||||
// SolidCraft: at most 100 steps. Near a double root (the eigenvalues of a
|
||||
// translation matrix) the iteration can cycle forever without meeting the tolerance.
|
||||
let mut steps = 0;
|
||||
while f.norm() > eps_2 * f_prime.norm() && steps < 100 {
|
||||
steps += 1;
|
||||
if f_prime.norm() < eps_2 {
|
||||
return;
|
||||
}
|
||||
*x -= f / f_prime;
|
||||
f = *x * *x * *x + *x * p + q;
|
||||
f_prime = *x * *x * three + p;
|
||||
}
|
||||
});
|
||||
res
|
||||
}
|
||||
|
||||
#[cfg_attr(doc, katexit::katexit)]
|
||||
/// solve equation: $x^3 + ax^2 + bx + c = 0$.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use matext4cgmath::solver;
|
||||
/// use num_complex::Complex;
|
||||
/// const EPS: f64 = 1.0e-10;
|
||||
///
|
||||
/// let mut res = solver::solve_cubic(-3.0, 0.0, 4.0);
|
||||
/// // Even in the case of real solutions, the order in the array is not guaranteed.
|
||||
/// res.sort_by(|x, y| x.re.partial_cmp(&y.re).unwrap());
|
||||
/// let ans = [Complex::from(-1.0), Complex::from(2.0), Complex::from(2.0)];
|
||||
/// res.iter().zip(ans).for_each(|(x, y)| {
|
||||
/// assert!(Complex::norm(x - y) < EPS);
|
||||
/// });
|
||||
/// ```
|
||||
pub fn solve_cubic<F: BaseFloat>(a: F, b: F, c: F) -> [Complex<F>; 3] {
|
||||
let two = F::one() + F::one();
|
||||
let three = two + F::one();
|
||||
let twenty_seven = three * three * three;
|
||||
let p = b - a * a / three;
|
||||
let q = c - a * b / three + two * a * a * a / twenty_seven;
|
||||
let mut res = pre_solve_cubic(p, q);
|
||||
res.iter_mut().for_each(|x| {
|
||||
*x -= a / three;
|
||||
});
|
||||
res
|
||||
}
|
||||
|
||||
#[cfg_attr(doc, katexit::katexit)]
|
||||
/// solve equation: $x^4 + px^2 + qx + r = 0$.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use matext4cgmath::solver;
|
||||
/// use num_complex::Complex;
|
||||
/// const EPS: f64 = 1e-7;
|
||||
///
|
||||
/// let mut res = solver::pre_solve_quartic(-5.0, 0.0, 4.0);
|
||||
/// // Even in the case of real solutions, the order in the array is not guaranteed.
|
||||
/// res.sort_by(|x, y| x.re.partial_cmp(&y.re).unwrap());
|
||||
/// let ans = [Complex::from(-2.0), Complex::from(-1.0), Complex::from(1.0), Complex::from(2.0)];
|
||||
/// res.iter().zip(ans).for_each(|(x, y)| {
|
||||
/// assert!(Complex::norm(x - y) < EPS);
|
||||
/// });
|
||||
/// ```
|
||||
pub fn pre_solve_quartic<F: BaseFloat>(p: F, q: F, r: F) -> [Complex<F>; 4] {
|
||||
let one = F::one();
|
||||
let two = one + one;
|
||||
let four = two + two;
|
||||
let eps_2 = F::sqrt(F::epsilon());
|
||||
|
||||
let a = two * p;
|
||||
let b = p * p - four * r;
|
||||
let c = -q * q;
|
||||
let f = solve_cubic(a, b, c);
|
||||
let a = f[0].sqrt() / two;
|
||||
let b = f[1].sqrt() / two;
|
||||
let c = f[2].sqrt() / two;
|
||||
|
||||
let mut res = (0..8)
|
||||
.map(|i| {
|
||||
let a = a * F::powi(-F::one(), i % 2);
|
||||
let b = b * F::powi(-F::one(), (i / 2) % 2);
|
||||
let c = c * F::powi(-F::one(), (i / 4) % 2);
|
||||
[-a - b - c, -a + b + c, a - b + c, a + b - c]
|
||||
})
|
||||
.map(|x| {
|
||||
let f = x
|
||||
.iter()
|
||||
.map(|t| (t * t * t * t + t * t * p + t * q + r).norm_sqr())
|
||||
.max_by(|x, y| x.partial_cmp(y).unwrap())
|
||||
.unwrap();
|
||||
(x, f)
|
||||
})
|
||||
.min_by(|x, y| x.1.partial_cmp(&y.1).unwrap())
|
||||
.unwrap()
|
||||
.0;
|
||||
// refinement by Newton method
|
||||
res.iter_mut().for_each(|x| {
|
||||
let mut f = *x * *x * *x * *x + *x * *x * p + *x * q + r;
|
||||
let mut f_prime = *x * *x * *x * four + *x * p * two + q;
|
||||
// SolidCraft: at most 100 steps. Near a double root (the eigenvalues of a
|
||||
// translation matrix) the iteration can cycle forever without meeting the tolerance.
|
||||
let mut steps = 0;
|
||||
while f.norm() > eps_2 * f_prime.norm() && steps < 100 {
|
||||
steps += 1;
|
||||
if f_prime.norm() < eps_2 {
|
||||
return;
|
||||
}
|
||||
*x -= f / f_prime;
|
||||
f_prime = *x * *x * *x * four + *x * p * two + q;
|
||||
f = *x * *x * *x * *x + *x * *x * p + *x * q + r;
|
||||
}
|
||||
});
|
||||
res
|
||||
}
|
||||
|
||||
#[cfg_attr(doc, katexit::katexit)]
|
||||
/// solve equation: $x^4 + ax^3 + bx^2 + cx + d = 0$.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use matext4cgmath::solver;
|
||||
/// use num_complex::Complex;
|
||||
/// const EPS: f64 = 1.0e-10;
|
||||
///
|
||||
/// let mut res = solver::solve_quartic(1.0, -7.0, -1.0, 6.0);
|
||||
/// // Even in the case of real solutions, the order in the array is not guaranteed.
|
||||
/// res.sort_by(|x, y| x.re.partial_cmp(&y.re).unwrap());
|
||||
/// let ans = [Complex::from(-3.0), Complex::from(-1.0), Complex::from(1.0), Complex::from(2.0)];
|
||||
/// res.iter().zip(ans).for_each(|(x, y)| {
|
||||
/// assert!(Complex::norm(x * x * x * x + x * x * x - 7.0 * x * x - x + 6.0) < EPS);
|
||||
/// assert!(Complex::norm(x - y) < EPS, "{x} {y}");
|
||||
/// });
|
||||
/// ```
|
||||
pub fn solve_quartic<F: BaseFloat>(a: F, b: F, c: F, d: F) -> [Complex<F>; 4] {
|
||||
let one = F::one();
|
||||
let two = one + one;
|
||||
let three = one + two;
|
||||
let four = two + two;
|
||||
let six = two * three;
|
||||
let eight = four + four;
|
||||
|
||||
let a_4 = a / four;
|
||||
let p = b - six * a_4 * a_4;
|
||||
let q = c - two * b * a_4 + eight * a_4 * a_4 * a_4;
|
||||
let r = d - c * a_4 + b * a_4 * a_4 - three * a_4 * a_4 * a_4 * a_4;
|
||||
let mut res = pre_solve_quartic(p, q, r);
|
||||
res.iter_mut().for_each(|x| *x -= a_4);
|
||||
res
|
||||
}
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
{"v":1}
|
||||
Vendored
-55
@@ -1,55 +0,0 @@
|
||||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
||||
#
|
||||
# When uploading crates to the registry Cargo will automatically
|
||||
# "normalize" Cargo.toml files for maximal compatibility
|
||||
# with all versions of Cargo and also rewrite `path` dependencies
|
||||
# to registry (e.g., crates.io) dependencies.
|
||||
#
|
||||
# If you are reading this file be aware that the original Cargo.toml
|
||||
# will likely look very different (and much more reasonable).
|
||||
# See Cargo.toml.orig for the original contents.
|
||||
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "truck-geometry"
|
||||
version = "0.5.0"
|
||||
authors = ["Yoshinori Tanimura <tanimura@ricos.co.jp>"]
|
||||
build = false
|
||||
autobins = false
|
||||
autoexamples = false
|
||||
autotests = false
|
||||
autobenches = false
|
||||
description = "geometrical structs: knot vector, B-spline and NURBS"
|
||||
homepage = "https://github.com/ricosjp/truck"
|
||||
readme = "README.md"
|
||||
keywords = [
|
||||
"truck",
|
||||
"graphics",
|
||||
]
|
||||
categories = ["graphics"]
|
||||
license = "Apache-2.0"
|
||||
repository = "https://github.com/ricosjp/truck"
|
||||
|
||||
[lib]
|
||||
name = "truck_geometry"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies.serde]
|
||||
version = "1.0.210"
|
||||
features = ["derive"]
|
||||
|
||||
[dependencies.thiserror]
|
||||
version = "1.0.63"
|
||||
|
||||
[dependencies.truck-base]
|
||||
version = "0.5.0"
|
||||
|
||||
[dependencies.truck-geotrait]
|
||||
version = "0.4.0"
|
||||
|
||||
[dev-dependencies.rand]
|
||||
version = "0.8.5"
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dev-dependencies.getrandom]
|
||||
version = "0.2.15"
|
||||
features = ["js"]
|
||||
Vendored
-177
@@ -1,177 +0,0 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
Vendored
-5
@@ -1,5 +0,0 @@
|
||||
# truck-geometry
|
||||
|
||||
[](https://crates.io/crates/truck-geometry) [](https://docs.rs/truck-geometry)
|
||||
|
||||
Geometrical structs: knot vector, B-spline and NURBS
|
||||
@@ -1,228 +0,0 @@
|
||||
use super::*;
|
||||
|
||||
impl<C, S> PCurve<C, S> {
|
||||
/// Creates composited
|
||||
#[inline(always)]
|
||||
pub const fn new(curve: C, surface: S) -> PCurve<C, S> { PCurve { curve, surface } }
|
||||
|
||||
/// Returns the reference to the parameter curve
|
||||
#[inline(always)]
|
||||
pub const fn curve(&self) -> &C { &self.curve }
|
||||
|
||||
/// Returns the reference to the surface
|
||||
#[inline(always)]
|
||||
pub const fn surface(&self) -> &S { &self.surface }
|
||||
|
||||
/// Decompose the pcurve into its parameter curve and surface.
|
||||
#[inline(always)]
|
||||
pub fn decompose(self) -> (C, S) { (self.curve, self.surface) }
|
||||
}
|
||||
|
||||
impl<C, S> ParametricCurve for PCurve<C, S>
|
||||
where
|
||||
C: ParametricCurve2D,
|
||||
S: ParametricSurface,
|
||||
S::Vector: VectorSpace<Scalar = f64>,
|
||||
{
|
||||
type Point = S::Point;
|
||||
type Vector = S::Vector;
|
||||
#[inline(always)]
|
||||
fn subs(&self, t: f64) -> Self::Point {
|
||||
let pt = self.curve.subs(t);
|
||||
self.surface.subs(pt[0], pt[1])
|
||||
}
|
||||
#[inline(always)]
|
||||
fn der(&self, t: f64) -> Self::Vector {
|
||||
let pt = self.curve.subs(t);
|
||||
let der = self.curve.der(t);
|
||||
self.surface.uder(pt[0], pt[1]) * der[0] + self.surface.vder(pt[0], pt[1]) * der[1]
|
||||
}
|
||||
#[inline(always)]
|
||||
fn parameter_range(&self) -> ParameterRange { self.curve.parameter_range() }
|
||||
#[inline(always)]
|
||||
fn der2(&self, t: f64) -> Self::Vector {
|
||||
let pt = self.curve.subs(t);
|
||||
let der = self.curve.der(t);
|
||||
let der2 = self.curve.der2(t);
|
||||
self.surface.uuder(pt[0], pt[1]) * der[0] * der[0]
|
||||
+ self.surface.uvder(pt[0], pt[1]) * der[0] * der[1] * 2.0
|
||||
+ self.surface.vvder(pt[0], pt[1]) * der[1] * der[1]
|
||||
+ self.surface.uder(pt[0], pt[1]) * der2[0]
|
||||
+ self.surface.vder(pt[0], pt[1]) * der2[1]
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, S> BoundedCurve for PCurve<C, S>
|
||||
where
|
||||
C: BoundedCurve,
|
||||
PCurve<C, S>: ParametricCurve,
|
||||
{
|
||||
}
|
||||
|
||||
impl<C, S> Cut for PCurve<C, S>
|
||||
where
|
||||
C: Cut,
|
||||
S: Clone,
|
||||
PCurve<C, S>: ParametricCurve,
|
||||
{
|
||||
fn cut(&mut self, t: f64) -> Self {
|
||||
let curve = self.curve.cut(t);
|
||||
Self {
|
||||
curve,
|
||||
surface: self.surface.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, S> SearchParameter<D1> for PCurve<C, S>
|
||||
where
|
||||
C: ParametricCurve2D + SearchParameter<D1, Point = Point2>,
|
||||
S: SearchParameter<D2>,
|
||||
{
|
||||
type Point = <S as SearchParameter<D2>>::Point;
|
||||
fn search_parameter<H: Into<SPHint1D>>(
|
||||
&self,
|
||||
point: Self::Point,
|
||||
hint: H,
|
||||
trials: usize,
|
||||
) -> Option<f64> {
|
||||
let hint = hint.into();
|
||||
let shint = match hint {
|
||||
SPHint1D::Parameter(hint) => {
|
||||
let p = self.curve.subs(hint);
|
||||
SPHint2D::Parameter(p.x, p.y)
|
||||
}
|
||||
SPHint1D::Range(x, y) => {
|
||||
let p = self.curve.subs(y);
|
||||
let ranges = (0..PRESEARCH_DIVISION).fold(
|
||||
((p.x, p.x), (p.y, p.y)),
|
||||
|((x0, x1), (y0, y1)), i| {
|
||||
let t = x + (y - x) * i as f64 / PRESEARCH_DIVISION as f64;
|
||||
let p = self.curve.subs(t);
|
||||
(
|
||||
(f64::min(x0, p.x), f64::max(x1, p.x)),
|
||||
(f64::min(y0, p.y), f64::max(y1, p.y)),
|
||||
)
|
||||
},
|
||||
);
|
||||
SPHint2D::Range(ranges.0, ranges.1)
|
||||
}
|
||||
SPHint1D::None => SPHint2D::None,
|
||||
};
|
||||
let (x, y) = self.surface.search_parameter(point, shint, trials)?;
|
||||
self.curve.search_parameter(Point2::new(x, y), hint, trials)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, S> SearchNearestParameter<D1> for PCurve<C, S>
|
||||
where
|
||||
Self: BoundedCurve,
|
||||
<Self as ParametricCurve>::Point: EuclideanSpace<Scalar = f64, Diff = <Self as ParametricCurve>::Vector>
|
||||
+ MetricSpace<Metric = f64>,
|
||||
<Self as ParametricCurve>::Vector: InnerSpace<Scalar = f64> + Tolerance,
|
||||
{
|
||||
type Point = <Self as ParametricCurve>::Point;
|
||||
fn search_nearest_parameter<H: Into<SPHint1D>>(
|
||||
&self,
|
||||
point: Self::Point,
|
||||
hint: H,
|
||||
trials: usize,
|
||||
) -> Option<f64> {
|
||||
let hint = match hint.into() {
|
||||
SPHint1D::Parameter(hint) => hint,
|
||||
SPHint1D::Range(x, y) => {
|
||||
algo::curve::presearch(self, point, (x, y), PRESEARCH_DIVISION)
|
||||
}
|
||||
SPHint1D::None => {
|
||||
algo::curve::presearch(self, point, self.range_tuple(), PRESEARCH_DIVISION)
|
||||
}
|
||||
};
|
||||
algo::curve::search_nearest_parameter(self, point, hint, trials)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, S> ParameterDivision1D for PCurve<C, S>
|
||||
where
|
||||
C: ParametricCurve2D,
|
||||
S: ParametricSurface,
|
||||
S::Point: EuclideanSpace<Scalar = f64> + MetricSpace<Metric = f64> + HashGen<f64>,
|
||||
S::Vector: VectorSpace<Scalar = f64>,
|
||||
{
|
||||
type Point = S::Point;
|
||||
fn parameter_division(&self, range: (f64, f64), tol: f64) -> (Vec<f64>, Vec<S::Point>) {
|
||||
algo::curve::parameter_division(self, range, tol)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, S> Invertible for PCurve<C, S>
|
||||
where
|
||||
C: Invertible,
|
||||
S: Clone,
|
||||
{
|
||||
fn invert(&mut self) { self.curve.invert() }
|
||||
}
|
||||
|
||||
impl<C: Clone, S: Clone, T> Transformed<T> for PCurve<C, S>
|
||||
where S: Transformed<T>
|
||||
{
|
||||
#[inline(always)]
|
||||
fn transform_by(&mut self, trans: T) { self.surface.transform_by(trans); }
|
||||
fn transformed(&self, trans: T) -> Self {
|
||||
Self {
|
||||
curve: self.curve.clone(),
|
||||
surface: self.surface.transformed(trans),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pcurve_test() {
|
||||
let curve = BSplineCurve::new(
|
||||
KnotVec::bezier_knot(2),
|
||||
vec![
|
||||
Point2::new(1.0, 1.0),
|
||||
Point2::new(1.0, 0.0),
|
||||
Point2::new(0.0, 0.0),
|
||||
],
|
||||
);
|
||||
let surface = BSplineSurface::new(
|
||||
(KnotVec::bezier_knot(2), KnotVec::bezier_knot(1)),
|
||||
vec![
|
||||
vec![Point3::new(0.0, 0.0, 0.0), Point3::new(0.0, 1.0, 0.0)],
|
||||
vec![Point3::new(0.0, 0.0, 1.0), Point3::new(0.0, 1.0, 1.0)],
|
||||
vec![Point3::new(1.0, 0.0, 1.0), Point3::new(1.0, 1.0, 1.0)],
|
||||
],
|
||||
);
|
||||
let pcurve = PCurve::new(curve, surface);
|
||||
assert_eq!(pcurve.range_tuple(), (0.0, 1.0));
|
||||
|
||||
const N: usize = 100;
|
||||
for i in 0..=N {
|
||||
let t = i as f64 / N as f64;
|
||||
assert_near!(
|
||||
pcurve.subs(t),
|
||||
Point3::new(
|
||||
(1.0 - t * t) * (1.0 - t * t),
|
||||
(1.0 - t) * (1.0 - t),
|
||||
1.0 - t * t * t * t,
|
||||
),
|
||||
);
|
||||
assert_near!(
|
||||
pcurve.der(t),
|
||||
Vector3::new(4.0 * t * (t * t - 1.0), 2.0 * (t - 1.0), -4.0 * t * t * t,),
|
||||
);
|
||||
assert_near!(
|
||||
pcurve.der2(t),
|
||||
Vector3::new(4.0 * (3.0 * t * t - 1.0), 2.0, -12.0 * t * t,),
|
||||
);
|
||||
}
|
||||
|
||||
let t = 0.675;
|
||||
let pt = pcurve.subs(t);
|
||||
assert_near!(pcurve.search_parameter(pt, None, 100).unwrap(), t);
|
||||
|
||||
let pt = pt + Vector3::new(0.01, 0.06, -0.03);
|
||||
assert!(pcurve.search_parameter(pt, None, 100).is_none());
|
||||
let t = pcurve.search_nearest_parameter(pt, None, 100).unwrap();
|
||||
assert!(pcurve.der(t).dot(pcurve.subs(t) - pt).so_small());
|
||||
}
|
||||
@@ -1,203 +0,0 @@
|
||||
use super::*;
|
||||
|
||||
impl<C, V: Copy> ExtrudedCurve<C, V> {
|
||||
/// Creates a linear extruded curve by extrusion.
|
||||
#[inline(always)]
|
||||
pub const fn by_extrusion(curve: C, vector: V) -> Self { Self { curve, vector } }
|
||||
|
||||
/// Returns the curve before extruded.
|
||||
#[inline(always)]
|
||||
pub const fn entity_curve(&self) -> &C { &self.curve }
|
||||
/// Into the curve before revoluted.
|
||||
#[inline(always)]
|
||||
pub fn into_entity_curve(self) -> C { self.curve }
|
||||
|
||||
/// Returns the vector of extruded curve.
|
||||
#[inline(always)]
|
||||
pub const fn extruding_vector(&self) -> V { self.vector }
|
||||
}
|
||||
|
||||
impl<C> ParametricSurface for ExtrudedCurve<C, C::Vector>
|
||||
where
|
||||
C: ParametricCurve,
|
||||
C::Point: EuclideanSpace<Scalar = f64, Diff = C::Vector>,
|
||||
C::Vector: VectorSpace<Scalar = f64>,
|
||||
{
|
||||
type Point = C::Point;
|
||||
type Vector = C::Vector;
|
||||
#[inline(always)]
|
||||
fn subs(&self, u: f64, v: f64) -> C::Point { self.curve.subs(u) + self.vector * v }
|
||||
#[inline(always)]
|
||||
fn uder(&self, u: f64, _: f64) -> C::Vector { self.curve.der(u) }
|
||||
#[inline(always)]
|
||||
fn vder(&self, _: f64, _: f64) -> C::Vector { self.vector }
|
||||
#[inline(always)]
|
||||
fn uuder(&self, u: f64, _: f64) -> C::Vector { self.curve.der2(u) }
|
||||
#[inline(always)]
|
||||
fn uvder(&self, _: f64, _: f64) -> C::Vector { C::Vector::zero() }
|
||||
#[inline(always)]
|
||||
fn vvder(&self, _: f64, _: f64) -> C::Vector { C::Vector::zero() }
|
||||
#[inline(always)]
|
||||
fn parameter_range(&self) -> (ParameterRange, ParameterRange) {
|
||||
(
|
||||
self.curve.parameter_range(),
|
||||
(Bound::Included(0.0), Bound::Included(1.0)),
|
||||
)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn u_period(&self) -> Option<f64> { self.curve.period() }
|
||||
}
|
||||
|
||||
impl<C: ParametricCurve3D> ParametricSurface3D for ExtrudedCurve<C, Vector3> {
|
||||
#[inline(always)]
|
||||
fn normal(&self, u: f64, _: f64) -> C::Vector {
|
||||
self.curve.der(u).cross(self.vector).normalize()
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, V> BoundedSurface for ExtrudedCurve<C, V>
|
||||
where
|
||||
C: BoundedCurve,
|
||||
Self: ParametricSurface,
|
||||
{
|
||||
}
|
||||
|
||||
impl<C: ParameterDivision1D, V> ParameterDivision2D for ExtrudedCurve<C, V> {
|
||||
#[inline(always)]
|
||||
fn parameter_division(
|
||||
&self,
|
||||
(urange, vrange): ((f64, f64), (f64, f64)),
|
||||
tol: f64,
|
||||
) -> (Vec<f64>, Vec<f64>) {
|
||||
(
|
||||
self.curve.parameter_division(urange, tol).0,
|
||||
vec![vrange.0, vrange.1],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: ParametricCurve2D + BoundedCurve> SearchParameter<D2> for ExtrudedCurve<C, Vector2> {
|
||||
type Point = Point2;
|
||||
#[inline(always)]
|
||||
fn search_parameter<H: Into<SPHint2D>>(
|
||||
&self,
|
||||
point: Point2,
|
||||
hint: H,
|
||||
trials: usize,
|
||||
) -> Option<(f64, f64)> {
|
||||
let hint = match hint.into() {
|
||||
SPHint2D::Parameter(x, y) => (x, y),
|
||||
SPHint2D::Range(range0, range1) => {
|
||||
algo::surface::presearch(self, point, (range0, range1), PRESEARCH_DIVISION)
|
||||
}
|
||||
SPHint2D::None => {
|
||||
algo::surface::presearch(self, point, self.range_tuple(), PRESEARCH_DIVISION)
|
||||
}
|
||||
};
|
||||
algo::surface::search_parameter2d(self, point, hint, trials)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: ParametricCurve3D + BoundedCurve> SearchParameter<D2> for ExtrudedCurve<C, Vector3> {
|
||||
type Point = Point3;
|
||||
#[inline(always)]
|
||||
fn search_parameter<H: Into<SPHint2D>>(
|
||||
&self,
|
||||
point: Point3,
|
||||
hint: H,
|
||||
trials: usize,
|
||||
) -> Option<(f64, f64)> {
|
||||
let hint = match hint.into() {
|
||||
SPHint2D::Parameter(x, y) => (x, y),
|
||||
SPHint2D::Range(range0, range1) => {
|
||||
algo::surface::presearch(self, point, (range0, range1), PRESEARCH_DIVISION)
|
||||
}
|
||||
SPHint2D::None => {
|
||||
algo::surface::presearch(self, point, self.range_tuple(), PRESEARCH_DIVISION)
|
||||
}
|
||||
};
|
||||
algo::surface::search_parameter3d(self, point, hint, trials)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: ParametricCurve3D + BoundedCurve> SearchNearestParameter<D2> for ExtrudedCurve<C, Vector3> {
|
||||
type Point = Point3;
|
||||
#[inline(always)]
|
||||
fn search_nearest_parameter<H: Into<SPHint2D>>(
|
||||
&self,
|
||||
point: Point3,
|
||||
hint: H,
|
||||
trials: usize,
|
||||
) -> Option<(f64, f64)> {
|
||||
let hint = match hint.into() {
|
||||
SPHint2D::Parameter(x, y) => (x, y),
|
||||
SPHint2D::Range(range0, range1) => {
|
||||
algo::surface::presearch(self, point, (range0, range1), PRESEARCH_DIVISION)
|
||||
}
|
||||
SPHint2D::None => {
|
||||
algo::surface::presearch(self, point, self.range_tuple(), PRESEARCH_DIVISION)
|
||||
}
|
||||
};
|
||||
algo::surface::search_nearest_parameter(self, point, hint, trials)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Invertible> Invertible for ExtrudedCurve<C, Vector3> {
|
||||
#[inline(always)]
|
||||
fn invert(&mut self) { self.curve.invert() }
|
||||
#[inline(always)]
|
||||
fn inverse(&self) -> Self {
|
||||
Self {
|
||||
curve: self.curve.inverse(),
|
||||
vector: self.vector,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Transformed<Matrix4>> Transformed<Matrix4> for ExtrudedCurve<C, Vector3> {
|
||||
fn transform_by(&mut self, trans: Matrix4) {
|
||||
self.curve.transform_by(trans);
|
||||
self.vector = trans.transform_vector(self.vector);
|
||||
}
|
||||
fn transformed(&self, trans: Matrix4) -> Self {
|
||||
Self {
|
||||
curve: self.curve.transformed(trans),
|
||||
vector: trans.transform_vector(self.vector),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extruded_curve_test() {
|
||||
let cpts = vec![
|
||||
Point3::new(0.0, 0.0, 0.0),
|
||||
Point3::new(0.0, 1.0, 0.0),
|
||||
Point3::new(1.0, 0.0, 0.0),
|
||||
];
|
||||
let spts = vec![
|
||||
vec![Point3::new(0.0, 0.0, 0.0), Point3::new(0.0, 0.0, 1.0)],
|
||||
vec![Point3::new(0.0, 1.0, 0.0), Point3::new(0.0, 1.0, 1.0)],
|
||||
vec![Point3::new(1.0, 0.0, 0.0), Point3::new(1.0, 0.0, 1.0)],
|
||||
];
|
||||
let curve = BSplineCurve::new(KnotVec::bezier_knot(2), cpts);
|
||||
let surface0 = ExtrudedCurve::by_extrusion(curve, Vector3::unit_z());
|
||||
let surface1 = BSplineSurface::new((KnotVec::bezier_knot(2), KnotVec::bezier_knot(1)), spts);
|
||||
assert_eq!(surface0.range_tuple(), surface1.range_tuple());
|
||||
const N: usize = 10;
|
||||
for i in 0..=N {
|
||||
for j in 0..=N {
|
||||
let u = i as f64 / N as f64;
|
||||
let v = j as f64 / N as f64;
|
||||
assert_near!(
|
||||
surface0.subs(u, v),
|
||||
ParametricSurface::subs(&surface1, u, v)
|
||||
);
|
||||
assert_near!(surface0.uder(u, v), surface1.uder(u, v));
|
||||
assert_near!(surface0.vder(u, v), surface1.vder(u, v));
|
||||
assert_near!(surface0.uuder(u, v), surface1.uuder(u, v));
|
||||
assert_near!(surface0.uvder(u, v), surface1.uvder(u, v));
|
||||
assert_near!(surface0.vvder(u, v), surface1.vvder(u, v));
|
||||
assert_near!(surface0.normal(u, v), surface1.normal(u, v));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,353 +0,0 @@
|
||||
use super::*;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn double_projection<S>(
|
||||
surface0: &S,
|
||||
hint0: Option<(f64, f64)>,
|
||||
surface1: &S,
|
||||
hint1: Option<(f64, f64)>,
|
||||
mut point: Point3,
|
||||
normal: Vector3,
|
||||
trials: usize,
|
||||
) -> Option<(Point3, Point2, Point2)>
|
||||
where
|
||||
S: ParametricSurface3D + SearchNearestParameter<D2, Point = Point3>,
|
||||
{
|
||||
#[cfg(all(test, debug_assertions))]
|
||||
let mut log = Vec::new();
|
||||
let mut uv0 = surface0.search_nearest_parameter(point, hint0, 10)?;
|
||||
let mut uv1 = surface1.search_nearest_parameter(point, hint1, 10)?;
|
||||
for _ in 0..trials {
|
||||
#[cfg(all(test, debug_assertions))]
|
||||
log.push((point, uv0, uv1));
|
||||
uv0 = surface0.search_nearest_parameter(point, Some(uv0), 10)?;
|
||||
let pt0 = surface0.subs(uv0.0, uv0.1);
|
||||
uv1 = surface1.search_nearest_parameter(point, Some(uv1), 10)?;
|
||||
let pt1 = surface1.subs(uv1.0, uv1.1);
|
||||
if point.near(&pt0) && point.near(&pt1) && pt0.near(&pt1) {
|
||||
return Some((point, Point2::from(uv0), Point2::from(uv1)));
|
||||
} else {
|
||||
let n0 = surface0.normal(uv0.0, uv0.1);
|
||||
let n1 = surface1.normal(uv1.0, uv1.1);
|
||||
let mat = Matrix3::from_cols(n0, n1, normal).transpose();
|
||||
let inv = mat.invert()?;
|
||||
let pt = inv * Vector3::new(pt0.dot(n0), pt1.dot(n1), point.dot(normal));
|
||||
point = Point3::from_vec(pt);
|
||||
}
|
||||
}
|
||||
#[cfg(all(test, debug_assertions))]
|
||||
{
|
||||
eprintln!("Newton method is not converges");
|
||||
log.into_iter().for_each(|t| eprintln!("{t:?}"));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Mutable editor for `IntersectionCurve`.
|
||||
#[doc(hidden)]
|
||||
#[derive(Debug)]
|
||||
pub struct IntersectionCurveEditor<'a, C, S> {
|
||||
pub surface0: &'a mut S,
|
||||
pub surface1: &'a mut S,
|
||||
pub leader: &'a mut C,
|
||||
pub tol: &'a mut f64,
|
||||
}
|
||||
|
||||
impl<C, S> IntersectionCurve<C, S> {
|
||||
/// This curve is a part of intersection of `self.surface0()` and `self.surface1()`.
|
||||
#[inline(always)]
|
||||
pub fn surface0(&self) -> &S { &self.surface0 }
|
||||
/// This curve is a part of intersection of `self.surface0()` and `self.surface1()`.
|
||||
#[inline(always)]
|
||||
pub fn surface1(&self) -> &S { &self.surface1 }
|
||||
/// Returns the polyline leading this curve.
|
||||
#[inline(always)]
|
||||
pub fn leader(&self) -> &C { &self.leader }
|
||||
/// Returns editor for `IntersectionCurve`. This method is only for developers, do not use.
|
||||
#[doc(hidden)]
|
||||
#[inline(always)]
|
||||
pub fn editor(&mut self) -> IntersectionCurveEditor<'_, C, S> {
|
||||
IntersectionCurveEditor {
|
||||
surface0: &mut self.surface0,
|
||||
surface1: &mut self.surface1,
|
||||
leader: &mut self.leader,
|
||||
tol: &mut self.tol,
|
||||
}
|
||||
}
|
||||
/// Change leader.
|
||||
#[doc(hidden)]
|
||||
#[inline(always)]
|
||||
pub fn change_leader<D>(self, f: impl FnOnce(C) -> D) -> IntersectionCurve<D, S> {
|
||||
IntersectionCurve {
|
||||
surface0: self.surface0,
|
||||
surface1: self.surface1,
|
||||
leader: f(self.leader),
|
||||
tol: self.tol,
|
||||
}
|
||||
}
|
||||
/// The tolerance for generating this intersection curve.
|
||||
#[inline(always)]
|
||||
pub fn tolerance(&self) -> f64 { self.tol }
|
||||
/// Creates intersection curve with unchecked bound. This method is only for developer of `truck`, deplicated for users.
|
||||
#[inline(always)]
|
||||
pub fn new_unchecked(surface0: Box<S>, surface1: Box<S>, leader: C, tol: f64) -> Self {
|
||||
Self {
|
||||
surface0,
|
||||
surface1,
|
||||
leader,
|
||||
tol,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, S> IntersectionCurve<C, S>
|
||||
where
|
||||
C: ParametricCurve3D,
|
||||
S: ParametricSurface3D + SearchNearestParameter<D2, Point = Point3>,
|
||||
{
|
||||
/// Search triple value of the point corresponding to the parameter `t`.
|
||||
/// - the coordinate on 3D space
|
||||
/// - the uv coordinate on `self.surface0()`
|
||||
/// - the uv coordinate on `self.surface1()`
|
||||
#[inline(always)]
|
||||
pub fn search_triple(&self, t: f64) -> Option<(Point3, Point2, Point2)> {
|
||||
double_projection(
|
||||
self.surface0(),
|
||||
None,
|
||||
self.surface1(),
|
||||
None,
|
||||
self.leader.subs(t),
|
||||
self.leader.der(t),
|
||||
100,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, S> ParametricCurve for IntersectionCurve<C, S>
|
||||
where
|
||||
C: ParametricCurve3D,
|
||||
S: ParametricSurface3D + SearchNearestParameter<D2, Point = Point3>,
|
||||
{
|
||||
type Point = Point3;
|
||||
type Vector = Vector3;
|
||||
fn subs(&self, t: f64) -> Point3 { self.search_triple(t).unwrap().0 }
|
||||
fn der(&self, t: f64) -> Vector3 {
|
||||
let n = self.leader.der(t);
|
||||
let (_, p0, p1) = self.search_triple(t).unwrap();
|
||||
let d = self
|
||||
.surface0
|
||||
.normal(p0.x, p0.y)
|
||||
.cross(self.surface1.normal(p1.x, p1.y))
|
||||
.normalize();
|
||||
d * (n.dot(n) / d.dot(n))
|
||||
}
|
||||
/// This method is unimplemented! Should panic!!
|
||||
#[inline(always)]
|
||||
fn der2(&self, _: f64) -> Vector3 {
|
||||
unimplemented!();
|
||||
}
|
||||
#[inline(always)]
|
||||
fn parameter_range(&self) -> ParameterRange { self.leader.parameter_range() }
|
||||
}
|
||||
|
||||
impl<C, S> BoundedCurve for IntersectionCurve<C, S>
|
||||
where
|
||||
C: ParametricCurve3D + BoundedCurve,
|
||||
S: ParametricSurface3D + SearchNearestParameter<D2, Point = Point3>,
|
||||
{
|
||||
}
|
||||
|
||||
impl<C, S> ParameterDivision1D for IntersectionCurve<C, S>
|
||||
where
|
||||
C: ParametricCurve3D,
|
||||
S: ParametricSurface3D + SearchNearestParameter<D2, Point = Point3>,
|
||||
{
|
||||
type Point = Point3;
|
||||
// SolidCraft: the division of `algo::curve::parameter_division` (the same split points and
|
||||
// the same order), but each point's projection onto the two surfaces starts from the
|
||||
// parameters of the point that begins its piece instead of a search of the whole surfaces:
|
||||
// that search ran for every point and made dividing an intersection edge on a large B-spline
|
||||
// surface take seconds. A point the hinted projection misses is searched for as before.
|
||||
fn parameter_division(&self, range: (f64, f64), tol: f64) -> (Vec<f64>, Vec<Point3>) {
|
||||
// SolidCraft: a tolerance finer than truck's (or not a number) divides at truck's
|
||||
// instead of panicking: the points are only found to within it anyway. truck asserted
|
||||
// the tolerance, and a join whose caller asked for less failed on the panic (#4623).
|
||||
let tol = if tol >= TOLERANCE { tol } else { TOLERANCE };
|
||||
let start = self.hinted_triple(range.0, None);
|
||||
let end = self.hinted_triple(range.1, None);
|
||||
hinted_sub_division(self, range, (start, end), tol, 100)
|
||||
}
|
||||
}
|
||||
|
||||
// SolidCraft: a point of the curve with its parameters on both surfaces.
|
||||
type Triple = (Point3, Point2, Point2);
|
||||
|
||||
thread_local! {
|
||||
// SolidCraft: a point of an intersection curve projected onto neither surface since the last
|
||||
// `take_projection_failure` (the leader's point stood in for it).
|
||||
static PROJECTION_FAILED: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
|
||||
}
|
||||
|
||||
/// SolidCraft: whether `parameter_division` of an intersection curve met a point that projects
|
||||
/// onto neither surface on this thread since the last call, which clears it. Such a division
|
||||
/// holds the leader's points there and stops dividing: a Boolean gives up on the curve, as it
|
||||
/// did when the failure panicked, instead of carrying approximate points on (#5562).
|
||||
#[doc(hidden)]
|
||||
pub fn take_projection_failure() -> bool { PROJECTION_FAILED.with(|f| f.replace(false)) }
|
||||
|
||||
impl<C, S> IntersectionCurve<C, S>
|
||||
where
|
||||
C: ParametricCurve3D,
|
||||
S: ParametricSurface3D + SearchNearestParameter<D2, Point = Point3>,
|
||||
{
|
||||
// SolidCraft: `search_triple` started from `hint`'s parameters, falling back to the search
|
||||
// from nothing. Where that fails too, the leader's point stands in and the failure is
|
||||
// recorded (`take_projection_failure`): `subs` unwrapped that same failed search and
|
||||
// panicked, failing a revolve cut through a box's corner (#5562). A hinted result is taken
|
||||
// only within the curve's tolerance of the leader's point, where the leader lies: started
|
||||
// from a neighbour (or from a periodic surface's seam), the projection can settle on another
|
||||
// part of a surface, such as the far side of a small hole, which the search from nothing
|
||||
// would not.
|
||||
fn hinted_triple(&self, t: f64, hint: Option<Triple>) -> Triple {
|
||||
let lead = self.leader.subs(t);
|
||||
let near = self.tol.max(TOLERANCE);
|
||||
let hinted = hint.and_then(|(_, uv0, uv1)| {
|
||||
double_projection(
|
||||
self.surface0(),
|
||||
Some((uv0.x, uv0.y)),
|
||||
self.surface1(),
|
||||
Some((uv1.x, uv1.y)),
|
||||
lead,
|
||||
self.leader.der(t),
|
||||
100,
|
||||
)
|
||||
.filter(|(p, _, _)| p.distance(lead) <= near)
|
||||
});
|
||||
match hinted.or_else(|| self.search_triple(t)) {
|
||||
Some(triple) => triple,
|
||||
None => {
|
||||
PROJECTION_FAILED.with(|f| f.set(true));
|
||||
(lead, Point2::origin(), Point2::origin())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SolidCraft: `algo::curve::sub_parameter_division`, carrying each end's parameters.
|
||||
fn hinted_sub_division<C, S>(
|
||||
curve: &IntersectionCurve<C, S>,
|
||||
range: (f64, f64),
|
||||
ends: (Triple, Triple),
|
||||
tol: f64,
|
||||
trials: usize,
|
||||
) -> (Vec<f64>, Vec<Point3>)
|
||||
where
|
||||
C: ParametricCurve3D,
|
||||
S: ParametricSurface3D + SearchNearestParameter<D2, Point = Point3>,
|
||||
{
|
||||
let (p0, p1) = (ends.0 .0, ends.1 .0);
|
||||
let gen = p0.midpoint(p1);
|
||||
let p = 0.5 + (0.2 * HashGen::hash1(gen) - 0.1);
|
||||
let t = range.0 * (1.0 - p) + range.1 * p;
|
||||
let mid = p0 + (p1 - p0) * p;
|
||||
let dist2 = curve.hinted_triple(t, Some(ends.0)).0.distance2(mid);
|
||||
// SolidCraft: a point that projects nowhere ends the division (see `take_projection_failure`).
|
||||
let failed = PROJECTION_FAILED.with(std::cell::Cell::get);
|
||||
if dist2 < tol * tol || trials == 0 || failed {
|
||||
(vec![range.0, range.1], vec![p0, p1])
|
||||
} else {
|
||||
let mid_param = (range.0 + range.1) / 2.0;
|
||||
let mid_value = curve.hinted_triple(mid_param, Some(ends.0));
|
||||
let (mut params, mut pts) =
|
||||
hinted_sub_division(curve, (range.0, mid_param), (ends.0, mid_value), tol, trials - 1);
|
||||
let _ = (params.pop(), pts.pop());
|
||||
let (new_params, new_pts) =
|
||||
hinted_sub_division(curve, (mid_param, range.1), (mid_value, ends.1), tol, trials - 1);
|
||||
params.extend(new_params);
|
||||
pts.extend(new_pts);
|
||||
(params, pts)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, S> Cut for IntersectionCurve<C, S>
|
||||
where
|
||||
C: Cut<Point = Point3, Vector = Vector3>,
|
||||
S: ParametricSurface3D + SearchNearestParameter<D2, Point = Point3>,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn cut(&mut self, t: f64) -> Self {
|
||||
Self {
|
||||
surface0: self.surface0.clone(),
|
||||
surface1: self.surface1.clone(),
|
||||
leader: self.leader.cut(t),
|
||||
tol: self.tol,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Invertible, S: Clone> Invertible for IntersectionCurve<C, S> {
|
||||
fn invert(&mut self) { self.leader.invert(); }
|
||||
}
|
||||
|
||||
impl<C, S> SearchParameter<D1> for IntersectionCurve<C, S>
|
||||
where
|
||||
C: ParametricCurve3D + SearchNearestParameter<D1, Point = Point3>,
|
||||
S: ParametricSurface3D + SearchNearestParameter<D2, Point = Point3>,
|
||||
{
|
||||
type Point = Point3;
|
||||
fn search_parameter<H: Into<SPHint1D>>(
|
||||
&self,
|
||||
point: Point3,
|
||||
hint: H,
|
||||
trials: usize,
|
||||
) -> Option<f64> {
|
||||
let t = self
|
||||
.leader()
|
||||
.search_nearest_parameter(point, hint, trials)
|
||||
.unwrap();
|
||||
let pt = self.subs(t);
|
||||
match pt.near(&point) {
|
||||
true => Some(t),
|
||||
false => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Only derive from leading curve. Not precise.
|
||||
impl<C, S> SearchNearestParameter<D1> for IntersectionCurve<C, S>
|
||||
where
|
||||
C: ParametricCurve3D + SearchNearestParameter<D1, Point = Point3>,
|
||||
S: ParametricSurface3D + SearchNearestParameter<D2, Point = Point3>,
|
||||
{
|
||||
type Point = Point3;
|
||||
fn search_nearest_parameter<H: Into<SPHint1D>>(
|
||||
&self,
|
||||
point: Point3,
|
||||
hint: H,
|
||||
trials: usize,
|
||||
) -> Option<f64> {
|
||||
self.leader().search_nearest_parameter(point, hint, trials)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, S> Transformed<Matrix4> for IntersectionCurve<C, S>
|
||||
where
|
||||
C: Transformed<Matrix4>,
|
||||
S: Transformed<Matrix4>,
|
||||
{
|
||||
fn transform_by(&mut self, trans: Matrix4) {
|
||||
self.surface0.transform_by(trans);
|
||||
self.surface1.transform_by(trans);
|
||||
self.leader.transform_by(trans);
|
||||
self.tol *= trans.norm_l2();
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: BoundedCurve> IntersectionCurve<C, Plane> {
|
||||
/// Optimizes intersection curve of [`Plane`] into [`Line`].
|
||||
#[inline]
|
||||
pub fn optimize(&self) -> Line<C::Point> {
|
||||
let (s, t) = self.leader.range_tuple();
|
||||
Line(self.leader.subs(s), self.leader.subs(t))
|
||||
}
|
||||
}
|
||||
-149
@@ -1,149 +0,0 @@
|
||||
use crate::{prelude::*, *};
|
||||
use std::ops::{Deref, DerefMut, Mul};
|
||||
|
||||
/// revolution
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||||
struct Revolution {
|
||||
origin: Point3,
|
||||
axis: Vector3,
|
||||
}
|
||||
|
||||
/// surface constructed by revoluting a curve
|
||||
/// # Examples
|
||||
/// Revoluted sphere
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// use std::f64::consts::PI;
|
||||
/// let knot_vec = KnotVec::bezier_knot(2);
|
||||
/// let control_points = vec![
|
||||
/// Vector4::new(1.0, 0.0, 0.0, 1.0),
|
||||
/// Vector4::new(0.0, 1.0, 0.0, 0.0),
|
||||
/// Vector4::new(-1.0, 0.0, 0.0, 1.0),
|
||||
/// ];
|
||||
/// // upper half circle on xy-plane
|
||||
/// let uhcircle = NurbsCurve::new(BSplineCurve::new(knot_vec, control_points));
|
||||
/// // sphere constructed by revolute circle
|
||||
/// let sphere = RevolutedCurve::by_revolution(
|
||||
/// uhcircle, Point3::origin(), Vector3::unit_x(),
|
||||
/// );
|
||||
/// const N: usize = 30;
|
||||
/// for i in 0..=N {
|
||||
/// for j in 0..=N {
|
||||
/// let u = i as f64 / N as f64;
|
||||
/// let v = 2.0 * PI * j as f64 / N as f64;
|
||||
/// let pt: Vector3 = sphere.subs(u, v).to_vec();
|
||||
/// assert_near2!(pt.magnitude2(), 1.0);
|
||||
/// assert_near!(pt, sphere.normal(u, v));
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RevolutedCurve<C> {
|
||||
curve: C,
|
||||
revolution: Revolution,
|
||||
}
|
||||
|
||||
/// Linearly extruded curve
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct ExtrudedCurve<C, V> {
|
||||
curve: C,
|
||||
vector: V,
|
||||
}
|
||||
|
||||
/// invertible and transformable geometric element
|
||||
/// # Examples
|
||||
/// Curve processing example
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let curve: BSplineCurve<Point3> = BSplineCurve::new(
|
||||
/// KnotVec::bezier_knot(2),
|
||||
/// vec![
|
||||
/// Point3::new(0.0, 0.0, 0.0),
|
||||
/// Point3::new(0.0, 0.0, 1.0),
|
||||
/// Point3::new(1.0, 0.0, 0.0),
|
||||
/// ],
|
||||
/// );
|
||||
/// let mut processed = Processor::<_, Matrix4>::new(curve.clone());
|
||||
///
|
||||
/// // both curves are the same curve
|
||||
/// const N: usize = 100;
|
||||
/// for i in 0..=N {
|
||||
/// let t = i as f64 / N as f64;
|
||||
/// assert_eq!(curve.subs(t), processed.subs(t));
|
||||
/// }
|
||||
///
|
||||
/// // Processed curve can inverted!
|
||||
/// processed.invert();
|
||||
/// for i in 0..=N {
|
||||
/// let t = i as f64 / N as f64;
|
||||
/// assert_eq!(curve.subs(1.0 - t), processed.subs(t));
|
||||
/// }
|
||||
/// ```
|
||||
/// Surface processing example
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// use std::f64::consts::PI;
|
||||
///
|
||||
/// let sphere = Sphere::new(Point3::new(1.0, 2.0, 3.0), 2.45);
|
||||
/// let mut processed = Processor::<_, Matrix4>::new(sphere);
|
||||
///
|
||||
/// // both surfaces are the same surface
|
||||
/// const N: usize = 100;
|
||||
/// for i in 0..=N {
|
||||
/// for j in 0..=N {
|
||||
/// let u = PI * i as f64 / N as f64;
|
||||
/// let v = 2.0 * PI * j as f64 / N as f64;
|
||||
/// assert_eq!(sphere.subs(u, v), processed.subs(u, v));
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // Processed surface can be inverted!
|
||||
/// // Here, "invert surface" means swap (u, v)-axes.
|
||||
/// processed.invert();
|
||||
/// for i in 0..=N {
|
||||
/// for j in 0..=N {
|
||||
/// let u = PI * i as f64 / N as f64;
|
||||
/// let v = 2.0 * PI * j as f64 / N as f64;
|
||||
/// assert_eq!(sphere.subs(u, v), processed.subs(v, u));
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Processor<E, T> {
|
||||
entity: E,
|
||||
transform: T,
|
||||
orientation: bool,
|
||||
}
|
||||
|
||||
/// The composited maps
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct PCurve<C, S> {
|
||||
curve: C,
|
||||
surface: S,
|
||||
}
|
||||
|
||||
/// Intersection curve between two surfaces.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct IntersectionCurve<C, S> {
|
||||
// Considering rotational surfaces, we can consider the case
|
||||
// where the class `S` holds the curve `C` as a variable.
|
||||
surface0: Box<S>,
|
||||
surface1: Box<S>,
|
||||
leader: C,
|
||||
tol: f64,
|
||||
}
|
||||
|
||||
/// trimmed curve for parametric curve
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||
pub struct TrimmedCurve<C> {
|
||||
curve: C,
|
||||
range: (f64, f64),
|
||||
}
|
||||
|
||||
mod curve_on_surface;
|
||||
mod extruded_curve;
|
||||
mod intersection_curve;
|
||||
mod processor;
|
||||
mod revolved_curve;
|
||||
mod trimmied_curve;
|
||||
pub use intersection_curve::{double_projection, take_projection_failure};
|
||||
-658
@@ -1,658 +0,0 @@
|
||||
use super::*;
|
||||
|
||||
impl<E, T: One> Processor<E, T> {
|
||||
/// Creates new processor
|
||||
#[inline(always)]
|
||||
pub fn new(entity: E) -> Processor<E, T> {
|
||||
Processor {
|
||||
entity,
|
||||
transform: T::one(),
|
||||
orientation: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the reference of entity
|
||||
#[inline(always)]
|
||||
pub const fn entity(&self) -> &E { &self.entity }
|
||||
|
||||
/// Returns the reference of transform
|
||||
#[inline(always)]
|
||||
pub const fn transform(&self) -> &T { &self.transform }
|
||||
|
||||
/// Returns the orientation of surface
|
||||
#[inline(always)]
|
||||
pub const fn orientation(&self) -> bool { self.orientation }
|
||||
|
||||
#[inline(always)]
|
||||
fn sign(&self) -> f64 {
|
||||
match self.orientation {
|
||||
true => 1.0,
|
||||
false => -1.0,
|
||||
}
|
||||
}
|
||||
|
||||
/// apply the function to the entity geometry
|
||||
#[inline(always)]
|
||||
pub fn map<G, F: FnOnce(E) -> G>(self, f: F) -> Processor<G, T> {
|
||||
Processor {
|
||||
entity: f(self.entity),
|
||||
transform: self.transform,
|
||||
orientation: self.orientation,
|
||||
}
|
||||
}
|
||||
|
||||
/// apply the function to the entity geometry
|
||||
#[inline(always)]
|
||||
pub fn map_ref<G, F: FnOnce(&E) -> G>(&self, f: F) -> Processor<G, T>
|
||||
where T: Copy {
|
||||
Processor {
|
||||
entity: f(&self.entity),
|
||||
transform: self.transform,
|
||||
orientation: self.orientation,
|
||||
}
|
||||
}
|
||||
|
||||
/// apply the transform and inverse
|
||||
pub fn constract(self) -> E
|
||||
where E: Transformed<T> + Invertible {
|
||||
let mut res = self.entity;
|
||||
res.transform_by(self.transform);
|
||||
if !self.orientation {
|
||||
res.invert();
|
||||
}
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Clone, T: Clone> Invertible for Processor<E, T> {
|
||||
#[inline(always)]
|
||||
fn invert(&mut self) { self.orientation = !self.orientation; }
|
||||
#[inline(always)]
|
||||
fn inverse(&self) -> Self {
|
||||
Processor {
|
||||
entity: self.entity.clone(),
|
||||
transform: self.transform.clone(),
|
||||
orientation: !self.orientation,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: BoundedCurve, T> Processor<C, T> {
|
||||
#[inline(always)]
|
||||
fn get_curve_parameter(&self, t: f64) -> f64 {
|
||||
let (t0, t1) = self.range_tuple();
|
||||
match self.orientation {
|
||||
true => t,
|
||||
false => t0 + t1 - t,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, T> ParametricCurve for Processor<C, T>
|
||||
where
|
||||
C: BoundedCurve,
|
||||
C::Point: EuclideanSpace<Diff = C::Vector>,
|
||||
C::Vector: VectorSpace<Scalar = f64>,
|
||||
T: Transform<C::Point> + Clone,
|
||||
{
|
||||
type Point = C::Point;
|
||||
type Vector = C::Vector;
|
||||
#[inline(always)]
|
||||
fn subs(&self, t: f64) -> C::Point {
|
||||
let t = self.get_curve_parameter(t);
|
||||
self.transform.transform_point(self.entity.subs(t))
|
||||
}
|
||||
#[inline(always)]
|
||||
fn der(&self, t: f64) -> Self::Vector {
|
||||
let t = self.get_curve_parameter(t);
|
||||
self.transform.transform_vector(self.entity.der(t)) * self.sign()
|
||||
}
|
||||
#[inline(always)]
|
||||
fn der2(&self, t: f64) -> Self::Vector {
|
||||
let t = self.get_curve_parameter(t);
|
||||
self.transform.transform_vector(self.entity.der2(t))
|
||||
}
|
||||
#[inline(always)]
|
||||
fn parameter_range(&self) -> ParameterRange { self.entity.parameter_range() }
|
||||
#[inline(always)]
|
||||
fn period(&self) -> Option<f64> { self.entity.period() }
|
||||
}
|
||||
|
||||
impl<C, T> BoundedCurve for Processor<C, T>
|
||||
where
|
||||
C: BoundedCurve,
|
||||
C::Point: EuclideanSpace<Diff = C::Vector>,
|
||||
C::Vector: VectorSpace<Scalar = f64>,
|
||||
T: Transform<C::Point> + Clone,
|
||||
{
|
||||
}
|
||||
|
||||
impl<C, T> Cut for Processor<C, T>
|
||||
where
|
||||
C: BoundedCurve + Cut,
|
||||
C::Point: EuclideanSpace<Diff = C::Vector>,
|
||||
C::Vector: VectorSpace<Scalar = f64>,
|
||||
T: Transform<C::Point> + Clone,
|
||||
{
|
||||
fn cut(&mut self, t: f64) -> Self {
|
||||
let t = self.get_curve_parameter(t);
|
||||
let mut entity = self.entity.cut(t);
|
||||
if !self.orientation {
|
||||
std::mem::swap(&mut entity, &mut self.entity);
|
||||
}
|
||||
Self {
|
||||
entity,
|
||||
transform: self.transform.clone(),
|
||||
orientation: self.orientation,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, T> ParametricSurface for Processor<S, T>
|
||||
where
|
||||
S: ParametricSurface,
|
||||
S::Point: EuclideanSpace<Scalar = f64, Diff = S::Vector>,
|
||||
T: Transform<S::Point> + SquareMatrix<Scalar = f64> + Clone,
|
||||
{
|
||||
type Point = S::Point;
|
||||
type Vector = S::Vector;
|
||||
#[inline(always)]
|
||||
fn subs(&self, u: f64, v: f64) -> Self::Point {
|
||||
match self.orientation {
|
||||
true => self.transform.transform_point(self.entity.subs(u, v)),
|
||||
false => self.transform.transform_point(self.entity.subs(v, u)),
|
||||
}
|
||||
}
|
||||
#[inline(always)]
|
||||
fn uder(&self, u: f64, v: f64) -> Self::Vector {
|
||||
match self.orientation {
|
||||
true => self.transform.transform_vector(self.entity.uder(u, v)),
|
||||
false => self.transform.transform_vector(self.entity.vder(v, u)),
|
||||
}
|
||||
}
|
||||
#[inline(always)]
|
||||
fn vder(&self, u: f64, v: f64) -> Self::Vector {
|
||||
match self.orientation {
|
||||
true => self.transform.transform_vector(self.entity.vder(u, v)),
|
||||
false => self.transform.transform_vector(self.entity.uder(v, u)),
|
||||
}
|
||||
}
|
||||
#[inline(always)]
|
||||
fn uuder(&self, u: f64, v: f64) -> Self::Vector {
|
||||
match self.orientation {
|
||||
true => self.transform.transform_vector(self.entity.uuder(u, v)),
|
||||
false => self.transform.transform_vector(self.entity.vvder(v, u)),
|
||||
}
|
||||
}
|
||||
#[inline(always)]
|
||||
fn uvder(&self, u: f64, v: f64) -> Self::Vector {
|
||||
match self.orientation {
|
||||
true => self.transform.transform_vector(self.entity.uvder(u, v)),
|
||||
false => self.transform.transform_vector(self.entity.uvder(v, u)),
|
||||
}
|
||||
}
|
||||
#[inline(always)]
|
||||
fn vvder(&self, u: f64, v: f64) -> Self::Vector {
|
||||
match self.orientation {
|
||||
true => self.transform.transform_vector(self.entity.vvder(u, v)),
|
||||
false => self.transform.transform_vector(self.entity.uuder(v, u)),
|
||||
}
|
||||
}
|
||||
#[inline(always)]
|
||||
fn parameter_range(&self) -> (ParameterRange, ParameterRange) {
|
||||
let (urange, vrange) = self.entity.parameter_range();
|
||||
match self.orientation {
|
||||
true => (urange, vrange),
|
||||
false => (vrange, urange),
|
||||
}
|
||||
}
|
||||
#[inline(always)]
|
||||
fn u_period(&self) -> Option<f64> {
|
||||
match self.orientation {
|
||||
true => self.entity.u_period(),
|
||||
false => self.entity.v_period(),
|
||||
}
|
||||
}
|
||||
#[inline(always)]
|
||||
fn v_period(&self) -> Option<f64> {
|
||||
match self.orientation {
|
||||
true => self.entity.v_period(),
|
||||
false => self.entity.u_period(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, T> ParametricSurface3D for Processor<S, T>
|
||||
where
|
||||
S: ParametricSurface3D,
|
||||
T: Transform<Point3> + SquareMatrix<Scalar = f64> + Clone,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn normal(&self, u: f64, v: f64) -> Self::Vector {
|
||||
let transtrans = self.transform.transpose();
|
||||
let n = match self.orientation {
|
||||
true => self.entity.normal(u, v),
|
||||
false => -self.entity.normal(v, u),
|
||||
};
|
||||
let n = transtrans
|
||||
.inverse_transform_vector(n)
|
||||
.expect("invalid transform");
|
||||
(n / self.transform.determinant()).normalize()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, T> BoundedSurface for Processor<S, T>
|
||||
where
|
||||
S: BoundedSurface<Point = Point3, Vector = Vector3>,
|
||||
T: Transform<S::Point> + SquareMatrix<Scalar = f64> + Clone,
|
||||
{
|
||||
}
|
||||
|
||||
impl<E, T> Deref for Processor<E, T> {
|
||||
type Target = E;
|
||||
#[inline(always)]
|
||||
fn deref(&self) -> &E { &self.entity }
|
||||
}
|
||||
|
||||
impl<E, T> DerefMut for Processor<E, T> {
|
||||
#[inline(always)]
|
||||
fn deref_mut(&mut self) -> &mut E { &mut self.entity }
|
||||
}
|
||||
|
||||
impl<E, T> Transformed<T> for Processor<E, T>
|
||||
where
|
||||
T: Mul<T, Output = T> + Copy,
|
||||
E: Clone,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn transform_by(&mut self, trans: T) { self.transform = trans * self.transform; }
|
||||
#[inline(always)]
|
||||
fn transformed(&self, trans: T) -> Self {
|
||||
Self {
|
||||
entity: self.entity.clone(),
|
||||
transform: trans * self.transform,
|
||||
orientation: self.orientation,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, T, C> IncludeCurve<C> for Processor<E, T>
|
||||
where
|
||||
C: ParametricCurve + Transformed<T> + Clone,
|
||||
C::Point: EuclideanSpace,
|
||||
E: IncludeCurve<C>,
|
||||
T: Transform<C::Point>,
|
||||
{
|
||||
fn include(&self, curve: &C) -> bool {
|
||||
let inv = self
|
||||
.transform
|
||||
.inverse_transform()
|
||||
.expect("irregular transform");
|
||||
let curve = curve.clone().transformed(inv);
|
||||
self.entity.include(&curve)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> ParameterDivision1D for Processor<C, Matrix3>
|
||||
where C: ParameterDivision1D<Point = Point2> + BoundedCurve<Point = Point2>
|
||||
{
|
||||
type Point = Point2;
|
||||
fn parameter_division(&self, range: (f64, f64), tol: f64) -> (Vec<f64>, Vec<Self::Point>) {
|
||||
let a = self.transform;
|
||||
let range = match self.orientation {
|
||||
true => range,
|
||||
false => (
|
||||
self.get_curve_parameter(range.1),
|
||||
self.get_curve_parameter(range.0),
|
||||
),
|
||||
};
|
||||
let (_, k, _) = a
|
||||
.iwasawa_decomposition()
|
||||
.expect("transform matrix must be invertible!");
|
||||
let n = f64::abs(k[0][0])
|
||||
.max(f64::abs(k[1][1]))
|
||||
.max(f64::abs(k[2][2]));
|
||||
let (mut params, mut points) = self.entity.parameter_division(range, tol / n);
|
||||
points
|
||||
.iter_mut()
|
||||
.for_each(|pt| *pt = a.transform_point(*pt));
|
||||
if !self.orientation {
|
||||
params
|
||||
.iter_mut()
|
||||
.for_each(|t| *t = self.get_curve_parameter(*t));
|
||||
points.reverse();
|
||||
}
|
||||
(params, points)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> ParameterDivision1D for Processor<C, Matrix4>
|
||||
where C: ParameterDivision1D<Point = Point3> + BoundedCurve<Point = Point3>
|
||||
{
|
||||
type Point = Point3;
|
||||
fn parameter_division(&self, range: (f64, f64), tol: f64) -> (Vec<f64>, Vec<Self::Point>) {
|
||||
let a = self.transform;
|
||||
let range = match self.orientation {
|
||||
true => range,
|
||||
false => (
|
||||
self.get_curve_parameter(range.1),
|
||||
self.get_curve_parameter(range.0),
|
||||
),
|
||||
};
|
||||
let (_, k, _) = a
|
||||
.iwasawa_decomposition()
|
||||
.expect("transform matrix must be invertible!");
|
||||
let n = f64::abs(k[0][0])
|
||||
.max(f64::abs(k[1][1]))
|
||||
.max(f64::abs(k[2][2]))
|
||||
/ f64::abs(k[3][3]);
|
||||
let (mut params, mut points) = self.entity.parameter_division(range, tol / n);
|
||||
points
|
||||
.iter_mut()
|
||||
.for_each(|pt| *pt = a.transform_point(*pt));
|
||||
if !self.orientation {
|
||||
params
|
||||
.iter_mut()
|
||||
.for_each(|t| *t = self.get_curve_parameter(*t));
|
||||
points.reverse();
|
||||
}
|
||||
(params, points)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: ParameterDivision2D> ParameterDivision2D for Processor<S, Matrix3> {
|
||||
fn parameter_division(
|
||||
&self,
|
||||
range: ((f64, f64), (f64, f64)),
|
||||
tol: f64,
|
||||
) -> (Vec<f64>, Vec<f64>) {
|
||||
let a = self.transform;
|
||||
let range = match self.orientation {
|
||||
true => range,
|
||||
false => (range.1, range.0),
|
||||
};
|
||||
let (_, k, _) = a
|
||||
.iwasawa_decomposition()
|
||||
.expect("transform matrix must be invertible!");
|
||||
let n = f64::abs(k[0][0])
|
||||
.max(f64::abs(k[1][1]))
|
||||
.max(f64::abs(k[2][2]));
|
||||
let (udiv, vdiv) = self.entity.parameter_division(range, tol / n);
|
||||
match self.orientation {
|
||||
true => (udiv, vdiv),
|
||||
false => (vdiv, udiv),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: ParameterDivision2D> ParameterDivision2D for Processor<S, Matrix4> {
|
||||
fn parameter_division(
|
||||
&self,
|
||||
range: ((f64, f64), (f64, f64)),
|
||||
tol: f64,
|
||||
) -> (Vec<f64>, Vec<f64>) {
|
||||
let a = self.transform;
|
||||
let range = match self.orientation {
|
||||
true => range,
|
||||
false => (range.1, range.0),
|
||||
};
|
||||
let (_, k, _) = a
|
||||
.iwasawa_decomposition()
|
||||
.expect("transform matrix must be invertible!");
|
||||
let n = f64::abs(k[0][0])
|
||||
.max(f64::abs(k[1][1]))
|
||||
.max(f64::abs(k[2][2]))
|
||||
/ f64::abs(k[3][3]);
|
||||
let (udiv, vdiv) = self.entity.parameter_division(range, tol / n);
|
||||
match self.orientation {
|
||||
true => (udiv, vdiv),
|
||||
false => (vdiv, udiv),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, T> SearchParameter<D1> for Processor<E, T>
|
||||
where
|
||||
E: SearchParameter<D1> + BoundedCurve,
|
||||
<E as SearchParameter<D1>>::Point: EuclideanSpace,
|
||||
T: Transform<<E as SearchParameter<D1>>::Point>,
|
||||
{
|
||||
type Point = <E as SearchParameter<D1>>::Point;
|
||||
fn search_parameter<H: Into<SPHint1D>>(
|
||||
&self,
|
||||
point: <E as SearchParameter<D1>>::Point,
|
||||
hint: H,
|
||||
trials: usize,
|
||||
) -> Option<f64> {
|
||||
let inv = self.transform.inverse_transform().unwrap();
|
||||
let t = self
|
||||
.entity
|
||||
.search_parameter(inv.transform_point(point), hint, trials)?;
|
||||
Some(self.get_curve_parameter(t))
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, T> SearchParameter<D2> for Processor<E, T>
|
||||
where
|
||||
E: SearchParameter<D2>,
|
||||
E::Point: EuclideanSpace,
|
||||
T: Transform<E::Point>,
|
||||
{
|
||||
type Point = E::Point;
|
||||
fn search_parameter<H: Into<SPHint2D>>(
|
||||
&self,
|
||||
point: E::Point,
|
||||
hint: H,
|
||||
trials: usize,
|
||||
) -> Option<(f64, f64)> {
|
||||
let inv = self.transform.inverse_transform().unwrap();
|
||||
let (u, v) = self
|
||||
.entity
|
||||
.search_parameter(inv.transform_point(point), hint, trials)?;
|
||||
match self.orientation {
|
||||
true => Some((u, v)),
|
||||
false => Some((v, u)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, E, T> SearchNearestParameter<D1> for Processor<E, T>
|
||||
where
|
||||
E: BoundedCurve<Point = P> + SearchNearestParameter<D1, Point = P>,
|
||||
P: EuclideanSpace<Scalar = f64, Diff = E::Vector>,
|
||||
E::Vector: InnerSpace<Scalar = f64> + Tolerance,
|
||||
T: Transform<P> + Clone,
|
||||
{
|
||||
type Point = P;
|
||||
fn search_nearest_parameter<H: Into<SPHint1D>>(
|
||||
&self,
|
||||
point: Self::Point,
|
||||
hint: H,
|
||||
trials: usize,
|
||||
) -> Option<f64> {
|
||||
let inv = self.transform.inverse_transform().unwrap();
|
||||
let hint =
|
||||
self.entity
|
||||
.search_nearest_parameter(inv.transform_point(point), hint, trials)?;
|
||||
let hint = self.get_curve_parameter(hint);
|
||||
algo::curve::search_nearest_parameter(self, point, hint, trials)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, E, T> SearchNearestParameter<D2> for Processor<E, T>
|
||||
where
|
||||
E: ParametricSurface<Point = P> + SearchNearestParameter<D2, Point = P>,
|
||||
P: EuclideanSpace<Scalar = f64, Diff = E::Vector>,
|
||||
E::Vector: InnerSpace<Scalar = f64> + Tolerance,
|
||||
T: Transform<P> + SquareMatrix<Scalar = f64> + Clone,
|
||||
{
|
||||
type Point = P;
|
||||
fn search_nearest_parameter<H: Into<SPHint2D>>(
|
||||
&self,
|
||||
point: Self::Point,
|
||||
hint: H,
|
||||
trials: usize,
|
||||
) -> Option<(f64, f64)> {
|
||||
let inv = self.transform.inverse_transform().unwrap();
|
||||
let hint =
|
||||
self.entity
|
||||
.search_nearest_parameter(inv.transform_point(point), hint, trials)?;
|
||||
let hint = match self.orientation {
|
||||
true => hint,
|
||||
false => (hint.1, hint.0),
|
||||
};
|
||||
algo::surface::search_nearest_parameter(self, point, hint, trials)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn exec_compatible_with_bspcurve() {
|
||||
const DEGREE: usize = 3;
|
||||
const DIVISION: usize = 4;
|
||||
let knot_vec = KnotVec::uniform_knot(DEGREE, DIVISION);
|
||||
let control_points: Vec<Point3> = (0..DEGREE + DIVISION)
|
||||
.map(|i| Point3::new(i as f64, 20.0 * rand::random::<f64>() - 10.0, 0.0))
|
||||
.collect();
|
||||
let mut curve = BSplineCurve::new(knot_vec, control_points);
|
||||
let mut processor = Processor::new(curve.clone());
|
||||
let mat = Matrix3::new(
|
||||
4.0 * rand::random::<f64>() - 2.0,
|
||||
4.0 * rand::random::<f64>() - 2.0,
|
||||
4.0 * rand::random::<f64>() - 2.0,
|
||||
4.0 * rand::random::<f64>() - 2.0,
|
||||
4.0 * rand::random::<f64>() - 2.0,
|
||||
4.0 * rand::random::<f64>() - 2.0,
|
||||
4.0 * rand::random::<f64>() - 2.0,
|
||||
4.0 * rand::random::<f64>() - 2.0,
|
||||
4.0 * rand::random::<f64>() - 2.0,
|
||||
);
|
||||
if mat.determinant().so_small() {
|
||||
println!("omitted: {mat:?}");
|
||||
return;
|
||||
}
|
||||
curve.transform_by(mat);
|
||||
processor.transform_by(mat);
|
||||
assert_eq!(curve.parameter_range(), processor.parameter_range());
|
||||
|
||||
const N: usize = 100;
|
||||
for i in 0..=N {
|
||||
let t = i as f64 / N as f64;
|
||||
assert_near!(ParametricCurve::subs(&curve, t), processor.subs(t));
|
||||
assert_near!(ParametricCurve::der(&curve, t), processor.der(t));
|
||||
assert_near!(ParametricCurve::der2(&curve, t), processor.der2(t));
|
||||
}
|
||||
|
||||
curve.invert();
|
||||
processor.invert();
|
||||
assert_eq!(curve.parameter_range(), processor.parameter_range());
|
||||
for i in 0..=N {
|
||||
let t = i as f64 / N as f64;
|
||||
assert_near!(ParametricCurve::subs(&curve, t), processor.subs(t));
|
||||
assert_near!(ParametricCurve::der(&curve, t), processor.der(t));
|
||||
assert_near!(ParametricCurve::der2(&curve, t), processor.der2(t));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compatible_with_bspcurve() { (0..10).for_each(|_| exec_compatible_with_bspcurve()) }
|
||||
|
||||
fn exec_compatible_with_bspsurface() {
|
||||
const DEGREE: usize = 3;
|
||||
const DIVISION: usize = 4;
|
||||
let knot_vec = KnotVec::uniform_knot(DEGREE, DIVISION);
|
||||
let knot_vecs = (knot_vec.clone(), knot_vec);
|
||||
let control_points: Vec<Vec<Point3>> = (0..DEGREE + DIVISION)
|
||||
.map(|i| {
|
||||
(0..DEGREE + DIVISION)
|
||||
.map(|j| Point3::new(i as f64, j as f64, 20.0 * rand::random::<f64>() - 10.0))
|
||||
.collect()
|
||||
})
|
||||
.collect();
|
||||
let mut surface = BSplineSurface::new(knot_vecs, control_points);
|
||||
let mut processor = Processor::new(surface.clone());
|
||||
let mat = Matrix3::new(
|
||||
4.0 * rand::random::<f64>() - 2.0,
|
||||
4.0 * rand::random::<f64>() - 2.0,
|
||||
4.0 * rand::random::<f64>() - 2.0,
|
||||
4.0 * rand::random::<f64>() - 2.0,
|
||||
4.0 * rand::random::<f64>() - 2.0,
|
||||
4.0 * rand::random::<f64>() - 2.0,
|
||||
4.0 * rand::random::<f64>() - 2.0,
|
||||
4.0 * rand::random::<f64>() - 2.0,
|
||||
4.0 * rand::random::<f64>() - 2.0,
|
||||
);
|
||||
if mat.determinant().so_small() {
|
||||
println!("omitted: {mat:?}");
|
||||
return;
|
||||
}
|
||||
surface.transform_by(mat);
|
||||
processor.transform_by(mat);
|
||||
assert_eq!(surface.range_tuple(), processor.range_tuple());
|
||||
|
||||
const N: usize = 30;
|
||||
for i in 0..=N {
|
||||
for j in 0..=N {
|
||||
let u = i as f64 / N as f64;
|
||||
let v = j as f64 / N as f64;
|
||||
let pt0 = ParametricSurface::subs(&surface, u, v);
|
||||
let pt1 = processor.subs(u, v);
|
||||
assert_near!(pt0, pt1);
|
||||
let uder0 = surface.uder(u, v);
|
||||
let uder1 = processor.uder(u, v);
|
||||
assert_near!(uder0, uder1);
|
||||
let vder0 = surface.vder(u, v);
|
||||
let vder1 = processor.vder(u, v);
|
||||
assert_near!(vder0, vder1);
|
||||
let uuder0 = surface.uuder(u, v);
|
||||
let uuder1 = processor.uuder(u, v);
|
||||
assert_near!(uuder0, uuder1);
|
||||
let uvder0 = surface.uvder(u, v);
|
||||
let uvder1 = processor.uvder(u, v);
|
||||
assert_near!(uvder0, uvder1);
|
||||
let vvder0 = surface.vvder(u, v);
|
||||
let vvder1 = processor.vvder(u, v);
|
||||
assert_near!(vvder0, vvder1);
|
||||
let n0 = surface.normal(u, v);
|
||||
let n1 = processor.normal(u, v);
|
||||
assert_near!(n0, n1);
|
||||
}
|
||||
}
|
||||
|
||||
surface.swap_axes();
|
||||
processor.invert();
|
||||
assert_eq!(surface.range_tuple(), processor.range_tuple());
|
||||
for i in 0..=N {
|
||||
for j in 0..=N {
|
||||
let u = i as f64 / N as f64;
|
||||
let v = j as f64 / N as f64;
|
||||
let pt0 = ParametricSurface::subs(&surface, u, v);
|
||||
let pt1 = processor.subs(u, v);
|
||||
assert_near!(pt0, pt1);
|
||||
let uder0 = surface.uder(u, v);
|
||||
let uder1 = processor.uder(u, v);
|
||||
assert_near!(uder0, uder1);
|
||||
let vder0 = surface.vder(u, v);
|
||||
let vder1 = processor.vder(u, v);
|
||||
assert_near!(vder0, vder1);
|
||||
let uuder0 = surface.uuder(u, v);
|
||||
let uuder1 = processor.uuder(u, v);
|
||||
assert_near!(uuder0, uuder1);
|
||||
let uvder0 = surface.uvder(u, v);
|
||||
let uvder1 = processor.uvder(u, v);
|
||||
assert_near!(uvder0, uvder1);
|
||||
let vvder0 = surface.vvder(u, v);
|
||||
let vvder1 = processor.vvder(u, v);
|
||||
assert_near!(vvder0, vvder1);
|
||||
let n0 = surface.normal(u, v);
|
||||
let n1 = processor.normal(u, v);
|
||||
assert_near!(n0, n1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compatible_with_bspsurface() { (0..3).for_each(|_| exec_compatible_with_bspsurface()) }
|
||||
}
|
||||
@@ -1,713 +0,0 @@
|
||||
use super::*;
|
||||
use std::f64::consts::PI;
|
||||
|
||||
impl Revolution {
|
||||
fn new(origin: Point3, axis: Vector3) -> Self {
|
||||
Self {
|
||||
origin,
|
||||
axis: axis.normalize(),
|
||||
}
|
||||
}
|
||||
#[inline(always)]
|
||||
fn point_rotation_matrix(self, v: f64) -> Matrix4 {
|
||||
Matrix4::from_translation(self.origin.to_vec())
|
||||
* Matrix4::from_axis_angle(self.axis, Rad(v))
|
||||
* Matrix4::from_translation(-self.origin.to_vec())
|
||||
}
|
||||
#[inline(always)]
|
||||
fn vector_rotation_matrix(self, v: f64) -> Matrix4 {
|
||||
Matrix4::from_axis_angle(self.axis, Rad(v))
|
||||
}
|
||||
#[inline(always)]
|
||||
fn derivation_rotation_matrix(self, v: f64) -> Matrix4 {
|
||||
let n = self.axis;
|
||||
Matrix3::new(
|
||||
n[0] * n[0] * f64::sin(v) - f64::sin(v),
|
||||
n[0] * n[1] * f64::sin(v) + n[2] * f64::cos(v),
|
||||
n[0] * n[2] * f64::sin(v) - n[1] * f64::cos(v),
|
||||
n[0] * n[1] * f64::sin(v) - n[2] * f64::cos(v),
|
||||
n[1] * n[1] * f64::sin(v) - f64::sin(v),
|
||||
n[1] * n[2] * f64::sin(v) + n[0] * f64::cos(v),
|
||||
n[0] * n[2] * f64::sin(v) + n[1] * f64::cos(v),
|
||||
n[1] * n[2] * f64::sin(v) - n[0] * f64::cos(v),
|
||||
n[2] * n[2] * f64::sin(v) - f64::sin(v),
|
||||
)
|
||||
.into()
|
||||
}
|
||||
#[inline(always)]
|
||||
fn invert(&mut self) { self.axis *= -1.0; }
|
||||
#[inline(always)]
|
||||
fn inverse(mut self) -> Self {
|
||||
self.axis *= -1.0;
|
||||
self
|
||||
}
|
||||
#[inline(always)]
|
||||
fn contains(self, p: Point3) -> bool { (p - self.origin).cross(self.axis).so_small() }
|
||||
#[inline(always)]
|
||||
fn proj_point(&self, p: Point3) -> Point2 {
|
||||
let r = p - self.origin;
|
||||
let z = r.dot(self.axis);
|
||||
let h = r - z * self.axis;
|
||||
Point2::new(z, h.magnitude())
|
||||
}
|
||||
#[inline(always)]
|
||||
fn proj_vector(&self, p: Point3, v: Vector3) -> Vector2 {
|
||||
let r = self.proj_point(p).y;
|
||||
let vz = v.dot(self.axis);
|
||||
let vxy = v - vz * self.axis;
|
||||
let vq = (p - self.origin).dot(vxy) / r;
|
||||
Vector2::new(vz, vq)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn proj_vector2(&self, p: Point3, v: Vector3, v2: Vector3) -> Vector2 {
|
||||
let r = self.proj_point(p).y;
|
||||
let v2z = v2.dot(self.axis);
|
||||
let v2xy = v2 - v2z * self.axis;
|
||||
let vz = v.dot(self.axis);
|
||||
let vxy = v - vz * self.axis;
|
||||
let a = (vxy.dot(vxy) + (p - self.origin).dot(v2xy)) / r;
|
||||
let b = f64::powi((p - self.origin).dot(vxy), 2) / (r * r * r);
|
||||
Vector2::new(v2z, a - b)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn proj_angle(&self, p: Point3, q: Point3) -> f64 {
|
||||
let (p, q) = (p - self.origin, q - self.origin);
|
||||
let hp = (p - p.dot(self.axis) * self.axis).normalize();
|
||||
let hq = (q - q.dot(self.axis) * self.axis).normalize();
|
||||
let t = f64::acos(f64::clamp(hp.dot(hq), -1.0, 1.0));
|
||||
match hp.cross(hq).dot(self.axis) < 0.0 {
|
||||
false => t,
|
||||
true => 2.0 * PI - t,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// SolidCraft: the angle of a point on the seam, where 0 and 2π are the same place, taken at the end
|
||||
/// of the period nearer the hint's angle. [`Revolution::proj_angle`] decides it by the sign of a cross
|
||||
/// product that is rounding noise there, so a boundary followed point by point could come back to
|
||||
/// the seam at the other end of the period from where it left, and its parameter polygon folded
|
||||
/// over itself (a whole turn of a cylinder had no area, and no intersection loop lay inside it).
|
||||
fn seam_side(angle: f64, hint: Option<f64>) -> f64 {
|
||||
const SEAM: f64 = 1.0e-7;
|
||||
match hint {
|
||||
Some(h) if angle < SEAM && h > PI => angle + 2.0 * PI,
|
||||
Some(h) if angle > 2.0 * PI - SEAM && h < PI => angle - 2.0 * PI,
|
||||
_ => angle,
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: ParametricCurve3D> ParametricSurface for RevolutedCurve<C> {
|
||||
type Point = Point3;
|
||||
type Vector = Vector3;
|
||||
#[inline(always)]
|
||||
fn subs(&self, u: f64, v: f64) -> Point3 {
|
||||
let mat = self.revolution.point_rotation_matrix(v);
|
||||
mat.transform_point(self.curve.subs(u))
|
||||
}
|
||||
#[inline(always)]
|
||||
fn uder(&self, u: f64, v: f64) -> Vector3 {
|
||||
let mat = self.revolution.vector_rotation_matrix(v);
|
||||
mat.transform_vector(self.curve.der(u))
|
||||
}
|
||||
#[inline(always)]
|
||||
fn vder(&self, u: f64, v: f64) -> Vector3 {
|
||||
let pt = self.curve.subs(u);
|
||||
let radius = self.axis().cross(pt - self.origin());
|
||||
let mat = self.revolution.vector_rotation_matrix(v);
|
||||
mat.transform_vector(radius)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn uuder(&self, u: f64, v: f64) -> Vector3 {
|
||||
let mat = self.revolution.vector_rotation_matrix(v);
|
||||
mat.transform_vector(self.curve.der2(u))
|
||||
}
|
||||
#[inline(always)]
|
||||
fn vvder(&self, u: f64, v: f64) -> Vector3 {
|
||||
let pt = self.curve.subs(u);
|
||||
let z = self.revolution.proj_point(pt).x;
|
||||
let radius = pt - self.origin() - z * self.axis();
|
||||
let mat = self.revolution.vector_rotation_matrix(v);
|
||||
-mat.transform_vector(radius)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn uvder(&self, u: f64, v: f64) -> Vector3 {
|
||||
let mat = self.revolution.derivation_rotation_matrix(v);
|
||||
mat.transform_vector(self.curve.der(u))
|
||||
}
|
||||
#[inline(always)]
|
||||
fn parameter_range(&self) -> (ParameterRange, ParameterRange) {
|
||||
(
|
||||
self.curve.parameter_range(),
|
||||
(Bound::Included(0.0), Bound::Excluded(2.0 * PI)),
|
||||
)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn u_period(&self) -> Option<f64> { self.curve.period() }
|
||||
#[inline(always)]
|
||||
fn v_period(&self) -> Option<f64> { Some(2.0 * PI) }
|
||||
}
|
||||
|
||||
impl<C: ParametricCurve3D + BoundedCurve> ParametricSurface3D for RevolutedCurve<C> {
|
||||
#[inline(always)]
|
||||
fn normal(&self, u: f64, v: f64) -> Vector3 {
|
||||
let (u0, u1) = self.curve.range_tuple();
|
||||
let (uder, vder) = if u.near(&u0) {
|
||||
let pt = self.curve.subs(u);
|
||||
let radius = self.axis().cross(pt - self.origin());
|
||||
if radius.so_small() {
|
||||
let uder = self.curve.der(u);
|
||||
(uder, self.axis().cross(uder))
|
||||
} else {
|
||||
(self.uder(u, v), self.vder(u, v))
|
||||
}
|
||||
} else if u.near(&u1) {
|
||||
let pt = self.curve.subs(u);
|
||||
let radius = self.axis().cross(pt - self.origin());
|
||||
if radius.so_small() {
|
||||
let uder = self.curve.der(u);
|
||||
(uder, uder.cross(self.axis()))
|
||||
} else {
|
||||
(self.uder(u, v), self.vder(u, v))
|
||||
}
|
||||
} else {
|
||||
(self.uder(u, v), self.vder(u, v))
|
||||
};
|
||||
uder.cross(vder).normalize()
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: ParametricCurve3D + BoundedCurve> BoundedSurface for RevolutedCurve<C> {}
|
||||
|
||||
impl<C: Clone> Invertible for RevolutedCurve<C> {
|
||||
#[inline(always)]
|
||||
fn invert(&mut self) { self.revolution.invert() }
|
||||
#[inline(always)]
|
||||
fn inverse(&self) -> Self {
|
||||
RevolutedCurve {
|
||||
curve: self.curve.clone(),
|
||||
revolution: self.revolution.inverse(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct ProjectedCurve<C> {
|
||||
curve: C,
|
||||
revolution: Revolution,
|
||||
}
|
||||
|
||||
impl<C: ParametricCurve3D> ParametricCurve for ProjectedCurve<C> {
|
||||
type Point = Point2;
|
||||
type Vector = Vector2;
|
||||
#[inline(always)]
|
||||
fn subs(&self, t: f64) -> Self::Point { self.revolution.proj_point(self.curve.subs(t)) }
|
||||
#[inline(always)]
|
||||
fn der(&self, t: f64) -> Self::Vector {
|
||||
self.revolution
|
||||
.proj_vector(self.curve.subs(t), self.curve.der(t))
|
||||
}
|
||||
#[inline(always)]
|
||||
fn der2(&self, t: f64) -> Self::Vector {
|
||||
self.revolution
|
||||
.proj_vector2(self.curve.subs(t), self.curve.der(t), self.curve.der2(t))
|
||||
}
|
||||
#[inline(always)]
|
||||
fn parameter_range(&self) -> ParameterRange { self.curve.parameter_range() }
|
||||
#[inline(always)]
|
||||
fn period(&self) -> Option<f64> { self.curve.period() }
|
||||
}
|
||||
|
||||
impl<C: ParametricCurve3D + BoundedCurve> BoundedCurve for ProjectedCurve<C> {}
|
||||
|
||||
impl<C: ParametricCurve3D + BoundedCurve> SearchParameter<D1> for ProjectedCurve<C> {
|
||||
type Point = Point2;
|
||||
fn search_parameter<H: Into<SPHint1D>>(
|
||||
&self,
|
||||
point: Self::Point,
|
||||
hint: H,
|
||||
trials: usize,
|
||||
) -> Option<f64> {
|
||||
let hint = match hint.into() {
|
||||
SPHint1D::Parameter(t) => t,
|
||||
SPHint1D::Range(x, y) => {
|
||||
algo::curve::presearch(self, point, (x, y), PRESEARCH_DIVISION)
|
||||
}
|
||||
SPHint1D::None => {
|
||||
algo::curve::presearch(self, point, self.range_tuple(), PRESEARCH_DIVISION)
|
||||
}
|
||||
};
|
||||
algo::curve::search_parameter(self, point, hint, trials)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: ParametricCurve3D + BoundedCurve> SearchNearestParameter<D1> for ProjectedCurve<C> {
|
||||
type Point = Point2;
|
||||
fn search_nearest_parameter<H: Into<SPHint1D>>(
|
||||
&self,
|
||||
point: Self::Point,
|
||||
hint: H,
|
||||
trials: usize,
|
||||
) -> Option<f64> {
|
||||
let hint = match hint.into() {
|
||||
SPHint1D::Parameter(t) => t,
|
||||
SPHint1D::Range(x, y) => {
|
||||
algo::curve::presearch(self, point, (x, y), PRESEARCH_DIVISION)
|
||||
}
|
||||
SPHint1D::None => {
|
||||
algo::curve::presearch(self, point, self.range_tuple(), PRESEARCH_DIVISION)
|
||||
}
|
||||
};
|
||||
algo::curve::search_nearest_parameter(self, point, hint, trials)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> RevolutedCurve<C> {
|
||||
/// Creates a surface by revoluting a curve.
|
||||
#[inline(always)]
|
||||
pub fn by_revolution(curve: C, origin: Point3, axis: Vector3) -> Self {
|
||||
RevolutedCurve {
|
||||
curve,
|
||||
revolution: Revolution::new(origin, axis),
|
||||
}
|
||||
}
|
||||
/// Returns the curve before revoluted.
|
||||
#[inline(always)]
|
||||
pub const fn entity_curve(&self) -> &C { &self.curve }
|
||||
/// Into the curve before revoluted.
|
||||
#[inline(always)]
|
||||
pub fn into_entity_curve(self) -> C { self.curve }
|
||||
/// Returns origin of revolution
|
||||
#[inline(always)]
|
||||
pub const fn origin(&self) -> Point3 { self.revolution.origin }
|
||||
/// Returns axis of revolution
|
||||
#[inline(always)]
|
||||
pub const fn axis(&self) -> Vector3 { self.revolution.axis }
|
||||
}
|
||||
|
||||
impl<C: ParametricCurve3D + BoundedCurve> RevolutedCurve<C> {
|
||||
/// Returns true if the front point of the curve is on the axis of rotation.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let line = BSplineCurve::new(
|
||||
/// KnotVec::bezier_knot(1),
|
||||
/// vec![Point3::new(0.0, 0.0, 0.0), Point3::new(0.0, 0.0, 1.0)],
|
||||
/// );
|
||||
/// let surface0 = RevolutedCurve::by_revolution(line.clone(), Point3::origin(), Vector3::unit_y());
|
||||
/// assert!(surface0.is_front_fixed());
|
||||
/// let surface1 = RevolutedCurve::by_revolution(line, Point3::new(1.0, 0.0, 0.0), Vector3::unit_y());
|
||||
/// assert!(!surface1.is_front_fixed());
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn is_front_fixed(&self) -> bool { self.revolution.contains(self.curve.front()) }
|
||||
/// Returns true if the back point of the curve is on the axis of rotation.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let line = BSplineCurve::new(
|
||||
/// KnotVec::bezier_knot(1),
|
||||
/// vec![Point3::new(0.0, 0.0, 1.0), Point3::new(0.0, 0.0, 0.0)],
|
||||
/// );
|
||||
/// let surface0 = RevolutedCurve::by_revolution(line.clone(), Point3::origin(), Vector3::unit_y());
|
||||
/// assert!(surface0.is_back_fixed());
|
||||
/// let surface1 = RevolutedCurve::by_revolution(line, Point3::new(1.0, 0.0, 0.0), Vector3::unit_y());
|
||||
/// assert!(!surface1.is_back_fixed());
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn is_back_fixed(&self) -> bool { self.revolution.contains(self.curve.back()) }
|
||||
}
|
||||
|
||||
impl<C: ParametricCurve3D + BoundedCurve> SearchParameter<D2> for RevolutedCurve<C> {
|
||||
type Point = Point3;
|
||||
fn search_parameter<H: Into<SPHint2D>>(
|
||||
&self,
|
||||
point: Point3,
|
||||
hint: H,
|
||||
trials: usize,
|
||||
) -> Option<(f64, f64)> {
|
||||
let (t0, t1) = self.curve.range_tuple();
|
||||
if self.is_front_fixed() && self.curve.front().near(&point) {
|
||||
match hint.into() {
|
||||
SPHint2D::Parameter(_, y) => Some((t0, y)),
|
||||
SPHint2D::Range((_, y), _) => Some((t0, y)),
|
||||
SPHint2D::None => Some((t0, 0.0)),
|
||||
}
|
||||
} else if self.is_back_fixed() && self.curve.back().near(&point) {
|
||||
match hint.into() {
|
||||
SPHint2D::Parameter(_, y) => Some((t1, y)),
|
||||
SPHint2D::Range(_, (_, y)) => Some((t1, y)),
|
||||
SPHint2D::None => Some((t1, 2.0 * PI)),
|
||||
}
|
||||
} else {
|
||||
let proj_curve = ProjectedCurve {
|
||||
curve: &self.curve,
|
||||
revolution: self.revolution,
|
||||
};
|
||||
let p = self.revolution.proj_point(point);
|
||||
let hint = hint.into();
|
||||
let hint_angle = match hint {
|
||||
SPHint2D::Parameter(_, y) => Some(y),
|
||||
_ => None,
|
||||
};
|
||||
let hint0 = match hint {
|
||||
SPHint2D::Parameter(x, _) => SPHint1D::Parameter(x),
|
||||
SPHint2D::Range((x0, _), (x1, _)) => SPHint1D::Range(x0, x1),
|
||||
SPHint2D::None => SPHint1D::None,
|
||||
};
|
||||
let t = proj_curve.search_parameter(p, hint0, trials)?;
|
||||
let p = self.curve.subs(t);
|
||||
let ang = seam_side(self.revolution.proj_angle(p, point), hint_angle);
|
||||
match self.subs(t, ang).near(&point) {
|
||||
true => Some((t, ang)),
|
||||
false => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: ParametricCurve3D + BoundedCurve> SearchNearestParameter<D2> for RevolutedCurve<C> {
|
||||
type Point = Point3;
|
||||
fn search_nearest_parameter<H: Into<SPHint2D>>(
|
||||
&self,
|
||||
point: Point3,
|
||||
hint: H,
|
||||
trials: usize,
|
||||
) -> Option<(f64, f64)> {
|
||||
let (t0, t1) = self.curve.range_tuple();
|
||||
let on_axis = move |o: Point3, normal: Vector3| {
|
||||
let op = point - o;
|
||||
op.cross(self.revolution.axis).so_small() && op.dot(normal) >= 0.0
|
||||
};
|
||||
if self.is_front_fixed() && on_axis(self.curve.front(), self.normal(t0, 0.0)) {
|
||||
match hint.into() {
|
||||
SPHint2D::Parameter(_, y) => Some((t0, y)),
|
||||
SPHint2D::Range((_, y), _) => Some((t0, y)),
|
||||
SPHint2D::None => Some((t0, 0.0)),
|
||||
}
|
||||
} else if self.is_back_fixed() && on_axis(self.curve.back(), self.normal(t1, 0.0)) {
|
||||
match hint.into() {
|
||||
SPHint2D::Parameter(_, y) => Some((t1, y)),
|
||||
SPHint2D::Range(_, (_, y)) => Some((t1, y)),
|
||||
SPHint2D::None => Some((t1, 2.0 * PI)),
|
||||
}
|
||||
} else {
|
||||
let proj_curve = ProjectedCurve {
|
||||
curve: &self.curve,
|
||||
revolution: self.revolution,
|
||||
};
|
||||
let p = self.revolution.proj_point(point);
|
||||
let hint = hint.into();
|
||||
let hint_angle = match hint {
|
||||
SPHint2D::Parameter(_, y) => Some(y),
|
||||
_ => None,
|
||||
};
|
||||
let hint0 = match hint {
|
||||
SPHint2D::Parameter(x, _) => SPHint1D::Parameter(x),
|
||||
SPHint2D::Range((x0, _), (x1, _)) => SPHint1D::Range(x0, x1),
|
||||
SPHint2D::None => SPHint1D::None,
|
||||
};
|
||||
let t = proj_curve.search_nearest_parameter(p, hint0, trials)?;
|
||||
let p = self.curve.subs(t);
|
||||
Some((t, seam_side(self.revolution.proj_angle(p, point), hint_angle)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn sub_include<C0, C1>(
|
||||
surface: &RevolutedCurve<C0>,
|
||||
curve: &C1,
|
||||
knots: &[f64],
|
||||
degree: usize,
|
||||
) -> bool
|
||||
where
|
||||
C0: ParametricCurve3D + BoundedCurve,
|
||||
C1: ParametricCurve3D + BoundedCurve,
|
||||
{
|
||||
let first = curve.subs(knots[0]);
|
||||
let mut hint = match surface.search_parameter(first, None, INCLUDE_CURVE_TRIALS) {
|
||||
Some(hint) => hint,
|
||||
None => return false,
|
||||
};
|
||||
knots
|
||||
.windows(2)
|
||||
.flat_map(move |knot| {
|
||||
(1..=degree).map(move |i| {
|
||||
let s = i as f64 / degree as f64;
|
||||
knot[0] * (1.0 - s) + knot[1] * s
|
||||
})
|
||||
})
|
||||
.all(move |t| {
|
||||
let pt = ParametricCurve::subs(curve, t);
|
||||
surface
|
||||
.search_parameter(pt, Some(hint), INCLUDE_CURVE_TRIALS)
|
||||
.or_else(|| surface.search_parameter(pt, None, INCLUDE_CURVE_TRIALS))
|
||||
.map(|res| hint = res)
|
||||
.is_some()
|
||||
})
|
||||
}
|
||||
|
||||
impl<'a> IncludeCurve<BSplineCurve<Point3>> for RevolutedCurve<&'a BSplineCurve<Point3>> {
|
||||
fn include(&self, curve: &BSplineCurve<Point3>) -> bool {
|
||||
let knots = curve.knot_vec().to_single_multi().0;
|
||||
let degree = usize::max(2, usize::max(curve.degree(), self.curve.degree()));
|
||||
sub_include(self, curve, &knots, degree)
|
||||
}
|
||||
}
|
||||
|
||||
impl IncludeCurve<BSplineCurve<Point3>> for RevolutedCurve<BSplineCurve<Point3>> {
|
||||
fn include(&self, curve: &BSplineCurve<Point3>) -> bool {
|
||||
let knots = curve.knot_vec().to_single_multi().0;
|
||||
let degree = usize::max(2, usize::max(curve.degree(), self.curve.degree()));
|
||||
sub_include(self, curve, &knots, degree)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IncludeCurve<BSplineCurve<Point3>> for RevolutedCurve<&'a NurbsCurve<Vector4>> {
|
||||
fn include(&self, curve: &BSplineCurve<Point3>) -> bool {
|
||||
let knots = curve.knot_vec().to_single_multi().0;
|
||||
let degree = curve.degree() + usize::max(2, self.curve.degree());
|
||||
sub_include(self, curve, &knots, degree)
|
||||
}
|
||||
}
|
||||
|
||||
impl IncludeCurve<BSplineCurve<Point3>> for RevolutedCurve<NurbsCurve<Vector4>> {
|
||||
fn include(&self, curve: &BSplineCurve<Point3>) -> bool {
|
||||
let knots = curve.knot_vec().to_single_multi().0;
|
||||
let degree = curve.degree() + usize::max(2, self.curve.degree());
|
||||
sub_include(self, curve, &knots, degree)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IncludeCurve<NurbsCurve<Vector4>> for RevolutedCurve<&'a BSplineCurve<Point3>> {
|
||||
fn include(&self, curve: &NurbsCurve<Vector4>) -> bool {
|
||||
let knots = curve.knot_vec().to_single_multi().0;
|
||||
let degree = curve.degree() + usize::max(2, self.curve.degree());
|
||||
sub_include(self, curve, &knots, degree)
|
||||
}
|
||||
}
|
||||
|
||||
impl IncludeCurve<NurbsCurve<Vector4>> for RevolutedCurve<BSplineCurve<Point3>> {
|
||||
fn include(&self, curve: &NurbsCurve<Vector4>) -> bool {
|
||||
let knots = curve.knot_vec().to_single_multi().0;
|
||||
let degree = curve.degree() + usize::max(2, self.curve.degree());
|
||||
sub_include(self, curve, &knots, degree)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IncludeCurve<NurbsCurve<Vector4>> for RevolutedCurve<&'a NurbsCurve<Vector4>> {
|
||||
fn include(&self, curve: &NurbsCurve<Vector4>) -> bool {
|
||||
let knots = curve.knot_vec().to_single_multi().0;
|
||||
let degree = curve.degree() + usize::max(2, self.curve.degree());
|
||||
sub_include(self, curve, &knots, degree)
|
||||
}
|
||||
}
|
||||
|
||||
impl IncludeCurve<NurbsCurve<Vector4>> for RevolutedCurve<NurbsCurve<Vector4>> {
|
||||
fn include(&self, curve: &NurbsCurve<Vector4>) -> bool {
|
||||
let knots = curve.knot_vec().to_single_multi().0;
|
||||
let degree = curve.degree() + usize::max(2, self.curve.degree());
|
||||
sub_include(self, curve, &knots, degree)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> ParameterDivision2D for RevolutedCurve<C>
|
||||
where C: ParametricCurve3D + ParameterDivision1D<Point = Point3>
|
||||
{
|
||||
fn parameter_division(
|
||||
&self,
|
||||
(urange, vrange): ((f64, f64), (f64, f64)),
|
||||
tol: f64,
|
||||
) -> (Vec<f64>, Vec<f64>) {
|
||||
let curve_division = self.curve.parameter_division(urange, tol);
|
||||
let max = curve_division
|
||||
.1
|
||||
.into_iter()
|
||||
.fold(0.0, |max2, pt| {
|
||||
let h = self.revolution.proj_point(pt).y;
|
||||
f64::max(max2, h)
|
||||
})
|
||||
.sqrt();
|
||||
let acos = f64::acos(1.0 - tol / max);
|
||||
let div: usize = 1 + ((vrange.1 - vrange.0) / acos).floor() as usize;
|
||||
let circle_division = (0..=div)
|
||||
.map(|j| vrange.0 + (vrange.1 - vrange.0) * j as f64 / div as f64)
|
||||
.collect();
|
||||
(curve_division.0, circle_division)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn revolve_test() {
|
||||
let pt0 = Point3::new(0.0, 2.0, 1.0);
|
||||
let pt1 = Point3::new(1.0, 0.0, 0.0);
|
||||
let curve = BSplineCurve::new(KnotVec::bezier_knot(1), vec![pt0, pt1]);
|
||||
let surface = RevolutedCurve::by_revolution(curve, Point3::origin(), Vector3::unit_y());
|
||||
const N: usize = 100;
|
||||
for i in 0..=N {
|
||||
for j in 0..=N {
|
||||
let u = i as f64 / N as f64;
|
||||
let v = 2.0 * PI * j as f64 / N as f64;
|
||||
let res = surface.subs(u, v);
|
||||
let ans = Point3::new(
|
||||
u * f64::cos(v) + (1.0 - u) * f64::sin(v),
|
||||
2.0 * (1.0 - u),
|
||||
-u * f64::sin(v) + (1.0 - u) * f64::cos(v),
|
||||
);
|
||||
assert_near!(res, ans);
|
||||
let res_uder = surface.uder(u, v);
|
||||
let ans_uder =
|
||||
Vector3::new(f64::cos(v) - f64::sin(v), -2.0, -f64::sin(v) - f64::cos(v));
|
||||
assert_near!(res_uder, ans_uder);
|
||||
let res_vder = surface.vder(u, v);
|
||||
let ans_vder = Vector3::new(
|
||||
-u * f64::sin(v) + (1.0 - u) * f64::cos(v),
|
||||
0.0,
|
||||
-u * f64::cos(v) - (1.0 - u) * f64::sin(v),
|
||||
);
|
||||
assert_near!(res_vder, ans_vder);
|
||||
let res_uuder = surface.uuder(u, v);
|
||||
let ans_uuder = Vector3::zero();
|
||||
assert_near!(res_uuder, ans_uuder);
|
||||
let res_uvder = surface.uvder(u, v);
|
||||
let ans_uvder =
|
||||
Vector3::new(-f64::sin(v) - f64::cos(v), 0.0, -f64::cos(v) + f64::sin(v));
|
||||
assert_near!(res_uvder, ans_uvder);
|
||||
let res_vvder = surface.vvder(u, v);
|
||||
let ans_vvder = Vector3::new(
|
||||
-u * f64::cos(v) - (1.0 - u) * f64::sin(v),
|
||||
0.0,
|
||||
u * f64::sin(v) - (1.0 - u) * f64::cos(v),
|
||||
);
|
||||
assert_near!(res_vvder, ans_vvder);
|
||||
let normal = surface.normal(u, v);
|
||||
assert!(normal.dot(res_uder).so_small());
|
||||
assert!(normal.dot(res_vder).so_small());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn search_parameter() {
|
||||
let line = BSplineCurve::new(
|
||||
KnotVec::bezier_knot(1),
|
||||
vec![Point3::new(0.0, 2.0, 1.0), Point3::new(1.0, 0.0, 0.0)],
|
||||
);
|
||||
let surface = RevolutedCurve::by_revolution(line, Point3::origin(), Vector3::unit_y());
|
||||
let pt = Point3::new(-0.5, 1.0, 0.5);
|
||||
let (u, v) = surface.search_parameter(pt, Some((0.4, 1.2)), 100).unwrap();
|
||||
assert_near!(surface.subs(u, v), pt);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn search_parameter_with_fixed_points() {
|
||||
let line = BSplineCurve::new(
|
||||
KnotVec::bezier_knot(2),
|
||||
vec![
|
||||
Point3::new(0.0, 1.0, 0.0),
|
||||
Point3::new(0.0, 0.0, 1.0),
|
||||
Point3::new(0.0, -1.0, 0.0),
|
||||
],
|
||||
);
|
||||
let surface = RevolutedCurve::by_revolution(line, Point3::origin(), Vector3::unit_y());
|
||||
|
||||
let (u, v) = surface
|
||||
.search_parameter(Point3::new(0.0, 1.0, 0.0), Some((0.5, 0.3)), 10)
|
||||
.unwrap();
|
||||
assert_near!(Vector2::new(u, v), Vector2::new(0.0, 0.3));
|
||||
|
||||
let (u, v) = surface
|
||||
.search_parameter(Point3::new(0.0, -1.0, 0.0), Some((0.5, 0.3)), 10)
|
||||
.unwrap();
|
||||
assert_near!(Vector2::new(u, v), Vector2::new(1.0, 0.3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn search_nearest_parameter() {
|
||||
let line = BSplineCurve::new(
|
||||
KnotVec::bezier_knot(1),
|
||||
vec![Point3::new(0.0, 2.0, 1.0), Point3::new(1.0, 0.0, 0.0)],
|
||||
);
|
||||
let surface = RevolutedCurve::by_revolution(line, Point3::origin(), Vector3::unit_y());
|
||||
let pt = surface.subs(0.4, 1.2) + 0.1 * surface.normal(0.4, 1.2);
|
||||
let (u, v) = surface
|
||||
.search_nearest_parameter(pt, Some((0.4, 1.2)), 100)
|
||||
.unwrap();
|
||||
assert_near!(Vector2::new(u, v), Vector2::new(0.4, 1.2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn search_nearest_parameter_with_fixed_points() {
|
||||
let line = BSplineCurve::new(
|
||||
KnotVec::bezier_knot(2),
|
||||
vec![
|
||||
Point3::new(0.0, 1.0, 0.0),
|
||||
Point3::new(0.0, 0.0, 1.0),
|
||||
Point3::new(0.0, -1.0, 0.0),
|
||||
],
|
||||
);
|
||||
let surface = RevolutedCurve::by_revolution(line, Point3::origin(), Vector3::unit_y());
|
||||
|
||||
let (u, v) = surface
|
||||
.search_nearest_parameter(Point3::new(0.0, 2.0, 0.0), Some((0.5, 0.3)), 10)
|
||||
.unwrap();
|
||||
assert_near!(Vector2::new(u, v), Vector2::new(0.0, 0.3));
|
||||
|
||||
let (u, v) = surface
|
||||
.search_nearest_parameter(Point3::new(0.0, -2.0, 0.0), Some((0.5, 0.3)), 10)
|
||||
.unwrap();
|
||||
assert_near!(Vector2::new(u, v), Vector2::new(1.0, 0.3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn include_curve_normal() {
|
||||
let line = BSplineCurve::new(
|
||||
KnotVec::bezier_knot(1),
|
||||
vec![Point3::new(0.0, 0.0, 0.0), Point3::new(0.0, 2.0, 2.0)],
|
||||
);
|
||||
let surface = RevolutedCurve::by_revolution(line, Point3::origin(), Vector3::unit_y());
|
||||
let parabola = BSplineCurve::new(
|
||||
KnotVec::bezier_knot(2),
|
||||
vec![
|
||||
Point3::new(1.0, 1.0, 0.0),
|
||||
Point3::new(0.0, 0.0, 1.0),
|
||||
Point3::new(-1.0, 1.0, 0.0),
|
||||
],
|
||||
);
|
||||
assert!(surface.include(¶bola));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn include_curve_abnormal0() {
|
||||
let line = BSplineCurve::new(
|
||||
KnotVec::bezier_knot(1),
|
||||
vec![Point3::new(0.0, 0.0, 0.0), Point3::new(0.0, 2.0, 2.0)],
|
||||
);
|
||||
let surface = RevolutedCurve::by_revolution(line, Point3::origin(), Vector3::unit_y());
|
||||
let parabola = BSplineCurve::new(
|
||||
KnotVec::bezier_knot(2),
|
||||
vec![
|
||||
Point3::new(1.0, 1.0, 0.0),
|
||||
Point3::new(0.0, 0.0, 2.0),
|
||||
Point3::new(-1.0, 1.0, 0.0),
|
||||
],
|
||||
);
|
||||
assert!(!surface.include(¶bola));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn include_curve_abnormal1() {
|
||||
let curve = NurbsCurve::new(BSplineCurve::new(
|
||||
KnotVec::bezier_knot(3),
|
||||
vec![
|
||||
Vector4::new(0.0, 3.0, 0.0, 1.0),
|
||||
Vector4::new(0.0, 3.0, 3.0, 0.5),
|
||||
Vector4::new(0.0, 0.0, 0.0, 1.0),
|
||||
Vector4::new(0.0, 0.0, 3.0, 1.0),
|
||||
],
|
||||
));
|
||||
let pt0 = curve.subs(0.2);
|
||||
let pt1 = curve.subs(0.6);
|
||||
let surface = RevolutedCurve::by_revolution(curve, Point3::origin(), Vector3::unit_y());
|
||||
let line = BSplineCurve::new(KnotVec::bezier_knot(1), vec![pt0, pt1]);
|
||||
assert!(!surface.include(&line));
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
use super::*;
|
||||
|
||||
impl<C> TrimmedCurve<C> {
|
||||
/// constructor
|
||||
#[inline(always)]
|
||||
pub const fn new(curve: C, range: (f64, f64)) -> Self { Self { curve, range } }
|
||||
/// Returns the reference of non-trimmed curve
|
||||
#[inline(always)]
|
||||
pub const fn curve(&self) -> &C { &self.curve }
|
||||
/// Returns the mutable reference of non-trimmed curve
|
||||
#[inline(always)]
|
||||
pub fn curve_mut(&mut self) -> &mut C { &mut self.curve }
|
||||
}
|
||||
|
||||
impl<C: ParametricCurve> ParametricCurve for TrimmedCurve<C> {
|
||||
type Point = C::Point;
|
||||
type Vector = C::Vector;
|
||||
#[inline(always)]
|
||||
fn subs(&self, t: f64) -> Self::Point { self.curve.subs(t) }
|
||||
#[inline(always)]
|
||||
fn der(&self, t: f64) -> Self::Vector { self.curve.der(t) }
|
||||
#[inline(always)]
|
||||
fn der2(&self, t: f64) -> Self::Vector { self.curve.der2(t) }
|
||||
#[inline(always)]
|
||||
fn period(&self) -> Option<f64> { self.curve.period() }
|
||||
#[inline(always)]
|
||||
fn parameter_range(&self) -> ParameterRange {
|
||||
(Bound::Included(self.range.0), Bound::Included(self.range.1))
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: ParametricCurve> BoundedCurve for TrimmedCurve<C> {}
|
||||
|
||||
impl<C: ParametricCurve> Cut for TrimmedCurve<C> {
|
||||
fn cut(&mut self, t: f64) -> Self {
|
||||
let (t0, t1) = self.range;
|
||||
self.range = (t0, t);
|
||||
Self::new(self.curve.clone(), (t, t1))
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: SearchNearestParameter<D1>> SearchNearestParameter<D1> for TrimmedCurve<C> {
|
||||
type Point = C::Point;
|
||||
#[inline(always)]
|
||||
fn search_nearest_parameter<H: Into<SPHint1D>>(
|
||||
&self,
|
||||
pt: C::Point,
|
||||
hint: H,
|
||||
trials: usize,
|
||||
) -> Option<f64> {
|
||||
self.curve.search_nearest_parameter(pt, hint, trials)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: SearchParameter<D1>> SearchParameter<D1> for TrimmedCurve<C> {
|
||||
type Point = C::Point;
|
||||
#[inline(always)]
|
||||
fn search_parameter<H: Into<SPHint1D>>(
|
||||
&self,
|
||||
pt: C::Point,
|
||||
hint: H,
|
||||
trials: usize,
|
||||
) -> Option<f64> {
|
||||
self.curve.search_parameter(pt, hint, trials)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: ParameterDivision1D> ParameterDivision1D for TrimmedCurve<C> {
|
||||
type Point = C::Point;
|
||||
fn parameter_division(&self, range: (f64, f64), tol: f64) -> (Vec<f64>, Vec<Self::Point>) {
|
||||
self.curve.parameter_division(range, tol)
|
||||
}
|
||||
}
|
||||
Vendored
-190
@@ -1,190 +0,0 @@
|
||||
use thiserror::Error;
|
||||
|
||||
/// Error handler for [`Error`](./errors/enum.Error.html)
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// Geometrical Errors
|
||||
#[derive(Debug, PartialEq, Error)]
|
||||
pub enum Error {
|
||||
/// The following operations are failed if the knot vector has zero range.
|
||||
/// * Creating `BSplineCurve` or `BSplineSurface`,
|
||||
/// * Calculating bspline basis functions, or
|
||||
/// * Normalizing the knot vector.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// use truck_geometry::errors::Error;
|
||||
/// let mut knot_vec = KnotVec::from(vec![0.0, 0.0, 0.0, 0.0]);
|
||||
/// assert_eq!(knot_vec.try_normalize(), Err(Error::ZeroRange));
|
||||
/// assert_eq!(knot_vec.try_bspline_basis_functions(1, 0.0), Err(Error::ZeroRange));
|
||||
///
|
||||
/// let ctrl_pts = vec![Vector2::new(0.0, 0.0), Vector2::new(1.0, 1.0)];
|
||||
/// assert!(matches!(BSplineCurve::try_new(knot_vec, ctrl_pts), Err(Error::ZeroRange)));
|
||||
/// ```
|
||||
#[error("This knot vector consists single value.")]
|
||||
ZeroRange,
|
||||
/// Fails concatting two knot vectors if there is a difference between the back knot of
|
||||
/// the former knot vector and the front knot of the latter knot vector.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// use truck_geometry::errors::Error;
|
||||
/// let mut knot_vec0 = KnotVec::from(vec![0.0, 0.0, 1.0, 1.0]);
|
||||
/// let knot_vec1 = KnotVec::from(vec![2.0, 2.0, 3.0, 3.0]);
|
||||
/// assert!(matches!(knot_vec0.try_concat(&knot_vec1, 1), Err(Error::DifferentBackFront(1.0, 2.0))));
|
||||
/// ```
|
||||
#[error("Cannot concat two knot vectors whose the back of the first and the front of the second are different.
|
||||
the back of the first knot vector: {0}
|
||||
the front of the second knot vector: {1}")]
|
||||
DifferentBackFront(f64, f64),
|
||||
/// If the knot vector is not clamped, then one cannot concat the vector with another knot vector.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// use truck_geometry::errors::Error;
|
||||
/// let mut knot_vec0 = KnotVec::from(vec![0.0, 0.0, 1.0, 1.0]);
|
||||
/// let knot_vec1 = KnotVec::from(vec![2.0, 2.0, 3.0, 3.0]);
|
||||
/// assert!(matches!(knot_vec0.try_concat(&knot_vec1, 2), Err(Error::NotClampedKnotVector)));
|
||||
/// ```
|
||||
#[error("This knot vector is not clamped.")]
|
||||
NotClampedKnotVector,
|
||||
/// Creating a knot vector by `KnotVec::try_from()` is failed if the given vector is not sorted.
|
||||
/// `<KnotVec as From<Vec<f64>>>::from()` does not panic by this error because sorts the given
|
||||
/// vector before creating the knot vector. So, `KnotVec::try_from()` is more efficient than
|
||||
/// `<KnotVec as From<Vec<f64>>>::from()`.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// use truck_geometry::errors::Error;
|
||||
/// use std::convert::*;
|
||||
///
|
||||
/// assert!(matches!(KnotVec::try_from(vec![1.0, 3.0, 0.0, 2.0]), Err(Error::NotSortedVector)));
|
||||
/// assert_eq!(
|
||||
/// <KnotVec as From<Vec<f64>>>::from(vec![1.0, 3.0, 0.0, 2.0]),
|
||||
/// KnotVec::try_from(vec![0.0, 1.0, 2.0, 3.0]).unwrap(),
|
||||
/// );
|
||||
/// ```
|
||||
#[error("This knot vector is not sorted.")]
|
||||
NotSortedVector,
|
||||
/// The given degree is too large to calculate bspline basis functions.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// use truck_geometry::errors::Error;
|
||||
///
|
||||
/// // a knot vector with length = 4.
|
||||
/// let knot_vec = KnotVec::from(vec![0.0, 0.0, 1.0, 1.0]);
|
||||
/// assert!(matches!(
|
||||
/// knot_vec.try_bspline_basis_functions(5, 0.5),
|
||||
/// Err(Error::TooLargeDegree(4, 5)),
|
||||
/// ));
|
||||
/// ```
|
||||
#[error(
|
||||
"This knot vector is too short compared to the degree.
|
||||
the length of knot_vec: {0}
|
||||
the degree: {1}"
|
||||
)]
|
||||
TooLargeDegree(usize, usize),
|
||||
/// The specified knot cannot be removed.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// use truck_geometry::errors::Error;
|
||||
/// let knot_vec = KnotVec::bezier_knot(2);
|
||||
/// let ctrl_pts = vec![Vector2::new(-1.0, 1.0), Vector2::new(0.0, -1.0), Vector2::new(1.0, 1.0)];
|
||||
/// let mut bspcurve = BSplineCurve::new(knot_vec, ctrl_pts);
|
||||
/// let org_curve = bspcurve.clone();
|
||||
/// bspcurve.add_knot(0.5).add_knot(0.5).add_knot(0.25).add_knot(0.75);
|
||||
/// assert!(bspcurve.try_remove_knot(3).is_ok());
|
||||
/// assert!(matches!(bspcurve.try_remove_knot(2), Err(Error::CannotRemoveKnot(2))));
|
||||
/// ```
|
||||
#[error("The {0}th knot in this knot vector cannot be removed.")]
|
||||
CannotRemoveKnot(usize),
|
||||
/// Empty vector of points cannot construct B-spline.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// use truck_geometry::errors::Error;
|
||||
///
|
||||
/// let knot_vec = KnotVec::bezier_knot(2);
|
||||
/// let ctrl_pts: Vec<Vector4> = Vec::new();
|
||||
/// assert!(matches!(
|
||||
/// BSplineCurve::try_new(knot_vec, ctrl_pts),
|
||||
/// Err(Error::EmptyControlPoints),
|
||||
/// ));
|
||||
/// ```
|
||||
#[error("The control point must not be empty.")]
|
||||
EmptyControlPoints,
|
||||
/// The knot vector of B-spline curves or B-spline surfaces must be longer than the corresponded
|
||||
/// array of control points.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// use truck_geometry::errors::Error;
|
||||
/// let knot_vec = KnotVec::from(vec![0.0, 1.0, 2.0]);
|
||||
/// let ctrl_pts = vec![Vector2::new(0.0, 0.0), Vector2::new(0.0, 0.0), Vector2::new(0.0, 0.0), Vector2::new(0.0, 0.0)];
|
||||
/// assert!(matches!(
|
||||
/// BSplineCurve::try_new(knot_vec, ctrl_pts),
|
||||
/// Err(Error::TooShortKnotVector(3, 4)),
|
||||
/// ));
|
||||
/// ```
|
||||
#[error(
|
||||
"The knot vector must be more than the control points.
|
||||
the length of knot_vec: {0}
|
||||
the number of control points: {1}"
|
||||
)]
|
||||
TooShortKnotVector(usize, usize),
|
||||
/// The length of the given arrays of control points to create a B-spline surface is irregular.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// use truck_geometry::errors::Error;
|
||||
/// let knot_vecs = (KnotVec::bezier_knot(2), KnotVec::bezier_knot(2));
|
||||
/// let ctrl_pts = vec![
|
||||
/// vec![Vector2::new(1.0, 2.0), Vector2::new(1.0, 2.0)], // length = 2
|
||||
/// vec![Vector2::new(1.0, 2.0)] // length = 1
|
||||
/// ];
|
||||
/// assert!(matches!(
|
||||
/// BSplineSurface::try_new(knot_vecs, ctrl_pts),
|
||||
/// Err(Error::IrregularControlPoints),
|
||||
/// ));
|
||||
/// ```
|
||||
#[error("The number of control points is irregular")]
|
||||
IrregularControlPoints,
|
||||
/// The vector of control points and the one of weights have different length.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// use truck_geometry::errors::Error;
|
||||
/// let bspcurve = BSplineCurve::new(
|
||||
/// KnotVec::bezier_knot(2),
|
||||
/// vec![Point2::new(0.0, 1.0), Point2::new(2.0, 3.0), Point2::new(4.0, 5.0)],
|
||||
/// );
|
||||
/// let weights = vec![1.0, 2.0]; // less than control points
|
||||
///
|
||||
/// assert!(matches!(
|
||||
/// NurbsCurve::<Vector3>::try_from_bspline_and_weights(bspcurve, weights),
|
||||
/// Err(Error::DifferentLength),
|
||||
/// ));
|
||||
/// ```
|
||||
#[error("The vector of control points and the one of weights have different length.")]
|
||||
DifferentLength,
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[rustfmt::skip]
|
||||
fn print_messages() {
|
||||
use std::io::Write;
|
||||
let stderr = &mut std::io::stderr();
|
||||
writeln!(stderr, "****** test of the expressions of error messages ******\n").unwrap();
|
||||
writeln!(stderr, "{}\n", Error::ZeroRange).unwrap();
|
||||
writeln!(stderr, "{}\n", Error::DifferentBackFront(0.0, 1.0)).unwrap();
|
||||
writeln!(stderr, "{}\n", Error::NotClampedKnotVector).unwrap();
|
||||
writeln!(stderr, "{}\n", Error::NotSortedVector).unwrap();
|
||||
writeln!(stderr, "{}\n", Error::TooLargeDegree(1, 2)).unwrap();
|
||||
writeln!(stderr, "{}\n", Error::CannotRemoveKnot(7)).unwrap();
|
||||
writeln!(stderr, "{}\n", Error::EmptyControlPoints).unwrap();
|
||||
writeln!(stderr, "{}\n", Error::TooShortKnotVector(1, 2)).unwrap();
|
||||
writeln!(stderr, "{}\n", Error::IrregularControlPoints).unwrap();
|
||||
writeln!(stderr, "*******************************************************").unwrap();
|
||||
}
|
||||
Vendored
-52
@@ -1,52 +0,0 @@
|
||||
//! Geometrical structs: knot vector, B-spline and NURBS
|
||||
|
||||
#![cfg_attr(not(debug_assertions), deny(warnings))]
|
||||
#![deny(clippy::all, rust_2018_idioms)]
|
||||
#![warn(
|
||||
missing_docs,
|
||||
missing_debug_implementations,
|
||||
trivial_casts,
|
||||
trivial_numeric_casts,
|
||||
unsafe_code,
|
||||
unstable_features,
|
||||
unused_import_braces,
|
||||
unused_qualifications
|
||||
)]
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{fmt::Debug, ops::Bound};
|
||||
use truck_base::bounding_box::Bounded;
|
||||
|
||||
const INCLUDE_CURVE_TRIALS: usize = 100;
|
||||
const PRESEARCH_DIVISION: usize = 50;
|
||||
|
||||
/// re-export `truck_base`
|
||||
pub mod base {
|
||||
pub use truck_base::bounding_box::BoundingBox;
|
||||
pub use truck_base::cgmath64::*;
|
||||
pub use truck_base::tolerance::*;
|
||||
pub use truck_base::{assert_near, assert_near2};
|
||||
pub use truck_base::{hash, hash::HashGen};
|
||||
pub use truck_geotrait::*;
|
||||
}
|
||||
/// Declares the nurbs
|
||||
pub mod nurbs;
|
||||
|
||||
/// Enumerats `Error`.
|
||||
pub mod errors;
|
||||
|
||||
/// Declares the specified gememetric items: Plane, Sphere, and so on.
|
||||
pub mod specifieds;
|
||||
|
||||
/// Declares some decorators
|
||||
pub mod decorators;
|
||||
|
||||
/// re-export all modules.
|
||||
pub mod prelude {
|
||||
use crate::*;
|
||||
pub use base::*;
|
||||
pub use decorators::*;
|
||||
pub use errors::*;
|
||||
pub use nurbs::*;
|
||||
pub use specifieds::*;
|
||||
}
|
||||
-1488
File diff suppressed because it is too large
Load Diff
-2236
File diff suppressed because it is too large
Load Diff
-596
@@ -1,596 +0,0 @@
|
||||
use super::*;
|
||||
use crate::errors::Error;
|
||||
use std::slice::SliceIndex;
|
||||
use std::vec::Vec;
|
||||
|
||||
impl KnotVec {
|
||||
/// empty constructor
|
||||
pub const fn new() -> KnotVec { KnotVec(Vec::new()) }
|
||||
|
||||
/// Returns the length of range.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let knot_vec = KnotVec::from(vec![0.0, 6.0]);
|
||||
/// assert_eq!(knot_vec.range_length(), 6.0);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn range_length(&self) -> f64 {
|
||||
match self.is_empty() {
|
||||
true => 0.0,
|
||||
false => self[self.len() - 1] - self[0],
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether two knot vectors have the same range.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let knot_vec0 = KnotVec::new(); // empty knot vector
|
||||
/// let knot_vec1 = KnotVec::from(vec![0.0, 0.0, 1.0, 1.0]);
|
||||
/// let knot_vec2 = KnotVec::from(vec![0.0, 0.5, 1.0]);
|
||||
/// let knot_vec3 = KnotVec::from(vec![0.0, 0.0, 2.0, 2.0]);
|
||||
/// assert!(knot_vec0.same_range(&KnotVec::new())); // both empty knot vector
|
||||
/// assert!(!knot_vec0.same_range(&knot_vec1));
|
||||
/// assert!(knot_vec1.same_range(&knot_vec2)); // the range of both knot vector is [0, 1].
|
||||
/// assert!(!knot_vec1.same_range(&knot_vec3));
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn same_range(&self, other: &KnotVec) -> bool {
|
||||
match (self.is_empty(), other.is_empty()) {
|
||||
(false, false) => {
|
||||
self[0].near(&other[0]) && self.range_length().near(&other.range_length())
|
||||
}
|
||||
(true, true) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes one item.
|
||||
#[inline(always)]
|
||||
pub fn remove(&mut self, idx: usize) -> f64 { self.0.remove(idx) }
|
||||
|
||||
/// Returns the maximum index `i` of `self[i] <= x`
|
||||
/// Return `None` if `x < self[0] or self.len() == 0`.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::KnotVec;
|
||||
/// let mut knot_vec = KnotVec::from(vec![0.0, 0.0, 1.0, 2.0, 3.0, 3.0]);
|
||||
/// let idx = knot_vec.floor(1.5).unwrap();
|
||||
/// assert_eq!(idx, 2);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn floor(&self, x: f64) -> Option<usize> { self.iter().rposition(|t| *t <= x) }
|
||||
|
||||
/// Returns the multiplicity of the `i`th knot
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::KnotVec;
|
||||
/// let knot_vec = KnotVec::from(vec![0.0, 0.0, 1.0, 2.0, 2.0, 2.0, 3.0, 3.0]);
|
||||
/// assert_eq!(knot_vec.multiplicity(5), 3);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn multiplicity(&self, i: usize) -> usize {
|
||||
self.iter().filter(|u| self[i].near(u)).count()
|
||||
}
|
||||
|
||||
/// Adds a knot and return the index of the added knot.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::KnotVec;
|
||||
/// let mut knot_vec = KnotVec::from(vec![0.0, 0.0, 1.0, 2.0, 3.0, 3.0]);
|
||||
/// let idx0 = knot_vec.add_knot(1.5);
|
||||
/// assert_eq!(idx0, 3);
|
||||
/// let idx1 = knot_vec.add_knot(-1.0);
|
||||
/// assert_eq!(idx1, 0);
|
||||
/// let ansvec = KnotVec::from(vec![-1.0, 0.0, 0.0, 1.0, 1.5, 2.0, 3.0, 3.0]);
|
||||
/// assert_eq!(knot_vec, ansvec);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn add_knot(&mut self, knot: f64) -> usize {
|
||||
match self.floor(knot) {
|
||||
Some(idx) => {
|
||||
self.0.insert(idx + 1, knot);
|
||||
idx + 1
|
||||
}
|
||||
None => {
|
||||
self.0.insert(0, knot);
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates B-spline basis functions at `t` with degree `degree`.
|
||||
/// # Panics
|
||||
/// If the length of `self` is not more than `degree`, panic occurs.
|
||||
/// # Remarks
|
||||
/// In this package, the B-spline basis function is based on the characteristic function of
|
||||
/// the right-open intervals [s, t). So, the value corresponding to the end point t = t_n is always 0.0.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// const N : usize = 100; // sample size in tests
|
||||
///
|
||||
/// // B-spline basis functions is a partition of unity in (t_k, t_{n - k}).
|
||||
/// let vec = vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
|
||||
/// let knot_vec = KnotVec::from(vec);
|
||||
/// let degree = 2;
|
||||
/// for i in 0..N {
|
||||
/// let t = 2.0 + 4.0 / (N as f64) * (i as f64);
|
||||
/// let res = knot_vec.bspline_basis_functions(degree, t);
|
||||
/// let sum = res.iter().fold(0.0, |sum, a| sum + a);
|
||||
/// assert_near2!(sum, 1.0);
|
||||
/// }
|
||||
/// ```
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// const N : usize = 100; // sample size in tests
|
||||
///
|
||||
/// // In some case, B-spline basis functions coincide with Bernstein polynomials.
|
||||
/// let vec = vec![0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0];
|
||||
/// let knot_vec = KnotVec::from(vec);
|
||||
/// let degree = 3;
|
||||
/// for i in 0..=N {
|
||||
/// let t = 1.0 / (N as f64) * (i as f64);
|
||||
/// let res = knot_vec.bspline_basis_functions(degree, t);
|
||||
/// let ans = [
|
||||
/// 1.0 * (1.0 - t) * (1.0 - t) * (1.0 - t),
|
||||
/// 3.0 * t * (1.0 - t) * (1.0 - t),
|
||||
/// 3.0 * t * t * (1.0 - t),
|
||||
/// 1.0 * t * t * t,
|
||||
/// ];
|
||||
/// for i in 0..4 { assert_near2!(res[i], ans[i]); }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn bspline_basis_functions(&self, degree: usize, t: f64) -> Vec<f64> {
|
||||
match self.try_bspline_basis_functions(degree, t) {
|
||||
Ok(got) => got,
|
||||
Err(error) => panic!("{}", error),
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates B-spline basis functions at `t` with degree `degree`.
|
||||
/// # Failures
|
||||
/// - If the range of the knot vector is zero, returns [`Error::ZeroRange`].
|
||||
/// - If the length of `self` is not more than `degree`, returns [`Error::TooLargeDegree`].
|
||||
/// # Remarks
|
||||
/// In this package, the B-spline basis function is based on the characteristic function of
|
||||
/// the right-open intervals [s, t). So, the value corresponding to the end point t = t_n is always 0.0.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// const N : usize = 100; // sample size in tests
|
||||
///
|
||||
/// // B-spline basis functions is a partition of unity in (t_k, t_{n - k}).
|
||||
/// let vec = vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
|
||||
/// let knot_vec = KnotVec::from(vec);
|
||||
/// let degree = 2;
|
||||
/// for i in 0..N {
|
||||
/// let t = 2.0 + 4.0 / (N as f64) * (i as f64);
|
||||
/// let res = knot_vec.try_bspline_basis_functions(degree, t).unwrap();
|
||||
/// let sum = res.iter().fold(0.0, |sum, a| sum + a);
|
||||
/// assert_near2!(sum, 1.0);
|
||||
/// }
|
||||
/// ```
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// const N : usize = 100; // sample size in tests
|
||||
///
|
||||
/// // In some case, B-spline basis functions coincide with Bernstein polynomials.
|
||||
/// let vec = vec![0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0];
|
||||
/// let knot_vec = KnotVec::from(vec);
|
||||
/// let degree = 3;
|
||||
/// for i in 0..=N {
|
||||
/// let t = i as f64 / N as f64;
|
||||
/// let res = knot_vec.try_bspline_basis_functions(degree, t).unwrap();
|
||||
/// let ans = [
|
||||
/// 1.0 * (1.0 - t) * (1.0 - t) * (1.0 - t),
|
||||
/// 3.0 * t * (1.0 - t) * (1.0 - t),
|
||||
/// 3.0 * t * t * (1.0 - t),
|
||||
/// 1.0 * t * t * t,
|
||||
/// ];
|
||||
/// for i in 0..4 { assert_near2!(res[i], ans[i]); }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn try_bspline_basis_functions(&self, degree: usize, t: f64) -> Result<Vec<f64>> {
|
||||
let n = self.len() - 1;
|
||||
if self[0].near(&self[n]) {
|
||||
return Err(Error::ZeroRange);
|
||||
} else if n < degree {
|
||||
return Err(Error::TooLargeDegree(n + 1, degree));
|
||||
}
|
||||
|
||||
let idx = {
|
||||
let idx = self
|
||||
.floor(t)
|
||||
.unwrap_or_else(|| self.floor(self[0]).unwrap());
|
||||
if idx == n {
|
||||
n - self.multiplicity(n)
|
||||
} else {
|
||||
idx
|
||||
}
|
||||
};
|
||||
let mut res = vec![0.0; n];
|
||||
res[idx] = 1.0;
|
||||
|
||||
for k in 1..=degree {
|
||||
let base = if idx < k { 0 } else { idx - k };
|
||||
let delta = self[base + k] - self[base];
|
||||
let max = if idx + k < n { idx } else { n - k - 1 };
|
||||
let mut a = inv_or_zero(delta) * (t - self[base]);
|
||||
for i in base..=max {
|
||||
let delta = self[i + k + 1] - self[i + 1];
|
||||
let b = inv_or_zero(delta) * (self[i + k + 1] - t);
|
||||
res[i] = a * res[i] + b * res[i + 1];
|
||||
a = 1.0 - b;
|
||||
}
|
||||
}
|
||||
|
||||
res.truncate(n - degree);
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn maximum_points(&self, degree: usize) -> Vec<f64> {
|
||||
let n = self.len();
|
||||
let m = n - degree - 1;
|
||||
let range = self.range_length();
|
||||
const N: i32 = 100;
|
||||
|
||||
let mut res = vec![0.0; m];
|
||||
let mut max = vec![0.0; m];
|
||||
for i in 1..N {
|
||||
let t = self[0] + range * (i as f64) / (N as f64);
|
||||
let vals = self.try_bspline_basis_functions(degree, t).unwrap();
|
||||
for j in 0..m {
|
||||
if max[j] < vals[j] {
|
||||
max[j] = vals[j];
|
||||
res[j] = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
/// Transforms the knot vector
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use std::vec::Vec;
|
||||
/// use truck_geometry::prelude::KnotVec;
|
||||
/// let mut knot_vec = KnotVec::from(vec![1.0, 1.0, 2.0, 3.0, 4.0, 5.0, 5.0]);
|
||||
/// knot_vec.transform(2.0, 3.0);
|
||||
/// let res : Vec<f64> = knot_vec.into();
|
||||
/// assert_eq!(res, vec![5.0, 5.0, 7.0, 9.0, 11.0, 13.0, 13.0]);
|
||||
/// ```
|
||||
/// # Panics
|
||||
/// Panic occurs if `scalar` is not positive.
|
||||
pub fn transform(&mut self, scalar: f64, r#move: f64) -> &mut Self {
|
||||
assert!(scalar > 0.0, "The scalar {scalar} is not positive.");
|
||||
self.0
|
||||
.iter_mut()
|
||||
.for_each(move |vec| *vec = *vec * scalar + r#move);
|
||||
self
|
||||
}
|
||||
|
||||
/// Normalizes the knot vector i.e. makes the first value 0 and the last value 1.
|
||||
/// # Failures
|
||||
/// Returns [`Error::ZeroRange`] if the range of the knot vector is so small.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::KnotVec;
|
||||
/// let mut knot_vec = KnotVec::from(vec![1.0, 1.0, 2.0, 3.0, 4.0, 5.0, 5.0]);
|
||||
/// knot_vec.try_normalize().unwrap();
|
||||
/// let res : Vec<f64> = knot_vec.into();
|
||||
/// assert_eq!(res, vec![0.0, 0.0, 0.25, 0.5, 0.75, 1.0, 1.0]);
|
||||
/// ```
|
||||
pub fn try_normalize(&mut self) -> Result<&mut Self> {
|
||||
let range = self.range_length();
|
||||
if range.so_small() {
|
||||
return Err(Error::ZeroRange);
|
||||
}
|
||||
Ok(self.transform(1.0 / range, -self[0] / range))
|
||||
}
|
||||
|
||||
/// Normalizes the knot vector i.e. makes the first value 0 and the last value 1.
|
||||
/// # Panics
|
||||
/// Panic occurs if the range of the knot vector is so small.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::KnotVec;
|
||||
/// let mut knot_vec = KnotVec::from(vec![1.0, 1.0, 2.0, 3.0, 4.0, 5.0, 5.0]);
|
||||
/// knot_vec.normalize();
|
||||
/// let res : Vec<f64> = knot_vec.into();
|
||||
/// assert_eq!(res, vec![0.0, 0.0, 0.25, 0.5, 0.75, 1.0, 1.0]);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn normalize(&mut self) -> &mut Self {
|
||||
self.try_normalize()
|
||||
.unwrap_or_else(|error| panic!("{}", error))
|
||||
}
|
||||
|
||||
/// Translates the knot vector
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use std::vec::Vec;
|
||||
/// use truck_geometry::prelude::KnotVec;
|
||||
/// let mut knot_vec = KnotVec::from(vec![1.0, 1.0, 2.0, 3.0, 4.0, 5.0, 5.0]);
|
||||
/// knot_vec.translate(3.0);
|
||||
/// let res : Vec<f64> = knot_vec.into();
|
||||
/// assert_eq!(res, vec![4.0, 4.0, 5.0, 6.0, 7.0, 8.0, 8.0]);
|
||||
/// ```
|
||||
pub fn translate(&mut self, x: f64) -> &mut Self { self.transform(1.0, x) }
|
||||
|
||||
/// Inverts the knot vector
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use std::vec::Vec;
|
||||
/// use truck_geometry::prelude::KnotVec;
|
||||
/// let mut knot_vec = KnotVec::from(vec![1.0, 1.0, 1.0, 3.0, 5.0, 6.0]);
|
||||
/// knot_vec.invert();
|
||||
/// let res : Vec<f64> = knot_vec.into();
|
||||
/// assert_eq!(res, vec![1.0, 2.0, 4.0, 6.0, 6.0, 6.0]);
|
||||
///
|
||||
/// ```
|
||||
pub fn invert(&mut self) -> &mut Self {
|
||||
let n = self.len();
|
||||
if n == 0 {
|
||||
return self;
|
||||
}
|
||||
let range = self[0] + self[n - 1];
|
||||
let clone = self.0.clone();
|
||||
for (knot1, knot0) in clone.iter().rev().zip(&mut self.0) {
|
||||
*knot0 = range - knot1;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Determines the knot vector is clamped for the given degree.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::KnotVec;
|
||||
/// let knot_vec = KnotVec::from(vec![0.0, 0.0, 0.0, 0.25, 0.5, 0.75, 1.0, 1.0, 1.0]);
|
||||
/// assert!(knot_vec.is_clamped(2));
|
||||
/// assert!(!knot_vec.is_clamped(3));
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn is_clamped(&self, degree: usize) -> bool {
|
||||
self.multiplicity(0) > degree && self.multiplicity(self.len() - 1) > degree
|
||||
}
|
||||
|
||||
/// Concats two knot vectors.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::KnotVec;
|
||||
/// let mut knot_vec0 = KnotVec::from(vec![0.0, 0.0, 0.0, 1.0, 1.0, 1.0]);
|
||||
/// let knot_vec1 = KnotVec::from(vec![1.0, 1.0, 1.0, 2.0, 2.0, 2.0]);
|
||||
/// knot_vec0.try_concat(&knot_vec1, 2).unwrap();
|
||||
/// assert_eq!(knot_vec0.as_slice(), &[0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0]);
|
||||
/// ```
|
||||
/// # Failures
|
||||
/// - If at least one of `self` or `other` is not clamped, returns [`Error::NotClampedKnotVector`]
|
||||
/// - If the last knot of `self` and the first knot of `other` are different, returns [`Error::DifferentBackFront`].
|
||||
pub fn try_concat(&mut self, other: &KnotVec, degree: usize) -> Result<&mut Self> {
|
||||
if !self.is_clamped(degree) || !other.is_clamped(degree) {
|
||||
return Err(Error::NotClampedKnotVector);
|
||||
}
|
||||
let back = self.0.last().unwrap();
|
||||
let front = other.0.first().unwrap();
|
||||
if front < back || !front.near(back) {
|
||||
return Err(Error::DifferentBackFront(*back, *front));
|
||||
}
|
||||
|
||||
self.0.truncate(self.len() - degree - 1);
|
||||
for knot in &other.0 {
|
||||
self.0.push(*knot);
|
||||
}
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Concats two knot vectors.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::KnotVec;
|
||||
/// let mut knot_vec0 = KnotVec::from(vec![0.0, 0.0, 0.0, 1.0, 1.0, 1.0]);
|
||||
/// let knot_vec1 = KnotVec::from(vec![1.0, 1.0, 1.0, 2.0, 2.0, 2.0]);
|
||||
/// knot_vec0.concat(&knot_vec1, 2);
|
||||
/// assert_eq!(knot_vec0.as_slice(), &[0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0]);
|
||||
/// ```
|
||||
/// # Panics
|
||||
/// Panic occurs if:
|
||||
/// - at least one of `self` or `other` is not clamped.
|
||||
/// - the last knot of `self` and the first knot of `other` are different.
|
||||
#[inline(always)]
|
||||
pub fn concat(&mut self, other: &KnotVec, degree: usize) -> &mut Self {
|
||||
self.try_concat(other, degree)
|
||||
.unwrap_or_else(|error| panic!("{}", error))
|
||||
}
|
||||
|
||||
/// Returns trimmed vector by the specified range.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let knot_vec = KnotVec::from(vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0]);
|
||||
/// let sub_vec = knot_vec.sub_vec(1..3);
|
||||
/// assert_eq!(sub_vec, KnotVec::from(vec![1.0, 2.0]));
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn sub_vec<I: SliceIndex<[f64], Output = [f64]>>(&self, range: I) -> KnotVec {
|
||||
KnotVec(Vec::from(&self.0[range]))
|
||||
}
|
||||
|
||||
/// To single-multi description. i.e. decompose the unique vector of knots and the vector of
|
||||
/// multiplicity of knots.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::KnotVec;
|
||||
/// let knot_vec = KnotVec::from(vec![0.0, 0.0, 0.0, 1.0, 2.0, 2.0, 2.0, 2.0, 3.0, 3.0]);
|
||||
/// let (knots, mults) = knot_vec.to_single_multi();
|
||||
/// assert_eq!(knots, vec![0.0, 1.0, 2.0, 3.0]);
|
||||
/// assert_eq!(mults, vec![3, 1, 4, 2]);
|
||||
/// ```
|
||||
pub fn to_single_multi(&self) -> (Vec<f64>, Vec<usize>) {
|
||||
let mut knots = Vec::new();
|
||||
let mut mults = Vec::new();
|
||||
|
||||
let mut iter = self.as_slice().iter().peekable();
|
||||
let mut mult = 1;
|
||||
while let Some(knot) = iter.next() {
|
||||
if let Some(next) = iter.peek() {
|
||||
if knot.near(next) {
|
||||
mult += 1;
|
||||
} else {
|
||||
knots.push(*knot);
|
||||
mults.push(mult);
|
||||
mult = 1;
|
||||
}
|
||||
} else {
|
||||
knots.push(*knot);
|
||||
mults.push(mult);
|
||||
}
|
||||
}
|
||||
(knots, mults)
|
||||
}
|
||||
|
||||
/// Constructs from single-multi description.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::KnotVec;
|
||||
/// let knots = vec![0.0, 1.0, 2.0, 3.0];
|
||||
/// let mults = vec![3, 1, 4, 2];
|
||||
/// let knot_vec = KnotVec::from_single_multi(knots, mults).unwrap();
|
||||
/// assert_eq!(knot_vec, KnotVec::from(vec![0.0, 0.0, 0.0, 1.0, 2.0, 2.0, 2.0, 2.0, 3.0, 3.0]));
|
||||
/// ```
|
||||
pub fn from_single_multi(knots: Vec<f64>, mults: Vec<usize>) -> Result<KnotVec> {
|
||||
for i in 1..knots.len() {
|
||||
if knots[i - 1] > knots[i] {
|
||||
return Err(Error::NotSortedVector);
|
||||
}
|
||||
}
|
||||
|
||||
let mut vec = Vec::new();
|
||||
for i in 0..knots.len() {
|
||||
for _ in 0..mults[i] {
|
||||
vec.push(knots[i]);
|
||||
}
|
||||
}
|
||||
Ok(KnotVec(vec))
|
||||
}
|
||||
/// Constructs from `Vec<f64>`. do not sort, only check sorted.
|
||||
pub fn try_from(vec: Vec<f64>) -> Result<KnotVec> {
|
||||
for i in 1..vec.len() {
|
||||
if vec[i - 1] > vec[i] {
|
||||
return Err(Error::NotSortedVector);
|
||||
}
|
||||
}
|
||||
Ok(KnotVec(vec))
|
||||
}
|
||||
|
||||
/// Constructs the knot vector for the bezier spline.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// assert_eq!(
|
||||
/// *KnotVec::bezier_knot(3),
|
||||
/// vec![0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0],
|
||||
/// );
|
||||
/// ```
|
||||
pub fn bezier_knot(degree: usize) -> KnotVec {
|
||||
let mut vec = vec![0.0; degree + 1];
|
||||
vec.extend(std::iter::repeat(1.0).take(degree + 1));
|
||||
KnotVec(vec)
|
||||
}
|
||||
|
||||
/// Constructs the uniform knot vector
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// assert_eq!(
|
||||
/// *KnotVec::uniform_knot(2, 5),
|
||||
/// vec![0.0, 0.0, 0.0, 0.2, 0.4, 0.6, 0.8, 1.0, 1.0, 1.0],
|
||||
/// );
|
||||
/// ```
|
||||
pub fn uniform_knot(degree: usize, division: usize) -> KnotVec {
|
||||
let mut vec = vec![0.0; degree + 1];
|
||||
vec.extend((1..division).map(|i| (i as f64) / (division as f64)));
|
||||
vec.extend(std::iter::repeat(1.0).take(degree + 1));
|
||||
KnotVec(vec)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<f64>> for KnotVec {
|
||||
/// constructs from `Vec<f64>`. The vector will sorted by the order.
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::KnotVec;
|
||||
/// let knot_vec = KnotVec::from(vec![1.0, 0.0, 3.0, 2.0]);
|
||||
/// let arr : Vec<f64> = knot_vec.into();
|
||||
/// assert_eq!(arr, vec![0.0, 1.0, 2.0, 3.0]);
|
||||
/// ```
|
||||
fn from(mut vec: Vec<f64>) -> KnotVec {
|
||||
vec.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
||||
KnotVec(vec)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Vec<f64>> for KnotVec {
|
||||
/// Constructs by the reference of vector. The clone of vector is sorted by the order.
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::KnotVec;
|
||||
/// let knot_vec = KnotVec::from(&vec![1.0, 0.0, 3.0, 2.0]);
|
||||
/// let arr : Vec<f64> = knot_vec.into();
|
||||
/// assert_eq!(arr, vec![0.0, 1.0, 2.0, 3.0]);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
fn from(vec: &Vec<f64>) -> KnotVec {
|
||||
let mut copy_vec = vec.clone();
|
||||
copy_vec.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
||||
KnotVec(copy_vec)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<KnotVec> for Vec<f64> {
|
||||
/// `KnotVec` into `Vec<f64>`
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::KnotVec;
|
||||
/// let vec = vec![0.0, 1.0, 2.0, 3.0];
|
||||
/// let knot_vec = KnotVec::from(&vec);
|
||||
/// let vec0 : Vec<f64> = knot_vec.into();
|
||||
/// assert_eq!(vec, vec0);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
fn from(knotvec: KnotVec) -> Vec<f64> { knotvec.0 }
|
||||
}
|
||||
|
||||
impl FromIterator<f64> for KnotVec {
|
||||
#[inline(always)]
|
||||
fn from_iter<I: IntoIterator<Item = f64>>(iter: I) -> KnotVec {
|
||||
KnotVec::try_from(iter.into_iter().collect::<Vec<_>>()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a KnotVec {
|
||||
type Item = &'a f64;
|
||||
type IntoIter = std::slice::Iter<'a, f64>;
|
||||
#[inline(always)]
|
||||
fn into_iter(self) -> Self::IntoIter { self.0.iter() }
|
||||
}
|
||||
|
||||
impl std::ops::Deref for KnotVec {
|
||||
type Target = Vec<f64>;
|
||||
#[inline(always)]
|
||||
fn deref(&self) -> &Vec<f64> { &self.0 }
|
||||
}
|
||||
|
||||
impl AsRef<[f64]> for KnotVec {
|
||||
#[inline(always)]
|
||||
fn as_ref(&self) -> &[f64] { &self.0 }
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for KnotVec {
|
||||
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
||||
where D: serde::Deserializer<'de> {
|
||||
let vec = Vec::<f64>::deserialize(deserializer)?;
|
||||
Self::try_from(vec).map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
-135
@@ -1,135 +0,0 @@
|
||||
use crate::{prelude::*, *};
|
||||
use truck_base::cgmath64::control_point::ControlPoint;
|
||||
|
||||
/// knot vector
|
||||
#[derive(Clone, PartialEq, Debug, Default, Serialize)]
|
||||
pub struct KnotVec(Vec<f64>);
|
||||
|
||||
/// B-spline curve
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
///
|
||||
/// // the knot vector
|
||||
/// let knot_vec = KnotVec::from(
|
||||
/// vec![0.0, 0.0, 0.0, 0.25, 0.25, 0.5, 0.5, 0.75, 0.75, 1.0, 1.0, 1.0]
|
||||
/// );
|
||||
///
|
||||
/// // sign up the control points in the vector of all points
|
||||
/// let ctrl_pts = vec![ // the vector of the indices of control points
|
||||
/// Vector4::new(0.0, -2.0, 0.0, 2.0),
|
||||
/// Vector4::new(1.0, -1.0, 0.0, 1.0),
|
||||
/// Vector4::new(1.0, 0.0, 0.0, 1.0),
|
||||
/// Vector4::new(1.0, 1.0, 0.0, 1.0),
|
||||
/// Vector4::new(0.0, 2.0, 0.0, 2.0),
|
||||
/// Vector4::new(-1.0, 1.0, 0.0, 1.0),
|
||||
/// Vector4::new(-1.0, 0.0, 0.0, 1.0),
|
||||
/// Vector4::new(-1.0, -1.0, 0.0, 1.0),
|
||||
/// Vector4::new(0.0, -2.0, 0.0, 2.0),
|
||||
/// ];
|
||||
///
|
||||
/// // construct the B-spline curve
|
||||
/// let bspline = BSplineCurve::new(knot_vec, ctrl_pts);
|
||||
///
|
||||
/// // This B-spline curve is a nurbs representation of the unit circle.
|
||||
/// const N : usize = 100; // sample size in test
|
||||
/// for i in 0..N {
|
||||
/// let t = 1.0 / (N as f64) * (i as f64);
|
||||
/// let v = bspline.subs(t); // We can use the instances as a function.
|
||||
/// let c = (v[0] / v[3]).powi(2) + (v[1] / v[3]).powi(2);
|
||||
/// assert_near2!(c, 1.0);
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Clone, PartialEq, Debug, Serialize)]
|
||||
pub struct BSplineCurve<P> {
|
||||
knot_vec: KnotVec, // the knot vector
|
||||
control_points: Vec<P>, // the indices of control points
|
||||
}
|
||||
|
||||
/// B-spline surface
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// const N : usize = 100; // sample size in test
|
||||
///
|
||||
/// // the knot vectors
|
||||
/// let knot_vec0 = KnotVec::bezier_knot(3);
|
||||
/// let knot_vec1 = KnotVec::from(
|
||||
/// vec![0.0, 0.0, 0.0, 0.0, 0.5, 0.5, 0.5, 1.0, 1.0, 1.0, 1.0]
|
||||
/// );
|
||||
/// let knot_vecs = (knot_vec0, knot_vec1);
|
||||
///
|
||||
/// // the control points
|
||||
/// let mut v = vec![vec![Vector4::zero(); 7]; 4];
|
||||
/// v[0][0] = Vector4::new(0.0, 0.0, 1.0, 1.0);
|
||||
/// v[0][1] = &v[0][0] / 3.0;
|
||||
/// v[0][2] = v[0][1].clone();
|
||||
/// v[0][3] = v[0][0].clone();
|
||||
/// v[0][4] = v[0][1].clone();
|
||||
/// v[0][5] = v[0][1].clone();
|
||||
/// v[0][6] = v[0][0].clone();
|
||||
/// v[1][0] = Vector4::new(2.0, 0.0, 1.0, 1.0) / 3.0;
|
||||
/// v[1][1] = Vector4::new(2.0, 4.0, 1.0, 1.0) / 9.0;
|
||||
/// v[1][2] = Vector4::new(-2.0, 4.0, 1.0, 1.0) / 9.0;
|
||||
/// v[1][3] = Vector4::new(-2.0, 0.0, 1.0, 1.0) / 3.0;
|
||||
/// v[1][4] = Vector4::new(-2.0, -4.0, 1.0, 1.0) / 9.0;
|
||||
/// v[1][5] = Vector4::new(2.0, -4.0, 1.0, 1.0) / 9.0;
|
||||
/// v[1][6] = Vector4::new(2.0, 0.0, 1.0, 1.0) / 3.0;
|
||||
/// v[2][0] = Vector4::new(2.0, 0.0, -1.0, 1.0) / 3.0;
|
||||
/// v[2][1] = Vector4::new(2.0, 4.0, -1.0, 1.0) / 9.0;
|
||||
/// v[2][2] = Vector4::new(-2.0, 4.0, -1.0, 1.0) / 9.0;
|
||||
/// v[2][3] = Vector4::new(-2.0, 0.0, -1.0, 1.0) / 3.0;
|
||||
/// v[2][4] = Vector4::new(-2.0, -4.0, -1.0, 1.0) / 9.0;
|
||||
/// v[2][5] = Vector4::new(2.0, -4.0, -1.0, 1.0) / 9.0;
|
||||
/// v[2][6] = Vector4::new(2.0, 0.0, -1.0, 1.0) / 3.0;
|
||||
/// v[3][0] = Vector4::new(0.0, 0.0, -1.0, 1.0);
|
||||
/// v[3][1] = &v[3][0] / 3.0;
|
||||
/// v[3][2] = v[3][1].clone();
|
||||
/// v[3][3] = v[3][0].clone();
|
||||
/// v[3][4] = v[3][1].clone();
|
||||
/// v[3][5] = v[3][1].clone();
|
||||
/// v[3][6] = v[3][0].clone();
|
||||
///
|
||||
/// // cunstruct the B-spline curve
|
||||
/// let bspline = BSplineSurface::new(knot_vecs, v);
|
||||
///
|
||||
/// // This B-spline curve is a nurbs representation of the unit sphere.
|
||||
/// for i in 0..N {
|
||||
/// for j in 0..N {
|
||||
/// let u = 1.0 / (N as f64) * (i as f64);
|
||||
/// let v = 1.0 / (N as f64) * (j as f64);
|
||||
/// let v = bspline.subs(u, v); // We can use the instances as a function.
|
||||
/// let c = (v[0] / v[3]).powi(2) + (v[1] / v[3]).powi(2) + (v[2] / v[3]).powi(2);
|
||||
/// assert_near2!(c, 1.0);
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Clone, PartialEq, Debug, Serialize)]
|
||||
pub struct BSplineSurface<P> {
|
||||
knot_vecs: (KnotVec, KnotVec),
|
||||
control_points: Vec<Vec<P>>,
|
||||
}
|
||||
|
||||
/// NURBS curve
|
||||
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
|
||||
pub struct NurbsCurve<V>(BSplineCurve<V>);
|
||||
|
||||
/// NURBS surface
|
||||
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
|
||||
pub struct NurbsSurface<V>(BSplineSurface<V>);
|
||||
|
||||
mod bspcurve;
|
||||
mod bspsurface;
|
||||
mod knot_vec;
|
||||
mod nurbscurve;
|
||||
mod nurbssurface;
|
||||
|
||||
#[doc(hidden)]
|
||||
#[inline(always)]
|
||||
pub fn inv_or_zero(delta: f64) -> f64 {
|
||||
if delta.so_small() {
|
||||
0.0
|
||||
} else {
|
||||
1.0 / delta
|
||||
}
|
||||
}
|
||||
-619
@@ -1,619 +0,0 @@
|
||||
use super::*;
|
||||
|
||||
impl<V> NurbsCurve<V> {
|
||||
/// Constructs the rationalized B-spline curve.
|
||||
#[inline(always)]
|
||||
pub const fn new(curve: BSplineCurve<V>) -> Self { NurbsCurve(curve) }
|
||||
|
||||
/// Returns the BSpline curve before rationalized.
|
||||
#[inline(always)]
|
||||
pub const fn non_rationalized(&self) -> &BSplineCurve<V> { &self.0 }
|
||||
|
||||
/// Returns the BSpline curve before rationalized.
|
||||
#[inline(always)]
|
||||
pub fn into_non_rationalized(self) -> BSplineCurve<V> { self.0 }
|
||||
|
||||
/// Returns the reference of the knot vector. cf.[`BSplineCurve::knot_vec`]
|
||||
#[inline(always)]
|
||||
pub const fn knot_vec(&self) -> &KnotVec { &self.0.knot_vec }
|
||||
|
||||
/// Returns the `idx`th knot. cf.[`BSplineCurve::knot`]
|
||||
#[inline(always)]
|
||||
pub fn knot(&self, idx: usize) -> f64 { self.0.knot_vec[idx] }
|
||||
|
||||
/// Returns the reference of the control points. cf.[`BSplineCurve::control_points`]
|
||||
#[inline(always)]
|
||||
pub const fn control_points(&self) -> &Vec<V> { &self.0.control_points }
|
||||
|
||||
/// Returns the reference of the control point corresponding to the index `idx`.
|
||||
/// cf.[`BSplineCurve::control_point`]
|
||||
#[inline(always)]
|
||||
pub fn control_point(&self, idx: usize) -> &V { &self.0.control_points[idx] }
|
||||
|
||||
/// Returns the mutable reference of the control point corresponding to index `idx`.
|
||||
/// cf.[`BSplineCurve::control_point_mut`]
|
||||
#[inline(always)]
|
||||
pub fn control_point_mut(&mut self, idx: usize) -> &mut V { &mut self.0.control_points[idx] }
|
||||
|
||||
/// Returns the iterator on all control points. cf.[`BSplineCurve::control_points_mut`]
|
||||
#[inline(always)]
|
||||
pub fn control_points_mut(&mut self) -> impl Iterator<Item = &mut V> {
|
||||
self.0.control_points.iter_mut()
|
||||
}
|
||||
|
||||
/// Applies the given transformation to all control points. cf.[`BSplineCurve::transform_control_points`]
|
||||
#[inline(always)]
|
||||
pub fn transform_control_points<F: FnMut(&mut V)>(&mut self, f: F) {
|
||||
self.0.transform_control_points(f)
|
||||
}
|
||||
|
||||
/// Returns the degree of NURBS curve. cf.[`BSplineCurve::degree`]
|
||||
#[inline(always)]
|
||||
pub fn degree(&self) -> usize { self.0.degree() }
|
||||
|
||||
/// Inverts a curve. cf.[`BSplineCurve::invert`]
|
||||
#[inline(always)]
|
||||
pub fn invert(&mut self) -> &mut Self {
|
||||
self.0.invert();
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns whether the knot vector is clamped or not. cf.[`BSplineCurve::is_clamped`]
|
||||
#[inline(always)]
|
||||
pub fn is_clamped(&self) -> bool { self.0.knot_vec.is_clamped(self.0.degree()) }
|
||||
|
||||
/// Normalizes the knot vector. cf.[`BSplineCurve::knot_normalize`]
|
||||
#[inline(always)]
|
||||
pub fn knot_normalize(&mut self) -> &mut Self {
|
||||
self.0.knot_vec.try_normalize().unwrap();
|
||||
self
|
||||
}
|
||||
|
||||
/// Translates the knot vector. cf.[`BSplineCurve::knot_translate`]
|
||||
#[inline(always)]
|
||||
pub fn knot_translate(&mut self, x: f64) -> &mut Self {
|
||||
self.0.knot_vec.translate(x);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Homogeneous<f64>> NurbsCurve<V> {
|
||||
/// Constructs a rationalization curve from the non-rationalized curve and weights.
|
||||
/// # Failures
|
||||
/// the length of `curve.control_points()` and `weights` must be the same.
|
||||
#[inline(always)]
|
||||
pub fn try_from_bspline_and_weights(
|
||||
curve: BSplineCurve<V::Point>,
|
||||
weights: Vec<f64>,
|
||||
) -> Result<Self> {
|
||||
let BSplineCurve {
|
||||
knot_vec,
|
||||
control_points,
|
||||
} = curve;
|
||||
if control_points.len() != weights.len() {
|
||||
return Err(Error::DifferentLength);
|
||||
}
|
||||
let control_points = control_points
|
||||
.into_iter()
|
||||
.zip(weights)
|
||||
.map(|(pt, w)| V::from_point_weight(pt, w))
|
||||
.collect();
|
||||
Ok(Self(BSplineCurve::new_unchecked(knot_vec, control_points)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V>> NurbsCurve<V> {
|
||||
/// Returns the closure of substitution.
|
||||
#[inline(always)]
|
||||
pub fn get_closure(&self) -> impl Fn(f64) -> V::Point + '_ { move |t| self.subs(t) }
|
||||
}
|
||||
|
||||
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V>> NurbsCurve<V>
|
||||
where V::Point: Tolerance
|
||||
{
|
||||
/// Returns whether all control points are the same or not.
|
||||
/// If the knot vector is clamped, it means whether the curve is constant or not.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
///
|
||||
/// let knot_vec = KnotVec::bezier_knot(2);
|
||||
/// let pt = Vector3::new(1.0, 2.0, 1.0);
|
||||
/// // allows differences upto scalars
|
||||
/// let mut ctrl_pts = vec![pt.clone(), pt.clone() * 2.0, pt.clone() * 3.0];
|
||||
/// let bspcurve = BSplineCurve::new(knot_vec.clone(), ctrl_pts.clone());
|
||||
/// assert!(!bspcurve.is_const());
|
||||
/// let const_curve = NurbsCurve::new(bspcurve);
|
||||
/// assert!(const_curve.is_const());
|
||||
///
|
||||
/// ctrl_pts.push(Vector3::new(2.0, 3.0, 1.0));
|
||||
/// let curve = NurbsCurve::new(BSplineCurve::new(knot_vec.clone(), ctrl_pts.clone()));
|
||||
/// assert!(!curve.is_const());
|
||||
/// ```
|
||||
/// # Remarks
|
||||
/// If the knot vector is not clamped and the BSpline basis function is not partition of unity,
|
||||
/// then perhaps returns true even if the curve is not constant.
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let knot_vec = KnotVec::uniform_knot(1, 5);
|
||||
/// let ctrl_pts = vec![Vector2::new(1.0, 2.0), Vector2::new(1.0, 2.0)];
|
||||
/// let bspcurve = BSplineCurve::new(knot_vec, ctrl_pts);
|
||||
///
|
||||
/// // bspcurve is not constant.
|
||||
/// assert_eq!(bspcurve.subs(0.0), Vector2::new(0.0, 0.0));
|
||||
/// assert_ne!(bspcurve.subs(0.5), Vector2::new(0.0, 0.0));
|
||||
///
|
||||
/// // bspcurve.is_const() is true
|
||||
/// assert!(bspcurve.is_const());
|
||||
/// ```
|
||||
pub fn is_const(&self) -> bool {
|
||||
let pt = self.0.control_points[0].to_point();
|
||||
self.0
|
||||
.control_points
|
||||
.iter()
|
||||
.all(move |vec| vec.to_point().near(&pt))
|
||||
}
|
||||
/// Determines whether `self` and `other` is near as the B-spline curves or not.
|
||||
///
|
||||
/// Divides each knot interval into the number of degree equal parts,
|
||||
/// and check `|self(t) - other(t)| < TOLERANCE` for each end points `t`.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let knot_vec = KnotVec::from(
|
||||
/// vec![0.0, 0.0, 0.0, 1.0, 2.0, 3.0, 4.0, 4.0, 4.0]
|
||||
/// );
|
||||
/// let ctrl_pts = vec![
|
||||
/// Vector3::new(1.0, 1.0, 1.0),
|
||||
/// Vector3::new(3.0, 2.0, 2.0),
|
||||
/// Vector3::new(2.0, 3.0, 1.0),
|
||||
/// Vector3::new(4.0, 5.0, 2.0),
|
||||
/// Vector3::new(5.0, 4.0, 1.0),
|
||||
/// Vector3::new(1.0, 1.0, 2.0),
|
||||
/// ];
|
||||
/// let curve0 = NurbsCurve::new(BSplineCurve::new(knot_vec, ctrl_pts));
|
||||
/// let mut curve1 = curve0.clone();
|
||||
/// assert!(curve0.near_as_curve(&curve1));
|
||||
/// *curve1.control_point_mut(1) += Vector3::new(0.01, 0.0002, 0.0);
|
||||
/// assert!(!curve0.near_as_curve(&curve1));
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn near_as_curve(&self, other: &Self) -> bool {
|
||||
self.0
|
||||
.sub_near_as_curve(&other.0, 2, move |x, y| x.to_point().near(&y.to_point()))
|
||||
}
|
||||
|
||||
/// Determines `self` and `other` is near in square order as the B-spline curves or not.
|
||||
///
|
||||
/// Divide each knot interval into the number of degree equal parts,
|
||||
/// and check `|self(t) - other(t)| < TOLERANCE`for each end points `t`.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let knot_vec = KnotVec::from(
|
||||
/// vec![0.0, 0.0, 0.0, 1.0, 2.0, 3.0, 4.0, 4.0, 4.0]
|
||||
/// );
|
||||
/// let ctrl_pts = vec![
|
||||
/// Vector3::new(1.0, 1.0, 1.0),
|
||||
/// Vector3::new(3.0, 2.0, 2.0),
|
||||
/// Vector3::new(2.0, 3.0, 1.0),
|
||||
/// Vector3::new(4.0, 5.0, 2.0),
|
||||
/// Vector3::new(5.0, 4.0, 1.0),
|
||||
/// Vector3::new(1.0, 1.0, 2.0),
|
||||
/// ];
|
||||
/// let curve0 = NurbsCurve::new(BSplineCurve::new(knot_vec, ctrl_pts));
|
||||
/// let mut curve1 = curve0.clone();
|
||||
/// assert!(curve0.near_as_curve(&curve1));
|
||||
/// *curve1.control_point_mut(1) += Vector3::new(0.01, TOLERANCE, 0.0);
|
||||
/// assert!(!curve0.near2_as_curve(&curve1));
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn near2_as_curve(&self, other: &Self) -> bool {
|
||||
self.0
|
||||
.sub_near_as_curve(&other.0, 2, move |x, y| x.to_point().near2(&y.to_point()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V> + Tolerance> NurbsCurve<V> {
|
||||
/// Adds a knot `x`, and do not change `self` as a curve. cf.[`BSplineCurve::add_knot`]
|
||||
pub fn add_knot(&mut self, x: f64) -> &mut Self {
|
||||
self.0.add_knot(x);
|
||||
self
|
||||
}
|
||||
|
||||
/// Removes a knot corresponding to the indices `idx`, and do not change `self` as a curve.
|
||||
/// If cannot remove the knot, do not change `self` and return `self`.
|
||||
/// cf.[`BSplineCurve::remove_knot`]
|
||||
pub fn remove_knot(&mut self, idx: usize) -> &mut Self {
|
||||
let _ = self.try_remove_knot(idx);
|
||||
self
|
||||
}
|
||||
|
||||
/// Removes a knot corresponding to the indice `idx`, and do not change `self` as a curve.
|
||||
/// If the knot cannot be removed, returns [`Error::CannotRemoveKnot`].
|
||||
/// cf.[`BSplineCurve::try_remove_knot`]
|
||||
pub fn try_remove_knot(&mut self, idx: usize) -> Result<&mut Self> {
|
||||
self.0.try_remove_knot(idx)?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Elevates 1 degree. cf.[`BSplineCurve::elevate_degree`]
|
||||
pub fn elevate_degree(&mut self) -> &mut Self {
|
||||
self.0.elevate_degree();
|
||||
self
|
||||
}
|
||||
|
||||
/// Makes the NURBS curve clamped. cf.[`BSplineCurve::clamp`]
|
||||
#[inline(always)]
|
||||
pub fn clamp(&mut self) -> &mut Self {
|
||||
self.0.clamp();
|
||||
self
|
||||
}
|
||||
|
||||
/// Repeats `Self::try_remove_knot()` from the back knot in turn until the knot cannot be removed.
|
||||
/// cf.[`BSplineCurve::optimize`]
|
||||
pub fn optimize(&mut self) -> &mut Self {
|
||||
self.0.optimize();
|
||||
self
|
||||
}
|
||||
|
||||
/// Makes two splines having the same degrees. cf.[`BSplineCurve::syncro_degree`]
|
||||
pub fn syncro_degree(&mut self, other: &mut Self) {
|
||||
let (degree0, degree1) = (self.degree(), other.degree());
|
||||
for _ in degree0..degree1 {
|
||||
self.elevate_degree();
|
||||
}
|
||||
for _ in degree1..degree0 {
|
||||
other.elevate_degree();
|
||||
}
|
||||
}
|
||||
|
||||
/// Makes two splines having the same normalized knot vectors. cf.[`BSplineCurve::syncro_knots`]
|
||||
pub fn syncro_knots(&mut self, other: &mut Self) { self.0.syncro_knots(&mut other.0) }
|
||||
}
|
||||
|
||||
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V> + Tolerance> ParameterTransform
|
||||
for NurbsCurve<V>
|
||||
{
|
||||
#[inline(always)]
|
||||
fn parameter_transform(&mut self, scalar: f64, r#move: f64) -> &mut Self {
|
||||
self.0.parameter_transform(scalar, r#move);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V> + Tolerance> Cut for NurbsCurve<V> {
|
||||
#[inline(always)]
|
||||
fn cut(&mut self, t: f64) -> Self { NurbsCurve(self.0.cut(t)) }
|
||||
}
|
||||
|
||||
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V> + Tolerance> Concat<NurbsCurve<V>>
|
||||
for NurbsCurve<V>
|
||||
where <V as Homogeneous<f64>>::Point: Debug
|
||||
{
|
||||
type Output = NurbsCurve<V>;
|
||||
fn try_concat(
|
||||
&self,
|
||||
other: &Self,
|
||||
) -> std::result::Result<Self, ConcatError<<V as Homogeneous<f64>>::Point>> {
|
||||
let curve0 = self.clone();
|
||||
let mut curve1 = other.clone();
|
||||
let w0 = curve0.0.control_points.last().unwrap().weight();
|
||||
let w1 = curve1.0.control_points[0].weight();
|
||||
curve1.transform_control_points(|pt| *pt *= w0 / w1);
|
||||
match curve0.0.try_concat(&curve1.0) {
|
||||
Ok(curve) => Ok(NurbsCurve::new(curve)),
|
||||
Err(err) => Err(err.point_map(|v| v.to_point())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn concat_positive_test() {
|
||||
let mut part0 = NurbsCurve::new(BSplineCurve::new(
|
||||
KnotVec::uniform_knot(4, 4),
|
||||
(0..8)
|
||||
.map(|_| {
|
||||
Vector4::new(
|
||||
rand::random::<f64>(),
|
||||
rand::random::<f64>(),
|
||||
rand::random::<f64>(),
|
||||
rand::random::<f64>() + 0.5,
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
));
|
||||
let mut part1 = part0.cut(0.56);
|
||||
let w = 20.0 * rand::random::<f64>() - 10.0;
|
||||
part1.transform_control_points(|vec| *vec *= w);
|
||||
assert_near!(part0.back(), part1.front());
|
||||
concat_random_test(&part0, &part1, 10);
|
||||
}
|
||||
|
||||
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V> + Tolerance> NurbsCurve<V>
|
||||
where V::Point: Tolerance
|
||||
{
|
||||
/// Makes the rational curve locally injective.
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// const N : usize = 100; // sample size for test
|
||||
///
|
||||
/// let knot_vec = KnotVec::from(
|
||||
/// vec![0.0, 0.0, 0.0, 1.0, 3.0, 4.0, 4.0, 4.0]
|
||||
/// );
|
||||
/// let control_points = vec![
|
||||
/// Vector4::new(1.0, 0.0, 0.0, 1.0),
|
||||
/// Vector4::new(0.0, 1.0, 0.0, 1.0),
|
||||
/// Vector4::new(0.0, 2.0, 0.0, 2.0),
|
||||
/// Vector4::new(0.0, 3.0, 0.0, 3.0),
|
||||
/// Vector4::new(0.0, 0.0, 3.0, 3.0),
|
||||
/// ];
|
||||
///
|
||||
/// let mut curve = NurbsCurve::new(BSplineCurve::new(knot_vec, control_points));
|
||||
/// let mut flag = false;
|
||||
/// for i in 0..N {
|
||||
/// let t = 4.0 * (i as f64) / (N as f64);
|
||||
/// let pt0 = curve.subs(t);
|
||||
/// let pt1 = curve.subs(t + 1.0 / (N as f64));
|
||||
/// flag = flag || pt0.near(&pt1);
|
||||
/// }
|
||||
/// // There exists t such that bspcurve(t) == bspcurve(t + 0.01).
|
||||
/// assert!(flag);
|
||||
///
|
||||
/// curve.make_locally_injective().knot_normalize();
|
||||
/// let mut flag = false;
|
||||
/// for i in 0..N {
|
||||
/// let t = 1.0 * (i as f64) / (N as f64);
|
||||
/// let pt0 = curve.subs(t);
|
||||
/// let pt1 = curve.subs(t + 1.0 / (N as f64));
|
||||
/// flag = flag || pt0.near(&pt1);
|
||||
/// }
|
||||
/// // There does not exist t such that bspcurve(t) == bspcurve(t + 0.01).
|
||||
/// assert!(!flag);
|
||||
/// ```
|
||||
/// # Remarks
|
||||
/// If `self` is a constant curve, then does nothing.
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let knot_vec = KnotVec::from(vec![0.0, 0.0, 0.0, 1.0, 2.0, 2.0, 2.0]);
|
||||
/// let ctrl_pts = vec![
|
||||
/// Vector3::new(1.0, 1.0, 1.0),
|
||||
/// Vector3::new(2.0, 2.0, 2.0),
|
||||
/// Vector3::new(3.0, 3.0, 3.0),
|
||||
/// Vector3::new(4.0, 4.0, 4.0),
|
||||
/// ];
|
||||
/// let mut curve = NurbsCurve::new(BSplineCurve::new(knot_vec, ctrl_pts));
|
||||
/// let org_curve = curve.clone();
|
||||
/// curve.make_locally_injective();
|
||||
/// assert_eq!(curve, org_curve);
|
||||
/// ```
|
||||
pub fn make_locally_injective(&mut self) -> &mut Self {
|
||||
let mut iter = self.0.bezier_decomposition().into_iter();
|
||||
while let Some(bezier) = iter.next().map(NurbsCurve::new) {
|
||||
if !bezier.is_const() {
|
||||
*self = bezier;
|
||||
break;
|
||||
}
|
||||
}
|
||||
let mut x = 0.0;
|
||||
for mut bezier in iter.map(NurbsCurve::new) {
|
||||
if bezier.is_const() {
|
||||
x += bezier.0.knot_vec.range_length();
|
||||
} else {
|
||||
let s0 = self.0.control_points.last().unwrap().weight();
|
||||
let s1 = bezier.0.control_points[0].weight();
|
||||
bezier
|
||||
.0
|
||||
.control_points
|
||||
.iter_mut()
|
||||
.for_each(move |vec| *vec *= s0 / s1);
|
||||
self.concat(bezier.knot_translate(-x));
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V>> ParameterDivision1D for NurbsCurve<V>
|
||||
where V::Point: MetricSpace<Metric = f64> + HashGen<f64>
|
||||
{
|
||||
type Point = V::Point;
|
||||
#[inline(always)]
|
||||
fn parameter_division(&self, range: (f64, f64), tol: f64) -> (Vec<f64>, Vec<V::Point>) {
|
||||
algo::curve::parameter_division(self, range, tol)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V>> SearchNearestParameter<D1> for NurbsCurve<V>
|
||||
where
|
||||
V::Point: MetricSpace<Metric = f64>,
|
||||
<V::Point as EuclideanSpace>::Diff: InnerSpace + Tolerance,
|
||||
{
|
||||
type Point = V::Point;
|
||||
/// Searches the parameter `t` which minimize |self(t) - point| by Newton's method with initial guess `hint`.
|
||||
/// Returns `None` if the number of attempts exceeds `trial` i.e. if `trial == 0`, then the trial is only one time.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
///
|
||||
/// // Defines the half unit circle in x > 0.
|
||||
/// let knot_vec = KnotVec::bezier_knot(2);
|
||||
/// let ctrl_pts = vec![Vector3::new(0.0, -1.0, 1.0), Vector3::new(1.0, 0.0, 0.0), Vector3::new(0.0, 1.0, 1.0)];
|
||||
/// let curve = NurbsCurve::new(BSplineCurve::new(knot_vec, ctrl_pts));
|
||||
///
|
||||
/// // search rational nearest parameter
|
||||
/// let pt = Point2::new(1.0, 2.0);
|
||||
/// let hint = 0.8;
|
||||
/// let t = curve.search_nearest_parameter(pt, Some(hint), 100).unwrap();
|
||||
///
|
||||
/// // check the answer
|
||||
/// let res = curve.subs(t);
|
||||
/// let ans = Point2::from_vec(pt.to_vec().normalize());
|
||||
/// assert_near!(res, ans);
|
||||
/// ```
|
||||
/// # Remarks
|
||||
/// It may converge to a local solution depending on the hint.
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
///
|
||||
/// // Same curve and point as above example
|
||||
/// let knot_vec = KnotVec::bezier_knot(2);
|
||||
/// let ctrl_pts = vec![Vector3::new(0.0, -1.0, 1.0), Vector3::new(1.0, 0.0, 0.0), Vector3::new(0.0, 1.0, 1.0)];
|
||||
/// let curve = NurbsCurve::new(BSplineCurve::new(knot_vec, ctrl_pts));
|
||||
/// let pt = Point2::new(1.0, 2.0);
|
||||
///
|
||||
/// // another hint
|
||||
/// let hint = 0.5;
|
||||
///
|
||||
/// // Newton's method is vibration divergent.
|
||||
/// assert!(curve.search_nearest_parameter(pt, Some(hint), 100).is_none());
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
fn search_nearest_parameter<H: Into<SPHint1D>>(
|
||||
&self,
|
||||
point: V::Point,
|
||||
hint: H,
|
||||
trial: usize,
|
||||
) -> Option<f64> {
|
||||
let hint = match hint.into() {
|
||||
SPHint1D::Parameter(hint) => hint,
|
||||
SPHint1D::Range(x, y) => {
|
||||
algo::curve::presearch(self, point, (x, y), PRESEARCH_DIVISION)
|
||||
}
|
||||
SPHint1D::None => {
|
||||
algo::curve::presearch(self, point, self.range_tuple(), PRESEARCH_DIVISION)
|
||||
}
|
||||
};
|
||||
algo::curve::search_nearest_parameter(self, point, hint, trial)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V>> SearchParameter<D1> for NurbsCurve<V>
|
||||
where
|
||||
V::Point: MetricSpace<Metric = f64>,
|
||||
<V::Point as EuclideanSpace>::Diff: InnerSpace + Tolerance,
|
||||
{
|
||||
type Point = V::Point;
|
||||
#[inline(always)]
|
||||
fn search_parameter<H: Into<SPHint1D>>(
|
||||
&self,
|
||||
point: V::Point,
|
||||
hint: H,
|
||||
trial: usize,
|
||||
) -> Option<f64> {
|
||||
let hint = match hint.into() {
|
||||
SPHint1D::Parameter(hint) => hint,
|
||||
SPHint1D::Range(x, y) => {
|
||||
algo::curve::presearch(self, point, (x, y), PRESEARCH_DIVISION)
|
||||
}
|
||||
SPHint1D::None => {
|
||||
algo::curve::presearch(self, point, self.range_tuple(), PRESEARCH_DIVISION)
|
||||
}
|
||||
};
|
||||
algo::curve::search_parameter(self, point, hint, trial)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Homogeneous<f64>> NurbsCurve<V>
|
||||
where V::Point: Bounded<Scalar = f64>
|
||||
{
|
||||
/// Returns the bounding box including all control points.
|
||||
#[inline(always)]
|
||||
pub fn roughly_bounding_box(&self) -> BoundingBox<V::Point> {
|
||||
self.0.control_points.iter().map(|p| p.to_point()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V>> ParametricCurve for NurbsCurve<V> {
|
||||
type Point = V::Point;
|
||||
type Vector = <V::Point as EuclideanSpace>::Diff;
|
||||
#[inline(always)]
|
||||
fn subs(&self, t: f64) -> Self::Point { self.0.subs(t).to_point() }
|
||||
#[inline(always)]
|
||||
fn der(&self, t: f64) -> Self::Vector {
|
||||
let pt = self.0.subs(t);
|
||||
let der = self.0.der(t);
|
||||
pt.rat_der(der)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn der2(&self, t: f64) -> Self::Vector {
|
||||
let pt = self.0.subs(t);
|
||||
let der = self.0.der(t);
|
||||
let der2 = self.0.der2(t);
|
||||
pt.rat_der2(der, der2)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn parameter_range(&self) -> ParameterRange {
|
||||
(
|
||||
Bound::Included(self.0.knot_vec[0]),
|
||||
Bound::Included(self.0.knot_vec[self.0.knot_vec.len() - 1]),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V>> BoundedCurve for NurbsCurve<V> {}
|
||||
|
||||
impl<V: Clone> Invertible for NurbsCurve<V> {
|
||||
#[inline(always)]
|
||||
fn invert(&mut self) { self.invert(); }
|
||||
#[inline(always)]
|
||||
fn inverse(&self) -> Self {
|
||||
let mut curve = self.0.clone();
|
||||
curve.invert();
|
||||
NurbsCurve(curve)
|
||||
}
|
||||
}
|
||||
|
||||
impl<M, V: Copy> Transformed<M> for NurbsCurve<V>
|
||||
where M: Copy + std::ops::Mul<V, Output = V>
|
||||
{
|
||||
#[inline(always)]
|
||||
fn transform_by(&mut self, trans: M) {
|
||||
self.0
|
||||
.control_points
|
||||
.iter_mut()
|
||||
.for_each(move |v| *v = trans * *v)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Homogeneous<f64>> From<BSplineCurve<V::Point>> for NurbsCurve<V> {
|
||||
fn from(bspcurve: BSplineCurve<V::Point>) -> NurbsCurve<V> {
|
||||
NurbsCurve::new(BSplineCurve::new_unchecked(
|
||||
bspcurve.knot_vec,
|
||||
bspcurve
|
||||
.control_points
|
||||
.into_iter()
|
||||
.map(V::from_point)
|
||||
.collect(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parameter_division() {
|
||||
let knot_vec = KnotVec::uniform_knot(2, 3);
|
||||
let ctrl_pts = vec![
|
||||
Vector4::new(0.0, 0.0, 0.0, 1.0),
|
||||
Vector4::new(2.0, 0.0, 0.0, 2.0),
|
||||
Vector4::new(0.0, 3.0, 0.0, 3.0),
|
||||
Vector4::new(0.0, 0.0, 2.0, 2.0),
|
||||
Vector4::new(1.0, 1.0, 1.0, 1.0),
|
||||
];
|
||||
let curve = NurbsCurve::new(BSplineCurve::new(knot_vec, ctrl_pts));
|
||||
let tol = 0.01;
|
||||
let (div, pts) = curve.parameter_division(curve.range_tuple(), tol * 0.5);
|
||||
let knot_vec = curve.knot_vec();
|
||||
assert_eq!(knot_vec[0], div[0]);
|
||||
assert_eq!(knot_vec.range_length(), div.last().unwrap() - div[0]);
|
||||
for i in 1..div.len() {
|
||||
let pt0 = curve.subs(div[i - 1]);
|
||||
assert_eq!(pt0, pts[i - 1]);
|
||||
let pt1 = curve.subs(div[i]);
|
||||
assert_eq!(pt1, pts[i]);
|
||||
let value_middle = pt0.midpoint(pt1);
|
||||
let param_middle = curve.subs((div[i - 1] + div[i]) / 2.0);
|
||||
let dist = value_middle.distance(param_middle);
|
||||
assert!(dist < tol, "large distance: {dist}");
|
||||
}
|
||||
}
|
||||
-920
@@ -1,920 +0,0 @@
|
||||
use super::*;
|
||||
|
||||
impl<V> NurbsSurface<V> {
|
||||
/// constructor
|
||||
#[inline(always)]
|
||||
pub const fn new(bspsurface: BSplineSurface<V>) -> Self { NurbsSurface(bspsurface) }
|
||||
|
||||
/// Returns the nurbs surface before rationalized
|
||||
#[inline(always)]
|
||||
pub const fn non_rationalized(&self) -> &BSplineSurface<V> { &self.0 }
|
||||
/// Returns the nurbs surface before rationalized
|
||||
#[inline(always)]
|
||||
pub fn non_rationalized_mut(&mut self) -> &mut BSplineSurface<V> { &mut self.0 }
|
||||
|
||||
/// Returns the nurbs surface before rationalized
|
||||
#[inline(always)]
|
||||
pub fn into_non_rationalized(self) -> BSplineSurface<V> { self.0 }
|
||||
|
||||
/// Returns the reference of the knot vectors
|
||||
#[inline(always)]
|
||||
pub const fn knot_vecs(&self) -> &(KnotVec, KnotVec) { &self.0.knot_vecs }
|
||||
|
||||
/// Returns the u knot vector.
|
||||
#[inline(always)]
|
||||
pub const fn uknot_vec(&self) -> &KnotVec { &self.0.knot_vecs.0 }
|
||||
/// Returns the v knot vector.
|
||||
#[inline(always)]
|
||||
pub const fn vknot_vec(&self) -> &KnotVec { &self.0.knot_vecs.1 }
|
||||
|
||||
/// Returns the `idx`th u knot.
|
||||
#[inline(always)]
|
||||
pub fn uknot(&self, idx: usize) -> f64 { self.0.knot_vecs.0[idx] }
|
||||
/// returns the `idx`th v knot.
|
||||
#[inline(always)]
|
||||
pub fn vknot(&self, idx: usize) -> f64 { self.0.knot_vecs.1[idx] }
|
||||
|
||||
/// Returns the reference of the vector of the control points
|
||||
#[inline(always)]
|
||||
pub const fn control_points(&self) -> &Vec<Vec<V>> { &self.0.control_points }
|
||||
|
||||
/// Returns the reference of the control point corresponding to the index `(idx0, idx1)`.
|
||||
#[inline(always)]
|
||||
pub fn control_point(&self, idx0: usize, idx1: usize) -> &V {
|
||||
&self.0.control_points[idx0][idx1]
|
||||
}
|
||||
|
||||
/// Apply the given transformation to all control points.
|
||||
#[inline(always)]
|
||||
pub fn transform_control_points<F: FnMut(&mut V)>(&mut self, f: F) {
|
||||
self.0.transform_control_points(f)
|
||||
}
|
||||
|
||||
/// Returns the iterator over the control points in the `column_idx`th row.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let uknot_vec = KnotVec::bezier_knot(1);
|
||||
/// let vknot_vec = KnotVec::bezier_knot(2);
|
||||
/// let knot_vecs = (uknot_vec, vknot_vec);
|
||||
/// let ctrl_pts = vec![
|
||||
/// vec![Vector3::new(0.0, 0.0, 0.0), Vector3::new(1.0, 0.0, 1.0), Vector3::new(2.0, 0.0, 2.0)],
|
||||
/// vec![Vector3::new(0.0, 1.0, 0.0), Vector3::new(1.0, 1.0, 1.0), Vector3::new(2.0, 1.0, 2.0)],
|
||||
/// ];
|
||||
/// let bspsurface = BSplineSurface::new(knot_vecs, ctrl_pts);
|
||||
/// let mut iter = bspsurface.ctrl_pts_row_iter(1);
|
||||
/// assert_eq!(iter.next(), Some(&Vector3::new(1.0, 0.0, 1.0)));
|
||||
/// assert_eq!(iter.next(), Some(&Vector3::new(1.0, 1.0, 1.0)));
|
||||
/// assert_eq!(iter.next(), None);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn ctrl_pts_row_iter(
|
||||
&self,
|
||||
column_idx: usize,
|
||||
) -> impl ExactSizeIterator<Item = &V> + std::iter::FusedIterator<Item = &V> {
|
||||
self.0.ctrl_pts_row_iter(column_idx)
|
||||
}
|
||||
|
||||
/// Returns the iterator over the control points in the `row_idx`th row.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let uknot_vec = KnotVec::bezier_knot(1);
|
||||
/// let vknot_vec = KnotVec::bezier_knot(2);
|
||||
/// let knot_vecs = (uknot_vec, vknot_vec);
|
||||
/// let ctrl_pts = vec![
|
||||
/// vec![Vector3::new(0.0, 0.0, 0.0), Vector3::new(1.0, 0.0, 1.0), Vector3::new(2.0, 0.0, 2.0)],
|
||||
/// vec![Vector3::new(0.0, 1.0, 0.0), Vector3::new(1.0, 1.0, 1.0), Vector3::new(2.0, 1.0, 2.0)],
|
||||
/// ];
|
||||
/// let bspsurface = BSplineSurface::new(knot_vecs, ctrl_pts);
|
||||
/// let mut iter = bspsurface.ctrl_pts_column_iter(1);
|
||||
/// assert_eq!(iter.next(), Some(&Vector3::new(0.0, 1.0, 0.0)));
|
||||
/// assert_eq!(iter.next(), Some(&Vector3::new(1.0, 1.0, 1.0)));
|
||||
/// assert_eq!(iter.next(), Some(&Vector3::new(2.0, 1.0, 2.0)));
|
||||
/// assert_eq!(iter.next(), None);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn ctrl_pts_column_iter(&self, row_idx: usize) -> std::slice::Iter<'_, V> {
|
||||
self.0.control_points[row_idx].iter()
|
||||
}
|
||||
|
||||
/// Returns the mutable reference of the control point corresponding to index `(idx0, idx1)`.
|
||||
#[inline(always)]
|
||||
pub fn control_point_mut(&mut self, idx0: usize, idx1: usize) -> &mut V {
|
||||
&mut self.0.control_points[idx0][idx1]
|
||||
}
|
||||
|
||||
/// Returns the iterator on all control points
|
||||
#[inline(always)]
|
||||
pub fn control_points_mut(&mut self) -> impl Iterator<Item = &mut V> {
|
||||
self.0.control_points.iter_mut().flatten()
|
||||
}
|
||||
/// Returns the degrees of B-spline surface
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let uknot_vec = KnotVec::from(vec![0.0, 0.0, 1.0, 1.0]);
|
||||
/// let vknot_vec = KnotVec::from(vec![0.0, 0.0, 0.0, 1.0, 1.0, 1.0]);
|
||||
/// let knot_vecs = (uknot_vec, vknot_vec);
|
||||
/// let ctrl_pts = vec![
|
||||
/// vec![Vector3::new(0.0, 0.0, 0.0), Vector3::new(1.0, 0.0, 1.0), Vector3::new(2.0, 0.0, 2.0)],
|
||||
/// vec![Vector3::new(0.0, 1.0, 0.0), Vector3::new(1.0, 1.0, 1.0), Vector3::new(2.0, 1.0, 2.0)],
|
||||
/// ];
|
||||
/// let bspsurface = BSplineSurface::new(knot_vecs, ctrl_pts);
|
||||
/// assert_eq!(bspsurface.udegree(), 1);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn udegree(&self) -> usize { self.0.udegree() }
|
||||
|
||||
/// Returns the degrees of B-spline surface
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let uknot_vec = KnotVec::from(vec![0.0, 0.0, 1.0, 1.0]);
|
||||
/// let vknot_vec = KnotVec::from(vec![0.0, 0.0, 0.0, 1.0, 1.0, 1.0]);
|
||||
/// let knot_vecs = (uknot_vec, vknot_vec);
|
||||
/// let ctrl_pts = vec![
|
||||
/// vec![Vector3::new(0.0, 0.0, 0.0), Vector3::new(1.0, 0.0, 1.0), Vector3::new(2.0, 0.0, 2.0)],
|
||||
/// vec![Vector3::new(0.0, 1.0, 0.0), Vector3::new(1.0, 1.0, 1.0), Vector3::new(2.0, 1.0, 2.0)],
|
||||
/// ];
|
||||
/// let bspsurface = BSplineSurface::new(knot_vecs, ctrl_pts);
|
||||
/// assert_eq!(bspsurface.vdegree(), 2);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn vdegree(&self) -> usize { self.0.vdegree() }
|
||||
|
||||
/// Returns the degrees of B-spline surface
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let uknot_vec = KnotVec::from(vec![0.0, 0.0, 1.0, 1.0]);
|
||||
/// let vknot_vec = KnotVec::from(vec![0.0, 0.0, 0.0, 1.0, 1.0, 1.0]);
|
||||
/// let knot_vecs = (uknot_vec, vknot_vec);
|
||||
/// let ctrl_pts = vec![
|
||||
/// vec![Vector3::new(0.0, 0.0, 0.0), Vector3::new(1.0, 0.0, 1.0), Vector3::new(2.0, 0.0, 2.0)],
|
||||
/// vec![Vector3::new(0.0, 1.0, 0.0), Vector3::new(1.0, 1.0, 1.0), Vector3::new(2.0, 1.0, 2.0)],
|
||||
/// ];
|
||||
/// let bspsurface = BSplineSurface::new(knot_vecs, ctrl_pts);
|
||||
/// assert_eq!(bspsurface.degrees(), (1, 2));
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn degrees(&self) -> (usize, usize) { (self.udegree(), self.vdegree()) }
|
||||
/// Returns whether the knot vectors are clamped or not.
|
||||
#[inline(always)]
|
||||
pub fn is_clamped(&self) -> bool { self.0.is_clamped() }
|
||||
/// Swaps two parameters.
|
||||
pub fn swap_axes(&mut self) -> &mut Self
|
||||
where V: Clone {
|
||||
self.0.swap_axes();
|
||||
self
|
||||
}
|
||||
/// The range of the parameter of the surface.
|
||||
#[inline(always)]
|
||||
pub fn parameter_range(&self) -> (ParameterRange, ParameterRange) { self.0.parameter_range() }
|
||||
/// Creates the curve whose control points are the `idx`th column control points of `self`.
|
||||
#[inline(always)]
|
||||
pub fn column_curve(&self, row_idx: usize) -> NurbsCurve<V>
|
||||
where V: Clone {
|
||||
NurbsCurve(self.0.column_curve(row_idx))
|
||||
}
|
||||
/// Creates the column sectional curve.
|
||||
#[inline(always)]
|
||||
pub fn row_curve(&self, column_idx: usize) -> NurbsCurve<V>
|
||||
where V: Clone {
|
||||
NurbsCurve(self.0.row_curve(column_idx))
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Homogeneous<f64>> NurbsSurface<V> {
|
||||
/// Constructs a rationalization surface from the non-rationalized surface and weights.
|
||||
/// # Failures
|
||||
/// the length of `surface.control_points()` and `weights` must be the same.
|
||||
#[inline(always)]
|
||||
pub fn try_from_bspline_and_weights(
|
||||
surface: BSplineSurface<V::Point>,
|
||||
weights: Vec<Vec<f64>>,
|
||||
) -> Result<Self> {
|
||||
let BSplineSurface {
|
||||
knot_vecs,
|
||||
control_points,
|
||||
} = surface;
|
||||
if control_points.len() != weights.len() {
|
||||
return Err(Error::DifferentLength);
|
||||
}
|
||||
let control_points = control_points
|
||||
.into_iter()
|
||||
.zip(weights)
|
||||
.map(|(control_points, weights)| {
|
||||
if control_points.len() != weights.len() {
|
||||
return Err(Error::DifferentLength);
|
||||
}
|
||||
Ok(control_points
|
||||
.into_iter()
|
||||
.zip(weights)
|
||||
.map(|(pt, w)| V::from_point_weight(pt, w))
|
||||
.collect::<Vec<_>>())
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
Ok(Self(BSplineSurface::new_unchecked(
|
||||
knot_vecs,
|
||||
control_points,
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V>> NurbsSurface<V> {
|
||||
/// Substitutes to a NURBS surface.
|
||||
#[inline(always)]
|
||||
pub fn subs(&self, u: f64, v: f64) -> V::Point { self.0.subs(u, v).to_point() }
|
||||
/// Substitutes derived NURBS surface by the first parameter `u`.
|
||||
#[inline(always)]
|
||||
pub fn uder(&self, u: f64, v: f64) -> <V::Point as EuclideanSpace>::Diff {
|
||||
let pt = self.0.subs(u, v);
|
||||
let ud = self.0.uder(u, v);
|
||||
pt.rat_der(ud)
|
||||
}
|
||||
/// Substitutes derived NURBS surface by the first parameter `v`.
|
||||
#[inline(always)]
|
||||
pub fn vder(&self, u: f64, v: f64) -> <V::Point as EuclideanSpace>::Diff {
|
||||
let pt = self.0.subs(u, v);
|
||||
let vd = self.0.vder(u, v);
|
||||
pt.rat_der(vd)
|
||||
}
|
||||
/// Substitutes 2nd-ord derived NURBS surface by the first parameter `u`.
|
||||
#[inline(always)]
|
||||
pub fn uuder(&self, u: f64, v: f64) -> <V::Point as EuclideanSpace>::Diff {
|
||||
let pt = self.0.subs(u, v);
|
||||
let ud = self.0.uder(u, v);
|
||||
let uud = self.0.uuder(u, v);
|
||||
pt.rat_der2(ud, uud)
|
||||
}
|
||||
/// Substitutes 2nd-ord derived NURBS surface by the first parameter `v`.
|
||||
#[inline(always)]
|
||||
pub fn vvder(&self, u: f64, v: f64) -> <V::Point as EuclideanSpace>::Diff {
|
||||
let pt = self.0.subs(u, v);
|
||||
let vd = self.0.vder(u, v);
|
||||
let vvd = self.0.vvder(u, v);
|
||||
pt.rat_der2(vd, vvd)
|
||||
}
|
||||
/// Substitutes 2nd-ord derived NURBS surface by both parameter `u, v`.
|
||||
#[inline(always)]
|
||||
pub fn uvder(&self, u: f64, v: f64) -> <V::Point as EuclideanSpace>::Diff {
|
||||
let pt = self.0.subs(u, v);
|
||||
let ud = self.0.uder(u, v);
|
||||
let vd = self.0.vder(u, v);
|
||||
let uvd = self.0.uvder(u, v);
|
||||
pt.rat_cross_der(ud, vd, uvd)
|
||||
}
|
||||
/// Returns the closure of substitution.
|
||||
#[inline(always)]
|
||||
pub fn get_closure(&self) -> impl Fn(f64, f64) -> V::Point + '_ { move |u, v| self.subs(u, v) }
|
||||
}
|
||||
|
||||
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V>> NurbsSurface<V>
|
||||
where V::Point: Tolerance
|
||||
{
|
||||
/// Returns whether constant curve or not, i.e. all control points are same or not.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let uknot_vec = KnotVec::bezier_knot(1);
|
||||
/// let vknot_vec = KnotVec::bezier_knot(2);
|
||||
/// let pt = Vector3::new(1.0, 2.0, 1.0);
|
||||
/// // allows differences upto scalars
|
||||
/// let ctrl_pts = vec![
|
||||
/// vec![pt.clone(), pt.clone() * 2.0, pt.clone() * 3.0],
|
||||
/// vec![pt.clone() * 0.5, pt.clone() * 0.25, pt.clone() * 0.125],
|
||||
/// ];
|
||||
/// let mut surface = NurbsSurface::new(BSplineSurface::new((uknot_vec, vknot_vec), ctrl_pts));
|
||||
/// assert!(surface.is_const());
|
||||
///
|
||||
/// *surface.control_point_mut(1, 2) = Vector3::new(2.0, 3.0, 1.0);
|
||||
/// assert!(!surface.is_const());
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn is_const(&self) -> bool {
|
||||
let pt = self.0.control_points[0][0].to_point();
|
||||
for vec in self.0.control_points.iter().flat_map(|pts| pts.iter()) {
|
||||
if !vec.to_point().near(&pt) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
/// Determines whether `self` and `other` is near as the B-spline rational surfaces or not.
|
||||
///
|
||||
/// Divides each knot domain into the number of degree equal parts,
|
||||
/// and check `|self(u, v) - other(u, v)| < TOLERANCE` for each end points `(u, v)`.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let knot_vecs = (KnotVec::bezier_knot(3), KnotVec::bezier_knot(2));
|
||||
/// let ctrl_pts = vec![
|
||||
/// vec![Vector3::new(0.0, 0.0, 1.0), Vector3::new(0.5, -1.0, 2.0), Vector3::new(1.0, 0.0, 1.0)],
|
||||
/// vec![Vector3::new(0.0, 1.0, 1.0), Vector3::new(0.5, 1.0, 1.0), Vector3::new(1.0, 1.0, 1.0)],
|
||||
/// vec![Vector3::new(0.0, 2.0, 1.0), Vector3::new(0.5, 2.0, 3.0), Vector3::new(1.0, 2.0, 1.0)],
|
||||
/// vec![Vector3::new(0.0, 3.0, 1.0), Vector3::new(0.5, 3.5, 2.0), Vector3::new(1.0, 3.0, 1.0)],
|
||||
/// ];
|
||||
/// let surface0 = NurbsSurface::new(BSplineSurface::new(knot_vecs, ctrl_pts));
|
||||
/// let mut surface1 = surface0.clone();
|
||||
/// assert!(surface0.near_as_surface(&surface1));
|
||||
///
|
||||
/// *surface1.control_point_mut(1, 1) = Vector3::new(0.5, 1.0, 0.9);
|
||||
/// assert!(!surface0.near_as_surface(&surface1));
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn near_as_surface(&self, other: &Self) -> bool {
|
||||
self.0
|
||||
.sub_near_as_surface(&other.0, 2, move |x, y| x.to_point().near(&y.to_point()))
|
||||
}
|
||||
/// Determines whether `self` and `other` is near in square order as the B-spline rational
|
||||
/// surfaces or not.
|
||||
///
|
||||
/// Divides each knot domain into the number of degree equal parts,
|
||||
/// and check `|self(u, v) - other(u, v)| < TOLERANCE` for each end points `(u, v)`.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let eps = TOLERANCE;
|
||||
/// let knot_vecs = (KnotVec::bezier_knot(3), KnotVec::bezier_knot(2));
|
||||
/// let ctrl_pts = vec![
|
||||
/// vec![Vector3::new(0.0, 0.0, 1.0), Vector3::new(0.5, -1.0, 2.0), Vector3::new(1.0, 0.0, 1.0)],
|
||||
/// vec![Vector3::new(0.0, 1.0, 1.0), Vector3::new(0.5, 1.0, 1.0), Vector3::new(1.0, 1.0, 1.0)],
|
||||
/// vec![Vector3::new(0.0, 2.0, 1.0), Vector3::new(0.5, 2.0, 3.0), Vector3::new(1.0, 2.0, 1.0)],
|
||||
/// vec![Vector3::new(0.0, 3.0, 1.0), Vector3::new(0.5, 3.5, 2.0), Vector3::new(1.0, 3.0, 1.0)],
|
||||
/// ];
|
||||
/// let surface0 = NurbsSurface::new(BSplineSurface::new(knot_vecs, ctrl_pts));
|
||||
/// let mut surface1 = surface0.clone();
|
||||
/// assert!(surface0.near_as_surface(&surface1));
|
||||
///
|
||||
/// *surface1.control_point_mut(1, 1) = Vector3::new(0.5, 1.0, 1.0 - eps);
|
||||
/// assert!(surface0.near_as_surface(&surface1));
|
||||
/// assert!(!surface0.near2_as_surface(&surface1));
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn near2_as_surface(&self, other: &Self) -> bool {
|
||||
self.0
|
||||
.sub_near_as_surface(&other.0, 2, move |x, y| x.to_point().near2(&y.to_point()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V> + Tolerance> NurbsSurface<V> {
|
||||
/// Adds a knot `x` of the first parameter `u`, and do not change `self` as a surface.
|
||||
#[inline(always)]
|
||||
pub fn add_uknot(&mut self, x: f64) -> &mut Self {
|
||||
self.0.add_uknot(x);
|
||||
self
|
||||
}
|
||||
/// Adds a knot `x` of the first parameter `u`, and do not change `self` as a surface.
|
||||
#[inline(always)]
|
||||
pub fn add_vknot(&mut self, x: f64) -> &mut Self {
|
||||
self.0.add_vknot(x);
|
||||
self
|
||||
}
|
||||
/// Removes the uknot corresponding to the indice `idx`, and do not change `self` as a curve.
|
||||
/// If the knot cannot be removed, returns [`Error::CannotRemoveKnot`].
|
||||
#[inline(always)]
|
||||
pub fn try_remove_uknot(&mut self, idx: usize) -> Result<&mut Self> {
|
||||
match self.0.try_remove_uknot(idx) {
|
||||
Ok(_) => Ok(self),
|
||||
Err(error) => Err(error),
|
||||
}
|
||||
}
|
||||
/// Removes the uknot corresponding to the indices `idx`, and do not change `self` as a curve.
|
||||
/// If cannot remove the knot, do not change `self` and return `self`.
|
||||
#[inline(always)]
|
||||
pub fn remove_uknot(&mut self, idx: usize) -> &mut Self {
|
||||
self.0.remove_uknot(idx);
|
||||
self
|
||||
}
|
||||
/// Removes the uknot corresponding to the indice `idx`, and do not change `self` as a curve.
|
||||
/// If the knot cannot be removed, returns [`Error::CannotRemoveKnot`].
|
||||
#[inline(always)]
|
||||
pub fn try_remove_vknot(&mut self, idx: usize) -> Result<&mut Self> {
|
||||
match self.0.try_remove_vknot(idx) {
|
||||
Ok(_) => Ok(self),
|
||||
Err(error) => Err(error),
|
||||
}
|
||||
}
|
||||
/// Removes the uknot corresponding to the indices `idx`, and do not change `self` as a curve.
|
||||
/// If cannot remove the knot, do not change `self` and return `self`.
|
||||
#[inline(always)]
|
||||
pub fn remove_vknot(&mut self, idx: usize) -> &mut Self {
|
||||
self.0.remove_vknot(idx);
|
||||
self
|
||||
}
|
||||
/// Elevates the udegree.
|
||||
#[inline(always)]
|
||||
pub fn elevate_udegree(&mut self) -> &mut Self {
|
||||
self.0.elevate_udegree();
|
||||
self
|
||||
}
|
||||
/// Elevates the vdegree.
|
||||
#[inline(always)]
|
||||
pub fn elevate_vdegree(&mut self) -> &mut Self {
|
||||
self.0.elevate_vdegree();
|
||||
self
|
||||
}
|
||||
/// Aligns the udegree with the same degrees.
|
||||
#[inline(always)]
|
||||
pub fn syncro_uvdegrees(&mut self) -> &mut Self {
|
||||
self.0.syncro_uvdegrees();
|
||||
self
|
||||
}
|
||||
/// Makes the uknot vector and the vknot vector the same knot vector.
|
||||
#[inline(always)]
|
||||
pub fn syncro_uvknots(&mut self) -> &mut Self {
|
||||
self.0.syncro_uvknots();
|
||||
self
|
||||
}
|
||||
|
||||
/// Cuts the surface into two surfaces at the parameter `u`
|
||||
#[inline(always)]
|
||||
pub fn ucut(&mut self, u: f64) -> Self { Self::new(self.0.ucut(u)) }
|
||||
|
||||
/// Cuts the surface into two surfaces at the parameter `v`
|
||||
#[inline(always)]
|
||||
pub fn vcut(&mut self, v: f64) -> Self { Self::new(self.0.vcut(v)) }
|
||||
|
||||
/// Normalizes the knot vectors
|
||||
#[inline(always)]
|
||||
pub fn knot_normalize(&mut self) -> &mut Self {
|
||||
self.0.knot_normalize();
|
||||
self
|
||||
}
|
||||
/// Translates the knot vectors.
|
||||
#[inline(always)]
|
||||
pub fn knot_translate(&mut self, x: f64, y: f64) -> &mut Self {
|
||||
self.0.knot_translate(x, y);
|
||||
self
|
||||
}
|
||||
|
||||
/// Removes knots in order from the back
|
||||
#[inline(always)]
|
||||
pub fn optimize(&mut self) -> &mut Self {
|
||||
self.0.optimize();
|
||||
self
|
||||
}
|
||||
|
||||
/// Get the boundary by four splitted curves.
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let knot_vecs = (KnotVec::bezier_knot(3), KnotVec::bezier_knot(2));
|
||||
/// let ctrl_pts = vec![
|
||||
/// vec![Vector3::new(0.0, 0.0, 1.0), Vector3::new(0.5, -1.0, 2.0), Vector3::new(1.0, 0.0, 1.0)],
|
||||
/// vec![Vector3::new(0.0, 1.0, 2.0), Vector3::new(0.5, 1.0, 3.0), Vector3::new(1.0, 1.0, 2.0)],
|
||||
/// vec![Vector3::new(0.0, 2.0, 2.0), Vector3::new(0.5, 2.0, 3.0), Vector3::new(1.0, 2.0, 2.0)],
|
||||
/// vec![Vector3::new(0.0, 3.0, 1.0), Vector3::new(0.5, 3.5, 2.0), Vector3::new(1.0, 3.0, 1.0)],
|
||||
/// ];
|
||||
/// let bspsurface = NurbsSurface::new(BSplineSurface::new(knot_vecs, ctrl_pts));
|
||||
/// let curves = bspsurface.splitted_boundary();
|
||||
/// assert_eq!(
|
||||
/// curves[0].control_points(),
|
||||
/// &vec![
|
||||
/// Vector3::new(0.0, 0.0, 1.0),
|
||||
/// Vector3::new(0.0, 1.0, 2.0),
|
||||
/// Vector3::new(0.0, 2.0, 2.0),
|
||||
/// Vector3::new(0.0, 3.0, 1.0),
|
||||
/// ],
|
||||
/// );
|
||||
/// assert_eq!(
|
||||
/// curves[1].control_points(),
|
||||
/// &vec![
|
||||
/// Vector3::new(0.0, 3.0, 1.0),
|
||||
/// Vector3::new(0.5, 3.5, 2.0),
|
||||
/// Vector3::new(1.0, 3.0, 1.0),
|
||||
/// ],
|
||||
/// );
|
||||
/// assert_eq!(
|
||||
/// curves[2].control_points(),
|
||||
/// &vec![
|
||||
/// Vector3::new(1.0, 3.0, 1.0),
|
||||
/// Vector3::new(1.0, 2.0, 2.0),
|
||||
/// Vector3::new(1.0, 1.0, 2.0),
|
||||
/// Vector3::new(1.0, 0.0, 1.0),
|
||||
/// ],
|
||||
/// );
|
||||
/// assert_eq!(
|
||||
/// curves[3].control_points(),
|
||||
/// &vec![
|
||||
/// Vector3::new(1.0, 0.0, 1.0),
|
||||
/// Vector3::new(0.5, -1.0, 2.0),
|
||||
/// Vector3::new(0.0, 0.0, 1.0),
|
||||
/// ],
|
||||
/// );
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn splitted_boundary(&self) -> [NurbsCurve<V>; 4] {
|
||||
TryFrom::try_from(
|
||||
self.0
|
||||
.splitted_boundary()
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(NurbsCurve::new)
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Extracts the boundary of surface
|
||||
#[inline(always)]
|
||||
pub fn boundary(&self) -> NurbsCurve<V> { NurbsCurve::new(self.0.boundary()) }
|
||||
}
|
||||
|
||||
impl<V: Homogeneous<f64>> SearchNearestParameter<D2> for NurbsSurface<V>
|
||||
where
|
||||
Self: ParametricSurface<Point = V::Point, Vector = <V::Point as EuclideanSpace>::Diff>,
|
||||
V::Point: EuclideanSpace<Scalar = f64> + MetricSpace<Metric = f64>,
|
||||
<V::Point as EuclideanSpace>::Diff: InnerSpace<Scalar = f64> + Tolerance,
|
||||
{
|
||||
type Point = V::Point;
|
||||
/// Searches the parameter `(u, v)` which minimize `|self(u, v) - point|` by Newton's method
|
||||
/// with initial guess `(u0, v0)`. If the repeated trial does not converge, then returns `None`.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let knot_vecs = (KnotVec::bezier_knot(3), KnotVec::bezier_knot(2));
|
||||
/// let ctrl_pts = vec![
|
||||
/// vec![Vector3::new(0.0, 0.0, 1.0), Vector3::new(1.0, -2.0, 2.0), Vector3::new(1.0, 0.0, 1.0)],
|
||||
/// vec![Vector3::new(0.0, 2.0, 2.0), Vector3::new(2.0, 4.0, 4.0), Vector3::new(2.0, 2.0, 2.0)],
|
||||
/// vec![Vector3::new(0.0, 4.0, 2.0), Vector3::new(2.0, 8.0, 4.0), Vector3::new(2.0, 4.0, 2.0)],
|
||||
/// vec![Vector3::new(0.0, 3.0, 1.0), Vector3::new(1.0, 7.0, 2.0), Vector3::new(1.0, 3.0, 1.0)],
|
||||
/// ];
|
||||
/// let surface = NurbsSurface::new(BSplineSurface::new(knot_vecs, ctrl_pts));
|
||||
/// let pt = surface.subs(0.3, 0.7);
|
||||
/// let (u, v) = surface.search_nearest_parameter(pt, Some((0.5, 0.5)), 100).unwrap();
|
||||
/// assert!(u.near(&0.3) && v.near(&0.7));
|
||||
/// ```
|
||||
/// # Remarks
|
||||
/// It may converge to a local solution depending on the hint.
|
||||
/// cf. [`BSplineCurve::search_rational_nearest_parameter`](struct.BSplineCurve.html#method.search_rational_nearest_parameter)
|
||||
#[inline(always)]
|
||||
fn search_nearest_parameter<H: Into<SPHint2D>>(
|
||||
&self,
|
||||
point: V::Point,
|
||||
hint: H,
|
||||
trials: usize,
|
||||
) -> Option<(f64, f64)> {
|
||||
let hint = match hint.into() {
|
||||
SPHint2D::Parameter(x, y) => (x, y),
|
||||
SPHint2D::Range(range0, range1) => {
|
||||
algo::surface::presearch(self, point, (range0, range1), PRESEARCH_DIVISION)
|
||||
}
|
||||
SPHint2D::None => {
|
||||
algo::surface::presearch(self, point, self.range_tuple(), PRESEARCH_DIVISION)
|
||||
}
|
||||
};
|
||||
algo::surface::search_nearest_parameter(self, point, hint, trials)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Homogeneous<f64>> NurbsSurface<V>
|
||||
where V::Point: Bounded<Scalar = f64>
|
||||
{
|
||||
/// Returns the bounding box including all control points.
|
||||
#[inline(always)]
|
||||
pub fn roughly_bounding_box(&self) -> BoundingBox<V::Point> {
|
||||
self.0
|
||||
.control_points
|
||||
.iter()
|
||||
.flatten()
|
||||
.map(|pt| pt.to_point())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchParameter<D2> for NurbsSurface<Vector3> {
|
||||
type Point = Point2;
|
||||
/// Search the parameter `(u, v)` such that `self.subs(u, v).rational_projection()` is near `pt`.
|
||||
/// If cannot find, then return `None`.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let knot_vec = KnotVec::uniform_knot(2, 2);
|
||||
/// let ctrl_pts = vec![
|
||||
/// vec![Vector3::new(0.0, 0.0, 1.0), Vector3::new(0.1, 0.0, 0.5), Vector3::new(0.5, 0.0, 0.3), Vector3::new(1.0, 0.0, 1.0)],
|
||||
/// vec![Vector3::new(0.0, 0.1, 0.1), Vector3::new(0.2, 0.2, 0.1), Vector3::new(0.4, 0.3, 0.4), Vector3::new(1.0, 0.3, 0.7)],
|
||||
/// vec![Vector3::new(0.0, 0.5, 0.4), Vector3::new(0.3, 0.6, 0.5), Vector3::new(0.6, 0.4, 1.0), Vector3::new(1.0, 0.5, 0.4)],
|
||||
/// vec![Vector3::new(0.0, 1.0, 1.0), Vector3::new(0.1, 1.0, 1.0), Vector3::new(0.5, 1.0, 0.5), Vector3::new(1.0, 1.0, 0.3)],
|
||||
/// ];
|
||||
/// let bspsurface = BSplineSurface::new((knot_vec.clone(), knot_vec), ctrl_pts);
|
||||
/// let surface = NurbsSurface::new(bspsurface);
|
||||
///
|
||||
/// let pt = surface.subs(0.3, 0.7);
|
||||
/// let (u, v) = surface.search_parameter(pt, Some((0.5, 0.5)), 100).unwrap();
|
||||
/// assert_near!(surface.subs(u, v), pt);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
fn search_parameter<H: Into<SPHint2D>>(
|
||||
&self,
|
||||
point: Point2,
|
||||
hint: H,
|
||||
trials: usize,
|
||||
) -> Option<(f64, f64)> {
|
||||
let hint = match hint.into() {
|
||||
SPHint2D::Parameter(x, y) => (x, y),
|
||||
SPHint2D::Range(range0, range1) => {
|
||||
algo::surface::presearch(self, point, (range0, range1), PRESEARCH_DIVISION)
|
||||
}
|
||||
SPHint2D::None => {
|
||||
algo::surface::presearch(self, point, self.range_tuple(), PRESEARCH_DIVISION)
|
||||
}
|
||||
};
|
||||
algo::surface::search_parameter2d(self, point, hint, trials)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Clone> Invertible for NurbsSurface<V> {
|
||||
#[inline(always)]
|
||||
fn invert(&mut self) { self.swap_axes(); }
|
||||
#[inline(always)]
|
||||
fn inverse(&self) -> Self {
|
||||
let mut surface = self.clone();
|
||||
surface.swap_axes();
|
||||
surface
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V>> ParametricSurface for NurbsSurface<V> {
|
||||
type Point = V::Point;
|
||||
type Vector = <V::Point as EuclideanSpace>::Diff;
|
||||
#[inline(always)]
|
||||
fn subs(&self, u: f64, v: f64) -> Self::Point { self.subs(u, v) }
|
||||
#[inline(always)]
|
||||
fn uder(&self, u: f64, v: f64) -> Self::Vector { self.uder(u, v) }
|
||||
#[inline(always)]
|
||||
fn vder(&self, u: f64, v: f64) -> Self::Vector { self.vder(u, v) }
|
||||
#[inline(always)]
|
||||
fn uuder(&self, u: f64, v: f64) -> Self::Vector { self.uuder(u, v) }
|
||||
#[inline(always)]
|
||||
fn uvder(&self, u: f64, v: f64) -> Self::Vector { self.uvder(u, v) }
|
||||
#[inline(always)]
|
||||
fn vvder(&self, u: f64, v: f64) -> Self::Vector { self.vvder(u, v) }
|
||||
#[inline(always)]
|
||||
fn parameter_range(&self) -> (ParameterRange, ParameterRange) { self.parameter_range() }
|
||||
}
|
||||
|
||||
impl ParametricSurface3D for NurbsSurface<Vector4> {
|
||||
#[inline(always)]
|
||||
fn normal(&self, u: f64, v: f64) -> Vector3 {
|
||||
let pt = self.0.subs(u, v);
|
||||
let ud = self.0.uder(u, v);
|
||||
let vd = self.0.vder(u, v);
|
||||
pt.rat_der(ud).cross(pt.rat_der(vd)).normalize()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Homogeneous<f64> + ControlPoint<f64, Diff = V>> ParameterDivision2D for NurbsSurface<V>
|
||||
where V::Point: MetricSpace<Metric = f64> + HashGen<f64>
|
||||
{
|
||||
#[inline(always)]
|
||||
fn parameter_division(
|
||||
&self,
|
||||
range: ((f64, f64), (f64, f64)),
|
||||
tol: f64,
|
||||
) -> (Vec<f64>, Vec<f64>) {
|
||||
algo::surface::parameter_division(self, range, tol)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> BoundedSurface for NurbsSurface<V> where Self: ParametricSurface {}
|
||||
|
||||
impl IncludeCurve<NurbsCurve<Vector3>> for NurbsSurface<Vector3> {
|
||||
#[inline(always)]
|
||||
fn include(&self, curve: &NurbsCurve<Vector3>) -> bool {
|
||||
let pt = curve.subs(curve.knot_vec()[0]);
|
||||
let mut hint = match self.search_parameter(pt, None, INCLUDE_CURVE_TRIALS) {
|
||||
Some(got) => got,
|
||||
None => return false,
|
||||
};
|
||||
let uknot_vec = self.uknot_vec();
|
||||
let vknot_vec = self.vknot_vec();
|
||||
let degree = curve.degree() * 6;
|
||||
let (knots, _) = curve.knot_vec().to_single_multi();
|
||||
for i in 1..knots.len() {
|
||||
for j in 1..=degree {
|
||||
let p = j as f64 / degree as f64;
|
||||
let t = knots[i - 1] * (1.0 - p) + knots[i] * p;
|
||||
let pt = curve.subs(t);
|
||||
hint = match self.search_parameter(pt, Some(hint), INCLUDE_CURVE_TRIALS) {
|
||||
Some(got) => got,
|
||||
None => return false,
|
||||
};
|
||||
if !self.subs(hint.0, hint.1).near(&pt)
|
||||
|| hint.0 < uknot_vec[0] - TOLERANCE
|
||||
|| hint.0 - uknot_vec[0] > uknot_vec.range_length() + TOLERANCE
|
||||
|| hint.1 < vknot_vec[0] - TOLERANCE
|
||||
|| hint.1 - vknot_vec[0] > vknot_vec.range_length() + TOLERANCE
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl IncludeCurve<BSplineCurve<Point3>> for NurbsSurface<Vector4> {
|
||||
#[inline(always)]
|
||||
fn include(&self, curve: &BSplineCurve<Point3>) -> bool {
|
||||
let pt = curve.front();
|
||||
let mut hint = match self.search_parameter(pt, None, INCLUDE_CURVE_TRIALS) {
|
||||
Some(got) => got,
|
||||
None => return false,
|
||||
};
|
||||
let uknot_vec = self.uknot_vec();
|
||||
let vknot_vec = self.vknot_vec();
|
||||
let degree = curve.degree() * 6;
|
||||
let (knots, _) = curve.knot_vec().to_single_multi();
|
||||
for i in 1..knots.len() {
|
||||
for j in 1..=degree {
|
||||
let p = j as f64 / degree as f64;
|
||||
let t = knots[i - 1] * (1.0 - p) + knots[i] * p;
|
||||
let pt = curve.subs(t);
|
||||
hint = match self.search_parameter(pt, Some(hint), INCLUDE_CURVE_TRIALS) {
|
||||
Some(got) => got,
|
||||
None => return false,
|
||||
};
|
||||
if !self.subs(hint.0, hint.1).near(&pt)
|
||||
|| hint.0 < uknot_vec[0] - TOLERANCE
|
||||
|| hint.0 - uknot_vec[0] > uknot_vec.range_length() + TOLERANCE
|
||||
|| hint.1 < vknot_vec[0] - TOLERANCE
|
||||
|| hint.1 - vknot_vec[0] > vknot_vec.range_length() + TOLERANCE
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl IncludeCurve<NurbsCurve<Vector4>> for NurbsSurface<Vector4> {
|
||||
#[inline(always)]
|
||||
fn include(&self, curve: &NurbsCurve<Vector4>) -> bool {
|
||||
let pt = curve.front();
|
||||
let mut hint = match self.search_parameter(pt, None, INCLUDE_CURVE_TRIALS) {
|
||||
Some(got) => got,
|
||||
None => return false,
|
||||
};
|
||||
let uknot_vec = self.uknot_vec();
|
||||
let vknot_vec = self.vknot_vec();
|
||||
let degree = curve.degree() * 6;
|
||||
let (knots, _) = curve.knot_vec().to_single_multi();
|
||||
for i in 1..knots.len() {
|
||||
for j in 1..=degree {
|
||||
let p = j as f64 / degree as f64;
|
||||
let t = knots[i - 1] * (1.0 - p) + knots[i] * p;
|
||||
let pt = curve.subs(t);
|
||||
hint = match self.search_parameter(pt, Some(hint), INCLUDE_CURVE_TRIALS) {
|
||||
Some(got) => got,
|
||||
None => return false,
|
||||
};
|
||||
if !self.subs(hint.0, hint.1).near(&pt)
|
||||
|| hint.0 < uknot_vec[0] - TOLERANCE
|
||||
|| hint.0 - uknot_vec[0] > uknot_vec.range_length() + TOLERANCE
|
||||
|| hint.1 < vknot_vec[0] - TOLERANCE
|
||||
|| hint.1 - vknot_vec[0] > vknot_vec.range_length() + TOLERANCE
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl<M, V: Copy> Transformed<M> for NurbsSurface<V>
|
||||
where M: Copy + std::ops::Mul<V, Output = V>
|
||||
{
|
||||
#[inline(always)]
|
||||
fn transform_by(&mut self, trans: M) {
|
||||
self.0
|
||||
.control_points
|
||||
.iter_mut()
|
||||
.flatten()
|
||||
.for_each(move |v| *v = trans * *v)
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchParameter<D2> for NurbsSurface<Vector4> {
|
||||
type Point = Point3;
|
||||
/// Search the parameter `(u, v)` such that `self.subs(u, v).rational_projection()` is near `pt`.
|
||||
/// If cannot find, then return `None`.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let knot_vec = KnotVec::uniform_knot(2, 2);
|
||||
/// let ctrl_pts = vec![
|
||||
/// vec![Vector4::new(0.0, 0.0, 0.0, 1.0), Vector4::new(0.1, 0.0, 0.5, 0.4), Vector4::new(1.0, 0.0, 0.6, 2.0), Vector4::new(0.4, 0.0, 0.4, 0.4)],
|
||||
/// vec![Vector4::new(0.0, 0.2, 0.2, 2.0), Vector4::new(0.24, 0.24, 0.1, 1.2), Vector4::new(2.4, 1.8, 2.4, 0.6), Vector4::new(1.4, 0.42, 0.98, 1.4)],
|
||||
/// vec![Vector4::new(0.0, 1.5, 1.2, 3.0), Vector4::new(1.02, 2.04, 1.7, 3.4), Vector4::new(0.42, 0.28, 0.7, 0.7), Vector4::new(0.6, 0.3, 0.0, 0.6)],
|
||||
/// vec![Vector4::new(0.0, 1.0, 1.0, 1.0), Vector4::new(0.2, 2.0, 2.0, 2.0), Vector4::new(0.85, 1.7, 0.85, 1.7), Vector4::new(1.0, 1.0, 0.3, 1.0)],
|
||||
/// ];
|
||||
/// let bspsurface = BSplineSurface::new((knot_vec.clone(), knot_vec), ctrl_pts);
|
||||
/// let surface = NurbsSurface::new(bspsurface);
|
||||
///
|
||||
/// let pt = surface.subs(0.3, 0.7);
|
||||
/// let (u, v) = surface.search_parameter(pt, Some((0.5, 0.5)), 100).unwrap();
|
||||
/// assert_near!(surface.subs(u, v), pt);
|
||||
/// ```
|
||||
fn search_parameter<H: Into<SPHint2D>>(
|
||||
&self,
|
||||
point: Point3,
|
||||
hint: H,
|
||||
trials: usize,
|
||||
) -> Option<(f64, f64)> {
|
||||
let hint = match hint.into() {
|
||||
SPHint2D::Parameter(x, y) => (x, y),
|
||||
SPHint2D::Range(range0, range1) => {
|
||||
algo::surface::presearch(self, point, (range0, range1), PRESEARCH_DIVISION)
|
||||
}
|
||||
SPHint2D::None => {
|
||||
algo::surface::presearch(self, point, self.range_tuple(), PRESEARCH_DIVISION)
|
||||
}
|
||||
};
|
||||
algo::surface::search_parameter3d(self, point, hint, trials)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_include2d() {
|
||||
let knot_vec = KnotVec::uniform_knot(2, 3);
|
||||
let ctrl_pts = vec![
|
||||
vec![
|
||||
Vector3::new(0.0, 0.0, 1.0),
|
||||
Vector3::new(0.05, 0.0, 0.5),
|
||||
Vector3::new(0.15, 0.0, 0.3),
|
||||
Vector3::new(1.0, 0.0, 1.0),
|
||||
],
|
||||
vec![
|
||||
Vector3::new(0.0, 0.01, 0.1),
|
||||
Vector3::new(0.02, 0.02, 0.1),
|
||||
Vector3::new(0.16, 0.12, 0.4),
|
||||
Vector3::new(0.7, 0.21, 0.7),
|
||||
],
|
||||
vec![
|
||||
Vector3::new(0.0, 0.02, 0.4),
|
||||
Vector3::new(0.15, 0.3, 0.5),
|
||||
Vector3::new(0.6, 0.4, 1.0),
|
||||
Vector3::new(0.4, 0.2, 0.4),
|
||||
],
|
||||
vec![
|
||||
Vector3::new(0.0, 1.0, 1.0),
|
||||
Vector3::new(0.1, 1.0, 1.0),
|
||||
Vector3::new(0.25, 0.5, 0.5),
|
||||
Vector3::new(0.3, 0.3, 0.3),
|
||||
],
|
||||
];
|
||||
let surface = BSplineSurface::new((knot_vec.clone(), knot_vec), ctrl_pts);
|
||||
let bnd_box = BoundingBox::from_iter(&[Vector2::new(0.2, 0.3), Vector2::new(0.8, 0.6)]);
|
||||
let mut curve = surface.sectional_curve(bnd_box);
|
||||
curve.control_points_mut().for_each(|pt| *pt *= 3.0);
|
||||
let surface = NurbsSurface::new(surface);
|
||||
let curve = NurbsCurve::new(curve);
|
||||
assert!(surface.include(&curve));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_include3d() {
|
||||
let knot_vec = KnotVec::bezier_knot(2);
|
||||
let ctrl_pts = vec![
|
||||
vec![
|
||||
Vector4::new(-1.0, -1.0, 2.0, 1.0),
|
||||
Vector4::new(-1.0, 0.0, 0.0, 1.0),
|
||||
Vector4::new(-1.0, 1.0, 2.0, 1.0),
|
||||
],
|
||||
vec![
|
||||
Vector4::new(0.0, -1.0, 0.0, 1.0),
|
||||
Vector4::new(0.0, 0.0, -2.0, 1.0),
|
||||
Vector4::new(0.0, 1.0, 0.0, 1.0),
|
||||
],
|
||||
vec![
|
||||
Vector4::new(1.0, -1.0, 2.0, 1.0),
|
||||
Vector4::new(1.0, 0.0, 0.0, 1.0),
|
||||
Vector4::new(1.0, 1.0, 2.0, 1.0),
|
||||
],
|
||||
];
|
||||
let surface = NurbsSurface::new(BSplineSurface::new((knot_vec.clone(), knot_vec), ctrl_pts));
|
||||
|
||||
let knot_vec = KnotVec::from(vec![
|
||||
0.0, 0.0, 0.0, 0.25, 0.25, 0.5, 0.5, 0.75, 0.75, 1.0, 1.0, 1.0,
|
||||
]);
|
||||
let ctrl_pts = vec![
|
||||
// the vector of the indices of control points
|
||||
Vector4::new(0.0, -2.0, 2.0, 2.0),
|
||||
Vector4::new(1.0, -1.0, 1.0, 1.0),
|
||||
Vector4::new(1.0, 0.0, 1.0, 1.0),
|
||||
Vector4::new(1.0, 1.0, 1.0, 1.0),
|
||||
Vector4::new(0.0, 2.0, 2.0, 2.0),
|
||||
Vector4::new(-1.0, 1.0, 1.0, 1.0),
|
||||
Vector4::new(-1.0, 0.0, 1.0, 1.0),
|
||||
Vector4::new(-1.0, -1.0, 1.0, 1.0),
|
||||
Vector4::new(0.0, -2.0, 2.0, 2.0),
|
||||
];
|
||||
let mut curve = NurbsCurve::new(BSplineCurve::new(knot_vec, ctrl_pts));
|
||||
assert!(surface.include(&curve));
|
||||
*curve.control_point_mut(1) += Vector4::new(0.0, 0.0, 0.00001, 0.0);
|
||||
assert!(!surface.include(&curve));
|
||||
}
|
||||
-161
@@ -1,161 +0,0 @@
|
||||
use super::*;
|
||||
use std::f64::consts::PI;
|
||||
|
||||
impl<P> UnitCircle<P> {
|
||||
/// constructor
|
||||
#[inline]
|
||||
pub const fn new() -> Self { Self(std::marker::PhantomData) }
|
||||
}
|
||||
|
||||
impl ParametricCurve for UnitCircle<Point2> {
|
||||
type Point = Point2;
|
||||
type Vector = Vector2;
|
||||
#[inline]
|
||||
fn subs(&self, t: f64) -> Self::Point { Point2::new(f64::cos(t), f64::sin(t)) }
|
||||
#[inline]
|
||||
fn der(&self, t: f64) -> Self::Vector { Vector2::new(-f64::sin(t), f64::cos(t)) }
|
||||
#[inline]
|
||||
fn der2(&self, t: f64) -> Self::Vector { Vector2::new(-f64::cos(t), -f64::sin(t)) }
|
||||
#[inline]
|
||||
fn parameter_range(&self) -> ParameterRange {
|
||||
(Bound::Included(0.0), Bound::Excluded(2.0 * PI))
|
||||
}
|
||||
}
|
||||
|
||||
impl BoundedCurve for UnitCircle<Point2> {}
|
||||
|
||||
impl ParametricCurve for UnitCircle<Point3> {
|
||||
type Point = Point3;
|
||||
type Vector = Vector3;
|
||||
#[inline]
|
||||
fn subs(&self, t: f64) -> Self::Point { Point3::new(f64::cos(t), f64::sin(t), 0.0) }
|
||||
#[inline]
|
||||
fn der(&self, t: f64) -> Self::Vector { Vector3::new(-f64::sin(t), f64::cos(t), 0.0) }
|
||||
#[inline]
|
||||
fn der2(&self, t: f64) -> Self::Vector { Vector3::new(-f64::cos(t), -f64::sin(t), 0.0) }
|
||||
#[inline]
|
||||
fn period(&self) -> Option<f64> { Some(2.0 * PI) }
|
||||
#[inline]
|
||||
fn parameter_range(&self) -> ParameterRange {
|
||||
(Bound::Included(0.0), Bound::Excluded(2.0 * PI))
|
||||
}
|
||||
}
|
||||
|
||||
impl BoundedCurve for UnitCircle<Point3> {}
|
||||
|
||||
impl<P> ParameterDivision1D for UnitCircle<P>
|
||||
where UnitCircle<P>: ParametricCurve<Point = P>
|
||||
{
|
||||
type Point = P;
|
||||
fn parameter_division(&self, range: (f64, f64), tol: f64) -> (Vec<f64>, Vec<P>) {
|
||||
nonpositive_tolerance!(tol);
|
||||
let tol = f64::min(tol, 0.8);
|
||||
let delta = 2.0 * f64::acos(1.0 - tol);
|
||||
let n = 1 + ((range.1 - range.0) / delta) as usize;
|
||||
let params = (0..=n)
|
||||
.map(|i| {
|
||||
let t = i as f64 / n as f64;
|
||||
range.0 * (1.0 - t) + range.1 * t
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let pts = params.iter().map(|t| self.subs(*t)).collect();
|
||||
(params, pts)
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchNearestParameter<D1> for UnitCircle<Point2> {
|
||||
type Point = Point2;
|
||||
fn search_nearest_parameter<H: Into<SPHint1D>>(
|
||||
&self,
|
||||
pt: Point2,
|
||||
_: H,
|
||||
_: usize,
|
||||
) -> Option<f64> {
|
||||
let v = pt.to_vec();
|
||||
if v.magnitude().so_small() {
|
||||
return None;
|
||||
}
|
||||
let v = v.normalize();
|
||||
let theta = f64::acos(f64::clamp(v.x, -1.0, 1.0));
|
||||
let theta = match v.y > 0.0 {
|
||||
true => theta,
|
||||
false => 2.0 * PI - theta,
|
||||
};
|
||||
Some(theta)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn search_nearest_parameter() {
|
||||
const N: usize = 100;
|
||||
let circle = UnitCircle::<Point2>::new();
|
||||
for i in 0..N {
|
||||
let t = 2.0 * PI * i as f64 / N as f64;
|
||||
let a = 5.0 * rand::random::<f64>() + 0.1;
|
||||
let p = a * circle.subs(t);
|
||||
let s = circle.search_nearest_parameter(p, None, 1).unwrap();
|
||||
let q = a * circle.subs(s);
|
||||
assert_near!(p, q);
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchParameter<D1> for UnitCircle<Point2> {
|
||||
type Point = Point2;
|
||||
fn search_parameter<H: Into<SPHint1D>>(&self, pt: Point2, _: H, _: usize) -> Option<f64> {
|
||||
let v = pt.to_vec();
|
||||
if !v.magnitude().near(&1.0) {
|
||||
return None;
|
||||
}
|
||||
let v = v.normalize();
|
||||
let theta = f64::acos(f64::clamp(v.x, -1.0, 1.0));
|
||||
let theta = match v.y > 0.0 {
|
||||
true => theta,
|
||||
false => 2.0 * PI - theta,
|
||||
};
|
||||
Some(theta)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn search_parameter() {
|
||||
const N: usize = 100;
|
||||
let circle = UnitCircle::<Point2>::new();
|
||||
for i in 1..N {
|
||||
let t = 2.0 * PI * i as f64 / N as f64;
|
||||
let p = circle.subs(t);
|
||||
let s = circle.search_parameter(p, None, 1).unwrap();
|
||||
assert_near!(s, t);
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchNearestParameter<D1> for UnitCircle<Point3> {
|
||||
type Point = Point3;
|
||||
fn search_nearest_parameter<H: Into<SPHint1D>>(
|
||||
&self,
|
||||
pt: Point3,
|
||||
_: H,
|
||||
_: usize,
|
||||
) -> Option<f64> {
|
||||
UnitCircle::<Point2>::new().search_nearest_parameter(Point2::new(pt.x, pt.y), None, 0)
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchParameter<D1> for UnitCircle<Point3> {
|
||||
type Point = Point3;
|
||||
fn search_parameter<H: Into<SPHint1D>>(&self, pt: Point3, _: H, _: usize) -> Option<f64> {
|
||||
if !f64::abs(pt.z).so_small() {
|
||||
return None;
|
||||
}
|
||||
UnitCircle::<Point2>::new().search_parameter(Point2::new(pt.x, pt.y), None, 0)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parameter_division() {
|
||||
let c = UnitCircle::<Point2>::new();
|
||||
let (_div, pts) = c.parameter_division(c.range_tuple(), 0.05);
|
||||
for a in pts.windows(2) {
|
||||
let p = a[0].midpoint(a[1]);
|
||||
assert!(p.to_vec().magnitude() > 0.95);
|
||||
}
|
||||
}
|
||||
-125
@@ -1,125 +0,0 @@
|
||||
use super::*;
|
||||
|
||||
impl<P> UnitHyperbola<P> {
|
||||
/// constructor
|
||||
#[inline]
|
||||
pub const fn new() -> UnitHyperbola<P> { UnitHyperbola(std::marker::PhantomData) }
|
||||
}
|
||||
|
||||
impl ParametricCurve for UnitHyperbola<Point2> {
|
||||
type Point = Point2;
|
||||
type Vector = Vector2;
|
||||
#[inline]
|
||||
fn subs(&self, t: f64) -> Self::Point { Point2::new(f64::cosh(t), f64::sinh(t)) }
|
||||
#[inline]
|
||||
fn der(&self, t: f64) -> Self::Vector { Vector2::new(f64::sinh(t), f64::cosh(t)) }
|
||||
#[inline]
|
||||
fn der2(&self, t: f64) -> Self::Vector { Vector2::new(f64::cosh(t), f64::sinh(t)) }
|
||||
}
|
||||
|
||||
impl ParametricCurve for UnitHyperbola<Point3> {
|
||||
type Point = Point3;
|
||||
type Vector = Vector3;
|
||||
#[inline]
|
||||
fn subs(&self, t: f64) -> Self::Point { Point3::new(f64::cosh(t), f64::sinh(t), 0.0) }
|
||||
#[inline]
|
||||
fn der(&self, t: f64) -> Self::Vector { Vector3::new(f64::sinh(t), f64::cosh(t), 0.0) }
|
||||
#[inline]
|
||||
fn der2(&self, t: f64) -> Self::Vector { Vector3::new(f64::cosh(t), f64::sinh(t), 0.0) }
|
||||
}
|
||||
|
||||
impl<P> ParameterDivision1D for UnitHyperbola<P>
|
||||
where
|
||||
UnitHyperbola<P>: ParametricCurve<Point = P>,
|
||||
P: EuclideanSpace<Scalar = f64> + MetricSpace<Metric = f64> + HashGen<f64>,
|
||||
{
|
||||
type Point = P;
|
||||
fn parameter_division(&self, range: (f64, f64), tol: f64) -> (Vec<f64>, Vec<P>) {
|
||||
algo::curve::parameter_division(self, range, tol)
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchNearestParameter<D1> for UnitHyperbola<Point2> {
|
||||
type Point = Point2;
|
||||
fn search_nearest_parameter<H: Into<SPHint1D>>(
|
||||
&self,
|
||||
p: Point2,
|
||||
_: H,
|
||||
_: usize,
|
||||
) -> Option<f64> {
|
||||
let a = -p.y;
|
||||
let b = (p.y * p.y - p.x * p.x) / 4.0 + 1.0;
|
||||
let c = -p.y;
|
||||
let d = p.y * p.y / 4.0;
|
||||
let y = solver::solve_quartic(a, b, c, d)
|
||||
.into_iter()
|
||||
.filter_map(|z| match z.im.so_small() {
|
||||
true => Some(z.re),
|
||||
false => None,
|
||||
})
|
||||
.min_by(|s, t| {
|
||||
p.distance2(self.subs(*s))
|
||||
.partial_cmp(&p.distance2(self.subs(*t)))
|
||||
.unwrap()
|
||||
})?;
|
||||
Some(f64::asinh(y))
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchNearestParameter<D1> for UnitHyperbola<Point3> {
|
||||
type Point = Point3;
|
||||
fn search_nearest_parameter<H: Into<SPHint1D>>(
|
||||
&self,
|
||||
p: Point3,
|
||||
_: H,
|
||||
_trials: usize,
|
||||
) -> Option<f64> {
|
||||
UnitHyperbola::<Point2>::new().search_nearest_parameter(
|
||||
Point2::new(p.x, p.y),
|
||||
None,
|
||||
_trials,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchParameter<D1> for UnitHyperbola<Point2> {
|
||||
type Point = Point2;
|
||||
fn search_parameter<H: Into<SPHint1D>>(&self, p: Point2, _: H, _: usize) -> Option<f64> {
|
||||
let t = f64::asinh(p.y);
|
||||
match p.near(&self.subs(t)) {
|
||||
true => Some(t),
|
||||
false => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchParameter<D1> for UnitHyperbola<Point3> {
|
||||
type Point = Point3;
|
||||
fn search_parameter<H: Into<SPHint1D>>(&self, p: Point3, _: H, _: usize) -> Option<f64> {
|
||||
let t = f64::asinh(p.y);
|
||||
match p.near(&self.subs(t)) {
|
||||
true => Some(t),
|
||||
false => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn snp_test() {
|
||||
let curve = UnitHyperbola::<Point2>::new();
|
||||
let p = curve.subs(2.0);
|
||||
let q = p + Vector2::new(-p.x, p.y);
|
||||
let t = curve.search_nearest_parameter(q, None, 0).unwrap();
|
||||
assert_near!(t, 2.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sp_test() {
|
||||
let curve = UnitHyperbola::<Point2>::new();
|
||||
let t = 100.0 * rand::random::<f64>() - 50.0;
|
||||
let p = curve.subs(t);
|
||||
assert_near!(curve.search_parameter(p, None, 0).unwrap(), t);
|
||||
|
||||
let q = Point2::new(-1.0, 0.0);
|
||||
assert!(curve.search_parameter(q, None, 0).is_none());
|
||||
}
|
||||
-217
@@ -1,217 +0,0 @@
|
||||
use super::*;
|
||||
use truck_base::cgmath64::control_point::ControlPoint;
|
||||
|
||||
impl<P: Copy> Line<P> {
|
||||
/// initialize line from vector
|
||||
#[inline]
|
||||
pub fn from_origin_direction<V>(origin: P, direction: V) -> Self
|
||||
where P: std::ops::Add<V, Output = P> {
|
||||
Self(origin, origin + direction)
|
||||
}
|
||||
/// to a bspline curve
|
||||
#[inline]
|
||||
pub fn to_bspline(self) -> BSplineCurve<P> {
|
||||
BSplineCurve::new(KnotVec::bezier_knot(1), vec![self.0, self.1])
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> Line<P>
|
||||
where
|
||||
P: EuclideanSpace<Scalar = f64>,
|
||||
P::Diff: InnerSpace<Scalar = f64>,
|
||||
{
|
||||
/// Returns the projected point to the line.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let line = Line(Point2::new(0.0, 0.0), Point2::new(1.0, 2.0));
|
||||
/// let pt = Point2::new(0.0, 1.0);
|
||||
/// assert_near!(line.projection(pt), Point2::new(0.4, 0.8));
|
||||
/// ```
|
||||
pub fn projection(self, point: P) -> P {
|
||||
let (u, v) = (point - self.0, self.1 - self.0);
|
||||
self.0 + v * u.dot(v) / v.dot(v)
|
||||
}
|
||||
/// Returns the distance between the line and the point `point`.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let line = Line(Point2::new(0.0, 0.0), Point2::new(3.0, 4.0));
|
||||
///
|
||||
/// // The foot of the perpendicular line is on a line segment
|
||||
/// let pt = Point2::new(3.0, 0.0);
|
||||
/// assert_near!(line.distance_to_point(pt), 2.4);
|
||||
///
|
||||
/// // The foot of the perpendicular line is not on a line segment
|
||||
/// let pt = Point2::new(0.0, -4.0);
|
||||
/// assert_near!(line.distance_to_point(pt), 2.4);
|
||||
/// ```
|
||||
pub fn distance_to_point(self, point: P) -> f64 {
|
||||
let (u, v) = (point - self.0, self.1 - self.0);
|
||||
(u - v * u.dot(v) / v.dot(v)).magnitude()
|
||||
}
|
||||
/// Returns the distance between the sengment and the point `point`.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let line = Line(Point2::new(0.0, 0.0), Point2::new(3.0, 4.0));
|
||||
///
|
||||
/// // The foot of the perpendicular line is on a line segment
|
||||
/// let pt = Point2::new(3.0, 0.0);
|
||||
/// assert_near!(line.distance_to_point_as_segment(pt), 2.4);
|
||||
///
|
||||
/// // The foot of the perpendicular line is not on a line segment
|
||||
/// let pt = Point2::new(0.0, -4.0);
|
||||
/// assert_near!(line.distance_to_point_as_segment(pt), 4.0);
|
||||
/// ```
|
||||
pub fn distance_to_point_as_segment(self, point: P) -> f64 {
|
||||
let (u, v) = (point - self.0, self.1 - self.0);
|
||||
let t = f64::clamp(u.dot(v) / v.dot(v), 0.0, 1.0);
|
||||
(u - v * t).magnitude()
|
||||
}
|
||||
}
|
||||
|
||||
impl Line<Point2> {
|
||||
/// Returns the intersection of two lines and its parameters.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let line0 = Line(Point2::new(0.0, 0.0), Point2::new(9.0, 3.0));
|
||||
/// let line1 = Line(Point2::new(0.0, 6.0), Point2::new(9.0, 0.0));
|
||||
/// let (s, t, p) = line0.intersection(line1).unwrap();
|
||||
/// assert_near!(line0.subs(s), Point2::new(6.0, 2.0));
|
||||
/// assert_near!(line1.subs(t), Point2::new(6.0, 2.0));
|
||||
/// assert_near!(p, Point2::new(6.0, 2.0));
|
||||
/// ```
|
||||
/// # Failures
|
||||
/// Returns `None` if two lines are parallel.
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let line0 = Line(Point2::new(0.0, 0.0), Point2::new(9.0, 3.0));
|
||||
/// let line1 = Line(Point2::new(-1.0, 0.0), Point2::new(8.0, 3.0));
|
||||
/// assert!(line0.intersection(line1).is_none());
|
||||
/// ```
|
||||
pub fn intersection(self, other: Line<Point2>) -> Option<(f64, f64, Point2)> {
|
||||
let mat = Matrix2::from_cols(self.1 - self.0, other.0 - other.1);
|
||||
let v = other.0 - self.0;
|
||||
let params = mat.invert().map(|inv| inv * v)?;
|
||||
Some((params.x, params.y, self.subs(params.x)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: ControlPoint<f64>> ParametricCurve for Line<P> {
|
||||
type Point = P;
|
||||
type Vector = P::Diff;
|
||||
#[inline]
|
||||
fn subs(&self, t: f64) -> Self::Point { self.0 + (self.1 - self.0) * t }
|
||||
#[inline]
|
||||
fn der(&self, _: f64) -> Self::Vector { self.1 - self.0 }
|
||||
#[inline]
|
||||
fn der2(&self, _: f64) -> Self::Vector { Self::Vector::zero() }
|
||||
/// Return `0.0..=1.0` i.e. we regard it as a segment
|
||||
#[inline]
|
||||
fn parameter_range(&self) -> ParameterRange { (Bound::Included(0.0), Bound::Included(1.0)) }
|
||||
}
|
||||
|
||||
impl<P: ControlPoint<f64>> BoundedCurve for Line<P> {}
|
||||
|
||||
impl<P: ControlPoint<f64>> Cut for Line<P> {
|
||||
#[inline]
|
||||
fn cut(&mut self, t: f64) -> Self {
|
||||
let r = self.subs(t);
|
||||
let res = Self(r, self.1);
|
||||
self.1 = r;
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: ControlPoint<f64>> ParameterDivision1D for Line<P> {
|
||||
type Point = P;
|
||||
#[inline]
|
||||
fn parameter_division(&self, range: (f64, f64), _: f64) -> (Vec<f64>, Vec<P>) {
|
||||
(
|
||||
vec![range.0, range.1],
|
||||
vec![self.subs(range.0), self.subs(range.1)],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Copy> Invertible for Line<P> {
|
||||
#[inline]
|
||||
fn invert(&mut self) { *self = Self(self.1, self.0); }
|
||||
#[inline]
|
||||
fn inverse(&self) -> Self { Self(self.1, self.0) }
|
||||
}
|
||||
|
||||
impl<P> SearchNearestParameter<D1> for Line<P>
|
||||
where
|
||||
P: ControlPoint<f64>,
|
||||
P::Diff: InnerSpace<Scalar = f64>,
|
||||
{
|
||||
type Point = P;
|
||||
#[inline]
|
||||
fn search_nearest_parameter<H: Into<SPHint1D>>(&self, pt: P, _: H, _: usize) -> Option<f64> {
|
||||
let b = self.1 - self.0;
|
||||
Some((pt - self.0).dot(b) / b.dot(b))
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> SearchParameter<D1> for Line<P>
|
||||
where
|
||||
P: ControlPoint<f64> + Tolerance,
|
||||
P::Diff: InnerSpace<Scalar = f64>,
|
||||
{
|
||||
type Point = P;
|
||||
#[inline]
|
||||
fn search_parameter<H: Into<SPHint1D>>(&self, pt: P, _: H, _: usize) -> Option<f64> {
|
||||
let b = self.1 - self.0;
|
||||
let t = (pt - self.0).dot(b) / b.dot(b);
|
||||
match self.subs(t).near(&pt) {
|
||||
true => Some(t),
|
||||
false => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: EuclideanSpace, M: Transform<P>> Transformed<M> for Line<P> {
|
||||
#[inline]
|
||||
fn transform_by(&mut self, trans: M) {
|
||||
self.0 = trans.transform_point(self.0);
|
||||
self.1 = trans.transform_point(self.1);
|
||||
}
|
||||
#[inline]
|
||||
fn transformed(&self, trans: M) -> Self {
|
||||
Line(trans.transform_point(self.0), trans.transform_point(self.1))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn line() {
|
||||
let line = Line(Point2::new(1.0, 0.0), Point2::new(0.0, 1.0));
|
||||
|
||||
// subs
|
||||
assert_near!(line.subs(0.4), Point2::new(0.6, 0.4));
|
||||
|
||||
// inverse
|
||||
let line_inverse = line.inverse();
|
||||
assert_eq!(line.0, line_inverse.1);
|
||||
assert_eq!(line.1, line_inverse.0);
|
||||
|
||||
// cut
|
||||
let mut line0 = line;
|
||||
let line1 = line0.cut(0.4);
|
||||
assert_eq!(line.0, line0.0);
|
||||
assert_near!(line0.1, line.subs(0.4));
|
||||
assert_eq!(line0.1, line1.0);
|
||||
assert_eq!(line1.1, line.1);
|
||||
|
||||
// SNP
|
||||
assert_near!(
|
||||
line.search_nearest_parameter(Point2::new(1.0, 1.0), None, 0)
|
||||
.unwrap(),
|
||||
0.5
|
||||
);
|
||||
assert!(line
|
||||
.search_parameter(Point2::new(1.0, 1.0), None, 0)
|
||||
.is_none());
|
||||
}
|
||||
-115
@@ -1,115 +0,0 @@
|
||||
use crate::{prelude::*, *};
|
||||
|
||||
/// line
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let line = Line(Point2::new(0.0, 0.0), Point2::new(1.0, 1.0));
|
||||
/// assert_near!(line.subs(0.5), Point2::new(0.5, 0.5));
|
||||
/// ```
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Line<P>(pub P, pub P);
|
||||
|
||||
/// unit circle
|
||||
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)]
|
||||
pub struct UnitCircle<P>(std::marker::PhantomData<P>);
|
||||
|
||||
/// unit hyperbola
|
||||
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)]
|
||||
pub struct UnitHyperbola<P>(std::marker::PhantomData<P>);
|
||||
|
||||
/// parabola whose apex is the origin.
|
||||
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)]
|
||||
pub struct UnitParabola<P>(std::marker::PhantomData<P>);
|
||||
|
||||
/// plane
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
///
|
||||
/// // arbitrary three points
|
||||
/// let pt0 = Point3::new(0.0, 1.0, 2.0);
|
||||
/// let pt1 = Point3::new(1.0, 1.0, 3.0);
|
||||
/// let pt2 = Point3::new(0.0, 2.0, 3.0);
|
||||
///
|
||||
/// // Creates a plane
|
||||
/// let plane: Plane = Plane::new(pt0, pt1, pt2);
|
||||
/// // The origin of the plane is pt0.
|
||||
/// assert_near!(plane.origin(), pt0);
|
||||
/// // The u-axis of the plane is the vector from pt0 to pt1.
|
||||
/// assert_near!(plane.u_axis(), pt1 - pt0);
|
||||
/// // The v-axis of the plane is the vector from pt0 to pt2.
|
||||
/// assert_near!(plane.v_axis(), pt2 - pt0);
|
||||
/// // The normal is the normalized u-axis × v-axis
|
||||
/// assert_near!(plane.normal(), (pt1 - pt0).cross(pt2 - pt0).normalize());
|
||||
/// ```
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Plane {
|
||||
o: Point3,
|
||||
p: Point3,
|
||||
q: Point3,
|
||||
}
|
||||
|
||||
/// sphere
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// use std::f64::consts::PI;
|
||||
///
|
||||
/// let center = Point3::new(1.0, 2.0, 3.0);
|
||||
/// let radius = 4.56;
|
||||
///
|
||||
/// let sphere = Sphere::new(center, radius);
|
||||
/// const N: usize = 100;
|
||||
/// for i in 0..=N {
|
||||
/// for j in 0..=N {
|
||||
/// // the parameter u is latitude
|
||||
/// let u = PI * i as f64 / N as f64;
|
||||
/// // the parameter v is longitude
|
||||
/// let v = 2.0 * PI * j as f64 / N as f64;
|
||||
///
|
||||
/// // simple relation between a point and its normal.
|
||||
/// let pt = sphere.subs(u, v);
|
||||
/// let n = sphere.normal(u, v);
|
||||
/// assert_near!(pt - center, n * radius);
|
||||
///
|
||||
/// // the proof of u is latitude and v is longitude
|
||||
/// assert!((PI / 2.0 - u) * (pt.z - center.z) >= 0.0);
|
||||
/// assert!((PI - v) * (pt.y - center.y) >= 0.0);
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Sphere {
|
||||
center: Point3,
|
||||
radius: f64,
|
||||
}
|
||||
|
||||
/// torus
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Torus {
|
||||
center: Point3,
|
||||
large_radius: f64,
|
||||
small_radius: f64,
|
||||
}
|
||||
|
||||
mod circle;
|
||||
mod hyperbola;
|
||||
mod line;
|
||||
mod parabola;
|
||||
mod plane;
|
||||
mod sphere;
|
||||
mod torus;
|
||||
|
||||
macro_rules! always_true {
|
||||
($ty: tt) => {
|
||||
impl<P> PartialEq for $ty<P> {
|
||||
fn eq(&self, _: &Self) -> bool { true }
|
||||
}
|
||||
impl<P> Eq for $ty<P> {}
|
||||
};
|
||||
}
|
||||
|
||||
always_true!(UnitCircle);
|
||||
always_true!(UnitParabola);
|
||||
always_true!(UnitHyperbola);
|
||||
-137
@@ -1,137 +0,0 @@
|
||||
use super::*;
|
||||
|
||||
impl<P> UnitParabola<P> {
|
||||
/// constructor
|
||||
#[inline]
|
||||
pub const fn new() -> Self { Self(std::marker::PhantomData) }
|
||||
}
|
||||
|
||||
impl ParametricCurve for UnitParabola<Point2> {
|
||||
type Point = Point2;
|
||||
type Vector = Vector2;
|
||||
#[inline]
|
||||
fn subs(&self, t: f64) -> Self::Point { Point2::new(t * t, 2.0 * t) }
|
||||
#[inline]
|
||||
fn der(&self, t: f64) -> Self::Vector { Vector2::new(2.0 * t, 2.0) }
|
||||
#[inline]
|
||||
fn der2(&self, _: f64) -> Self::Vector { Vector2::new(2.0, 0.0) }
|
||||
}
|
||||
|
||||
impl ParametricCurve for UnitParabola<Point3> {
|
||||
type Point = Point3;
|
||||
type Vector = Vector3;
|
||||
#[inline]
|
||||
fn subs(&self, t: f64) -> Self::Point { Point3::new(t * t, 2.0 * t, 0.0) }
|
||||
#[inline]
|
||||
fn der(&self, t: f64) -> Self::Vector { Vector3::new(2.0 * t, 2.0, 0.0) }
|
||||
#[inline]
|
||||
fn der2(&self, _: f64) -> Self::Vector { Vector3::new(2.0, 0.0, 0.0) }
|
||||
}
|
||||
|
||||
impl<P> ParameterDivision1D for UnitParabola<P>
|
||||
where
|
||||
UnitParabola<P>: ParametricCurve<Point = P>,
|
||||
P: EuclideanSpace<Scalar = f64> + MetricSpace<Metric = f64> + HashGen<f64>,
|
||||
{
|
||||
type Point = P;
|
||||
fn parameter_division(&self, range: (f64, f64), tol: f64) -> (Vec<f64>, Vec<P>) {
|
||||
algo::curve::parameter_division(self, range, tol)
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchNearestParameter<D1> for UnitParabola<Point2> {
|
||||
type Point = Point2;
|
||||
#[inline]
|
||||
fn search_nearest_parameter<H: Into<SPHint1D>>(
|
||||
&self,
|
||||
pt: Point2,
|
||||
_: H,
|
||||
_: usize,
|
||||
) -> Option<f64> {
|
||||
let p = 2.0 - pt.x;
|
||||
let q = -pt.y;
|
||||
solver::pre_solve_cubic(p, q)
|
||||
.into_iter()
|
||||
.filter_map(|x| match x.im.so_small() {
|
||||
true => Some(x.re),
|
||||
false => None,
|
||||
})
|
||||
.min_by(|s, t| {
|
||||
pt.distance2(self.subs(*s))
|
||||
.partial_cmp(&pt.distance2(self.subs(*t)))
|
||||
.unwrap()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchNearestParameter<D1> for UnitParabola<Point3> {
|
||||
type Point = Point3;
|
||||
#[inline]
|
||||
fn search_nearest_parameter<H: Into<SPHint1D>>(
|
||||
&self,
|
||||
pt: Point3,
|
||||
_hint: H,
|
||||
_trials: usize,
|
||||
) -> Option<f64> {
|
||||
UnitParabola::<Point2>::new().search_nearest_parameter(
|
||||
Point2::new(pt.x, pt.y),
|
||||
_hint,
|
||||
_trials,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchParameter<D1> for UnitParabola<Point2> {
|
||||
type Point = Point2;
|
||||
#[inline]
|
||||
fn search_parameter<H: Into<SPHint1D>>(&self, pt: Point2, _: H, _: usize) -> Option<f64> {
|
||||
let t = pt.y / 2.0;
|
||||
let pt0 = self.subs(t);
|
||||
match pt.near(&pt0) {
|
||||
true => Some(t),
|
||||
false => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchParameter<D1> for UnitParabola<Point3> {
|
||||
type Point = Point3;
|
||||
#[inline]
|
||||
fn search_parameter<H: Into<SPHint1D>>(
|
||||
&self,
|
||||
pt: Point3,
|
||||
_hint: H,
|
||||
_trials: usize,
|
||||
) -> Option<f64> {
|
||||
match pt.z.so_small() {
|
||||
true => UnitParabola::<Point2>::new().search_parameter(
|
||||
Point2::new(pt.x, pt.y),
|
||||
_hint,
|
||||
_trials,
|
||||
),
|
||||
false => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn snp_test() {
|
||||
let curve = UnitParabola::<Point2>::new();
|
||||
|
||||
let p = Point2::new(-3.0, 0.0);
|
||||
assert_near!(curve.search_nearest_parameter(p, None, 0).unwrap(), 0.0);
|
||||
let p = Point2::new(-3.0, 6.0);
|
||||
assert_near!(curve.search_nearest_parameter(p, None, 0).unwrap(), 1.0);
|
||||
let p = Point2::new(1.5, 1.5);
|
||||
assert_near!(curve.search_nearest_parameter(p, None, 0).unwrap(), 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sp_test() {
|
||||
let curve = UnitParabola::<Point2>::new();
|
||||
|
||||
let p = Point2::new(4.0, -4.0);
|
||||
assert_near!(curve.search_parameter(p, None, 0).unwrap(), -2.0);
|
||||
let p = Point2::new(-3.0, 6.0);
|
||||
assert!(curve.search_parameter(p, None, 0).is_none());
|
||||
}
|
||||
-256
@@ -1,256 +0,0 @@
|
||||
use super::*;
|
||||
|
||||
impl Plane {
|
||||
/// Creates a new plane from three points.
|
||||
#[inline(always)]
|
||||
pub const fn new(origin: Point3, one: Point3, another: Point3) -> Plane {
|
||||
Plane {
|
||||
o: origin,
|
||||
p: one,
|
||||
q: another,
|
||||
}
|
||||
}
|
||||
/// Returns the origin
|
||||
#[inline(always)]
|
||||
pub const fn origin(&self) -> Point3 { self.o }
|
||||
/// Returns the u-axis
|
||||
#[inline(always)]
|
||||
pub fn u_axis(&self) -> Vector3 { self.p - self.o }
|
||||
/// Returns the v-axis
|
||||
#[inline(always)]
|
||||
pub fn v_axis(&self) -> Vector3 { self.q - self.o }
|
||||
/// Returns the normal
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let plane = Plane::new(
|
||||
/// Point3::new(0.0, 0.0, 0.0),
|
||||
/// Point3::new(1.0, 0.0, 0.0),
|
||||
/// Point3::new(0.0, 1.0, 0.0),
|
||||
/// );
|
||||
/// assert_near!(plane.normal(), Vector3::unit_z());
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn normal(&self) -> Vector3 { self.u_axis().cross(self.v_axis()).normalize() }
|
||||
/// Gets the parameter of `pt` in plane's matrix.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let plane = Plane::new(
|
||||
/// Point3::new(1.0, 2.0, 3.0),
|
||||
/// Point3::new(2.0, 1.0, 3.0),
|
||||
/// Point3::new(3.0, 4.0, -1.0),
|
||||
/// );
|
||||
///
|
||||
/// let pt = Point3::new(2.1, -6.5, 4.7);
|
||||
/// let prm = plane.get_parameter(pt);
|
||||
/// let rev = plane.origin()
|
||||
/// + prm[0] * plane.u_axis()
|
||||
/// + prm[1] * plane.v_axis()
|
||||
/// + prm[2] * plane.normal();
|
||||
/// assert_near!(pt, rev);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn get_parameter(&self, pt: Point3) -> Vector3 {
|
||||
let a = self.u_axis();
|
||||
let b = self.v_axis();
|
||||
let c = self.normal();
|
||||
let mat = Matrix3::from_cols(a, b, c).invert().unwrap();
|
||||
mat * (pt - self.o)
|
||||
}
|
||||
/// into B-spline surface
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let pt0 = Point3::new(0.0, 1.0, 2.0);
|
||||
/// let pt1 = Point3::new(1.0, 1.0, 3.0);
|
||||
/// let pt2 = Point3::new(0.0, 2.0, 3.0);
|
||||
/// let plane: Plane = Plane::new(pt0, pt1, pt2);
|
||||
/// let surface: BSplineSurface<Point3> = plane.into_bspline();
|
||||
/// assert_eq!(surface.range_tuple(), ((0.0, 1.0), (0.0, 1.0)));
|
||||
///
|
||||
/// const N: usize = 100;
|
||||
/// for i in 0..=N {
|
||||
/// for j in 0..=N {
|
||||
/// let u = i as f64 / N as f64;
|
||||
/// let v = j as f64 / N as f64;
|
||||
/// let res = surface.subs(u, v);
|
||||
/// let ans = plane.subs(u, v);
|
||||
/// assert_near!(ans, res);
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn into_bspline(&self) -> BSplineSurface<Point3> {
|
||||
let o = self.o;
|
||||
let p = self.p;
|
||||
let q = self.q;
|
||||
BSplineSurface::debug_new(
|
||||
(KnotVec::bezier_knot(1), KnotVec::bezier_knot(1)),
|
||||
vec![vec![o, q], vec![p, p + (q - o)]],
|
||||
)
|
||||
}
|
||||
/// into NURBS surface
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_geometry::prelude::*;
|
||||
/// let pt0 = Point3::new(0.0, 1.0, 2.0);
|
||||
/// let pt1 = Point3::new(1.0, 1.0, 3.0);
|
||||
/// let pt2 = Point3::new(0.0, 2.0, 3.0);
|
||||
/// let plane: Plane = Plane::new(pt0, pt1, pt2);
|
||||
/// let surface: NurbsSurface<Vector4> = plane.into_nurbs();
|
||||
/// assert_eq!(surface.range_tuple(), ((0.0, 1.0), (0.0, 1.0)));
|
||||
///
|
||||
/// const N: usize = 100;
|
||||
/// for i in 0..=N {
|
||||
/// for j in 0..=N {
|
||||
/// let u = i as f64 / N as f64;
|
||||
/// let v = j as f64 / N as f64;
|
||||
/// let res = surface.subs(u, v);
|
||||
/// let ans = plane.subs(u, v);
|
||||
/// assert_near!(ans, res);
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn into_nurbs(&self) -> NurbsSurface<Vector4> {
|
||||
let o = self.o.to_homogeneous();
|
||||
let p = self.p.to_homogeneous();
|
||||
let q = self.q.to_homogeneous();
|
||||
NurbsSurface::new(BSplineSurface::debug_new(
|
||||
(KnotVec::bezier_knot(1), KnotVec::bezier_knot(1)),
|
||||
vec![vec![o, q], vec![p, p + q - o]],
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl ParametricSurface for Plane {
|
||||
type Point = Point3;
|
||||
type Vector = Vector3;
|
||||
#[inline(always)]
|
||||
fn subs(&self, u: f64, v: f64) -> Point3 {
|
||||
self.o + u * (self.p - self.o) + v * (self.q - self.o)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn uder(&self, _: f64, _: f64) -> Vector3 { self.p - self.o }
|
||||
#[inline(always)]
|
||||
fn vder(&self, _: f64, _: f64) -> Vector3 { self.q - self.o }
|
||||
#[inline(always)]
|
||||
fn uuder(&self, _: f64, _: f64) -> Vector3 { Vector3::zero() }
|
||||
#[inline(always)]
|
||||
fn uvder(&self, _: f64, _: f64) -> Vector3 { Vector3::zero() }
|
||||
#[inline(always)]
|
||||
fn vvder(&self, _: f64, _: f64) -> Vector3 { Vector3::zero() }
|
||||
/// as square
|
||||
#[inline(always)]
|
||||
fn parameter_range(&self) -> (ParameterRange, ParameterRange) {
|
||||
let range = (Bound::Included(0.0), Bound::Included(1.0));
|
||||
(range, range)
|
||||
}
|
||||
}
|
||||
|
||||
impl ParametricSurface3D for Plane {
|
||||
#[inline(always)]
|
||||
fn normal(&self, _: f64, _: f64) -> Vector3 { self.normal() }
|
||||
}
|
||||
|
||||
impl BoundedSurface for Plane {}
|
||||
|
||||
impl Invertible for Plane {
|
||||
#[inline(always)]
|
||||
fn inverse(&self) -> Self {
|
||||
Plane {
|
||||
o: self.o,
|
||||
p: self.q,
|
||||
q: self.p,
|
||||
}
|
||||
}
|
||||
#[inline(always)]
|
||||
fn invert(&mut self) { *self = self.inverse(); }
|
||||
}
|
||||
|
||||
impl IncludeCurve<BSplineCurve<Point3>> for Plane {
|
||||
#[inline(always)]
|
||||
fn include(&self, curve: &BSplineCurve<Point3>) -> bool {
|
||||
let origin = self.origin();
|
||||
let normal = self.normal();
|
||||
curve
|
||||
.control_points()
|
||||
.iter()
|
||||
.all(|pt| (pt - origin).dot(normal).so_small())
|
||||
}
|
||||
}
|
||||
|
||||
impl IncludeCurve<NurbsCurve<Vector4>> for Plane {
|
||||
fn include(&self, curve: &NurbsCurve<Vector4>) -> bool {
|
||||
let origin = self.origin();
|
||||
let normal = self.normal();
|
||||
let (s, e) = (curve.front(), curve.back());
|
||||
if !(s - origin).dot(normal).so_small() || !(e - origin).dot(normal).so_small() {
|
||||
return false;
|
||||
}
|
||||
curve.non_rationalized().control_points().iter().all(|pt| {
|
||||
if pt[3].so_small() {
|
||||
true
|
||||
} else {
|
||||
let pt = Point3::from_homogeneous(*pt);
|
||||
(pt - origin).dot(normal).so_small()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ParameterDivision2D for Plane {
|
||||
#[inline(always)]
|
||||
fn parameter_division(&self, range: ((f64, f64), (f64, f64)), _: f64) -> (Vec<f64>, Vec<f64>) {
|
||||
(vec![range.0 .0, range.0 .1], vec![range.1 .0, range.1 .1])
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Transform3<Scalar = f64>> Transformed<T> for Plane {
|
||||
#[inline(always)]
|
||||
fn transform_by(&mut self, trans: T) {
|
||||
self.o = trans.transform_point(self.o);
|
||||
self.p = trans.transform_point(self.p);
|
||||
self.q = trans.transform_point(self.q);
|
||||
}
|
||||
#[inline(always)]
|
||||
fn transformed(&self, trans: T) -> Self {
|
||||
Plane {
|
||||
o: trans.transform_point(self.o),
|
||||
p: trans.transform_point(self.p),
|
||||
q: trans.transform_point(self.q),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchParameter<D2> for Plane {
|
||||
type Point = Point3;
|
||||
#[inline(always)]
|
||||
fn search_parameter<H: Into<SPHint2D>>(
|
||||
&self,
|
||||
point: Point3,
|
||||
_: H,
|
||||
_: usize,
|
||||
) -> Option<(f64, f64)> {
|
||||
let v = self.get_parameter(point);
|
||||
match v[2].so_small() {
|
||||
true => Some((v[0], v[1])),
|
||||
false => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchNearestParameter<D2> for Plane {
|
||||
type Point = Point3;
|
||||
#[inline(always)]
|
||||
fn search_nearest_parameter<H: Into<SPHint2D>>(
|
||||
&self,
|
||||
point: Point3,
|
||||
_: H,
|
||||
_: usize,
|
||||
) -> Option<(f64, f64)> {
|
||||
let v = self.get_parameter(point);
|
||||
Some((v[0], v[1]))
|
||||
}
|
||||
}
|
||||
-219
@@ -1,219 +0,0 @@
|
||||
use super::*;
|
||||
use std::f64::consts::PI;
|
||||
|
||||
impl Sphere {
|
||||
/// Creates a sphere
|
||||
#[inline(always)]
|
||||
pub const fn new(center: Point3, radius: f64) -> Sphere { Sphere { center, radius } }
|
||||
/// Returns the center
|
||||
#[inline(always)]
|
||||
pub const fn center(&self) -> Point3 { self.center }
|
||||
/// Returns the radius
|
||||
#[inline(always)]
|
||||
pub const fn radius(&self) -> f64 { self.radius }
|
||||
/// Returns whether the point `pt` is on sphere
|
||||
#[inline(always)]
|
||||
pub fn include(&self, pt: Point3) -> bool { self.center.distance(pt).near(&self.radius) }
|
||||
}
|
||||
|
||||
impl ParametricSurface for Sphere {
|
||||
type Point = Point3;
|
||||
type Vector = Vector3;
|
||||
#[inline(always)]
|
||||
fn subs(&self, u: f64, v: f64) -> Point3 { self.center() + self.radius * self.normal(u, v) }
|
||||
#[inline(always)]
|
||||
fn uder(&self, u: f64, v: f64) -> Vector3 {
|
||||
self.radius
|
||||
* Vector3::new(
|
||||
f64::cos(u) * f64::cos(v),
|
||||
f64::cos(u) * f64::sin(v),
|
||||
-f64::sin(u),
|
||||
)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn vder(&self, u: f64, v: f64) -> Vector3 {
|
||||
self.radius * f64::sin(u) * Vector3::new(-f64::sin(v), f64::cos(v), 0.0)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn uuder(&self, u: f64, v: f64) -> Vector3 { -self.radius * self.normal(u, v) }
|
||||
#[inline(always)]
|
||||
fn uvder(&self, u: f64, v: f64) -> Vector3 {
|
||||
self.radius * f64::cos(u) * Vector3::new(-f64::sin(v), f64::cos(v), 0.0)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn vvder(&self, u: f64, v: f64) -> Vector3 {
|
||||
-self.radius * f64::sin(u) * Vector3::new(f64::cos(v), f64::sin(v), 0.0)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn parameter_range(&self) -> (ParameterRange, ParameterRange) {
|
||||
(
|
||||
(Bound::Included(0.0), Bound::Included(PI)),
|
||||
(Bound::Included(0.0), Bound::Excluded(2.0 * PI)),
|
||||
)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn v_period(&self) -> Option<f64> { Some(2.0 * PI) }
|
||||
}
|
||||
|
||||
impl ParametricSurface3D for Sphere {
|
||||
#[inline(always)]
|
||||
fn normal(&self, u: f64, v: f64) -> Vector3 {
|
||||
Vector3::new(
|
||||
f64::sin(u) * f64::cos(v),
|
||||
f64::sin(u) * f64::sin(v),
|
||||
f64::cos(u),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sphere_derivation_test() {
|
||||
let center = Point3::new(1.0, 2.0, 3.0);
|
||||
let radius = 4.56;
|
||||
let sphere = Sphere::new(center, radius);
|
||||
const N: usize = 100;
|
||||
for i in 0..N {
|
||||
for j in 0..N {
|
||||
let u = PI * i as f64 / N as f64;
|
||||
let v = 2.0 * PI * j as f64 / N as f64;
|
||||
let normal = sphere.normal(u, v);
|
||||
assert!(normal.dot(sphere.uder(u, v)).so_small());
|
||||
assert!(normal.dot(sphere.vder(u, v)).so_small());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BoundedSurface for Sphere {}
|
||||
|
||||
impl IncludeCurve<BSplineCurve<Point3>> for Sphere {
|
||||
#[inline(always)]
|
||||
fn include(&self, curve: &BSplineCurve<Point3>) -> bool {
|
||||
curve.is_const() && self.include(curve.front())
|
||||
}
|
||||
}
|
||||
|
||||
impl IncludeCurve<NurbsCurve<Vector4>> for Sphere {
|
||||
fn include(&self, curve: &NurbsCurve<Vector4>) -> bool {
|
||||
let (knots, _) = curve.knot_vec().to_single_multi();
|
||||
let degree = curve.degree() * 2;
|
||||
knots
|
||||
.windows(2)
|
||||
.flat_map(move |window| (1..degree).map(move |i| (window, i)))
|
||||
.all(move |(window, i)| {
|
||||
let t = i as f64 / degree as f64;
|
||||
let t = window[0] * (1.0 - t) + window[1] * t;
|
||||
self.include(curve.subs(t))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ParameterDivision2D for Sphere {
|
||||
#[inline(always)]
|
||||
fn parameter_division(
|
||||
&self,
|
||||
(urange, vrange): ((f64, f64), (f64, f64)),
|
||||
tol: f64,
|
||||
) -> (Vec<f64>, Vec<f64>) {
|
||||
nonpositive_tolerance!(tol);
|
||||
assert!(
|
||||
tol < self.radius,
|
||||
"Tolerance is larger than the radius of sphere."
|
||||
);
|
||||
let delta = 2.0 * f64::acos(1.0 - tol / self.radius);
|
||||
let u_div = 1 + ((urange.1 - urange.0) / delta).floor() as usize;
|
||||
let v_div = 1 + ((vrange.1 - vrange.0) / delta).floor() as usize;
|
||||
(
|
||||
(0..=u_div)
|
||||
.map(|i| urange.0 + (urange.1 - urange.0) * i as f64 / u_div as f64)
|
||||
.collect(),
|
||||
(0..=v_div)
|
||||
.map(|j| vrange.0 + (vrange.1 - vrange.0) * j as f64 / v_div as f64)
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchParameter<D2> for Sphere {
|
||||
type Point = Point3;
|
||||
#[inline(always)]
|
||||
fn search_parameter<H: Into<SPHint2D>>(
|
||||
&self,
|
||||
point: Point3,
|
||||
hint: H,
|
||||
_: usize,
|
||||
) -> Option<(f64, f64)> {
|
||||
let radius = point - self.center;
|
||||
if (self.radius * self.radius).near(&radius.magnitude2()) {
|
||||
let radius = radius.normalize();
|
||||
let u = f64::acos(radius[2]);
|
||||
let sinu = f64::sqrt(1.0 - radius[2] * radius[2]);
|
||||
let cosv = f64::clamp(radius[0] / sinu, -1.0, 1.0);
|
||||
let v = if sinu.so_small() {
|
||||
match hint.into() {
|
||||
SPHint2D::Parameter(_, hint) => hint,
|
||||
_ => 0.0,
|
||||
}
|
||||
} else if radius[1] > 0.0 {
|
||||
f64::acos(cosv)
|
||||
} else {
|
||||
2.0 * PI - f64::acos(cosv)
|
||||
};
|
||||
Some((u, v))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchNearestParameter<D2> for Sphere {
|
||||
type Point = Point3;
|
||||
#[inline(always)]
|
||||
fn search_nearest_parameter<H: Into<SPHint2D>>(
|
||||
&self,
|
||||
point: Point3,
|
||||
_: H,
|
||||
_: usize,
|
||||
) -> Option<(f64, f64)> {
|
||||
let radius = (point - self.center).normalize();
|
||||
let u = f64::acos(radius[2]);
|
||||
let sinu = f64::sqrt(1.0 - radius[2] * radius[2]);
|
||||
let cosv = radius[0] / sinu;
|
||||
let v = if radius[1] > 0.0 {
|
||||
f64::acos(cosv)
|
||||
} else {
|
||||
2.0 * PI - f64::acos(cosv)
|
||||
};
|
||||
Some((u, v))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn exec_search_parameter_test() {
|
||||
let center = Point3::new(
|
||||
100.0 * rand::random::<f64>() - 50.0,
|
||||
100.0 * rand::random::<f64>() - 50.0,
|
||||
100.0 * rand::random::<f64>() - 50.0,
|
||||
);
|
||||
let radius = 100.0 * rand::random::<f64>();
|
||||
let sphere = Sphere::new(center, radius);
|
||||
let u = PI * rand::random::<f64>();
|
||||
let v = 2.0 * PI * rand::random::<f64>();
|
||||
let pt = sphere.subs(u, v);
|
||||
let (u0, v0) = sphere.search_parameter(pt, None, 100).unwrap();
|
||||
assert_near!(Vector2::new(u, v), Vector2::new(u0, v0));
|
||||
let pt = pt
|
||||
+ Vector3::new(
|
||||
(0.1 * rand::random::<f64>() + 0.01) * f64::signum(rand::random::<f64>() - 0.5),
|
||||
(0.1 * rand::random::<f64>() + 0.01) * f64::signum(rand::random::<f64>() - 0.5),
|
||||
(0.1 * rand::random::<f64>() + 0.01) * f64::signum(rand::random::<f64>() - 0.5),
|
||||
);
|
||||
assert!(sphere.search_parameter(pt, None, 100).is_none());
|
||||
let (u, v) = sphere.search_nearest_parameter(pt, None, 100).unwrap();
|
||||
assert_near!(
|
||||
sphere.subs(u, v),
|
||||
center + (pt - center).normalize() * radius
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn search_parameter_test() { (0..10).for_each(|_| exec_search_parameter_test()) }
|
||||
-238
@@ -1,238 +0,0 @@
|
||||
use super::*;
|
||||
use std::f64::consts::PI;
|
||||
|
||||
impl Torus {
|
||||
/// constructor
|
||||
#[inline(always)]
|
||||
pub fn new(center: Point3, large_radius: f64, small_radius: f64) -> Self {
|
||||
if large_radius <= 0.0 || small_radius <= 0.0 {
|
||||
panic!("radius must be larger than 0");
|
||||
}
|
||||
Self {
|
||||
center,
|
||||
large_radius,
|
||||
small_radius,
|
||||
}
|
||||
}
|
||||
|
||||
/// get center
|
||||
#[inline(always)]
|
||||
pub const fn center(&self) -> Point3 { self.center }
|
||||
|
||||
/// get large radius
|
||||
#[inline(always)]
|
||||
pub const fn large_radius(&self) -> f64 { self.large_radius }
|
||||
|
||||
/// get small radius
|
||||
#[inline(always)]
|
||||
pub const fn small_radius(&self) -> f64 { self.small_radius }
|
||||
}
|
||||
|
||||
impl ParametricSurface for Torus {
|
||||
type Point = Point3;
|
||||
type Vector = Vector3;
|
||||
#[inline(always)]
|
||||
fn subs(&self, u: f64, v: f64) -> Point3 {
|
||||
let sr = self.small_radius() * Vector2::new(f64::cos(v), f64::sin(v));
|
||||
let lr = (self.large_radius() + sr.x) * Vector2::new(f64::cos(u), f64::sin(u));
|
||||
self.center() + Vector3::new(lr.x, lr.y, sr.y)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn uder(&self, u: f64, v: f64) -> Vector3 {
|
||||
let sr = self.small_radius() * f64::cos(v);
|
||||
let lr = (self.large_radius() + sr) * Vector2::new(f64::cos(u), f64::sin(u));
|
||||
Vector3::new(-lr.y, lr.x, 0.0)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn vder(&self, u: f64, v: f64) -> Vector3 {
|
||||
let sv = self.small_radius() * Vector2::new(-f64::sin(v), f64::cos(v));
|
||||
Vector3::new(sv.x * f64::cos(u), sv.x * f64::sin(u), sv.y)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn uuder(&self, u: f64, v: f64) -> Vector3 {
|
||||
let sr = self.small_radius() * f64::cos(v);
|
||||
let lr = (self.large_radius() + sr) * Vector2::new(f64::cos(u), f64::sin(u));
|
||||
Vector3::new(-lr.x, -lr.y, 0.0)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn uvder(&self, u: f64, v: f64) -> Vector3 {
|
||||
let sr = -self.small_radius() * f64::sin(v);
|
||||
let lr = sr * Vector2::new(f64::cos(u), f64::sin(u));
|
||||
Vector3::new(-lr.y, lr.x, 0.0)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn vvder(&self, u: f64, v: f64) -> Vector3 {
|
||||
let sv = -self.small_radius() * Vector2::new(f64::cos(v), f64::sin(v));
|
||||
Vector3::new(sv.x * f64::cos(u), sv.x * f64::sin(u), sv.y)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn parameter_range(&self) -> (ParameterRange, ParameterRange) {
|
||||
const RANGE: (Bound<f64>, Bound<f64>) = (Bound::Included(0.0), Bound::Excluded(2.0 * PI));
|
||||
(RANGE, RANGE)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn u_period(&self) -> Option<f64> { Some(2.0 * PI) }
|
||||
#[inline(always)]
|
||||
fn v_period(&self) -> Option<f64> { Some(2.0 * PI) }
|
||||
}
|
||||
|
||||
impl ParametricSurface3D for Torus {
|
||||
fn normal(&self, u: f64, v: f64) -> Vector3 {
|
||||
let sv = Vector2::new(f64::cos(v), f64::sin(v));
|
||||
Vector3::new(sv.x * f64::cos(u), sv.x * f64::sin(u), sv.y)
|
||||
}
|
||||
}
|
||||
|
||||
impl BoundedSurface for Torus {}
|
||||
|
||||
impl SearchParameter<D2> for Torus {
|
||||
type Point = Point3;
|
||||
fn search_parameter<H: Into<SPHint2D>>(
|
||||
&self,
|
||||
point: Point3,
|
||||
_: H,
|
||||
_: usize,
|
||||
) -> Option<(f64, f64)> {
|
||||
let r = point - self.center();
|
||||
let rxy = Vector2::new(r.x, r.y);
|
||||
let v = f64::asin(f64::clamp(r.z / self.small_radius(), -1.0, 1.0));
|
||||
let minus = rxy.magnitude2() < self.large_radius() * self.large_radius();
|
||||
let v = match (minus, v < 0.0) {
|
||||
(true, _) => PI - v,
|
||||
(false, false) => v,
|
||||
(false, true) => 2.0 * PI + v,
|
||||
};
|
||||
let rxy_n = rxy.normalize();
|
||||
let u = f64::acos(f64::clamp(rxy_n.x, -1.0, 1.0));
|
||||
let u = match rxy_n.y < 0.0 {
|
||||
true => 2.0 * PI - u,
|
||||
false => u,
|
||||
};
|
||||
match self.subs(u, v).near(&point) {
|
||||
true => Some((u, v)),
|
||||
false => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SearchNearestParameter<D2> for Torus {
|
||||
type Point = Point3;
|
||||
fn search_nearest_parameter<H: Into<SPHint2D>>(
|
||||
&self,
|
||||
point: Point3,
|
||||
_: H,
|
||||
_: usize,
|
||||
) -> Option<(f64, f64)> {
|
||||
let r = point - self.center();
|
||||
let rxy = Vector2::new(r.x, r.y);
|
||||
if rxy.so_small() {
|
||||
return None;
|
||||
}
|
||||
let rxy_n = rxy.normalize();
|
||||
let large_r = self.large_radius() * rxy_n.extend(0.0);
|
||||
let diff = r - large_r;
|
||||
if diff.so_small() {
|
||||
return None;
|
||||
}
|
||||
let small_r = diff.normalize();
|
||||
|
||||
let u = f64::acos(f64::clamp(rxy_n.x, -1.0, 1.0));
|
||||
let u = match rxy_n.y < 0.0 {
|
||||
true => 2.0 * PI - u,
|
||||
false => u,
|
||||
};
|
||||
let v = f64::asin(f64::clamp(small_r.z, -1.0, 1.0));
|
||||
let v = match (small_r.dot(large_r) < 0.0, v < 0.0) {
|
||||
(true, _) => PI - v,
|
||||
(false, false) => v,
|
||||
(false, true) => 2.0 * PI + v,
|
||||
};
|
||||
Some((u, v))
|
||||
}
|
||||
}
|
||||
|
||||
impl ParameterDivision2D for Torus {
|
||||
fn parameter_division(
|
||||
&self,
|
||||
(urange, vrange): ((f64, f64), (f64, f64)),
|
||||
tol: f64,
|
||||
) -> (Vec<f64>, Vec<f64>) {
|
||||
let circle = UnitCircle::<Point2>::new();
|
||||
let utol = tol / (self.small_radius() + self.large_radius());
|
||||
let (udiv, _) = circle.parameter_division(urange, utol);
|
||||
let vtol = tol / self.small_radius();
|
||||
let (vdiv, _) = circle.parameter_division(vrange, vtol);
|
||||
(udiv, vdiv)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn surface() {
|
||||
use std::ops::RangeBounds;
|
||||
let small_radius = 5.0 * rand::random::<f64>() + 1.0;
|
||||
let large_radius = 2.0 * small_radius + 5.0 * rand::random::<f64>();
|
||||
let torus = Torus::new(Point3::new(0.0, 0.0, 0.0), large_radius, small_radius);
|
||||
const N: usize = 10;
|
||||
const EPS: f64 = 1.0e-2;
|
||||
for i in 0..N {
|
||||
for j in 0..N {
|
||||
let u = 2.0 * PI * i as f64 / N as f64;
|
||||
let v = 2.0 * PI * j as f64 / N as f64;
|
||||
let p = torus.subs(u, v);
|
||||
let r = large_radius * Point3::new(f64::cos(u), f64::sin(u), 0.0);
|
||||
assert_near!(p.distance(r), small_radius);
|
||||
assert_near!(p.z, small_radius * f64::sin(v));
|
||||
|
||||
let uder0 = torus.uder(u, v);
|
||||
let uder1 = (torus.subs(u + EPS, v) - torus.subs(u - EPS, v)) / (2.0 * EPS);
|
||||
assert!((uder0 - uder1).magnitude() < EPS, "{:?} {:?}", uder0, uder1);
|
||||
|
||||
let vder0 = torus.vder(u, v);
|
||||
let vder1 = (torus.subs(u, v + EPS) - torus.subs(u, v - EPS)) / (2.0 * EPS);
|
||||
assert!((vder0 - vder1).magnitude() < EPS, "{:?} {:?}", vder0, vder1);
|
||||
|
||||
let uuder0 = torus.uuder(u, v);
|
||||
let uuder1 = (torus.uder(u + EPS, v) - torus.uder(u - EPS, v)) / (2.0 * EPS);
|
||||
assert!(
|
||||
(uuder0 - uuder1).magnitude() < EPS,
|
||||
"{:?} {:?}",
|
||||
uuder0,
|
||||
uuder1
|
||||
);
|
||||
|
||||
let uvder0 = torus.uvder(u, v);
|
||||
let uvder1 = (torus.vder(u + EPS, v) - torus.vder(u - EPS, v)) / (2.0 * EPS);
|
||||
assert!(
|
||||
(uvder0 - uvder1).magnitude() < EPS,
|
||||
"{:?} {:?}",
|
||||
uvder0,
|
||||
uvder1
|
||||
);
|
||||
|
||||
let vvder0 = torus.vvder(u, v);
|
||||
let vvder1 = (torus.vder(u, v + EPS) - torus.vder(u, v - EPS)) / (2.0 * EPS);
|
||||
assert!(
|
||||
(vvder0 - vvder1).magnitude() < EPS,
|
||||
"{:?} {:?}",
|
||||
vvder0,
|
||||
vvder1
|
||||
);
|
||||
|
||||
let n0 = torus.normal(u, v);
|
||||
let n1 = torus.uder(u, v).cross(torus.vder(u, v)).normalize();
|
||||
assert_near!(n0, n1);
|
||||
|
||||
let (u0, v0) = torus.search_parameter(p, None, 1).unwrap();
|
||||
let (urange, vrange) = torus.parameter_range();
|
||||
assert!(urange.contains(&u0) && vrange.contains(&v0), "{u0}, {v0}");
|
||||
assert_near!(torus.subs(u0, v0), p);
|
||||
|
||||
let deform = (0.8 * rand::random::<f64>() - 0.4) * small_radius;
|
||||
let q = p + deform * n0;
|
||||
let (u0, v0) = torus.search_nearest_parameter(q, None, 1).unwrap();
|
||||
let (urange, vrange) = torus.parameter_range();
|
||||
assert!(urange.contains(&u0) && vrange.contains(&v0), "{u0}, {v0}");
|
||||
assert_near!(torus.subs(u0, v0), p);
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
-117
@@ -1,117 +0,0 @@
|
||||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
||||
#
|
||||
# When uploading crates to the registry Cargo will automatically
|
||||
# "normalize" Cargo.toml files for maximal compatibility
|
||||
# with all versions of Cargo and also rewrite `path` dependencies
|
||||
# to registry (e.g., crates.io) dependencies.
|
||||
#
|
||||
# If you are reading this file be aware that the original Cargo.toml
|
||||
# will likely look very different (and much more reasonable).
|
||||
# See Cargo.toml.orig for the original contents.
|
||||
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "truck-meshalgo"
|
||||
version = "0.4.0"
|
||||
build = false
|
||||
autobins = false
|
||||
autoexamples = false
|
||||
autotests = false
|
||||
autobenches = false
|
||||
description = "Mesh algorigthms, include tessellations of the shape."
|
||||
homepage = "https://github.com/ricosjp/truck"
|
||||
readme = "README.md"
|
||||
keywords = [
|
||||
"truck",
|
||||
"graphics",
|
||||
"mesh",
|
||||
]
|
||||
categories = ["graphics"]
|
||||
license = "Apache-2.0"
|
||||
repository = "https://github.com/ricosjp/truck"
|
||||
|
||||
[lib]
|
||||
name = "truck_meshalgo"
|
||||
path = "src/lib.rs"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[dependencies.array-macro]
|
||||
version = "2.1.8"
|
||||
|
||||
[dependencies.derive_more]
|
||||
version = "0.99.18"
|
||||
|
||||
[dependencies.itertools]
|
||||
version = "0.13.0"
|
||||
|
||||
[dependencies.rustc-hash]
|
||||
version = "2.0.0"
|
||||
|
||||
[dependencies.spade]
|
||||
version = "2.12.1"
|
||||
optional = true
|
||||
|
||||
[dependencies.truck-base]
|
||||
version = "0.5.0"
|
||||
|
||||
[dependencies.truck-geometry]
|
||||
version = "0.5.0"
|
||||
|
||||
[dependencies.truck-modeling]
|
||||
version = "0.6.0"
|
||||
|
||||
[dependencies.truck-polymesh]
|
||||
version = "0.6.0"
|
||||
|
||||
[dependencies.truck-topology]
|
||||
version = "0.6.0"
|
||||
|
||||
[dev-dependencies.proptest]
|
||||
version = "1.5.0"
|
||||
|
||||
[dev-dependencies.rand]
|
||||
version = "0.8.5"
|
||||
|
||||
[dev-dependencies.serde_json]
|
||||
version = "1.0.128"
|
||||
|
||||
[dev-dependencies.truck-modeling]
|
||||
version = "0.6.0"
|
||||
|
||||
[features]
|
||||
analyzers = []
|
||||
default = [
|
||||
"analyzers",
|
||||
"filters",
|
||||
"tessellation",
|
||||
]
|
||||
filters = []
|
||||
# Exposes equivalence checks for the SolidCraft kernel tests. Never on in shipped builds.
|
||||
test-hooks = []
|
||||
tessellation = [
|
||||
"spade",
|
||||
"filters",
|
||||
]
|
||||
vtk = ["vtkio"]
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.rayon]
|
||||
version = "1.10.0"
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.vtkio]
|
||||
version = "0.6.3"
|
||||
optional = true
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dev-dependencies.getrandom]
|
||||
version = "0.2.15"
|
||||
features = ["js"]
|
||||
Vendored
-177
@@ -1,177 +0,0 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
Vendored
-68
@@ -1,68 +0,0 @@
|
||||
# truck-meshalgo
|
||||
|
||||
[](https://crates.io/crates/truck-meshalgo) [](https://docs.rs/truck-meshalgo)
|
||||
|
||||
Mesh algorigthms, include tessellations of the shape.
|
||||
|
||||
## Sample Codes
|
||||
|
||||
### filleted-cube
|
||||
|
||||
An experiment to decompose a mesh into elements for future NURBS-shape approximation of the mesh.
|
||||
Contains `doc(hidden)` methods.
|
||||
|
||||
- Input: filleted_cube.obj
|
||||
- Output: planes.obj, lower.obj, upper.obj
|
||||
|
||||
### irregular-sphere
|
||||
|
||||
Add the normal to the sphere containing
|
||||
the irregular normal generated from the NURBS containing the critical point.
|
||||
|
||||
- Input: irregular_sphere.obj
|
||||
- Output: regular_sphere.obj
|
||||
|
||||
### octahedron-subdivision
|
||||
|
||||
Apply loop subdivision to regular octahedron.
|
||||
|
||||
- Input: hardcoded octahedron
|
||||
- Output: octahedron.obj, subdivision-octahedron.obj
|
||||
|
||||
### requadrangulate-buddha
|
||||
|
||||
A benchmark that reads in heavy mesh data, applies triangulation and quadrangulation, and writes it out.
|
||||
|
||||
- Input: happy-buddha.obj
|
||||
- Output: requadrangulated-buddha.obj
|
||||
|
||||
### smoothing-bunny
|
||||
|
||||
Adds smooth normals to the stanford bunny.
|
||||
|
||||
- Input: bunny.obj
|
||||
- Output: smooth_bunny.obj
|
||||
|
||||
### splitting-sample
|
||||
|
||||
An experiment to decompose a mesh into elements for future NURBS-shape approximation of the mesh.
|
||||
|
||||
- Input: sample.obj
|
||||
- Output: planes_parts_#.obj, others_parts_#.obj
|
||||
|
||||
### teapot
|
||||
|
||||
Adds smooth normals to and quadrangulate the famous teapot.
|
||||
|
||||
- Input: teapot.obj
|
||||
- Output: quaded_pot.obj
|
||||
|
||||
### tessellate-shape
|
||||
|
||||
Tessellate a shape and output an obj file.
|
||||
|
||||
```bash
|
||||
usage: tessellate-shape <input json file> <output json file>
|
||||
```
|
||||
|
||||
The default `<output file>` is output.obj.
|
||||
-420
@@ -1,420 +0,0 @@
|
||||
use super::*;
|
||||
use array_macro::array;
|
||||
|
||||
/// Find collisions between two polygon meshes and extract interference lines.
|
||||
///
|
||||
/// # Details
|
||||
///
|
||||
/// The algorithm for mesh extraction is as follows.
|
||||
/// All the triangles are projected toward the axis to form intervals.
|
||||
/// By looking at the overlap of these intervals, we can narrow down the interfering triangles.
|
||||
/// The overlap of the intervals is obtained by sorting the endpoints.
|
||||
///
|
||||
/// # Remarks
|
||||
///
|
||||
/// We see that the surfaces that are in contact are not interfering with each other.
|
||||
/// Therefore, polygon meshes included in one plane are not interfering.
|
||||
pub trait Collision {
|
||||
/// If `self` and `other` collide, then returns only one interference line.
|
||||
/// Otherwise, returns `None`.
|
||||
fn collide_with(&self, other: &PolygonMesh) -> Option<(Point3, Point3)>;
|
||||
/// Extract all interference lines between `self` and `other`.
|
||||
/// # Remarks
|
||||
/// The results is not arranged so that included lines make continuous maximal polyline curve.
|
||||
fn extract_interference(&self, other: &PolygonMesh) -> Vec<(Point3, Point3)>;
|
||||
}
|
||||
|
||||
impl Collision for PolygonMesh {
|
||||
#[inline(always)]
|
||||
fn collide_with(&self, other: &PolygonMesh) -> Option<(Point3, Point3)> {
|
||||
are_colliding(self, other)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn extract_interference(&self, other: &PolygonMesh) -> Vec<(Point3, Point3)> {
|
||||
collision(self, other)
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
enum EndPointType {
|
||||
Front,
|
||||
Back,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
|
||||
struct EndPoint {
|
||||
entity: f64,
|
||||
r#type: EndPointType,
|
||||
segnum: usize,
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl EndPoint {
|
||||
#[inline(always)]
|
||||
fn new(entity: f64, r#type: EndPointType, segnum: usize, index: usize) -> EndPoint {
|
||||
EndPoint {
|
||||
entity,
|
||||
r#type,
|
||||
segnum,
|
||||
index,
|
||||
}
|
||||
}
|
||||
#[inline(always)]
|
||||
fn from_seg(seg: (f64, f64), segnum: usize, index: usize) -> Vec<EndPoint> {
|
||||
vec![
|
||||
EndPoint::new(seg.0, EndPointType::Front, segnum, index),
|
||||
EndPoint::new(seg.1, EndPointType::Back, segnum, index),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn tri_to_seg(tri: [Point3; 3], unit: Vector3) -> (f64, f64) {
|
||||
let a = tri[0].to_vec().dot(unit);
|
||||
let b = tri[1].to_vec().dot(unit);
|
||||
let c = tri[2].to_vec().dot(unit);
|
||||
(f64::min(f64::min(a, b), c), f64::max(f64::max(a, b), c))
|
||||
}
|
||||
|
||||
fn sorted_endpoints<I, J>(iter0: I, iter1: J, unit: Vector3) -> Vec<EndPoint>
|
||||
where
|
||||
I: IntoIterator<Item = [Point3; 3]>,
|
||||
J: IntoIterator<Item = [Point3; 3]>, {
|
||||
let mut res: Vec<EndPoint> = iter0
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.filter(|(_, tri)| !(tri[1] - tri[0]).cross(tri[2] - tri[0]).so_small())
|
||||
.flat_map(|(i, tri)| EndPoint::from_seg(tri_to_seg(tri, unit), 0, i))
|
||||
.chain(
|
||||
iter1
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.filter(|(_, tri)| !(tri[1] - tri[0]).cross(tri[2] - tri[0]).so_small())
|
||||
.flat_map(|(i, tri)| EndPoint::from_seg(tri_to_seg(tri, unit), 1, i)),
|
||||
)
|
||||
.collect();
|
||||
res.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Greater));
|
||||
res
|
||||
}
|
||||
|
||||
fn collide_seg_triangle(seg: [Point3; 2], tri: [Point3; 3]) -> Option<Point3> {
|
||||
let ab = tri[1] - tri[0];
|
||||
let bc = tri[2] - tri[1];
|
||||
let ca = tri[0] - tri[2];
|
||||
let nor = ab.cross(ca);
|
||||
if nor.so_small() {
|
||||
return None;
|
||||
}
|
||||
let ap = seg[0] - tri[0];
|
||||
let aq = seg[1] - tri[0];
|
||||
let dotapnor = ap.dot(nor);
|
||||
let dotaqnor = aq.dot(nor);
|
||||
if dotapnor * dotaqnor > 0.0 {
|
||||
return None;
|
||||
}
|
||||
let h = seg[0] + dotapnor / (dotapnor - dotaqnor) * (seg[1] - seg[0]);
|
||||
if f64::signum(ab.cross(nor).dot(h - tri[0]) + TOLERANCE2)
|
||||
+ f64::signum(bc.cross(nor).dot(h - tri[1]) + TOLERANCE2)
|
||||
+ f64::signum(ca.cross(nor).dot(h - tri[2]) + TOLERANCE2)
|
||||
>= 2.0
|
||||
{
|
||||
Some(h)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn collide_triangles(tri0: [Point3; 3], tri1: [Point3; 3]) -> Option<(Point3, Point3)> {
|
||||
let mut tuple = (None, None);
|
||||
[
|
||||
collide_seg_triangle([tri0[0], tri0[1]], tri1),
|
||||
collide_seg_triangle([tri0[1], tri0[2]], tri1),
|
||||
collide_seg_triangle([tri0[2], tri0[0]], tri1),
|
||||
collide_seg_triangle([tri1[0], tri1[1]], tri0),
|
||||
collide_seg_triangle([tri1[1], tri1[2]], tri0),
|
||||
collide_seg_triangle([tri1[2], tri1[0]], tri0),
|
||||
]
|
||||
.into_iter()
|
||||
.for_each(|pt| match tuple {
|
||||
(None, _) => tuple.0 = pt,
|
||||
(Some(_), None) => tuple.1 = pt,
|
||||
(Some(ref mut p), Some(ref mut q)) => {
|
||||
if let Some(pt) = pt {
|
||||
let dist0 = pt.distance2(*p);
|
||||
let dist1 = pt.distance2(*q);
|
||||
let dist2 = p.distance2(*q);
|
||||
if dist2 < dist0 {
|
||||
*q = pt;
|
||||
} else if dist2 < dist1 {
|
||||
*p = pt;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
match tuple {
|
||||
(Some(a), Some(b)) => Some((a, b)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn make_pos_tri(poly: &PolygonMesh, face: [StandardVertex; 3]) -> [Point3; 3] {
|
||||
array![i => poly.positions()[face[i].pos]; 3]
|
||||
}
|
||||
|
||||
/// Triangles of a mesh with their bounding boxes, each computed once.
|
||||
fn boxed_triangles(poly: &PolygonMesh) -> Vec<([Point3; 3], BoundingBox<Point3>)> {
|
||||
poly.faces()
|
||||
.triangle_iter()
|
||||
.map(|face| {
|
||||
let tri = make_pos_tri(poly, face);
|
||||
(tri, tri.iter().collect())
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Whether two triangles' bounding boxes miss each other (touching boxes do not miss).
|
||||
#[inline(always)]
|
||||
fn disjoint_boxes(bdb0: &BoundingBox<Point3>, bdb1: &BoundingBox<Point3>) -> bool {
|
||||
bdb0.max()[0] < bdb1.min()[0]
|
||||
|| bdb1.max()[0] < bdb0.min()[0]
|
||||
|| bdb0.max()[1] < bdb1.min()[1]
|
||||
|| bdb1.max()[1] < bdb0.min()[1]
|
||||
|| bdb0.max()[2] < bdb1.min()[2]
|
||||
|| bdb1.max()[2] < bdb0.min()[2]
|
||||
}
|
||||
|
||||
/// Pairs of triangles (one from each mesh) whose bounding boxes overlap, in the order the
|
||||
/// sweep along `unit` meets them.
|
||||
///
|
||||
/// The sweep keeps the triangles whose interval along `unit` is open. When a triangle opens,
|
||||
/// it pairs with every open triangle of the other mesh whose box it overlaps. The pairs and
|
||||
/// their order are the same as the earlier version, which cloned the open list for every
|
||||
/// triangle and rejected disjoint boxes afterwards (rebuilding both boxes for each pair).
|
||||
fn candidate_pairs(
|
||||
tris0: &[([Point3; 3], BoundingBox<Point3>)],
|
||||
tris1: &[([Point3; 3], BoundingBox<Point3>)],
|
||||
unit: Vector3,
|
||||
) -> Vec<(usize, usize)> {
|
||||
let endpoints = sorted_endpoints(
|
||||
tris0.iter().map(|(t, _)| *t),
|
||||
tris1.iter().map(|(t, _)| *t),
|
||||
unit,
|
||||
);
|
||||
let boxes = [tris0, tris1];
|
||||
let mut open = [Vec::<usize>::new(), Vec::<usize>::new()];
|
||||
let mut pairs = Vec::new();
|
||||
for EndPoint {
|
||||
r#type,
|
||||
segnum,
|
||||
index,
|
||||
..
|
||||
} in endpoints
|
||||
{
|
||||
match r#type {
|
||||
EndPointType::Front => {
|
||||
let mine = &boxes[segnum][index].1;
|
||||
for &other in &open[1 - segnum] {
|
||||
if !disjoint_boxes(mine, &boxes[1 - segnum][other].1) {
|
||||
pairs.push(if segnum == 0 {
|
||||
(index, other)
|
||||
} else {
|
||||
(other, index)
|
||||
});
|
||||
}
|
||||
}
|
||||
open[segnum].push(index);
|
||||
}
|
||||
EndPointType::Back => {
|
||||
// Every interval opens before it closes unless a NaN broke the sort. Then the
|
||||
// interval stays open: more box tests, never a panic (the web build aborts on any).
|
||||
if let Some(i) = open[segnum].iter().position(|idx| *idx == index) {
|
||||
open[segnum].swap_remove(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pairs
|
||||
}
|
||||
|
||||
fn collision(poly0: &PolygonMesh, poly1: &PolygonMesh) -> Vec<(Point3, Point3)> {
|
||||
let unit = match poly0.positions().is_empty() {
|
||||
true => return Vec::new(),
|
||||
false => hash::take_one_unit(poly0.positions()[0]),
|
||||
};
|
||||
let (tris0, tris1) = (boxed_triangles(poly0), boxed_triangles(poly1));
|
||||
let pairs = candidate_pairs(&tris0, &tris1, unit);
|
||||
let test = |&(i, j): &(usize, usize)| collide_triangles(tris0[i].0, tris1[j].0);
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
use rayon::prelude::*;
|
||||
// Indexed parallel collect: results in the same order as the sequential loop.
|
||||
pairs.par_iter().filter_map(test).collect()
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
pairs.iter().filter_map(test).collect()
|
||||
}
|
||||
}
|
||||
|
||||
fn are_colliding(poly0: &PolygonMesh, poly1: &PolygonMesh) -> Option<(Point3, Point3)> {
|
||||
let unit = match poly0.positions().is_empty() {
|
||||
true => return None,
|
||||
false => hash::take_one_unit(poly0.positions()[0]),
|
||||
};
|
||||
let (tris0, tris1) = (boxed_triangles(poly0), boxed_triangles(poly1));
|
||||
let pairs = candidate_pairs(&tris0, &tris1, unit);
|
||||
let test = |&(i, j): &(usize, usize)| collide_triangles(tris0[i].0, tris1[j].0);
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
use rayon::prelude::*;
|
||||
// The first collision in sweep order, as the sequential search returns.
|
||||
pairs.par_iter().find_map_first(test)
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
pairs.iter().find_map(test)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collide_triangles_test() {
|
||||
let tri0 = [
|
||||
Point3::origin(),
|
||||
Point3::new(1.0, 0.0, 0.0),
|
||||
Point3::new(0.0, 1.0, 0.0),
|
||||
];
|
||||
let tri1 = [
|
||||
Point3::new(0.0, 0.0, -1.0),
|
||||
Point3::new(-1.0, -1.0, 1.0),
|
||||
Point3::new(1.0, 1.0, 1.0),
|
||||
];
|
||||
assert!(collide_triangles(tri0, tri1).is_some());
|
||||
let tri0 = [
|
||||
Point3::new(0.0, 0.0, 0.0),
|
||||
Point3::new(1.0, 0.0, 0.0),
|
||||
Point3::new(0.0, 1.0, 0.0),
|
||||
];
|
||||
let tri1 = [
|
||||
Point3::new(0.0, 0.0, 0.5),
|
||||
Point3::new(1.0, 0.0, 1.0),
|
||||
Point3::new(1.0, 1.0, 1.0),
|
||||
];
|
||||
assert!(collide_triangles(tri0, tri1).is_none());
|
||||
}
|
||||
|
||||
/// The candidate search before boxes were computed once and the open list stopped being
|
||||
/// cloned: kept to check the new search finds the same collisions in the same order.
|
||||
///
|
||||
/// Not part of the API. Built only for tests, or with the `test-hooks` feature, so the
|
||||
/// SolidCraft kernel tests can run these checks in CI (this crate's own tests do not run there).
|
||||
#[cfg(any(test, feature = "test-hooks"))]
|
||||
#[doc(hidden)]
|
||||
pub mod test_hooks {
|
||||
use super::*;
|
||||
|
||||
fn old_pairs(sort_endpoints: Vec<EndPoint>) -> Vec<(usize, usize)> {
|
||||
let mut current = [Vec::<usize>::new(), Vec::<usize>::new()];
|
||||
let mut out = Vec::new();
|
||||
for EndPoint { r#type, segnum, index, .. } in sort_endpoints {
|
||||
match r#type {
|
||||
EndPointType::Front => {
|
||||
current[segnum].push(index);
|
||||
for i in current[1 - segnum].clone() {
|
||||
out.push(if segnum == 0 { (index, i) } else { (i, index) });
|
||||
}
|
||||
}
|
||||
EndPointType::Back => {
|
||||
if let Some(i) = current[segnum].iter().position(|idx| *idx == index) {
|
||||
current[segnum].swap_remove(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn old_disjoint(tri0: [Point3; 3], tri1: [Point3; 3]) -> bool {
|
||||
let b0: BoundingBox<Point3> = tri0.iter().collect();
|
||||
let b1: BoundingBox<Point3> = tri1.iter().collect();
|
||||
disjoint_boxes(&b0, &b1)
|
||||
}
|
||||
|
||||
/// Small seeded generator (no extra dependency).
|
||||
struct Lcg(u64);
|
||||
impl Lcg {
|
||||
fn unit(&mut self) -> f64 {
|
||||
self.0 = self.0.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
|
||||
(self.0 >> 11) as f64 / (1u64 << 53) as f64
|
||||
}
|
||||
/// On a coarse grid half the time, so shared vertices, touching boxes and
|
||||
/// degenerate triangles come up.
|
||||
fn coord(&mut self) -> f64 {
|
||||
if self.unit() < 0.5 { (self.unit() * 8.0).floor() * 0.5 } else { self.unit() * 4.0 }
|
||||
}
|
||||
fn tri(&mut self) -> [Point3; 3] {
|
||||
array![_ => Point3::new(self.coord(), self.coord(), self.coord()); 3]
|
||||
}
|
||||
}
|
||||
|
||||
/// A NaN coordinate breaks the endpoint sort: the search must go on without a panic.
|
||||
pub fn nan_triangles_do_not_panic() -> Result<(), String> {
|
||||
let good = [Point3::new(0.0, 0.0, 0.0), Point3::new(1.0, 0.0, 0.0), Point3::new(0.0, 1.0, 0.0)];
|
||||
let nan = [Point3::new(f64::NAN, 0.0, 0.0), Point3::new(1.0, f64::NAN, 0.0), Point3::new(0.0, 1.0, 0.5)];
|
||||
let crossing = [Point3::new(0.2, 0.2, -1.0), Point3::new(0.2, 0.2, 1.0), Point3::new(0.6, 0.2, 0.0)];
|
||||
for (a, b) in [(vec![good, nan], vec![crossing, nan]), (vec![nan, nan], vec![nan]), (vec![nan], vec![good])] {
|
||||
let ba: Vec<_> = a.iter().map(|t| (*t, t.iter().collect())).collect();
|
||||
let bb: Vec<_> = b.iter().map(|t| (*t, t.iter().collect())).collect();
|
||||
let r = std::panic::catch_unwind(|| candidate_pairs(&ba, &bb, Vector3::new(1.0, 0.0, 0.0)));
|
||||
if r.is_err() {
|
||||
return Err(format!("panicked on {a:?} / {b:?}"));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The new candidate search finds the same collisions, in the same order, as the old one
|
||||
/// on random triangle sets. Returns how many collisions were compared.
|
||||
pub fn same_collisions_in_the_same_order(seeds: std::ops::Range<u64>) -> Result<usize, String> {
|
||||
let mut compared = 0;
|
||||
for seed in seeds {
|
||||
let mut r = Lcg(seed * 7919 + 1);
|
||||
let n0 = 1 + (r.unit() * 60.0) as usize;
|
||||
let n1 = 1 + (r.unit() * 60.0) as usize;
|
||||
let t0: Vec<[Point3; 3]> = (0..n0).map(|_| r.tri()).collect();
|
||||
let t1: Vec<[Point3; 3]> = (0..n1).map(|_| r.tri()).collect();
|
||||
let unit = hash::take_one_unit(t0[0][0]);
|
||||
|
||||
let old: Vec<(Point3, Point3)> = old_pairs(sorted_endpoints(t0.iter().copied(), t1.iter().copied(), unit))
|
||||
.into_iter()
|
||||
.filter(|&(i, j)| !old_disjoint(t0[i], t1[j]))
|
||||
.filter_map(|(i, j)| collide_triangles(t0[i], t1[j]))
|
||||
.collect();
|
||||
|
||||
let b0: Vec<_> = t0.iter().map(|t| (*t, t.iter().collect())).collect();
|
||||
let b1: Vec<_> = t1.iter().map(|t| (*t, t.iter().collect())).collect();
|
||||
let new: Vec<(Point3, Point3)> = candidate_pairs(&b0, &b1, unit)
|
||||
.into_iter()
|
||||
.filter_map(|(i, j)| collide_triangles(t0[i], t1[j]))
|
||||
.collect();
|
||||
|
||||
if old.len() != new.len() {
|
||||
return Err(format!("seed {seed}: {} collisions before, {} now", old.len(), new.len()));
|
||||
}
|
||||
for (k, (a, b)) in old.iter().zip(&new).enumerate() {
|
||||
if !(a.0 == b.0 && a.1 == b.1) {
|
||||
return Err(format!("seed {seed}: collision {k} differs: {a:?} vs {b:?}"));
|
||||
}
|
||||
}
|
||||
compared += old.len();
|
||||
}
|
||||
Ok(compared)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn hooks_pass() {
|
||||
assert_eq!(nan_triangles_do_not_panic(), Ok(()));
|
||||
let n = same_collisions_in_the_same_order(0..200);
|
||||
assert!(matches!(n, Ok(n) if n > 0), "{n:?}");
|
||||
}
|
||||
}
|
||||
-140
@@ -1,140 +0,0 @@
|
||||
use super::*;
|
||||
use derive_more::{Deref, DerefMut};
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct Ray {
|
||||
origin: Point3,
|
||||
direction: Vector3,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deref, DerefMut)]
|
||||
struct Triangle([Point3; 3]);
|
||||
|
||||
impl Triangle {
|
||||
#[inline(always)]
|
||||
fn normal(self) -> Vector3 { (self[1] - self[0]).cross(self[2] - self[0]).normalize() }
|
||||
|
||||
fn is_crossing(self, ray: Ray) -> bool {
|
||||
let a = self[0] - self[1];
|
||||
let b = self[0] - self[2];
|
||||
let mat = Matrix3::from_cols(a, b, ray.direction);
|
||||
if mat.determinant().so_small() {
|
||||
false
|
||||
} else {
|
||||
let inv = mat.invert().unwrap();
|
||||
let uvt = inv * (self[0] - ray.origin);
|
||||
uvt[0] > 0.0 && uvt[1] > 0.0 && uvt[0] + uvt[1] < 1.0 && uvt[2] > 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// whether a point is in a domain rounded by a closed polygon.
|
||||
pub trait IncludingPointInDomain {
|
||||
/// Count signed number of faces crossing ray with origin `point` and direction `ray_direction`.
|
||||
/// Counter increase if the dot product of the ray and the normal of a face is positive,
|
||||
/// and decrease if it is negative.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_meshalgo::prelude::*;
|
||||
/// let simplex = PolygonMesh::new(
|
||||
/// StandardAttributes {
|
||||
/// positions: vec![
|
||||
/// Point3::new(0.0, 0.0, 0.0),
|
||||
/// Point3::new(1.0, 0.0, 0.0),
|
||||
/// Point3::new(0.0, 1.0, 0.0),
|
||||
/// Point3::new(0.0, 0.0, 1.0),
|
||||
/// ],
|
||||
/// ..Default::default()
|
||||
/// },
|
||||
/// Faces::from_iter(vec![
|
||||
/// [0, 2, 1],
|
||||
/// [0, 1, 3],
|
||||
/// [0, 3, 2],
|
||||
/// [1, 2, 3],
|
||||
/// ]),
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(simplex.signed_crossing_faces(Point3::new(0.1, 0.1, 0.1), Vector3::unit_x()), 1);
|
||||
/// assert_eq!(simplex.signed_crossing_faces(Point3::new(-0.1, 0.1, 0.1), Vector3::unit_x()), 0);
|
||||
/// ```
|
||||
fn signed_crossing_faces(&self, point: Point3, ray_direction: Vector3) -> isize;
|
||||
/// whether `point` is in a domain rounded by a closed polygon.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_meshalgo::prelude::*;
|
||||
/// let simplex = PolygonMesh::new(
|
||||
/// StandardAttributes {
|
||||
/// positions: vec![
|
||||
/// Point3::new(0.0, 0.0, 0.0),
|
||||
/// Point3::new(1.0, 0.0, 0.0),
|
||||
/// Point3::new(0.0, 1.0, 0.0),
|
||||
/// Point3::new(0.0, 0.0, 1.0),
|
||||
/// ],
|
||||
/// ..Default::default()
|
||||
/// },
|
||||
/// Faces::from_iter(vec![
|
||||
/// [0, 2, 1],
|
||||
/// [0, 1, 3],
|
||||
/// [0, 3, 2],
|
||||
/// [1, 2, 3],
|
||||
/// ]),
|
||||
/// );
|
||||
///
|
||||
/// assert!(simplex.inside(Point3::new(0.1, 0.1, 0.1)));
|
||||
/// assert!(!simplex.inside(Point3::new(-0.1, 0.1, 0.1)));
|
||||
/// ```
|
||||
fn inside(&self, point: Point3) -> bool;
|
||||
}
|
||||
|
||||
impl IncludingPointInDomain for PolygonMesh {
|
||||
fn signed_crossing_faces(&self, point: Point3, ray_direction: Vector3) -> isize {
|
||||
let ray = Ray {
|
||||
origin: point,
|
||||
direction: ray_direction,
|
||||
};
|
||||
self.face_iter().fold(0, |mut counter, face| {
|
||||
for i in 2..face.len() {
|
||||
let tri = Triangle([
|
||||
self.positions()[face[0].pos],
|
||||
self.positions()[face[i - 1].pos],
|
||||
self.positions()[face[i].pos],
|
||||
]);
|
||||
if tri.is_crossing(ray) {
|
||||
counter += f64::signum(tri.normal().dot(ray.direction)) as isize;
|
||||
}
|
||||
}
|
||||
counter
|
||||
})
|
||||
}
|
||||
fn inside(&self, point: Point3) -> bool {
|
||||
let dir = hash::take_one_unit(point);
|
||||
self.signed_crossing_faces(point, dir) >= 1
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inside100() {
|
||||
let positions = vec![
|
||||
Point3::new(0.0, 0.0, 0.0),
|
||||
Point3::new(1.0, 0.0, 0.0),
|
||||
Point3::new(0.0, 1.0, 0.0),
|
||||
Point3::new(0.0, 0.0, 1.0),
|
||||
];
|
||||
let faces: Faces = vec![[0, 2, 1], [0, 1, 3], [0, 3, 2], [1, 2, 3]]
|
||||
.into_iter()
|
||||
.collect();
|
||||
let simplex = PolygonMesh::new(
|
||||
StandardAttributes {
|
||||
positions,
|
||||
..Default::default()
|
||||
},
|
||||
faces,
|
||||
);
|
||||
|
||||
for _ in 0..100 {
|
||||
assert!(simplex.inside(Point3::new(0.1, 0.1, 0.1)));
|
||||
assert!(!simplex.inside(Point3::new(-0.1, 0.1, 0.1)));
|
||||
}
|
||||
}
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
use crate::*;
|
||||
|
||||
mod collision;
|
||||
mod in_out_judge;
|
||||
mod point_cloud;
|
||||
mod splitting;
|
||||
mod topology;
|
||||
mod volume;
|
||||
|
||||
pub use collision::Collision;
|
||||
#[cfg(feature = "test-hooks")]
|
||||
#[doc(hidden)]
|
||||
pub use collision::test_hooks as collision_test_hooks;
|
||||
pub use in_out_judge::IncludingPointInDomain;
|
||||
pub use point_cloud::WithPointCloud;
|
||||
pub use splitting::ExperimentalSplitters;
|
||||
pub use splitting::Splitting;
|
||||
pub use topology::Topology;
|
||||
pub use truck_topology::shell::ShellCondition;
|
||||
pub use volume::CalcVolume;
|
||||
@@ -1,283 +0,0 @@
|
||||
use super::*;
|
||||
use array_macro::array;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct HashedPointCloud {
|
||||
space: Vec<Vec<Point3>>,
|
||||
size: [usize; 3],
|
||||
range: [[f64; 2]; 3],
|
||||
num_points: usize,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl HashedPointCloud {
|
||||
#[inline(always)]
|
||||
pub fn new(size: [usize; 3], range: [[f64; 2]; 3]) -> Self {
|
||||
HashedPointCloud {
|
||||
space: vec![Vec::new(); size[0] * size[1] * size[2]],
|
||||
size,
|
||||
range,
|
||||
num_points: 0,
|
||||
}
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn from_points<'a, I>(points: I, inf: f64) -> Self
|
||||
where
|
||||
I: IntoIterator + Clone,
|
||||
I::IntoIter: Iterator<Item = &'a Point3>, {
|
||||
let mut bdb = BoundingBox::<Point3>::new();
|
||||
let len = points.clone().into_iter().copied().fold(0, |counter, pt| {
|
||||
bdb.push(pt);
|
||||
counter + 1
|
||||
});
|
||||
let size = 1.0 + f64::powf(len as f64, 1.0 / 3.0);
|
||||
let size = bdb.diagonal().map(|a| {
|
||||
if a / size < inf {
|
||||
1 + (a / inf) as usize
|
||||
} else {
|
||||
size as usize
|
||||
}
|
||||
});
|
||||
let mut res =
|
||||
HashedPointCloud::new(size.into(), array![i => [bdb.min()[i], bdb.max()[i]]; 3]);
|
||||
points.into_iter().for_each(|pt| res.push(*pt));
|
||||
res
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn size(&self) -> [usize; 3] { self.size }
|
||||
#[inline(always)]
|
||||
pub fn push(&mut self, point: Point3) {
|
||||
self.num_points += 1;
|
||||
let idx = point.hash(self);
|
||||
self[idx].push(point);
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn distance(&self, t: impl DistanceWithPointCloud) -> f64 { t.distance(self) }
|
||||
#[inline(always)]
|
||||
pub fn distance2(&self, t: impl DistanceWithPointCloud) -> f64 { t.distance2(self) }
|
||||
#[inline(always)]
|
||||
pub fn is_colliding(&self, t: impl DistanceWithPointCloud, tol: f64) -> bool {
|
||||
t.is_colliding(self, tol)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Index<[usize; 3]> for HashedPointCloud {
|
||||
type Output = Vec<Point3>;
|
||||
#[inline(always)]
|
||||
fn index(&self, idcs: [usize; 3]) -> &Vec<Point3> {
|
||||
&self.space[(idcs[0] * self.size[1] + idcs[1]) * self.size[2] + idcs[2]]
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::IndexMut<[usize; 3]> for HashedPointCloud {
|
||||
#[inline(always)]
|
||||
fn index_mut(&mut self, idcs: [usize; 3]) -> &mut Vec<Point3> {
|
||||
&mut self.space[(idcs[0] * self.size[1] + idcs[1]) * self.size[2] + idcs[2]]
|
||||
}
|
||||
}
|
||||
|
||||
pub trait SpaceHash {
|
||||
fn hash(self, space: &HashedPointCloud) -> [usize; 3];
|
||||
}
|
||||
|
||||
impl SpaceHash for Point3 {
|
||||
#[inline(always)]
|
||||
fn hash(self, space: &HashedPointCloud) -> [usize; 3] {
|
||||
let x = (self[0] - space.range[0][0]) / (space.range[0][1] - space.range[0][0]);
|
||||
let ix = f64::clamp(x * space.size[0] as f64, 0.0, space.size[0] as f64 - 1.0) as usize;
|
||||
let y = (self[1] - space.range[1][0]) / (space.range[1][1] - space.range[1][0]);
|
||||
let iy = f64::clamp(y * space.size[1] as f64, 0.0, space.size[1] as f64 - 1.0) as usize;
|
||||
let z = (self[2] - space.range[2][0]) / (space.range[2][1] - space.range[2][0]);
|
||||
let iz = f64::clamp(z * space.size[2] as f64, 0.0, space.size[2] as f64 - 1.0) as usize;
|
||||
[ix, iy, iz]
|
||||
}
|
||||
}
|
||||
|
||||
impl SpaceHash for usize {
|
||||
#[inline(always)]
|
||||
fn hash(self, space: &HashedPointCloud) -> [usize; 3] {
|
||||
[
|
||||
self / (space.size[1] * space.size[2]),
|
||||
self % (space.size[1] * space.size[2]) / space.size[2],
|
||||
self % space.size[2],
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub trait DistanceWithPointCloud: Sized {
|
||||
fn distance2(&self, space: &HashedPointCloud) -> f64;
|
||||
fn distance(&self, space: &HashedPointCloud) -> f64 { f64::sqrt(self.distance2(space)) }
|
||||
fn is_colliding(&self, space: &HashedPointCloud, tol: f64) -> bool {
|
||||
nonpositive_tolerance!(tol, 0.0);
|
||||
self.distance2(space) < tol * tol
|
||||
}
|
||||
}
|
||||
|
||||
impl DistanceWithPointCloud for Point3 {
|
||||
fn distance2(&self, space: &HashedPointCloud) -> f64 {
|
||||
let idcs = self.hash(space);
|
||||
let closure = |dist2: f64, pt: &Point3| f64::min(dist2, MetricSpace::distance2(*self, *pt));
|
||||
let mut dist2 = space[idcs].iter().fold(f64::INFINITY, closure);
|
||||
if idcs[0] > 0 {
|
||||
dist2 = space[[idcs[0] - 1, idcs[1], idcs[2]]]
|
||||
.iter()
|
||||
.fold(dist2, closure);
|
||||
}
|
||||
if idcs[0] + 1 < space.size[0] {
|
||||
dist2 = space[[idcs[0] + 1, idcs[1], idcs[2]]]
|
||||
.iter()
|
||||
.fold(dist2, closure);
|
||||
}
|
||||
if idcs[1] > 0 {
|
||||
dist2 = space[[idcs[0], idcs[1] - 1, idcs[2]]]
|
||||
.iter()
|
||||
.fold(dist2, closure);
|
||||
}
|
||||
if idcs[1] + 1 < space.size[1] {
|
||||
dist2 = space[[idcs[0], idcs[1] + 1, idcs[2]]]
|
||||
.iter()
|
||||
.fold(dist2, closure);
|
||||
}
|
||||
if idcs[2] > 0 {
|
||||
dist2 = space[[idcs[0], idcs[1], idcs[2] - 1]]
|
||||
.iter()
|
||||
.fold(dist2, closure);
|
||||
}
|
||||
if idcs[2] + 1 < space.size[2] {
|
||||
dist2 = space[[idcs[0], idcs[1], idcs[2] + 1]]
|
||||
.iter()
|
||||
.fold(dist2, closure);
|
||||
}
|
||||
dist2
|
||||
}
|
||||
}
|
||||
|
||||
impl DistanceWithPointCloud for [Point3; 3] {
|
||||
fn distance2(&self, space: &HashedPointCloud) -> f64 {
|
||||
let bdd: BoundingBox<Point3> = self.iter().collect();
|
||||
let mut range: [[usize; 3]; 2] = [bdd.min().hash(space), bdd.max().hash(space)];
|
||||
range[0][0] = usize::max(range[0][0], 1) - 1;
|
||||
range[0][1] = usize::max(range[0][1], 1) - 1;
|
||||
range[0][2] = usize::max(range[0][2], 1) - 1;
|
||||
range[1][0] = usize::min(range[1][0] + 1, space.size[0] - 1);
|
||||
range[1][1] = usize::min(range[1][1] + 1, space.size[1] - 1);
|
||||
range[1][2] = usize::min(range[1][2] + 1, space.size[2] - 1);
|
||||
let mut dist2 = f64::INFINITY;
|
||||
(range[0][0]..=range[1][0]).for_each(|ix| {
|
||||
(range[0][1]..=range[1][1]).for_each(|iy| {
|
||||
(range[0][2]..=range[1][2]).for_each(|iz| {
|
||||
dist2 = space[[ix, iy, iz]].iter().fold(dist2, |dist2, pt| {
|
||||
f64::min(dist2, distance2_point_triangle(*pt, *self))
|
||||
});
|
||||
})
|
||||
})
|
||||
});
|
||||
dist2
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> DistanceWithPointCloud for &'a PolygonMesh {
|
||||
fn distance2(&self, space: &HashedPointCloud) -> f64 {
|
||||
let dist2 = self.faces().triangle_iter().fold(-1.0, |dist2, tri| {
|
||||
let tri = array![i => self.positions()[tri[i].pos]; 3];
|
||||
f64::max(dist2, tri.distance2(space))
|
||||
});
|
||||
match dist2 < 0.0 {
|
||||
true => f64::INFINITY,
|
||||
false => dist2,
|
||||
}
|
||||
}
|
||||
fn is_colliding(&self, space: &HashedPointCloud, tol: f64) -> bool {
|
||||
nonpositive_tolerance!(tol, 0.0);
|
||||
self.faces().triangle_iter().any(|tri| {
|
||||
let tri = array![i => self.positions()[tri[i].pos]; 3];
|
||||
tri.distance2(space) < tol * tol
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn distance_point_triangle_test() {
|
||||
let triangle = [
|
||||
Point3::new(0.0, 0.0, 0.0),
|
||||
Point3::new(1.0, 0.0, 0.0),
|
||||
Point3::new(0.0, 1.0, 0.0),
|
||||
];
|
||||
assert_eq!(
|
||||
12.45 * 12.45,
|
||||
distance2_point_triangle(Point3::new(0.25, 0.25, 12.45), triangle)
|
||||
);
|
||||
assert_eq!(
|
||||
12.45 * 12.45,
|
||||
distance2_point_triangle(Point3::new(0.25, 0.25, -12.45), triangle)
|
||||
);
|
||||
assert_eq!(
|
||||
2.0,
|
||||
distance2_point_triangle(Point3::new(-1.0, 0.5, -1.0), triangle)
|
||||
);
|
||||
assert_eq!(
|
||||
2.0,
|
||||
distance2_point_triangle(Point3::new(0.5, -1.0, 1.0), triangle)
|
||||
);
|
||||
assert_eq!(
|
||||
1.5,
|
||||
distance2_point_triangle(Point3::new(1.0, 1.0, 1.0), triangle)
|
||||
);
|
||||
assert_eq!(
|
||||
3.0,
|
||||
distance2_point_triangle(Point3::new(-1.0, -1.0, -1.0), triangle)
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn exec_space_division_distance() {
|
||||
const SPACE_SIZE: f64 = 100.0;
|
||||
const NUM_POINTS: usize = 1000;
|
||||
const NUM_TRIANGLES: usize = 10;
|
||||
const TRIANGLE_DISPLACEMENT: f64 = 1.0;
|
||||
|
||||
let points = (0..NUM_POINTS)
|
||||
.map(|_| {
|
||||
Point3::new(
|
||||
SPACE_SIZE * rand::random::<f64>() - SPACE_SIZE / 2.0,
|
||||
SPACE_SIZE * rand::random::<f64>() - SPACE_SIZE / 2.0,
|
||||
SPACE_SIZE * rand::random::<f64>() - SPACE_SIZE / 2.0,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let triangles = (0..NUM_TRIANGLES)
|
||||
.map(|_| {
|
||||
let pt = Point3::new(
|
||||
SPACE_SIZE * rand::random::<f64>() - SPACE_SIZE / 2.0,
|
||||
SPACE_SIZE * rand::random::<f64>() - SPACE_SIZE / 2.0,
|
||||
SPACE_SIZE * rand::random::<f64>() - SPACE_SIZE / 2.0,
|
||||
);
|
||||
[
|
||||
pt,
|
||||
pt + Vector3::new(
|
||||
TRIANGLE_DISPLACEMENT * 2.0 * rand::random::<f64>() - TRIANGLE_DISPLACEMENT,
|
||||
TRIANGLE_DISPLACEMENT * 2.0 * rand::random::<f64>() - TRIANGLE_DISPLACEMENT,
|
||||
TRIANGLE_DISPLACEMENT * 2.0 * rand::random::<f64>() - TRIANGLE_DISPLACEMENT,
|
||||
),
|
||||
pt + Vector3::new(
|
||||
TRIANGLE_DISPLACEMENT * 2.0 * rand::random::<f64>() - TRIANGLE_DISPLACEMENT,
|
||||
TRIANGLE_DISPLACEMENT * 2.0 * rand::random::<f64>() - TRIANGLE_DISPLACEMENT,
|
||||
TRIANGLE_DISPLACEMENT * 2.0 * rand::random::<f64>() - TRIANGLE_DISPLACEMENT,
|
||||
),
|
||||
]
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let hashed = HashedPointCloud::from_points(&points, 1.0);
|
||||
let dist_0 = triangles.iter().fold(std::f64::INFINITY, |dist, triangle| {
|
||||
f64::min(dist, hashed.distance2(*triangle))
|
||||
});
|
||||
let dist_1 = points.iter().fold(std::f64::INFINITY, |dist2, pt| {
|
||||
triangles.iter().fold(dist2, |dist2, triangle| {
|
||||
f64::min(dist2, distance2_point_triangle(*pt, *triangle))
|
||||
})
|
||||
});
|
||||
assert_eq!(dist_0, dist_1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn space_division_distance() { (0..10).for_each(|_| exec_space_division_distance()) }
|
||||
@@ -1,84 +0,0 @@
|
||||
use super::*;
|
||||
mod hashed_point_cloud;
|
||||
use hashed_point_cloud::HashedPointCloud;
|
||||
mod sort_end_points;
|
||||
|
||||
/// Investigates positional relation between polygon mesh and point cloud.
|
||||
pub trait WithPointCloud {
|
||||
/// Whether all faces of the polygon mesh `self` has intersection with the neighborhood of `point_cloud`.
|
||||
/// # Arguments
|
||||
/// - `tol`: the radius of the neighborhoods of points in point cloud.
|
||||
/// # Panics
|
||||
/// `tol` must be more than `TOLERANCE`.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_meshalgo::prelude::*;
|
||||
/// let mesh = PolygonMesh::new(
|
||||
/// StandardAttributes {
|
||||
/// positions: vec![
|
||||
/// Point3::new(0.0, 0.0, 0.0),
|
||||
/// Point3::new(1.0, 0.0, 0.0),
|
||||
/// Point3::new(0.0, 1.0, 0.0),
|
||||
/// Point3::new(0.0, 0.0, 2.0),
|
||||
/// Point3::new(1.0, 0.0, 2.0),
|
||||
/// Point3::new(0.0, 1.0, 2.0),
|
||||
/// ],
|
||||
/// ..Default::default()
|
||||
/// },
|
||||
/// Faces::from_iter(vec![[0, 1, 2], [3, 4, 5]]),
|
||||
/// );
|
||||
///
|
||||
/// let mut point_cloud = vec![Point3::new(0.25, 0.25, 0.0)];
|
||||
/// assert!(!mesh.is_clung_to_by(&point_cloud, 0.001));
|
||||
/// point_cloud.push(Point3::new(0.25, 0.25, 2.0));
|
||||
/// assert!(mesh.is_clung_to_by(&point_cloud, 0.001));
|
||||
/// ```
|
||||
fn is_clung_to_by(&self, point_cloud: &[Point3], tol: f64) -> bool;
|
||||
/// Whether the neighborhood of the polygon mesh `self` includes `point_cloud`.
|
||||
/// # Panics
|
||||
/// `tol` must be more than `TOLERANCE`.
|
||||
fn neighborhood_include(&self, point_cloud: &[Point3], tol: f64) -> bool;
|
||||
/// Whether the polygon mesh `self` and `point_cloud` collides.
|
||||
/// # Panics
|
||||
/// `tol` must be more than `TOLERANCE`.
|
||||
fn collide_with_neiborhood_of(&self, point_cloud: &[Point3], tol: f64) -> bool;
|
||||
}
|
||||
|
||||
impl WithPointCloud for PolygonMesh {
|
||||
#[inline(always)]
|
||||
fn is_clung_to_by(&self, point_cloud: &[Point3], tol: f64) -> bool {
|
||||
nonpositive_tolerance!(tol);
|
||||
HashedPointCloud::from_points(point_cloud, tol * 2.0).distance2(self) < tol * tol
|
||||
}
|
||||
#[inline(always)]
|
||||
fn collide_with_neiborhood_of(&self, point_cloud: &[Point3], tol: f64) -> bool {
|
||||
HashedPointCloud::from_points(point_cloud, tol * 2.0).is_colliding(self, tol)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn neighborhood_include(&self, point_cloud: &[Point3], tol: f64) -> bool {
|
||||
sort_end_points::pointcloud_in_polygon_neighborhood(self, point_cloud, tol)
|
||||
}
|
||||
}
|
||||
|
||||
// https://iquilezles.org/www/articles/distfunctions/distfunctions.htm
|
||||
fn distance2_point_triangle(point: Point3, triangle: [Point3; 3]) -> f64 {
|
||||
let ab = triangle[1] - triangle[0];
|
||||
let ap = point - triangle[0];
|
||||
let bc = triangle[2] - triangle[1];
|
||||
let bp = point - triangle[1];
|
||||
let ca = triangle[0] - triangle[2];
|
||||
let cp = point - triangle[2];
|
||||
let nor = ab.cross(ca);
|
||||
|
||||
let coef = f64::signum(ab.cross(nor).dot(ap))
|
||||
+ f64::signum(bc.cross(nor).dot(bp))
|
||||
+ f64::signum(ca.cross(nor).dot(cp));
|
||||
if coef < 2.0 || nor.magnitude().so_small() {
|
||||
let a = (ap - ab * f64::clamp(ab.dot(ap) / ab.dot(ab), 0.0, 1.0)).magnitude2();
|
||||
let b = (bp - bc * f64::clamp(bc.dot(bp) / bc.dot(bc), 0.0, 1.0)).magnitude2();
|
||||
let c = (cp - ca * f64::clamp(ca.dot(cp) / ca.dot(ca), 0.0, 1.0)).magnitude2();
|
||||
f64::min(f64::min(a, b), c)
|
||||
} else {
|
||||
nor.dot(ap) * nor.dot(ap) / nor.magnitude2()
|
||||
}
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
use super::*;
|
||||
use array_macro::array;
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
enum EndPointType {
|
||||
Front,
|
||||
Middle,
|
||||
Back,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
|
||||
struct EndPoint {
|
||||
entity: f64,
|
||||
r#type: EndPointType,
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl EndPoint {
|
||||
#[inline(always)]
|
||||
fn new(entity: f64, r#type: EndPointType, index: usize) -> EndPoint {
|
||||
EndPoint {
|
||||
entity,
|
||||
r#type,
|
||||
index,
|
||||
}
|
||||
}
|
||||
#[inline(always)]
|
||||
fn from_seg(seg: (f64, f64), index: usize) -> Vec<EndPoint> {
|
||||
vec![
|
||||
EndPoint::new(seg.0, EndPointType::Front, index),
|
||||
EndPoint::new(seg.1, EndPointType::Back, index),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn tri_to_seg(tri: [Point3; 3], unit: Vector3, tol: f64) -> (f64, f64) {
|
||||
let a = tri[0].to_vec().dot(unit);
|
||||
let b = tri[1].to_vec().dot(unit);
|
||||
let c = tri[2].to_vec().dot(unit);
|
||||
(
|
||||
f64::min(f64::min(a, b), c) - tol,
|
||||
f64::max(f64::max(a, b), c) + tol,
|
||||
)
|
||||
}
|
||||
|
||||
fn sorted_endpoints<'a, I, J>(iter0: I, iter1: J, unit: Vector3, tol: f64) -> Vec<EndPoint>
|
||||
where
|
||||
I: IntoIterator<Item = [Point3; 3]>,
|
||||
J: IntoIterator<Item = &'a Point3>, {
|
||||
let mut res: Vec<EndPoint> = iter0
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.filter(|(_, tri)| !(tri[1] - tri[0]).cross(tri[2] - tri[0]).so_small())
|
||||
.flat_map(|(i, tri)| EndPoint::from_seg(tri_to_seg(tri, unit, tol), i))
|
||||
.chain(
|
||||
iter1
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i, point)| EndPoint::new(point.to_vec().dot(unit), EndPointType::Middle, i)),
|
||||
)
|
||||
.collect();
|
||||
res.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Greater));
|
||||
res
|
||||
}
|
||||
|
||||
fn sorted_endpoints_by_polymesh_points(
|
||||
polygon: &PolygonMesh,
|
||||
points: &[Point3],
|
||||
tol: f64,
|
||||
) -> Vec<EndPoint> {
|
||||
let unit = if !polygon.positions().is_empty() {
|
||||
hash::take_one_unit(polygon.positions()[0])
|
||||
} else if !points.is_empty() {
|
||||
hash::take_one_unit(points[0])
|
||||
} else {
|
||||
return Vec::new();
|
||||
};
|
||||
sorted_endpoints(
|
||||
polygon
|
||||
.faces()
|
||||
.triangle_iter()
|
||||
.map(|tri| array![i => polygon.positions()[tri[i].pos]; 3]),
|
||||
points.iter(),
|
||||
unit,
|
||||
tol,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn pointcloud_in_polygon_neighborhood(
|
||||
polygon: &PolygonMesh,
|
||||
points: &[Point3],
|
||||
tol: f64,
|
||||
) -> bool {
|
||||
nonpositive_tolerance!(tol, 0.0);
|
||||
let mut current = Vec::new();
|
||||
let triangles = polygon.faces().triangle_iter().collect::<Vec<_>>();
|
||||
sorted_endpoints_by_polymesh_points(polygon, points, tol)
|
||||
.into_iter()
|
||||
.all(move |EndPoint { r#type, index, .. }| match r#type {
|
||||
EndPointType::Front => {
|
||||
current.push(index);
|
||||
true
|
||||
}
|
||||
EndPointType::Back => {
|
||||
let i = current
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, idx)| **idx == index)
|
||||
.unwrap()
|
||||
.0;
|
||||
current.swap_remove(i);
|
||||
true
|
||||
}
|
||||
EndPointType::Middle => current.iter().any(|i| {
|
||||
let tri = triangles[*i];
|
||||
let tri = array![i => polygon.positions()[tri[i].pos]; 3];
|
||||
distance2_point_triangle(points[index], tri) < tol * tol
|
||||
}),
|
||||
})
|
||||
}
|
||||
-343
@@ -1,343 +0,0 @@
|
||||
use super::*;
|
||||
use std::f64::consts::PI;
|
||||
|
||||
/// Splitting the faces into several clusters.
|
||||
pub trait Splitting {
|
||||
/// Creates a sub mesh by the face indices.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_polymesh::*;
|
||||
/// use truck_meshalgo::analyzers::*;
|
||||
///
|
||||
/// // cube
|
||||
/// let mesh = PolygonMesh::new(
|
||||
/// StandardAttributes {
|
||||
/// positions: vec![
|
||||
/// Point3::new(0.0, 0.0, 0.0),
|
||||
/// Point3::new(1.0, 0.0, 0.0),
|
||||
/// Point3::new(1.0, 1.0, 0.0),
|
||||
/// Point3::new(0.0, 1.0, 0.0),
|
||||
/// Point3::new(0.0, 0.0, 1.0),
|
||||
/// Point3::new(1.0, 0.0, 1.0),
|
||||
/// Point3::new(1.0, 1.0, 1.0),
|
||||
/// Point3::new(0.0, 1.0, 1.0),
|
||||
/// ],
|
||||
/// ..Default::default()
|
||||
/// },
|
||||
/// Faces::from_iter(&[
|
||||
/// &[3, 2, 1, 0], &[0, 1, 5, 4], &[1, 2, 6, 5],
|
||||
/// &[2, 3, 7, 6], &[3, 0, 4, 7], &[4, 5, 6, 7],
|
||||
/// ]),
|
||||
/// );
|
||||
///
|
||||
/// let submesh = mesh.create_mesh_by_face_indices(&[0, 3, 5]);
|
||||
///
|
||||
/// // the same attributes vector
|
||||
/// assert_eq!(mesh.positions(), submesh.positions());
|
||||
///
|
||||
/// let faces0 = Faces::from_iter(&[
|
||||
/// &[3, 2, 1, 0], &[2, 3, 7, 6], &[4, 5, 6, 7],
|
||||
/// ]);
|
||||
/// assert_eq!(submesh.faces().len(), faces0.len());
|
||||
/// assert_eq!(submesh.faces()[0], faces0[0]);
|
||||
/// assert_eq!(submesh.faces()[1], faces0[1]);
|
||||
/// assert_eq!(submesh.faces()[2], faces0[2]);
|
||||
/// ```
|
||||
fn create_mesh_by_face_indices(&self, indices: &[usize]) -> PolygonMesh;
|
||||
/// Extracts polygons such that there exists its normal is the same as its face normal.
|
||||
/// # Arguments
|
||||
/// - `tol`: tolerance to be regarded as the same normal as the face normal
|
||||
/// # Returns
|
||||
/// - The first polygon consists the faces included in planes.
|
||||
/// - The second polygon is the extracted remainder.
|
||||
/// # Panics
|
||||
/// `tol` must be more than `TOLERANCE`.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_polymesh::*;
|
||||
/// use truck_meshalgo::analyzers::*;
|
||||
/// let mesh = PolygonMesh::new(
|
||||
/// StandardAttributes {
|
||||
/// positions: vec![
|
||||
/// Point3::new(0.0, 0.5, 0.0),
|
||||
/// Point3::new(0.0, 0.5, 1.0),
|
||||
/// Point3::new(1.0, 0.5, 1.0),
|
||||
/// Point3::new(1.0, 0.5, 0.0),
|
||||
/// Point3::new(0.0, 0.0, 2.0),
|
||||
/// Point3::new(1.0, 0.0, 2.0),
|
||||
/// ],
|
||||
/// normals: vec![
|
||||
/// Vector3::new(0.0, 1.0, 0.0),
|
||||
/// // displaced normals for smooth rendering
|
||||
/// Vector3::new(0.0, 1.0, 1.0).normalize(),
|
||||
/// ],
|
||||
/// ..Default::default()
|
||||
/// },
|
||||
/// Faces::from_iter(&[
|
||||
/// &[(0, None, Some(0)), (1, None, Some(0)), (2, None, Some(0)), (3, None, Some(0))],
|
||||
/// &[(2, None, Some(0)), (1, None, Some(0)), (4, None, Some(1)), (5, None, Some(1))],
|
||||
/// ]),
|
||||
/// );
|
||||
/// let (plane, remained) = mesh.extract_planes(TOLERANCE); // TOLERANCE == 1.0e-7
|
||||
/// assert_eq!(plane.len(), 1); assert_eq!(plane[0], 0);
|
||||
/// assert_eq!(remained.len(), 1); assert_eq!(remained[0], 1);
|
||||
/// ```
|
||||
fn extract_planes(&self, tol: f64) -> (Vec<usize>, Vec<usize>);
|
||||
/// Splits into the components.
|
||||
/// # Details
|
||||
/// Two polygons are considered to be in the same component if they share an edge
|
||||
/// whose vertices has the same positions (and normals if `use_normal == true`).
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_polymesh::*;
|
||||
/// use truck_meshalgo::{analyzers::*, filters::*};
|
||||
///
|
||||
/// // cube consisting tri_faces
|
||||
/// let mut mesh = PolygonMesh::new(
|
||||
/// StandardAttributes {
|
||||
/// positions: vec![
|
||||
/// Point3::new(0.0, 0.0, 0.0),
|
||||
/// Point3::new(1.0, 0.0, 0.0),
|
||||
/// Point3::new(1.0, 1.0, 0.0),
|
||||
/// Point3::new(0.0, 1.0, 0.0),
|
||||
/// Point3::new(0.0, 0.0, 1.0),
|
||||
/// Point3::new(1.0, 0.0, 1.0),
|
||||
/// Point3::new(1.0, 1.0, 1.0),
|
||||
/// Point3::new(0.0, 1.0, 1.0),
|
||||
/// ],
|
||||
/// ..Default::default()
|
||||
/// },
|
||||
/// Faces::from_iter(&[
|
||||
/// &[3, 2, 0], &[1, 0, 2], &[0, 1, 4], &[5, 4, 1],
|
||||
/// &[1, 2, 5], &[6, 5, 2], &[2, 3, 6], &[7, 6, 3],
|
||||
/// &[3, 0, 7], &[4, 7, 0], &[4, 5, 7], &[6, 7, 5],
|
||||
/// ]),
|
||||
/// );
|
||||
///
|
||||
/// // sign up normals
|
||||
/// mesh.add_naive_normals(true).put_together_same_attrs(TOLERANCE);
|
||||
///
|
||||
/// // into components with normals
|
||||
/// let components = mesh.components(true);
|
||||
/// // The number of components is six because the mesh is a cube.
|
||||
/// assert_eq!(components.len(), 6);
|
||||
///
|
||||
/// // into components without normals
|
||||
/// let components = mesh.components(false);
|
||||
/// // The number of components is one because the mesh is a connected cube.
|
||||
/// assert_eq!(components.len(), 1);
|
||||
/// ```
|
||||
fn components(&self, use_normal: bool) -> Vec<Vec<usize>>;
|
||||
}
|
||||
|
||||
impl Splitting for PolygonMesh {
|
||||
fn create_mesh_by_face_indices(&self, indices: &[usize]) -> PolygonMesh {
|
||||
let positions = self.positions().clone();
|
||||
let uv_coords = self.uv_coords().clone();
|
||||
let normals = self.normals().clone();
|
||||
let faces: Faces = indices.iter().map(|i| &self.faces()[*i]).collect();
|
||||
PolygonMesh::new(
|
||||
StandardAttributes {
|
||||
positions,
|
||||
uv_coords,
|
||||
normals,
|
||||
},
|
||||
faces,
|
||||
)
|
||||
}
|
||||
|
||||
fn extract_planes(&self, tol: f64) -> (Vec<usize>, Vec<usize>) {
|
||||
nonpositive_tolerance!(tol, 0.0);
|
||||
self.faces_into_two_clusters(|face: &[Vertex]| {
|
||||
is_in_the_plane(self.positions(), self.normals(), face, tol * tol)
|
||||
})
|
||||
}
|
||||
|
||||
fn components(&self, use_normal: bool) -> Vec<Vec<usize>> {
|
||||
let face_adjacency = self.faces().face_adjacency(use_normal);
|
||||
get_components(&face_adjacency)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub trait ExperimentalSplitters {
|
||||
fn faces_into_two_clusters<F: Fn(&[Vertex]) -> bool>(
|
||||
&self,
|
||||
func: F,
|
||||
) -> (Vec<usize>, Vec<usize>);
|
||||
fn clustering_faces_by_gcurvature(
|
||||
&self,
|
||||
threshold: f64,
|
||||
preferred_upper: bool,
|
||||
) -> (Vec<usize>, Vec<usize>);
|
||||
fn get_gcurve(&self) -> Vec<f64>;
|
||||
}
|
||||
|
||||
/// splitting the global faces
|
||||
impl ExperimentalSplitters for PolygonMesh {
|
||||
/// separate all faces into two clusters, one with `func` returning true and the other with false.
|
||||
/// # Returns
|
||||
/// (the vector of all faces `f` which `func(f) == true`, the one of the other faces)
|
||||
fn faces_into_two_clusters<F: Fn(&[Vertex]) -> bool>(
|
||||
&self,
|
||||
func: F,
|
||||
) -> (Vec<usize>, Vec<usize>) {
|
||||
let mut true_faces = Vec::new();
|
||||
let mut false_faces = Vec::new();
|
||||
for (i, face) in self.face_iter().enumerate() {
|
||||
match func(face) {
|
||||
true => true_faces.push(i),
|
||||
false => false_faces.push(i),
|
||||
}
|
||||
}
|
||||
(true_faces, false_faces)
|
||||
}
|
||||
|
||||
fn clustering_faces_by_gcurvature(
|
||||
&self,
|
||||
threshold: f64,
|
||||
preferred_upper: bool,
|
||||
) -> (Vec<usize>, Vec<usize>) {
|
||||
let gcurve = self.get_gcurve();
|
||||
self.faces_into_two_clusters(|face: &[Vertex]| {
|
||||
is_signed_up_upper(face, &gcurve, preferred_upper, threshold)
|
||||
})
|
||||
}
|
||||
|
||||
fn get_gcurve(&self) -> Vec<f64> {
|
||||
let positions = self.positions();
|
||||
let mut angles = vec![0.0; positions.len()];
|
||||
let mut weights = vec![0.0; positions.len()];
|
||||
for face in self.tri_faces() {
|
||||
angles[face[0].pos] += get_angle(positions, face, 0, 1, 2);
|
||||
angles[face[1].pos] += get_angle(positions, face, 1, 2, 0);
|
||||
angles[face[2].pos] += get_angle(positions, face, 2, 0, 1);
|
||||
add_weights(&mut weights, positions, face);
|
||||
}
|
||||
for face in self.quad_faces() {
|
||||
angles[face[0].pos] += get_angle(positions, face, 0, 1, 3);
|
||||
angles[face[1].pos] += get_angle(positions, face, 1, 2, 0);
|
||||
angles[face[2].pos] += get_angle(positions, face, 2, 3, 1);
|
||||
angles[face[3].pos] += get_angle(positions, face, 3, 0, 1);
|
||||
add_weights(&mut weights, positions, face);
|
||||
}
|
||||
for face in self.other_faces() {
|
||||
let n = face.len() - 1;
|
||||
angles[face[0].pos] += get_angle(positions, face, 0, 1, n);
|
||||
for i in 1..n {
|
||||
angles[face[i].pos] += get_angle(positions, face, i, i + 1, i - 1);
|
||||
}
|
||||
angles[face[n].pos] += get_angle(positions, face, n, 0, n - 1);
|
||||
add_weights(&mut weights, positions, face);
|
||||
}
|
||||
|
||||
angles
|
||||
.into_iter()
|
||||
.zip(weights)
|
||||
.map(|(ang, weight)| (PI * 2.0 - ang) / weight)
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// divide the graph to the connected components.
|
||||
/// # Arguments
|
||||
/// * adjacency - the adjacency matrix
|
||||
/// # Return
|
||||
/// * the list of the indices of faces contained in each components
|
||||
fn get_components(adjacency: &[Vec<usize>]) -> Vec<Vec<usize>> {
|
||||
let mut unchecked = vec![true; adjacency.len()];
|
||||
let mut components = Vec::new();
|
||||
loop {
|
||||
let first = match unchecked.iter().position(|x| *x) {
|
||||
Some(idx) => idx,
|
||||
None => return components,
|
||||
};
|
||||
let mut stack = vec![first];
|
||||
let mut component = vec![first];
|
||||
unchecked[first] = false;
|
||||
while let Some(cursor) = stack.pop() {
|
||||
for i in &adjacency[cursor] {
|
||||
if unchecked[*i] {
|
||||
unchecked[*i] = false;
|
||||
component.push(*i);
|
||||
stack.push(*i);
|
||||
}
|
||||
}
|
||||
}
|
||||
components.push(component);
|
||||
}
|
||||
}
|
||||
|
||||
fn is_in_the_plane(positions: &[Point3], normals: &[Vector3], face: &[Vertex], tol2: f64) -> bool {
|
||||
let n = FaceNormal::new(positions, face, 0).normal;
|
||||
for v in face {
|
||||
if let Some(nor) = v.nor {
|
||||
if n.distance2(normals[nor]) < tol2 {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn is_signed_up_upper(
|
||||
face: &[Vertex],
|
||||
gcurve: &[f64],
|
||||
preferred_upper: bool,
|
||||
threshold: f64,
|
||||
) -> bool {
|
||||
if preferred_upper {
|
||||
face.as_ref().iter().any(|v| gcurve[v.pos] > threshold)
|
||||
} else {
|
||||
face.as_ref().iter().all(|v| gcurve[v.pos] > threshold)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_angle(positions: &[Point3], face: &[Vertex], idx0: usize, idx1: usize, idx2: usize) -> f64 {
|
||||
let vec0 = positions[face[idx1].pos] - positions[face[idx0].pos];
|
||||
let vec1 = positions[face[idx2].pos] - positions[face[idx0].pos];
|
||||
vec0.angle(vec1).0
|
||||
}
|
||||
|
||||
fn add_weights(weights: &mut [f64], positions: &[Point3], face: &[Vertex]) {
|
||||
let area = (2..face.len()).fold(0.0, |sum, i| {
|
||||
let vec0 = positions[face[i - 1].pos] - positions[face[0].pos];
|
||||
let vec1 = positions[face[i].pos] - positions[face[0].pos];
|
||||
sum + (vec0.cross(vec1)).magnitude() / 2.0
|
||||
}) / (face.len() as f64);
|
||||
for v in face {
|
||||
weights[v.pos] += area;
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn into_components_test() {
|
||||
let faces = vec![
|
||||
[(0, None, Some(0)), (1, None, Some(1)), (2, None, Some(2))].as_ref(),
|
||||
&[(0, None, Some(0)), (5, None, Some(5)), (1, None, Some(1))],
|
||||
&[(0, None, Some(7)), (5, None, Some(5)), (6, None, Some(6))],
|
||||
&[(5, None, Some(5)), (6, None, Some(6)), (7, None, Some(7))],
|
||||
&[
|
||||
(1, None, Some(1)),
|
||||
(4, None, Some(4)),
|
||||
(3, None, Some(3)),
|
||||
(2, None, Some(2)),
|
||||
],
|
||||
]
|
||||
.into_iter()
|
||||
.collect();
|
||||
let positions = vec![Point3::origin(); 8];
|
||||
let normals = vec![Vector3::unit_x(); 8];
|
||||
let mesh = PolygonMesh::new(
|
||||
StandardAttributes {
|
||||
positions,
|
||||
normals,
|
||||
..Default::default()
|
||||
},
|
||||
faces,
|
||||
);
|
||||
let comp = mesh.components(true);
|
||||
assert_eq!(comp.len(), 2);
|
||||
assert_eq!(comp[0], vec![0, 1, 4]);
|
||||
assert_eq!(comp[1], vec![2, 3]);
|
||||
}
|
||||
-117
@@ -1,117 +0,0 @@
|
||||
use super::*;
|
||||
use itertools::Itertools;
|
||||
use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
|
||||
use truck_topology::shell::ShellCondition;
|
||||
|
||||
/// Extracts boundaries, and check shell condition: closed or orientation.
|
||||
pub trait Topology {
|
||||
/// Returns a vector of all boundaries as line strip.
|
||||
fn extract_boundaries(&self) -> Vec<Vec<usize>>;
|
||||
/// Determines the shell conditions: non-regular, regular, oriented, or closed.
|
||||
/// The complexity increases in proportion to the number of edges.
|
||||
///
|
||||
/// Examples for each condition can be found on the page of
|
||||
/// [`ShellCondition`](https://docs.rs/truck-topology/0.2.0/truck_topology/shell/enum.ShellCondition.html).
|
||||
fn shell_condition(&self) -> ShellCondition;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Boundaries {
|
||||
checked: HashSet<[usize; 2]>,
|
||||
boundary: HashMap<[usize; 2], bool>,
|
||||
condition: ShellCondition,
|
||||
}
|
||||
|
||||
impl Boundaries {
|
||||
#[inline(always)]
|
||||
fn new() -> Self {
|
||||
Boundaries {
|
||||
checked: Default::default(),
|
||||
boundary: Default::default(),
|
||||
condition: ShellCondition::Oriented,
|
||||
}
|
||||
}
|
||||
#[inline(always)]
|
||||
fn insert(&mut self, edge: [Vertex; 2]) {
|
||||
let ori = edge[0].pos < edge[1].pos;
|
||||
let edge = match ori {
|
||||
true => [edge[0].pos, edge[1].pos],
|
||||
false => [edge[1].pos, edge[0].pos],
|
||||
};
|
||||
self.condition = self.condition
|
||||
& match (self.checked.insert(edge), self.boundary.insert(edge, ori)) {
|
||||
(true, None) => ShellCondition::Oriented,
|
||||
(false, None) => ShellCondition::Irregular,
|
||||
(true, Some(_)) => panic!("unexpected case!"),
|
||||
(false, Some(ori0)) => {
|
||||
self.boundary.remove(&edge);
|
||||
match ori == ori0 {
|
||||
true => ShellCondition::Regular,
|
||||
false => ShellCondition::Oriented,
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn condition(&self) -> ShellCondition {
|
||||
if self.condition == ShellCondition::Oriented && self.boundary.is_empty() {
|
||||
ShellCondition::Closed
|
||||
} else {
|
||||
self.condition
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<[Vertex; 2]> for Boundaries {
|
||||
fn from_iter<I: IntoIterator<Item = [Vertex; 2]>>(iter: I) -> Boundaries {
|
||||
let mut boundaries = Boundaries::new();
|
||||
iter.into_iter().for_each(|edge| boundaries.insert(edge));
|
||||
boundaries
|
||||
}
|
||||
}
|
||||
|
||||
fn face_edge_iter<T: Copy>(face: &[T]) -> impl Iterator<Item = [T; 2]> + '_ {
|
||||
face.iter().circular_tuple_windows().map(|(v, w)| [*v, *w])
|
||||
}
|
||||
|
||||
impl Topology for Faces {
|
||||
fn extract_boundaries(&self) -> Vec<Vec<usize>> {
|
||||
let boundaries: Boundaries = self.face_iter().flat_map(face_edge_iter).collect();
|
||||
let mut vemap: HashMap<usize, usize> = boundaries
|
||||
.boundary
|
||||
.into_iter()
|
||||
.map(|(edge, ori)| match ori {
|
||||
true => (edge[0], edge[1]),
|
||||
false => (edge[1], edge[0]),
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut res = Vec::new();
|
||||
while !vemap.is_empty() {
|
||||
let mut wire = Vec::new();
|
||||
let front = vemap.iter().next().unwrap();
|
||||
let front = (*front.0, *front.1);
|
||||
vemap.remove(&front.0);
|
||||
wire.push(front.0);
|
||||
let mut cursor = front.1;
|
||||
while cursor != front.0 {
|
||||
wire.push(cursor);
|
||||
cursor = vemap.remove(&cursor).unwrap_or(front.0);
|
||||
}
|
||||
res.push(wire);
|
||||
}
|
||||
res
|
||||
}
|
||||
fn shell_condition(&self) -> ShellCondition {
|
||||
self.face_iter()
|
||||
.flat_map(face_edge_iter)
|
||||
.collect::<Boundaries>()
|
||||
.condition()
|
||||
}
|
||||
}
|
||||
|
||||
impl Topology for PolygonMesh {
|
||||
fn extract_boundaries(&self) -> Vec<Vec<usize>> { self.faces().extract_boundaries() }
|
||||
fn shell_condition(&self) -> ShellCondition { self.faces().shell_condition() }
|
||||
}
|
||||
-175
@@ -1,175 +0,0 @@
|
||||
use super::*;
|
||||
use array_macro::array;
|
||||
|
||||
/// Calculate the volume and the center of gravity of mesh.
|
||||
pub trait CalcVolume {
|
||||
/// Returns the volume of the mesh if the mesh is closed.
|
||||
///
|
||||
/// # Details
|
||||
/// More strictly, it returns the integral value of `xdydz`. Because of the linearity of this operation,
|
||||
/// if multiple meshes represent a single closed geometry, the overall volume can be calculated by computing
|
||||
/// this value for each and adding them together.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// // The regular trihedron in the unit sphere.
|
||||
/// use truck_meshalgo::prelude::*;
|
||||
/// let a = 1.0 / f64::sqrt(3.0);
|
||||
/// let positions = vec![
|
||||
/// Point3::new(-a, -a, -a),
|
||||
/// Point3::new(a, a, -a),
|
||||
/// Point3::new(a, -a, a),
|
||||
/// Point3::new(-a, a, a),
|
||||
/// ];
|
||||
/// let attrs = StandardAttributes {
|
||||
/// positions,
|
||||
/// ..Default::default()
|
||||
/// };
|
||||
/// let faces = Faces::from_iter(&[
|
||||
/// [0, 1, 2],
|
||||
/// [1, 3, 2],
|
||||
/// [1, 0, 3],
|
||||
/// [0, 2, 3],
|
||||
/// ]);
|
||||
/// let polygon = PolygonMesh::new(attrs, faces);
|
||||
/// assert_near!(polygon.volume(), 8.0 * f64::sqrt(3.0) / 27.0);
|
||||
/// ```
|
||||
/// ```
|
||||
/// // Cube with a hollow inside, defined by two boundaries.
|
||||
/// use truck_meshalgo::prelude::*;
|
||||
/// let positions0 = vec![
|
||||
/// Point3::new(0.0, 0.0, 0.0),
|
||||
/// Point3::new(3.0, 0.0, 0.0),
|
||||
/// Point3::new(0.0, 3.0, 0.0),
|
||||
/// Point3::new(0.0, 0.0, 3.0),
|
||||
/// Point3::new(0.0, 3.0, 3.0),
|
||||
/// Point3::new(3.0, 0.0, 3.0),
|
||||
/// Point3::new(3.0, 3.0, 0.0),
|
||||
/// Point3::new(3.0, 3.0, 3.0),
|
||||
/// ];
|
||||
/// let attrs0 = StandardAttributes {
|
||||
/// positions: positions0,
|
||||
/// ..Default::default()
|
||||
/// };
|
||||
/// let faces0 = Faces::from_iter(&[
|
||||
/// [0, 2, 6, 1],
|
||||
/// [0, 1, 5, 3],
|
||||
/// [1, 6, 7, 5],
|
||||
/// [6, 2, 4, 7],
|
||||
/// [2, 0, 3, 4],
|
||||
/// [3, 5, 7, 4],
|
||||
/// ]);
|
||||
/// let polygon0 = PolygonMesh::new(attrs0, faces0);
|
||||
/// let positions1 = vec![
|
||||
/// Point3::new(1.0, 1.0, 1.0),
|
||||
/// Point3::new(2.0, 1.0, 1.0),
|
||||
/// Point3::new(1.0, 2.0, 1.0),
|
||||
/// Point3::new(1.0, 1.0, 2.0),
|
||||
/// Point3::new(1.0, 2.0, 2.0),
|
||||
/// Point3::new(2.0, 1.0, 2.0),
|
||||
/// Point3::new(2.0, 2.0, 1.0),
|
||||
/// Point3::new(2.0, 2.0, 2.0),
|
||||
/// ];
|
||||
/// let attrs1 = StandardAttributes {
|
||||
/// positions: positions1,
|
||||
/// ..Default::default()
|
||||
/// };
|
||||
/// let faces1 = Faces::from_iter(&[
|
||||
/// [1, 6, 2, 0],
|
||||
/// [3, 5, 1, 0],
|
||||
/// [5, 7, 6, 1],
|
||||
/// [7, 4, 2, 6],
|
||||
/// [4, 3, 0, 2],
|
||||
/// [4, 7, 5, 3],
|
||||
/// ]);
|
||||
/// let polygon1 = PolygonMesh::new(attrs1, faces1);
|
||||
/// // We consider the solid defined by two boundaries(polygon0 and polygon1).
|
||||
/// // The volume will be calculated by `position0.volume() + position1.volume()`.
|
||||
/// assert_near!(polygon0.volume() + polygon1.volume(), 26.0);
|
||||
/// ```
|
||||
fn volume(&self) -> f64;
|
||||
/// Returns the center of gravity of the mesh by homogeneous coordinate if the mesh is closed.
|
||||
///
|
||||
/// # Details
|
||||
/// More strictly, it returns the integral value of `(1/2 x^2 dydz, 1/2 y^2dzdx, 1/2 z^2 dxdy, xdydz)`.
|
||||
/// Because of the linearity of this operation, if multiple meshes represent a single closed geometry,
|
||||
/// the overall volume can be calculated by computing this value for each and adding them together.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// // The triangular prism
|
||||
/// use truck_meshalgo::prelude::*;
|
||||
/// let positions = vec![
|
||||
/// Point3::new(0.0, 0.0, 0.0),
|
||||
/// Point3::new(2.0, 5.0, 0.0),
|
||||
/// Point3::new(-5.0, 1.0, 0.0),
|
||||
/// Point3::new(0.0, 0.0, 2.0),
|
||||
/// Point3::new(2.0, 5.0, 2.0),
|
||||
/// Point3::new(-5.0, 1.0, 2.0),
|
||||
/// ];
|
||||
/// let attrs = StandardAttributes {
|
||||
/// positions,
|
||||
/// ..Default::default()
|
||||
/// };
|
||||
/// let faces = Faces::from_iter(&[
|
||||
/// [2, 1, 0].as_slice(),
|
||||
/// &[0, 1, 4, 3],
|
||||
/// &[1, 2, 5, 4],
|
||||
/// &[2, 0, 3, 5],
|
||||
/// &[3, 4, 5],
|
||||
/// ]);
|
||||
/// let polygon = PolygonMesh::new(attrs, faces);
|
||||
/// let homog = polygon.center_of_gravity();
|
||||
/// assert_near!(homog.to_point(), Point3::new(-1.0, 2.0, 1.0));
|
||||
/// ```
|
||||
fn center_of_gravity(&self) -> Vector4;
|
||||
}
|
||||
|
||||
impl CalcVolume for PolygonMesh {
|
||||
fn volume(&self) -> f64 {
|
||||
point_triangles(self).fold(0.0, |sum, [p, q, r]| {
|
||||
sum + (p.x + q.x + r.x) * ((q.y - p.y) * (r.z - p.z) - (r.y - p.y) * (q.z - p.z))
|
||||
}) / 6.0
|
||||
}
|
||||
fn center_of_gravity(&self) -> Vector4 {
|
||||
let arr = point_triangles(self).fold([0.0; 4], |sum, [p, q, r]| {
|
||||
let det = array![i => {
|
||||
let (j, k) = ((i + 1) % 3, (i + 2) % 3);
|
||||
(q[j] - p[j]) * (r[k] - p[k]) - (r[j] - p[j]) * (q[k] - p[k])
|
||||
}; 3];
|
||||
let vals = array![i => {
|
||||
let s = p[i] + q[i] + r[i];
|
||||
s * s - p[i] * q[i] - q[i] * r[i] - r[i] * p[i]
|
||||
}; 3];
|
||||
let res = array![i => sum[i] + vals[i] * det[i]; 3];
|
||||
let res3 = sum[3] + (p.x + q.x + r.x) * det[0];
|
||||
[res[0], res[1], res[2], res3]
|
||||
});
|
||||
Vector4::new(arr[0] / 24.0, arr[1] / 24.0, arr[2] / 24.0, arr[3] / 6.0)
|
||||
}
|
||||
}
|
||||
|
||||
fn point_triangles(poly: &PolygonMesh) -> impl Iterator<Item = [Point3; 3]> + '_ {
|
||||
poly.faces()
|
||||
.triangle_iter()
|
||||
.map(|faces| array![i => poly.positions()[faces[i].pos]; 3])
|
||||
}
|
||||
|
||||
impl CalcVolume for truck_topology::Solid<Point3, PolylineCurve<Point3>, PolygonMesh> {
|
||||
fn volume(&self) -> f64 {
|
||||
self.face_iter()
|
||||
.map(|face| match face.orientation() {
|
||||
true => face.surface().volume(),
|
||||
false => -face.surface().volume(),
|
||||
})
|
||||
.sum::<f64>()
|
||||
}
|
||||
fn center_of_gravity(&self) -> Vector4 {
|
||||
self.face_iter()
|
||||
.map(|face| match face.orientation() {
|
||||
true => face.surface().center_of_gravity(),
|
||||
false => -face.surface().center_of_gravity(),
|
||||
})
|
||||
.sum::<Vector4>()
|
||||
}
|
||||
}
|
||||
-77
@@ -1,77 +0,0 @@
|
||||
use super::*;
|
||||
use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
|
||||
|
||||
pub trait Adjacency {
|
||||
#[allow(dead_code)]
|
||||
fn vertex_adjacency(&self, num_of_vertices: usize) -> Vec<Vec<usize>>;
|
||||
/// create the adjacency list of the faces
|
||||
fn face_adjacency(&self, use_normal: bool) -> Vec<Vec<usize>>;
|
||||
}
|
||||
|
||||
impl Adjacency for Faces {
|
||||
fn vertex_adjacency(&self, num_of_vertices: usize) -> Vec<Vec<usize>> {
|
||||
let mut already = HashSet::default();
|
||||
let mut res = vec![Vec::new(); num_of_vertices];
|
||||
for face in self.face_iter() {
|
||||
face.windows(2)
|
||||
.chain(std::iter::once([face[face.len() - 1], face[0]].as_ref()))
|
||||
.map(|edge| [edge[0].pos, edge[1].pos])
|
||||
.for_each(|edge| {
|
||||
let first = match edge[0] < edge[1] {
|
||||
true => already.insert((edge[0], edge[1])),
|
||||
false => already.insert((edge[1], edge[0])),
|
||||
};
|
||||
if first {
|
||||
res[edge[0]].push(edge[1]);
|
||||
res[edge[1]].push(edge[0]);
|
||||
}
|
||||
})
|
||||
}
|
||||
res
|
||||
}
|
||||
fn face_adjacency(&self, use_normal: bool) -> Vec<Vec<usize>> {
|
||||
let len = self.len();
|
||||
let mut face_adjacency = vec![Vec::<usize>::new(); len];
|
||||
let mut edge_face_map = HashMap::default();
|
||||
for (i, face) in self.face_iter().enumerate() {
|
||||
face.windows(2)
|
||||
.chain(std::iter::once([face[face.len() - 1], face[0]].as_ref()))
|
||||
.for_each(|v| {
|
||||
signup_adjacency(
|
||||
i,
|
||||
v[0],
|
||||
v[1],
|
||||
&mut face_adjacency,
|
||||
&mut edge_face_map,
|
||||
use_normal,
|
||||
)
|
||||
})
|
||||
}
|
||||
face_adjacency
|
||||
}
|
||||
}
|
||||
|
||||
fn signup_adjacency(
|
||||
i: usize,
|
||||
v0: Vertex,
|
||||
v1: Vertex,
|
||||
face_adjacency: &mut [Vec<usize>],
|
||||
edge_face_map: &mut HashMap<[(usize, Option<usize>); 2], usize>,
|
||||
use_normal: bool,
|
||||
) {
|
||||
let edge = match (v0.pos < v1.pos, use_normal) {
|
||||
(true, true) => [(v0.pos, v0.nor), (v1.pos, v1.nor)],
|
||||
(false, true) => [(v1.pos, v1.nor), (v0.pos, v0.nor)],
|
||||
(true, false) => [(v0.pos, None), (v1.pos, None)],
|
||||
(false, false) => [(v1.pos, None), (v0.pos, None)],
|
||||
};
|
||||
match edge_face_map.get(&edge) {
|
||||
Some(j) => {
|
||||
face_adjacency[i].push(*j);
|
||||
face_adjacency[*j].push(i);
|
||||
}
|
||||
None => {
|
||||
edge_face_map.insert(edge, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
use super::*;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct FaceNormal {
|
||||
pub face_id: usize,
|
||||
pub normal: Vector3,
|
||||
}
|
||||
|
||||
impl FaceNormal {
|
||||
pub fn new(positions: &[Point3], face: &[Vertex], face_id: usize) -> FaceNormal {
|
||||
let center = face
|
||||
.iter()
|
||||
.fold(Vector3::zero(), |sum, v| sum + positions[v.pos].to_vec())
|
||||
/ face.len() as f64;
|
||||
let normal = face
|
||||
.windows(2)
|
||||
.chain(std::iter::once([face[face.len() - 1], face[0]].as_ref()))
|
||||
.fold(Vector3::zero(), |sum, v| {
|
||||
let vec0 = positions[v[0].pos].to_vec() - center;
|
||||
let vec1 = positions[v[1].pos].to_vec() - center;
|
||||
sum + vec0.cross(vec1)
|
||||
})
|
||||
.normalize();
|
||||
FaceNormal { face_id, normal }
|
||||
}
|
||||
}
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
use crate::*;
|
||||
|
||||
mod adjacency;
|
||||
mod face_normal;
|
||||
pub(super) use adjacency::Adjacency;
|
||||
pub(super) use face_normal::FaceNormal;
|
||||
-105
@@ -1,105 +0,0 @@
|
||||
use super::*;
|
||||
|
||||
/// STL face generate from `PolygonMesh`
|
||||
#[derive(Debug)]
|
||||
pub struct TriangleIter<'a> {
|
||||
tri_faces: std::slice::Iter<'a, [Vertex; 3]>,
|
||||
quad_faces: std::slice::Iter<'a, [Vertex; 4]>,
|
||||
other_faces: std::slice::Iter<'a, [Vertex; 3]>,
|
||||
current_face: Option<&'a [Vertex]>,
|
||||
current_vertex: usize,
|
||||
len: usize,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for TriangleIter<'a> {
|
||||
type Item = [Vertex; 3];
|
||||
fn next(&mut self) -> Option<[Vertex; 3]> {
|
||||
if self.current_face.is_none() {
|
||||
self.current_face = if let Some(face) = self.tri_faces.next() {
|
||||
Some(face)
|
||||
} else if let Some(face) = self.quad_faces.next() {
|
||||
Some(face)
|
||||
} else if let Some(face) = self.other_faces.next() {
|
||||
Some(face)
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
let face = self.current_face.unwrap();
|
||||
let res = [
|
||||
face[0],
|
||||
face[self.current_vertex + 1],
|
||||
face[self.current_vertex + 2],
|
||||
];
|
||||
if self.current_vertex + 3 == face.len() {
|
||||
self.current_face = None;
|
||||
self.current_vertex = 0;
|
||||
} else {
|
||||
self.current_vertex += 1;
|
||||
}
|
||||
self.len -= 1;
|
||||
Some(res)
|
||||
}
|
||||
#[inline(always)]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) { (self.len, Some(self.len)) }
|
||||
}
|
||||
|
||||
impl<'a> ExactSizeIterator for TriangleIter<'a> {}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Triangulate<'a> {
|
||||
entity: &'a PolygonMesh,
|
||||
other_faces: Vec<[Vertex; 3]>,
|
||||
}
|
||||
|
||||
impl<'a> Triangulate<'a> {
|
||||
#[inline(always)]
|
||||
pub fn new(entity: &'a PolygonMesh) -> Self {
|
||||
fn divide_mesh<T: Copy>(face: &Vec<T>) -> impl Iterator<Item = [T; 3]> + '_ {
|
||||
face.windows(2).skip(1).map(move |a| [face[0], a[0], a[1]])
|
||||
}
|
||||
Triangulate {
|
||||
other_faces: entity.other_faces().iter().flat_map(divide_mesh).collect(),
|
||||
entity,
|
||||
}
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn get(&self, idx: usize) -> [Vertex; 3] {
|
||||
let tri_and_quad = self.entity.tri_faces().len() + 2 * self.entity.quad_faces().len();
|
||||
if idx < self.entity.tri_faces().len() {
|
||||
self.entity.tri_faces()[idx]
|
||||
} else if idx < tri_and_quad {
|
||||
let idx = idx - self.entity.tri_faces().len();
|
||||
let face = self.entity.quad_faces()[idx / 2];
|
||||
if idx % 2 == 0 {
|
||||
[face[0], face[1], face[2]]
|
||||
} else {
|
||||
[face[0], face[2], face[3]]
|
||||
}
|
||||
} else {
|
||||
self.other_faces[idx - tri_and_quad]
|
||||
}
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn entity(&self) -> &PolygonMesh { self.entity }
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a Triangulate<'a> {
|
||||
type Item = [Vertex; 3];
|
||||
type IntoIter = TriangleIter<'a>;
|
||||
#[inline(always)]
|
||||
fn into_iter(self) -> TriangleIter<'a> {
|
||||
let len = self
|
||||
.entity
|
||||
.face_iter()
|
||||
.fold(0, |len, face| len + face.len());
|
||||
TriangleIter {
|
||||
tri_faces: self.entity.tri_faces().iter(),
|
||||
quad_faces: self.entity.quad_faces().iter(),
|
||||
other_faces: self.other_faces.iter(),
|
||||
current_face: None,
|
||||
current_vertex: 0,
|
||||
len,
|
||||
}
|
||||
}
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
use crate::*;
|
||||
use polygon_mesh::PolygonMeshEditor;
|
||||
|
||||
mod normal_filters;
|
||||
mod optimizing;
|
||||
mod structuring;
|
||||
mod subdivision;
|
||||
|
||||
pub use normal_filters::NormalFilters;
|
||||
pub use optimizing::OptimizingFilter;
|
||||
pub use structuring::StructuringFilter;
|
||||
pub use subdivision::Subdivision;
|
||||
-396
@@ -1,396 +0,0 @@
|
||||
use super::*;
|
||||
use rustc_hash::FxHashMap as HashMap;
|
||||
|
||||
/// Filters for adding normals
|
||||
pub trait NormalFilters {
|
||||
/// Normalize all normals and assign `None` to the `nor` index of the vertices
|
||||
/// that has irregular normals.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_polymesh::*;
|
||||
/// use truck_meshalgo::filters::*;
|
||||
///
|
||||
/// // Morbid data only for testing
|
||||
/// let mut mesh = PolygonMesh::new(
|
||||
/// StandardAttributes {
|
||||
/// positions: vec![Point3::new(0.0, 0.0, 0.0)],
|
||||
/// normals: vec![
|
||||
/// Vector3::new(100.0, 20.0, 56.0),
|
||||
/// Vector3::new(1.0e-12, 3.536e10, std::f64::NAN),
|
||||
/// Vector3::new(0.0, 1.0, 0.0),
|
||||
/// ],
|
||||
/// ..Default::default()
|
||||
/// },
|
||||
/// Faces::from_iter(&[&[
|
||||
/// (0, None, Some(0)),
|
||||
/// (0, None, Some(1)),
|
||||
/// (0, None, Some(2))
|
||||
/// ]]),
|
||||
/// );
|
||||
///
|
||||
/// mesh.normalize_normals();
|
||||
/// assert!(mesh.normals()[0].magnitude().near(&1.0));
|
||||
/// assert_eq!(mesh.faces()[0][1].nor, None);
|
||||
/// ```
|
||||
fn normalize_normals(&mut self) -> &mut Self;
|
||||
/// Adds face normals to each vertices.
|
||||
/// # Arguments
|
||||
/// - If `overwrite == true`, clear all normals and update all normals in vertices.
|
||||
/// - If `overwrite == false`, add normals only for `nor` is `None`.
|
||||
/// # Examples
|
||||
/// Compare with the examples of [`add_smooth_normals`](./trait.NormalFilters.html#tymethod.add_smooth_normals).
|
||||
/// ```
|
||||
/// use truck_meshalgo::prelude::*;
|
||||
/// let mut mesh = PolygonMesh::new(
|
||||
/// StandardAttributes {
|
||||
/// positions: vec![
|
||||
/// Point3::new(-5.0, 0.0, 0.0),
|
||||
/// Point3::new(0.0, 2.0, -2.0),
|
||||
/// Point3::new(0.0, 2.0, 0.0),
|
||||
/// Point3::new(0.0, 2.0, 2.0),
|
||||
/// Point3::new(5.0, 0.0, 0.0),
|
||||
/// ],
|
||||
/// ..Default::default()
|
||||
/// },
|
||||
/// Faces::from_iter(&[
|
||||
/// &[0, 2, 1],
|
||||
/// &[0, 3, 2],
|
||||
/// &[1, 2, 4],
|
||||
/// &[2, 3, 4],
|
||||
/// ]),
|
||||
/// );
|
||||
///
|
||||
/// mesh.add_naive_normals(true);
|
||||
/// let v0: StandardVertex = mesh.faces()[0][1];
|
||||
/// let v1: StandardVertex = mesh.faces()[3][0];
|
||||
///
|
||||
/// // those vertices are at position with the index 2.
|
||||
/// assert_eq!(v0.pos, 2); assert_eq!(v1.pos, 2);
|
||||
///
|
||||
/// // Each normal is each face normal.
|
||||
/// assert!(mesh.normals()[v0.nor.unwrap()].near(&Vector3::new(-2.0, 5.0, 0.0).normalize()));
|
||||
/// assert!(mesh.normals()[v1.nor.unwrap()].near(&Vector3::new(2.0, 5.0, 0.0).normalize()));
|
||||
/// ```
|
||||
fn add_naive_normals(&mut self, overwrite: bool) -> &mut Self;
|
||||
/// add the smooth normal vectors to the mesh.
|
||||
/// # Details
|
||||
/// For each vertex, apply the following algorithm:
|
||||
/// 1. prepare vectors that enumerate the normals of the faces containing the target vertices in order.
|
||||
/// 1. cluster each normal `n` in turn in the following manner.
|
||||
/// * If there is an existing cluster `A` in which the angle between the weighted
|
||||
/// average of `A` and `n` is less than or equal to `tol_ang`, add `n` to `A`.
|
||||
/// * If cluster `A` as described above does not exist, create a new cluster that contains only `n`.
|
||||
/// # Arguments
|
||||
/// - If `overwrite == true`, clear all normals and update all normals in vertices.
|
||||
/// - If `overwrite == false`, add normals only for `nor` is `None`.
|
||||
/// # Examples
|
||||
/// Compare with the examples of [`add_smooth_normals`](./trait.NormalFilters.html#tymethod.add_smooth_normals).
|
||||
/// ```
|
||||
/// use truck_meshalgo::prelude::*;
|
||||
/// let mut mesh = PolygonMesh::new(
|
||||
/// StandardAttributes {
|
||||
/// positions: vec![
|
||||
/// Point3::new(-5.0, 0.0, 0.0),
|
||||
/// Point3::new(0.0, 2.0, -2.0),
|
||||
/// Point3::new(0.0, 2.0, 0.0),
|
||||
/// Point3::new(0.0, 2.0, 2.0),
|
||||
/// Point3::new(5.0, 0.0, 0.0),
|
||||
/// ],
|
||||
/// ..Default::default()
|
||||
/// },
|
||||
/// Faces::from_iter(&[
|
||||
/// &[0, 2, 1], &[0, 3, 2], &[1, 2, 4], &[2, 3, 4],
|
||||
/// ]),
|
||||
/// );
|
||||
///
|
||||
/// mesh.add_smooth_normals(0.8, true);
|
||||
/// let v0: StandardVertex = mesh.faces()[0][1];
|
||||
/// let v1: StandardVertex = mesh.faces()[3][0];
|
||||
///
|
||||
/// // those vertices are at position with the index 2.
|
||||
/// assert_eq!(v0.pos, 2); assert_eq!(v1.pos, 2);
|
||||
///
|
||||
/// // Normals are avaraged!
|
||||
/// assert!(mesh.normals()[v0.nor.unwrap()].near(&Vector3::new(0.0, 1.0, 0.0)));
|
||||
/// assert!(mesh.normals()[v1.nor.unwrap()].near(&Vector3::new(0.0, 1.0, 0.0)));
|
||||
/// assert_eq!(v0.nor, v1.nor);
|
||||
///
|
||||
/// // If the tolerance is enough little, the faces are recognized as edges.
|
||||
/// mesh.add_smooth_normals(0.6, true); // Normals are overwritten!
|
||||
/// let v0: StandardVertex = mesh.faces()[0][1];
|
||||
/// let v1: StandardVertex = mesh.faces()[3][0];
|
||||
/// assert!(mesh.normals()[v0.nor.unwrap()].near(&Vector3::new(-2.0, 5.0, 0.0).normalize()));
|
||||
/// assert!(mesh.normals()[v1.nor.unwrap()].near(&Vector3::new(2.0, 5.0, 0.0).normalize()));
|
||||
/// ```
|
||||
fn add_smooth_normals(&mut self, tol_ang: f64, overwrite: bool) -> &mut Self;
|
||||
/// Makes the orientation of faces compatible to the normal vectors.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_polymesh::*;
|
||||
/// use truck_meshalgo::filters::*;
|
||||
/// let mut polymesh = PolygonMesh::new(
|
||||
/// StandardAttributes {
|
||||
/// positions: vec![
|
||||
/// Point3::new(0.0, 0.0, 0.0),
|
||||
/// Point3::new(1.0, 0.0, 0.0),
|
||||
/// Point3::new(0.0, 1.0, 0.0),
|
||||
/// ],
|
||||
/// normals: vec![Vector3::new(0.0, 0.0, -1.0)],
|
||||
/// ..Default::default()
|
||||
/// },
|
||||
/// Faces::from_tri_and_quad_faces(
|
||||
/// vec![[
|
||||
/// (0, None, Some(0)).into(),
|
||||
/// (1, None, Some(0)).into(),
|
||||
/// (2, None, Some(0)).into(),
|
||||
/// ]],
|
||||
/// Vec::new(),
|
||||
/// ),
|
||||
/// );
|
||||
/// polymesh.make_face_compatible_to_normal();
|
||||
/// assert_eq!(polymesh.faces()[0][0].pos, 2);
|
||||
/// assert_eq!(polymesh.faces()[0][1].pos, 1);
|
||||
/// assert_eq!(polymesh.faces()[0][2].pos, 0);
|
||||
/// ```
|
||||
fn make_face_compatible_to_normal(&mut self) -> &mut Self;
|
||||
/// Makes the orientation of faces compatible to the normal vectors.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_polymesh::*;
|
||||
/// use truck_meshalgo::filters::*;
|
||||
/// let mut polymesh = PolygonMesh::new(
|
||||
/// StandardAttributes {
|
||||
/// positions: vec![
|
||||
/// Point3::new(0.0, 0.0, 0.0),
|
||||
/// Point3::new(1.0, 0.0, 0.0),
|
||||
/// Point3::new(0.0, 1.0, 0.0),
|
||||
/// ],
|
||||
/// normals: vec![Vector3::new(0.0, 0.0, -1.0)],
|
||||
/// ..Default::default()
|
||||
/// },
|
||||
/// Faces::from_tri_and_quad_faces(
|
||||
/// vec![[
|
||||
/// (0, None, Some(0)).into(),
|
||||
/// (1, None, Some(0)).into(),
|
||||
/// (2, None, Some(0)).into(),
|
||||
/// ]],
|
||||
/// Vec::new(),
|
||||
/// ),
|
||||
/// );
|
||||
/// polymesh.make_normal_compatible_to_face();
|
||||
/// let nor = polymesh.faces()[0][0].nor.unwrap();
|
||||
/// assert_eq!(polymesh.normals()[nor], Vector3::new(0.0, 0.0, 1.0));
|
||||
/// ```
|
||||
fn make_normal_compatible_to_face(&mut self) -> &mut Self;
|
||||
}
|
||||
|
||||
impl NormalFilters for PolygonMesh {
|
||||
fn normalize_normals(&mut self) -> &mut Self {
|
||||
let mut mesh = self.debug_editor();
|
||||
let PolygonMeshEditor {
|
||||
attributes: StandardAttributes { normals, .. },
|
||||
faces,
|
||||
..
|
||||
} = &mut mesh;
|
||||
normals
|
||||
.iter_mut()
|
||||
.for_each(move |normal| *normal = normal.normalize());
|
||||
faces.face_iter_mut().flatten().for_each(|v| {
|
||||
if let Some(idx) = v.nor {
|
||||
if !normals[idx].magnitude2().near(&1.0) {
|
||||
v.nor = None;
|
||||
}
|
||||
}
|
||||
});
|
||||
drop(mesh);
|
||||
self
|
||||
}
|
||||
fn make_face_compatible_to_normal(&mut self) -> &mut Self {
|
||||
let mut mesh = self.debug_editor();
|
||||
let PolygonMeshEditor {
|
||||
attributes: StandardAttributes {
|
||||
positions, normals, ..
|
||||
},
|
||||
faces,
|
||||
..
|
||||
} = &mut mesh;
|
||||
for face in faces.face_iter_mut() {
|
||||
let normal = face.iter().fold(Vector3::zero(), |normal, v| {
|
||||
normal + v.nor.map(|i| normals[i]).unwrap_or_else(Vector3::zero)
|
||||
});
|
||||
let face_normal = FaceNormal::new(positions, face, 0).normal;
|
||||
if normal.dot(face_normal) < 0.0 {
|
||||
face.reverse();
|
||||
}
|
||||
}
|
||||
drop(mesh);
|
||||
self
|
||||
}
|
||||
fn make_normal_compatible_to_face(&mut self) -> &mut Self {
|
||||
let mut mesh = self.debug_editor();
|
||||
let PolygonMeshEditor {
|
||||
attributes: StandardAttributes {
|
||||
positions, normals, ..
|
||||
},
|
||||
faces,
|
||||
..
|
||||
} = &mut mesh;
|
||||
for face in faces.face_iter_mut() {
|
||||
let face_normal = FaceNormal::new(positions, face, 0).normal;
|
||||
face.iter_mut().for_each(|v| {
|
||||
v.nor = v.nor.map(|idx| {
|
||||
if normals[idx].dot(face_normal) < 0.0 {
|
||||
normals.push(-normals[idx]);
|
||||
normals.len() - 1
|
||||
} else {
|
||||
idx
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
drop(mesh);
|
||||
self
|
||||
}
|
||||
fn add_naive_normals(&mut self, overwrite: bool) -> &mut Self {
|
||||
let mut mesh = self.debug_editor();
|
||||
let PolygonMeshEditor {
|
||||
attributes: StandardAttributes {
|
||||
positions, normals, ..
|
||||
},
|
||||
faces,
|
||||
..
|
||||
} = &mut mesh;
|
||||
if overwrite {
|
||||
normals.clear()
|
||||
}
|
||||
faces.face_iter_mut().for_each(move |face| {
|
||||
let normal = FaceNormal::new(positions, face, 0).normal;
|
||||
let mut added = false;
|
||||
face.iter_mut().for_each(|v| {
|
||||
if v.nor.is_none() || overwrite {
|
||||
if !added {
|
||||
normals.push(normal);
|
||||
added = true;
|
||||
}
|
||||
v.nor = Some(normals.len() - 1);
|
||||
}
|
||||
});
|
||||
});
|
||||
drop(mesh);
|
||||
self
|
||||
}
|
||||
fn add_smooth_normals(&mut self, tol_ang: f64, overwrite: bool) -> &mut Self {
|
||||
let vnmap = self.clustering_noraml_faces(tol_ang.cos());
|
||||
self.reflect_normal_clusters(vnmap, overwrite);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
trait SubNormalFilter {
|
||||
fn clustering_noraml_faces(&self, inf: f64) -> HashMap<usize, Vec<Vec<FaceNormal>>>;
|
||||
fn reflect_normal_clusters(
|
||||
&mut self,
|
||||
vnmap: HashMap<usize, Vec<Vec<FaceNormal>>>,
|
||||
overwrite: bool,
|
||||
);
|
||||
}
|
||||
|
||||
impl SubNormalFilter for PolygonMesh {
|
||||
fn clustering_noraml_faces(&self, inf: f64) -> HashMap<usize, Vec<Vec<FaceNormal>>> {
|
||||
let positions = self.positions();
|
||||
let mut vnmap = HashMap::default();
|
||||
self.face_iter()
|
||||
.enumerate()
|
||||
.for_each(|(i, face)| add_face_normal(positions, i, face, &mut vnmap, inf));
|
||||
vnmap
|
||||
}
|
||||
|
||||
fn reflect_normal_clusters(
|
||||
&mut self,
|
||||
vnmap: HashMap<usize, Vec<Vec<FaceNormal>>>,
|
||||
overwrite: bool,
|
||||
) {
|
||||
let mut mesh = self.debug_editor();
|
||||
let PolygonMeshEditor {
|
||||
attributes: StandardAttributes { normals, .. },
|
||||
faces,
|
||||
..
|
||||
} = &mut mesh;
|
||||
if overwrite {
|
||||
normals.clear();
|
||||
}
|
||||
for (pos_id, vecs) in vnmap.into_iter() {
|
||||
for vec in vecs {
|
||||
let normal = vec
|
||||
.iter()
|
||||
.fold(Vector3::zero(), |sum, x| sum + x.normal)
|
||||
.normalize();
|
||||
for FaceNormal { face_id, .. } in vec {
|
||||
signup_vertex_normal(pos_id, face_id, normals, normal, faces, overwrite);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_face_normal(
|
||||
positions: &[Point3],
|
||||
face_id: usize,
|
||||
face: &[Vertex],
|
||||
vnmap: &mut HashMap<usize, Vec<Vec<FaceNormal>>>,
|
||||
inf: f64,
|
||||
) {
|
||||
let face_normal = FaceNormal::new(positions, face, face_id);
|
||||
face.iter().for_each(|v| {
|
||||
add_to_vnmap(v.pos, face_normal, vnmap, inf);
|
||||
})
|
||||
}
|
||||
|
||||
fn add_to_vnmap(
|
||||
pos_id: usize,
|
||||
face_normal: FaceNormal,
|
||||
vnmap: &mut HashMap<usize, Vec<Vec<FaceNormal>>>,
|
||||
inf: f64,
|
||||
) {
|
||||
match vnmap.get_mut(&pos_id) {
|
||||
Some(vecs) => {
|
||||
for vec in vecs.iter_mut() {
|
||||
let normal = vec
|
||||
.iter()
|
||||
.fold(Vector3::zero(), |sum, x| sum + x.normal)
|
||||
.normalize();
|
||||
if face_normal.normal.dot(normal) > inf {
|
||||
vec.push(face_normal);
|
||||
return;
|
||||
}
|
||||
}
|
||||
vecs.push(vec![face_normal]);
|
||||
}
|
||||
None => {
|
||||
let vecs = vec![vec![face_normal]];
|
||||
vnmap.insert(pos_id, vecs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn signup_vertex_normal(
|
||||
pos_id: usize,
|
||||
face_id: usize,
|
||||
normals: &mut Vec<Vector3>,
|
||||
normal: Vector3,
|
||||
faces: &mut Faces,
|
||||
overwrite: bool,
|
||||
) {
|
||||
let face = faces[face_id].as_mut();
|
||||
let j = (0..face.len()).find(|j| face[*j].pos == pos_id).unwrap();
|
||||
if face[j].nor.is_none() || overwrite {
|
||||
if let Some(n) = normals.last() {
|
||||
if n != &normal {
|
||||
normals.push(normal);
|
||||
}
|
||||
} else {
|
||||
normals.push(normal);
|
||||
}
|
||||
face[j].nor = Some(normals.len() - 1);
|
||||
}
|
||||
}
|
||||
-441
@@ -1,441 +0,0 @@
|
||||
use super::*;
|
||||
use rustc_hash::FxHashMap as HashMap;
|
||||
use std::iter::Iterator;
|
||||
use std::ops::{Div, Mul};
|
||||
|
||||
/// Filters for optimizing data
|
||||
pub trait OptimizingFilter {
|
||||
/// remove all unused position, texture coordinates, and normal vectors.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_polymesh::*;
|
||||
/// use truck_meshalgo::filters::*;
|
||||
/// let mut mesh = PolygonMesh::new(
|
||||
/// StandardAttributes {
|
||||
/// positions: vec![
|
||||
/// Point3::new(0.0, 0.0, 0.0),
|
||||
/// Point3::new(1.0, 0.0, 0.0),
|
||||
/// Point3::new(0.0, 1.0, 0.0),
|
||||
/// Point3::new(0.0, 0.0, 1.0),
|
||||
/// ],
|
||||
/// ..Default::default()
|
||||
/// },
|
||||
/// // 0 is not used!
|
||||
/// Faces::from_iter(&[&[1, 2, 3]]),
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(mesh.positions().len(), 4);
|
||||
/// mesh.remove_unused_attrs();
|
||||
/// assert_eq!(mesh.positions().len(), 3);
|
||||
/// ```
|
||||
fn remove_unused_attrs(&mut self) -> &mut Self;
|
||||
/// Removes degenerate polygons.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_polymesh::*;
|
||||
/// use truck_meshalgo::filters::*;
|
||||
/// let mut mesh = PolygonMesh::new(
|
||||
/// StandardAttributes {
|
||||
/// positions: vec![
|
||||
/// Point3::new(0.0, 0.0, 0.0),
|
||||
/// Point3::new(1.0, 0.0, 0.0),
|
||||
/// Point3::new(0.0, 1.0, 0.0),
|
||||
/// Point3::new(0.0, 0.0, 1.0),
|
||||
/// ],
|
||||
/// ..Default::default()
|
||||
/// },
|
||||
/// Faces::from_iter(&[
|
||||
/// &[0, 1, 2],
|
||||
/// &[2, 1, 2], // degenerate face!
|
||||
/// &[2, 1, 3],
|
||||
/// ]),
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(mesh.faces().len(), 3);
|
||||
/// mesh.remove_degenerate_faces();
|
||||
/// assert_eq!(mesh.faces().len(), 2);
|
||||
/// ```
|
||||
fn remove_degenerate_faces(&mut self) -> &mut Self;
|
||||
/// Gives the same indices to the same positions, texture coordinate, and normal vectors, respectively.
|
||||
/// # Remarks
|
||||
/// No longer needed attributes are NOT autoremoved.
|
||||
/// One can remove such attributes by running [`remove_unused_attrs`] manually.
|
||||
///
|
||||
/// [`remove_unused_attrs`]: ./trait.WasteEliminatingFilter.html#tymethod.remove_unused_attrs
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_meshalgo::prelude::*;
|
||||
/// let mut mesh = PolygonMesh::new(
|
||||
/// StandardAttributes {
|
||||
/// positions: vec![
|
||||
/// Point3::new(0.0, 0.0, 0.0),
|
||||
/// Point3::new(1.0, 0.0, 0.0),
|
||||
/// Point3::new(0.0, 1.0, 0.0),
|
||||
/// Point3::new(1.0, 1.0, 0.0),
|
||||
/// Point3::new(0.0, 1.0, 0.0),
|
||||
/// Point3::new(1.0, 0.0, 0.0),
|
||||
/// ],
|
||||
/// ..Default::default()
|
||||
/// },
|
||||
/// Faces::from_iter(&[
|
||||
/// &[0, 1, 2],
|
||||
/// &[3, 4, 5],
|
||||
/// ]),
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(mesh.faces()[1][1], StandardVertex { pos: 4, uv: None, nor: None });
|
||||
/// mesh.put_together_same_attrs(TOLERANCE);
|
||||
/// assert_eq!(mesh.faces()[1][1], StandardVertex { pos: 2, uv: None, nor: None });
|
||||
///
|
||||
/// // Remarks: No longer needed attributes are NOT autoremoved!
|
||||
/// assert_eq!(mesh.positions().len(), 6);
|
||||
/// mesh.remove_unused_attrs();
|
||||
/// assert_eq!(mesh.positions().len(), 4);
|
||||
/// ```
|
||||
fn put_together_same_attrs(&mut self, tol: f64) -> &mut Self;
|
||||
}
|
||||
|
||||
fn all_pos_mut(faces: &mut Faces) -> impl Iterator<Item = &mut usize> {
|
||||
faces.face_iter_mut().flatten().map(move |v| &mut v.pos)
|
||||
}
|
||||
|
||||
fn all_uv_mut(faces: &mut Faces) -> impl Iterator<Item = &mut usize> {
|
||||
faces
|
||||
.face_iter_mut()
|
||||
.flatten()
|
||||
.filter_map(move |v| v.uv.as_mut())
|
||||
}
|
||||
|
||||
fn all_nor_mut(faces: &mut Faces) -> impl Iterator<Item = &mut usize> {
|
||||
faces
|
||||
.face_iter_mut()
|
||||
.flatten()
|
||||
.filter_map(move |v| v.nor.as_mut())
|
||||
}
|
||||
|
||||
impl OptimizingFilter for PolygonMesh {
|
||||
fn remove_unused_attrs(&mut self) -> &mut Self {
|
||||
let mut mesh = self.debug_editor();
|
||||
let PolygonMeshEditor {
|
||||
attributes:
|
||||
StandardAttributes {
|
||||
positions,
|
||||
uv_coords,
|
||||
normals,
|
||||
},
|
||||
faces,
|
||||
..
|
||||
} = &mut mesh;
|
||||
let pos_iter = all_pos_mut(faces);
|
||||
let idcs = sub_remove_unused_attrs(pos_iter, positions.len());
|
||||
*positions = idcs.iter().map(|i| positions[*i]).collect();
|
||||
let uv_iter = all_uv_mut(faces);
|
||||
let idcs = sub_remove_unused_attrs(uv_iter, uv_coords.len());
|
||||
*uv_coords = idcs.iter().map(|i| uv_coords[*i]).collect();
|
||||
let nor_iter = all_nor_mut(faces);
|
||||
let idcs = sub_remove_unused_attrs(nor_iter, normals.len());
|
||||
*normals = idcs.iter().map(|i| normals[*i]).collect();
|
||||
drop(mesh);
|
||||
self
|
||||
}
|
||||
|
||||
fn remove_degenerate_faces(&mut self) -> &mut Self {
|
||||
let mesh = self.debug_editor();
|
||||
let mut faces = Faces::default();
|
||||
for tri in mesh.faces.tri_faces() {
|
||||
if !degenerate_triangle(*tri) {
|
||||
faces.push(tri);
|
||||
}
|
||||
}
|
||||
for quad in mesh.faces.quad_faces() {
|
||||
match degenerate_quadrangle(*quad) {
|
||||
QuadrangleType::TotallyDegenerate => {}
|
||||
QuadrangleType::Triangle(tri) => faces.push(tri),
|
||||
QuadrangleType::NonDegenerate => faces.push(quad),
|
||||
}
|
||||
}
|
||||
for face in mesh.faces.other_faces() {
|
||||
faces.extend(split_into_nondegenerate(face.clone()));
|
||||
}
|
||||
*mesh.faces = faces;
|
||||
drop(mesh);
|
||||
self
|
||||
}
|
||||
|
||||
fn put_together_same_attrs(&mut self, tol: f64) -> &mut Self {
|
||||
let mut mesh = self.debug_editor();
|
||||
let PolygonMeshEditor {
|
||||
attributes:
|
||||
StandardAttributes {
|
||||
positions,
|
||||
uv_coords,
|
||||
normals,
|
||||
},
|
||||
faces,
|
||||
..
|
||||
} = &mut mesh;
|
||||
let bnd_box: BoundingBox<_> = positions.iter().collect();
|
||||
let center = bnd_box.center();
|
||||
let diag = bnd_box.diagonal().map(|a| f64::max(a.abs(), 1.0));
|
||||
let normalized_positions = positions
|
||||
.iter()
|
||||
.map(move |position| 2.0 * (position - center).zip(diag, |a, b| a / b))
|
||||
.collect::<Vec<_>>();
|
||||
let pos_map = sub_put_together_same_attrs(&normalized_positions, tol);
|
||||
all_pos_mut(faces).for_each(|idx| *idx = pos_map[*idx]);
|
||||
let uv_map = sub_put_together_same_attrs(uv_coords, tol);
|
||||
all_uv_mut(faces).for_each(|idx| *idx = uv_map[*idx]);
|
||||
let nor_map = sub_put_together_same_attrs(normals, tol);
|
||||
all_nor_mut(faces).for_each(|idx| *idx = nor_map[*idx]);
|
||||
drop(mesh);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
fn sub_remove_unused_attrs<'a, I: Iterator<Item = &'a mut usize>>(
|
||||
iter: I,
|
||||
old_len: usize,
|
||||
) -> Vec<usize> {
|
||||
let mut new2old = Vec::new();
|
||||
let mut old2new = vec![None; old_len];
|
||||
for idx in iter {
|
||||
*idx = match old2new[*idx] {
|
||||
Some(k) => k,
|
||||
None => {
|
||||
let k = new2old.len();
|
||||
new2old.push(*idx);
|
||||
old2new[*idx] = Some(k);
|
||||
k
|
||||
}
|
||||
};
|
||||
}
|
||||
new2old
|
||||
}
|
||||
|
||||
trait SameAttr: Copy + CastIntVector + MetricSpace<Metric = f64> {}
|
||||
impl<T> SameAttr for T where T: Copy + CastIntVector + MetricSpace<Metric = f64> {}
|
||||
|
||||
fn sub_put_together_same_attrs<T: SameAttr>(attrs: &[T], tol: f64) -> Vec<usize> {
|
||||
let map = create_blocks(attrs, tol);
|
||||
let adj = create_adjacency(map, attrs, tol);
|
||||
let components = create_components(adj, attrs);
|
||||
centralize(components, attrs)
|
||||
}
|
||||
|
||||
type BlockMap<'a, T> = HashMap<<T as CastIntVector>::IntVector, Vec<usize>>;
|
||||
fn create_blocks<T: SameAttr>(attrs: &[T], tol: f64) -> BlockMap<'_, T> {
|
||||
let mut map = HashMap::default();
|
||||
attrs.iter().enumerate().for_each(|(i, attr)| {
|
||||
attr.round(tol).neighborhood().into_iter().for_each(|idx| {
|
||||
map.entry(idx).or_insert_with(Vec::new).push(i);
|
||||
});
|
||||
});
|
||||
map
|
||||
}
|
||||
|
||||
fn create_adjacency<T: SameAttr>(map: BlockMap<'_, T>, attrs: &[T], tol: f64) -> Vec<Vec<usize>> {
|
||||
let mut adj = vec![Vec::new(); attrs.len()];
|
||||
map.into_iter().for_each(|(_, vec)| {
|
||||
if vec.len() > 1 {
|
||||
vec.iter().copied().enumerate().for_each(|(k, i)| {
|
||||
vec[(k + 1)..].iter().copied().for_each(|j| {
|
||||
if attrs[i].distance2(attrs[j]) < tol * tol {
|
||||
adj[i].push(j);
|
||||
adj[j].push(i);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
adj
|
||||
}
|
||||
|
||||
fn create_components<T: SameAttr>(
|
||||
mut adj: Vec<Vec<usize>>,
|
||||
attrs: &[T],
|
||||
) -> impl Iterator<Item = Vec<usize>> {
|
||||
let mut already = vec![false; attrs.len()];
|
||||
(0..adj.len()).filter_map(move |i| {
|
||||
if already[i] {
|
||||
return None;
|
||||
}
|
||||
let mut stack = vec![i];
|
||||
let mut component = Vec::new();
|
||||
while let Some(i) = stack.pop() {
|
||||
if !already[i] {
|
||||
component.push(i);
|
||||
already[i] = true;
|
||||
stack.append(&mut adj[i]);
|
||||
}
|
||||
}
|
||||
Some(component)
|
||||
})
|
||||
}
|
||||
|
||||
fn centralize<T: SameAttr>(
|
||||
components: impl Iterator<Item = Vec<usize>>,
|
||||
attrs: &[T],
|
||||
) -> Vec<usize> {
|
||||
let mut res = vec![0; attrs.len()];
|
||||
components.for_each(|component| {
|
||||
let center = component
|
||||
.iter()
|
||||
.fold(T::zero(), |sum, j| sum.add_element_wise(attrs[*j]))
|
||||
/ component.len() as f64;
|
||||
let (mut mindist, mut mindx) = (f64::INFINITY, 0);
|
||||
component.iter().copied().for_each(|i| {
|
||||
let dist = attrs[i].distance2(center);
|
||||
if dist < mindist {
|
||||
mindist = dist;
|
||||
mindx = i;
|
||||
}
|
||||
});
|
||||
component.into_iter().for_each(|i| {
|
||||
res[i] = mindx;
|
||||
});
|
||||
});
|
||||
res
|
||||
}
|
||||
|
||||
fn degenerate_triangle(tri: [Vertex; 3]) -> bool {
|
||||
tri[0].pos == tri[1].pos || tri[1].pos == tri[2].pos || tri[2].pos == tri[0].pos
|
||||
}
|
||||
|
||||
enum QuadrangleType {
|
||||
NonDegenerate,
|
||||
Triangle([Vertex; 3]),
|
||||
TotallyDegenerate,
|
||||
}
|
||||
|
||||
fn degenerate_quadrangle(quad: [Vertex; 4]) -> QuadrangleType {
|
||||
if (quad[0].pos == quad[2].pos || quad[1].pos == quad[3].pos)
|
||||
|| (quad[0].pos == quad[1].pos && quad[2].pos == quad[3].pos)
|
||||
|| quad[1].pos == quad[2].pos && quad[3].pos == quad[0].pos
|
||||
{
|
||||
QuadrangleType::TotallyDegenerate
|
||||
} else if quad[0].pos == quad[1].pos || quad[1].pos == quad[2].pos {
|
||||
QuadrangleType::Triangle([quad[0], quad[2], quad[3]])
|
||||
} else if quad[2].pos == quad[3].pos || quad[3].pos == quad[0].pos {
|
||||
QuadrangleType::Triangle([quad[0], quad[1], quad[2]])
|
||||
} else {
|
||||
QuadrangleType::NonDegenerate
|
||||
}
|
||||
}
|
||||
|
||||
fn split_into_nondegenerate(poly: Vec<Vertex>) -> Vec<Vec<Vertex>> {
|
||||
for i in 0..poly.len() {
|
||||
for j in (i + 1)..poly.len() {
|
||||
if poly[i].pos == poly[j].pos {
|
||||
let polygon0: Vec<_> = (0..(j - i)).map(|k| poly[k + i]).collect();
|
||||
let polygon1: Vec<_> = ((j - i)..poly.len())
|
||||
.map(|k| poly[(k + i) % poly.len()])
|
||||
.collect();
|
||||
let mut result = split_into_nondegenerate(polygon0);
|
||||
result.extend(split_into_nondegenerate(polygon1));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
vec![poly]
|
||||
}
|
||||
|
||||
trait CastIntVector:
|
||||
Sized + ElementWise<Self> + Mul<f64, Output = Self> + Div<f64, Output = Self> {
|
||||
type IntVector: IntVector;
|
||||
fn cast_int(self) -> Self::IntVector;
|
||||
fn round(self, tol: f64) -> Self::IntVector;
|
||||
fn zero() -> Self;
|
||||
}
|
||||
|
||||
macro_rules! impl_cast_int {
|
||||
($typename: ident, $n: expr) => {
|
||||
impl CastIntVector for $typename {
|
||||
type IntVector = [i64; $n];
|
||||
fn cast_int(self) -> [i64; $n] {
|
||||
self.cast::<i64>()
|
||||
.unwrap_or_else(|| panic!("failed to cast: {self:?}"))
|
||||
.into()
|
||||
}
|
||||
fn round(self, tol: f64) -> Self::IntVector { (self / tol).cast_int() }
|
||||
fn zero() -> Self { Self::from([0.0; $n]) }
|
||||
}
|
||||
};
|
||||
}
|
||||
impl_cast_int!(Vector2, 2);
|
||||
impl_cast_int!(Vector3, 3);
|
||||
impl_cast_int!(Point3, 3);
|
||||
|
||||
trait IntVector: Copy + std::hash::Hash + Eq {
|
||||
type Neighborhood: IntoIterator<Item = Self>;
|
||||
fn neighborhood(self) -> Self::Neighborhood;
|
||||
}
|
||||
|
||||
impl IntVector for [i64; 2] {
|
||||
type Neighborhood = [[i64; 2]; 9];
|
||||
fn neighborhood(self) -> Self::Neighborhood {
|
||||
let [a, b] = self;
|
||||
[
|
||||
[a - 1, b - 1],
|
||||
[a, b - 1],
|
||||
[a + 1, b - 1],
|
||||
[a - 1, b],
|
||||
[a, b],
|
||||
[a + 1, b],
|
||||
[a - 1, b + 1],
|
||||
[a, b + 1],
|
||||
[a + 1, b + 1],
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl IntVector for [i64; 3] {
|
||||
type Neighborhood = [[i64; 3]; 27];
|
||||
fn neighborhood(self) -> Self::Neighborhood {
|
||||
let [a, b, c] = self;
|
||||
[
|
||||
[a - 1, b - 1, c - 1],
|
||||
[a, b - 1, c - 1],
|
||||
[a + 1, b - 1, c - 1],
|
||||
[a - 1, b, c - 1],
|
||||
[a, b, c - 1],
|
||||
[a + 1, b, c - 1],
|
||||
[a - 1, b + 1, c - 1],
|
||||
[a, b + 1, c - 1],
|
||||
[a + 1, b + 1, c - 1],
|
||||
[a - 1, b - 1, c],
|
||||
[a, b - 1, c],
|
||||
[a + 1, b - 1, c],
|
||||
[a - 1, b, c],
|
||||
[a, b, c],
|
||||
[a + 1, b, c],
|
||||
[a - 1, b + 1, c],
|
||||
[a, b + 1, c],
|
||||
[a + 1, b + 1, c],
|
||||
[a - 1, b - 1, c + 1],
|
||||
[a, b - 1, c + 1],
|
||||
[a + 1, b - 1, c + 1],
|
||||
[a - 1, b, c + 1],
|
||||
[a, b, c + 1],
|
||||
[a + 1, b, c + 1],
|
||||
[a - 1, b + 1, c + 1],
|
||||
[a, b + 1, c + 1],
|
||||
[a + 1, b + 1, c + 1],
|
||||
]
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn into_vertices(iter: &[usize]) -> Vec<Vertex> { iter.iter().map(|i| i.into()).collect() }
|
||||
|
||||
#[test]
|
||||
fn degenerate_polygon_test() {
|
||||
let poly = into_vertices(&[0, 1, 2, 0, 3, 4, 5, 6, 3, 7, 8, 9]);
|
||||
let polys = split_into_nondegenerate(poly);
|
||||
assert_eq!(polys[0], into_vertices(&[0, 1, 2]));
|
||||
assert_eq!(polys[1], into_vertices(&[3, 4, 5, 6]));
|
||||
assert_eq!(polys[2], into_vertices(&[3, 7, 8, 9, 0]));
|
||||
}
|
||||
}
|
||||
-242
@@ -1,242 +0,0 @@
|
||||
use super::*;
|
||||
|
||||
/// triangulation, quadrangulation, give a structure
|
||||
pub trait StructuringFilter {
|
||||
/// triangulate all n-gons
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_polymesh::*;
|
||||
/// use truck_meshalgo::filters::*;
|
||||
///
|
||||
/// // cube consisting quad faces
|
||||
/// let mut mesh = PolygonMesh::new(
|
||||
/// StandardAttributes {
|
||||
/// positions: vec![
|
||||
/// Point3::new(0.0, 0.0, 0.0),
|
||||
/// Point3::new(1.0, 0.0, 0.0),
|
||||
/// Point3::new(1.0, 1.0, 0.0),
|
||||
/// Point3::new(0.0, 1.0, 0.0),
|
||||
/// Point3::new(0.0, 0.0, 1.0),
|
||||
/// Point3::new(1.0, 0.0, 1.0),
|
||||
/// Point3::new(1.0, 1.0, 1.0),
|
||||
/// Point3::new(0.0, 1.0, 1.0),
|
||||
/// ],
|
||||
/// ..Default::default()
|
||||
/// },
|
||||
/// Faces::from_iter(&[
|
||||
/// &[3, 2, 1, 0], &[0, 1, 5, 4], &[1, 2, 6, 5],
|
||||
/// &[2, 3, 7, 6], &[3, 0, 4, 7], &[4, 5, 6, 7],
|
||||
/// ]),
|
||||
/// );
|
||||
///
|
||||
/// // the number of face becomes twice since each quadrangle decompose into two triangles.
|
||||
/// assert_eq!(mesh.faces().len(), 6);
|
||||
/// mesh.triangulate();
|
||||
/// assert_eq!(mesh.faces().len(), 12);
|
||||
/// ```
|
||||
fn triangulate(&mut self) -> &mut Self;
|
||||
/// join two triangles into one quadrangle.
|
||||
/// # Arguments
|
||||
/// * `plane_tol` - the tolerance for determining that four points are in the same plane
|
||||
/// * `score_tol` - The upper limit of the score to determine if the four points form an uncrushed rectangle
|
||||
/// # Details
|
||||
/// The overview of the algorithm is the following:
|
||||
/// 1. make the list of pairs of triangles satisfying the following conditions:
|
||||
/// * two faces are adjacent by one edge,
|
||||
/// * the pair of faces consists four vertices in the same plane
|
||||
/// 1. for each joined quadrangle, calculate the score by the sum of the absolute value of
|
||||
/// the cosine for each angles,
|
||||
/// 1. sort the list of the pairs of triangles by the score
|
||||
/// 1. take a pair of triangles in order from the top of the list and register a new one
|
||||
/// if it doesn't conflict with the one has been already registered.
|
||||
/// # Panics
|
||||
/// `plane_tol` and `score_tol` must be more than `TOLERANCE`.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_polymesh::*;
|
||||
/// use truck_meshalgo::filters::*;
|
||||
///
|
||||
/// // cube consisting tri_faces
|
||||
/// let mut mesh = PolygonMesh::new(
|
||||
/// StandardAttributes {
|
||||
/// positions: vec![
|
||||
/// Point3::new(0.0, 0.0, 0.0),
|
||||
/// Point3::new(1.0, 0.0, 0.0),
|
||||
/// Point3::new(1.0, 1.0, 0.0),
|
||||
/// Point3::new(0.0, 1.0, 0.0),
|
||||
/// Point3::new(0.0, 0.0, 1.0),
|
||||
/// Point3::new(1.0, 0.0, 1.0),
|
||||
/// Point3::new(1.0, 1.0, 1.0),
|
||||
/// Point3::new(0.0, 1.0, 1.0),
|
||||
/// ],
|
||||
/// ..Default::default()
|
||||
/// },
|
||||
/// Faces::from_iter(&[
|
||||
/// &[3, 2, 0], &[1, 0, 2], &[0, 1, 4], &[5, 4, 1],
|
||||
/// &[1, 2, 5], &[6, 5, 2], &[2, 3, 6], &[7, 6, 3],
|
||||
/// &[3, 0, 7], &[4, 7, 0], &[4, 5, 7], &[6, 7, 5],
|
||||
/// ]),
|
||||
/// );
|
||||
///
|
||||
/// // The number of faces becomes a half since each pair of triangles is combined.
|
||||
/// assert_eq!(mesh.faces().len(), 12);
|
||||
/// mesh.quadrangulate(0.01, 0.1);
|
||||
/// assert_eq!(mesh.faces().len(), 6);
|
||||
/// ```
|
||||
fn quadrangulate(&mut self, plane_tol: f64, score_tol: f64) -> &mut Self;
|
||||
}
|
||||
|
||||
impl StructuringFilter for PolygonMesh {
|
||||
fn triangulate(&mut self) -> &mut Self {
|
||||
let tri_faces = self.faces().triangle_iter().collect::<Vec<_>>();
|
||||
*self.debug_editor().faces = Faces::from_tri_and_quad_faces(tri_faces, Vec::new());
|
||||
self
|
||||
}
|
||||
fn quadrangulate(&mut self, plane_tol: f64, score_tol: f64) -> &mut Self {
|
||||
nonpositive_tolerance!(plane_tol, 0.0);
|
||||
nonpositive_tolerance!(score_tol, 0.0);
|
||||
let list = self.create_face_edge_list(plane_tol, score_tol);
|
||||
self.reflect_face_edge_list(list);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
trait SubStructureFilter {
|
||||
fn create_face_edge_list(&self, plane_tol: f64, score_tol: f64) -> Vec<FaceEdge>;
|
||||
fn reflect_face_edge_list(&mut self, list: Vec<FaceEdge>);
|
||||
fn get_face_edge(
|
||||
&self,
|
||||
face0_id: usize,
|
||||
face1_id: usize,
|
||||
plane_tol: f64,
|
||||
score_tol: f64,
|
||||
) -> Option<FaceEdge>;
|
||||
}
|
||||
|
||||
impl SubStructureFilter for PolygonMesh {
|
||||
fn create_face_edge_list(&self, plane_tol: f64, score_tol: f64) -> Vec<FaceEdge> {
|
||||
let face_adjacency = self.faces().face_adjacency(true);
|
||||
let mut passed = Vec::new();
|
||||
let tri_len = self.faces().tri_faces().len();
|
||||
for (i, face) in face_adjacency.iter().enumerate().take(tri_len) {
|
||||
for j in face {
|
||||
if i > *j {
|
||||
continue;
|
||||
} else if let Some(face_edge) = self.get_face_edge(i, *j, plane_tol, score_tol) {
|
||||
passed.push(face_edge);
|
||||
}
|
||||
}
|
||||
}
|
||||
passed.sort_by(|x, y| x.score.partial_cmp(&y.score).unwrap());
|
||||
passed
|
||||
}
|
||||
|
||||
fn reflect_face_edge_list(&mut self, list: Vec<FaceEdge>) {
|
||||
let mut used = vec![false; self.faces().tri_faces().len()];
|
||||
let mut quad_faces = self.faces().quad_faces().clone();
|
||||
quad_faces.extend(list.into_iter().filter_map(|face_edge| {
|
||||
let (i, j) = face_edge.faces;
|
||||
if used[i] || used[j] {
|
||||
None
|
||||
} else {
|
||||
used[i] = true;
|
||||
used[j] = true;
|
||||
Some(face_edge.positions)
|
||||
}
|
||||
}));
|
||||
let tri_faces = self.faces().tri_faces();
|
||||
let tri_faces = used
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.filter_map(move |(i, flag)| match flag {
|
||||
true => None,
|
||||
false => Some(tri_faces[i]),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
*self.debug_editor().faces = Faces::from_tri_and_quad_faces(tri_faces, quad_faces);
|
||||
}
|
||||
fn get_face_edge(
|
||||
&self,
|
||||
face0_id: usize,
|
||||
face1_id: usize,
|
||||
plane_tol: f64,
|
||||
score_tol: f64,
|
||||
) -> Option<FaceEdge> {
|
||||
let face0 = self.faces().tri_faces()[face0_id];
|
||||
let face1 = self.faces().tri_faces()[face1_id];
|
||||
|
||||
let k = (0..3)
|
||||
.find(|k| face0.iter().all(|x| x.pos != face1[*k].pos))
|
||||
.unwrap();
|
||||
let vec0 = self.positions()[face0[1].pos] - self.positions()[face0[0].pos];
|
||||
let vec1 = self.positions()[face0[2].pos] - self.positions()[face0[0].pos];
|
||||
let mut n = vec0.cross(vec1);
|
||||
n /= n.magnitude();
|
||||
let vec2 = self.positions()[face1[k].pos] - self.positions()[face0[0].pos];
|
||||
let mat = Matrix3::from_cols(vec0, vec1, n);
|
||||
let coef = mat.invert().unwrap() * vec2;
|
||||
|
||||
if coef[2] > plane_tol {
|
||||
None
|
||||
} else if coef[0] > 0.0 && coef[1] > 0.0 {
|
||||
let score = calc_score(vec0, vec2 - vec0, vec1 - vec2, vec1);
|
||||
if score < score_tol {
|
||||
Some(FaceEdge {
|
||||
faces: (face0_id, face1_id),
|
||||
positions: [face0[0], face0[1], face1[k], face0[2]],
|
||||
score,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else if coef[0] < 0.0 && coef[1] > 0.0 && coef[0] + coef[1] < 1.0 {
|
||||
let score = calc_score(vec0, vec1 - vec0, vec2 - vec1, vec2);
|
||||
if score < score_tol {
|
||||
Some(FaceEdge {
|
||||
faces: (face0_id, face1_id),
|
||||
positions: [face0[0], face0[1], face0[2], face1[k]],
|
||||
score,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else if coef[0] > 0.0 && coef[1] < 0.0 && coef[0] + coef[1] < 1.0 {
|
||||
let score = calc_score(vec2, vec0 - vec2, vec1 - vec0, vec1);
|
||||
if score < score_tol {
|
||||
Some(FaceEdge {
|
||||
faces: (face0_id, face1_id),
|
||||
positions: [face0[0], face1[k], face0[1], face0[2]],
|
||||
score,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct FaceEdge {
|
||||
faces: (usize, usize),
|
||||
positions: [Vertex; 4],
|
||||
score: f64,
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn calc_score(edge0: Vector3, edge1: Vector3, edge2: Vector3, edge3: Vector3) -> f64 {
|
||||
edge0.cos_angle(edge1).abs()
|
||||
+ edge1.cos_angle(edge2).abs()
|
||||
+ edge2.cos_angle(edge3).abs()
|
||||
+ edge3.cos_angle(edge0).abs()
|
||||
}
|
||||
|
||||
trait CosAngle {
|
||||
fn cos_angle(self, other: Self) -> f64;
|
||||
}
|
||||
|
||||
impl CosAngle for Vector3 {
|
||||
fn cos_angle(self, other: Self) -> f64 {
|
||||
self.dot(other) / (self.magnitude() * other.magnitude())
|
||||
}
|
||||
}
|
||||
-199
@@ -1,199 +0,0 @@
|
||||
use super::*;
|
||||
use rustc_hash::FxHashMap as HashMap;
|
||||
use std::f64::consts::PI;
|
||||
|
||||
/// subdivision surface algorithms
|
||||
pub trait Subdivision {
|
||||
/// Extended Loop method
|
||||
///
|
||||
/// # Remarks
|
||||
/// Confirm:
|
||||
/// - All faces are triangles.
|
||||
/// - `self.shell_condition()` is `Oriented` or `Closed` before use.
|
||||
///
|
||||
/// This method does NOT check these conditions.
|
||||
fn loop_subdivision(&mut self) -> &mut Self;
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
struct Edge(usize, usize);
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct EdgeInfo {
|
||||
idx: usize,
|
||||
first_wing: usize,
|
||||
second_wing: Option<usize>,
|
||||
}
|
||||
|
||||
impl Edge {
|
||||
#[inline]
|
||||
fn new(v0: usize, v1: usize) -> Self { Edge(usize::min(v0, v1), usize::max(v0, v1)) }
|
||||
}
|
||||
|
||||
impl EdgeInfo {
|
||||
#[inline]
|
||||
fn new(idx: usize, first_wing: usize) -> Self {
|
||||
Self {
|
||||
idx,
|
||||
first_wing,
|
||||
second_wing: None,
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
fn add_second_wing(&mut self, second_wing: usize) { self.second_wing = Some(second_wing); }
|
||||
}
|
||||
|
||||
impl Subdivision for PolygonMesh {
|
||||
fn loop_subdivision(&mut self) -> &mut Self {
|
||||
let mut edges = HashMap::default();
|
||||
let mut vertex_adjacency = vec![Vec::new(); self.positions().len()];
|
||||
self.tri_faces().iter().for_each(|face| {
|
||||
let face = [face[0].pos, face[1].pos, face[2].pos];
|
||||
add_vertex_edge(&mut edges, &mut vertex_adjacency, face[1], face[2], face[0]);
|
||||
add_vertex_edge(&mut edges, &mut vertex_adjacency, face[2], face[0], face[1]);
|
||||
add_vertex_edge(&mut edges, &mut vertex_adjacency, face[0], face[1], face[2]);
|
||||
});
|
||||
let positions = vertex_adjacency
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|t| calc_new_position(t.0, t.1, &edges, self.positions()))
|
||||
.chain(edge_positions(&edges, &vertex_adjacency, self.positions()))
|
||||
.collect::<Vec<_>>();
|
||||
let tri_faces = self
|
||||
.tri_faces()
|
||||
.iter()
|
||||
.flat_map(|v| {
|
||||
let len = self.positions().len();
|
||||
let e: [StandardVertex; 3] = [
|
||||
(edges.get(&Edge::new(v[1].pos, v[2].pos)).unwrap().idx + len).into(),
|
||||
(edges.get(&Edge::new(v[2].pos, v[0].pos)).unwrap().idx + len).into(),
|
||||
(edges.get(&Edge::new(v[0].pos, v[1].pos)).unwrap().idx + len).into(),
|
||||
];
|
||||
vec![
|
||||
[v[0], e[2], e[1]],
|
||||
[e[0], v[2], e[1]],
|
||||
[e[0], e[2], v[1]],
|
||||
[e[0], e[1], e[2]],
|
||||
]
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
{
|
||||
let editor = self.debug_editor();
|
||||
editor.attributes.positions = positions;
|
||||
*editor.faces = Faces::from_tri_and_quad_faces(tri_faces, Vec::new());
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
fn add_vertex_edge(
|
||||
edges: &mut HashMap<Edge, EdgeInfo>,
|
||||
vertex_adjacency: &mut [Vec<usize>],
|
||||
v0: usize,
|
||||
v1: usize,
|
||||
v2: usize,
|
||||
) {
|
||||
let idx = edges.len();
|
||||
edges
|
||||
.entry(Edge::new(v0, v1))
|
||||
.and_modify(|tuple| tuple.add_second_wing(v2))
|
||||
.or_insert_with(|| {
|
||||
vertex_adjacency[v0].push(v1);
|
||||
vertex_adjacency[v1].push(v0);
|
||||
EdgeInfo::new(idx, v2)
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
enum VertexBoundaryCondition {
|
||||
#[allow(dead_code)]
|
||||
Corner(usize, usize),
|
||||
Boundary(usize, usize),
|
||||
Inner,
|
||||
}
|
||||
|
||||
impl VertexBoundaryCondition {
|
||||
fn new(v: usize, adjacency: &[usize], edges: &HashMap<Edge, EdgeInfo>) -> Self {
|
||||
let binfo = adjacency.iter().copied().fold((None, None), |binfo, w| {
|
||||
let edge = edges.get(&Edge::new(v, w)).unwrap();
|
||||
match (edge.second_wing, binfo) {
|
||||
(None, (None, _)) => (Some(w), None),
|
||||
(None, (Some(x), None)) => (Some(x), Some(w)),
|
||||
_ => binfo,
|
||||
}
|
||||
});
|
||||
if let (Some(x), Some(y)) = binfo {
|
||||
if adjacency.len() == 2 {
|
||||
Self::Corner(x, y)
|
||||
} else {
|
||||
Self::Boundary(x, y)
|
||||
}
|
||||
} else {
|
||||
Self::Inner
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn calc_new_position(
|
||||
v: usize,
|
||||
adjacency: &[usize],
|
||||
edges: &HashMap<Edge, EdgeInfo>,
|
||||
positions: &[Point3],
|
||||
) -> Point3 {
|
||||
use VertexBoundaryCondition::*;
|
||||
let point = positions[v];
|
||||
match VertexBoundaryCondition::new(v, adjacency, edges) {
|
||||
Corner(_, _) => point,
|
||||
Boundary(w0, w1) => point + ((positions[w0] - point) + (positions[w1] - point)) / 8.0,
|
||||
Inner => {
|
||||
let alpha = 3.0 / 8.0 + f64::cos(2.0 * PI / adjacency.len() as f64) / 4.0;
|
||||
let alpha = (5.0 / 8.0 - alpha * alpha) / adjacency.len() as f64;
|
||||
point
|
||||
+ adjacency
|
||||
.iter()
|
||||
.copied()
|
||||
.map(|w| positions[w] - point)
|
||||
.sum::<Vector3>()
|
||||
* alpha
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn edge_positions(
|
||||
edges: &HashMap<Edge, EdgeInfo>,
|
||||
vertex_adjacency: &[Vec<usize>],
|
||||
positions: &[Point3],
|
||||
) -> Vec<Point3> {
|
||||
use VertexBoundaryCondition::*;
|
||||
let mut res = vec![Point3::origin(); edges.len()];
|
||||
edges.iter().for_each(|tuple| {
|
||||
let Edge(v0, v1) = *tuple.0;
|
||||
let EdgeInfo {
|
||||
idx,
|
||||
first_wing,
|
||||
second_wing,
|
||||
} = *tuple.1;
|
||||
res[idx] = match second_wing {
|
||||
Some(second_wing) => {
|
||||
let gamma = match VertexBoundaryCondition::new(v0, &vertex_adjacency[v0], edges) {
|
||||
Boundary(_, _) => {
|
||||
5.0 / 8.0 - f64::cos(PI / vertex_adjacency[v0].len() as f64) / 4.0
|
||||
}
|
||||
Inner => match VertexBoundaryCondition::new(v1, &vertex_adjacency[v1], edges) {
|
||||
Boundary(_, _) => {
|
||||
3.0 / 8.0 + f64::cos(PI / vertex_adjacency[v1].len() as f64) / 4.0
|
||||
}
|
||||
Inner => 0.5,
|
||||
Corner(_, _) => unreachable!(),
|
||||
},
|
||||
Corner(_, _) => unreachable!(),
|
||||
};
|
||||
positions[v0]
|
||||
+ (positions[v1] - positions[v0]) * gamma
|
||||
+ (positions[first_wing] - positions[v0]) / 8.0
|
||||
+ (positions[second_wing] - positions[v1]) / 8.0
|
||||
}
|
||||
None => positions[v0].midpoint(positions[v1]),
|
||||
};
|
||||
});
|
||||
res
|
||||
}
|
||||
Vendored
-56
@@ -1,56 +0,0 @@
|
||||
//! Mesh algorigthms, include tessellations of the shape.
|
||||
|
||||
// SolidCraft: compiler warnings no longer fail the build.
|
||||
#![deny(clippy::all, rust_2018_idioms)]
|
||||
#![warn(
|
||||
missing_docs,
|
||||
missing_debug_implementations,
|
||||
trivial_casts,
|
||||
trivial_numeric_casts,
|
||||
unsafe_code,
|
||||
unstable_features,
|
||||
unused_import_braces,
|
||||
unused_qualifications
|
||||
)]
|
||||
|
||||
use common::*;
|
||||
|
||||
/// re-export polymesh
|
||||
pub mod rexport_polymesh {
|
||||
pub use truck_polymesh::*;
|
||||
}
|
||||
use truck_polymesh::{StandardVertex as Vertex, *};
|
||||
|
||||
/// polygon mesh analizers, including
|
||||
///
|
||||
/// - determines topological properties: connectivity, boundary extraction, or shell conditions (closed or oriented)
|
||||
/// - detects collisions between two meshes and extracts interference lines
|
||||
/// - investigates positional relations between mesh and point clouds.
|
||||
#[cfg(feature = "analyzers")]
|
||||
pub mod analyzers;
|
||||
mod common;
|
||||
/// Edits meshes. Add normals, optimizing data, and so on.
|
||||
#[cfg(feature = "filters")]
|
||||
pub mod filters;
|
||||
/// Tessellates shapes.
|
||||
#[cfg(feature = "tessellation")]
|
||||
pub mod tessellation;
|
||||
|
||||
/// VTK Output
|
||||
#[cfg(feature = "vtk")]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub mod vtk;
|
||||
|
||||
/// This module contains all traits and re-exports `truck_polymesh`.
|
||||
pub mod prelude {
|
||||
#[cfg(feature = "analyzers")]
|
||||
pub use crate::analyzers::*;
|
||||
#[cfg(feature = "filters")]
|
||||
pub use crate::filters::*;
|
||||
pub use crate::rexport_polymesh::*;
|
||||
#[cfg(feature = "tessellation")]
|
||||
pub use crate::tessellation::*;
|
||||
#[cfg(feature = "vtk")]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use crate::vtk::*;
|
||||
}
|
||||
-121
@@ -1,121 +0,0 @@
|
||||
//! SolidCraft: a local periodic chart for geometrically closed spline surfaces.
|
||||
//! The stored spline still has a finite knot domain; only meshing wraps its parameters.
|
||||
use super::*;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(super) struct Closed<'a, S> {
|
||||
pub base: &'a S,
|
||||
ranges: [Option<(f64, f64)>; 2],
|
||||
periods: [Option<f64>; 2],
|
||||
inferred: [bool; 2],
|
||||
}
|
||||
|
||||
impl<'a, S: PreMeshableSurface> Closed<'a, S> {
|
||||
pub fn new(base: &'a S) -> Self {
|
||||
let (u, v) = base.try_range_tuple();
|
||||
let mut out = Self { base, ranges: [u, v], periods: [base.u_period(), base.v_period()], inferred: [false; 2] };
|
||||
let (Some((u0, u1)), Some((v0, v1))) = (u, v) else { return out };
|
||||
if ![u0, u1, v0, v1].iter().all(|v| v.is_finite()) || u1 <= u0 || v1 <= v0 {
|
||||
return out;
|
||||
}
|
||||
let origin = base.subs(u0, v0);
|
||||
let scale = (0..=4)
|
||||
.flat_map(|i| (0..=4).map(move |j| (i, j)))
|
||||
.map(|(i, j)| base.subs(u0 + (u1 - u0) * i as f64 / 4., v0 + (v1 - v0) * j as f64 / 4.).distance(origin))
|
||||
.fold(0.0_f64, f64::max);
|
||||
let tol2 = (scale * 1e-8).max(1e-9).powi(2);
|
||||
for d in 0..2 {
|
||||
if out.periods[d].is_some() {
|
||||
continue;
|
||||
}
|
||||
let (lo, hi, a, b) = if d == 0 { (u0, u1, v0, v1) } else { (v0, v1, u0, u1) };
|
||||
let mut noncollapsed = false;
|
||||
let closed = (0..=8).all(|k| {
|
||||
let t = a + (b - a) * k as f64 / 8.;
|
||||
let at = |x| if d == 0 { (x, t) } else { (t, x) };
|
||||
let (p, q, m) = (at(lo), at(hi), at((lo + hi) * 0.5));
|
||||
let first = base.subs(p.0, p.1);
|
||||
let (second, middle) = (base.subs(q.0, q.1), base.subs(m.0, m.1));
|
||||
if ![first.x, first.y, first.z, second.x, second.y, second.z, middle.x, middle.y, middle.z].iter().all(|x| x.is_finite()) {
|
||||
return false;
|
||||
}
|
||||
noncollapsed |= first.distance2(middle) > tol2;
|
||||
if first.distance2(second) > tol2 {
|
||||
return false;
|
||||
}
|
||||
let (n0, n1) = (base.normal(p.0, p.1), base.normal(q.0, q.1));
|
||||
!n0.magnitude2().is_finite() || !n1.magnitude2().is_finite() || n0.dot(n1) > 1.0 - 1e-6
|
||||
});
|
||||
if closed && noncollapsed {
|
||||
out.periods[d] = Some(hi - lo);
|
||||
out.inferred[d] = true;
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
pub fn periods(&self) -> [Option<f64>; 2] {
|
||||
self.periods
|
||||
}
|
||||
|
||||
pub fn wrap(&self, (u, v): (f64, f64)) -> (f64, f64) {
|
||||
let mut p = [u, v];
|
||||
for (d, x) in p.iter_mut().enumerate() {
|
||||
if let (true, Some((lo, hi)), Some(period)) = (self.inferred[d], self.ranges[d], self.periods[d]) {
|
||||
if *x < lo || *x > hi {
|
||||
*x = lo + (*x - lo).rem_euclid(period);
|
||||
}
|
||||
}
|
||||
}
|
||||
(p[0], p[1])
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: PreMeshableSurface> ParametricSurface for Closed<'_, S> {
|
||||
type Point = Point3;
|
||||
type Vector = Vector3;
|
||||
fn subs(&self, u: f64, v: f64) -> Point3 {
|
||||
let (u, v) = self.wrap((u, v));
|
||||
self.base.subs(u, v)
|
||||
}
|
||||
fn uder(&self, u: f64, v: f64) -> Vector3 {
|
||||
let (u, v) = self.wrap((u, v));
|
||||
self.base.uder(u, v)
|
||||
}
|
||||
fn vder(&self, u: f64, v: f64) -> Vector3 {
|
||||
let (u, v) = self.wrap((u, v));
|
||||
self.base.vder(u, v)
|
||||
}
|
||||
fn uuder(&self, u: f64, v: f64) -> Vector3 {
|
||||
let (u, v) = self.wrap((u, v));
|
||||
self.base.uuder(u, v)
|
||||
}
|
||||
fn uvder(&self, u: f64, v: f64) -> Vector3 {
|
||||
let (u, v) = self.wrap((u, v));
|
||||
self.base.uvder(u, v)
|
||||
}
|
||||
fn vvder(&self, u: f64, v: f64) -> Vector3 {
|
||||
let (u, v) = self.wrap((u, v));
|
||||
self.base.vvder(u, v)
|
||||
}
|
||||
fn parameter_range(&self) -> (ParameterRange, ParameterRange) {
|
||||
self.base.parameter_range()
|
||||
}
|
||||
fn u_period(&self) -> Option<f64> {
|
||||
self.periods[0]
|
||||
}
|
||||
fn v_period(&self) -> Option<f64> {
|
||||
self.periods[1]
|
||||
}
|
||||
}
|
||||
impl<S: PreMeshableSurface> ParametricSurface3D for Closed<'_, S> {
|
||||
fn normal(&self, u: f64, v: f64) -> Vector3 {
|
||||
let (u, v) = self.wrap((u, v));
|
||||
self.base.normal(u, v)
|
||||
}
|
||||
}
|
||||
impl<S: PreMeshableSurface> ParameterDivision2D for Closed<'_, S> {
|
||||
fn parameter_division(&self, range: ((f64, f64), (f64, f64)), tol: f64) -> (Vec<f64>, Vec<f64>) {
|
||||
triangulation::bounded_surface_division(self, range, Deflection::chord(tol), triangulation::MAX_GRID)
|
||||
}
|
||||
}
|
||||
-530
@@ -1,530 +0,0 @@
|
||||
use crate::*;
|
||||
use spade::{iterators::*, *};
|
||||
use truck_topology::{compress::*, *};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod parallelizable {
|
||||
/// Parallelizable by `rayon`.
|
||||
pub trait Parallelizable: Send + Sync {}
|
||||
impl<T: Send + Sync> Parallelizable for T {}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod parallelizable {
|
||||
/// No parallelization in the case of wasm.
|
||||
pub trait Parallelizable {}
|
||||
impl<T> Parallelizable for T {}
|
||||
}
|
||||
|
||||
pub use parallelizable::*;
|
||||
|
||||
/// SolidCraft: a disposable preview of a face from already sampled boundary loops.
|
||||
/// Caps input points and uses a fixed parameter grid, including periodic seams.
|
||||
/// This is approximate display, never an accuracy-controlled modelling mesh.
|
||||
pub fn preview_face<S: RobustMeshableSurface>(surface: &S, loops: &[Vec<Point3>], steps: usize) -> Option<PolygonMesh> {
|
||||
triangulation::preview_face(surface, loops, steps)
|
||||
}
|
||||
|
||||
/// Periods of a surface, including smooth geometric closure of bounded splines.
|
||||
/// Meshing wraps inferred periods into the original knot domain before evaluation.
|
||||
pub fn surface_periods<S: PreMeshableSurface>(surface: &S) -> [Option<f64>; 2] {
|
||||
closed::Closed::new(surface).periods()
|
||||
}
|
||||
|
||||
/// Project a trim point with the same bounded, tolerance-aware search used by the mesher.
|
||||
/// A preceding parameter is a hint, never proof that the search converged correctly.
|
||||
pub fn project_trim_point<S: RobustMeshableSurface>(surface: &S, point: Point3, hint: Option<(f64, f64)>, tolerance: f64) -> Option<(f64, f64)> {
|
||||
triangulation::nearest_projection(surface, point, hint, tolerance, 100)
|
||||
}
|
||||
|
||||
/// SolidCraft: an edge's polyline as the meshers divide it (at most 20 000 points). Exposed for
|
||||
/// the kernel's regressions in the workspace CI.
|
||||
#[doc(hidden)]
|
||||
pub fn bounded_polyline<C: PolylineableCurve>(curve: &C, range: (f64, f64), d: Deflection) -> PolylineCurve {
|
||||
triangulation::bounded_polyline(curve, range, d)
|
||||
}
|
||||
|
||||
/// SolidCraft: sampling a curve that may fail to evaluate. truck's `IntersectionCurve::subs`
|
||||
/// unwraps the projection of its leader's point onto both surfaces, which does not converge
|
||||
/// everywhere; the mesher samples through this trait so that a failed sample is data, not a panic.
|
||||
pub trait TrySubs: ParametricCurve3D {
|
||||
/// The point at `t`, or `None` where the curve does not evaluate.
|
||||
fn try_subs(&self, t: f64) -> Option<Point3> { Some(self.subs(t)) }
|
||||
/// A point near the curve at `t` that always exists (an intersection curve's leader).
|
||||
fn approx_subs(&self, t: f64) -> Point3 { self.subs(t) }
|
||||
}
|
||||
|
||||
impl TrySubs for truck_geometry::prelude::Line<Point3> {}
|
||||
impl TrySubs for truck_geometry::prelude::BSplineCurve<Point3> {}
|
||||
impl TrySubs for truck_geometry::prelude::NurbsCurve<Vector4> {}
|
||||
impl TrySubs for PolylineCurve {}
|
||||
|
||||
impl<C, S> TrySubs for truck_geometry::prelude::IntersectionCurve<C, S>
|
||||
where
|
||||
C: ParametricCurve3D,
|
||||
S: ParametricSurface3D + SearchNearestParameter<D2, Point = Point3>,
|
||||
{
|
||||
fn try_subs(&self, t: f64) -> Option<Point3> { self.search_triple(t).map(|triple| triple.0) }
|
||||
fn approx_subs(&self, t: f64) -> Point3 { self.leader().subs(t) }
|
||||
}
|
||||
|
||||
impl TrySubs for truck_modeling::Curve {
|
||||
fn try_subs(&self, t: f64) -> Option<Point3> {
|
||||
match self {
|
||||
truck_modeling::Curve::IntersectionCurve(curve) => TrySubs::try_subs(curve, t),
|
||||
other => Some(other.subs(t)),
|
||||
}
|
||||
}
|
||||
fn approx_subs(&self, t: f64) -> Point3 {
|
||||
match self {
|
||||
truck_modeling::Curve::IntersectionCurve(curve) => TrySubs::approx_subs(curve, t),
|
||||
other => other.subs(t),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Gathered the traits used in tessellation.
|
||||
pub trait PolylineableCurve:
|
||||
ParametricCurve3D + BoundedCurve + ParameterDivision1D<Point = Point3> + Parallelizable + TrySubs {
|
||||
}
|
||||
impl<
|
||||
C: ParametricCurve3D + BoundedCurve + ParameterDivision1D<Point = Point3> + Parallelizable + TrySubs,
|
||||
> PolylineableCurve for C
|
||||
{
|
||||
}
|
||||
|
||||
/// It can be meshed, but not necessarily trimmed.
|
||||
pub trait PreMeshableSurface: ParametricSurface3D + ParameterDivision2D + Parallelizable {}
|
||||
impl<S: ParametricSurface3D + ParameterDivision2D + Parallelizable> PreMeshableSurface for S {}
|
||||
|
||||
/// The generated mesh can be trimmed only if the boundary curves ride strictly on a surface.
|
||||
pub trait MeshableSurface: PreMeshableSurface + SearchParameter<D2, Point = Point3> {}
|
||||
impl<S: PreMeshableSurface + SearchParameter<D2, Point = Point3>> MeshableSurface for S {}
|
||||
|
||||
/// The generated mesh can be trimmed if the boundary curves does not ride strictly on a surface.
|
||||
pub trait RobustMeshableSurface:
|
||||
MeshableSurface + SearchNearestParameter<D2, Point = Point3> {
|
||||
}
|
||||
impl<S: MeshableSurface + SearchNearestParameter<D2, Point = Point3>> RobustMeshableSurface for S {}
|
||||
|
||||
type PolylineCurve = truck_polymesh::PolylineCurve<Point3>;
|
||||
|
||||
/// SolidCraft: how finely curves and surfaces are divided into facets. `chord` is the usual
|
||||
/// tolerance: the most a facet may sag from the true shape. `angle`, when finite, also bounds
|
||||
/// what one facet spans: a facet that turns more than `angle` is split whatever its size (a
|
||||
/// radius that is small beside the tolerance would otherwise stay one flat facet), until its
|
||||
/// sag is below `floor`.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct Deflection {
|
||||
/// Most a facet may sag from the curve or surface.
|
||||
pub chord: f64,
|
||||
/// Most a facet may turn (radians: the arc it spans on a circle); infinite for no bound.
|
||||
pub angle: f64,
|
||||
/// Sag below which `angle` no longer splits a facet.
|
||||
pub floor: f64,
|
||||
/// SolidCraft: a mesh to display or export, which must lie on its faces, rather than one a
|
||||
/// Boolean operation intersects (whose results must not depend on what follows). Such a
|
||||
/// mesh gets grid lines through the vertices that split a face's ruling edges (see
|
||||
/// `with_ruling_vertices`), triangulates a trimmed face in speed-scaled parameters
|
||||
/// (`UvScale`), keeps every boundary segment as a constraint (`PolyBoundary::insert_to`),
|
||||
/// and puts its boundary points on the face's edges (`onto_edges`).
|
||||
pub display: bool,
|
||||
}
|
||||
|
||||
impl Deflection {
|
||||
/// Most boundary segments a face may get from the angle bound. Triangulating a face tests
|
||||
/// each triangle against the boundary loops around it, so the work grows faster than the
|
||||
/// boundary (with its square for one long loop): the curved edges of a face that would
|
||||
/// get more are divided at twice the angle, and again, down to the chord tolerance alone.
|
||||
pub const MAX_ANGLE_BOUNDARY: usize = 4096;
|
||||
/// Most grid points a face may get from the angle bound (beyond them only `chord` refines).
|
||||
pub const MAX_ANGLE_GRID: usize = 16_384;
|
||||
/// How often the angle is doubled for a crowded face before only `chord` is left.
|
||||
pub const MAX_RELAX: u8 = 4;
|
||||
|
||||
/// The chord tolerance alone: what `triangulation(tol)` divides by.
|
||||
pub const fn chord(tol: f64) -> Self {
|
||||
Self {
|
||||
chord: tol,
|
||||
angle: f64::INFINITY,
|
||||
floor: 0.0,
|
||||
display: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether `angle` bounds anything.
|
||||
pub fn angular(&self) -> bool { self.angle.is_finite() && self.angle > 0.0 }
|
||||
|
||||
/// The same with the angle doubled `level` times; from [`Deflection::MAX_RELAX`] times, or
|
||||
/// past half a turn, the chord tolerance alone.
|
||||
pub fn relaxed(&self, level: u8) -> Self {
|
||||
let angle = self.angle * f64::from(1u32 << level.min(Self::MAX_RELAX));
|
||||
match level == 0 || (level < Self::MAX_RELAX && angle < std::f64::consts::PI) {
|
||||
true => Self { angle, ..*self },
|
||||
false => Self { display: self.display, ..Self::chord(self.chord) },
|
||||
}
|
||||
}
|
||||
|
||||
/// The squared sag over squared length above which a facet turns more than `angle`: a
|
||||
/// chord spanning `a` of a circle sags `tan(a / 4) / 2` of its length. Infinite without
|
||||
/// an angle bound. SolidCraft: public for the kernel's regressions.
|
||||
#[doc(hidden)]
|
||||
pub fn turn2(&self) -> f64 {
|
||||
match self.angular() && self.angle < std::f64::consts::PI {
|
||||
true => (f64::tan(self.angle / 4.0) / 2.0).powi(2),
|
||||
false => f64::INFINITY,
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the angle bound splits a facet of squared sag `sag2` and squared length `len2`
|
||||
/// (`turn2` from [`Deflection::turn2`]). SolidCraft: public for the kernel's regressions.
|
||||
#[doc(hidden)]
|
||||
pub fn turns(&self, turn2: f64, sag2: f64, len2: f64) -> bool {
|
||||
turn2.is_finite() && sag2 > self.floor * self.floor && sag2 > turn2 * len2
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for converting tessellated shape into polygon.
|
||||
pub trait MeshedShape {
|
||||
/// Converts tessellated shape into polygon.
|
||||
fn to_polygon(&self) -> PolygonMesh;
|
||||
}
|
||||
|
||||
impl MeshedShape for Shell<Point3, PolylineCurve, PolygonMesh> {
|
||||
fn to_polygon(&self) -> PolygonMesh {
|
||||
let mut polygon = PolygonMesh::default();
|
||||
self.face_iter().for_each(|face| {
|
||||
polygon.merge(face.oriented_surface());
|
||||
});
|
||||
polygon
|
||||
}
|
||||
}
|
||||
|
||||
impl MeshedShape for Shell<Point3, PolylineCurve, Option<PolygonMesh>> {
|
||||
fn to_polygon(&self) -> PolygonMesh {
|
||||
let mut polygon = PolygonMesh::default();
|
||||
self.face_iter().for_each(|face| {
|
||||
if let Some(mut poly) = face.surface() {
|
||||
if !face.orientation() {
|
||||
poly.invert();
|
||||
}
|
||||
polygon.merge(poly);
|
||||
}
|
||||
});
|
||||
polygon
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, C, S> MeshedShape for Solid<P, C, S>
|
||||
where Shell<P, C, S>: MeshedShape
|
||||
{
|
||||
fn to_polygon(&self) -> PolygonMesh {
|
||||
let mut polygon = PolygonMesh::default();
|
||||
self.boundaries().iter().for_each(|shell| {
|
||||
polygon.merge(shell.to_polygon());
|
||||
});
|
||||
polygon
|
||||
}
|
||||
}
|
||||
|
||||
impl MeshedShape for CompressedShell<Point3, PolylineCurve, PolygonMesh> {
|
||||
fn to_polygon(&self) -> PolygonMesh {
|
||||
let mut polygon = PolygonMesh::default();
|
||||
self.faces.iter().for_each(|face| match face.orientation {
|
||||
true => polygon.merge(face.surface.clone()),
|
||||
false => polygon.merge(face.surface.inverse()),
|
||||
});
|
||||
polygon
|
||||
}
|
||||
}
|
||||
|
||||
impl MeshedShape for CompressedShell<Point3, PolylineCurve, Option<PolygonMesh>> {
|
||||
fn to_polygon(&self) -> PolygonMesh {
|
||||
let mut polygon = PolygonMesh::default();
|
||||
self.faces.iter().for_each(|face| {
|
||||
if let Some(surface) = &face.surface {
|
||||
match face.orientation {
|
||||
true => polygon.merge(surface.clone()),
|
||||
false => polygon.merge(surface.inverse()),
|
||||
}
|
||||
}
|
||||
});
|
||||
polygon
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, C, S> MeshedShape for CompressedSolid<P, C, S>
|
||||
where CompressedShell<P, C, S>: MeshedShape
|
||||
{
|
||||
fn to_polygon(&self) -> PolygonMesh {
|
||||
let mut polygon = PolygonMesh::default();
|
||||
self.boundaries.iter().for_each(|shell| {
|
||||
polygon.merge(shell.to_polygon());
|
||||
});
|
||||
polygon
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for tessellating `Shell` and `Solid`.
|
||||
pub trait MeshableShape {
|
||||
/// Shape whose edges are made polylines and faces polygon surface.
|
||||
type MeshedShape: MeshedShape;
|
||||
/// Tessellates shapes. The division of curves and surfaces are by `ParameterDivision1D` and `ParameterDivision2D`,
|
||||
/// and the constrained Delauney triangulation is based on the crate [`spade`](https://crates.io/crates/spade).
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// `tol` must be more than `TOLERANCE`.
|
||||
///
|
||||
/// # Remarks
|
||||
///
|
||||
/// - The tessellated mesh is not necessarily closed even if `self` is `Solid`.
|
||||
/// If you want to get closed mesh, use [`OptimizingFilter::put_together_same_attrs`].
|
||||
/// - This method requires that the curve ride strictly on a surface. If not, try [`RobustMeshableShape`].
|
||||
///
|
||||
/// [`OptimizingFilter::put_together_same_attrs`]: crate::filters::OptimizingFilter::put_together_same_attrs
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_meshalgo::prelude::*;
|
||||
/// use truck_modeling::builder;
|
||||
/// use truck_topology::shell::ShellCondition;
|
||||
///
|
||||
/// // modeling a unit cube
|
||||
/// let v = builder::vertex(Point3::origin());
|
||||
/// let e = builder::tsweep(&v, Vector3::unit_x());
|
||||
/// let f = builder::tsweep(&e, Vector3::unit_y());
|
||||
/// let cube = builder::tsweep(&f, Vector3::unit_z());
|
||||
///
|
||||
/// // cube is Solid, however, the tessellated mesh is not closed.
|
||||
/// let mut mesh = cube.triangulation(0.01).to_polygon();
|
||||
/// assert_ne!(mesh.shell_condition(), ShellCondition::Closed);
|
||||
///
|
||||
/// // use optimization filters!
|
||||
/// mesh.put_together_same_attrs(TOLERANCE);
|
||||
/// assert_eq!(mesh.shell_condition(), ShellCondition::Closed);
|
||||
/// ```
|
||||
fn triangulation(&self, tol: f64) -> Self::MeshedShape;
|
||||
}
|
||||
|
||||
/// Trait for tessellating `Shell` and `Solid` in `truck-modeling`.
|
||||
pub trait RobustMeshableShape {
|
||||
/// Shape whose edges are made polylines and faces polygon surface.
|
||||
type MeshedShape: MeshedShape;
|
||||
/// Tessellates shapes. The division of curves and surfaces are by `ParameterDivision1D` and `ParameterDivision2D`,
|
||||
/// and the constrained Delauney triangulation is based on the crate [`spade`](https://crates.io/crates/spade).
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// `tol` must be more than `TOLERANCE`.
|
||||
///
|
||||
/// # Remarks
|
||||
///
|
||||
/// Since polyline vertices are projected onto the surface, processing speed is often slower than with [`MeshableShape::triangulation`].
|
||||
/// It also does not close the mesh of a solid even if one uses [`OptimizingFilter::put_together_same_attrs`].
|
||||
///
|
||||
/// [`OptimizingFilter::put_together_same_attrs`]: crate::filters::OptimizingFilter::put_together_same_attrs
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_meshalgo::prelude::*;
|
||||
/// use truck_modeling::*;
|
||||
/// use truck_topology::shell::ShellCondition;
|
||||
///
|
||||
/// // manual modeling an open half cylinder
|
||||
///
|
||||
/// // points
|
||||
/// let p = [
|
||||
/// Point3::new(1.0, 0.0, 0.0),
|
||||
/// Point3::new(-1.0, 0.0, 0.0),
|
||||
/// Point3::new(1.0, 0.0, 1.0),
|
||||
/// Point3::new(-1.0, 0.0, 1.0),
|
||||
/// ];
|
||||
/// // vertices
|
||||
/// let v = Vertex::news(&p);
|
||||
/// // Curves that do not ride on a cylinder
|
||||
/// let bsp0: Curve = BSplineCurve::new(
|
||||
/// KnotVec::bezier_knot(3),
|
||||
/// vec![
|
||||
/// p[0],
|
||||
/// Point3::new(1.0, 4.0 / 3.0, 0.0),
|
||||
/// Point3::new(-1.0, 4.0 / 3.0, 0.0),
|
||||
/// p[1],
|
||||
/// ],
|
||||
/// )
|
||||
/// .into();
|
||||
/// let bsp1: Curve = BSplineCurve::new(
|
||||
/// KnotVec::bezier_knot(3),
|
||||
/// vec![
|
||||
/// p[3],
|
||||
/// Point3::new(-1.0, 4.0 / 3.0, 1.0),
|
||||
/// Point3::new(1.0, 4.0 / 3.0, 1.0),
|
||||
/// p[2],
|
||||
/// ],
|
||||
/// )
|
||||
/// .into();
|
||||
/// // wire
|
||||
/// let w: Wire = vec![
|
||||
/// builder::line(&v[2], &v[0]),
|
||||
/// Edge::new(&v[0], &v[1], bsp0),
|
||||
/// builder::line(&v[1], &v[3]),
|
||||
/// Edge::new(&v[3], &v[2], bsp1),
|
||||
/// ]
|
||||
/// .into();
|
||||
/// // revoluted curve
|
||||
/// let surface_raw = RevolutedCurve::by_revolution(
|
||||
/// Curve::Line(Line(p[2], p[0])),
|
||||
/// Point3::origin(),
|
||||
/// Vector3::unit_z(),
|
||||
/// );
|
||||
/// let surface: Surface = Processor::new(surface_raw).into();
|
||||
/// // shell
|
||||
/// let shell: Shell = vec![Face::new(vec![w], surface)].into();
|
||||
///
|
||||
/// // Simple triangulation fails since some edges do not ride on a cylinder
|
||||
/// let poly_shell = shell.triangulation(0.01);
|
||||
/// assert!(poly_shell[0].surface().is_none());
|
||||
///
|
||||
/// // Robust triangulation!
|
||||
/// let poly_shell = shell.robust_triangulation(0.01);
|
||||
/// let poly = poly_shell[0].surface().unwrap();
|
||||
/// assert!(!poly.positions().is_empty());
|
||||
/// ```
|
||||
fn robust_triangulation(&self, tol: f64) -> Self::MeshedShape;
|
||||
}
|
||||
|
||||
impl<C: PolylineableCurve, S: MeshableSurface> MeshableShape for Shell<Point3, C, S> {
|
||||
type MeshedShape = Shell<Point3, PolylineCurve, Option<PolygonMesh>>;
|
||||
fn triangulation(&self, tol: f64) -> Self::MeshedShape {
|
||||
nonpositive_tolerance!(tol);
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let res = triangulation::shell_tessellation(
|
||||
self,
|
||||
Deflection::chord(tol),
|
||||
triangulation::by_search_parameter,
|
||||
);
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let res = triangulation::shell_tessellation_single_thread(
|
||||
self,
|
||||
Deflection::chord(tol),
|
||||
triangulation::by_search_parameter,
|
||||
);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: PolylineableCurve, S: RobustMeshableSurface> RobustMeshableShape for Shell<Point3, C, S> {
|
||||
type MeshedShape = Shell<Point3, PolylineCurve, Option<PolygonMesh>>;
|
||||
fn robust_triangulation(&self, tol: f64) -> Self::MeshedShape {
|
||||
self.robust_triangulation_within(Deflection::chord(tol))
|
||||
}
|
||||
}
|
||||
|
||||
/// SolidCraft: tessellation with a bound on the angle one facet spans as well as on its sag.
|
||||
pub trait RoundMeshableShape {
|
||||
/// Shape whose edges are made polylines and faces polygon surface.
|
||||
type MeshedShape: MeshedShape;
|
||||
/// [`RobustMeshableShape::robust_triangulation`] divided by `deflection`: with
|
||||
/// [`Deflection::chord`] the very same shape; with an angle, curved edges and faces whose
|
||||
/// radius is small beside the chord tolerance are divided as well. Every edge is still
|
||||
/// divided once, for both faces it bounds, so neighbouring faces keep sharing its points.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// `deflection.chord` must be more than `TOLERANCE`.
|
||||
fn robust_triangulation_within(&self, deflection: Deflection) -> Self::MeshedShape;
|
||||
}
|
||||
|
||||
impl<C: PolylineableCurve, S: RobustMeshableSurface> RoundMeshableShape for Shell<Point3, C, S> {
|
||||
type MeshedShape = Shell<Point3, PolylineCurve, Option<PolygonMesh>>;
|
||||
fn robust_triangulation_within(&self, deflection: Deflection) -> Self::MeshedShape {
|
||||
nonpositive_tolerance!(deflection.chord);
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let res = triangulation::shell_tessellation(
|
||||
self,
|
||||
deflection,
|
||||
triangulation::by_search_nearest_parameter,
|
||||
);
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let res = triangulation::shell_tessellation_single_thread(
|
||||
self,
|
||||
deflection,
|
||||
triangulation::by_search_nearest_parameter,
|
||||
);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: PolylineableCurve, S: MeshableSurface> MeshableShape for Solid<Point3, C, S> {
|
||||
type MeshedShape = Solid<Point3, PolylineCurve, Option<PolygonMesh>>;
|
||||
fn triangulation(&self, tol: f64) -> Self::MeshedShape {
|
||||
let boundaries = self
|
||||
.boundaries()
|
||||
.iter()
|
||||
.map(|shell| shell.triangulation(tol))
|
||||
.collect::<Vec<_>>();
|
||||
Solid::new(boundaries)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: PolylineableCurve, S: RobustMeshableSurface> RobustMeshableShape for Solid<Point3, C, S> {
|
||||
type MeshedShape = Solid<Point3, PolylineCurve, Option<PolygonMesh>>;
|
||||
fn robust_triangulation(&self, tol: f64) -> Self::MeshedShape {
|
||||
let boundaries = self
|
||||
.boundaries()
|
||||
.iter()
|
||||
.map(|shell| shell.robust_triangulation(tol))
|
||||
.collect::<Vec<_>>();
|
||||
Solid::new(boundaries)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: PolylineableCurve, S: MeshableSurface> MeshableShape for CompressedShell<Point3, C, S> {
|
||||
type MeshedShape = CompressedShell<Point3, PolylineCurve, Option<PolygonMesh>>;
|
||||
fn triangulation(&self, tol: f64) -> Self::MeshedShape {
|
||||
nonpositive_tolerance!(tol);
|
||||
triangulation::cshell_tessellation(self, tol, triangulation::by_search_parameter)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: PolylineableCurve, S: RobustMeshableSurface> RobustMeshableShape
|
||||
for CompressedShell<Point3, C, S>
|
||||
{
|
||||
type MeshedShape = CompressedShell<Point3, PolylineCurve, Option<PolygonMesh>>;
|
||||
fn robust_triangulation(&self, tol: f64) -> Self::MeshedShape {
|
||||
nonpositive_tolerance!(tol);
|
||||
triangulation::cshell_tessellation(self, tol, triangulation::by_search_nearest_parameter)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: PolylineableCurve, S: MeshableSurface> MeshableShape for CompressedSolid<Point3, C, S> {
|
||||
type MeshedShape = CompressedSolid<Point3, PolylineCurve, Option<PolygonMesh>>;
|
||||
fn triangulation(&self, tol: f64) -> Self::MeshedShape {
|
||||
let boundaries = self
|
||||
.boundaries
|
||||
.iter()
|
||||
.map(|shell| shell.triangulation(tol))
|
||||
.collect::<Vec<_>>();
|
||||
CompressedSolid { boundaries }
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: PolylineableCurve, S: RobustMeshableSurface> RobustMeshableShape
|
||||
for CompressedSolid<Point3, C, S>
|
||||
{
|
||||
type MeshedShape = CompressedSolid<Point3, PolylineCurve, Option<PolygonMesh>>;
|
||||
fn robust_triangulation(&self, tol: f64) -> Self::MeshedShape {
|
||||
let boundaries = self
|
||||
.boundaries
|
||||
.iter()
|
||||
.map(|shell| shell.robust_triangulation(tol))
|
||||
.collect::<Vec<_>>();
|
||||
CompressedSolid { boundaries }
|
||||
}
|
||||
}
|
||||
|
||||
mod closed;
|
||||
mod triangulation;
|
||||
#[cfg(feature = "test-hooks")]
|
||||
#[doc(hidden)]
|
||||
pub use triangulation::{include_test_hooks, loop_fill_test_hooks};
|
||||
-1814
File diff suppressed because it is too large
Load Diff
Vendored
-525
@@ -1,525 +0,0 @@
|
||||
use crate::*;
|
||||
use rustc_hash::FxHashMap as HashMap;
|
||||
use std::convert::identity;
|
||||
use truck_base::tolerance::TOLERANCE;
|
||||
use truck_topology::{compress::*, Vertex, *};
|
||||
pub use vtkio;
|
||||
use vtkio::model::{Attributes, *};
|
||||
|
||||
/// Trait for convert to [`DataSet`]
|
||||
pub trait ToDataSet {
|
||||
/// Converts to [`DataSet`]
|
||||
/// # Remarks
|
||||
/// The `DataSet` is generated assuming output in XML format.
|
||||
fn to_data_set(&self) -> DataSet;
|
||||
}
|
||||
|
||||
fn to_vertex_numbers(faces: &Faces<usize>) -> VertexNumbers {
|
||||
let connectivity = faces
|
||||
.face_iter()
|
||||
.flat_map(|face| face.iter().map(|idx| *idx as u64))
|
||||
.collect::<Vec<_>>();
|
||||
let mut offset: u64 = 0;
|
||||
let offsets = faces
|
||||
.face_iter()
|
||||
.map(|face| {
|
||||
offset += face.len() as u64;
|
||||
offset
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
VertexNumbers::XML {
|
||||
connectivity,
|
||||
offsets,
|
||||
}
|
||||
}
|
||||
|
||||
impl ToDataSet for PolygonMesh<usize, Vec<Point3>> {
|
||||
fn to_data_set(&self) -> DataSet {
|
||||
let flatten_points = self
|
||||
.attributes()
|
||||
.iter()
|
||||
.flat_map(|p| Into::<[f64; 3]>::into(*p))
|
||||
.collect::<Vec<_>>();
|
||||
DataSet::PolyData {
|
||||
meta: None,
|
||||
pieces: vec![Piece::Inline(Box::new(PolyDataPiece {
|
||||
points: IOBuffer::F64(flatten_points),
|
||||
polys: Some(to_vertex_numbers(self.faces())),
|
||||
..Default::default()
|
||||
}))],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToDataSet for PolygonMesh<usize, Vec<(Point3, Vector2)>> {
|
||||
fn to_data_set(&self) -> DataSet {
|
||||
let flatten_points = self
|
||||
.attributes()
|
||||
.iter()
|
||||
.flat_map(|(p, _)| Into::<[f64; 3]>::into(*p))
|
||||
.collect::<Vec<_>>();
|
||||
let flatten_uvs = self
|
||||
.attributes()
|
||||
.iter()
|
||||
.flat_map(|(_, uv)| Into::<[f64; 2]>::into(*uv))
|
||||
.collect::<Vec<_>>();
|
||||
let uvs = Attribute::DataArray(DataArray {
|
||||
name: "TCoords".to_owned(),
|
||||
elem: ElementType::TCoords(2),
|
||||
data: IOBuffer::F64(flatten_uvs),
|
||||
});
|
||||
DataSet::PolyData {
|
||||
meta: None,
|
||||
pieces: vec![Piece::Inline(Box::new(PolyDataPiece {
|
||||
points: IOBuffer::F64(flatten_points),
|
||||
polys: Some(to_vertex_numbers(self.faces())),
|
||||
data: Attributes {
|
||||
point: vec![uvs],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}))],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToDataSet for PolygonMesh<usize, Vec<(Point3, Vector3)>> {
|
||||
fn to_data_set(&self) -> DataSet {
|
||||
let flatten_points = self
|
||||
.attributes()
|
||||
.iter()
|
||||
.flat_map(|(p, _)| Into::<[f64; 3]>::into(*p))
|
||||
.collect::<Vec<_>>();
|
||||
let flatten_normals = self
|
||||
.attributes()
|
||||
.iter()
|
||||
.flat_map(|(_, n)| Into::<[f64; 3]>::into(*n))
|
||||
.collect::<Vec<_>>();
|
||||
let normals = Attribute::DataArray(DataArray {
|
||||
name: "Normals".to_owned(),
|
||||
elem: ElementType::Normals,
|
||||
data: IOBuffer::F64(flatten_normals),
|
||||
});
|
||||
DataSet::PolyData {
|
||||
meta: None,
|
||||
pieces: vec![Piece::Inline(Box::new(PolyDataPiece {
|
||||
points: IOBuffer::F64(flatten_points),
|
||||
polys: Some(to_vertex_numbers(self.faces())),
|
||||
data: Attributes {
|
||||
point: vec![normals],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}))],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToDataSet for PolygonMesh<usize, Vec<StandardAttribute>> {
|
||||
fn to_data_set(&self) -> DataSet {
|
||||
let flatten_points = self
|
||||
.attributes()
|
||||
.iter()
|
||||
.flat_map(|attr| Into::<[f64; 3]>::into(attr.position))
|
||||
.collect::<Vec<_>>();
|
||||
let flatten_uvs = self
|
||||
.attributes()
|
||||
.iter()
|
||||
.flat_map(|attr| match attr.uv_coord {
|
||||
Some(uv) => Into::<[f64; 2]>::into(uv),
|
||||
None => [f64::NAN; 2],
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let uvs = Attribute::DataArray(DataArray {
|
||||
name: "TCoords".to_owned(),
|
||||
elem: ElementType::TCoords(2),
|
||||
data: IOBuffer::F64(flatten_uvs),
|
||||
});
|
||||
let flatten_normals = self
|
||||
.attributes()
|
||||
.iter()
|
||||
.flat_map(|attr| match attr.normal {
|
||||
Some(normal) => Into::<[f64; 3]>::into(normal),
|
||||
None => [f64::NAN; 3],
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let normals = Attribute::DataArray(DataArray {
|
||||
name: "Normals".to_owned(),
|
||||
elem: ElementType::Normals,
|
||||
data: IOBuffer::F64(flatten_normals),
|
||||
});
|
||||
DataSet::PolyData {
|
||||
meta: None,
|
||||
pieces: vec![Piece::Inline(Box::new(PolyDataPiece {
|
||||
points: IOBuffer::F64(flatten_points),
|
||||
polys: Some(to_vertex_numbers(self.faces())),
|
||||
data: Attributes {
|
||||
point: vec![uvs, normals],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}))],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToDataSet for PolygonMesh {
|
||||
fn to_data_set(&self) -> DataSet {
|
||||
let attrs = self.attributes();
|
||||
match (attrs.uv_coords().is_empty(), attrs.normals().is_empty()) {
|
||||
(true, true) => self.to_positions_mesh().to_data_set(),
|
||||
(false, true) => self
|
||||
.expands(|attr| {
|
||||
let uv = match attr.uv_coord {
|
||||
Some(uv) => uv,
|
||||
None => Vector2::new(f64::NAN, f64::NAN),
|
||||
};
|
||||
(attr.position, uv)
|
||||
})
|
||||
.to_data_set(),
|
||||
(true, false) => self
|
||||
.expands(|attr| {
|
||||
let normal = match attr.normal {
|
||||
Some(normal) => normal,
|
||||
None => Vector3::new(f64::NAN, f64::NAN, f64::NAN),
|
||||
};
|
||||
(attr.position, normal)
|
||||
})
|
||||
.to_data_set(),
|
||||
(false, false) => self.expands(identity).to_data_set(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn hash_point(p: Point3) -> Option<[i64; 3]> {
|
||||
(p / (TOLERANCE * 50.0) - Vector3::new(0.5, 0.5, 0.5))
|
||||
.cast()
|
||||
.map(Into::into)
|
||||
}
|
||||
|
||||
impl ToDataSet for Vertex<Point3> {
|
||||
fn to_data_set(&self) -> DataSet {
|
||||
DataSet::UnstructuredGrid {
|
||||
meta: None,
|
||||
pieces: vec![Piece::Inline(Box::new(UnstructuredGridPiece {
|
||||
points: IOBuffer::F64(Into::<[f64; 3]>::into(self.point()).to_vec()),
|
||||
cells: Cells {
|
||||
cell_verts: VertexNumbers::XML {
|
||||
connectivity: vec![0],
|
||||
offsets: vec![1],
|
||||
},
|
||||
types: vec![CellType::Vertex],
|
||||
},
|
||||
data: Default::default(),
|
||||
}))],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToDataSet for Edge<Point3, PolylineCurve<Point3>> {
|
||||
fn to_data_set(&self) -> DataSet {
|
||||
let curve = self.oriented_curve();
|
||||
let points = curve
|
||||
.0
|
||||
.iter()
|
||||
.copied()
|
||||
.flat_map(Into::<[f64; 3]>::into)
|
||||
.collect::<Vec<_>>();
|
||||
let len = curve.0.len() as u64;
|
||||
let connectivity = (0..len).chain(vec![0, len - 1]).collect();
|
||||
DataSet::UnstructuredGrid {
|
||||
meta: None,
|
||||
pieces: vec![Piece::Inline(Box::new(UnstructuredGridPiece {
|
||||
points: IOBuffer::F64(points),
|
||||
cells: Cells {
|
||||
cell_verts: VertexNumbers::XML {
|
||||
connectivity,
|
||||
offsets: vec![len, len + 1, len + 2],
|
||||
},
|
||||
types: vec![CellType::PolyLine, CellType::Vertex, CellType::Vertex],
|
||||
},
|
||||
data: Default::default(),
|
||||
}))],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToDataSet for Wire<Point3, PolylineCurve<Point3>> {
|
||||
fn to_data_set(&self) -> DataSet {
|
||||
let mut points = Vec::<[f64; 3]>::new();
|
||||
let mut connectivity = Vec::<u64>::new();
|
||||
let mut offsets = Vec::<u64>::new();
|
||||
let mut types = Vec::<CellType>::new();
|
||||
let mut vmap = truck_base::entry_map::FxEntryMap::new(
|
||||
|v: &Vertex<Point3>| v.id(),
|
||||
|v: &Vertex<Point3>| {
|
||||
points.push(v.point().into());
|
||||
points.len() as u64 - 1
|
||||
},
|
||||
);
|
||||
let tmp: Vec<_> = self.vertex_iter().collect();
|
||||
tmp.iter().for_each(|v| {
|
||||
vmap.entry_or_insert(v);
|
||||
});
|
||||
let vmap: HashMap<_, _> = vmap.into();
|
||||
self.edge_iter().for_each(|edge| {
|
||||
let pts = edge.oriented_curve().0;
|
||||
connectivity.push(*vmap.get(&edge.front().id()).unwrap());
|
||||
connectivity.extend((1..pts.len() - 1).map(|i| (points.len() + i - 1) as u64));
|
||||
connectivity.push(*vmap.get(&edge.back().id()).unwrap());
|
||||
offsets.push(connectivity.len() as u64);
|
||||
points.extend(
|
||||
pts[1..pts.len() - 1]
|
||||
.iter()
|
||||
.copied()
|
||||
.map(Into::<[f64; 3]>::into),
|
||||
);
|
||||
types.push(CellType::PolyLine);
|
||||
});
|
||||
self.vertex_iter().enumerate().for_each(|(idx, _)| {
|
||||
connectivity.push(idx as u64);
|
||||
offsets.push(connectivity.len() as u64);
|
||||
types.push(CellType::Vertex);
|
||||
});
|
||||
DataSet::UnstructuredGrid {
|
||||
meta: None,
|
||||
pieces: vec![Piece::Inline(Box::new(UnstructuredGridPiece {
|
||||
points: IOBuffer::F64(points.concat().to_vec()),
|
||||
cells: Cells {
|
||||
cell_verts: VertexNumbers::XML {
|
||||
connectivity,
|
||||
offsets,
|
||||
},
|
||||
types,
|
||||
},
|
||||
data: Default::default(),
|
||||
}))],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn face_piece(
|
||||
face: &Face<Point3, PolylineCurve<Point3>, PolygonMesh>,
|
||||
) -> Piece<UnstructuredGridPiece> {
|
||||
let polygon = face.oriented_surface().expands(identity);
|
||||
let mut length = 0;
|
||||
let map: HashMap<Option<[i64; 3]>, usize> = polygon
|
||||
.attributes()
|
||||
.iter()
|
||||
.map(move |attr| {
|
||||
length += 1;
|
||||
(hash_point(attr.position), length - 1)
|
||||
})
|
||||
.collect();
|
||||
let flatten_points = polygon
|
||||
.attributes()
|
||||
.iter()
|
||||
.flat_map(|attr| Into::<[f64; 3]>::into(attr.position))
|
||||
.collect::<Vec<_>>();
|
||||
let flatten_uvs = polygon
|
||||
.attributes()
|
||||
.iter()
|
||||
.flat_map(|attr| match attr.uv_coord {
|
||||
Some(uv) => Into::<[f64; 2]>::into(uv),
|
||||
None => [f64::NAN; 2],
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let uvs = Attribute::DataArray(DataArray {
|
||||
name: "TCoords".to_owned(),
|
||||
elem: ElementType::TCoords(2),
|
||||
data: IOBuffer::F64(flatten_uvs),
|
||||
});
|
||||
let flatten_normals = polygon
|
||||
.attributes()
|
||||
.iter()
|
||||
.flat_map(|attr| match attr.normal {
|
||||
Some(normal) => Into::<[f64; 3]>::into(normal),
|
||||
None => [f64::NAN; 3],
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let normals = Attribute::DataArray(DataArray {
|
||||
name: "Normals".to_owned(),
|
||||
elem: ElementType::Normals,
|
||||
data: IOBuffer::F64(flatten_normals),
|
||||
});
|
||||
let mut cell_verts = to_vertex_numbers(polygon.faces());
|
||||
let mut types = vec![CellType::Polygon; cell_verts.num_cells()];
|
||||
face.boundaries().into_iter().flatten().for_each(|edge| {
|
||||
types.push(CellType::PolyLine);
|
||||
let curve = edge.oriented_curve().0;
|
||||
let polyline = curve
|
||||
.into_iter()
|
||||
.filter_map(|p| map.get(&hash_point(p)).map(|i| *i as u64));
|
||||
if let VertexNumbers::XML {
|
||||
connectivity,
|
||||
offsets,
|
||||
} = &mut cell_verts
|
||||
{
|
||||
connectivity.extend(polyline);
|
||||
offsets.push(connectivity.len() as u64);
|
||||
}
|
||||
});
|
||||
face.vertex_iter().try_for_each(|v| {
|
||||
types.push(CellType::Vertex);
|
||||
let idx = map.get(&hash_point(v.point())).map(|i| *i as u64)?;
|
||||
if let VertexNumbers::XML {
|
||||
connectivity,
|
||||
offsets,
|
||||
} = &mut cell_verts
|
||||
{
|
||||
connectivity.push(idx);
|
||||
offsets.push(connectivity.len() as u64)
|
||||
}
|
||||
Some(())
|
||||
});
|
||||
Piece::Inline(Box::new(UnstructuredGridPiece {
|
||||
points: IOBuffer::F64(flatten_points),
|
||||
cells: Cells { cell_verts, types },
|
||||
data: Attributes {
|
||||
point: vec![uvs, normals],
|
||||
..Default::default()
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
impl ToDataSet for Face<Point3, PolylineCurve<Point3>, PolygonMesh> {
|
||||
fn to_data_set(&self) -> DataSet {
|
||||
DataSet::UnstructuredGrid {
|
||||
meta: None,
|
||||
pieces: vec![face_piece(self)],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToDataSet for Shell<Point3, PolylineCurve<Point3>, PolygonMesh> {
|
||||
fn to_data_set(&self) -> DataSet {
|
||||
DataSet::UnstructuredGrid {
|
||||
meta: None,
|
||||
pieces: self.face_iter().map(face_piece).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn cface_piece(
|
||||
vertices: &[Point3],
|
||||
edges: &[CompressedEdge<PolylineCurve<Point3>>],
|
||||
face: &CompressedFace<PolygonMesh>,
|
||||
) -> Option<Piece<UnstructuredGridPiece>> {
|
||||
let polygon = match face.orientation {
|
||||
true => face.surface.expands(identity),
|
||||
false => face.surface.inverse().expands(identity),
|
||||
};
|
||||
let mut length = 0;
|
||||
let map: HashMap<Option<[i64; 3]>, usize> = polygon
|
||||
.attributes()
|
||||
.iter()
|
||||
.map(move |attr| {
|
||||
length += 1;
|
||||
(hash_point(attr.position), length - 1)
|
||||
})
|
||||
.collect();
|
||||
let flatten_points = polygon
|
||||
.attributes()
|
||||
.iter()
|
||||
.flat_map::<[f64; 3], _>(|attr| attr.position.into())
|
||||
.collect::<Vec<_>>();
|
||||
let flatten_uvs = polygon
|
||||
.attributes()
|
||||
.iter()
|
||||
.flat_map(|attr| match attr.uv_coord {
|
||||
Some(uv) => uv.into(),
|
||||
None => [f64::NAN; 2],
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let uvs = Attribute::DataArray(DataArray {
|
||||
name: "TCoords".to_owned(),
|
||||
elem: ElementType::TCoords(2),
|
||||
data: IOBuffer::F64(flatten_uvs),
|
||||
});
|
||||
let flatten_normals = polygon
|
||||
.attributes()
|
||||
.iter()
|
||||
.flat_map(|attr| match attr.normal {
|
||||
Some(normal) => normal.into(),
|
||||
None => [f64::NAN; 3],
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let normals = Attribute::DataArray(DataArray {
|
||||
name: "Normals".to_owned(),
|
||||
elem: ElementType::Normals,
|
||||
data: IOBuffer::F64(flatten_normals),
|
||||
});
|
||||
let mut cell_verts = to_vertex_numbers(polygon.faces());
|
||||
let mut types = vec![CellType::Polygon; cell_verts.num_cells()];
|
||||
let edge_indices: Vec<_> = face
|
||||
.boundaries
|
||||
.iter()
|
||||
.flat_map::<Box<dyn Iterator<Item = &CompressedEdgeIndex>>, _>(|boundary| {
|
||||
match face.orientation {
|
||||
true => Box::new(boundary.iter()),
|
||||
false => Box::new(boundary.iter().rev()),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
edge_indices.iter().try_for_each(|edge| {
|
||||
let curve = match face.orientation == edge.orientation {
|
||||
true => edges[edge.index].curve.clone(),
|
||||
false => edges[edge.index].curve.inverse(),
|
||||
};
|
||||
let polyline = curve
|
||||
.iter()
|
||||
.filter_map(|p| map.get(&hash_point(*p)).map(|i| *i as u64))
|
||||
.collect::<Vec<_>>();
|
||||
if polyline.is_empty() {
|
||||
return None;
|
||||
}
|
||||
types.push(CellType::PolyLine);
|
||||
if let VertexNumbers::XML {
|
||||
connectivity,
|
||||
offsets,
|
||||
} = &mut cell_verts
|
||||
{
|
||||
connectivity.extend(polyline);
|
||||
offsets.push(connectivity.len() as u64);
|
||||
}
|
||||
Some(())
|
||||
})?;
|
||||
edge_indices.into_iter().try_for_each(|edge| {
|
||||
let v = match face.orientation == edge.orientation {
|
||||
true => edges[edge.index].vertices.0,
|
||||
false => edges[edge.index].vertices.1,
|
||||
};
|
||||
let idx = map.get(&hash_point(vertices[v])).map(|i| *i as u64)?;
|
||||
types.push(CellType::Vertex);
|
||||
if let VertexNumbers::XML {
|
||||
connectivity,
|
||||
offsets,
|
||||
} = &mut cell_verts
|
||||
{
|
||||
connectivity.push(idx);
|
||||
offsets.push(connectivity.len() as u64)
|
||||
}
|
||||
Some(())
|
||||
})?;
|
||||
Some(Piece::Inline(Box::new(UnstructuredGridPiece {
|
||||
points: IOBuffer::F64(flatten_points),
|
||||
cells: Cells { cell_verts, types },
|
||||
data: Attributes {
|
||||
point: vec![uvs, normals],
|
||||
..Default::default()
|
||||
},
|
||||
})))
|
||||
}
|
||||
|
||||
impl ToDataSet for CompressedShell<Point3, PolylineCurve<Point3>, PolygonMesh> {
|
||||
fn to_data_set(&self) -> DataSet {
|
||||
DataSet::UnstructuredGrid {
|
||||
meta: None,
|
||||
pieces: self
|
||||
.faces
|
||||
.iter()
|
||||
.filter_map(|face| cface_piece(&self.vertices, &self.edges, face))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
{"v":1}
|
||||
Vendored
-78
@@ -1,78 +0,0 @@
|
||||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
||||
#
|
||||
# When uploading crates to the registry Cargo will automatically
|
||||
# "normalize" Cargo.toml files for maximal compatibility
|
||||
# with all versions of Cargo and also rewrite `path` dependencies
|
||||
# to registry (e.g., crates.io) dependencies.
|
||||
#
|
||||
# If you are reading this file be aware that the original Cargo.toml
|
||||
# will likely look very different (and much more reasonable).
|
||||
# See Cargo.toml.orig for the original contents.
|
||||
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "truck-shapeops"
|
||||
version = "0.4.0"
|
||||
build = false
|
||||
autobins = false
|
||||
autoexamples = false
|
||||
autotests = false
|
||||
autobenches = false
|
||||
description = "Provides boolean operations to Solid"
|
||||
homepage = "https://github.com/ricosjp/truck"
|
||||
readme = "README.md"
|
||||
license = "Apache-2.0"
|
||||
repository = "https://github.com/ricosjp/truck"
|
||||
|
||||
[lib]
|
||||
name = "truck_shapeops"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[example]]
|
||||
name = "punched-cube-shapeops"
|
||||
path = "examples/punched-cube-shapeops.rs"
|
||||
|
||||
[dependencies.derive_more]
|
||||
version = "0.99.18"
|
||||
|
||||
[dependencies.itertools]
|
||||
version = "0.13.0"
|
||||
|
||||
[dependencies.rustc-hash]
|
||||
version = "2.0.0"
|
||||
|
||||
[dependencies.truck-base]
|
||||
version = "0.5.0"
|
||||
|
||||
[dependencies.truck-geometry]
|
||||
version = "0.5.0"
|
||||
|
||||
[dependencies.truck-geotrait]
|
||||
version = "0.4.0"
|
||||
|
||||
[dependencies.truck-meshalgo]
|
||||
version = "0.4.0"
|
||||
|
||||
[dependencies.truck-stepio]
|
||||
version = "0.3.0"
|
||||
optional = true
|
||||
|
||||
[dependencies.truck-topology]
|
||||
version = "0.6.0"
|
||||
|
||||
[dev-dependencies.rand]
|
||||
version = "0.8.5"
|
||||
|
||||
[dev-dependencies.serde_json]
|
||||
version = "1.0.128"
|
||||
|
||||
[dev-dependencies.truck-modeling]
|
||||
version = "0.6.0"
|
||||
|
||||
[features]
|
||||
default = ["step-test"]
|
||||
step-test = ["truck-stepio"]
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dev-dependencies.getrandom]
|
||||
version = "0.2.15"
|
||||
features = ["js"]
|
||||
Vendored
-177
@@ -1,177 +0,0 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
Vendored
-11
@@ -1,11 +0,0 @@
|
||||
# truck-shapeops
|
||||
|
||||
[](https://crates.io/crates/truck-shapeops) [](https://docs.rs/truck-shapeops)
|
||||
|
||||
Crate for operationg shapes. Provides boolean operations to Solid, and shape healing for importing shapes from other CAD systems.
|
||||
|
||||
## Sample Codes
|
||||
|
||||
### punched-cube-shapeops
|
||||
|
||||
A punched cube generated by shape operator `AND`.
|
||||
@@ -1,30 +0,0 @@
|
||||
//! A punched cube generated by shape operator `AND`.
|
||||
|
||||
use truck_modeling::*;
|
||||
|
||||
fn main() {
|
||||
let v = builder::vertex(Point3::origin());
|
||||
let e = builder::tsweep(&v, Vector3::unit_x());
|
||||
let f = builder::tsweep(&e, Vector3::unit_y());
|
||||
let cube = builder::tsweep(&f, Vector3::unit_z());
|
||||
|
||||
let v = builder::vertex(Point3::new(0.5, 0.25, -0.5));
|
||||
let w = builder::rsweep(&v, Point3::new(0.5, 0.5, 0.0), Vector3::unit_z(), Rad(7.0));
|
||||
let f = builder::try_attach_plane(&[w]).unwrap();
|
||||
let mut cylinder = builder::tsweep(&f, Vector3::unit_z() * 2.0);
|
||||
cylinder.not();
|
||||
let and = truck_shapeops::and(&cube, &cylinder, 0.05).unwrap();
|
||||
and.edge_iter().for_each(|edge| {
|
||||
let mut curve = edge.curve();
|
||||
if let Curve::IntersectionCurve(inter) = &curve {
|
||||
if matches! { inter.leader(), Leader::Polyline(_) } {
|
||||
let flag = curve.to_bspline_leader(0.01, 0.1, 20);
|
||||
println!("{flag}");
|
||||
}
|
||||
}
|
||||
edge.set_curve(curve);
|
||||
});
|
||||
|
||||
let json = serde_json::to_vec_pretty(&and).unwrap();
|
||||
std::fs::write("punched-cube-shapeops.json", json).unwrap();
|
||||
}
|
||||
-182
@@ -1,182 +0,0 @@
|
||||
use truck_meshalgo::prelude::*;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Alternative<T, U> {
|
||||
FirstType(T),
|
||||
SecondType(U),
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! impl_from {
|
||||
($firsttype: ty, $secondtype: ty) => {
|
||||
impl From<$firsttype> for $crate::alternative::Alternative<$firsttype, $secondtype> {
|
||||
#[inline(always)]
|
||||
fn from(t: $firsttype) -> Self { $crate::alternative::Alternative::FirstType(t) }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl<T, U> From<U> for Alternative<T, U> {
|
||||
#[inline(always)]
|
||||
fn from(u: U) -> Self { Alternative::SecondType(u) }
|
||||
}
|
||||
|
||||
// test for impl_from
|
||||
impl_from!((), usize);
|
||||
|
||||
macro_rules! derive_method {
|
||||
($method: tt, $return_type: ty, $($var: ident : $paramtype: ty),*) => {
|
||||
fn $method (&self, $($var: $paramtype),*) -> $return_type {
|
||||
match &self {
|
||||
Alternative::FirstType(got) => got.$method($($var),*),
|
||||
Alternative::SecondType(got) => got.$method($($var),*),
|
||||
}
|
||||
}
|
||||
};
|
||||
($method: tt <$x: ident : $y: path>, $return_type: ty, $($var: ident : $paramtype: ty),*) => {
|
||||
fn $method<$x : $y>(&self, $($var: $paramtype),*) -> $return_type {
|
||||
match &self {
|
||||
Alternative::FirstType(got) => got.$method($($var),*),
|
||||
Alternative::SecondType(got) => got.$method($($var),*),
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl<C0, C1> ParametricCurve for Alternative<C0, C1>
|
||||
where
|
||||
C0: ParametricCurve,
|
||||
C1: ParametricCurve<Point = C0::Point, Vector = C0::Vector>,
|
||||
{
|
||||
type Point = C0::Point;
|
||||
type Vector = C0::Vector;
|
||||
derive_method!(subs, C0::Point, t: f64);
|
||||
derive_method!(der, C0::Vector, t: f64);
|
||||
derive_method!(der2, C0::Vector, t: f64);
|
||||
derive_method!(
|
||||
parameter_range,
|
||||
(std::ops::Bound<f64>, std::ops::Bound<f64>),
|
||||
);
|
||||
}
|
||||
|
||||
impl<C0, C1> BoundedCurve for Alternative<C0, C1>
|
||||
where
|
||||
C0: BoundedCurve,
|
||||
C1: BoundedCurve<Point = C0::Point, Vector = C0::Vector>,
|
||||
{
|
||||
}
|
||||
|
||||
impl<S0, S1> ParametricSurface for Alternative<S0, S1>
|
||||
where
|
||||
S0: ParametricSurface,
|
||||
S1: ParametricSurface<Point = S0::Point, Vector = S0::Vector>,
|
||||
{
|
||||
type Point = S0::Point;
|
||||
type Vector = S0::Vector;
|
||||
derive_method!(subs, S0::Point, u: f64, v: f64);
|
||||
derive_method!(uder, S0::Vector, u: f64, v: f64);
|
||||
derive_method!(vder, S0::Vector, u: f64, v: f64);
|
||||
derive_method!(uuder, S0::Vector, u: f64, v: f64);
|
||||
derive_method!(uvder, S0::Vector, u: f64, v: f64);
|
||||
derive_method!(vvder, S0::Vector, u: f64, v: f64);
|
||||
}
|
||||
|
||||
impl<S0, S1> ParametricSurface3D for Alternative<S0, S1>
|
||||
where
|
||||
S0: ParametricSurface3D,
|
||||
S1: ParametricSurface3D,
|
||||
{
|
||||
derive_method!(normal, Vector3, u: f64, v: f64);
|
||||
}
|
||||
|
||||
impl<C0, C1> Cut for Alternative<C0, C1>
|
||||
where
|
||||
C0: Cut,
|
||||
C1: Cut<Point = C0::Point, Vector = C0::Vector>,
|
||||
{
|
||||
fn cut(&mut self, t: f64) -> Self {
|
||||
match self {
|
||||
Self::FirstType(curve) => Self::FirstType(curve.cut(t)),
|
||||
Self::SecondType(curve) => Self::SecondType(curve.cut(t)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C0, C1> ParameterDivision1D for Alternative<C0, C1>
|
||||
where
|
||||
C0: ParameterDivision1D,
|
||||
C1: ParameterDivision1D<Point = C0::Point>,
|
||||
{
|
||||
type Point = C0::Point;
|
||||
derive_method!(
|
||||
parameter_division,
|
||||
(Vec<f64>, Vec<C0::Point>),
|
||||
range: (f64, f64),
|
||||
tol: f64
|
||||
);
|
||||
}
|
||||
|
||||
impl<S0, S1> ParameterDivision2D for Alternative<S0, S1>
|
||||
where
|
||||
S0: ParameterDivision2D,
|
||||
S1: ParameterDivision2D,
|
||||
{
|
||||
derive_method!(
|
||||
parameter_division,
|
||||
(Vec<f64>, Vec<f64>),
|
||||
range: ((f64, f64), (f64, f64)),
|
||||
tol: f64
|
||||
);
|
||||
}
|
||||
|
||||
impl<D: SPDimension, T, U> SearchParameter<D> for Alternative<T, U>
|
||||
where
|
||||
T: SearchParameter<D>,
|
||||
U: SearchParameter<D, Point = T::Point>,
|
||||
{
|
||||
type Point = T::Point;
|
||||
derive_method!(
|
||||
search_parameter<H: Into<D::Hint>>,
|
||||
Option<D::Parameter>,
|
||||
point: T::Point,
|
||||
hint: H,
|
||||
trials: usize
|
||||
);
|
||||
}
|
||||
|
||||
impl<D: SPDimension, T, U> SearchNearestParameter<D> for Alternative<T, U>
|
||||
where
|
||||
T: SearchNearestParameter<D>,
|
||||
U: SearchNearestParameter<D, Point = T::Point>,
|
||||
{
|
||||
type Point = T::Point;
|
||||
derive_method!(
|
||||
search_nearest_parameter<H: Into<D::Hint>>,
|
||||
Option<D::Parameter>,
|
||||
point: T::Point,
|
||||
hint: H,
|
||||
trials: usize
|
||||
);
|
||||
}
|
||||
|
||||
impl<T, U> Invertible for Alternative<T, U>
|
||||
where
|
||||
T: Invertible,
|
||||
U: Invertible,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn invert(&mut self) {
|
||||
match self {
|
||||
Self::FirstType(entity) => entity.invert(),
|
||||
Self::SecondType(entity) => entity.invert(),
|
||||
}
|
||||
}
|
||||
#[inline(always)]
|
||||
fn inverse(&self) -> Self {
|
||||
match self {
|
||||
Self::FirstType(entity) => Self::FirstType(entity.inverse()),
|
||||
Self::SecondType(entity) => Self::SecondType(entity.inverse()),
|
||||
}
|
||||
}
|
||||
}
|
||||
-132
@@ -1,132 +0,0 @@
|
||||
use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
|
||||
use truck_geometry::prelude::*;
|
||||
use truck_meshalgo::rexport_polymesh::*;
|
||||
use truck_topology::compress::*;
|
||||
|
||||
type Edge<C> = CompressedEdge<C>;
|
||||
type EdgeIndex = CompressedEdgeIndex;
|
||||
type Wire = Vec<EdgeIndex>;
|
||||
type Face<S> = CompressedFace<S>;
|
||||
type Shell<P, C, S> = CompressedShell<P, C, S>;
|
||||
|
||||
trait SP<S>: Fn(&S, Point3, Option<(f64, f64)>) -> Option<(f64, f64)> {}
|
||||
impl<S, F> SP<S> for F where F: Fn(&S, Point3, Option<(f64, f64)>) -> Option<(f64, f64)> {}
|
||||
|
||||
mod split_closed_edges;
|
||||
use split_closed_edges::split_closed_edges;
|
||||
|
||||
mod split_closed_faces;
|
||||
use split_closed_faces::split_closed_faces;
|
||||
|
||||
/// Splits closed edges and faces
|
||||
///
|
||||
/// # Details
|
||||
/// The topology of the shapes handled by truck has the following rules
|
||||
/// - The endpoints of the edges must be different.
|
||||
/// - The boundaries of the faces must be a simple wire.
|
||||
///
|
||||
/// Shapes created in other CAD systems do not necessarily follow these rules.
|
||||
/// When such shapes are handled by truck, this method is applied at the stage
|
||||
/// of `CompressedShell` and `CompressedSolid`, which are intermediate forms.
|
||||
///
|
||||
/// # Remarks
|
||||
/// Boundary simplification is still only implemented for cylinders.
|
||||
/// It has not yet been implemented for cases involving singularities, such as spherical surfaces.
|
||||
pub trait SplitClosedEdgesAndFaces {
|
||||
/// Splits closed edges and faces
|
||||
fn split_closed_edges_and_faces(&mut self, tol: f64);
|
||||
}
|
||||
|
||||
impl<C, S> SplitClosedEdgesAndFaces for CompressedShell<Point3, C, S>
|
||||
where
|
||||
C: ParametricCurve3D
|
||||
+ BoundedCurve
|
||||
+ Cut
|
||||
+ ParameterDivision1D<Point = Point3>
|
||||
+ SearchNearestParameter<D1, Point = Point3>
|
||||
+ TryFrom<PCurve<Line<Point2>, S>>,
|
||||
S: ParametricSurface3D + SearchParameter<D2, Point = Point3>,
|
||||
{
|
||||
fn split_closed_edges_and_faces(&mut self, tol: f64) {
|
||||
fn sp<S>(surface: &S, point: Point3, hint: Option<(f64, f64)>) -> Option<(f64, f64)>
|
||||
where S: SearchParameter<D2, Point = Point3> {
|
||||
surface.search_parameter(point, hint, 100)
|
||||
}
|
||||
split_closed_edges(self);
|
||||
split_closed_faces(self, tol, sp);
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, S> SplitClosedEdgesAndFaces for CompressedSolid<Point3, C, S>
|
||||
where
|
||||
C: ParametricCurve3D
|
||||
+ BoundedCurve
|
||||
+ Cut
|
||||
+ ParameterDivision1D<Point = Point3>
|
||||
+ SearchNearestParameter<D1, Point = Point3>
|
||||
+ TryFrom<PCurve<Line<Point2>, S>>,
|
||||
S: ParametricSurface3D + SearchParameter<D2, Point = Point3>,
|
||||
{
|
||||
fn split_closed_edges_and_faces(&mut self, tol: f64) {
|
||||
self.boundaries
|
||||
.iter_mut()
|
||||
.for_each(|shell| shell.split_closed_edges_and_faces(tol))
|
||||
}
|
||||
}
|
||||
|
||||
/// robust version of splitting closed edges and faces.
|
||||
///
|
||||
/// # Details
|
||||
/// Robust version of [`SplitClosedEdgesAndFaces`] based on [`SearchNearestParameter`].
|
||||
pub trait RobustSplitClosedEdgesAndFaces {
|
||||
/// Splits closed edges and faces
|
||||
fn robust_split_closed_edges_and_faces(&mut self, tol: f64);
|
||||
}
|
||||
|
||||
impl<C, S> RobustSplitClosedEdgesAndFaces for CompressedShell<Point3, C, S>
|
||||
where
|
||||
C: ParametricCurve3D
|
||||
+ BoundedCurve
|
||||
+ Cut
|
||||
+ ParameterDivision1D<Point = Point3>
|
||||
+ SearchNearestParameter<D1, Point = Point3>
|
||||
+ TryFrom<PCurve<Line<Point2>, S>>,
|
||||
S: ParametricSurface3D
|
||||
+ SearchParameter<D2, Point = Point3>
|
||||
+ SearchNearestParameter<D2, Point = Point3>,
|
||||
{
|
||||
fn robust_split_closed_edges_and_faces(&mut self, tol: f64) {
|
||||
fn sp<S>(surface: &S, point: Point3, hint: Option<(f64, f64)>) -> Option<(f64, f64)>
|
||||
where S: SearchParameter<D2, Point = Point3> + SearchNearestParameter<D2, Point = Point3>
|
||||
{
|
||||
surface
|
||||
.search_parameter(point, hint, 100)
|
||||
.or_else(|| surface.search_parameter(point, None, 100))
|
||||
.or_else(|| surface.search_nearest_parameter(point, hint, 100))
|
||||
.or_else(|| surface.search_nearest_parameter(point, None, 100))
|
||||
}
|
||||
split_closed_edges(self);
|
||||
split_closed_faces(self, tol, sp);
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, S> RobustSplitClosedEdgesAndFaces for CompressedSolid<Point3, C, S>
|
||||
where
|
||||
C: ParametricCurve3D
|
||||
+ BoundedCurve
|
||||
+ Cut
|
||||
+ ParameterDivision1D<Point = Point3>
|
||||
+ SearchNearestParameter<D1, Point = Point3>
|
||||
+ TryFrom<PCurve<Line<Point2>, S>>,
|
||||
S: ParametricSurface3D
|
||||
+ SearchParameter<D2, Point = Point3>
|
||||
+ SearchNearestParameter<D2, Point = Point3>,
|
||||
{
|
||||
fn robust_split_closed_edges_and_faces(&mut self, tol: f64) {
|
||||
let fs = RobustSplitClosedEdgesAndFaces::robust_split_closed_edges_and_faces;
|
||||
self.boundaries.iter_mut().for_each(|shell| fs(shell, tol))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@@ -1,68 +0,0 @@
|
||||
use super::*;
|
||||
|
||||
/// Cuts the edge at the middle point of the bounded curve `curve`.
|
||||
fn cut_edge<P, C>(
|
||||
vertices: &mut Vec<P>,
|
||||
Edge {
|
||||
vertices: (_, v1),
|
||||
curve,
|
||||
}: &mut Edge<C>,
|
||||
) -> Edge<C>
|
||||
where
|
||||
C: BoundedCurve<Point = P> + Cut,
|
||||
{
|
||||
let (t0, t1) = curve.range_tuple();
|
||||
let t = (t0 + t1) / 2.0;
|
||||
vertices.push(curve.subs(t));
|
||||
|
||||
let v2 = *v1;
|
||||
*v1 = vertices.len() - 1;
|
||||
let v1 = *v1;
|
||||
let curve0 = curve.cut(t);
|
||||
Edge {
|
||||
vertices: (v1, v2),
|
||||
curve: curve0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds cut edges to the vector `edges`.
|
||||
fn add_edges<P, C>(vertices: &mut Vec<P>, edges: &mut Vec<Edge<C>>) -> HashMap<usize, usize>
|
||||
where C: BoundedCurve<Point = P> + Cut {
|
||||
let len = edges.len();
|
||||
let sub_add_edges = move |i: usize| {
|
||||
let (v0, v1) = edges[i].vertices;
|
||||
if v0 == v1 {
|
||||
let new_edge = cut_edge(vertices, &mut edges[i]);
|
||||
edges.push(new_edge);
|
||||
Some((i, edges.len() - 1))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
(0..len).filter_map(sub_add_edges).collect()
|
||||
}
|
||||
|
||||
fn replace_edges(wire: &mut Wire, added: &HashMap<usize, usize>) {
|
||||
let insert_one_edge = |edge: EdgeIndex| {
|
||||
let Some(new_edge_index) = added.get(&edge.index) else {
|
||||
return vec![edge];
|
||||
};
|
||||
let new_edge = EdgeIndex {
|
||||
index: *new_edge_index,
|
||||
orientation: edge.orientation,
|
||||
};
|
||||
match edge.orientation {
|
||||
true => vec![edge, new_edge],
|
||||
false => vec![new_edge, edge],
|
||||
}
|
||||
};
|
||||
let new_wire = wire.iter().copied().flat_map(insert_one_edge).collect();
|
||||
*wire = new_wire;
|
||||
}
|
||||
|
||||
pub(super) fn split_closed_edges<P, C, S>(shell: &mut Shell<P, C, S>)
|
||||
where C: BoundedCurve<Point = P> + Cut {
|
||||
let added = add_edges(&mut shell.vertices, &mut shell.edges);
|
||||
let wire_iter = shell.faces.iter_mut().flat_map(|face| &mut face.boundaries);
|
||||
wire_iter.for_each(|wire| replace_edges(wire, &added));
|
||||
}
|
||||
@@ -1,975 +0,0 @@
|
||||
use super::*;
|
||||
use std::ops::Range;
|
||||
|
||||
pub(super) fn split_closed_faces<C, S>(shell: &mut Shell<Point3, C, S>, tol: f64, sp: impl SP<S>)
|
||||
where
|
||||
C: ParametricCurve3D
|
||||
+ BoundedCurve
|
||||
+ Cut
|
||||
+ ParameterDivision1D<Point = Point3>
|
||||
+ SearchNearestParameter<D1, Point = Point3>
|
||||
+ TryFrom<PCurve<Line<Point2>, S>>,
|
||||
S: ParametricSurface3D, {
|
||||
let to_poly = closure_to_poly(tol);
|
||||
let mut poly_edges: Vec<_> = shell.edges.iter().map(to_poly).collect();
|
||||
let len = shell.faces.len();
|
||||
(0..len).for_each(|i| {
|
||||
split_face_with_non_closed_boundary(i, shell, &mut poly_edges, &sp, tol);
|
||||
});
|
||||
let len = shell.faces.len();
|
||||
let closure = |i| split_face_with_non_simple_wire(i, shell, &mut poly_edges, &sp, tol);
|
||||
let new_faces: Vec<_> = (0..len).filter_map(closure).flatten().collect();
|
||||
shell.faces.extend(new_faces);
|
||||
}
|
||||
|
||||
fn split_face_with_non_closed_boundary<C, S>(
|
||||
face_index: usize,
|
||||
shell: &mut Shell<Point3, C, S>,
|
||||
poly_edges: &mut Vec<PolylineCurve<Point3>>,
|
||||
sp: impl SP<S>,
|
||||
tol: f64,
|
||||
) -> Option<()>
|
||||
where
|
||||
C: ParametricCurve3D
|
||||
+ BoundedCurve
|
||||
+ Cut
|
||||
+ ParameterDivision1D<Point = Point3>
|
||||
+ SearchNearestParameter<D1, Point = Point3>
|
||||
+ TryFrom<PCurve<Line<Point2>, S>>,
|
||||
S: ParametricSurface3D,
|
||||
{
|
||||
let (divisor, open, closed) =
|
||||
non_closed_wires_in_param_divisor(face_index, shell, poly_edges, &sp)?;
|
||||
debug_assert_eq!(open.len(), 2);
|
||||
take_vertices_to_intersections(divisor, face_index, shell, poly_edges, &sp, tol);
|
||||
let Shell { faces, edges, .. } = shell;
|
||||
let new_boundaries = split_boundaries_by_divisor(
|
||||
&faces[face_index],
|
||||
&closed,
|
||||
divisor,
|
||||
edges,
|
||||
poly_edges,
|
||||
&sp,
|
||||
tol,
|
||||
)?;
|
||||
let boundaries = &mut faces[face_index].boundaries;
|
||||
connect_open_boundaries(boundaries, new_boundaries, divisor, edges, open)
|
||||
}
|
||||
|
||||
fn split_face_with_non_simple_wire<C, S>(
|
||||
face_index: usize,
|
||||
shell: &mut Shell<Point3, C, S>,
|
||||
poly_edges: &mut Vec<PolylineCurve<Point3>>,
|
||||
sp: impl SP<S>,
|
||||
tol: f64,
|
||||
) -> Option<Vec<Face<S>>>
|
||||
where
|
||||
C: ParametricCurve3D
|
||||
+ BoundedCurve
|
||||
+ Cut
|
||||
+ ParameterDivision1D<Point = Point3>
|
||||
+ SearchNearestParameter<D1, Point = Point3>
|
||||
+ TryFrom<PCurve<Line<Point2>, S>>,
|
||||
S: ParametricSurface3D,
|
||||
{
|
||||
let Face { boundaries, .. } = &shell.faces[face_index];
|
||||
let divisor = find_non_simple_wire_divisor(boundaries, &shell.edges, &shell.vertices)?;
|
||||
split_face_by_divisor(face_index, divisor, shell, poly_edges, sp, tol)
|
||||
}
|
||||
|
||||
// --- find_non_simple_wire_divisor ---
|
||||
|
||||
fn find_non_simple_wire_divisor<C>(
|
||||
boundaries: &[Wire],
|
||||
edges: &[Edge<C>],
|
||||
vertices: &[Point3],
|
||||
) -> Option<(usize, usize)> {
|
||||
let closure = |boundary| non_simple_wire_divisor(boundary, edges, vertices);
|
||||
boundaries.iter().cloned().find_map(closure)
|
||||
}
|
||||
|
||||
fn non_simple_wire_divisor<C>(
|
||||
mut boundary: Wire,
|
||||
edges: &[Edge<C>],
|
||||
vertices: &[Point3],
|
||||
) -> Option<(usize, usize)> {
|
||||
let (i0, j0) = boundary.iter().enumerate().find_map(find_loop())?;
|
||||
boundary.rotate_left(i0);
|
||||
let j0 = j0 - i0;
|
||||
|
||||
let (i1, j1) = boundary
|
||||
.iter()
|
||||
.enumerate()
|
||||
.skip(j0 + 1)
|
||||
.find_map(find_loop())
|
||||
.unwrap_or((j0, boundary.len()));
|
||||
|
||||
let (k0, k1) = ((j0 + 1) / 2, (i1 + j1 + 1) / 2);
|
||||
let f = closure_take_front(edges);
|
||||
let pre_divisor = (f(boundary[k0]), f(boundary[k1]));
|
||||
nearest_correction(
|
||||
pre_divisor,
|
||||
&boundary[1..j0],
|
||||
&boundary[i1 + 1..j1],
|
||||
vertices,
|
||||
closure_take_front(edges),
|
||||
)
|
||||
}
|
||||
|
||||
fn find_loop() -> impl FnMut((usize, &EdgeIndex)) -> Option<(usize, usize)> {
|
||||
let mut map = HashMap::<usize, usize>::default();
|
||||
move |(i, edge)| map.insert(edge.index, i).map(move |j| (j, i))
|
||||
}
|
||||
|
||||
// --- find_non_closed_wires_in_param_divisor ---
|
||||
|
||||
type FindNonClosedWiresInParamDivisorResult = Option<((usize, usize), Vec<Wire>, Vec<Wire>)>;
|
||||
fn non_closed_wires_in_param_divisor<C, S>(
|
||||
face_index: usize,
|
||||
Shell {
|
||||
edges,
|
||||
faces,
|
||||
ref vertices,
|
||||
}: &mut Shell<Point3, C, S>,
|
||||
poly_edges: &[PolylineCurve<Point3>],
|
||||
sp: impl SP<S>,
|
||||
) -> FindNonClosedWiresInParamDivisorResult
|
||||
where
|
||||
C: ParametricCurve3D + BoundedCurve + TryFrom<PCurve<Line<Point2>, S>>,
|
||||
S: ParametricSurface3D,
|
||||
{
|
||||
let Face {
|
||||
boundaries,
|
||||
surface,
|
||||
..
|
||||
} = &faces[face_index];
|
||||
let param_boundaries = create_param_boundaries(boundaries, surface, poly_edges, sp)?;
|
||||
let (mut open, mut closed) = (Vec::new(), Vec::new());
|
||||
boundaries
|
||||
.iter()
|
||||
.zip(¶m_boundaries)
|
||||
.for_each(|(boundary, param_boundary)| {
|
||||
let poly = PolylineCurve(param_boundary.iter().flatten().copied().collect());
|
||||
match boundary_type(&poly) {
|
||||
BoundaryType::NotClosed => open.push((boundary, param_boundary)),
|
||||
_ => closed.push((boundary, param_boundary)),
|
||||
}
|
||||
});
|
||||
if open.len() != 2 {
|
||||
return None;
|
||||
}
|
||||
let take_front = &closure_take_front(edges);
|
||||
let find_nearest_to_ends = move |i: usize, u0: f64, up: f64| {
|
||||
move |(edge_index, poly): ZippedEdge<'_>| {
|
||||
let u = poly[0][i];
|
||||
let closure = move |i| {
|
||||
let u = u + i as f64 * up;
|
||||
f64::abs(u - u0)
|
||||
};
|
||||
let dist = (-2..=2)
|
||||
.map(closure)
|
||||
.min_by(|x, y| x.partial_cmp(y).unwrap())
|
||||
.unwrap();
|
||||
(take_front(*edge_index), dist)
|
||||
}
|
||||
};
|
||||
let create_pre_divisor = move |(wire, poly_wire): ZippedWire<'_>| {
|
||||
let (urange, vrange) = surface.try_range_tuple();
|
||||
let (up, vp) = (surface.u_period(), surface.v_period());
|
||||
let p = poly_wire[0][0];
|
||||
let q = *poly_wire.last().unwrap().last().unwrap();
|
||||
let vertices: Vec<(usize, f64)> = if p.x.near(&q.x) {
|
||||
if let (Some(vp), Some((v0, _))) = (vp, vrange) {
|
||||
let closure = find_nearest_to_ends(1, v0, vp);
|
||||
Some(wire.iter().zip(poly_wire).map(closure).collect())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else if p.y.near(&q.y) {
|
||||
if let (Some(up), Some((u0, _))) = (up, urange) {
|
||||
let closure = find_nearest_to_ends(0, u0, up);
|
||||
Some(wire.iter().zip(poly_wire).map(closure).collect())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}?;
|
||||
vertices
|
||||
.into_iter()
|
||||
.min_by(|(_, x), (_, y)| x.partial_cmp(y).unwrap())
|
||||
.map(|(v, _)| v)
|
||||
};
|
||||
let pre_divisor_vec = open
|
||||
.iter()
|
||||
.copied()
|
||||
.map(create_pre_divisor)
|
||||
.collect::<Option<Vec<_>>>()?;
|
||||
let open: Vec<_> = open.into_iter().map(|(w, _)| w.to_vec()).collect();
|
||||
let closed: Vec<_> = closed.into_iter().map(|(w, _)| w.to_vec()).collect();
|
||||
let divisor = nearest_correction(
|
||||
(pre_divisor_vec[0], pre_divisor_vec[1]),
|
||||
&open[0],
|
||||
&open[1],
|
||||
vertices,
|
||||
take_front,
|
||||
)?;
|
||||
Some((divisor, open, closed))
|
||||
}
|
||||
|
||||
// --- split_face_by_divisor ---
|
||||
|
||||
fn split_face_by_divisor<C, S>(
|
||||
face_index: usize,
|
||||
divisor: (usize, usize),
|
||||
shell: &mut Shell<Point3, C, S>,
|
||||
poly_edges: &mut Vec<PolylineCurve<Point3>>,
|
||||
sp: impl SP<S>,
|
||||
tol: f64,
|
||||
) -> Option<Vec<Face<S>>>
|
||||
where
|
||||
C: ParametricCurve3D
|
||||
+ BoundedCurve
|
||||
+ Cut
|
||||
+ ParameterDivision1D<Point = Point3>
|
||||
+ SearchNearestParameter<D1, Point = Point3>
|
||||
+ TryFrom<PCurve<Line<Point2>, S>>,
|
||||
S: ParametricSurface3D,
|
||||
{
|
||||
take_vertices_to_intersections(divisor, face_index, shell, poly_edges, &sp, tol);
|
||||
let face = &shell.faces[face_index];
|
||||
let new_boundaries = split_boundaries_by_divisor(
|
||||
face,
|
||||
&face.boundaries,
|
||||
divisor,
|
||||
&mut shell.edges,
|
||||
poly_edges,
|
||||
&sp,
|
||||
tol,
|
||||
)?;
|
||||
divide_face(
|
||||
&mut shell.faces[face_index],
|
||||
new_boundaries,
|
||||
poly_edges,
|
||||
&sp,
|
||||
)
|
||||
}
|
||||
|
||||
// --- take_vertices_to_intersections ---
|
||||
|
||||
fn take_vertices_to_intersections<C, S>(
|
||||
divisor: (usize, usize),
|
||||
face_index: usize,
|
||||
Shell {
|
||||
vertices,
|
||||
edges,
|
||||
faces,
|
||||
}: &mut Shell<Point3, C, S>,
|
||||
poly_edges: &mut Vec<PolylineCurve<Point3>>,
|
||||
sp: impl SP<S>,
|
||||
tol: f64,
|
||||
) -> Option<()>
|
||||
where
|
||||
C: ParametricCurve3D
|
||||
+ BoundedCurve
|
||||
+ Cut
|
||||
+ SearchNearestParameter<D1, Point = Point3>
|
||||
+ ParameterDivision1D<Point = Point3>,
|
||||
S: ParametricSurface3D,
|
||||
{
|
||||
let Face {
|
||||
ref boundaries,
|
||||
ref surface,
|
||||
..
|
||||
} = faces[face_index];
|
||||
let param_boundaries = create_param_boundaries(boundaries, surface, poly_edges, &sp)?;
|
||||
let (v0, v1) = divisor;
|
||||
let p = zip_boundaries(boundaries, ¶m_boundaries)
|
||||
.find_map(closure_find_vertex_parameter(v0, edges))?;
|
||||
let q = zip_boundaries(boundaries, ¶m_boundaries)
|
||||
.find_map(closure_find_vertex_parameter(v1, edges))?;
|
||||
let Face {
|
||||
ref boundaries,
|
||||
ref surface,
|
||||
..
|
||||
} = faces[face_index];
|
||||
let periods = (surface.u_period(), surface.v_period());
|
||||
let q = periodic_iterator(q, periods)
|
||||
.min_by(|q, r| p.distance2(*q).partial_cmp(&p.distance2(*r)).unwrap())?;
|
||||
let pcurve = PCurve::new(Line(p, q), surface.clone());
|
||||
let cut_edge = |(edge_index, param_edge): ZippedEdge<'_>| {
|
||||
let index = edge_index.index;
|
||||
let vec = enumerate_intersections(&edges[index], param_edge, &pcurve)?;
|
||||
if vec.is_empty() {
|
||||
return Some(None);
|
||||
}
|
||||
let erange = cut_edge_by_intersections(index, vec, edges, poly_edges, vertices, tol);
|
||||
Some(Some((index, erange)))
|
||||
};
|
||||
let new_edges = zip_boundaries(boundaries, ¶m_boundaries)
|
||||
.map(cut_edge)
|
||||
.collect::<Option<Vec<_>>>()?;
|
||||
let insert = |(index, erange): (usize, Range<usize>)| {
|
||||
faces
|
||||
.iter_mut()
|
||||
.flat_map(|face| &mut face.boundaries)
|
||||
.for_each(|wire| insert_new_edges(wire, index, erange.clone()));
|
||||
};
|
||||
new_edges.into_iter().flatten().for_each(insert);
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn closure_find_vertex_parameter<C>(
|
||||
v: usize,
|
||||
edges: &[Edge<C>],
|
||||
) -> impl Fn(ZippedEdge<'_>) -> Option<Point2> + '_ {
|
||||
let take_front = closure_take_front(edges);
|
||||
move |(edge_index, param_edge): ZippedEdge<'_>| match v == take_front(*edge_index) {
|
||||
true => Some(param_edge[0]),
|
||||
false => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn enumerate_intersections<C, S>(
|
||||
edge: &Edge<C>,
|
||||
param_edge: &PolylineCurve<Point2>,
|
||||
pcurve: &PCurve<Line<Point2>, S>,
|
||||
) -> Option<Vec<(f64, Point3)>>
|
||||
where
|
||||
C: ParametricCurve3D + SearchNearestParameter<D1, Point = Point3>,
|
||||
S: ParametricSurface3D,
|
||||
{
|
||||
let rough_intersections = intersections_between_line_polyline(*pcurve.curve(), param_edge);
|
||||
let intersections = exact_intersections(rough_intersections, pcurve, &edge.curve)?;
|
||||
Some(intersections)
|
||||
}
|
||||
|
||||
fn intersections_between_line_polyline(
|
||||
line: Line<Point2>,
|
||||
param_edge: &PolylineCurve<Point2>,
|
||||
) -> Vec<f64> {
|
||||
let filter = |p: &[Point2]| {
|
||||
let (s, t, _) = line.intersection(Line(p[0], p[1]))?;
|
||||
let unit = 0.0..1.0;
|
||||
match unit.contains(&s) && unit.contains(&t) {
|
||||
true => Some(s),
|
||||
false => None,
|
||||
}
|
||||
};
|
||||
let mut vec: Vec<_> = param_edge.windows(2).filter_map(filter).collect();
|
||||
if !vec.is_empty() {
|
||||
let first = *param_edge.first().unwrap();
|
||||
let last = *param_edge.last().unwrap();
|
||||
if line.distance_to_point(first).so_small() {
|
||||
vec.remove(0);
|
||||
}
|
||||
if line.distance_to_point(last).so_small() {
|
||||
vec.pop();
|
||||
}
|
||||
}
|
||||
vec
|
||||
}
|
||||
|
||||
fn exact_intersections<C, S>(
|
||||
naive_intersections: Vec<f64>,
|
||||
pcurve: &PCurve<Line<Point2>, S>,
|
||||
curve: &C,
|
||||
) -> Option<Vec<(f64, Point3)>>
|
||||
where
|
||||
C: ParametricCurve3D + SearchNearestParameter<D1, Point = Point3>,
|
||||
S: ParametricSurface3D,
|
||||
{
|
||||
let closure = |t| {
|
||||
let (_, t, p) = search_intersection(pcurve, curve, t)?;
|
||||
Some((t, p))
|
||||
};
|
||||
let mut res = naive_intersections
|
||||
.into_iter()
|
||||
.map(closure)
|
||||
.collect::<Option<Vec<_>>>()?;
|
||||
res.sort_by(|(s, _), (t, _)| s.partial_cmp(t).unwrap());
|
||||
Some(res)
|
||||
}
|
||||
|
||||
fn search_intersection<C, S>(
|
||||
pcurve: &PCurve<Line<Point2>, S>,
|
||||
curve: &C,
|
||||
mut t0: f64,
|
||||
) -> Option<(f64, f64, Point3)>
|
||||
where
|
||||
C: ParametricCurve3D + SearchNearestParameter<D1, Point = Point3>,
|
||||
S: ParametricSurface3D,
|
||||
{
|
||||
let mut previous0 = t0;
|
||||
let mut previous1 = None;
|
||||
for _ in 0..100 {
|
||||
let p = pcurve.subs(t0);
|
||||
let t1 = curve.search_nearest_parameter(p, previous1, 100)?;
|
||||
let q = curve.subs(t1);
|
||||
t0 = pcurve.search_nearest_parameter(q, t0, 100)?;
|
||||
if let Some(previous1) = previous1 {
|
||||
if previous0.near(&t0) && previous1.near(&t1) {
|
||||
return Some((t0, t1, pcurve.subs(t0)));
|
||||
}
|
||||
}
|
||||
previous0 = t0;
|
||||
previous1 = Some(t1);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn cut_edge_by_intersections<C>(
|
||||
edge_index: usize,
|
||||
intersections: Vec<(f64, Point3)>,
|
||||
edges: &mut Vec<Edge<C>>,
|
||||
poly_edges: &mut Vec<PolylineCurve<Point3>>,
|
||||
vertices: &mut Vec<Point3>,
|
||||
tol: f64,
|
||||
) -> Range<usize>
|
||||
where
|
||||
C: Cut<Point = Point3> + ParameterDivision1D<Point = Point3>,
|
||||
{
|
||||
let edge = &mut edges[edge_index];
|
||||
let vfirst = vertices.len();
|
||||
vertices.extend(intersections.iter().map(|(_, p)| *p));
|
||||
let intersections_iter = intersections.into_iter().enumerate().rev();
|
||||
let closure = |(i, (t, _))| Edge {
|
||||
vertices: (vfirst + i, vfirst + i + 1),
|
||||
curve: edge.curve.cut(t),
|
||||
};
|
||||
let mut new_edges: Vec<_> = intersections_iter.map(closure).collect();
|
||||
let to_poly = closure_to_poly(tol);
|
||||
poly_edges[edge_index] = to_poly(edge);
|
||||
poly_edges.extend(new_edges.iter().map(to_poly));
|
||||
new_edges.last_mut().unwrap().vertices.1 = edge.vertices.1;
|
||||
edge.vertices.1 = vfirst;
|
||||
let efirst = edges.len();
|
||||
edges.extend(new_edges);
|
||||
efirst..edges.len()
|
||||
}
|
||||
|
||||
fn insert_new_edges(wire: &mut Wire, pivot_edge_index: usize, inserted_range: Range<usize>) {
|
||||
let positions = wire
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(
|
||||
|(i, edge_index)| match edge_index.index == pivot_edge_index {
|
||||
true => Some(i),
|
||||
false => None,
|
||||
},
|
||||
)
|
||||
.collect::<Vec<usize>>();
|
||||
positions.into_iter().rev().for_each(|i| {
|
||||
let orientation = wire[i].orientation;
|
||||
let ei = |index| EdgeIndex { index, orientation };
|
||||
if orientation {
|
||||
let tmp_wire = wire.split_off(i + 1);
|
||||
wire.extend(inserted_range.clone().map(ei));
|
||||
wire.extend(tmp_wire);
|
||||
} else {
|
||||
let tmp_wire = wire.split_off(i);
|
||||
wire.extend(inserted_range.clone().rev().map(ei));
|
||||
wire.extend(tmp_wire);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// --- split boundaries ---
|
||||
|
||||
fn split_boundaries_by_divisor<C, S>(
|
||||
Face {
|
||||
boundaries,
|
||||
surface,
|
||||
..
|
||||
}: &Face<S>,
|
||||
closed: &[Wire],
|
||||
divisor: (usize, usize),
|
||||
edges: &mut Vec<Edge<C>>,
|
||||
poly_edges: &mut Vec<PolylineCurve<Point3>>,
|
||||
sp: impl SP<S>,
|
||||
tol: f64,
|
||||
) -> Option<Vec<Wire>>
|
||||
where
|
||||
C: ParametricCurve3D + BoundedCurve + TryFrom<PCurve<Line<Point2>, S>>,
|
||||
S: ParametricSurface3D,
|
||||
{
|
||||
let param_boundaries = create_param_boundaries(boundaries, surface, poly_edges, &sp)?;
|
||||
let param_vertices = create_param_vertices(boundaries, ¶m_boundaries, edges);
|
||||
let mut duplicated_edges = duplicated_edges(boundaries.iter().flatten().map(|ei| ei.index));
|
||||
let vertices_on_divisor = enumerate_vertices_on_divisor(divisor, ¶m_vertices, surface)?;
|
||||
let new_edges = create_new_edges(&vertices_on_divisor, poly_edges, surface, tol)?;
|
||||
let (new_edge_range, new_edge_indices) = signup_new_edges(edges, new_edges);
|
||||
duplicated_edges.extend(new_edge_range);
|
||||
let edge_iter = closed.iter().flatten().copied().chain(new_edge_indices);
|
||||
let vemap = create_vemap(edges, edge_iter);
|
||||
Some(construct_boundaries(vemap, edges, &duplicated_edges))
|
||||
}
|
||||
|
||||
// --- create_param_boundaries ---
|
||||
|
||||
fn create_param_boundaries<S: ParametricSurface3D>(
|
||||
boundaries: &[Wire],
|
||||
surface: &S,
|
||||
poly_edges: &[PolylineCurve<Point3>],
|
||||
sp: impl SP<S>,
|
||||
) -> Option<Vec<Vec<PolylineCurve<Point2>>>> {
|
||||
let closure = |boundary: &Wire| boundary_to_param_polys(boundary, surface, poly_edges, &sp);
|
||||
boundaries.iter().map(closure).collect()
|
||||
}
|
||||
|
||||
fn boundary_to_param_polys<S: ParametricSurface3D>(
|
||||
boundary: &Wire,
|
||||
surface: &S,
|
||||
poly_edges: &[PolylineCurve<Point3>],
|
||||
sp: impl SP<S>,
|
||||
) -> Option<Vec<PolylineCurve<Point2>>> {
|
||||
let get_poly = closure_get_poly(poly_edges);
|
||||
let poly_boundary: Vec<_> = boundary.iter().copied().map(get_poly).collect();
|
||||
let poly_wire_iter = PolyWireIter::try_new(&poly_boundary)?;
|
||||
let closure = poly_project_to_uv(surface, sp);
|
||||
let mut long_poly = poly_wire_iter.map(closure).collect::<Option<Vec<_>>>()?;
|
||||
boundary_into_domain(&mut long_poly, surface);
|
||||
let mut vec = vec![long_poly.remove(0)];
|
||||
let split_into_2dpoly = move |poly: &PolylineCurve<_>| {
|
||||
let latter = long_poly.split_off(poly.len() - 1);
|
||||
vec.append(&mut long_poly);
|
||||
let mut res = Vec::new();
|
||||
res.append(&mut vec);
|
||||
vec.push(*res.last().unwrap());
|
||||
long_poly = latter;
|
||||
PolylineCurve(res)
|
||||
};
|
||||
Some(poly_boundary.iter().map(split_into_2dpoly).collect())
|
||||
}
|
||||
|
||||
struct PolyWireIter<'a, P> {
|
||||
current: std::slice::Iter<'a, P>,
|
||||
stock: std::vec::IntoIter<std::slice::Iter<'a, P>>,
|
||||
}
|
||||
|
||||
impl<'a, P> PolyWireIter<'a, P> {
|
||||
fn try_new<I: AsRef<[P]> + 'a>(vec: &'a [I]) -> Option<Self> {
|
||||
let iters: Vec<_> = vec.iter().map(|v| v.as_ref().iter()).collect();
|
||||
let mut stock = iters.into_iter();
|
||||
Some(Self {
|
||||
current: stock.next()?,
|
||||
stock,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, P: Copy> Iterator for PolyWireIter<'a, P> {
|
||||
type Item = P;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if let Some(next) = self.current.next() {
|
||||
Some(*next)
|
||||
} else {
|
||||
self.current = self.stock.next()?;
|
||||
self.current.next()?;
|
||||
self.current.next().cloned()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn poly_project_to_uv<'a, S: ParametricSurface3D>(
|
||||
surface: &'a S,
|
||||
sp: impl SP<S> + 'a,
|
||||
) -> impl FnMut(Point3) -> Option<Point2> + 'a {
|
||||
let (up, vp) = (surface.u_period(), surface.v_period());
|
||||
let mut previous = None;
|
||||
move |pt| {
|
||||
let (mut u, mut v) = sp(surface, pt, previous)?;
|
||||
if let (Some(up), Some((u0, _))) = (up, previous) {
|
||||
u = get_mindiff(u, u0, up);
|
||||
}
|
||||
if let (Some(vp), Some((_, v0))) = (vp, previous) {
|
||||
v = get_mindiff(v, v0, vp);
|
||||
}
|
||||
previous = Some((u, v));
|
||||
Some(Point2::new(u, v))
|
||||
}
|
||||
}
|
||||
|
||||
fn abs_diff(previous: f64) -> impl Fn(&f64, &f64) -> std::cmp::Ordering {
|
||||
let f = move |x: &f64| f64::abs(x - previous);
|
||||
move |x: &f64, y: &f64| f(x).partial_cmp(&f(y)).unwrap()
|
||||
}
|
||||
fn get_mindiff(u: f64, u0: f64, up: f64) -> f64 {
|
||||
let closure = |i| u + i as f64 * up;
|
||||
(-2..=2).map(closure).min_by(abs_diff(u0)).unwrap()
|
||||
}
|
||||
|
||||
fn boundary_into_domain<S: ParametricSurface3D>(vec: &mut Vec<Point2>, surface: &S) {
|
||||
let (up, vp) = (surface.u_period(), surface.v_period());
|
||||
let (urange, vrange) = surface.try_range_tuple();
|
||||
let grav = vec.iter().fold(Point2::origin(), |g, p| g + p.to_vec()) / vec.len() as f64;
|
||||
if let (Some(up), Some((u0, _))) = (up, urange) {
|
||||
let quot = f64::floor((grav.x - u0) / up);
|
||||
vec.iter_mut().for_each(|p| p.x -= quot * up);
|
||||
}
|
||||
if let (Some(vp), Some((v0, _))) = (vp, vrange) {
|
||||
let quot = f64::floor((grav.y - v0) / vp);
|
||||
vec.iter_mut().for_each(|p| p.y -= quot * vp);
|
||||
}
|
||||
let last = *vec.last().unwrap();
|
||||
if !vec[0].near(&last) {
|
||||
let Point2 { x: u0, y: v0 } = last;
|
||||
if surface.uder(u0, v0).so_small() || surface.vder(u0, v0).so_small() {
|
||||
vec.push(vec[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- create_param_vertices ---
|
||||
|
||||
fn create_param_vertices<C>(
|
||||
boundaries: &[Wire],
|
||||
param_boundaries: &[Vec<PolylineCurve<Point2>>],
|
||||
edges: &[Edge<C>],
|
||||
) -> HashMap<usize, Point2> {
|
||||
let take_front = closure_take_front(edges);
|
||||
zip_boundaries(boundaries, param_boundaries)
|
||||
.map(|(edge_index, param_edge)| (take_front(*edge_index), param_edge[0]))
|
||||
.collect()
|
||||
}
|
||||
|
||||
// --- duplicated_edges ---
|
||||
|
||||
fn duplicated_edges(edges: impl Iterator<Item = usize>) -> HashSet<usize> {
|
||||
let mut done = HashSet::default();
|
||||
edges.filter(move |index| !done.insert(*index)).collect()
|
||||
}
|
||||
|
||||
// --- enumerate_vertices_on_divisor ---
|
||||
|
||||
fn enumerate_vertices_on_divisor<S: ParametricSurface>(
|
||||
divisor: (usize, usize),
|
||||
param_vertices: &HashMap<usize, Point2>,
|
||||
surface: &S,
|
||||
) -> Option<Vec<(f64, (usize, Point2))>> {
|
||||
let (v0, v1) = divisor;
|
||||
let (p, q) = (*param_vertices.get(&v0)?, *param_vertices.get(&v1)?);
|
||||
let periods = (surface.u_period(), surface.v_period());
|
||||
let q = periodic_iterator(q, periods)
|
||||
.min_by(|q, r| p.distance2(*q).partial_cmp(&p.distance2(*r)).unwrap())?;
|
||||
let line = Line(p, q);
|
||||
let iter = param_vertices.iter().filter_map(move |(v, uv)| {
|
||||
periodic_iterator(*uv, periods)
|
||||
.find(move |uv| line.distance_to_point_as_segment(*uv).so_small())
|
||||
.map(move |uv| Some((line.search_nearest_parameter(uv, None, 1)?, (*v, uv))))
|
||||
});
|
||||
let mut vertices_on_divisor = iter.collect::<Option<Vec<_>>>()?;
|
||||
vertices_on_divisor.sort_by(|(s, _), (t, _)| s.partial_cmp(t).unwrap());
|
||||
Some(vertices_on_divisor)
|
||||
}
|
||||
|
||||
// --- create_new_edges ---
|
||||
|
||||
fn create_new_edges<C, S>(
|
||||
vertices_on_divisor: &[(f64, (usize, Point2))],
|
||||
poly_edges: &mut Vec<PolylineCurve<Point3>>,
|
||||
surface: &S,
|
||||
tol: f64,
|
||||
) -> Option<Vec<Edge<C>>>
|
||||
where
|
||||
C: TryFrom<PCurve<Line<Point2>, S>>,
|
||||
S: ParametricSurface3D,
|
||||
{
|
||||
vertices_on_divisor
|
||||
.iter()
|
||||
.for_each(|(_, (_, uv))| print!("{:?} ", surface.subs(uv.x, uv.y)));
|
||||
println!();
|
||||
let make_edge = move |p: &[(f64, (usize, Point2))]| {
|
||||
let ((_, (v0, uv0)), (_, (v1, uv1))) = (p[0], p[1]);
|
||||
let pcurve = PCurve::new(Line(uv0, uv1), surface.clone());
|
||||
poly_edges.push(PolylineCurve::from_curve(&pcurve, (0.0, 1.0), tol));
|
||||
Some(Edge {
|
||||
vertices: (v0, v1),
|
||||
curve: C::try_from(pcurve).ok()?,
|
||||
})
|
||||
};
|
||||
vertices_on_divisor.chunks(2).map(make_edge).collect()
|
||||
}
|
||||
|
||||
// --- signup new edges ---
|
||||
|
||||
fn signup_new_edges<C>(
|
||||
edges: &mut Vec<Edge<C>>,
|
||||
new_edges: Vec<Edge<C>>,
|
||||
) -> (Range<usize>, impl Iterator<Item = EdgeIndex>) {
|
||||
let len = edges.len();
|
||||
edges.extend(new_edges);
|
||||
let new_edge_indices = (len..edges.len()).flat_map(move |index| {
|
||||
let ei = |orientation: bool| EdgeIndex { index, orientation };
|
||||
[ei(true), ei(false)]
|
||||
});
|
||||
(len..edges.len(), new_edge_indices)
|
||||
}
|
||||
|
||||
// --- create_vemap ---
|
||||
|
||||
fn create_vemap<C>(
|
||||
edges: &[Edge<C>],
|
||||
edge_iter: impl Iterator<Item = EdgeIndex>,
|
||||
) -> HashMap<usize, Vec<EdgeIndex>> {
|
||||
let mut vemap = HashMap::<usize, Vec<EdgeIndex>>::default();
|
||||
let take_front = closure_take_front(edges);
|
||||
edge_iter.for_each(|edge_index| {
|
||||
let v = take_front(edge_index);
|
||||
vemap.entry(v).or_insert_with(Vec::new).push(edge_index);
|
||||
});
|
||||
vemap
|
||||
}
|
||||
|
||||
// --- construct boundaries ---
|
||||
|
||||
fn construct_boundaries<C>(
|
||||
mut vemap: HashMap<usize, Vec<EdgeIndex>>,
|
||||
edges: &[Edge<C>],
|
||||
new_edge_range: &HashSet<usize>,
|
||||
) -> Vec<Wire> {
|
||||
let take_back = closure_take_back(edges);
|
||||
let mut new_boundaries = Vec::new();
|
||||
while !vemap.is_empty() {
|
||||
let (start, vec) = vemap.iter_mut().min_by_key(|(idx, _)| *idx).unwrap();
|
||||
let start = *start;
|
||||
let mut edge_index = vec.pop().unwrap();
|
||||
if vec.is_empty() {
|
||||
vemap.remove(&start);
|
||||
}
|
||||
let mut wire = Vec::new();
|
||||
loop {
|
||||
wire.push(edge_index);
|
||||
let v = take_back(edge_index);
|
||||
if v == start {
|
||||
break;
|
||||
}
|
||||
let Some(vec) = vemap.get_mut(&v) else {
|
||||
unreachable!();
|
||||
};
|
||||
match vec.len() {
|
||||
1 => {
|
||||
edge_index = vec.pop().unwrap();
|
||||
vemap.remove(&v);
|
||||
}
|
||||
2 => {
|
||||
let is_new0 = new_edge_range.contains(&edge_index.index);
|
||||
let is_new1 = new_edge_range.contains(&vec[0].index);
|
||||
let i = if is_new0 != is_new1 { 0 } else { 1 };
|
||||
edge_index = vec.remove(i);
|
||||
}
|
||||
_ => panic!("something wrong!"),
|
||||
}
|
||||
}
|
||||
new_boundaries.push(wire);
|
||||
}
|
||||
new_boundaries
|
||||
}
|
||||
|
||||
// --- divide_face ---
|
||||
|
||||
fn divide_face<S: ParametricSurface3D>(
|
||||
Face {
|
||||
ref mut boundaries,
|
||||
ref surface,
|
||||
ref orientation,
|
||||
}: &mut Face<S>,
|
||||
new_boundaries: Vec<Wire>,
|
||||
poly_edges: &[PolylineCurve<Point3>],
|
||||
sp: impl SP<S>,
|
||||
) -> Option<Vec<Face<S>>> {
|
||||
let mut face_boundaries = assort_boundary(surface, new_boundaries, poly_edges, sp)?.into_iter();
|
||||
*boundaries = face_boundaries.next().unwrap();
|
||||
let create_face = |boundaries| Face {
|
||||
boundaries,
|
||||
surface: surface.clone(),
|
||||
orientation: *orientation,
|
||||
};
|
||||
Some(face_boundaries.map(create_face).collect())
|
||||
}
|
||||
|
||||
fn assort_boundary<S>(
|
||||
surface: &S,
|
||||
boundaries: Vec<Wire>,
|
||||
poly_edges: &[PolylineCurve<Point3>],
|
||||
sp: impl SP<S>,
|
||||
) -> Option<Vec<Vec<Wire>>>
|
||||
where
|
||||
S: ParametricSurface3D,
|
||||
{
|
||||
let (mut positives, mut negatives) = (Vec::new(), Vec::new());
|
||||
let param_boundaries = create_param_boundaries(&boundaries, surface, poly_edges, sp)?;
|
||||
boundaries
|
||||
.into_iter()
|
||||
.zip(param_boundaries)
|
||||
.try_for_each(|(boundary, param_boundary)| {
|
||||
let poly_boundary = PolylineCurve(param_boundary.into_iter().flatten().collect());
|
||||
match boundary_type(&poly_boundary) {
|
||||
BoundaryType::Positive => positives.push((boundary, poly_boundary)),
|
||||
BoundaryType::Negative => negatives.push((boundary, poly_boundary)),
|
||||
_ => return None,
|
||||
}
|
||||
Some(())
|
||||
})?;
|
||||
let mut assorted = positives.into_iter().map(|x| vec![x]).collect::<Vec<_>>();
|
||||
negatives
|
||||
.into_iter()
|
||||
.try_for_each(|(boundary, poly_boundary)| {
|
||||
let p = poly_boundary[0];
|
||||
let vec = assorted.iter_mut().find(|vec| vec[0].1.include(p))?;
|
||||
vec.push((boundary, poly_boundary));
|
||||
Some(())
|
||||
})?;
|
||||
let res = assorted
|
||||
.into_iter()
|
||||
.map(|vec| vec.into_iter().map(|(x, _)| x).collect())
|
||||
.collect();
|
||||
Some(res)
|
||||
}
|
||||
|
||||
// --- connect_open_boundaries ---
|
||||
|
||||
fn connect_open_boundaries<C>(
|
||||
boundaries: &mut Vec<Wire>,
|
||||
mut new_boundaries: Vec<Wire>,
|
||||
divisor: (usize, usize),
|
||||
edges: &[Edge<C>],
|
||||
mut open: Vec<Wire>,
|
||||
) -> Option<()> {
|
||||
let (v0, v1) = divisor;
|
||||
let take_front = closure_take_front(edges);
|
||||
let find_index = move |wire: &Wire, v: usize| {
|
||||
wire.iter()
|
||||
.position(|edge_index| take_front(*edge_index) == v)
|
||||
};
|
||||
let (i, index) = open
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find_map(|(i, wire)| find_index(wire, v0).map(|index| (i, index)))?;
|
||||
if index != 0 {
|
||||
open.swap(0, 1);
|
||||
}
|
||||
let (mut open_v1, mut open_v0) = (open.pop()?, open.pop()?);
|
||||
let j = find_index(&open_v1, v1)?;
|
||||
open_v0.rotate_left(i);
|
||||
open_v1.rotate_left(j);
|
||||
let (k, added_wire) = new_boundaries
|
||||
.iter_mut()
|
||||
.find_map(|wire| find_index(wire, v0).map(|index| (index, wire)))?;
|
||||
added_wire.rotate_left(k);
|
||||
open_v0.append(added_wire);
|
||||
let l = find_index(&open_v0, v1)?;
|
||||
open_v0.rotate_left(l);
|
||||
open_v0.append(&mut open_v1);
|
||||
*added_wire = open_v0;
|
||||
*boundaries = new_boundaries;
|
||||
Some(())
|
||||
}
|
||||
|
||||
// --- common functions ---
|
||||
|
||||
fn closure_take_front<C>(edges: &[Edge<C>]) -> impl Fn(EdgeIndex) -> usize + '_ {
|
||||
move |edge_index: EdgeIndex| {
|
||||
let index = edge_index.index;
|
||||
let (v0, v1) = edges[index].vertices;
|
||||
match edge_index.orientation {
|
||||
true => v0,
|
||||
false => v1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn closure_take_back<C>(edges: &[Edge<C>]) -> impl Fn(EdgeIndex) -> usize + '_ {
|
||||
move |edge_index: EdgeIndex| {
|
||||
let (v0, v1) = edges[edge_index.index].vertices;
|
||||
match edge_index.orientation {
|
||||
true => v1,
|
||||
false => v0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn closure_get_poly<P: Clone>(
|
||||
poly_edges: &[PolylineCurve<P>],
|
||||
) -> impl Fn(EdgeIndex) -> PolylineCurve<P> + '_ {
|
||||
move |edge_index: EdgeIndex| {
|
||||
let poly = &poly_edges[edge_index.index];
|
||||
match edge_index.orientation {
|
||||
true => poly.clone(),
|
||||
false => poly.inverse(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn closure_to_poly<P, C>(tol: f64) -> impl Fn(&Edge<C>) -> PolylineCurve<P> + 'static
|
||||
where C: BoundedCurve + ParameterDivision1D<Point = P> {
|
||||
move |Edge { curve, .. }: &Edge<C>| PolylineCurve::from_curve(curve, curve.range_tuple(), tol)
|
||||
}
|
||||
|
||||
type ZippedEdge<'a> = (&'a EdgeIndex, &'a PolylineCurve<Point2>);
|
||||
type ZippedWire<'a> = (&'a Wire, &'a Vec<PolylineCurve<Point2>>);
|
||||
|
||||
fn zip_boundaries<'a>(
|
||||
boundaries: &'a [Wire],
|
||||
param_boundaries: &'a [Vec<PolylineCurve<Point2>>],
|
||||
) -> impl Iterator<Item = ZippedEdge<'a>> {
|
||||
boundaries
|
||||
.iter()
|
||||
.zip(param_boundaries)
|
||||
.flat_map(move |(b, pb)| b.iter().zip(pb))
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
enum BoundaryType {
|
||||
Positive,
|
||||
Negative,
|
||||
NotClosed,
|
||||
}
|
||||
|
||||
fn boundary_type(poly: &PolylineCurve<Point2>) -> BoundaryType {
|
||||
if poly[0].distance2(poly[poly.len() - 1]) < 1.0e-3 {
|
||||
match poly.area() > 0.0 {
|
||||
true => BoundaryType::Positive,
|
||||
false => BoundaryType::Negative,
|
||||
}
|
||||
} else {
|
||||
BoundaryType::NotClosed
|
||||
}
|
||||
}
|
||||
|
||||
fn periodic_iterator(
|
||||
p: Point2,
|
||||
(up, vp): (Option<f64>, Option<f64>),
|
||||
) -> impl Iterator<Item = Point2> {
|
||||
let up_range = match up {
|
||||
Some(up) => vec![-2.0 * up, -up, 0.0, up, 2.0 * up],
|
||||
None => vec![0.0],
|
||||
};
|
||||
let vp_range = match vp {
|
||||
Some(vp) => vec![-2.0 * vp, -vp, 0.0, vp, 2.0 * vp],
|
||||
None => vec![0.0],
|
||||
};
|
||||
itertools::iproduct!(up_range, vp_range).map(move |(dx, dy)| p + Vector2::new(dx, dy))
|
||||
}
|
||||
|
||||
fn nearest_correction(
|
||||
(v0, _v1): (usize, usize),
|
||||
wire0: &[EdgeIndex],
|
||||
wire1: &[EdgeIndex],
|
||||
vertices: &[Point3],
|
||||
take_front: impl Fn(EdgeIndex) -> usize,
|
||||
) -> Option<(usize, usize)> {
|
||||
let p0 = vertices[v0];
|
||||
let v1 = wire1.iter().copied().map(&take_front).min_by(|v, w| {
|
||||
p0.distance2(vertices[*v])
|
||||
.partial_cmp(&p0.distance2(vertices[*w]))
|
||||
.unwrap()
|
||||
})?;
|
||||
let p1 = vertices[v1];
|
||||
let v0 = wire0.iter().copied().map(&take_front).min_by(|v, w| {
|
||||
p1.distance2(vertices[*v])
|
||||
.partial_cmp(&p1.distance2(vertices[*w]))
|
||||
.unwrap()
|
||||
})?;
|
||||
Some((v0, v1))
|
||||
}
|
||||
-1534
File diff suppressed because it is too large
Load Diff
Vendored
-43
@@ -1,43 +0,0 @@
|
||||
//! Crate for operationg shapes. Provides boolean operations to Solid, and shape healing for importing shapes from other CAD systems.
|
||||
|
||||
// SolidCraft: warnings are not errors in the vendored copy (newer compilers flag more).
|
||||
#![allow(dead_code)]
|
||||
#![warn(clippy::all, rust_2018_idioms)]
|
||||
#![warn(
|
||||
missing_docs,
|
||||
missing_debug_implementations,
|
||||
trivial_casts,
|
||||
trivial_numeric_casts,
|
||||
unsafe_code,
|
||||
unstable_features,
|
||||
unused_import_braces,
|
||||
unused_qualifications
|
||||
)]
|
||||
|
||||
/// SolidCraft: report where an operation gives up (set `SHAPEOPS_TRACE`).
|
||||
macro_rules! tr {
|
||||
($e:expr, $tag:expr) => {
|
||||
match $e {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
if std::env::var_os("SHAPEOPS_TRACE").is_some() {
|
||||
eprintln!("shapeops: gave up at {} ({}:{})", $tag, file!(), line!());
|
||||
}
|
||||
return None;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
pub(crate) use tr;
|
||||
|
||||
mod healing;
|
||||
pub use healing::{RobustSplitClosedEdgesAndFaces, SplitClosedEdgesAndFaces};
|
||||
mod transversal;
|
||||
pub use transversal::{
|
||||
and, and_with_history, and_with_history_meshed, meshes, or, or_with_history, EdgeOrigin, FaceIndex, FaceOrigin,
|
||||
History, MeshedShell, ShapeOpsCurve, ShapeOpsSurface,
|
||||
};
|
||||
// SolidCraft: exercised by kernel regressions in the workspace CI.
|
||||
#[doc(hidden)]
|
||||
pub use transversal::IntersectionCurveWithParameters;
|
||||
mod alternative;
|
||||
@@ -1,263 +0,0 @@
|
||||
#![allow(clippy::many_single_char_names)]
|
||||
|
||||
use super::faces_classification::FacesClassification;
|
||||
use super::history::Lineage;
|
||||
use super::loops_store::*;
|
||||
use rustc_hash::FxHashMap as HashMap;
|
||||
use std::ops::Deref;
|
||||
use truck_meshalgo::prelude::*;
|
||||
use truck_topology::*;
|
||||
|
||||
/// SolidCraft: `p` moved into the surface's parameter range (a point found on the boundary can lie a
|
||||
/// hair outside it, and a search started from there fails).
|
||||
fn clamped<P, S: ParametricSurface<Point = P>>(surface: &S, p: Point2) -> Point2 {
|
||||
match surface.try_range_tuple() {
|
||||
(Some((u0, u1)), Some((v0, v1))) => Point2::new(p.x.clamp(u0, u1), p.y.clamp(v0, v1)),
|
||||
_ => p,
|
||||
}
|
||||
}
|
||||
|
||||
fn create_parameter_boundary<P, C, S>(
|
||||
face: &Face<P, C, S>,
|
||||
wire: &Wire<P, C>,
|
||||
polys: &mut HashMap<EdgeID<C>, PolylineCurve<P>>,
|
||||
tol: f64,
|
||||
lenient: bool,
|
||||
) -> Option<PolylineCurve<Point2>>
|
||||
where
|
||||
P: Copy,
|
||||
C: BoundedCurve<Point = P> + ParameterDivision1D<Point = P>,
|
||||
S: Clone + ParametricSurface<Point = P> + SearchParameter<D2, Point = P> + SearchNearestParameter<D2, Point = P>,
|
||||
{
|
||||
let surface = face.surface();
|
||||
let pt = wire.front_vertex().unwrap().point();
|
||||
// SolidCraft: intersection-curve points lie on the surface only within the tolerance; fall
|
||||
// back to the nearest parameter when the exact search fails.
|
||||
let p: Point2 = surface
|
||||
.search_parameter(pt, None, 100)
|
||||
.or_else(|| surface.search_nearest_parameter(pt, None, 100))?
|
||||
.into();
|
||||
let vec = wire.edge_iter().try_fold(vec![p], |mut vec, edge| {
|
||||
// SolidCraft: an intersection curve with a point that projects onto neither surface
|
||||
// gives this division up (`take_projection_failure`), as its panic did (#5562).
|
||||
if !polys.contains_key(&edge.id()) {
|
||||
let curve = edge.curve();
|
||||
let _ = truck_geometry::prelude::take_projection_failure();
|
||||
let div = curve.parameter_division(curve.range_tuple(), tol).1;
|
||||
if truck_geometry::prelude::take_projection_failure() {
|
||||
return None;
|
||||
}
|
||||
polys.insert(edge.id(), PolylineCurve(div));
|
||||
}
|
||||
let poly = polys.get(&edge.id())?;
|
||||
let mut p = *vec.last().unwrap();
|
||||
let closure = |q: &P| -> Option<Point2> {
|
||||
// SolidCraft: the points of an intersection curve on a lofted or swept surface lie on
|
||||
// the seam between its halves, a hair outside the parameter range, and Newton's
|
||||
// method from the previous point's parameter then fails (or the surface bends away
|
||||
// from it). `lenient`, the second try after the first gave up: start from the hint
|
||||
// moved into the range, and as a last resort search the whole surface.
|
||||
let hint = if lenient { clamped(&surface, p) } else { p };
|
||||
let found = surface
|
||||
.search_parameter(*q, Some(hint.into()), 100)
|
||||
.or_else(|| surface.search_nearest_parameter(*q, Some(hint.into()), 100));
|
||||
let found = if lenient {
|
||||
found
|
||||
.or_else(|| surface.search_parameter(*q, None, 100))
|
||||
.or_else(|| surface.search_nearest_parameter(*q, None, 100))
|
||||
} else {
|
||||
found
|
||||
};
|
||||
p = found?.into();
|
||||
Some(p)
|
||||
};
|
||||
let add: Option<Vec<Point2>> = match edge.orientation() {
|
||||
true => poly.iter().skip(1).map(closure).collect(),
|
||||
false => poly.iter().rev().skip(1).map(closure).collect(),
|
||||
};
|
||||
vec.append(&mut add?);
|
||||
Some(vec)
|
||||
})?;
|
||||
// SolidCraft: a lenient boundary whose points jump half the parameter range or more has been
|
||||
// found across a periodic surface's seam by the whole-surface search (on a cylinder the
|
||||
// points of one edge landed at both ends of its angle): it would divide the face wrongly, so
|
||||
// the face is left undivided as before.
|
||||
if lenient {
|
||||
let (u, v) = surface.try_range_tuple();
|
||||
let half = |r: Option<(f64, f64)>| r.map_or(f64::INFINITY, |(a, b)| (b - a).abs() / 2.0);
|
||||
let (hu, hv) = (half(u), half(v));
|
||||
if vec.windows(2).any(|w| (w[1].x - w[0].x).abs() >= hu || (w[1].y - w[0].y).abs() >= hv) {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some(PolylineCurve(vec))
|
||||
}
|
||||
|
||||
/// SolidCraft: whether a loop's polygon in the parameter plane has no area to speak of: two
|
||||
/// points (a closed loop of two edges divided at their ends only), or an area that is rounding
|
||||
/// beside the polygon's extent. Its sign then tells nothing.
|
||||
fn flat_polygon(poly: &PolylineCurve<Point2>) -> bool {
|
||||
let (mut lo, mut hi) = (Point2::new(f64::INFINITY, f64::INFINITY), Point2::new(f64::NEG_INFINITY, f64::NEG_INFINITY));
|
||||
for p in poly.iter() {
|
||||
lo = Point2::new(lo.x.min(p.x), lo.y.min(p.y));
|
||||
hi = Point2::new(hi.x.max(p.x), hi.y.max(p.y));
|
||||
}
|
||||
let diag2 = (hi.x - lo.x).powi(2) + (hi.y - lo.y).powi(2);
|
||||
poly.len() < 4 || !(poly.area().abs() > 1e-9 * diag2)
|
||||
}
|
||||
|
||||
/// SolidCraft: a loop whose polygon is flat ([`flat_polygon`]) divided again, finer, to the
|
||||
/// loop's own size. A small hole's wire is two half circles; divided at a tolerance that
|
||||
/// follows a much larger body (a few millimetres for a part over a metre long) each half is a
|
||||
/// single chord, the polygon has no area, and its sign, which tells a hole from a face piece,
|
||||
/// comes out at random: the hole became a face of its own, inside out, and the face it was cut
|
||||
/// in was left whole. The polygon only places the loop; the edges are not changed.
|
||||
fn finer_if_flat<C, S>(face: &Face<Point3, C, S>, wire: &Wire<Point3, C>, poly: PolylineCurve<Point2>, tol: f64) -> PolylineCurve<Point2>
|
||||
where
|
||||
C: BoundedCurve<Point = Point3> + ParameterDivision1D<Point = Point3>,
|
||||
S: Clone + ParametricSurface3D + SearchParameter<D2, Point = Point3> + SearchNearestParameter<D2, Point = Point3>,
|
||||
{
|
||||
if !flat_polygon(&poly) {
|
||||
return poly;
|
||||
}
|
||||
// The loop's extent: the diagonal of the box round its edges' ends and middles.
|
||||
let mut pts = Vec::new();
|
||||
for edge in wire.edge_iter() {
|
||||
let curve = edge.curve();
|
||||
let (t0, t1) = curve.range_tuple();
|
||||
pts.extend([curve.subs(t0), curve.subs((t0 + t1) / 2.0), curve.subs(t1)]);
|
||||
}
|
||||
let (mut lo, mut hi) = (Point3::new(f64::INFINITY, f64::INFINITY, f64::INFINITY), Point3::new(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY));
|
||||
for p in &pts {
|
||||
lo = Point3::new(lo.x.min(p.x), lo.y.min(p.y), lo.z.min(p.z));
|
||||
hi = Point3::new(hi.x.max(p.x), hi.y.max(p.y), hi.z.max(p.z));
|
||||
}
|
||||
let size = if pts.is_empty() { 0.0 } else { hi.distance(lo) };
|
||||
let mut fine = tol;
|
||||
for _ in 0..3 {
|
||||
fine = (fine / 8.0).min(size * 0.01);
|
||||
if !(fine > 0.0 && fine.is_finite()) {
|
||||
break;
|
||||
}
|
||||
let mut map = HashMap::default();
|
||||
let again = create_parameter_boundary(face, wire, &mut map, fine, false).or_else(|| create_parameter_boundary(face, wire, &mut map, fine, true));
|
||||
if let Some(again) = again.filter(|p| !flat_polygon(p)) {
|
||||
return again;
|
||||
}
|
||||
}
|
||||
poly
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct WireChunk<'a, C> {
|
||||
poly: PolylineCurve<Point2>,
|
||||
wire: &'a BoundaryWire<Point3, C>,
|
||||
}
|
||||
|
||||
type FaceWithShapesOpStatus<C, S> = (Face<Point3, C, S>, ShapesOpStatus);
|
||||
fn divide_one_face<C, S>(
|
||||
face: &Face<Point3, C, S>,
|
||||
loops: &Loops<Point3, C>,
|
||||
tol: f64,
|
||||
) -> Option<Vec<FaceWithShapesOpStatus<C, S>>>
|
||||
where
|
||||
C: BoundedCurve<Point = Point3> + ParameterDivision1D<Point = Point3>,
|
||||
S: Clone + ParametricSurface3D + SearchParameter<D2, Point = Point3> + SearchNearestParameter<D2, Point = Point3>,
|
||||
{
|
||||
let (mut pre_faces, mut negative_wires) = (Vec::new(), Vec::new());
|
||||
let mut map = HashMap::default();
|
||||
loops.iter().try_for_each(|wire| {
|
||||
let poly = crate::tr!(
|
||||
create_parameter_boundary(face, wire, &mut map, tol, false).or_else(|| create_parameter_boundary(face, wire, &mut map, tol, true)),
|
||||
"parameter boundary"
|
||||
);
|
||||
let poly = finer_if_flat(face, wire, poly, tol);
|
||||
match poly.area() > 0.0 {
|
||||
true => pre_faces.push(vec![WireChunk { poly, wire }]),
|
||||
false => negative_wires.push(WireChunk { poly, wire }),
|
||||
}
|
||||
Some(())
|
||||
})?;
|
||||
if std::env::var_os("SHAPEOPS_TRACE").is_some() {
|
||||
for c in pre_faces.iter().flatten() {
|
||||
eprintln!(" positive loop: area {:.4} pts {} first {:?}", c.poly.area(), c.poly.len(), c.poly.front());
|
||||
}
|
||||
for c in &negative_wires {
|
||||
eprintln!(" negative loop: area {:.4} pts {} first {:?} status {:?}", c.poly.area(), c.poly.len(), c.poly.front(), c.wire.status());
|
||||
}
|
||||
}
|
||||
negative_wires.into_iter().try_for_each(|chunk| {
|
||||
let pt = chunk.poly.front();
|
||||
// SolidCraft: the innermost piece that holds the loop (nested intersection loops, such
|
||||
// as both walls of a tube crossing one face), never the loop's own twin.
|
||||
let hole = -chunk.poly.area();
|
||||
let op = crate::tr!(
|
||||
pre_faces
|
||||
.iter_mut()
|
||||
.filter(|face| face[0].poly.area() > hole * (1.0 + 1e-6) && face[0].poly.include(pt))
|
||||
.min_by(|x, y| x[0].poly.area().total_cmp(&y[0].poly.area())),
|
||||
"loop not inside any face piece"
|
||||
);
|
||||
op.push(chunk);
|
||||
Some(())
|
||||
})?;
|
||||
let vec: Vec<_> = pre_faces
|
||||
.into_iter()
|
||||
.map(|pre_face| -> Option<_> {
|
||||
let surface = face.surface();
|
||||
let op = pre_face
|
||||
.iter()
|
||||
.find(|chunk| chunk.wire.status() != ShapesOpStatus::Unknown);
|
||||
let status = match op {
|
||||
Some(chunk) => chunk.wire.status(),
|
||||
None => ShapesOpStatus::Unknown,
|
||||
};
|
||||
let wires: Vec<Wire<Point3, C>> = pre_face
|
||||
.into_iter()
|
||||
.map(|chunk| chunk.wire.deref().clone())
|
||||
.collect();
|
||||
// SolidCraft: no panics (wasm can't catch them): a wire visiting a vertex twice gives up.
|
||||
let mut new_face = crate::tr!(Face::try_new(wires, surface).ok(), "face of a wire visiting a vertex twice");
|
||||
if !face.orientation() {
|
||||
new_face.invert();
|
||||
}
|
||||
Some((new_face, status))
|
||||
})
|
||||
.collect::<Option<_>>()?;
|
||||
Some(vec)
|
||||
}
|
||||
|
||||
pub fn divide_faces<C, S>(
|
||||
shell: &Shell<Point3, C, S>,
|
||||
loops_store: &LoopsStore<Point3, C>,
|
||||
tol: f64,
|
||||
lineage: &mut Lineage<C, S>,
|
||||
) -> Option<FacesClassification<Point3, C, S>>
|
||||
where
|
||||
C: BoundedCurve<Point = Point3> + ParameterDivision1D<Point = Point3>,
|
||||
S: Clone + ParametricSurface3D + SearchParameter<D2, Point = Point3> + SearchNearestParameter<D2, Point = Point3>,
|
||||
{
|
||||
let mut res = FacesClassification::<Point3, C, S>::default();
|
||||
shell
|
||||
.iter()
|
||||
.zip(loops_store)
|
||||
.try_for_each(|(face, loops)| {
|
||||
if loops
|
||||
.iter()
|
||||
.all(|wire| wire.status() == ShapesOpStatus::Unknown)
|
||||
{
|
||||
res.push(face.clone(), ShapesOpStatus::Unknown);
|
||||
} else {
|
||||
let vec = crate::tr!(divide_one_face(face, loops, tol), "divide one face");
|
||||
vec.into_iter().for_each(|(piece, status)| {
|
||||
lineage.face_piece(face.id(), piece.id());
|
||||
res.push(piece, status)
|
||||
});
|
||||
}
|
||||
Some(())
|
||||
})?;
|
||||
Some(res)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@@ -1,226 +0,0 @@
|
||||
use super::*;
|
||||
use shell::ShellCondition;
|
||||
use truck_geometry::prelude::*;
|
||||
use truck_topology::Vertex;
|
||||
const TOL: f64 = 0.05;
|
||||
|
||||
fn line(v0: &Vertex<Point3>, v1: &Vertex<Point3>) -> Edge<Point3, BSplineCurve<Point3>> {
|
||||
let curve = BSplineCurve::new(KnotVec::bezier_knot(1), vec![v0.point(), v1.point()]);
|
||||
Edge::new(v0, v1, curve)
|
||||
}
|
||||
|
||||
fn parabola(
|
||||
v0: &Vertex<Point3>,
|
||||
v1: &Vertex<Point3>,
|
||||
pt: Point3,
|
||||
) -> Edge<Point3, BSplineCurve<Point3>> {
|
||||
let curve = BSplineCurve::new(KnotVec::bezier_knot(2), vec![v0.point(), pt, v1.point()]);
|
||||
Edge::new(v0, v1, curve)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn divide_plane_test() {
|
||||
let v = Vertex::news([
|
||||
Point3::new(0.0, 0.0, 0.0),
|
||||
Point3::new(0.0, 4.0, 0.0),
|
||||
Point3::new(-1.0, 1.0, 0.0),
|
||||
Point3::new(-1.0, 3.0, 0.0),
|
||||
Point3::new(1.0, 1.0, 0.0),
|
||||
Point3::new(1.0, 3.0, 0.0),
|
||||
]);
|
||||
let edge = vec![
|
||||
parabola(&v[0], &v[1], Point3::new(-4.0, 2.0, 0.0)),
|
||||
parabola(&v[0], &v[1], Point3::new(4.0, 2.0, 0.0)),
|
||||
line(&v[0], &v[1]),
|
||||
parabola(&v[2], &v[3], Point3::new(-3.0, 2.0, 0.0)),
|
||||
parabola(&v[2], &v[3], Point3::new(-1.0, 2.0, 0.0)),
|
||||
parabola(&v[4], &v[5], Point3::new(1.0, 2.0, 0.0)),
|
||||
parabola(&v[4], &v[5], Point3::new(3.0, 2.0, 0.0)),
|
||||
];
|
||||
let wire: Vec<Wire<_, _>> = vec![
|
||||
vec![edge[1].clone(), edge[0].inverse()].into(),
|
||||
vec![edge[2].clone(), edge[0].inverse()].into(),
|
||||
vec![edge[1].clone(), edge[2].inverse()].into(),
|
||||
vec![edge[3].clone(), edge[4].inverse()].into(),
|
||||
vec![edge[5].clone(), edge[6].inverse()].into(),
|
||||
];
|
||||
let face = Face::new(
|
||||
vec![wire[0].clone(), wire[3].clone(), wire[4].clone()],
|
||||
Plane::new(
|
||||
Point3::origin(),
|
||||
Point3::new(1.0, 0.0, 0.0),
|
||||
Point3::new(0.0, 1.0, 0.0),
|
||||
),
|
||||
);
|
||||
let loops: Loops<_, _> = vec![
|
||||
BoundaryWire::new(wire[1].clone(), ShapesOpStatus::Or),
|
||||
BoundaryWire::new(wire[2].clone(), ShapesOpStatus::And),
|
||||
BoundaryWire::new(wire[3].clone(), ShapesOpStatus::Unknown),
|
||||
BoundaryWire::new(wire[4].clone(), ShapesOpStatus::Unknown),
|
||||
]
|
||||
.into_iter()
|
||||
.collect();
|
||||
let res = divide_one_face(&face, &loops, 0.01).unwrap();
|
||||
assert_eq!(res.len(), 2);
|
||||
let (mut or, mut and) = (true, true);
|
||||
for (face, status) in res {
|
||||
let bdd = face.absolute_boundaries();
|
||||
match status {
|
||||
ShapesOpStatus::Or => {
|
||||
assert_eq!(bdd.len(), 2);
|
||||
assert!(bdd[0] == wire[1] || bdd[0] == wire[3]);
|
||||
assert!(bdd[1] == wire[1] || bdd[1] == wire[3]);
|
||||
assert_ne!(bdd[0], bdd[1]);
|
||||
assert!(or);
|
||||
or = false;
|
||||
}
|
||||
ShapesOpStatus::And => {
|
||||
assert_eq!(bdd.len(), 2);
|
||||
assert!(bdd[0] == wire[2] || bdd[0] == wire[4]);
|
||||
assert!(bdd[1] == wire[2] || bdd[1] == wire[4]);
|
||||
assert_ne!(bdd[0], bdd[1]);
|
||||
assert!(and);
|
||||
and = false;
|
||||
}
|
||||
_ => panic!("There must be no unknown!"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type AlternativeIntersection = crate::alternative::Alternative<
|
||||
NurbsCurve<Vector4>,
|
||||
IntersectionCurve<PolylineCurve<Point3>, AlternativeSurface>,
|
||||
>;
|
||||
type AlternativeSurface = crate::alternative::Alternative<BSplineSurface<Point3>, Plane>;
|
||||
|
||||
crate::impl_from!(
|
||||
NurbsCurve<Vector4>,
|
||||
IntersectionCurve<PolylineCurve<Point3>, AlternativeSurface>
|
||||
);
|
||||
crate::impl_from!(BSplineSurface<Point3>, Plane);
|
||||
|
||||
fn parabola_surfaces() -> (AlternativeSurface, AlternativeSurface) {
|
||||
// define surfaces
|
||||
#[rustfmt::skip]
|
||||
let ctrl0 = vec![
|
||||
vec![Point3::new(-1.0, -1.0, 3.0), Point3::new(-1.0, 0.0, -1.0), Point3::new(-1.0, 1.0, 3.0)],
|
||||
vec![Point3::new(0.0, -1.0, -1.0), Point3::new(0.0, 0.0, -5.0), Point3::new(0.0, 1.0, -1.0)],
|
||||
vec![Point3::new(1.0, -1.0, 3.0), Point3::new(1.0, 0.0, -1.0), Point3::new(1.0, 1.0, 3.0)],
|
||||
];
|
||||
#[rustfmt::skip]
|
||||
let ctrl1 = vec![
|
||||
vec![Point3::new(-1.0, -1.0, -3.0), Point3::new(-1.0, 0.0, 1.0), Point3::new(-1.0, 1.0, -3.0)],
|
||||
vec![Point3::new(0.0, -1.0, 1.0), Point3::new(0.0, 0.0, 5.0), Point3::new(0.0, 1.0, 1.0)],
|
||||
vec![Point3::new(1.0, -1.0, -3.0), Point3::new(1.0, 0.0, 1.0), Point3::new(1.0, 1.0, -3.0)],
|
||||
];
|
||||
(
|
||||
BSplineSurface::new((KnotVec::bezier_knot(2), KnotVec::bezier_knot(2)), ctrl0).into(),
|
||||
BSplineSurface::new((KnotVec::bezier_knot(2), KnotVec::bezier_knot(2)), ctrl1).into(),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn independent_intersection() {
|
||||
// prepare geoetries
|
||||
let arc00: AlternativeIntersection = NurbsCurve::new(BSplineCurve::new(
|
||||
KnotVec::bezier_knot(2),
|
||||
vec![
|
||||
Vector4::new(1.0, 0.0, 1.0, 1.0),
|
||||
Vector4::new(0.0, 1.0, 0.0, 0.0),
|
||||
Vector4::new(-1.0, 0.0, 1.0, 1.0),
|
||||
],
|
||||
))
|
||||
.into();
|
||||
let arc01: AlternativeIntersection = NurbsCurve::new(BSplineCurve::new(
|
||||
KnotVec::bezier_knot(2),
|
||||
vec![
|
||||
Vector4::new(-1.0, 0.0, 1.0, 1.0),
|
||||
Vector4::new(0.0, -1.0, 0.0, 0.0),
|
||||
Vector4::new(1.0, 0.0, 1.0, 1.0),
|
||||
],
|
||||
))
|
||||
.into();
|
||||
let arc10: AlternativeIntersection = NurbsCurve::new(BSplineCurve::new(
|
||||
KnotVec::bezier_knot(2),
|
||||
vec![
|
||||
Vector4::new(1.0, 0.0, -1.0, 1.0),
|
||||
Vector4::new(0.0, 1.0, 0.0, 0.0),
|
||||
Vector4::new(-1.0, 0.0, -1.0, 1.0),
|
||||
],
|
||||
))
|
||||
.into();
|
||||
let arc11: AlternativeIntersection = NurbsCurve::new(BSplineCurve::new(
|
||||
KnotVec::bezier_knot(2),
|
||||
vec![
|
||||
Vector4::new(-1.0, 0.0, -1.0, 1.0),
|
||||
Vector4::new(0.0, -1.0, 0.0, 0.0),
|
||||
Vector4::new(1.0, 0.0, -1.0, 1.0),
|
||||
],
|
||||
))
|
||||
.into();
|
||||
let (surface0, surface1) = parabola_surfaces();
|
||||
let plane0: AlternativeSurface = Plane::new(
|
||||
Point3::new(0.0, 0.0, 1.0),
|
||||
Point3::new(1.0, 0.0, 1.0),
|
||||
Point3::new(0.0, 1.0, 1.0),
|
||||
)
|
||||
.into();
|
||||
let plane1: AlternativeSurface = Plane::new(
|
||||
Point3::new(0.0, 0.0, -1.0),
|
||||
Point3::new(1.0, 0.0, -1.0),
|
||||
Point3::new(0.0, 1.0, -1.0),
|
||||
)
|
||||
.into();
|
||||
|
||||
// prepare topologies
|
||||
let v00 = Vertex::new(Point3::new(1.0, 0.0, 1.0));
|
||||
let v01 = Vertex::new(Point3::new(-1.0, 0.0, 1.0));
|
||||
let v10 = Vertex::new(Point3::new(1.0, 0.0, -1.0));
|
||||
let v11 = Vertex::new(Point3::new(-1.0, 0.0, -1.0));
|
||||
let wire0: Wire<_, _> = vec![Edge::new(&v00, &v01, arc00), Edge::new(&v01, &v00, arc01)].into();
|
||||
let wire1: Wire<_, _> = vec![Edge::new(&v10, &v11, arc10), Edge::new(&v11, &v10, arc11)].into();
|
||||
let shell0: Shell<_, _, _> = vec![
|
||||
Face::new(vec![wire0.clone()], plane0),
|
||||
Face::new(vec![wire0], surface0).inverse(),
|
||||
]
|
||||
.into();
|
||||
assert_eq!(shell0.shell_condition(), ShellCondition::Closed);
|
||||
let shell1: Shell<_, _, _> = vec![
|
||||
Face::new(vec![wire1.clone()], plane1).inverse(),
|
||||
Face::new(vec![wire1], surface1),
|
||||
]
|
||||
.into();
|
||||
assert_eq!(shell1.shell_condition(), ShellCondition::Closed);
|
||||
let poly_shell0 = shell0.triangulation(TOL);
|
||||
let poly_shell1 = shell1.triangulation(TOL);
|
||||
|
||||
let LoopsStoreQuadruple {
|
||||
geom_loops_store0: loops_store0,
|
||||
geom_loops_store1: loops_store1,
|
||||
..
|
||||
} = create_loops_stores(&shell0, &poly_shell0, &shell1, &poly_shell1, TOL, &mut Default::default()).unwrap();
|
||||
let [and0, or0, unknown0] = divide_faces(&shell0, &loops_store0, TOL, &mut Default::default())
|
||||
.unwrap()
|
||||
.and_or_unknown();
|
||||
let [and1, or1, unknown1] = divide_faces(&shell1, &loops_store1, TOL, &mut Default::default())
|
||||
.unwrap()
|
||||
.and_or_unknown();
|
||||
assert_eq!(and0.len(), 1);
|
||||
assert_eq!(or0.len(), 1);
|
||||
assert_eq!(unknown0.len(), 1);
|
||||
assert_eq!(and1.len(), 1);
|
||||
assert_eq!(or1.len(), 1);
|
||||
assert_eq!(unknown1.len(), 1);
|
||||
|
||||
match unknown0[0].surface() {
|
||||
AlternativeSurface::FirstType(_) => panic!("This face must plane!"),
|
||||
AlternativeSurface::SecondType(_) => {}
|
||||
}
|
||||
match unknown1[0].surface() {
|
||||
AlternativeSurface::FirstType(_) => panic!("This face must plane!"),
|
||||
AlternativeSurface::SecondType(_) => {}
|
||||
}
|
||||
|
||||
let and_shell: Shell<_, _, _> = vec![and0[0].clone(), and1[0].clone()].into();
|
||||
assert_eq!(and_shell.shell_condition(), ShellCondition::Closed);
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
use super::loops_store::ShapesOpStatus;
|
||||
use rustc_hash::FxHashMap as HashMap;
|
||||
use truck_topology::*;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FacesClassification<P, C, S> {
|
||||
shell: Shell<P, C, S>,
|
||||
status: HashMap<FaceID<S>, ShapesOpStatus>,
|
||||
}
|
||||
|
||||
impl<P, C, S> Default for FacesClassification<P, C, S> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
shell: Default::default(),
|
||||
status: HashMap::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, C, S> FacesClassification<P, C, S> {
|
||||
pub fn push(&mut self, face: Face<P, C, S>, status: ShapesOpStatus) {
|
||||
self.status.insert(face.id(), status);
|
||||
self.shell.push(face);
|
||||
}
|
||||
|
||||
pub fn and_or_unknown(&self) -> [Shell<P, C, S>; 3] {
|
||||
let [mut and, mut or, mut unknown] = <[Shell<P, C, S>; 3]>::default();
|
||||
for face in &self.shell {
|
||||
// SolidCraft: no panics (wasm can't catch them).
|
||||
match self.status.get(&face.id()) {
|
||||
Some(ShapesOpStatus::And) => and.push(face.clone()),
|
||||
Some(ShapesOpStatus::Or) => or.push(face.clone()),
|
||||
_ => unknown.push(face.clone()),
|
||||
}
|
||||
}
|
||||
[and, or, unknown]
|
||||
}
|
||||
|
||||
pub fn integrate_by_component(&mut self) {
|
||||
let [and, or, unknown] = self.and_or_unknown();
|
||||
let and_boundary = and.extract_boundaries();
|
||||
let or_boundary = or.extract_boundaries();
|
||||
// SolidCraft: `connected_components` walks a map keyed by face pointers, so each
|
||||
// component's face order (and with it which boundary edge comes first) followed the
|
||||
// heap addresses: the same Boolean could classify a component differently from run to
|
||||
// run. Put each component's faces back in the shell's order first.
|
||||
let position: HashMap<_, usize> = unknown.face_iter().enumerate().map(|(i, face)| (face.id(), i)).collect();
|
||||
let components = unknown.connected_components();
|
||||
for comp in components {
|
||||
let mut faces: Vec<_> = comp.face_iter().cloned().collect();
|
||||
faces.sort_by_key(|face| position.get(&face.id()).copied().unwrap_or(usize::MAX));
|
||||
let comp: Shell<P, C, S> = faces.into_iter().collect();
|
||||
let boundary = comp.extract_boundaries();
|
||||
let Some(first) = boundary.first().and_then(|w| w.edge_iter().next()).map(|e| e.id()) else { continue };
|
||||
let set = if and_boundary.iter().flatten().any(|edge| edge.id() == first) {
|
||||
Some(ShapesOpStatus::And)
|
||||
} else if or_boundary.iter().flatten().any(|edge| edge.id() == first) {
|
||||
Some(ShapesOpStatus::Or)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Some(st) = set {
|
||||
comp.iter().for_each(|face| {
|
||||
if let Some(x) = self.status.get_mut(&face.id()) {
|
||||
*x = st;
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@@ -1,131 +0,0 @@
|
||||
use super::{
|
||||
super::{divide_face, loops_store},
|
||||
*,
|
||||
};
|
||||
use truck_geometry::prelude::*;
|
||||
use truck_meshalgo::prelude::*;
|
||||
use truck_topology::{shell::ShellCondition, Vertex};
|
||||
const TOL: f64 = 0.05;
|
||||
|
||||
type AlternativeIntersection = crate::alternative::Alternative<
|
||||
NurbsCurve<Vector4>,
|
||||
IntersectionCurve<PolylineCurve<Point3>, AlternativeSurface>,
|
||||
>;
|
||||
type AlternativeSurface = crate::alternative::Alternative<BSplineSurface<Point3>, Plane>;
|
||||
|
||||
fn parabola_surfaces() -> (AlternativeSurface, AlternativeSurface) {
|
||||
// define surfaces
|
||||
#[rustfmt::skip]
|
||||
let ctrl0 = vec![
|
||||
vec![Point3::new(-1.0, -1.0, 3.0), Point3::new(-1.0, 0.0, -1.0), Point3::new(-1.0, 1.0, 3.0)],
|
||||
vec![Point3::new(0.0, -1.0, -1.0), Point3::new(0.0, 0.0, -5.0), Point3::new(0.0, 1.0, -1.0)],
|
||||
vec![Point3::new(1.0, -1.0, 3.0), Point3::new(1.0, 0.0, -1.0), Point3::new(1.0, 1.0, 3.0)],
|
||||
];
|
||||
#[rustfmt::skip]
|
||||
let ctrl1 = vec![
|
||||
vec![Point3::new(-1.0, -1.0, -3.0), Point3::new(-1.0, 0.0, 1.0), Point3::new(-1.0, 1.0, -3.0)],
|
||||
vec![Point3::new(0.0, -1.0, 1.0), Point3::new(0.0, 0.0, 5.0), Point3::new(0.0, 1.0, 1.0)],
|
||||
vec![Point3::new(1.0, -1.0, -3.0), Point3::new(1.0, 0.0, 1.0), Point3::new(1.0, 1.0, -3.0)],
|
||||
];
|
||||
(
|
||||
BSplineSurface::new((KnotVec::bezier_knot(2), KnotVec::bezier_knot(2)), ctrl0).into(),
|
||||
BSplineSurface::new((KnotVec::bezier_knot(2), KnotVec::bezier_knot(2)), ctrl1).into(),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn independent_intersection() {
|
||||
// prepare geoetries
|
||||
let arc00: AlternativeIntersection = NurbsCurve::new(BSplineCurve::new(
|
||||
KnotVec::bezier_knot(2),
|
||||
vec![
|
||||
Vector4::new(1.0, 0.0, 1.0, 1.0),
|
||||
Vector4::new(0.0, 1.0, 0.0, 0.0),
|
||||
Vector4::new(-1.0, 0.0, 1.0, 1.0),
|
||||
],
|
||||
))
|
||||
.into();
|
||||
let arc01: AlternativeIntersection = NurbsCurve::new(BSplineCurve::new(
|
||||
KnotVec::bezier_knot(2),
|
||||
vec![
|
||||
Vector4::new(-1.0, 0.0, 1.0, 1.0),
|
||||
Vector4::new(0.0, -1.0, 0.0, 0.0),
|
||||
Vector4::new(1.0, 0.0, 1.0, 1.0),
|
||||
],
|
||||
))
|
||||
.into();
|
||||
let arc10: AlternativeIntersection = NurbsCurve::new(BSplineCurve::new(
|
||||
KnotVec::bezier_knot(2),
|
||||
vec![
|
||||
Vector4::new(1.0, 0.0, -1.0, 1.0),
|
||||
Vector4::new(0.0, 1.0, 0.0, 0.0),
|
||||
Vector4::new(-1.0, 0.0, -1.0, 1.0),
|
||||
],
|
||||
))
|
||||
.into();
|
||||
let arc11: AlternativeIntersection = NurbsCurve::new(BSplineCurve::new(
|
||||
KnotVec::bezier_knot(2),
|
||||
vec![
|
||||
Vector4::new(-1.0, 0.0, -1.0, 1.0),
|
||||
Vector4::new(0.0, -1.0, 0.0, 0.0),
|
||||
Vector4::new(1.0, 0.0, -1.0, 1.0),
|
||||
],
|
||||
))
|
||||
.into();
|
||||
let (surface0, surface1) = parabola_surfaces();
|
||||
let plane0: AlternativeSurface = Plane::new(
|
||||
Point3::new(0.0, 0.0, 1.0),
|
||||
Point3::new(1.0, 0.0, 1.0),
|
||||
Point3::new(0.0, 1.0, 1.0),
|
||||
)
|
||||
.into();
|
||||
let plane1: AlternativeSurface = Plane::new(
|
||||
Point3::new(0.0, 0.0, -1.0),
|
||||
Point3::new(1.0, 0.0, -1.0),
|
||||
Point3::new(0.0, 1.0, -1.0),
|
||||
)
|
||||
.into();
|
||||
|
||||
// prepare topologies
|
||||
let v00 = Vertex::new(Point3::new(1.0, 0.0, 1.0));
|
||||
let v01 = Vertex::new(Point3::new(-1.0, 0.0, 1.0));
|
||||
let v10 = Vertex::new(Point3::new(1.0, 0.0, -1.0));
|
||||
let v11 = Vertex::new(Point3::new(-1.0, 0.0, -1.0));
|
||||
let wire0: Wire<_, _> = vec![Edge::new(&v00, &v01, arc00), Edge::new(&v01, &v00, arc01)].into();
|
||||
let wire1: Wire<_, _> = vec![Edge::new(&v10, &v11, arc10), Edge::new(&v11, &v10, arc11)].into();
|
||||
let shell0: Shell<_, _, _> = vec![
|
||||
Face::new(vec![wire0.clone()], plane0),
|
||||
Face::new(vec![wire0], surface0).inverse(),
|
||||
]
|
||||
.into();
|
||||
assert_eq!(shell0.shell_condition(), ShellCondition::Closed);
|
||||
let shell1: Shell<_, _, _> = vec![
|
||||
Face::new(vec![wire1.clone()], plane1).inverse(),
|
||||
Face::new(vec![wire1], surface1),
|
||||
]
|
||||
.into();
|
||||
assert_eq!(shell1.shell_condition(), ShellCondition::Closed);
|
||||
let poly_shell0 = shell0.triangulation(TOL);
|
||||
let poly_shell1 = shell1.triangulation(TOL);
|
||||
|
||||
let loops_store::LoopsStoreQuadruple {
|
||||
geom_loops_store0: loops_store0,
|
||||
geom_loops_store1: loops_store1,
|
||||
..
|
||||
} = loops_store::create_loops_stores(&shell0, &poly_shell0, &shell1, &poly_shell1, TOL, &mut Default::default())
|
||||
.unwrap();
|
||||
let mut cls0 = divide_face::divide_faces(&shell0, &loops_store0, TOL, &mut Default::default()).unwrap();
|
||||
cls0.integrate_by_component();
|
||||
let mut cls1 = divide_face::divide_faces(&shell1, &loops_store1, TOL, &mut Default::default()).unwrap();
|
||||
cls1.integrate_by_component();
|
||||
|
||||
let [mut and, mut or, _] = cls0.and_or_unknown();
|
||||
let [and1, or1, _] = cls1.and_or_unknown();
|
||||
and.extend(and1);
|
||||
or.extend(or1);
|
||||
|
||||
assert_eq!(and.len(), 2);
|
||||
assert_eq!(or.len(), 4);
|
||||
assert_eq!(and.shell_condition(), ShellCondition::Closed);
|
||||
assert_eq!(or.shell_condition(), ShellCondition::Closed);
|
||||
}
|
||||
-133
@@ -1,133 +0,0 @@
|
||||
//! SolidCraft: where each face and edge of a boolean's result came from, recorded by the code
|
||||
//! that builds them (never reconstructed from geometry).
|
||||
//!
|
||||
//! Topology ids are addresses of shared data, so they identify a face or an edge only while it
|
||||
//! lives. [`Lineage`] therefore stores, for every face and edge an operation creates, its origin
|
||||
//! in terms of the operands at the moment it is created (while its parent is alive), and never a
|
||||
//! chain of ids: a dead entity's address that is reused is overwritten when the new entity is
|
||||
//! recorded.
|
||||
|
||||
use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
|
||||
use truck_topology::*;
|
||||
|
||||
/// A face of one of a boolean's two operands.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct FaceIndex {
|
||||
/// 0 for the first operand, 1 for the second.
|
||||
pub operand: usize,
|
||||
/// Position in that operand's `face_iter` order.
|
||||
pub face: usize,
|
||||
}
|
||||
|
||||
/// Where a face of a boolean's result lies. Every result face is a region of exactly one
|
||||
/// operand face (a boolean makes no face of its own).
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct FaceOrigin {
|
||||
/// The operand face it is a region of (same surface, same orientation).
|
||||
pub source: FaceIndex,
|
||||
/// `true` when it is that very face, passed through untouched (no intersection reached
|
||||
/// it); `false` for a region rebuilt from it by the face division.
|
||||
pub kept: bool,
|
||||
}
|
||||
|
||||
/// Where an edge of a boolean's result came from.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum EdgeOrigin {
|
||||
/// An operand edge or a piece of it.
|
||||
Edge {
|
||||
/// 0 for the first operand, 1 for the second.
|
||||
operand: usize,
|
||||
/// Position in that operand's edge order (see [`History`]).
|
||||
edge: usize,
|
||||
},
|
||||
/// The intersection of two operand faces, or a piece of it (`a < b`).
|
||||
Meet {
|
||||
/// The lesser of the two faces.
|
||||
a: FaceIndex,
|
||||
/// The greater of the two faces.
|
||||
b: FaceIndex,
|
||||
},
|
||||
}
|
||||
|
||||
/// History of a boolean's result: one entry per face in its `face_iter` order and one per edge
|
||||
/// in its edge order. A solid's edge order lists its distinct edges in order of first use,
|
||||
/// walking `face_iter` and each face's `edge_iter` (that is, `Solid::edge_iter` without repeats).
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct History {
|
||||
/// Origin of each result face.
|
||||
pub faces: Vec<FaceOrigin>,
|
||||
/// Origin of each result edge.
|
||||
pub edges: Vec<EdgeOrigin>,
|
||||
}
|
||||
|
||||
/// The ids of `solid`'s edges in its edge order (see [`History`]).
|
||||
fn edge_order<P, C, S>(solid: &Solid<P, C, S>) -> Vec<EdgeID<C>> {
|
||||
let mut seen = HashSet::default();
|
||||
solid.edge_iter().map(|e| e.id()).filter(|id| seen.insert(*id)).collect()
|
||||
}
|
||||
|
||||
/// Records `origin` under `key`, or, when the origin is unknown, leaves nothing stale under the
|
||||
/// new entity's address.
|
||||
fn set<K: std::hash::Hash + Eq, V>(map: &mut HashMap<K, V>, key: K, origin: Option<V>) {
|
||||
match origin {
|
||||
Some(o) => map.insert(key, o),
|
||||
None => map.remove(&key),
|
||||
};
|
||||
}
|
||||
|
||||
/// Origins, in operand terms, of the faces and edges alive during one boolean.
|
||||
///
|
||||
/// The operands must not share faces or edges (a shared one is attributed to the second).
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Lineage<C, S> {
|
||||
faces: HashMap<FaceID<S>, FaceOrigin>,
|
||||
edges: HashMap<EdgeID<C>, EdgeOrigin>,
|
||||
}
|
||||
|
||||
impl<C, S> Default for Lineage<C, S> {
|
||||
fn default() -> Self { Self { faces: HashMap::default(), edges: HashMap::default() } }
|
||||
}
|
||||
|
||||
impl<C, S> Lineage<C, S> {
|
||||
/// Every face and edge of the operands, as itself.
|
||||
pub fn new<P>(operands: [&Solid<P, C, S>; 2]) -> Self {
|
||||
let mut lineage = Self::default();
|
||||
for (operand, solid) in operands.into_iter().enumerate() {
|
||||
for (face, f) in solid.face_iter().enumerate() {
|
||||
let source = FaceIndex { operand, face };
|
||||
lineage.faces.insert(f.id(), FaceOrigin { source, kept: true });
|
||||
}
|
||||
for (edge, id) in edge_order(solid).into_iter().enumerate() {
|
||||
lineage.edges.insert(id, EdgeOrigin::Edge { operand, edge });
|
||||
}
|
||||
}
|
||||
lineage
|
||||
}
|
||||
|
||||
/// `piece` is a region trimmed out of the live face `parent`.
|
||||
pub fn face_piece(&mut self, parent: FaceID<S>, piece: FaceID<S>) {
|
||||
let origin = self.faces.get(&parent).map(|&o| FaceOrigin { kept: false, ..o });
|
||||
set(&mut self.faces, piece, origin);
|
||||
}
|
||||
|
||||
/// `piece` is the live edge `parent` or a piece of it.
|
||||
pub fn edge_piece(&mut self, parent: EdgeID<C>, piece: EdgeID<C>) {
|
||||
let origin = self.edges.get(&parent).copied();
|
||||
set(&mut self.edges, piece, origin);
|
||||
}
|
||||
|
||||
/// `edge` is (part of) the intersection of the live faces `faces`.
|
||||
pub fn meet(&mut self, edge: EdgeID<C>, faces: [FaceID<S>; 2]) {
|
||||
let [a, b] = faces.map(|f| self.faces.get(&f).map(|o| o.source));
|
||||
let origin = a.zip(b).map(|(a, b)| EdgeOrigin::Meet { a: a.min(b), b: a.max(b) });
|
||||
set(&mut self.edges, edge, origin);
|
||||
}
|
||||
|
||||
/// The history of `solid`, made from the operands by the recorded steps; `None` when one
|
||||
/// of its faces or edges was not recorded.
|
||||
pub fn history<P>(&self, solid: &Solid<P, C, S>) -> Option<History> {
|
||||
let faces = solid.face_iter().map(|f| self.faces.get(&f.id()).copied()).collect::<Option<_>>();
|
||||
let edges = edge_order(solid).into_iter().map(|id| self.edges.get(&id).copied()).collect::<Option<_>>();
|
||||
Some(History { faces: crate::tr!(faces, "face history"), edges: crate::tr!(edges, "edge history") })
|
||||
}
|
||||
}
|
||||
@@ -1,243 +0,0 @@
|
||||
use super::history::{History, Lineage};
|
||||
use super::*;
|
||||
use truck_geometry::prelude::IntersectionCurve;
|
||||
use truck_meshalgo::prelude::*;
|
||||
use truck_topology::*;
|
||||
|
||||
/// Only solids consisting of faces whose surface is implemented this trait can be used for set operations.
|
||||
pub trait ShapeOpsSurface:
|
||||
ParametricSurface3D
|
||||
+ ParameterDivision2D
|
||||
+ SearchParameter<D2, Point = Point3>
|
||||
+ SearchNearestParameter<D2, Point = Point3>
|
||||
+ Invertible
|
||||
+ Send
|
||||
+ Sync {
|
||||
}
|
||||
impl<S> ShapeOpsSurface for S where S: ParametricSurface3D
|
||||
+ ParameterDivision2D
|
||||
+ SearchParameter<D2, Point = Point3>
|
||||
+ SearchNearestParameter<D2, Point = Point3>
|
||||
+ Invertible
|
||||
+ Send
|
||||
+ Sync
|
||||
{
|
||||
}
|
||||
|
||||
/// Only solids consisting of edges whose curve is implemented this trait can be used for set operations.
|
||||
pub trait ShapeOpsCurve<S: ShapeOpsSurface>:
|
||||
ParametricCurve3D
|
||||
+ TrySubs
|
||||
+ ParameterDivision1D<Point = Point3>
|
||||
+ Cut
|
||||
+ Invertible
|
||||
+ From<IntersectionCurve<PolylineCurve<Point3>, S>>
|
||||
+ SearchParameter<D1, Point = Point3>
|
||||
+ SearchNearestParameter<D1, Point = Point3>
|
||||
+ Send
|
||||
+ Sync {
|
||||
}
|
||||
impl<C, S: ShapeOpsSurface> ShapeOpsCurve<S> for C where C: ParametricCurve3D
|
||||
+ TrySubs
|
||||
+ ParameterDivision1D<Point = Point3>
|
||||
+ Cut
|
||||
+ Invertible
|
||||
+ From<IntersectionCurve<PolylineCurve<Point3>, S>>
|
||||
+ SearchParameter<D1, Point = Point3>
|
||||
+ SearchNearestParameter<D1, Point = Point3>
|
||||
+ Send
|
||||
+ Sync
|
||||
{
|
||||
}
|
||||
|
||||
/// SolidCraft: the meshed form of a shell that a set operation works on (see [`meshes`]).
|
||||
pub type MeshedShell = Shell<Point3, PolylineCurve<Point3>, Option<PolygonMesh>>;
|
||||
|
||||
/// SolidCraft: the meshes of `solid`'s shells at `tol`, as a set operation makes them: an
|
||||
/// operand used in many operations at one tolerance (a large body cut down to many small
|
||||
/// regions) is meshed once and handed to [`and_with_history_meshed`], which is most of what
|
||||
/// such an operation costs. They fit any copy of `solid` with its faces in the same order.
|
||||
pub fn meshes<C: ShapeOpsCurve<S>, S: ShapeOpsSurface>(solid: &Solid<Point3, C, S>, tol: f64) -> Vec<MeshedShell> {
|
||||
solid.boundaries().iter().map(|shell| shell.robust_triangulation(tol)).collect()
|
||||
}
|
||||
|
||||
/// SolidCraft: whether `shell` faces inward (a solid's void, a cut's inverted tool), by the sign
|
||||
/// of its volume with each face counted as it is oriented (a face's mesh is its surface's,
|
||||
/// whatever the face's orientation).
|
||||
fn faces_inward(shell: &MeshedShell) -> Option<bool> {
|
||||
let mut volume = 0.0;
|
||||
for face in shell.face_iter() {
|
||||
let v = face.surface()?.volume();
|
||||
volume += if face.orientation() { v } else { -v };
|
||||
}
|
||||
Some(volume < 0.0)
|
||||
}
|
||||
|
||||
/// SolidCraft: whether `pt` is inside the closed `shell`, by the faces a ray from it crosses.
|
||||
/// For an AND (`and`) each face counts as it is oriented, and a shell facing inward (`inward`,
|
||||
/// see [`faces_inward`]) holds what lies outside it: the crossings count 0 there and -1 inside.
|
||||
/// An OR keeps truck's count, by the faces' surfaces alone: it folds a solid's voids in as if
|
||||
/// they were solids of their own, which is no union with voids whichever way they are counted.
|
||||
fn inside_shell(shell: &MeshedShell, pt: Point3, and: bool, inward: bool) -> Option<bool> {
|
||||
let dir = hash::take_one_unit(pt);
|
||||
let mut count = 0;
|
||||
for face in shell.face_iter() {
|
||||
let sign = if face.orientation() || !and { 1 } else { -1 };
|
||||
count += sign * face.surface()?.signed_crossing_faces(pt, dir);
|
||||
}
|
||||
Some(if inward { count >= 0 } else { count >= 1 })
|
||||
}
|
||||
|
||||
fn process_one_pair_of_shells<C: ShapeOpsCurve<S>, S: ShapeOpsSurface>(
|
||||
shell0: &Shell<Point3, C, S>,
|
||||
meshed0: Option<&MeshedShell>,
|
||||
shell1: &Shell<Point3, C, S>,
|
||||
tol: f64,
|
||||
lineage: &mut Lineage<C, S>,
|
||||
and: bool,
|
||||
) -> Option<[Shell<Point3, C, S>; 2]> {
|
||||
nonpositive_tolerance!(tol);
|
||||
// SolidCraft: robust meshing (boundaries from earlier operations lie on surfaces only
|
||||
// within tolerance). A mesh made before is copied: the loops stores cut its edges.
|
||||
let poly_shell0 = match meshed0 {
|
||||
Some(m) if m.len() == shell0.len() => m.mapped(|p| *p, |c| c.clone(), |s| s.clone()),
|
||||
_ => shell0.robust_triangulation(tol),
|
||||
};
|
||||
let poly_shell1 = shell1.robust_triangulation(tol);
|
||||
let loops_store::LoopsStoreQuadruple {
|
||||
geom_loops_store0: loops_store0,
|
||||
geom_loops_store1: loops_store1,
|
||||
..
|
||||
} = crate::tr!(loops_store::create_loops_stores(shell0, &poly_shell0, shell1, &poly_shell1, tol, lineage), "loops stores");
|
||||
let mut cls0 = crate::tr!(divide_face::divide_faces(shell0, &loops_store0, tol, lineage), "divide faces 0");
|
||||
cls0.integrate_by_component();
|
||||
let mut cls1 = crate::tr!(divide_face::divide_faces(shell1, &loops_store1, tol, lineage), "divide faces 1");
|
||||
cls1.integrate_by_component();
|
||||
let [mut and0, mut or0, unknown0] = cls0.and_or_unknown();
|
||||
let inward1 = and && !unknown0.is_empty() && faces_inward(&poly_shell1)?;
|
||||
unknown0.into_iter().try_for_each(|face| {
|
||||
// SolidCraft: no panics (wasm can't catch them).
|
||||
let pt = face.boundaries().first()?.vertex_iter().next()?.point();
|
||||
if inside_shell(&poly_shell1, pt, and, inward1)? {
|
||||
and0.push(face);
|
||||
} else {
|
||||
or0.push(face);
|
||||
}
|
||||
Some(())
|
||||
})?;
|
||||
let [mut and1, mut or1, unknown1] = cls1.and_or_unknown();
|
||||
let inward0 = and && !unknown1.is_empty() && faces_inward(&poly_shell0)?;
|
||||
unknown1.into_iter().try_for_each(|face| {
|
||||
// SolidCraft: no panics (wasm can't catch them).
|
||||
let pt = face.boundaries().first()?.vertex_iter().next()?.point();
|
||||
if inside_shell(&poly_shell0, pt, and, inward0)? {
|
||||
and1.push(face);
|
||||
} else {
|
||||
or1.push(face);
|
||||
}
|
||||
Some(())
|
||||
})?;
|
||||
and0.append(&mut and1);
|
||||
or0.append(&mut or1);
|
||||
Some([and0, or0])
|
||||
}
|
||||
|
||||
/// AND operation between two solids.
|
||||
pub fn and<C: ShapeOpsCurve<S>, S: ShapeOpsSurface>(
|
||||
solid0: &Solid<Point3, C, S>,
|
||||
solid1: &Solid<Point3, C, S>,
|
||||
tol: f64,
|
||||
) -> Option<Solid<Point3, C, S>> {
|
||||
boolean(solid0, None, solid1, tol, true).map(|(solid, _)| solid)
|
||||
}
|
||||
|
||||
/// OR operation between two solids.
|
||||
pub fn or<C: ShapeOpsCurve<S>, S: ShapeOpsSurface>(
|
||||
solid0: &Solid<Point3, C, S>,
|
||||
solid1: &Solid<Point3, C, S>,
|
||||
tol: f64,
|
||||
) -> Option<Solid<Point3, C, S>> {
|
||||
boolean(solid0, None, solid1, tol, false).map(|(solid, _)| solid)
|
||||
}
|
||||
|
||||
/// SolidCraft: AND operation between two solids, with where each face and edge of the result
|
||||
/// came from (operand 0 is `solid0`, operand 1 is `solid1`): the same result as [`and`], and
|
||||
/// its history, `None` when a face or edge of it was not recorded. The operands must not share
|
||||
/// faces or edges.
|
||||
pub fn and_with_history<C: ShapeOpsCurve<S>, S: ShapeOpsSurface>(
|
||||
solid0: &Solid<Point3, C, S>,
|
||||
solid1: &Solid<Point3, C, S>,
|
||||
tol: f64,
|
||||
) -> Option<(Solid<Point3, C, S>, Option<History>)> {
|
||||
boolean(solid0, None, solid1, tol, true)
|
||||
}
|
||||
|
||||
/// SolidCraft: [`and_with_history`] with `solid0` meshed already ([`meshes`] of it, or of a
|
||||
/// copy of it, at `tol`): the same result.
|
||||
pub fn and_with_history_meshed<C: ShapeOpsCurve<S>, S: ShapeOpsSurface>(
|
||||
solid0: &Solid<Point3, C, S>,
|
||||
meshed0: &[MeshedShell],
|
||||
solid1: &Solid<Point3, C, S>,
|
||||
tol: f64,
|
||||
) -> Option<(Solid<Point3, C, S>, Option<History>)> {
|
||||
boolean(solid0, Some(meshed0), solid1, tol, true)
|
||||
}
|
||||
|
||||
/// SolidCraft: OR operation between two solids, with where each face and edge of the result
|
||||
/// came from (operand 0 is `solid0`, operand 1 is `solid1`): the same result as [`or`], and
|
||||
/// its history, `None` when a face or edge of it was not recorded. The operands must not share
|
||||
/// faces or edges.
|
||||
pub fn or_with_history<C: ShapeOpsCurve<S>, S: ShapeOpsSurface>(
|
||||
solid0: &Solid<Point3, C, S>,
|
||||
solid1: &Solid<Point3, C, S>,
|
||||
tol: f64,
|
||||
) -> Option<(Solid<Point3, C, S>, Option<History>)> {
|
||||
boolean(solid0, None, solid1, tol, false)
|
||||
}
|
||||
|
||||
/// Combines the operands' first shells, then folds in their other shells one at a time
|
||||
/// (`and` chooses the AND or the OR shell of each pass). The history is `None` when a face or
|
||||
/// edge of the result was not recorded; the result stands without it.
|
||||
fn boolean<C: ShapeOpsCurve<S>, S: ShapeOpsSurface>(
|
||||
solid0: &Solid<Point3, C, S>,
|
||||
meshed0: Option<&[MeshedShell]>,
|
||||
solid1: &Solid<Point3, C, S>,
|
||||
tol: f64,
|
||||
and: bool,
|
||||
) -> Option<(Solid<Point3, C, S>, Option<History>)> {
|
||||
let pick = |[and_shell, or_shell]: [Shell<Point3, C, S>; 2]| if and { and_shell } else { or_shell };
|
||||
let mut lineage = Lineage::new([solid0, solid1]);
|
||||
let mut iter0 = solid0.boundaries().iter();
|
||||
let mut iter1 = solid1.boundaries().iter();
|
||||
let shell0 = iter0.next()?;
|
||||
let shell1 = iter1.next()?;
|
||||
let meshed0 = meshed0.filter(|m| m.len() == solid0.boundaries().len()).and_then(|m| m.first());
|
||||
let mut shell = pick(crate::tr!(process_one_pair_of_shells(shell0, meshed0, shell1, tol, &mut lineage, and), "first shells"));
|
||||
for other in iter0.chain(iter1) {
|
||||
shell = pick(crate::tr!(process_one_pair_of_shells(&shell, None, other, tol, &mut lineage, and), "another shell"));
|
||||
}
|
||||
let solid = crate::tr!(Solid::try_new(components_in_order(&shell)).ok(), "result solid");
|
||||
let history = lineage.history(&solid);
|
||||
Some((solid, history))
|
||||
}
|
||||
|
||||
// SolidCraft: truck's connected components come out in hash order of face addresses, so the
|
||||
// result's face order changed from run to run. Keep the shell's own order instead.
|
||||
fn components_in_order<C, S>(shell: &Shell<Point3, C, S>) -> Vec<Shell<Point3, C, S>> {
|
||||
let pos: rustc_hash::FxHashMap<_, usize> = shell.face_iter().enumerate().map(|(i, f)| (f.id(), i)).collect();
|
||||
let key = |f: &Face<Point3, C, S>| pos.get(&f.id()).copied().unwrap_or(usize::MAX);
|
||||
let mut comps: Vec<Vec<Face<Point3, C, S>>> = shell
|
||||
.connected_components()
|
||||
.into_iter()
|
||||
.map(|c| {
|
||||
let mut faces: Vec<_> = c.into_iter().collect();
|
||||
faces.sort_by_key(|f| key(f));
|
||||
faces
|
||||
})
|
||||
.collect();
|
||||
comps.sort_by_key(|c| c.first().map(|f| key(f)).unwrap_or(usize::MAX));
|
||||
comps.into_iter().map(Shell::from).collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@@ -1,153 +0,0 @@
|
||||
use truck_meshalgo::prelude::*;
|
||||
use truck_modeling::*;
|
||||
|
||||
#[test]
|
||||
fn punched_cube() {
|
||||
let v = builder::vertex(Point3::origin());
|
||||
let e = builder::tsweep(&v, Vector3::unit_x());
|
||||
let f = builder::tsweep(&e, Vector3::unit_y());
|
||||
let cube = builder::tsweep(&f, Vector3::unit_z());
|
||||
|
||||
let v = builder::vertex(Point3::new(0.5, 0.25, -0.5));
|
||||
let w = builder::rsweep(&v, Point3::new(0.5, 0.5, 0.0), Vector3::unit_z(), Rad(7.0));
|
||||
let f = builder::try_attach_plane(&[w]).unwrap();
|
||||
let mut cylinder = builder::tsweep(&f, Vector3::unit_z() * 2.0);
|
||||
cylinder.not();
|
||||
let and = crate::and(&cube, &cylinder, 0.05).unwrap();
|
||||
|
||||
let poly = and.triangulation(0.01).to_polygon();
|
||||
let file = std::fs::File::create("punched-cube.obj").unwrap();
|
||||
obj::write(&poly, file).unwrap();
|
||||
}
|
||||
|
||||
// SolidCraft: boolean history.
|
||||
|
||||
use crate::{EdgeOrigin, FaceIndex, History};
|
||||
|
||||
fn cuboid(origin: Point3, size: Vector3) -> Solid {
|
||||
let v = builder::vertex(origin);
|
||||
let e = builder::tsweep(&v, size.x * Vector3::unit_x());
|
||||
let f = builder::tsweep(&e, size.y * Vector3::unit_y());
|
||||
builder::tsweep(&f, size.z * Vector3::unit_z())
|
||||
}
|
||||
|
||||
/// Distinct edges in order of first use along the faces.
|
||||
fn edges_in_order(solid: &Solid) -> Vec<Edge> {
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
solid.face_iter().flat_map(Face::edge_iter).filter(|e| seen.insert(e.id())).collect()
|
||||
}
|
||||
|
||||
fn on_surface(surface: &Surface, pt: Point3, tol: f64) -> bool {
|
||||
surface.search_nearest_parameter(pt, None, 100).is_some_and(|(u, v)| surface.subs(u, v).distance(pt) < tol)
|
||||
}
|
||||
|
||||
fn on_curve(curve: &Curve, pt: Point3, tol: f64) -> bool {
|
||||
curve.search_nearest_parameter(pt, None, 100).is_some_and(|t| curve.subs(t).distance(pt) < tol)
|
||||
}
|
||||
|
||||
/// Checks `history` against the geometry of `result` and the operands; returns how many result
|
||||
/// faces are kept operand faces and the intersection edges.
|
||||
fn check(result: &Solid, operands: [&Solid; 2], history: &History, tol: f64) -> (usize, Vec<[FaceIndex; 2]>) {
|
||||
let operand_faces = operands.map(|s| s.face_iter().collect::<Vec<_>>());
|
||||
let face = |f: FaceIndex| operand_faces[f.operand][f.face];
|
||||
assert_eq!(history.faces.len(), result.face_iter().count());
|
||||
for (out, origin) in result.face_iter().zip(&history.faces) {
|
||||
let src = face(origin.source);
|
||||
assert_eq!(out.orientation(), src.orientation());
|
||||
assert_eq!(out.id() == src.id(), origin.kept);
|
||||
assert!(out.vertex_iter().all(|v| on_surface(&src.surface(), v.point(), tol)));
|
||||
}
|
||||
let edges = edges_in_order(result);
|
||||
assert_eq!(history.edges.len(), edges.len());
|
||||
let operand_edges = operands.map(edges_in_order);
|
||||
let mut meets = Vec::new();
|
||||
for (out, origin) in edges.iter().zip(&history.edges) {
|
||||
let ends = [out.front().point(), out.back().point()];
|
||||
match *origin {
|
||||
EdgeOrigin::Edge { operand, edge } => {
|
||||
let src = &operand_edges[operand][edge];
|
||||
assert!(ends.iter().all(|&p| on_curve(&src.curve(), p, tol)));
|
||||
}
|
||||
EdgeOrigin::Meet { a, b } => {
|
||||
assert!(a < b);
|
||||
for f in [a, b] {
|
||||
assert!(ends.iter().all(|&p| on_surface(&face(f).surface(), p, tol)));
|
||||
}
|
||||
meets.push([a, b]);
|
||||
}
|
||||
}
|
||||
}
|
||||
(history.faces.iter().filter(|o| o.kept).count(), meets)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn box_union_box_history() {
|
||||
let a = cuboid(Point3::origin(), Vector3::new(1.0, 1.0, 1.0));
|
||||
let b = cuboid(Point3::new(0.5, 0.25, 0.25), Vector3::new(1.0, 0.5, 0.5));
|
||||
let (or, history) = crate::or_with_history(&a, &b, 0.05).unwrap();
|
||||
let history = history.unwrap();
|
||||
let (kept, meets) = check(&or, [&a, &b], &history, 0.05);
|
||||
// a's five faces away from b and b's far end are kept; a's face at x = 1 gets a hole and
|
||||
// b's four sides are trimmed where they enter a.
|
||||
assert_eq!(history.faces.len(), 11);
|
||||
assert_eq!(kept, 6);
|
||||
let pieces: Vec<_> = history.faces.iter().filter(|o| !o.kept).map(|o| o.source).collect();
|
||||
assert_eq!(pieces.iter().filter(|f| f.operand == 0).count(), 1);
|
||||
assert_eq!(pieces.iter().filter(|f| f.operand == 1).count(), 4);
|
||||
// b's sides meet a's face at x = 1 along four new edges.
|
||||
assert_eq!(meets.len(), 4);
|
||||
assert!(meets.iter().all(|[x, y]| x.operand == 0 && y.operand == 1 && *x == pieces[0]));
|
||||
// a's 12 edges, b's 4 far-end edges and the 4 trimmed edges along x.
|
||||
assert_eq!(history.edges.len(), 24);
|
||||
let from_b = history.edges.iter().filter(|o| matches!(o, EdgeOrigin::Edge { operand: 1, .. })).count();
|
||||
assert_eq!(from_b, 8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn box_minus_cylinder_history() {
|
||||
let cube = cuboid(Point3::origin(), Vector3::new(1.0, 1.0, 1.0));
|
||||
let v = builder::vertex(Point3::new(0.5, 0.25, -0.5));
|
||||
let w = builder::rsweep(&v, Point3::new(0.5, 0.5, 0.0), Vector3::unit_z(), Rad(7.0));
|
||||
let f = builder::try_attach_plane(&[w]).unwrap();
|
||||
let mut cylinder = builder::tsweep(&f, Vector3::unit_z() * 2.0);
|
||||
cylinder.not();
|
||||
let (cut, history) = crate::and_with_history(&cube, &cylinder, 0.05).unwrap();
|
||||
let history = history.unwrap();
|
||||
let (kept, meets) = check(&cut, [&cube, &cylinder], &history, 0.05);
|
||||
// The cube's four sides are kept; its top and bottom get holes; only the cylinder's wall
|
||||
// remains of the tool, trimmed to the cube.
|
||||
assert_eq!(kept, 4);
|
||||
assert!(history.faces.iter().filter(|o| o.source.operand == 1).all(|o| !o.kept));
|
||||
// The holes' rims: the wall meets the top and the bottom.
|
||||
assert!(!meets.is_empty());
|
||||
assert!(meets.iter().all(|[x, y]| x.operand == 0 && y.operand == 1));
|
||||
let rims: std::collections::BTreeSet<_> = meets.iter().map(|[x, _]| *x).collect();
|
||||
assert_eq!(rims.len(), 2);
|
||||
}
|
||||
|
||||
/// A cube [0, 3]³ with a cubic void [1, 2]³: two shells, the void's faces after the outer ones.
|
||||
fn hollow_cube() -> Solid {
|
||||
let mut void = cuboid(Point3::new(1.0, 1.0, 1.0), Vector3::new(1.0, 1.0, 1.0));
|
||||
void.not();
|
||||
let outer = cuboid(Point3::origin(), Vector3::new(3.0, 3.0, 3.0));
|
||||
Solid::new(outer.into_boundaries().into_iter().chain(void.into_boundaries()).collect())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_shell_operand_history() {
|
||||
let hollow = hollow_cube();
|
||||
// Covers the upper half of the hollow cube: its bottom crosses both shells.
|
||||
let slab = cuboid(Point3::new(-0.5, -0.5, 1.5), Vector3::new(4.0, 4.0, 2.5));
|
||||
let (and, history) = crate::and_with_history(&hollow, &slab, 0.05).unwrap();
|
||||
let history = history.unwrap();
|
||||
let (kept, meets) = check(&and, [&hollow, &slab], &history, 0.05);
|
||||
// The outer top and the void's top (numbered after the outer shell's six faces) are kept.
|
||||
assert_eq!(kept, 2);
|
||||
assert!(history.faces.iter().any(|o| o.kept && o.source.operand == 0 && o.source.face >= 6));
|
||||
// The slab's bottom meets the four outer sides and the void's four sides.
|
||||
let outer = meets.iter().filter(|[x, _]| x.operand == 0 && x.face < 6).count();
|
||||
let void = meets.iter().filter(|[x, _]| x.operand == 0 && x.face >= 6).count();
|
||||
assert_eq!((outer, void), (4, 4), "{meets:?}");
|
||||
let slab_faces: std::collections::BTreeSet<_> = meets.iter().map(|[_, y]| *y).collect();
|
||||
assert_eq!(slab_faces.len(), 1);
|
||||
}
|
||||
@@ -1,183 +0,0 @@
|
||||
use truck_base::cgmath64::*;
|
||||
use truck_geometry::prelude::*;
|
||||
use truck_meshalgo::prelude::*;
|
||||
|
||||
/// polyline base ntersection curve with parameter
|
||||
#[derive(Debug, Clone, derive_more::Deref, derive_more::DerefMut)]
|
||||
pub struct IntersectionCurveWithParameters<S> {
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
ic: IntersectionCurve<PolylineCurve<Point3>, S>,
|
||||
params0: PolylineCurve<Point2>,
|
||||
params1: PolylineCurve<Point2>,
|
||||
}
|
||||
|
||||
impl<S> From<IntersectionCurveWithParameters<S>> for IntersectionCurve<PolylineCurve<Point3>, S> {
|
||||
fn from(a: IntersectionCurveWithParameters<S>) -> Self { a.ic }
|
||||
}
|
||||
|
||||
impl<S> IntersectionCurveWithParameters<S>
|
||||
where S: ParametricSurface3D + SearchNearestParameter<D2, Point = Point3>
|
||||
{
|
||||
/// SolidCraft: construct a projected curve, returning `None` for degenerate input.
|
||||
pub fn try_new(
|
||||
surface0: S,
|
||||
surface1: S,
|
||||
poly: PolylineCurve<Point3>,
|
||||
tol: f64,
|
||||
) -> Option<Self> {
|
||||
// SolidCraft: a point contact is not a curve. Reject it before indexing the
|
||||
// endpoints, and reject collapsed/non-finite segments before normalizing.
|
||||
if poly.len() < 2 || poly.windows(2).any(|p| {
|
||||
let length2 = (p[1] - p[0]).magnitude2();
|
||||
!length2.is_finite() || length2 <= 0.0
|
||||
}) {
|
||||
return None;
|
||||
}
|
||||
let mut polyline = PolylineCurve(Vec::new());
|
||||
let mut params0 = PolylineCurve(Vec::new());
|
||||
let mut params1 = PolylineCurve(Vec::new());
|
||||
for p in poly.windows(2) {
|
||||
let n = (p[1] - p[0]).normalize();
|
||||
let (q, p0, p1) = double_projection(&surface0, None, &surface1, None, p[0], n, 100)?;
|
||||
polyline.push(q);
|
||||
params0.push(p0);
|
||||
params1.push(p1);
|
||||
}
|
||||
let (q, p0, p1) = if poly[0].near(&poly[poly.len() - 1]) {
|
||||
(polyline[0], params0[0], params1[0])
|
||||
} else {
|
||||
let n = (poly[poly.len() - 1] - poly[poly.len() - 2]).normalize();
|
||||
double_projection(
|
||||
&surface0,
|
||||
None,
|
||||
&surface1,
|
||||
None,
|
||||
poly[poly.len() - 1],
|
||||
n,
|
||||
100,
|
||||
)?
|
||||
};
|
||||
polyline.push(q);
|
||||
params0.push(p0);
|
||||
params1.push(p1);
|
||||
Some(Self {
|
||||
ic: IntersectionCurve::new_unchecked(
|
||||
Box::new(surface0),
|
||||
Box::new(surface1),
|
||||
polyline,
|
||||
tol,
|
||||
),
|
||||
params0,
|
||||
params1,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> ParametricCurve for IntersectionCurveWithParameters<S>
|
||||
where S: ParametricSurface3D + SearchNearestParameter<D2, Point = Point3>
|
||||
{
|
||||
type Point = Point3;
|
||||
type Vector = Vector3;
|
||||
#[inline(always)]
|
||||
fn subs(&self, t: f64) -> Point3 { self.ic.subs(t) }
|
||||
#[inline(always)]
|
||||
fn der(&self, t: f64) -> Vector3 { self.ic.der(t) }
|
||||
#[inline(always)]
|
||||
fn der2(&self, t: f64) -> Vector3 { self.ic.der2(t) }
|
||||
#[inline(always)]
|
||||
fn parameter_range(&self) -> ParameterRange { self.ic.parameter_range() }
|
||||
}
|
||||
|
||||
impl<S> BoundedCurve for IntersectionCurveWithParameters<S> where S: ParametricSurface3D + SearchNearestParameter<D2, Point = Point3> {}
|
||||
|
||||
impl<S> ParameterDivision1D for IntersectionCurveWithParameters<S>
|
||||
where S: ParametricSurface3D + SearchNearestParameter<D2, Point = Point3>
|
||||
{
|
||||
type Point = Point3;
|
||||
#[inline(always)]
|
||||
fn parameter_division(&self, range: (f64, f64), tol: f64) -> (Vec<f64>, Vec<Point3>) {
|
||||
self.ic.parameter_division(range, tol)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Cut for IntersectionCurveWithParameters<S>
|
||||
where S: ParametricSurface3D + SearchNearestParameter<D2, Point = Point3>
|
||||
{
|
||||
#[inline(always)]
|
||||
fn cut(&mut self, t: f64) -> Self {
|
||||
Self {
|
||||
ic: self.ic.cut(t),
|
||||
params0: self.params0.cut(t),
|
||||
params1: self.params1.cut(t),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Clone> Invertible for IntersectionCurveWithParameters<S> {
|
||||
fn invert(&mut self) {
|
||||
self.ic.invert();
|
||||
self.params0.invert();
|
||||
self.params1.invert();
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> SearchParameter<D1> for IntersectionCurveWithParameters<S>
|
||||
where S: ParametricSurface3D + SearchNearestParameter<D2, Point = Point3>
|
||||
{
|
||||
type Point = Point3;
|
||||
fn search_parameter<H: Into<SPHint1D>>(
|
||||
&self,
|
||||
point: Point3,
|
||||
hint: H,
|
||||
trials: usize,
|
||||
) -> Option<f64> {
|
||||
self.ic.search_parameter(point, hint, trials)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> SearchNearestParameter<D1> for IntersectionCurveWithParameters<S>
|
||||
where S: ParametricSurface3D + SearchNearestParameter<D2, Point = Point3>
|
||||
{
|
||||
type Point = Point3;
|
||||
fn search_nearest_parameter<H: Into<SPHint1D>>(
|
||||
&self,
|
||||
point: Point3,
|
||||
hint: H,
|
||||
trials: usize,
|
||||
) -> Option<f64> {
|
||||
self.ic.search_nearest_parameter(point, hint, trials)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn intersection_curves<S>(
|
||||
surface0: S,
|
||||
polygon0: &PolygonMesh,
|
||||
surface1: S,
|
||||
polygon1: &PolygonMesh,
|
||||
tol: f64,
|
||||
) -> Vec<(
|
||||
PolylineCurve<Point3>,
|
||||
Option<IntersectionCurveWithParameters<S>>,
|
||||
)>
|
||||
where
|
||||
S: ParametricSurface3D + SearchNearestParameter<D2, Point = Point3>,
|
||||
{
|
||||
let interferences = polygon0.extract_interference(polygon1);
|
||||
let polylines = super::polyline_construction::construct_polylines(&interferences);
|
||||
polylines
|
||||
.into_iter()
|
||||
.map(|polyline| {
|
||||
let curve = IntersectionCurveWithParameters::try_new(
|
||||
surface0.clone(),
|
||||
surface1.clone(),
|
||||
polyline.clone(),
|
||||
tol,
|
||||
);
|
||||
(polyline, curve)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@@ -1,173 +0,0 @@
|
||||
use super::*;
|
||||
use std::f64::consts::PI;
|
||||
|
||||
mod double_projection_tests {
|
||||
use super::*;
|
||||
|
||||
fn create_axis(n: Vector3) -> (Vector3, Vector3) {
|
||||
let idx = if n[0].abs() < n[1].abs() { 0 } else { 1 };
|
||||
let idx = if n[idx].abs() < n[2].abs() { idx } else { 2 };
|
||||
let mut e = Vector3::zero();
|
||||
e[idx] = 1.0;
|
||||
let x = n.cross(e).normalize();
|
||||
(x, n.cross(x))
|
||||
}
|
||||
|
||||
fn exec_plane_case() {
|
||||
let c0 = Point3::new(
|
||||
2.0 * rand::random::<f64>() - 1.0,
|
||||
2.0 * rand::random::<f64>() - 1.0,
|
||||
2.0 * rand::random::<f64>() - 1.0,
|
||||
);
|
||||
let n0 = Vector3::new(
|
||||
2.0 * rand::random::<f64>() - 1.0,
|
||||
2.0 * rand::random::<f64>() - 1.0,
|
||||
2.0 * rand::random::<f64>() - 1.0,
|
||||
)
|
||||
.normalize();
|
||||
let (x, y) = create_axis(n0);
|
||||
let plane0 = Plane::new(c0, c0 + x, c0 + y);
|
||||
let c1 = Point3::new(
|
||||
2.0 * rand::random::<f64>() - 1.0,
|
||||
2.0 * rand::random::<f64>() - 1.0,
|
||||
2.0 * rand::random::<f64>() - 1.0,
|
||||
);
|
||||
let n1 = Vector3::new(
|
||||
2.0 * rand::random::<f64>() - 1.0,
|
||||
2.0 * rand::random::<f64>() - 1.0,
|
||||
2.0 * rand::random::<f64>() - 1.0,
|
||||
)
|
||||
.normalize();
|
||||
let (x, y) = create_axis(n1);
|
||||
let plane1 = Plane::new(c1, c1 + x, c1 + y);
|
||||
let n = n0.cross(n1).normalize();
|
||||
let mut o = None;
|
||||
for i in 0..10 {
|
||||
let t = i as f64;
|
||||
let p = Point3::origin() + t * n;
|
||||
let (q, p0, p1) = double_projection(&plane0, None, &plane1, None, p, n, 100)
|
||||
.unwrap_or_else(|| panic!("plane0: {plane0:?}\nplane1: {plane1:?}\n p: {p:?}"));
|
||||
assert_near!(q, plane0.subs(p0.x, p0.y));
|
||||
assert_near!(q, plane1.subs(p1.x, p1.y));
|
||||
if let Some(o) = o {
|
||||
assert_near!(q.distance2(o), t * t);
|
||||
} else {
|
||||
o = Some(q);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plane_case() { (0..100).for_each(|_| exec_plane_case()); }
|
||||
|
||||
#[test]
|
||||
fn sphere_case() {
|
||||
let sphere0 = Sphere::new(Point3::new(0.0, 0.0, 1.0), f64::sqrt(2.0));
|
||||
let sphere1 = Sphere::new(Point3::new(0.0, 0.0, -1.0), f64::sqrt(2.0));
|
||||
for _ in 0..100 {
|
||||
let t = 2.0 * PI * rand::random::<f64>();
|
||||
let r = 2.0 * rand::random::<f64>();
|
||||
let p = Point3::new(r * f64::cos(t), r * f64::sin(t), 0.0);
|
||||
let n = Vector3::new(-f64::sin(t), f64::cos(t), 0.0);
|
||||
let (q, p0, p1) = double_projection(&sphere0, None, &sphere1, None, p, n, 100)
|
||||
.unwrap_or_else(|| panic!("p: {p:?}"));
|
||||
assert_near!(q, sphere0.subs(p0.x, p0.y));
|
||||
assert_near!(q, sphere1.subs(p1.x, p1.y));
|
||||
assert_near!(q, Point3::new(f64::cos(t), f64::sin(t), 0.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn intersection_curve_sphere_case() {
|
||||
let sphere0 = Sphere::new(Point3::new(0.0, 0.0, 1.0), f64::sqrt(2.0));
|
||||
let sphere1 = Sphere::new(Point3::new(0.0, 0.0, -1.0), f64::sqrt(2.0));
|
||||
const M: usize = 5;
|
||||
let polyline = (0..=M)
|
||||
.map(|i| {
|
||||
let t = 2.0 * PI * i as f64 / M as f64;
|
||||
Point3::new(0.8 * f64::cos(t), 0.8 * f64::sin(t), 0.0)
|
||||
})
|
||||
.collect::<PolylineCurve<_>>();
|
||||
let curve: IntersectionCurve<_, _> =
|
||||
IntersectionCurveWithParameters::try_new(sphere0, sphere1, polyline, 0.5)
|
||||
.unwrap()
|
||||
.into();
|
||||
|
||||
const N: usize = 100;
|
||||
let mut sum = 0.0;
|
||||
let (t0, t1) = curve.range_tuple();
|
||||
for i in 0..N {
|
||||
let t = t0 + (t1 - t0) * i as f64 / N as f64;
|
||||
let pt = curve.subs(t);
|
||||
assert_near!(pt.distance(Point3::origin()), 1.0);
|
||||
let vec = curve.der(t);
|
||||
assert!(pt.dot(vec).so_small(), "{i} {t} {vec:?}");
|
||||
assert!(vec[2].so_small());
|
||||
sum += vec.magnitude() * (t1 - t0) / N as f64;
|
||||
}
|
||||
assert!(f64::abs(sum - 2.0 * PI) < 0.1, "{}", sum);
|
||||
|
||||
let theta = 2.0 * PI * rand::random::<f64>();
|
||||
let pt = Point3::new(f64::cos(theta), f64::sin(theta), 0.0);
|
||||
let t = curve.search_parameter(pt, None, 1).unwrap();
|
||||
assert!(curve.subs(t).near(&pt));
|
||||
let pt = Point3::new(1.1 * f64::cos(theta), 1.1 * f64::sin(theta), 0.0);
|
||||
assert!(curve.search_parameter(pt, None, 1).is_none());
|
||||
|
||||
let mut curve0 = curve.clone();
|
||||
let curve1 = curve0.cut(2.5);
|
||||
assert_near!(curve0.front(), curve.front());
|
||||
assert_near!(curve0.back(), curve.subs(2.5));
|
||||
assert_near!(curve1.front(), curve.subs(2.5));
|
||||
assert_near!(curve1.back(), curve.back());
|
||||
let mut curve0 = curve.clone();
|
||||
let curve1 = curve0.cut(2.0);
|
||||
assert_near!(curve0.front(), curve.front());
|
||||
assert_near!(curve0.back(), curve.subs(2.0));
|
||||
assert_near!(curve1.front(), curve.subs(2.0));
|
||||
assert_near!(curve1.back(), curve.back());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collide_parabola() {
|
||||
const TOL: f64 = 0.05;
|
||||
|
||||
// define surfaces
|
||||
#[rustfmt::skip]
|
||||
let ctrl0 = vec![
|
||||
vec![Point3::new(-1.0, -1.0, 3.0), Point3::new(-1.0, 0.0, -1.0), Point3::new(-1.0, 1.0, 3.0)],
|
||||
vec![Point3::new(0.0, -1.0, -1.0), Point3::new(0.0, 0.0, -5.0), Point3::new(0.0, 1.0, -1.0)],
|
||||
vec![Point3::new(1.0, -1.0, 3.0), Point3::new(1.0, 0.0, -1.0), Point3::new(1.0, 1.0, 3.0)],
|
||||
];
|
||||
#[rustfmt::skip]
|
||||
let ctrl1 = vec![
|
||||
vec![Point3::new(-1.0, -1.0, -3.0), Point3::new(-1.0, 0.0, 1.0), Point3::new(-1.0, 1.0, -3.0)],
|
||||
vec![Point3::new(0.0, -1.0, 1.0), Point3::new(0.0, 0.0, 5.0), Point3::new(0.0, 1.0, 1.0)],
|
||||
vec![Point3::new(1.0, -1.0, -3.0), Point3::new(1.0, 0.0, 1.0), Point3::new(1.0, 1.0, -3.0)],
|
||||
];
|
||||
let surface0 = BSplineSurface::new((KnotVec::bezier_knot(2), KnotVec::bezier_knot(2)), ctrl0);
|
||||
let surface1 = BSplineSurface::new((KnotVec::bezier_knot(2), KnotVec::bezier_knot(2)), ctrl1);
|
||||
|
||||
// meshing surface
|
||||
let instant = std::time::Instant::now();
|
||||
let polygon0 = StructuredMesh::from_surface(&surface0, surface0.range_tuple(), TOL).destruct();
|
||||
let polygon1 = StructuredMesh::from_surface(&surface1, surface1.range_tuple(), TOL).destruct();
|
||||
println!("Meshing Surfaces: {}s", instant.elapsed().as_secs_f64());
|
||||
// extract intersection curves
|
||||
let instant = std::time::Instant::now();
|
||||
let curves = intersection_curves(surface0, &polygon0, surface1, &polygon1, TOL);
|
||||
println!(
|
||||
"Extracting Intersection: {}s",
|
||||
instant.elapsed().as_secs_f64()
|
||||
);
|
||||
assert_eq!(curves.len(), 1);
|
||||
let curve = curves[0].1.clone().unwrap();
|
||||
const N: usize = 100;
|
||||
for i in 0..N {
|
||||
let t1 = curve.range_tuple().1;
|
||||
let t = t1 * i as f64 / N as f64;
|
||||
let pt = curve.subs(t);
|
||||
assert_near!(pt.distance(Point3::origin()) * 0.5, f64::sqrt(0.5) * 0.5);
|
||||
}
|
||||
}
|
||||
@@ -1,723 +0,0 @@
|
||||
#![allow(clippy::many_single_char_names)]
|
||||
|
||||
use super::*;
|
||||
use super::history::Lineage;
|
||||
use rustc_hash::FxHashMap as HashMap;
|
||||
use truck_base::cgmath64::*;
|
||||
use truck_geometry::prelude::IntersectionCurve;
|
||||
use truck_meshalgo::prelude::*;
|
||||
use truck_topology::{Vertex, *};
|
||||
|
||||
type PolylineCurve = truck_meshalgo::prelude::PolylineCurve<Point3>;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum ShapesOpStatus {
|
||||
Unknown,
|
||||
And,
|
||||
Or,
|
||||
}
|
||||
|
||||
impl ShapesOpStatus {
|
||||
fn not(self) -> Self {
|
||||
match self {
|
||||
Self::Unknown => Self::Unknown,
|
||||
Self::And => Self::Or,
|
||||
Self::Or => Self::And,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BoundaryWire<P, C> {
|
||||
wire: Wire<P, C>,
|
||||
status: ShapesOpStatus,
|
||||
}
|
||||
|
||||
impl<P, C> BoundaryWire<P, C> {
|
||||
#[inline(always)]
|
||||
pub fn new(wire: Wire<P, C>, status: ShapesOpStatus) -> Self { Self { wire, status } }
|
||||
#[inline(always)]
|
||||
pub fn status(&self) -> ShapesOpStatus { self.status }
|
||||
#[inline(always)]
|
||||
pub fn invert(&mut self) {
|
||||
self.wire.invert();
|
||||
self.status = self.status.not();
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn inverse(&self) -> Self {
|
||||
Self {
|
||||
wire: self.wire.inverse(),
|
||||
status: self.status.not(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ShapesOpStatus {
|
||||
fn from_is_curve<C, S>(curve: &IntersectionCurve<C, S>) -> Option<ShapesOpStatus>
|
||||
where
|
||||
C: ParametricCurve3D + BoundedCurve,
|
||||
S: ParametricSurface3D + SearchNearestParameter<D2, Point = Point3>, {
|
||||
let (t0, t1) = curve.range_tuple();
|
||||
let t = (t0 + t1) / 2.0;
|
||||
let (_, pt0, pt1) = curve.search_triple(t)?;
|
||||
let der = curve.leader().der(t);
|
||||
let normal0 = curve.surface0().normal(pt0[0], pt0[1]);
|
||||
let normal1 = curve.surface1().normal(pt1[0], pt1[1]);
|
||||
match normal0.cross(der).dot(normal1) > 0.0 {
|
||||
true => Some(ShapesOpStatus::Or),
|
||||
false => Some(ShapesOpStatus::And),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, C> std::ops::Deref for BoundaryWire<P, C> {
|
||||
type Target = Wire<P, C>;
|
||||
#[inline(always)]
|
||||
fn deref(&self) -> &Self::Target { &self.wire }
|
||||
}
|
||||
|
||||
impl<P, C> std::ops::DerefMut for BoundaryWire<P, C> {
|
||||
#[inline(always)]
|
||||
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.wire }
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Loops<P, C>(Vec<BoundaryWire<P, C>>);
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct LoopsStore<P, C>(Vec<Loops<P, C>>);
|
||||
|
||||
impl<P, C> std::ops::Deref for Loops<P, C> {
|
||||
type Target = Vec<BoundaryWire<P, C>>;
|
||||
#[inline(always)]
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
|
||||
impl<P, C> std::ops::DerefMut for Loops<P, C> {
|
||||
#[inline(always)]
|
||||
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
|
||||
}
|
||||
|
||||
impl<P, C> std::ops::Deref for LoopsStore<P, C> {
|
||||
type Target = Vec<Loops<P, C>>;
|
||||
#[inline(always)]
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
|
||||
impl<P, C> std::ops::DerefMut for LoopsStore<P, C> {
|
||||
#[inline(always)]
|
||||
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
|
||||
}
|
||||
|
||||
impl<P, C> FromIterator<BoundaryWire<P, C>> for Loops<P, C> {
|
||||
#[inline(always)]
|
||||
fn from_iter<I: IntoIterator<Item = BoundaryWire<P, C>>>(iter: I) -> Self {
|
||||
Self(Vec::from_iter(iter))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, P, C, S> From<&'a Face<P, C, S>> for Loops<P, C> {
|
||||
#[inline(always)]
|
||||
fn from(face: &'a Face<P, C, S>) -> Loops<P, C> {
|
||||
face.absolute_boundaries()
|
||||
.iter()
|
||||
.map(|wire| BoundaryWire::new(wire.clone(), ShapesOpStatus::Unknown))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, P: 'a, C: 'a, S: 'a> FromIterator<&'a Face<P, C, S>> for LoopsStore<P, C> {
|
||||
fn from_iter<I: IntoIterator<Item = &'a Face<P, C, S>>>(iter: I) -> Self {
|
||||
Self(iter.into_iter().map(Loops::from).collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, P, C> IntoIterator for &'a LoopsStore<P, C> {
|
||||
type Item = <&'a Vec<Loops<P, C>> as IntoIterator>::Item;
|
||||
type IntoIter = <&'a Vec<Loops<P, C>> as IntoIterator>::IntoIter;
|
||||
fn into_iter(self) -> Self::IntoIter { self.0.iter() }
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Copy, PartialEq)]
|
||||
enum ParameterKind {
|
||||
Front,
|
||||
Back,
|
||||
Inner(f64),
|
||||
}
|
||||
|
||||
impl ParameterKind {
|
||||
fn try_new(t: f64, (t0, t1): (f64, f64)) -> Option<ParameterKind> {
|
||||
if t0.near(&t) {
|
||||
Some(ParameterKind::Front)
|
||||
} else if t1.near(&t) {
|
||||
Some(ParameterKind::Back)
|
||||
} else if t0 < t && t < t1 {
|
||||
Some(ParameterKind::Inner(t))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Copy + Tolerance, C: Clone> Loops<P, C> {
|
||||
fn search_parameter(&self, pt: P) -> Option<(usize, usize, ParameterKind)>
|
||||
where C: BoundedCurve<Point = P> + SearchParameter<D1, Point = P> {
|
||||
self.iter()
|
||||
.enumerate()
|
||||
.flat_map(move |(i, wire)| wire.iter().enumerate().map(move |(j, edge)| (i, j, edge)))
|
||||
.find_map(|(i, j, edge)| {
|
||||
let curve = edge.curve();
|
||||
curve.search_parameter(pt, None, 1).and_then(|t| {
|
||||
// SolidCraft: a point at an end of the curve is that end, however far its
|
||||
// parameter is from the end's. The parameter alone is no distance: on a
|
||||
// polyline it counts segments, and an intersection curve ending 2e-7 mm from
|
||||
// another's end (two walls of a glyph meeting a face) read 1.4e-6 before it,
|
||||
// so it was cut into a sliver and the result's shell never closed (#391).
|
||||
let kind = if pt.near(&curve.front()) {
|
||||
ParameterKind::Front
|
||||
} else if pt.near(&curve.back()) {
|
||||
ParameterKind::Back
|
||||
} else {
|
||||
ParameterKind::try_new(t, curve.range_tuple())?
|
||||
};
|
||||
Some((i, j, kind))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// SolidCraft: `replaced(old, new)` is told of every edge rebuilt on the new vertex.
|
||||
fn change_vertex(
|
||||
&mut self,
|
||||
old_vertex: &Vertex<P>,
|
||||
new_vertex: &Vertex<P>,
|
||||
emap: &mut HashMap<EdgeID<C>, Edge<P, C>>,
|
||||
replaced: &mut impl FnMut(EdgeID<C>, EdgeID<C>),
|
||||
) {
|
||||
self.iter_mut()
|
||||
.flat_map(|wire| wire.iter_mut())
|
||||
.for_each(|edge| {
|
||||
let mut new_edge = if edge.absolute_front() == old_vertex {
|
||||
emap.entry(edge.id()).or_insert_with(|| {
|
||||
Edge::new(new_vertex, edge.absolute_back(), edge.curve())
|
||||
})
|
||||
} else if edge.absolute_back() == old_vertex {
|
||||
emap.entry(edge.id()).or_insert_with(|| {
|
||||
Edge::new(edge.absolute_front(), new_vertex, edge.curve())
|
||||
})
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
.clone();
|
||||
replaced(edge.id(), new_edge.id());
|
||||
if !edge.orientation() {
|
||||
new_edge.invert();
|
||||
}
|
||||
// Remove the edge from the HashMap when it is no longer there because ID reassignment will occur.
|
||||
if edge.count() == 1 {
|
||||
emap.remove(&edge.id());
|
||||
}
|
||||
*edge = new_edge;
|
||||
})
|
||||
}
|
||||
|
||||
fn swap_edge_into_wire(&mut self, edge_id: EdgeID<C>, new_wire: &Wire<P, C>) {
|
||||
self.iter_mut().for_each(|wire| {
|
||||
let mut iter = wire.iter().enumerate();
|
||||
if let Some((idx, edge)) = iter.find(|(_, edge)| edge.id() == edge_id) {
|
||||
if edge.orientation() {
|
||||
wire.swap_edge_into_wire(idx, new_wire.clone());
|
||||
} else {
|
||||
wire.swap_edge_into_wire(idx, new_wire.inverse());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn add_independent_loop(&mut self, r#loop: BoundaryWire<P, C>) {
|
||||
self.push(r#loop.inverse());
|
||||
self.push(r#loop);
|
||||
}
|
||||
|
||||
fn add_edge(
|
||||
&mut self,
|
||||
edge0: Edge<P, C>,
|
||||
status: ShapesOpStatus,
|
||||
) -> [Option<(usize, usize)>; 2] {
|
||||
self.add_edge_choosing(edge0, status, &|_, _, _| None)
|
||||
}
|
||||
|
||||
// SolidCraft: the edge added at the visits `chosen` (found for the same edge in the mirror
|
||||
// store, whose wires and edges lie in the same order); the default where they are no visits
|
||||
// of its ends.
|
||||
fn add_edge_at(&mut self, edge0: Edge<P, C>, status: ShapesOpStatus, chosen: [Option<(usize, usize)>; 2]) -> [Option<(usize, usize)>; 2] {
|
||||
self.add_edge_choosing(edge0, status, &|_, at_back, at_front| {
|
||||
let held = |visits: &[(usize, usize)], c: Option<(usize, usize)>| c.is_none_or(|v| visits.contains(&v));
|
||||
(held(at_back, chosen[0]) && held(at_front, chosen[1])).then_some(chosen)
|
||||
})
|
||||
}
|
||||
|
||||
// SolidCraft: `pick(loops, at_back, at_front)` may name the visits (wire, edge) the edge is
|
||||
// added at, from the visits of a wire to its back and front vertices; `None` leaves it to the
|
||||
// default below.
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn add_edge_choosing(
|
||||
&mut self,
|
||||
edge0: Edge<P, C>,
|
||||
status: ShapesOpStatus,
|
||||
pick: &dyn Fn(&Self, &[(usize, usize)], &[(usize, usize)]) -> Option<[Option<(usize, usize)>; 2]>,
|
||||
) -> [Option<(usize, usize)>; 2] {
|
||||
// SolidCraft: a vertex several intersection curves end on (the tangent point of two equal
|
||||
// cylinders, say) is visited by more than one wire. The edge belongs where both of its
|
||||
// ends are: a wire holding the visit at each end. Taking the first visit of each end
|
||||
// joined wires that are different regions of the face, and the face came out with a wire
|
||||
// visiting a vertex twice.
|
||||
let starting_at = |loops: &Self, vertex: &Vertex<P>| -> Vec<(usize, usize)> {
|
||||
loops
|
||||
.iter()
|
||||
.enumerate()
|
||||
.flat_map(|(i, wire)| wire.iter().enumerate().filter(|(_, edge)| edge.front() == vertex).map(move |(j, _)| (i, j)))
|
||||
.collect()
|
||||
};
|
||||
let (at_back, at_front) = (starting_at(self, edge0.back()), starting_at(self, edge0.front()));
|
||||
let together = at_back
|
||||
.iter()
|
||||
.find_map(|&(i, j)| at_front.iter().find(|&&(k, _)| k == i).map(|&(_, l)| ((i, j), (i, l))));
|
||||
let (a, b) = match (pick(self, &at_back, &at_front), together) {
|
||||
(Some([a, b]), _) => (a, b),
|
||||
(None, Some((a, b))) => (Some(a), Some(b)),
|
||||
(None, None) => (at_back.first().copied(), at_front.first().copied()),
|
||||
};
|
||||
if let Some((wire_index0, edge_index0)) = a {
|
||||
self[wire_index0].rotate_left(edge_index0);
|
||||
self[wire_index0].push_front(edge0.clone());
|
||||
self[wire_index0].push_back(edge0.inverse());
|
||||
}
|
||||
match (a, b) {
|
||||
(Some((wire_index0, edge_index0)), Some((wire_index1, edge_index1))) => {
|
||||
if wire_index0 == wire_index1 {
|
||||
let len = self[wire_index0].len() - 2;
|
||||
let edge_index1 = (len + edge_index1 - edge_index0) % len + 1;
|
||||
let new_wire = self[wire_index0].split_off(edge_index1);
|
||||
self[wire_index0].status = status;
|
||||
self.push(BoundaryWire::new(new_wire, status.not()));
|
||||
} else {
|
||||
let mut new_wire0 = self[wire_index1].clone();
|
||||
let mut new_wire1 = new_wire0.split_off(edge_index1);
|
||||
new_wire0.append(&mut self[wire_index0]);
|
||||
new_wire0.append(&mut new_wire1);
|
||||
self[wire_index0] = new_wire0;
|
||||
self.swap_remove(wire_index1);
|
||||
}
|
||||
}
|
||||
(None, Some((wire_index1, edge_index1))) => {
|
||||
self[wire_index1].rotate_left(edge_index1);
|
||||
self[wire_index1].push_front(edge0.inverse());
|
||||
self[wire_index1].push_back(edge0);
|
||||
}
|
||||
(None, None) => self.push(BoundaryWire::new(
|
||||
vec![edge0.inverse(), edge0].into(),
|
||||
ShapesOpStatus::Unknown,
|
||||
)),
|
||||
_ => {}
|
||||
}
|
||||
[a, b]
|
||||
}
|
||||
}
|
||||
|
||||
/// SolidCraft: where an edge leaves a vertex, as a unit vector: along the polyline from its
|
||||
/// front (or back) vertex to the first point a tenth of its length away, which is steadier
|
||||
/// than its first segment where the mesh it was traced on is noisy.
|
||||
fn leaving(edge: &Edge<Point3, PolylineCurve>, from_front: bool) -> Option<Vector3> {
|
||||
let curve = edge.curve();
|
||||
let n = curve.len();
|
||||
if n < 2 {
|
||||
return None;
|
||||
}
|
||||
let total: f64 = (1..n).map(|k| (curve[k] - curve[k - 1]).magnitude()).sum();
|
||||
let (origin, order): (Point3, Vec<usize>) = if from_front { (curve[0], (1..n).collect()) } else { (curve[n - 1], (0..n - 1).rev().collect()) };
|
||||
let mut found = None;
|
||||
for k in order {
|
||||
let d = curve[k] - origin;
|
||||
found = Some(d);
|
||||
if d.magnitude() >= 0.1 * total {
|
||||
break;
|
||||
}
|
||||
}
|
||||
found.filter(|d| d.magnitude() > 1e-12 && d.magnitude().is_finite()).map(|d| d.normalize())
|
||||
}
|
||||
|
||||
/// SolidCraft: the counterclockwise angle about `normal` from `from` to `to`, in `[0, 2π)`.
|
||||
fn sweep(from: Vector3, to: Vector3, normal: Vector3) -> Option<f64> {
|
||||
let e1 = from - normal * from.dot(normal);
|
||||
if e1.magnitude() < 1e-9 {
|
||||
return None;
|
||||
}
|
||||
let e1 = e1.normalize();
|
||||
let e2 = normal.cross(e1);
|
||||
let angle = to.dot(e2).atan2(to.dot(e1));
|
||||
Some(if angle < 0.0 { angle + 2.0 * std::f64::consts::PI } else { angle })
|
||||
}
|
||||
|
||||
impl Loops<Point3, PolylineCurve> {
|
||||
/// SolidCraft: the visit among `visits` (of the vertex `at`) whose corner holds the direction
|
||||
/// `d` the new edge leaves in: the wedge swept counterclockwise about `normal` from the
|
||||
/// wire's outgoing edge to the way back along its incoming one. `None` when no corner can be
|
||||
/// told (a single visit is returned as it is).
|
||||
fn corner_holding(&self, visits: &[(usize, usize)], d: Vector3, normal: Vector3) -> Option<(usize, usize)> {
|
||||
if let [only] = visits {
|
||||
return Some(*only);
|
||||
}
|
||||
let tau = 2.0 * std::f64::consts::PI;
|
||||
visits
|
||||
.iter()
|
||||
.filter_map(|&(i, j)| {
|
||||
let wire = self.get(i)?;
|
||||
let out = leaving(wire.get(j)?, true)?;
|
||||
let before = wire.get((j + wire.len() - 1) % wire.len())?;
|
||||
let back = leaving(before, false)?;
|
||||
let wedge = sweep(out, back, normal)?;
|
||||
let wedge = if wedge < 1e-6 { tau } else { wedge };
|
||||
let at = sweep(out, d, normal)?;
|
||||
(at > 1e-6 && at < wedge - 1e-6).then_some(((i, j), wedge))
|
||||
})
|
||||
.min_by(|x, y| x.1.total_cmp(&y.1))
|
||||
.map(|(visit, _)| visit)
|
||||
}
|
||||
|
||||
/// SolidCraft: [`Loops::add_edge`], with the corner at each end of the edge told by its
|
||||
/// direction there (`normals`: the face's normal at its back and front vertices) where a
|
||||
/// vertex is visited more than once.
|
||||
fn add_edge_by_angle(&mut self, edge0: Edge<Point3, PolylineCurve>, status: ShapesOpStatus, normals: [Option<Vector3>; 2]) -> [Option<(usize, usize)>; 2] {
|
||||
let (d_back, d_front) = (leaving(&edge0, false), leaving(&edge0, true));
|
||||
let pick = |loops: &Self, at_back: &[(usize, usize)], at_front: &[(usize, usize)]| {
|
||||
if at_back.len() < 2 && at_front.len() < 2 {
|
||||
return None;
|
||||
}
|
||||
let a = match (d_back, normals[0]) {
|
||||
(Some(d), Some(n)) => loops.corner_holding(at_back, d, n),
|
||||
_ => None,
|
||||
};
|
||||
let b = match (d_front, normals[1]) {
|
||||
(Some(d), Some(n)) => loops.corner_holding(at_front, d, n),
|
||||
_ => None,
|
||||
};
|
||||
// A vertex visited more than once whose corner could not be told is left to the default.
|
||||
let told = |visits: &[(usize, usize)], c: Option<(usize, usize)>| visits.len() < 2 || c.is_some();
|
||||
(told(at_back, a) && told(at_front, b)).then_some([a.or_else(|| at_back.first().copied()), b.or_else(|| at_front.first().copied())])
|
||||
};
|
||||
self.add_edge_choosing(edge0, status, &pick)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Copy + Tolerance, C: Clone> LoopsStore<P, C> {
|
||||
#[inline(always)]
|
||||
fn change_vertex(
|
||||
&mut self,
|
||||
old_vertex: &Vertex<P>,
|
||||
new_vertex: &Vertex<P>,
|
||||
emap: &mut HashMap<EdgeID<C>, Edge<P, C>>,
|
||||
replaced: &mut impl FnMut(EdgeID<C>, EdgeID<C>),
|
||||
) {
|
||||
self.iter_mut()
|
||||
.for_each(|loops| loops.change_vertex(old_vertex, new_vertex, emap, replaced));
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn swap_edge_into_wire(&mut self, edge_id: EdgeID<C>, new_wire: &Wire<P, C>) {
|
||||
self.iter_mut()
|
||||
.for_each(|loops| loops.swap_edge_into_wire(edge_id, new_wire))
|
||||
}
|
||||
|
||||
fn add_polygon_vertex(
|
||||
&mut self,
|
||||
loops_index: usize,
|
||||
v: &Vertex<P>,
|
||||
emap: &mut HashMap<EdgeID<C>, Edge<P, C>>,
|
||||
) -> Option<(usize, usize, ParameterKind)>
|
||||
where
|
||||
C: Cut<Point = P> + SearchParameter<D1, Point = P>,
|
||||
{
|
||||
let pt = v.point();
|
||||
let (wire_index, edge_index, kind) = self[loops_index].search_parameter(pt)?;
|
||||
match kind {
|
||||
ParameterKind::Front => {
|
||||
let old_vertex = self[loops_index][wire_index][edge_index]
|
||||
.absolute_front()
|
||||
.clone();
|
||||
self.change_vertex(&old_vertex, v, emap, &mut |_, _| {});
|
||||
}
|
||||
ParameterKind::Back => {
|
||||
let old_vertex = self[loops_index][wire_index][edge_index]
|
||||
.absolute_back()
|
||||
.clone();
|
||||
self.change_vertex(&old_vertex, v, emap, &mut |_, _| {});
|
||||
}
|
||||
ParameterKind::Inner(t) => {
|
||||
let edge = self[loops_index][wire_index][edge_index].absolute_clone();
|
||||
let edge_id = edge.id();
|
||||
let (edge0, edge1) = edge.cut_with_parameter(v, t)?;
|
||||
let new_wire: Wire<_, _> = vec![edge0, edge1].into();
|
||||
self.swap_edge_into_wire(edge_id, &new_wire);
|
||||
}
|
||||
}
|
||||
Some((wire_index, edge_index, kind))
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> LoopsStore<Point3, C> {
|
||||
fn add_geom_vertex<S>(
|
||||
&mut self,
|
||||
(loops_index, wire_index, edge_index): (usize, usize, usize),
|
||||
v: &Vertex<Point3>,
|
||||
kind: ParameterKind,
|
||||
another_surface: &S,
|
||||
emap: &mut HashMap<EdgeID<C>, Edge<Point3, C>>,
|
||||
lineage: &mut Lineage<C, S>,
|
||||
) -> Option<()>
|
||||
where
|
||||
C: Cut<Point = Point3, Vector = Vector3> + SearchNearestParameter<D1, Point = Point3>,
|
||||
S: ParametricSurface3D + SearchNearestParameter<D2, Point = Point3>,
|
||||
{
|
||||
match kind {
|
||||
ParameterKind::Front => {
|
||||
let old_vertex = self[loops_index][wire_index][edge_index]
|
||||
.absolute_front()
|
||||
.clone();
|
||||
v.set_point(old_vertex.point());
|
||||
self.change_vertex(&old_vertex, v, emap, &mut |old, new| lineage.edge_piece(old, new));
|
||||
}
|
||||
ParameterKind::Back => {
|
||||
let old_vertex = self[loops_index][wire_index][edge_index]
|
||||
.absolute_back()
|
||||
.clone();
|
||||
v.set_point(old_vertex.point());
|
||||
self.change_vertex(&old_vertex, v, emap, &mut |old, new| lineage.edge_piece(old, new));
|
||||
}
|
||||
ParameterKind::Inner(_) => {
|
||||
let curve = self[loops_index][wire_index][edge_index].curve();
|
||||
let (pt, t, _) =
|
||||
curve_surface_projection(&curve, None, another_surface, None, v.point(), 100)?;
|
||||
v.set_point(pt);
|
||||
let edge = self[loops_index][wire_index][edge_index].absolute_clone();
|
||||
let edge_id = edge.id();
|
||||
let (edge0, edge1) = edge.cut_with_parameter(v, t)?;
|
||||
lineage.edge_piece(edge_id, edge0.id());
|
||||
lineage.edge_piece(edge_id, edge1.id());
|
||||
let new_wire: Wire<_, _> = vec![edge0, edge1].into();
|
||||
self.swap_edge_into_wire(edge_id, &new_wire);
|
||||
}
|
||||
}
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
|
||||
fn curve_surface_projection<C, S>(
|
||||
curve: &C,
|
||||
curve_hint: Option<f64>,
|
||||
surface: &S,
|
||||
surface_hint: Option<(f64, f64)>,
|
||||
point: Point3,
|
||||
trials: usize,
|
||||
) -> Option<(Point3, f64, Point2)>
|
||||
where
|
||||
C: ParametricCurve3D + SearchNearestParameter<D1, Point = Point3>,
|
||||
S: ParametricSurface3D + SearchNearestParameter<D2, Point = Point3>,
|
||||
{
|
||||
if trials == 0 {
|
||||
return None;
|
||||
}
|
||||
let t = curve.search_nearest_parameter(point, curve_hint, 10)?;
|
||||
let pt0 = curve.subs(t);
|
||||
let (u, v) = surface.search_nearest_parameter(point, surface_hint, 10)?;
|
||||
let pt1 = surface.subs(u, v);
|
||||
if point.near(&pt0) && point.near(&pt1) && pt0.near(&pt1) {
|
||||
Some((point, t, Point2::new(u, v)))
|
||||
} else {
|
||||
let l = curve.der(t);
|
||||
let n = surface.normal(u, v);
|
||||
let t0 = (pt1 - pt0).dot(n) / l.dot(n);
|
||||
curve_surface_projection(
|
||||
curve,
|
||||
Some(t),
|
||||
surface,
|
||||
Some((u, v)),
|
||||
pt0 + t0 * l,
|
||||
trials - 1,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn create_independent_loop<P, C, D>(mut poly_curve0: C) -> Wire<P, D>
|
||||
where
|
||||
C: Cut<Point = P>,
|
||||
D: From<C>, {
|
||||
let (t0, t1) = poly_curve0.range_tuple();
|
||||
let t = (t0 + t1) / 2.0;
|
||||
let poly_curve1 = poly_curve0.cut(t);
|
||||
let v0 = Vertex::new(poly_curve0.front());
|
||||
let v1 = Vertex::new(poly_curve1.front());
|
||||
let edge0 = Edge::new(&v0, &v1, poly_curve0.into());
|
||||
let edge1 = Edge::new(&v1, &v0, poly_curve1.into());
|
||||
vec![edge0, edge1].into()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct LoopsStoreQuadruple<C> {
|
||||
pub geom_loops_store0: LoopsStore<Point3, C>,
|
||||
pub poly_loops_store0: LoopsStore<Point3, PolylineCurve>,
|
||||
pub geom_loops_store1: LoopsStore<Point3, C>,
|
||||
pub poly_loops_store1: LoopsStore<Point3, PolylineCurve>,
|
||||
}
|
||||
|
||||
pub fn create_loops_stores<C, S>(
|
||||
geom_shell0: &Shell<Point3, C, S>,
|
||||
poly_shell0: &Shell<Point3, PolylineCurve, Option<PolygonMesh>>,
|
||||
geom_shell1: &Shell<Point3, C, S>,
|
||||
poly_shell1: &Shell<Point3, PolylineCurve, Option<PolygonMesh>>,
|
||||
tol: f64,
|
||||
lineage: &mut Lineage<C, S>,
|
||||
) -> Option<LoopsStoreQuadruple<C>>
|
||||
where
|
||||
C: SearchNearestParameter<D1, Point = Point3>
|
||||
+ SearchParameter<D1, Point = Point3>
|
||||
+ Cut<Point = Point3, Vector = Vector3>
|
||||
+ From<IntersectionCurve<PolylineCurve, S>>,
|
||||
S: ParametricSurface3D + SearchNearestParameter<D2, Point = Point3>,
|
||||
{
|
||||
let mut geom_loops_store0: LoopsStore<_, _> = geom_shell0.face_iter().collect();
|
||||
let mut poly_loops_store0: LoopsStore<_, _> = poly_shell0.face_iter().collect();
|
||||
let mut geom_loops_store1: LoopsStore<_, _> = geom_shell1.face_iter().collect();
|
||||
let mut poly_loops_store1: LoopsStore<_, _> = poly_shell1.face_iter().collect();
|
||||
let store0_len = geom_loops_store0.len();
|
||||
let store1_len = geom_loops_store1.len();
|
||||
(0..store0_len)
|
||||
.flat_map(move |i| (0..store1_len).map(move |j| (i, j)))
|
||||
.try_for_each(|(face_index0, face_index1)| {
|
||||
let ori0 = geom_shell0[face_index0].orientation();
|
||||
let ori1 = geom_shell1[face_index1].orientation();
|
||||
let surface0 = geom_shell0[face_index0].surface();
|
||||
let surface1 = geom_shell1[face_index1].surface();
|
||||
let meeting = [geom_shell0[face_index0].id(), geom_shell1[face_index1].id()];
|
||||
let polygon0 = crate::tr!(poly_shell0[face_index0].surface(), "polygon0");
|
||||
let polygon1 = crate::tr!(poly_shell1[face_index1].surface(), "polygon1");
|
||||
intersection_curve::intersection_curves(
|
||||
surface0.clone(),
|
||||
&polygon0,
|
||||
surface1.clone(),
|
||||
&polygon1,
|
||||
tol,
|
||||
)
|
||||
.into_iter()
|
||||
.try_for_each(|(polyline, intersection_curve)| {
|
||||
let mut intersection_curve = crate::tr!(intersection_curve, "intersection curve").into();
|
||||
let status = crate::tr!(ShapesOpStatus::from_is_curve(&intersection_curve), "status");
|
||||
let (status0, status1) = match (ori0, ori1) {
|
||||
(true, true) => (status, status.not()),
|
||||
(true, false) => (status.not(), status.not()),
|
||||
(false, true) => (status, status),
|
||||
(false, false) => (status.not(), status),
|
||||
};
|
||||
if polyline.front().near(&polyline.back()) {
|
||||
let poly_wire = create_independent_loop(polyline);
|
||||
poly_loops_store0[face_index0]
|
||||
.add_independent_loop(BoundaryWire::new(poly_wire.clone(), status0));
|
||||
poly_loops_store1[face_index1]
|
||||
.add_independent_loop(BoundaryWire::new(poly_wire, status1));
|
||||
let geom_wire = create_independent_loop(intersection_curve);
|
||||
geom_wire.iter().for_each(|edge| lineage.meet(edge.id(), meeting));
|
||||
geom_loops_store0[face_index0]
|
||||
.add_independent_loop(BoundaryWire::new(geom_wire.clone(), status0));
|
||||
geom_loops_store1[face_index1]
|
||||
.add_independent_loop(BoundaryWire::new(geom_wire, status1));
|
||||
} else {
|
||||
let pv0 = Vertex::new(polyline.front());
|
||||
let pv1 = Vertex::new(polyline.back());
|
||||
let gv0 = Vertex::new(polyline.front());
|
||||
let gv1 = Vertex::new(polyline.back());
|
||||
let mut pemap0 = HashMap::default();
|
||||
let mut pemap1 = HashMap::default();
|
||||
let mut gemap0 = HashMap::default();
|
||||
let mut gemap1 = HashMap::default();
|
||||
let idx00 =
|
||||
poly_loops_store0.add_polygon_vertex(face_index0, &pv0, &mut pemap0);
|
||||
if let Some((wire_index, edge_index, kind)) = idx00 {
|
||||
crate::tr!(geom_loops_store0.add_geom_vertex(
|
||||
(face_index0, wire_index, edge_index),
|
||||
&gv0,
|
||||
kind,
|
||||
&surface1,
|
||||
&mut gemap0,
|
||||
lineage,
|
||||
), "add_geom_vertex 1");
|
||||
let polyline = intersection_curve.editor().leader;
|
||||
*polyline.first_mut().unwrap() = gv0.point();
|
||||
}
|
||||
let idx01 =
|
||||
poly_loops_store0.add_polygon_vertex(face_index0, &pv1, &mut pemap1);
|
||||
if let Some((wire_index, edge_index, kind)) = idx01 {
|
||||
crate::tr!(geom_loops_store0.add_geom_vertex(
|
||||
(face_index0, wire_index, edge_index),
|
||||
&gv1,
|
||||
kind,
|
||||
&surface1,
|
||||
&mut gemap1,
|
||||
lineage,
|
||||
), "add_geom_vertex 2");
|
||||
let polyline = intersection_curve.editor().leader;
|
||||
*polyline.last_mut().unwrap() = gv1.point();
|
||||
}
|
||||
let idx10 =
|
||||
poly_loops_store1.add_polygon_vertex(face_index1, &pv0, &mut pemap0);
|
||||
if let Some((wire_index, edge_index, kind)) = idx10 {
|
||||
crate::tr!(geom_loops_store1.add_geom_vertex(
|
||||
(face_index1, wire_index, edge_index),
|
||||
&gv0,
|
||||
kind,
|
||||
&surface0,
|
||||
&mut gemap0,
|
||||
lineage,
|
||||
), "add_geom_vertex 3");
|
||||
let polyline = intersection_curve.editor().leader;
|
||||
*polyline.first_mut().unwrap() = gv0.point();
|
||||
}
|
||||
let idx11 =
|
||||
poly_loops_store1.add_polygon_vertex(face_index1, &pv1, &mut pemap1);
|
||||
if let Some((wire_index, edge_index, kind)) = idx11 {
|
||||
crate::tr!(geom_loops_store1.add_geom_vertex(
|
||||
(face_index1, wire_index, edge_index),
|
||||
&gv1,
|
||||
kind,
|
||||
&surface0,
|
||||
&mut gemap1,
|
||||
lineage,
|
||||
), "add_geom_vertex 4");
|
||||
let polyline = intersection_curve.editor().leader;
|
||||
*polyline.last_mut().unwrap() = gv1.point();
|
||||
}
|
||||
let pedge = Edge::new(&pv0, &pv1, polyline);
|
||||
let gedge = Edge::new(&gv0, &gv1, intersection_curve.into());
|
||||
lineage.meet(gedge.id(), meeting);
|
||||
let normal_at = |surface: &S, p: Point3| -> Option<Vector3> {
|
||||
let (u, v) = surface.search_nearest_parameter(p, None, 100)?;
|
||||
Some(surface.normal(u, v)).filter(|n| n.magnitude() > 0.0 && n.magnitude().is_finite())
|
||||
};
|
||||
let normals0 = [normal_at(&surface0, gv1.point()), normal_at(&surface0, gv0.point())];
|
||||
let normals1 = [normal_at(&surface1, gv1.point()), normal_at(&surface1, gv0.point())];
|
||||
let chosen0 = poly_loops_store0[face_index0].add_edge_by_angle(pedge.clone(), status0, normals0);
|
||||
geom_loops_store0[face_index0].add_edge_at(gedge.clone(), status0, chosen0);
|
||||
let chosen1 = poly_loops_store1[face_index1].add_edge_by_angle(pedge, status1, normals1);
|
||||
geom_loops_store1[face_index1].add_edge_at(gedge, status1, chosen1);
|
||||
}
|
||||
Some(())
|
||||
})
|
||||
})?;
|
||||
Some(LoopsStoreQuadruple {
|
||||
geom_loops_store0,
|
||||
poly_loops_store0,
|
||||
geom_loops_store1,
|
||||
poly_loops_store1,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@@ -1,776 +0,0 @@
|
||||
use super::*;
|
||||
use std::fmt::Debug;
|
||||
use truck_geometry::prelude::*;
|
||||
const TOL: f64 = 0.01;
|
||||
|
||||
crate::impl_from!(
|
||||
NurbsCurve<Vector4>,
|
||||
IntersectionCurve<PolylineCurve, BSplineSurface<Point3>>
|
||||
);
|
||||
type AlternativeIntersection = crate::alternative::Alternative<
|
||||
NurbsCurve<Vector4>,
|
||||
IntersectionCurve<PolylineCurve, BSplineSurface<Point3>>,
|
||||
>;
|
||||
|
||||
struct DebugDisplay<'a, T, Format> {
|
||||
entity: &'a T,
|
||||
format: Format,
|
||||
}
|
||||
|
||||
impl<'a, P: Debug, C: Debug> Debug for DebugDisplay<'a, Loops<P, C>, WireDisplayFormat> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_tuple("Loops")
|
||||
.field(
|
||||
&self
|
||||
.entity
|
||||
.0
|
||||
.iter()
|
||||
.map(|wire| wire.display(self.format))
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, P: Debug, C: Debug> Debug for DebugDisplay<'a, LoopsStore<P, C>, WireDisplayFormat> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_list()
|
||||
.entries(self.entity.0.iter().map(|loops| DebugDisplay {
|
||||
entity: loops,
|
||||
format: self.format,
|
||||
}))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: Debug, C: Debug> LoopsStore<P, C> {
|
||||
fn display(&self, format: WireDisplayFormat) -> DebugDisplay<'_, Self, WireDisplayFormat> {
|
||||
DebugDisplay {
|
||||
entity: self,
|
||||
format,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parabola_surfaces() -> (BSplineSurface<Point3>, BSplineSurface<Point3>) {
|
||||
// define surfaces
|
||||
#[rustfmt::skip]
|
||||
let ctrl0 = vec![
|
||||
vec![Point3::new(-1.0, -1.0, 3.0), Point3::new(-1.0, 0.0, -1.0), Point3::new(-1.0, 1.0, 3.0)],
|
||||
vec![Point3::new(0.0, -1.0, -1.0), Point3::new(0.0, 0.0, -5.0), Point3::new(0.0, 1.0, -1.0)],
|
||||
vec![Point3::new(1.0, -1.0, 3.0), Point3::new(1.0, 0.0, -1.0), Point3::new(1.0, 1.0, 3.0)],
|
||||
];
|
||||
#[rustfmt::skip]
|
||||
let ctrl1 = vec![
|
||||
vec![Point3::new(-1.0, -1.0, -3.0), Point3::new(-1.0, 0.0, 1.0), Point3::new(-1.0, 1.0, -3.0)],
|
||||
vec![Point3::new(0.0, -1.0, 1.0), Point3::new(0.0, 0.0, 5.0), Point3::new(0.0, 1.0, 1.0)],
|
||||
vec![Point3::new(1.0, -1.0, -3.0), Point3::new(1.0, 0.0, 1.0), Point3::new(1.0, 1.0, -3.0)],
|
||||
];
|
||||
(
|
||||
BSplineSurface::new((KnotVec::bezier_knot(2), KnotVec::bezier_knot(2)), ctrl0),
|
||||
BSplineSurface::new((KnotVec::bezier_knot(2), KnotVec::bezier_knot(2)), ctrl1),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn independent_intersection() {
|
||||
// prepare geoetries
|
||||
let arc00: AlternativeIntersection = NurbsCurve::new(BSplineCurve::new(
|
||||
KnotVec::bezier_knot(2),
|
||||
vec![
|
||||
Vector4::new(1.0, 0.0, 1.0, 1.0),
|
||||
Vector4::new(0.0, 1.0, 0.0, 0.0),
|
||||
Vector4::new(-1.0, 0.0, 1.0, 1.0),
|
||||
],
|
||||
))
|
||||
.into();
|
||||
let arc01 = NurbsCurve::new(BSplineCurve::new(
|
||||
KnotVec::bezier_knot(2),
|
||||
vec![
|
||||
Vector4::new(-1.0, 0.0, 1.0, 1.0),
|
||||
Vector4::new(0.0, -1.0, 0.0, 0.0),
|
||||
Vector4::new(1.0, 0.0, 1.0, 1.0),
|
||||
],
|
||||
))
|
||||
.into();
|
||||
let arc10: AlternativeIntersection = NurbsCurve::new(BSplineCurve::new(
|
||||
KnotVec::bezier_knot(2),
|
||||
vec![
|
||||
Vector4::new(1.0, 0.0, -1.0, 1.0),
|
||||
Vector4::new(0.0, 1.0, 0.0, 0.0),
|
||||
Vector4::new(-1.0, 0.0, -1.0, 1.0),
|
||||
],
|
||||
))
|
||||
.into();
|
||||
let arc11 = NurbsCurve::new(BSplineCurve::new(
|
||||
KnotVec::bezier_knot(2),
|
||||
vec![
|
||||
Vector4::new(-1.0, 0.0, -1.0, 1.0),
|
||||
Vector4::new(0.0, -1.0, 0.0, 0.0),
|
||||
Vector4::new(1.0, 0.0, -1.0, 1.0),
|
||||
],
|
||||
))
|
||||
.into();
|
||||
let (surface0, surface1) = parabola_surfaces();
|
||||
|
||||
// prepare topologies
|
||||
let v00 = Vertex::new(Point3::new(1.0, 0.0, 1.0));
|
||||
let v01 = Vertex::new(Point3::new(-1.0, 0.0, 1.0));
|
||||
let v10 = Vertex::new(Point3::new(1.0, 0.0, -1.0));
|
||||
let v11 = Vertex::new(Point3::new(-1.0, 0.0, -1.0));
|
||||
let wire0: Wire<_, _> = vec![Edge::new(&v00, &v01, arc00), Edge::new(&v01, &v00, arc01)].into();
|
||||
let wire1: Wire<_, _> = vec![Edge::new(&v10, &v11, arc10), Edge::new(&v11, &v10, arc11)].into();
|
||||
let geom_shell0: Shell<_, _, _> = vec![Face::new(vec![wire0], surface0).inverse()].into();
|
||||
let geom_shell1: Shell<_, _, _> = vec![Face::new(vec![wire1], surface1)].into();
|
||||
let poly_shell0 = geom_shell0.triangulation(TOL);
|
||||
let poly_shell1 = geom_shell1.triangulation(TOL);
|
||||
|
||||
// exec create loops stores!
|
||||
let LoopsStoreQuadruple {
|
||||
geom_loops_store0,
|
||||
geom_loops_store1,
|
||||
..
|
||||
} = create_loops_stores(&geom_shell0, &poly_shell0, &geom_shell1, &poly_shell1, TOL, &mut Default::default()).unwrap();
|
||||
|
||||
// check the topology
|
||||
let vertex_format = VertexDisplayFormat::AsPoint;
|
||||
let edge_id_format = EdgeDisplayFormat::VerticesTupleAndID { vertex_format };
|
||||
let wire_id_format = WireDisplayFormat::EdgesListTuple {
|
||||
edge_format: edge_id_format,
|
||||
};
|
||||
let edge_geom_format = EdgeDisplayFormat::VerticesTupleAndCurve { vertex_format };
|
||||
let wire_geom_format = WireDisplayFormat::EdgesListTuple {
|
||||
edge_format: edge_geom_format,
|
||||
};
|
||||
assert_eq!(geom_loops_store0.len(), 1);
|
||||
assert_eq!(geom_loops_store0[0].len(), 3);
|
||||
assert_eq!(geom_loops_store0[0][0].len(), 2);
|
||||
assert_eq!(geom_loops_store0[0][1].len(), 2);
|
||||
assert_eq!(geom_loops_store0[0][2].len(), 2);
|
||||
assert!(
|
||||
geom_loops_store0[0][0].is_closed(),
|
||||
"{:?}",
|
||||
geom_loops_store0.display(wire_id_format)
|
||||
);
|
||||
assert!(
|
||||
geom_loops_store0[0][1].is_closed(),
|
||||
"{:?}",
|
||||
geom_loops_store0.display(wire_id_format)
|
||||
);
|
||||
assert!(
|
||||
geom_loops_store0[0][2].is_closed(),
|
||||
"{:?}",
|
||||
geom_loops_store0.display(wire_id_format)
|
||||
);
|
||||
assert!(
|
||||
geom_loops_store0[0][0].is_geometric_consistent(),
|
||||
"{:?}",
|
||||
geom_loops_store0.display(wire_geom_format)
|
||||
);
|
||||
assert!(
|
||||
geom_loops_store0[0][1].is_geometric_consistent(),
|
||||
"{:?}",
|
||||
geom_loops_store0.display(wire_geom_format)
|
||||
);
|
||||
assert!(
|
||||
geom_loops_store0[0][2].is_geometric_consistent(),
|
||||
"{:?}",
|
||||
geom_loops_store0.display(wire_geom_format)
|
||||
);
|
||||
assert_eq!(geom_loops_store1.len(), 1);
|
||||
assert_eq!(geom_loops_store1[0].len(), 3);
|
||||
assert_eq!(geom_loops_store1[0][0].len(), 2);
|
||||
assert_eq!(geom_loops_store1[0][1].len(), 2);
|
||||
assert_eq!(geom_loops_store1[0][2].len(), 2);
|
||||
assert!(
|
||||
geom_loops_store1[0][0].is_closed(),
|
||||
"{:?}",
|
||||
geom_loops_store1.display(wire_id_format)
|
||||
);
|
||||
assert!(
|
||||
geom_loops_store1[0][1].is_closed(),
|
||||
"{:?}",
|
||||
geom_loops_store1.display(wire_id_format)
|
||||
);
|
||||
assert!(
|
||||
geom_loops_store1[0][2].is_closed(),
|
||||
"{:?}",
|
||||
geom_loops_store1.display(wire_id_format)
|
||||
);
|
||||
assert!(
|
||||
geom_loops_store1[0][0].is_geometric_consistent(),
|
||||
"{:?}",
|
||||
geom_loops_store1.display(wire_geom_format)
|
||||
);
|
||||
assert!(
|
||||
geom_loops_store1[0][1].is_geometric_consistent(),
|
||||
"{:?}",
|
||||
geom_loops_store1.display(wire_geom_format)
|
||||
);
|
||||
assert!(
|
||||
geom_loops_store1[0][2].is_geometric_consistent(),
|
||||
"{:?}",
|
||||
geom_loops_store1.display(wire_geom_format)
|
||||
);
|
||||
|
||||
// check the boundary status
|
||||
let mut flags = [true; 3];
|
||||
for i in 0..3 {
|
||||
let bw = &geom_loops_store0[0][i];
|
||||
match bw.status() {
|
||||
ShapesOpStatus::Unknown => {
|
||||
let curve = bw[0].oriented_curve();
|
||||
assert_near!(curve.subs(0.0)[2], 1.0);
|
||||
assert!(flags[0]);
|
||||
flags[0] = false;
|
||||
}
|
||||
ShapesOpStatus::And => {
|
||||
let curve = bw[0].oriented_curve();
|
||||
let pt = curve.subs(0.5) - Point3::origin();
|
||||
let der = curve.der(0.5);
|
||||
assert!(pt.cross(der)[2] > 0.0);
|
||||
assert!(flags[1]);
|
||||
flags[1] = false;
|
||||
}
|
||||
ShapesOpStatus::Or => {
|
||||
let curve = bw[0].oriented_curve();
|
||||
let pt = curve.subs(0.5) - Point3::origin();
|
||||
let der = curve.der(0.5);
|
||||
assert!(pt.cross(der)[2] < 0.0);
|
||||
assert!(flags[2]);
|
||||
flags[2] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut flags = [true; 3];
|
||||
for i in 0..3 {
|
||||
let bw = &geom_loops_store1[0][i];
|
||||
match bw.status() {
|
||||
ShapesOpStatus::Unknown => {
|
||||
let curve = bw[0].oriented_curve();
|
||||
assert_near!(curve.subs(0.0)[2], -1.0);
|
||||
assert!(flags[0]);
|
||||
flags[0] = false;
|
||||
}
|
||||
ShapesOpStatus::Or => {
|
||||
let curve = bw[0].oriented_curve();
|
||||
let pt = curve.subs(0.5) - Point3::origin();
|
||||
let der = curve.der(0.5);
|
||||
assert!(pt.cross(der)[2] < 0.0);
|
||||
assert!(flags[1]);
|
||||
flags[1] = false;
|
||||
}
|
||||
ShapesOpStatus::And => {
|
||||
let curve = bw[0].oriented_curve();
|
||||
let pt = curve.subs(0.5) - Point3::origin();
|
||||
let der = curve.der(0.5);
|
||||
assert!(pt.cross(der)[2] > 0.0);
|
||||
assert!(flags[2]);
|
||||
flags[2] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rotated_intersection() {
|
||||
// prepare geoetries
|
||||
let arc00: AlternativeIntersection = NurbsCurve::new(BSplineCurve::new(
|
||||
KnotVec::bezier_knot(2),
|
||||
vec![
|
||||
Vector4::new(1.0, 0.0, 1.0, 1.0),
|
||||
Vector4::new(0.0, 1.0, 0.0, 0.0),
|
||||
Vector4::new(-1.0, 0.0, 1.0, 1.0),
|
||||
],
|
||||
))
|
||||
.into();
|
||||
let arc01: AlternativeIntersection = NurbsCurve::new(BSplineCurve::new(
|
||||
KnotVec::bezier_knot(2),
|
||||
vec![
|
||||
Vector4::new(-1.0, 0.0, 1.0, 1.0),
|
||||
Vector4::new(0.0, -1.0, 0.0, 0.0),
|
||||
Vector4::new(1.0, 0.0, 1.0, 1.0),
|
||||
],
|
||||
))
|
||||
.into();
|
||||
let arc02: AlternativeIntersection = NurbsCurve::new(BSplineCurve::new(
|
||||
KnotVec::bezier_knot(2),
|
||||
vec![
|
||||
Vector4::new(1.0, 0.0, 1.0, 1.0),
|
||||
Vector4::new(0.0, 0.0, -3.0, 1.0),
|
||||
Vector4::new(-1.0, 0.0, 1.0, 1.0),
|
||||
],
|
||||
))
|
||||
.into();
|
||||
let arc10: AlternativeIntersection = NurbsCurve::new(BSplineCurve::new(
|
||||
KnotVec::bezier_knot(2),
|
||||
vec![
|
||||
Vector4::new(0.0, -1.0, -1.0, 1.0),
|
||||
Vector4::new(1.0, 0.0, 0.0, 0.0),
|
||||
Vector4::new(0.0, 1.0, -1.0, 1.0),
|
||||
],
|
||||
))
|
||||
.into();
|
||||
let arc11: AlternativeIntersection = NurbsCurve::new(BSplineCurve::new(
|
||||
KnotVec::bezier_knot(2),
|
||||
vec![
|
||||
Vector4::new(0.0, 1.0, -1.0, 1.0),
|
||||
Vector4::new(-1.0, 0.0, 0.0, 0.0),
|
||||
Vector4::new(0.0, -1.0, -1.0, 1.0),
|
||||
],
|
||||
))
|
||||
.into();
|
||||
let arc12: AlternativeIntersection = NurbsCurve::new(BSplineCurve::new(
|
||||
KnotVec::bezier_knot(2),
|
||||
vec![
|
||||
Vector4::new(0.0, -1.0, -1.0, 1.0),
|
||||
Vector4::new(0.0, 0.0, 3.0, 1.0),
|
||||
Vector4::new(0.0, 1.0, -1.0, 1.0),
|
||||
],
|
||||
))
|
||||
.into();
|
||||
let (surface0, surface1) = parabola_surfaces();
|
||||
|
||||
let v00 = Vertex::new(Point3::new(1.0, 0.0, 1.0));
|
||||
let v01 = Vertex::new(Point3::new(-1.0, 0.0, 1.0));
|
||||
let edge00 = Edge::new(&v00, &v01, arc00);
|
||||
let edge01 = Edge::new(&v01, &v00, arc01);
|
||||
let edge02 = Edge::new(&v00, &v01, arc02);
|
||||
let wire00: Wire<_, _> = vec![edge00, edge02.inverse()].into();
|
||||
let wire01: Wire<_, _> = vec![edge01, edge02].into();
|
||||
let face00 = Face::new(vec![wire00], surface0.clone());
|
||||
let face01 = Face::new(vec![wire01], surface0);
|
||||
let geom_shell0: Shell<_, _, _> = vec![face00.inverse(), face01.inverse()].into();
|
||||
|
||||
let v10 = Vertex::new(Point3::new(0.0, -1.0, -1.0));
|
||||
let v11 = Vertex::new(Point3::new(0.0, 1.0, -1.0));
|
||||
let edge10 = Edge::new(&v10, &v11, arc10);
|
||||
let edge11 = Edge::new(&v11, &v10, arc11);
|
||||
let edge12 = Edge::new(&v10, &v11, arc12);
|
||||
let wire10: Wire<_, _> = vec![edge10, edge12.inverse()].into();
|
||||
let wire11: Wire<_, _> = vec![edge12, edge11].into();
|
||||
let face10 = Face::new(vec![wire10], surface1.clone());
|
||||
let face11 = Face::new(vec![wire11], surface1);
|
||||
let geom_shell1: Shell<_, _, _> = vec![face10, face11].into();
|
||||
|
||||
let poly_shell0 = geom_shell0.triangulation(TOL);
|
||||
let poly_shell1 = geom_shell1.triangulation(TOL);
|
||||
let file = std::fs::File::create("polyshell0.obj").unwrap();
|
||||
obj::write(&poly_shell0.to_polygon(), file).unwrap();
|
||||
let file = std::fs::File::create("polyshell1.obj").unwrap();
|
||||
obj::write(&poly_shell1.to_polygon(), file).unwrap();
|
||||
let LoopsStoreQuadruple {
|
||||
geom_loops_store0,
|
||||
geom_loops_store1,
|
||||
..
|
||||
} = create_loops_stores(&geom_shell0, &poly_shell0, &geom_shell1, &poly_shell1, TOL, &mut Default::default()).unwrap();
|
||||
|
||||
let vertex_format = VertexDisplayFormat::AsPoint;
|
||||
let edge_id_format = EdgeDisplayFormat::VerticesTupleAndID { vertex_format };
|
||||
let wire_id_format = WireDisplayFormat::EdgesListTuple {
|
||||
edge_format: edge_id_format,
|
||||
};
|
||||
let edge_geom_format = EdgeDisplayFormat::VerticesTupleAndCurve { vertex_format };
|
||||
let wire_geom_format = WireDisplayFormat::EdgesListTuple {
|
||||
edge_format: edge_geom_format,
|
||||
};
|
||||
assert_eq!(geom_loops_store0.len(), 2);
|
||||
assert_eq!(geom_loops_store0[0].len(), 2);
|
||||
assert!(
|
||||
geom_loops_store0[0][0].is_closed(),
|
||||
"{:?}",
|
||||
geom_loops_store0.display(wire_id_format)
|
||||
);
|
||||
assert!(
|
||||
geom_loops_store0[0][1].is_closed(),
|
||||
"{:?}",
|
||||
geom_loops_store0.display(wire_id_format)
|
||||
);
|
||||
assert!(
|
||||
geom_loops_store0[0][0].is_geometric_consistent(),
|
||||
"{:?}",
|
||||
geom_loops_store0.display(wire_geom_format)
|
||||
);
|
||||
assert!(
|
||||
geom_loops_store0[0][1].is_geometric_consistent(),
|
||||
"{:?}",
|
||||
geom_loops_store0.display(wire_geom_format)
|
||||
);
|
||||
let (a, b) = (geom_loops_store0[0][0].len(), geom_loops_store0[0][1].len());
|
||||
let compatible0 = match geom_loops_store0[0][0].status() {
|
||||
ShapesOpStatus::And => a == 3,
|
||||
ShapesOpStatus::Or => a == 5,
|
||||
_ => false,
|
||||
};
|
||||
let compatible1 = match geom_loops_store0[0][1].status() {
|
||||
ShapesOpStatus::And => b == 3,
|
||||
ShapesOpStatus::Or => b == 5,
|
||||
_ => false,
|
||||
};
|
||||
assert!(
|
||||
compatible0 && compatible1 && a * b == 15,
|
||||
"{:?}",
|
||||
geom_loops_store0.display(wire_id_format)
|
||||
);
|
||||
assert_eq!(geom_loops_store0[1].len(), 2);
|
||||
assert!(
|
||||
geom_loops_store0[1][0].is_closed(),
|
||||
"{:?}",
|
||||
geom_loops_store0.display(wire_id_format)
|
||||
);
|
||||
assert!(
|
||||
geom_loops_store0[1][1].is_closed(),
|
||||
"{:?}",
|
||||
geom_loops_store0.display(wire_id_format)
|
||||
);
|
||||
assert!(
|
||||
geom_loops_store0[1][0].is_geometric_consistent(),
|
||||
"{:?}",
|
||||
geom_loops_store0.display(wire_geom_format)
|
||||
);
|
||||
assert!(
|
||||
geom_loops_store0[1][1].is_geometric_consistent(),
|
||||
"{:?}",
|
||||
geom_loops_store0.display(wire_geom_format)
|
||||
);
|
||||
let (a, b) = (geom_loops_store0[1][0].len(), geom_loops_store0[1][1].len());
|
||||
let compatible0 = match geom_loops_store0[1][0].status() {
|
||||
ShapesOpStatus::And => a == 3,
|
||||
ShapesOpStatus::Or => a == 5,
|
||||
_ => false,
|
||||
};
|
||||
let compatible1 = match geom_loops_store0[1][1].status() {
|
||||
ShapesOpStatus::And => b == 3,
|
||||
ShapesOpStatus::Or => b == 5,
|
||||
_ => false,
|
||||
};
|
||||
assert!(
|
||||
compatible0 && compatible1 && a * b == 15,
|
||||
"{:?}",
|
||||
geom_loops_store0.display(wire_id_format)
|
||||
);
|
||||
assert_eq!(geom_loops_store1.len(), 2);
|
||||
assert_eq!(geom_loops_store1[0].len(), 2);
|
||||
assert!(
|
||||
geom_loops_store1[0][0].is_closed(),
|
||||
"{:?}",
|
||||
geom_loops_store1.display(wire_id_format)
|
||||
);
|
||||
assert!(
|
||||
geom_loops_store1[0][1].is_closed(),
|
||||
"{:?}",
|
||||
geom_loops_store1.display(wire_id_format)
|
||||
);
|
||||
assert!(
|
||||
geom_loops_store1[0][0].is_geometric_consistent(),
|
||||
"{:?}",
|
||||
geom_loops_store1.display(wire_geom_format)
|
||||
);
|
||||
assert!(
|
||||
geom_loops_store1[0][1].is_geometric_consistent(),
|
||||
"{:?}",
|
||||
geom_loops_store1.display(wire_geom_format)
|
||||
);
|
||||
let (a, b) = (geom_loops_store1[0][0].len(), geom_loops_store1[0][1].len());
|
||||
let compatible0 = match geom_loops_store1[0][0].status() {
|
||||
ShapesOpStatus::And => a == 3,
|
||||
ShapesOpStatus::Or => a == 5,
|
||||
_ => false,
|
||||
};
|
||||
let compatible1 = match geom_loops_store1[0][1].status() {
|
||||
ShapesOpStatus::And => b == 3,
|
||||
ShapesOpStatus::Or => b == 5,
|
||||
_ => false,
|
||||
};
|
||||
assert!(
|
||||
compatible0 && compatible1 && a * b == 15,
|
||||
"{:?}",
|
||||
geom_loops_store1.display(wire_id_format)
|
||||
);
|
||||
assert_eq!(geom_loops_store1[1].len(), 2);
|
||||
assert!(
|
||||
geom_loops_store1[1][0].is_closed(),
|
||||
"{:?}",
|
||||
geom_loops_store1.display(wire_id_format)
|
||||
);
|
||||
assert!(
|
||||
geom_loops_store1[1][1].is_closed(),
|
||||
"{:?}",
|
||||
geom_loops_store1.display(wire_id_format)
|
||||
);
|
||||
assert!(
|
||||
geom_loops_store1[1][0].is_geometric_consistent(),
|
||||
"{:?}",
|
||||
geom_loops_store1.display(wire_geom_format)
|
||||
);
|
||||
assert!(
|
||||
geom_loops_store1[1][1].is_geometric_consistent(),
|
||||
"{:?}",
|
||||
geom_loops_store1.display(wire_geom_format)
|
||||
);
|
||||
let (a, b) = (geom_loops_store1[1][0].len(), geom_loops_store1[1][1].len());
|
||||
let compatible0 = match geom_loops_store1[1][0].status() {
|
||||
ShapesOpStatus::And => a == 3,
|
||||
ShapesOpStatus::Or => a == 5,
|
||||
_ => false,
|
||||
};
|
||||
let compatible1 = match geom_loops_store1[1][1].status() {
|
||||
ShapesOpStatus::And => b == 3,
|
||||
ShapesOpStatus::Or => b == 5,
|
||||
_ => false,
|
||||
};
|
||||
assert!(
|
||||
compatible0 && compatible1 && a * b == 15,
|
||||
"{:?}",
|
||||
geom_loops_store1.display(wire_id_format)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn crossing_edges() {
|
||||
let arc00: AlternativeIntersection = NurbsCurve::new(BSplineCurve::new(
|
||||
KnotVec::bezier_knot(2),
|
||||
vec![
|
||||
Vector4::new(1.0, 0.0, 1.0, 1.0),
|
||||
Vector4::new(0.0, 1.0, 0.0, 0.0),
|
||||
Vector4::new(-1.0, 0.0, 1.0, 1.0),
|
||||
],
|
||||
))
|
||||
.into();
|
||||
let arc01: AlternativeIntersection = NurbsCurve::new(BSplineCurve::new(
|
||||
KnotVec::bezier_knot(2),
|
||||
vec![
|
||||
Vector4::new(-1.0, 0.0, 1.0, 1.0),
|
||||
Vector4::new(0.0, -1.0, 0.0, 0.0),
|
||||
Vector4::new(1.0, 0.0, 1.0, 1.0),
|
||||
],
|
||||
))
|
||||
.into();
|
||||
let arc02: AlternativeIntersection = NurbsCurve::new(BSplineCurve::new(
|
||||
KnotVec::bezier_knot(2),
|
||||
vec![
|
||||
Vector4::new(1.0, 0.0, 1.0, 1.0),
|
||||
Vector4::new(0.0, 0.0, -3.0, 1.0),
|
||||
Vector4::new(-1.0, 0.0, 1.0, 1.0),
|
||||
],
|
||||
))
|
||||
.into();
|
||||
let arc10: AlternativeIntersection = NurbsCurve::new(BSplineCurve::new(
|
||||
KnotVec::bezier_knot(2),
|
||||
vec![
|
||||
Vector4::new(1.0, 0.0, -1.0, 1.0),
|
||||
Vector4::new(0.0, 1.0, 0.0, 0.0),
|
||||
Vector4::new(-1.0, 0.0, -1.0, 1.0),
|
||||
],
|
||||
))
|
||||
.into();
|
||||
let arc11: AlternativeIntersection = NurbsCurve::new(BSplineCurve::new(
|
||||
KnotVec::bezier_knot(2),
|
||||
vec![
|
||||
Vector4::new(-1.0, 0.0, -1.0, 1.0),
|
||||
Vector4::new(0.0, -1.0, 0.0, 0.0),
|
||||
Vector4::new(1.0, 0.0, -1.0, 1.0),
|
||||
],
|
||||
))
|
||||
.into();
|
||||
let arc12: AlternativeIntersection = NurbsCurve::new(BSplineCurve::new(
|
||||
KnotVec::bezier_knot(2),
|
||||
vec![
|
||||
Vector4::new(1.0, 0.0, -1.0, 1.0),
|
||||
Vector4::new(0.0, 0.0, 3.0, 1.0),
|
||||
Vector4::new(-1.0, 0.0, -1.0, 1.0),
|
||||
],
|
||||
))
|
||||
.into();
|
||||
let (surface0, surface1) = parabola_surfaces();
|
||||
|
||||
let v00 = Vertex::new(Point3::new(1.0, 0.0, 1.0));
|
||||
let v01 = Vertex::new(Point3::new(-1.0, 0.0, 1.0));
|
||||
let edge00 = Edge::new(&v00, &v01, arc00);
|
||||
let edge01 = Edge::new(&v01, &v00, arc01);
|
||||
let edge02 = Edge::new(&v00, &v01, arc02);
|
||||
let wire00: Wire<_, _> = vec![edge00, edge02.inverse()].into();
|
||||
let wire01: Wire<_, _> = vec![edge01, edge02].into();
|
||||
let face00 = Face::new(vec![wire00], surface0.clone());
|
||||
let face01 = Face::new(vec![wire01], surface0);
|
||||
let geom_shell0: Shell<_, _, _> = vec![face00.inverse(), face01.inverse()].into();
|
||||
|
||||
let v10 = Vertex::new(Point3::new(1.0, 0.0, -1.0));
|
||||
let v11 = Vertex::new(Point3::new(-1.0, 0.0, -1.0));
|
||||
let edge10 = Edge::new(&v10, &v11, arc10);
|
||||
let edge11 = Edge::new(&v11, &v10, arc11);
|
||||
let edge12 = Edge::new(&v10, &v11, arc12);
|
||||
let wire10: Wire<_, _> = vec![edge10, edge12.inverse()].into();
|
||||
let wire11: Wire<_, _> = vec![edge11, edge12].into();
|
||||
let face10 = Face::new(vec![wire10], surface1.clone());
|
||||
let face11 = Face::new(vec![wire11], surface1);
|
||||
let geom_shell1: Shell<_, _, _> = vec![face10, face11].into();
|
||||
|
||||
let poly_shell0 = geom_shell0.triangulation(TOL);
|
||||
let poly_shell1 = geom_shell1.triangulation(TOL);
|
||||
let LoopsStoreQuadruple {
|
||||
geom_loops_store0,
|
||||
geom_loops_store1,
|
||||
..
|
||||
} = create_loops_stores(&geom_shell0, &poly_shell0, &geom_shell1, &poly_shell1, TOL, &mut Default::default()).unwrap();
|
||||
|
||||
let vertex_format = VertexDisplayFormat::AsPoint;
|
||||
let edge_id_format = EdgeDisplayFormat::VerticesTupleAndID { vertex_format };
|
||||
let wire_id_format = WireDisplayFormat::EdgesListTuple {
|
||||
edge_format: edge_id_format,
|
||||
};
|
||||
let edge_geom_format = EdgeDisplayFormat::VerticesTupleAndCurve { vertex_format };
|
||||
let wire_geom_format = WireDisplayFormat::EdgesListTuple {
|
||||
edge_format: edge_geom_format,
|
||||
};
|
||||
assert_eq!(geom_loops_store0.len(), 2);
|
||||
assert_eq!(geom_loops_store0[0].len(), 2);
|
||||
assert!(
|
||||
geom_loops_store0[0][0].is_closed(),
|
||||
"{:?}",
|
||||
geom_loops_store0.display(wire_id_format)
|
||||
);
|
||||
assert!(
|
||||
geom_loops_store0[0][1].is_closed(),
|
||||
"{:?}",
|
||||
geom_loops_store0.display(wire_id_format)
|
||||
);
|
||||
assert!(
|
||||
geom_loops_store0[0][0].is_geometric_consistent(),
|
||||
"{:?}",
|
||||
geom_loops_store0.display(wire_geom_format)
|
||||
);
|
||||
assert!(
|
||||
geom_loops_store0[0][1].is_geometric_consistent(),
|
||||
"{:?}",
|
||||
geom_loops_store0.display(wire_geom_format)
|
||||
);
|
||||
let (a, b) = (geom_loops_store0[0][0].len(), geom_loops_store0[0][1].len());
|
||||
let compatible0 = match geom_loops_store0[0][0].status() {
|
||||
ShapesOpStatus::And => a == 2,
|
||||
ShapesOpStatus::Or => a == 4,
|
||||
_ => false,
|
||||
};
|
||||
let compatible1 = match geom_loops_store0[0][1].status() {
|
||||
ShapesOpStatus::And => b == 2,
|
||||
ShapesOpStatus::Or => b == 4,
|
||||
_ => false,
|
||||
};
|
||||
assert!(
|
||||
compatible0 && compatible1 && a * b == 8,
|
||||
"{:?}",
|
||||
geom_loops_store0.display(wire_id_format)
|
||||
);
|
||||
assert_eq!(geom_loops_store0[1].len(), 2);
|
||||
assert!(
|
||||
geom_loops_store0[1][0].is_closed(),
|
||||
"{:?}",
|
||||
geom_loops_store0.display(wire_id_format)
|
||||
);
|
||||
assert!(
|
||||
geom_loops_store0[1][1].is_closed(),
|
||||
"{:?}",
|
||||
geom_loops_store0.display(wire_id_format)
|
||||
);
|
||||
assert!(
|
||||
geom_loops_store0[1][0].is_geometric_consistent(),
|
||||
"{:?}",
|
||||
geom_loops_store0.display(wire_geom_format)
|
||||
);
|
||||
assert!(
|
||||
geom_loops_store0[1][1].is_geometric_consistent(),
|
||||
"{:?}",
|
||||
geom_loops_store0.display(wire_geom_format)
|
||||
);
|
||||
let (a, b) = (geom_loops_store0[1][0].len(), geom_loops_store0[1][1].len());
|
||||
let compatible0 = match geom_loops_store0[1][0].status() {
|
||||
ShapesOpStatus::And => a == 2,
|
||||
ShapesOpStatus::Or => a == 4,
|
||||
_ => false,
|
||||
};
|
||||
let compatible1 = match geom_loops_store0[1][1].status() {
|
||||
ShapesOpStatus::And => b == 2,
|
||||
ShapesOpStatus::Or => b == 4,
|
||||
_ => false,
|
||||
};
|
||||
assert!(
|
||||
compatible0 && compatible1 && a * b == 8,
|
||||
"{:?}",
|
||||
geom_loops_store0.display(wire_id_format)
|
||||
);
|
||||
assert_eq!(geom_loops_store1.len(), 2);
|
||||
assert_eq!(geom_loops_store1[0].len(), 2);
|
||||
assert!(
|
||||
geom_loops_store1[0][0].is_closed(),
|
||||
"{:?}",
|
||||
geom_loops_store1.display(wire_id_format)
|
||||
);
|
||||
assert!(
|
||||
geom_loops_store1[0][1].is_closed(),
|
||||
"{:?}",
|
||||
geom_loops_store1.display(wire_id_format)
|
||||
);
|
||||
assert!(
|
||||
geom_loops_store1[0][0].is_geometric_consistent(),
|
||||
"{:?}",
|
||||
geom_loops_store1.display(wire_geom_format)
|
||||
);
|
||||
assert!(
|
||||
geom_loops_store1[0][1].is_geometric_consistent(),
|
||||
"{:?}",
|
||||
geom_loops_store1.display(wire_geom_format)
|
||||
);
|
||||
let (a, b) = (geom_loops_store1[0][0].len(), geom_loops_store1[0][1].len());
|
||||
let compatible0 = match geom_loops_store1[0][0].status() {
|
||||
ShapesOpStatus::And => a == 2,
|
||||
ShapesOpStatus::Or => a == 4,
|
||||
_ => false,
|
||||
};
|
||||
let compatible1 = match geom_loops_store1[0][1].status() {
|
||||
ShapesOpStatus::And => b == 2,
|
||||
ShapesOpStatus::Or => b == 4,
|
||||
_ => false,
|
||||
};
|
||||
assert!(
|
||||
compatible0 && compatible1 && a * b == 8,
|
||||
"{:?}",
|
||||
geom_loops_store1.display(wire_id_format)
|
||||
);
|
||||
assert_eq!(geom_loops_store1[1].len(), 2);
|
||||
assert!(
|
||||
geom_loops_store1[1][0].is_closed(),
|
||||
"{:?}",
|
||||
geom_loops_store1.display(wire_id_format)
|
||||
);
|
||||
assert!(
|
||||
geom_loops_store1[1][1].is_closed(),
|
||||
"{:?}",
|
||||
geom_loops_store1.display(wire_id_format)
|
||||
);
|
||||
assert!(
|
||||
geom_loops_store1[1][0].is_geometric_consistent(),
|
||||
"{:?}",
|
||||
geom_loops_store1.display(wire_geom_format)
|
||||
);
|
||||
assert!(
|
||||
geom_loops_store1[1][1].is_geometric_consistent(),
|
||||
"{:?}",
|
||||
geom_loops_store1.display(wire_geom_format)
|
||||
);
|
||||
let (a, b) = (geom_loops_store1[1][0].len(), geom_loops_store1[1][1].len());
|
||||
let compatible0 = match geom_loops_store1[1][0].status() {
|
||||
ShapesOpStatus::And => a == 2,
|
||||
ShapesOpStatus::Or => a == 4,
|
||||
_ => false,
|
||||
};
|
||||
let compatible1 = match geom_loops_store1[1][1].status() {
|
||||
ShapesOpStatus::And => b == 2,
|
||||
ShapesOpStatus::Or => b == 4,
|
||||
_ => false,
|
||||
};
|
||||
assert!(
|
||||
compatible0 && compatible1 && a * b == 8,
|
||||
"{:?}",
|
||||
geom_loops_store0.display(wire_id_format)
|
||||
);
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
mod divide_face;
|
||||
mod faces_classification;
|
||||
mod history;
|
||||
mod integrate;
|
||||
mod intersection_curve;
|
||||
// SolidCraft: expose the constructor for workspace regression coverage of the vendored fix.
|
||||
#[doc(hidden)]
|
||||
pub use intersection_curve::IntersectionCurveWithParameters;
|
||||
mod loops_store;
|
||||
mod polyline_construction;
|
||||
pub use history::{EdgeOrigin, FaceIndex, FaceOrigin, History};
|
||||
pub use integrate::{and, and_with_history, and_with_history_meshed, meshes, or, or_with_history, MeshedShell, ShapeOpsCurve, ShapeOpsSurface};
|
||||
@@ -1,117 +0,0 @@
|
||||
use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
|
||||
use std::collections::VecDeque;
|
||||
use truck_base::{cgmath64::*, tolerance::*};
|
||||
use truck_meshalgo::prelude::PolylineCurve;
|
||||
|
||||
pub fn construct_polylines(lines: &[(Point3, Point3)]) -> Vec<PolylineCurve<Point3>> {
|
||||
let mut graph: Graph = lines.iter().collect();
|
||||
let mut res = Vec::new();
|
||||
while !graph.is_empty() {
|
||||
let (mut idx, node) = graph.get_one();
|
||||
let mut wire: VecDeque<_> = vec![node.coord].into();
|
||||
while let Some((idx0, pt)) = graph.get_a_next_node(idx) {
|
||||
idx = idx0;
|
||||
wire.push_back(pt);
|
||||
}
|
||||
let mut idx = PointIndex::from(wire[0]);
|
||||
while let Some((idx0, pt)) = graph.get_a_next_node(idx) {
|
||||
idx = idx0;
|
||||
wire.push_front(pt);
|
||||
}
|
||||
res.push(PolylineCurve(wire.into()));
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Copy, Hash, PartialEq, Eq)]
|
||||
struct PointIndex([i64; 3]);
|
||||
|
||||
impl From<Point3> for PointIndex {
|
||||
#[inline(always)]
|
||||
fn from(pt: Point3) -> PointIndex {
|
||||
let idx = pt.add_element_wise(TOLERANCE) / (2.0 * TOLERANCE);
|
||||
PointIndex(idx.cast::<i64>().unwrap().into())
|
||||
}
|
||||
}
|
||||
|
||||
struct Node {
|
||||
coord: Point3,
|
||||
adjacency: HashSet<PointIndex>,
|
||||
}
|
||||
|
||||
impl Node {
|
||||
#[inline(always)]
|
||||
fn new(coord: Point3, adjacency: HashSet<PointIndex>) -> Node { Node { coord, adjacency } }
|
||||
|
||||
fn pop_one_adjacency(&mut self) -> PointIndex {
|
||||
let idx = *self.adjacency.iter().next().unwrap();
|
||||
self.adjacency.remove(&idx);
|
||||
idx
|
||||
}
|
||||
}
|
||||
|
||||
struct Graph(HashMap<PointIndex, Node>);
|
||||
|
||||
impl std::ops::Deref for Graph {
|
||||
type Target = HashMap<PointIndex, Node>;
|
||||
#[inline(always)]
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
|
||||
impl std::ops::DerefMut for Graph {
|
||||
#[inline(always)]
|
||||
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
|
||||
}
|
||||
|
||||
impl Graph {
|
||||
fn add_half_edge(&mut self, pt0: Point3, pt1: Point3) {
|
||||
let idx0 = pt0.into();
|
||||
let idx1 = pt1.into();
|
||||
if let Some(node) = self.get_mut(&idx0) {
|
||||
node.adjacency.insert(idx1);
|
||||
} else {
|
||||
let mut set = HashSet::default();
|
||||
set.insert(idx1);
|
||||
self.insert(idx0, Node::new(pt0, set));
|
||||
}
|
||||
}
|
||||
|
||||
fn add_edge(&mut self, line: (Point3, Point3)) {
|
||||
if !line.0.near(&line.1) {
|
||||
self.add_half_edge(line.0, line.1);
|
||||
self.add_half_edge(line.1, line.0);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn get_one(&self) -> (PointIndex, &Node) {
|
||||
let (idx, node) = self.iter().next().unwrap();
|
||||
(*idx, node)
|
||||
}
|
||||
|
||||
fn get_a_next_node(&mut self, idx: PointIndex) -> Option<(PointIndex, Point3)> {
|
||||
let node = self.get_mut(&idx)?;
|
||||
let idx0 = node.pop_one_adjacency();
|
||||
if node.adjacency.is_empty() {
|
||||
self.remove(&idx);
|
||||
}
|
||||
let node = self.get_mut(&idx0)?;
|
||||
node.adjacency.remove(&idx);
|
||||
let pt = node.coord;
|
||||
if node.adjacency.is_empty() {
|
||||
self.remove(&idx0);
|
||||
}
|
||||
Some((idx0, pt))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FromIterator<&'a (Point3, Point3)> for Graph {
|
||||
fn from_iter<I: IntoIterator<Item = &'a (Point3, Point3)>>(iter: I) -> Graph {
|
||||
let mut res = Graph(HashMap::default());
|
||||
iter.into_iter().for_each(|line| res.add_edge(*line));
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@@ -1,113 +0,0 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn construct_polylines_positive0() {
|
||||
let lines = vec![
|
||||
(Point3::new(0.0, 0.0, 0.0), Point3::new(1.0, 0.0, 0.0)),
|
||||
(Point3::new(1.0, 0.0, 0.0), Point3::new(0.0, 1.0, 0.0)),
|
||||
(Point3::new(1.0, 1.0, 0.0), Point3::new(0.0, 0.0, 1.0)),
|
||||
(Point3::new(0.0, 1.0, 1.0), Point3::new(1.0, 1.0, 1.0)),
|
||||
(Point3::new(0.0, 0.0, 1.0), Point3::new(1.0, 0.0, 1.0)),
|
||||
(Point3::new(0.0, 1.0, 0.0), Point3::new(1.0, 1.0, 0.0)),
|
||||
(Point3::new(1.0, 1.0, 1.0), Point3::new(0.0, 0.0, 0.0)),
|
||||
(Point3::new(1.0, 0.0, 1.0), Point3::new(0.0, 1.0, 1.0)),
|
||||
];
|
||||
let polyline = construct_polylines(&lines);
|
||||
assert_eq!(polyline.len(), 1);
|
||||
assert_eq!(polyline[0].len(), 9);
|
||||
|
||||
let mut sign = None;
|
||||
for line in polyline[0].windows(2) {
|
||||
let a = line[0][0] + line[0][1] * 2.0 + line[0][2] * 4.0;
|
||||
let b = line[1][0] + line[1][1] * 2.0 + line[1][2] * 4.0;
|
||||
let x = b - a;
|
||||
assert!(f64::abs(x) == 1.0 || f64::abs(x) == 7.0);
|
||||
let s = f64::signum(x * (x - 2.0) * (x + 2.0));
|
||||
if let Some(sign) = sign {
|
||||
assert!(s == sign);
|
||||
} else {
|
||||
sign = Some(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn construct_polylines_positive1() {
|
||||
let lines = vec![
|
||||
(Point3::new(0.0, 0.0, 0.0), Point3::new(1.0, 0.0, 0.0)),
|
||||
(Point3::new(1.0, 0.0, 0.0), Point3::new(1.0, 1.0, 0.0)),
|
||||
(Point3::new(1.0, 0.0, 1.0), Point3::new(1.0, 1.0, 1.0)),
|
||||
(Point3::new(1.0, 1.0, 1.0), Point3::new(0.0, 1.0, 1.0)),
|
||||
(Point3::new(1.0, 1.0, 0.0), Point3::new(0.0, 1.0, 0.0)),
|
||||
(Point3::new(0.0, 1.0, 0.0), Point3::new(0.0, 0.0, 0.0)),
|
||||
(Point3::new(0.0, 0.0, 1.0), Point3::new(1.0, 0.0, 1.0)),
|
||||
(Point3::new(0.0, 1.0, 1.0), Point3::new(0.0, 0.0, 1.0)),
|
||||
];
|
||||
let polyline = construct_polylines(&lines);
|
||||
assert_eq!(polyline.len(), 2);
|
||||
assert_eq!(polyline[0].len(), 5);
|
||||
assert_eq!(polyline[1].len(), 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn construct_polylines_positive2() {
|
||||
let lines = vec![
|
||||
(Point3::new(0.0, 0.0, 0.0), Point3::new(1.0, 0.0, 0.0)),
|
||||
(Point3::new(1.0, 0.0, 0.0), Point3::new(0.0, 1.0, 0.0)),
|
||||
(Point3::new(1.0, 1.0, 0.0), Point3::new(0.0, 0.0, 1.0)),
|
||||
(Point3::new(0.0, 1.0, 1.0), Point3::new(1.0, 1.0, 1.0)),
|
||||
(Point3::new(0.0, 0.0, 1.0), Point3::new(1.0, 0.0, 1.0)),
|
||||
(Point3::new(1.0, 1.0, 0.0), Point3::new(1.0, 1.0, 0.0)),
|
||||
(Point3::new(0.0, 1.0, 0.0), Point3::new(1.0, 1.0, 0.0)),
|
||||
(Point3::new(1.0, 1.0, 1.0), Point3::new(0.0, 0.0, 0.0)),
|
||||
(Point3::new(1.0, 0.0, 1.0), Point3::new(0.0, 1.0, 1.0)),
|
||||
];
|
||||
let polyline = construct_polylines(&lines);
|
||||
assert_eq!(polyline.len(), 1);
|
||||
assert_eq!(polyline[0].len(), 9);
|
||||
|
||||
let mut sign = None;
|
||||
for line in polyline[0].windows(2) {
|
||||
let a = line[0][0] + line[0][1] * 2.0 + line[0][2] * 4.0;
|
||||
let b = line[1][0] + line[1][1] * 2.0 + line[1][2] * 4.0;
|
||||
let x = b - a;
|
||||
assert!(f64::abs(x) == 1.0 || f64::abs(x) == 7.0);
|
||||
let s = f64::signum(x * (x - 2.0) * (x + 2.0));
|
||||
if let Some(sign) = sign {
|
||||
assert!(s == sign);
|
||||
} else {
|
||||
sign = Some(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn construct_polylines_positive3() {
|
||||
let lines = vec![
|
||||
(Point3::new(0.0, 0.0, 0.0), Point3::new(1.0, 0.0, 0.0)),
|
||||
(Point3::new(1.0, 0.0, 0.0), Point3::new(0.0, 1.0, 0.0)),
|
||||
(Point3::new(1.0, 1.0, 0.0), Point3::new(0.0, 0.0, 1.0)),
|
||||
(Point3::new(0.0, 1.0, 1.0), Point3::new(1.0, 1.0, 1.0)),
|
||||
(Point3::new(0.0, 0.0, 1.0), Point3::new(1.0, 0.0, 1.0)),
|
||||
(Point3::new(1.0, 1.0, 0.0), Point3::new(1.0, 1.0, 0.0)),
|
||||
(Point3::new(0.0, 1.0, 0.0), Point3::new(1.0, 1.0, 0.0)),
|
||||
(Point3::new(1.0, 0.0, 1.0), Point3::new(0.0, 1.0, 1.0)),
|
||||
];
|
||||
let polyline = construct_polylines(&lines);
|
||||
assert_eq!(polyline.len(), 1);
|
||||
assert_eq!(polyline[0].len(), 8);
|
||||
|
||||
let mut sign = None;
|
||||
for line in polyline[0].windows(2) {
|
||||
let a = line[0][0] + line[0][1] * 2.0 + line[0][2] * 4.0;
|
||||
let b = line[1][0] + line[1][1] * 2.0 + line[1][2] * 4.0;
|
||||
let x = b - a;
|
||||
assert!(f64::abs(x) == 1.0);
|
||||
let s = f64::signum(x * (x - 2.0) * (x + 2.0));
|
||||
if let Some(sign) = sign {
|
||||
assert!(s == sign);
|
||||
} else {
|
||||
sign = Some(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
-109
@@ -1,109 +0,0 @@
|
||||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
||||
#
|
||||
# When uploading crates to the registry Cargo will automatically
|
||||
# "normalize" Cargo.toml files for maximal compatibility
|
||||
# with all versions of Cargo and also rewrite `path` dependencies
|
||||
# to registry (e.g., crates.io) dependencies.
|
||||
#
|
||||
# If you are reading this file be aware that the original Cargo.toml
|
||||
# will likely look very different (and much more reasonable).
|
||||
# See Cargo.toml.orig for the original contents.
|
||||
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "truck-stepio"
|
||||
version = "0.3.0"
|
||||
authors = ["Yoshinori Tanimura <tanimura@ricos.co.jp>"]
|
||||
build = false
|
||||
autobins = false
|
||||
autoexamples = false
|
||||
autotests = false
|
||||
autobenches = false
|
||||
description = "Reads/writes STEP files from/to truck"
|
||||
homepage = "https://github.com/ricosjp/truck"
|
||||
readme = "README.md"
|
||||
keywords = [
|
||||
"truck",
|
||||
"graphics",
|
||||
]
|
||||
categories = ["graphics"]
|
||||
license = "Apache-2.0"
|
||||
repository = "https://github.com/ricosjp/truck"
|
||||
|
||||
[lib]
|
||||
name = "truck_stepio"
|
||||
path = "src/lib.rs"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[dependencies.chrono]
|
||||
version = "0.4.38"
|
||||
features = ["wasmbind"]
|
||||
|
||||
[dependencies.derive_more]
|
||||
version = "0.99.18"
|
||||
optional = true
|
||||
|
||||
[dependencies.ruststep]
|
||||
version = "0.4.0"
|
||||
optional = true
|
||||
|
||||
[dependencies.serde]
|
||||
version = "1.0.210"
|
||||
features = ["derive"]
|
||||
optional = true
|
||||
|
||||
[dependencies.truck-derivers]
|
||||
version = "0.1.0"
|
||||
optional = true
|
||||
|
||||
[dependencies.truck-geometry]
|
||||
version = "0.5.0"
|
||||
|
||||
[dependencies.truck-geotrait]
|
||||
version = "0.4.0"
|
||||
optional = true
|
||||
|
||||
[dependencies.truck-modeling]
|
||||
version = "0.6.0"
|
||||
|
||||
[dependencies.truck-polymesh]
|
||||
version = "0.6.0"
|
||||
|
||||
[dependencies.truck-topology]
|
||||
version = "0.6.0"
|
||||
|
||||
[dev-dependencies.clap]
|
||||
version = "4.5.17"
|
||||
features = ["derive"]
|
||||
|
||||
[dev-dependencies.espr-derive]
|
||||
version = "0.4.0"
|
||||
|
||||
[dev-dependencies.itertools]
|
||||
version = "0.13.0"
|
||||
|
||||
[dev-dependencies.proptest]
|
||||
version = "1.5.0"
|
||||
|
||||
[dev-dependencies.serde_json]
|
||||
version = "1.0.128"
|
||||
|
||||
[dev-dependencies.truck-meshalgo]
|
||||
version = "0.4.0"
|
||||
|
||||
[features]
|
||||
default = [
|
||||
"in",
|
||||
"derive",
|
||||
]
|
||||
derive = ["truck-derivers"]
|
||||
in = [
|
||||
"derive_more",
|
||||
"ruststep",
|
||||
"serde",
|
||||
"truck-geotrait",
|
||||
]
|
||||
Vendored
-177
@@ -1,177 +0,0 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
Vendored
-21
@@ -1,21 +0,0 @@
|
||||
# truck-stepio
|
||||
|
||||
[](https://crates.io/crates/truck-stepio) [](https://docs.rs/truck-stepio)
|
||||
|
||||
Reads/writes STEP files from/to truck.
|
||||
|
||||
## Sample Codes
|
||||
|
||||
### shape-to-step
|
||||
|
||||
convert from truck shape json to step file.
|
||||
|
||||
#### usage
|
||||
|
||||
```bash
|
||||
shape-to-step <input shape file> [output shape file]
|
||||
```
|
||||
|
||||
### step-to-mesh
|
||||
|
||||
Parse STEP data, extract shape, and meshing.
|
||||
Vendored
-397
@@ -1,397 +0,0 @@
|
||||
use crate::{self as truck_stepio};
|
||||
use derive_more::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use truck_derivers::{DisplayByStep, StepCurve, StepLength, StepSurface};
|
||||
|
||||
/// re-export structs in `truck-geometry` and `truck-polymesh`.
|
||||
pub mod re_exports {
|
||||
pub use truck_geometry::prelude::*;
|
||||
pub use truck_polymesh::*;
|
||||
}
|
||||
pub use re_exports::*;
|
||||
|
||||
/// Errors that occur when converting STEP format
|
||||
pub type StepConvertingError = Box<dyn std::error::Error>;
|
||||
|
||||
/// `ellipse`, realized in `truck`
|
||||
pub type Ellipse<P, M> = Processor<TrimmedCurve<UnitCircle<P>>, M>;
|
||||
/// `hyperbola`, realized in `truck`
|
||||
pub type Hyperbola<P, M> = Processor<TrimmedCurve<UnitHyperbola<P>>, M>;
|
||||
/// `parabola`, realized in `truck`
|
||||
pub type Parabola<P, M> = Processor<TrimmedCurve<UnitParabola<P>>, M>;
|
||||
/// `spherical_surface`, realized in `truck`
|
||||
pub type SphericalSurface = Processor<Sphere, Matrix4>;
|
||||
/// `cylindrical_surface`, realized in `truck`
|
||||
pub type CylindricalSurface = Processor<RevolutedCurve<Line<Point3>>, Matrix4>;
|
||||
/// `toroidal_surface`, realized in `truck`
|
||||
pub type ToroidalSurface = Processor<Torus, Matrix4>;
|
||||
/// `conical_surface`, realized in `truck`
|
||||
pub type ConicalSurface = Processor<RevolutedCurve<Line<Point3>>, Matrix4>;
|
||||
/// `surface_of_linear_extrusion`, realized in `truck`
|
||||
pub type StepExtrudedCurve = ExtrudedCurve<Curve3D, Vector3>;
|
||||
/// `surface_of_revolution`, realized in `truck`
|
||||
pub type StepRevolutedCurve = Processor<RevolutedCurve<Curve3D>, Matrix4>;
|
||||
/// `pcurve`, realized in `truck`
|
||||
pub type PCurve = truck_geometry::prelude::PCurve<Box<Curve2D>, Box<Surface>>;
|
||||
|
||||
/// `conic` in 2D, realized in `truck`
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
PartialEq,
|
||||
From,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
ParametricCurve,
|
||||
BoundedCurve,
|
||||
Cut,
|
||||
Invertible,
|
||||
ParameterDivision1D,
|
||||
SearchParameterD1,
|
||||
SearchNearestParameterD1,
|
||||
TransformedM3,
|
||||
StepLength,
|
||||
DisplayByStep,
|
||||
StepCurve,
|
||||
)]
|
||||
pub enum Conic2D {
|
||||
Ellipse(Ellipse<Point2, Matrix3>),
|
||||
Hyperbola(Hyperbola<Point2, Matrix3>),
|
||||
Parabola(Parabola<Point2, Matrix3>),
|
||||
}
|
||||
|
||||
/// `curve` in 2D, realized in `truck`
|
||||
#[derive(
|
||||
Clone,
|
||||
Debug,
|
||||
PartialEq,
|
||||
From,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
ParametricCurve,
|
||||
BoundedCurve,
|
||||
Cut,
|
||||
Invertible,
|
||||
ParameterDivision1D,
|
||||
SearchParameterD1,
|
||||
SearchNearestParameterD1,
|
||||
TransformedM3,
|
||||
StepLength,
|
||||
DisplayByStep,
|
||||
StepCurve,
|
||||
)]
|
||||
|
||||
pub enum Curve2D {
|
||||
Line(Line<Point2>),
|
||||
Polyline(PolylineCurve<Point2>),
|
||||
Conic(Conic2D),
|
||||
BSplineCurve(BSplineCurve<Point2>),
|
||||
NurbsCurve(NurbsCurve<Vector3>),
|
||||
}
|
||||
|
||||
/// `conic` in 3D, realized in `truck`
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
PartialEq,
|
||||
From,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
ParametricCurve,
|
||||
BoundedCurve,
|
||||
Cut,
|
||||
Invertible,
|
||||
ParameterDivision1D,
|
||||
SearchParameterD1,
|
||||
SearchNearestParameterD1,
|
||||
TransformedM4,
|
||||
StepLength,
|
||||
DisplayByStep,
|
||||
StepCurve,
|
||||
)]
|
||||
pub enum Conic3D {
|
||||
Ellipse(Ellipse<Point3, Matrix4>),
|
||||
Hyperbola(Hyperbola<Point3, Matrix4>),
|
||||
Parabola(Parabola<Point3, Matrix4>),
|
||||
}
|
||||
|
||||
/// `curve` in 3D, realized in `truck`
|
||||
#[derive(
|
||||
Clone,
|
||||
Debug,
|
||||
PartialEq,
|
||||
From,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
ParametricCurve,
|
||||
BoundedCurve,
|
||||
Cut,
|
||||
Invertible,
|
||||
ParameterDivision1D,
|
||||
SearchParameterD1,
|
||||
SearchNearestParameterD1,
|
||||
TransformedM4,
|
||||
StepLength,
|
||||
DisplayByStep,
|
||||
StepCurve,
|
||||
)]
|
||||
pub enum Curve3D {
|
||||
Line(Line<Point3>),
|
||||
Polyline(PolylineCurve<Point3>),
|
||||
Conic(Conic3D),
|
||||
BSplineCurve(BSplineCurve<Point3>),
|
||||
PCurve(PCurve),
|
||||
NurbsCurve(NurbsCurve<Vector4>),
|
||||
}
|
||||
|
||||
/// `elementary_surface`, realized in `truck`
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
ParametricSurface3D,
|
||||
ParameterDivision2D,
|
||||
SearchParameterD2,
|
||||
SearchNearestParameterD2,
|
||||
Invertible,
|
||||
TransformedM4,
|
||||
StepLength,
|
||||
DisplayByStep,
|
||||
StepSurface,
|
||||
)]
|
||||
pub enum ElementarySurface {
|
||||
Plane(Plane),
|
||||
Sphere(SphericalSurface),
|
||||
CylindricalSurface(CylindricalSurface),
|
||||
ToroidalSurface(ToroidalSurface),
|
||||
ConicalSurface(ConicalSurface),
|
||||
}
|
||||
|
||||
/// `swept_surface`, realized in `truck`
|
||||
#[derive(
|
||||
Clone,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
ParametricSurface3D,
|
||||
ParameterDivision2D,
|
||||
SearchParameterD2,
|
||||
SearchNearestParameterD2,
|
||||
Invertible,
|
||||
TransformedM4,
|
||||
StepLength,
|
||||
DisplayByStep,
|
||||
StepSurface,
|
||||
)]
|
||||
pub enum SweptCurve {
|
||||
ExtrudedCurve(StepExtrudedCurve),
|
||||
RevolutedCurve(StepRevolutedCurve),
|
||||
}
|
||||
|
||||
/// `surface`, realized in `truck`
|
||||
#[derive(
|
||||
Clone,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
ParametricSurface3D,
|
||||
ParameterDivision2D,
|
||||
SearchParameterD2,
|
||||
SearchNearestParameterD2,
|
||||
Invertible,
|
||||
TransformedM4,
|
||||
StepLength,
|
||||
StepSurface,
|
||||
)]
|
||||
pub enum Surface {
|
||||
ElementarySurface(Box<ElementarySurface>),
|
||||
SweptCurve(Box<SweptCurve>),
|
||||
BSplineSurface(Box<BSplineSurface<Point3>>),
|
||||
NurbsSurface(Box<NurbsSurface<Vector4>>),
|
||||
}
|
||||
|
||||
impl truck_stepio::out::DisplayByStep for Surface {
|
||||
fn fmt(&self, idx: usize, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
use Surface::*;
|
||||
match self {
|
||||
ElementarySurface(x) => x.fmt(idx, f),
|
||||
SweptCurve(x) => x.fmt(idx, f),
|
||||
BSplineSurface(x) => x.fmt(idx, f),
|
||||
NurbsSurface(x) => x.fmt(idx, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `spherical_surface`, realized in `truck`
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, StepSurface)]
|
||||
pub struct Sphere(pub truck_geometry::prelude::Sphere);
|
||||
|
||||
impl truck_stepio::out::StepSurface for Processor<Sphere, Matrix4> {
|
||||
#[inline(always)]
|
||||
fn same_sense(&self) -> bool { self.orientation() }
|
||||
}
|
||||
|
||||
mod sphere {
|
||||
use super::*;
|
||||
use std::f64::consts::PI;
|
||||
use std::ops::Bound;
|
||||
impl ParametricSurface for Sphere {
|
||||
type Point = Point3;
|
||||
type Vector = Vector3;
|
||||
#[inline]
|
||||
fn subs(&self, u: f64, v: f64) -> Point3 { self.0.subs(PI / 2.0 - v, u) }
|
||||
#[inline]
|
||||
fn uder(&self, u: f64, v: f64) -> Vector3 { self.0.vder(PI / 2.0 - v, u) }
|
||||
#[inline]
|
||||
fn vder(&self, u: f64, v: f64) -> Vector3 { -self.0.uder(PI / 2.0 - v, u) }
|
||||
#[inline]
|
||||
fn uuder(&self, u: f64, v: f64) -> Vector3 { self.0.vvder(PI / 2.0 - v, u) }
|
||||
#[inline]
|
||||
fn uvder(&self, u: f64, v: f64) -> Vector3 { -self.0.uvder(PI / 2.0 - v, u) }
|
||||
#[inline]
|
||||
fn vvder(&self, u: f64, v: f64) -> Vector3 { self.0.uuder(PI / 2.0 - v, u) }
|
||||
#[inline]
|
||||
fn parameter_range(&self) -> (ParameterRange, ParameterRange) {
|
||||
(
|
||||
(Bound::Included(0.0), Bound::Excluded(2.0 * PI)),
|
||||
(Bound::Included(-PI / 2.0), Bound::Excluded(PI / 2.0)),
|
||||
)
|
||||
}
|
||||
#[inline]
|
||||
fn u_period(&self) -> Option<f64> { Some(2.0 * PI) }
|
||||
}
|
||||
impl ParametricSurface3D for Sphere {
|
||||
#[inline]
|
||||
fn normal(&self, u: f64, v: f64) -> Vector3 { self.0.normal(PI / 2.0 - v, u) }
|
||||
}
|
||||
impl SearchNearestParameter<D2> for Sphere {
|
||||
type Point = Point3;
|
||||
#[inline]
|
||||
fn search_nearest_parameter<H: Into<SPHint2D>>(
|
||||
&self,
|
||||
point: Self::Point,
|
||||
hint: H,
|
||||
trials: usize,
|
||||
) -> Option<(f64, f64)> {
|
||||
self.0
|
||||
.search_nearest_parameter(point, hint, trials)
|
||||
.map(|(u, v)| (v, PI / 2.0 - u))
|
||||
}
|
||||
}
|
||||
impl SearchParameter<D2> for Sphere {
|
||||
type Point = Point3;
|
||||
#[inline]
|
||||
fn search_parameter<H: Into<SPHint2D>>(
|
||||
&self,
|
||||
point: Self::Point,
|
||||
hint: H,
|
||||
trials: usize,
|
||||
) -> Option<(f64, f64)> {
|
||||
self.0
|
||||
.search_parameter(point, hint, trials)
|
||||
.map(|(u, v)| (v, PI / 2.0 - u))
|
||||
}
|
||||
}
|
||||
impl ParameterDivision2D for Sphere {
|
||||
#[inline]
|
||||
fn parameter_division(
|
||||
&self,
|
||||
((u0, u1), (v0, v1)): ((f64, f64), (f64, f64)),
|
||||
tol: f64,
|
||||
) -> (Vec<f64>, Vec<f64>) {
|
||||
let range = ((PI / 2.0 - v1, PI / 2.0 - v0), (u0, u1));
|
||||
let (udiv0, vdiv0) = self.0.parameter_division(range, tol);
|
||||
let vdiv = udiv0.into_iter().map(|u| PI / 2.0 - u).collect();
|
||||
(vdiv0, vdiv)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
proptest::proptest! {
|
||||
#[test]
|
||||
fn surface(
|
||||
center in proptest::array::uniform3(-100.0f64..100.0f64),
|
||||
radius in 0.1f64..100.0f64,
|
||||
(u, v) in (0.0..=2.0 * PI, -PI / 2.0..=PI / 2.0),
|
||||
) {
|
||||
const EPS: f64 = 1.0e-3;
|
||||
let sphere = Sphere(truck_geometry::prelude::Sphere::new(center.into(), radius));
|
||||
|
||||
let uder0 = sphere.uder(u, v);
|
||||
let uder1 = (sphere.subs(u + EPS, v) - sphere.subs(u - EPS, v)) / (2.0 * EPS);
|
||||
assert!(
|
||||
(uder0 - uder1).magnitude2() < EPS,
|
||||
"uder failed: {uder0:?}, {uder1:?}"
|
||||
);
|
||||
|
||||
let vder0 = sphere.vder(u, v);
|
||||
let vder1 = (sphere.subs(u, v + EPS) - sphere.subs(u, v - EPS)) / (2.0 * EPS);
|
||||
assert!(
|
||||
(vder0 - vder1).magnitude2() < EPS,
|
||||
"vder failed: {vder0:?}, {vder1:?}"
|
||||
);
|
||||
|
||||
let uuder0 = sphere.uuder(u, v);
|
||||
let uuder1 = (sphere.uder(u + EPS, v) - sphere.uder(u - EPS, v)) / (2.0 * EPS);
|
||||
assert!(
|
||||
(uuder0 - uuder1).magnitude2() < EPS,
|
||||
"uuder failed: {uuder0:?}, {uuder1:?}"
|
||||
);
|
||||
|
||||
let uvder0 = sphere.uvder(u, v);
|
||||
let uvder1 = (sphere.uder(u, v + EPS) - sphere.uder(u, v - EPS)) / (2.0 * EPS);
|
||||
assert!(
|
||||
(uvder0 - uvder1).magnitude2() < EPS,
|
||||
"uvder failed: {uvder0:?}, {uvder1:?}"
|
||||
);
|
||||
|
||||
let vvder0 = sphere.vvder(u, v);
|
||||
let vvder1 = (sphere.vder(u, v + EPS) - sphere.vder(u, v - EPS)) / (2.0 * EPS);
|
||||
assert!(
|
||||
(vvder0 - vvder1).magnitude2() < EPS,
|
||||
"vvder failed: {vvder0:?}, {vvder1:?}"
|
||||
);
|
||||
|
||||
let n0 = sphere.normal(u, v);
|
||||
let n1 = sphere.uder(u, v).cross(sphere.vder(u, v)).normalize();
|
||||
assert!(
|
||||
(n0 - n1).magnitude2() < EPS,
|
||||
"normal failed: {n0:?}, {n1:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl truck_stepio::out::ConstStepLength for Processor<Sphere, Matrix4> {
|
||||
const LENGTH: usize = Processor::<truck_geometry::prelude::Sphere, Matrix4>::LENGTH;
|
||||
}
|
||||
impl truck_stepio::out::StepLength for Processor<Sphere, Matrix4> {
|
||||
fn step_length(&self) -> usize { <Self as truck_stepio::out::ConstStepLength>::LENGTH }
|
||||
}
|
||||
impl truck_stepio::out::DisplayByStep for Processor<Sphere, Matrix4> {
|
||||
fn fmt(&self, idx: usize, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
Processor::new(self.entity().0)
|
||||
.transformed(*self.transform())
|
||||
.fmt(idx, f)
|
||||
}
|
||||
}
|
||||
|
||||
/// Implementation required to apply a closed surface division to a shape parsed from a STEP file.
|
||||
mod from_pcurve {
|
||||
use super::{Curve2D, Curve3D, Surface};
|
||||
use truck_geometry::prelude::*;
|
||||
|
||||
impl From<PCurve<Line<Point2>, Surface>> for Curve3D {
|
||||
fn from(value: PCurve<Line<Point2>, Surface>) -> Self {
|
||||
let (line, surface) = value.decompose();
|
||||
Curve3D::PCurve(PCurve::new(Curve2D::Line(line).into(), surface.into()))
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
-2784
File diff suppressed because it is too large
Load Diff
Vendored
-78
@@ -1,78 +0,0 @@
|
||||
//! Reads/writes STEP files from/to truck.
|
||||
//!
|
||||
//! # Current Status
|
||||
//!
|
||||
//! It is possible to output data modeled by truck-modeling.
|
||||
//! Shapes created by set operations cannot be output yet.
|
||||
//! Input will come further down the road.
|
||||
|
||||
// SolidCraft: compiler warnings no longer fail the build.
|
||||
#![deny(clippy::all, rust_2018_idioms)]
|
||||
#![warn(
|
||||
missing_docs,
|
||||
missing_debug_implementations,
|
||||
trivial_casts,
|
||||
trivial_numeric_casts,
|
||||
unsafe_code,
|
||||
unstable_features,
|
||||
unused_import_braces,
|
||||
unused_qualifications
|
||||
)]
|
||||
|
||||
/// STEP input module
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use truck_stepio::r#in::{*, alias::*};
|
||||
/// use ruststep::tables::EntityTable;
|
||||
/// // read file
|
||||
/// let step_string = include_str!(concat!(
|
||||
/// env!("CARGO_MANIFEST_DIR"),
|
||||
/// "/../resources/step/occt-cube.step",
|
||||
/// ));
|
||||
/// // parse step file
|
||||
/// let exchange = ruststep::parser::parse(&step_string).unwrap();
|
||||
/// // convert the parsing results to a Rust struct
|
||||
/// let table = Table::from_data_section(&exchange.data[0]);
|
||||
/// // get `CartesianPoint` registered in #102
|
||||
/// let step_point = EntityTable::<CartesianPointHolder>::get_owned(&table, 102).unwrap();
|
||||
/// // convert `CartesianPoint` in STEP to `Point3` in cgmath
|
||||
/// let cgmath_point = Point3::from(&step_point);
|
||||
/// // check parse result
|
||||
/// assert_eq!(cgmath_point, Point3::new(0.0, 10.0, 0.0));
|
||||
/// ```
|
||||
#[cfg(feature = "in")]
|
||||
pub mod r#in;
|
||||
/// STEP output module
|
||||
pub mod out;
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! impl_from {
|
||||
($(impl From<&$refed: ty> for $converted: ty {
|
||||
$from_func: item
|
||||
})*) => {
|
||||
$(impl From<&$refed> for $converted {
|
||||
$from_func
|
||||
}
|
||||
impl From<$refed> for $converted {
|
||||
fn from(x: $refed) -> Self { Self::from(&x) }
|
||||
})*
|
||||
};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! impl_try_from {
|
||||
($(impl TryFrom<&$refed: ty> for $converted: ty {
|
||||
$try_from_func: item
|
||||
})*) => {
|
||||
$(impl TryFrom<&$refed> for $converted {
|
||||
type Error = ExpressParseError;
|
||||
$try_from_func
|
||||
}
|
||||
impl TryFrom<$refed> for $converted {
|
||||
type Error = ExpressParseError;
|
||||
fn try_from(x: $refed) -> Result<Self, ExpressParseError> { Self::try_from(&x) }
|
||||
})*
|
||||
};
|
||||
}
|
||||
-834
@@ -1,834 +0,0 @@
|
||||
use super::{Result, *};
|
||||
use truck_geometry::prelude::*;
|
||||
use truck_modeling::{Curve as ModelingCurve, Leader, Surface as ModelingSurface};
|
||||
use truck_polymesh::PolylineCurve;
|
||||
|
||||
impl DisplayByStep for Point2 {
|
||||
fn fmt(&self, idx: usize, f: &mut Formatter<'_>) -> Result {
|
||||
f.write_fmt(format_args!(
|
||||
"#{idx} = CARTESIAN_POINT('', {coordinates});\n",
|
||||
coordinates = SliceDisplay(AsRef::<[f64; 2]>::as_ref(self)),
|
||||
))
|
||||
}
|
||||
}
|
||||
impl_const_step_length!(Point2, 1);
|
||||
|
||||
impl DisplayByStep for Point3 {
|
||||
fn fmt(&self, idx: usize, f: &mut Formatter<'_>) -> Result {
|
||||
f.write_fmt(format_args!(
|
||||
"#{idx} = CARTESIAN_POINT('', {coordinates});\n",
|
||||
coordinates = SliceDisplay(AsRef::<[f64; 3]>::as_ref(self)),
|
||||
))
|
||||
}
|
||||
}
|
||||
impl_const_step_length!(Point3, 1);
|
||||
|
||||
/// class for display `DIRECTION`.
|
||||
#[derive(Clone, Debug, Copy)]
|
||||
pub struct VectorAsDirection<V>(pub V);
|
||||
|
||||
impl DisplayByStep for VectorAsDirection<Vector2> {
|
||||
fn fmt(&self, idx: usize, f: &mut Formatter<'_>) -> Result {
|
||||
f.write_fmt(format_args!(
|
||||
"#{idx} = DIRECTION('', {direction_ratios});\n",
|
||||
direction_ratios = SliceDisplay(AsRef::<[f64; 2]>::as_ref(&self.0)),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl DisplayByStep for VectorAsDirection<Vector3> {
|
||||
fn fmt(&self, idx: usize, f: &mut Formatter<'_>) -> Result {
|
||||
f.write_fmt(format_args!(
|
||||
"#{idx} = DIRECTION('', {direction_ratios});\n",
|
||||
direction_ratios = SliceDisplay(AsRef::<[f64; 3]>::as_ref(&self.0)),
|
||||
))
|
||||
}
|
||||
}
|
||||
impl_const_step_length!(VectorAsDirection<V>, 1, <V>);
|
||||
|
||||
impl<V> DisplayByStep for V
|
||||
where
|
||||
V: InnerSpace<Scalar = f64>,
|
||||
VectorAsDirection<V>: DisplayByStep,
|
||||
{
|
||||
fn fmt(&self, idx: usize, f: &mut Formatter<'_>) -> Result {
|
||||
let magnitude = self.magnitude();
|
||||
let direction_idx = idx + 1;
|
||||
f.write_fmt(format_args!(
|
||||
"#{idx} = VECTOR('', #{direction_idx}, {magnitude});\n{direction}",
|
||||
direction = StepDisplay::new(VectorAsDirection(*self / magnitude), direction_idx),
|
||||
magnitude = FloatDisplay(magnitude),
|
||||
))
|
||||
}
|
||||
}
|
||||
impl_const_step_length!(Vector2, 2);
|
||||
impl_const_step_length!(Vector3, 2);
|
||||
|
||||
impl<P> DisplayByStep for Line<P>
|
||||
where
|
||||
P: EuclideanSpace + ConstStepLength + DisplayByStep,
|
||||
P::Diff: DisplayByStep,
|
||||
{
|
||||
fn fmt(&self, idx: usize, f: &mut Formatter<'_>) -> Result {
|
||||
let pnt_idx = idx + 1;
|
||||
let dir_idx = idx + 1 + P::LENGTH;
|
||||
f.write_fmt(format_args!(
|
||||
"#{idx} = LINE('', #{pnt_idx}, #{dir_idx});\n{pnt}{dir}",
|
||||
pnt = StepDisplay::new(self.0, pnt_idx),
|
||||
dir = StepDisplay::new(self.1 - self.0, dir_idx),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> StepLength for Line<P>
|
||||
where
|
||||
P: EuclideanSpace + ConstStepLength,
|
||||
P::Diff: ConstStepLength,
|
||||
{
|
||||
#[inline(always)]
|
||||
fn step_length(&self) -> usize { <Self as ConstStepLength>::LENGTH }
|
||||
}
|
||||
|
||||
impl<P> ConstStepLength for Line<P>
|
||||
where
|
||||
P: EuclideanSpace + ConstStepLength,
|
||||
P::Diff: ConstStepLength,
|
||||
{
|
||||
const LENGTH: usize = 1 + P::LENGTH + P::Diff::LENGTH;
|
||||
}
|
||||
|
||||
impl<P> StepCurve for Line<P> {}
|
||||
|
||||
impl<P> DisplayByStep for PolylineCurve<P>
|
||||
where P: Copy + ConstStepLength + DisplayByStep
|
||||
{
|
||||
fn fmt(&self, idx: usize, f: &mut Formatter<'_>) -> Result {
|
||||
f.write_fmt(format_args!(
|
||||
"#{idx} = POLYLINE('', {range});\n",
|
||||
range = IndexSliceDisplay(idx + 1..=idx + self.0.len())
|
||||
))?;
|
||||
let closure = |(i, p): (usize, &P)| p.fmt(idx + 1 + i * P::LENGTH, f);
|
||||
self.0.iter().enumerate().try_for_each(closure)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: ConstStepLength> StepLength for PolylineCurve<P> {
|
||||
#[inline(always)]
|
||||
fn step_length(&self) -> usize { 1 + self.0.len() * P::LENGTH }
|
||||
}
|
||||
|
||||
impl<P> StepCurve for PolylineCurve<P> {}
|
||||
|
||||
impl<P> DisplayByStep for BSplineCurve<P>
|
||||
where P: Copy + ConstStepLength + DisplayByStep
|
||||
{
|
||||
fn fmt(&self, idx: usize, f: &mut Formatter<'_>) -> Result {
|
||||
let (knots, multi) = self.knot_vec().to_single_multi();
|
||||
let control_points_instances = self
|
||||
.control_points()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, p)| StepDisplay::new(*p, idx + 1 + i * P::LENGTH))
|
||||
.collect::<Vec<_>>();
|
||||
f.write_fmt(format_args!(
|
||||
"#{idx} = B_SPLINE_CURVE_WITH_KNOTS('', {degree}, {control_points_list}, .UNSPECIFIED., .U., .U., {knot_multiplicities}, {knots}, .UNSPECIFIED.);\n{control_points_instances}",
|
||||
degree = self.degree(),
|
||||
control_points_list = IndexSliceDisplay((idx + 1..=idx + self.control_points().len() * P::LENGTH).step_by(P::LENGTH)),
|
||||
knot_multiplicities = SliceDisplay(&multi),
|
||||
knots = SliceDisplay(&knots),
|
||||
control_points_instances = SliceDisplay(&control_points_instances),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> StepLength for BSplineCurve<P> {
|
||||
#[inline(always)]
|
||||
fn step_length(&self) -> usize { self.control_points().len() + 1 }
|
||||
}
|
||||
|
||||
impl<P> StepCurve for BSplineCurve<P> {}
|
||||
|
||||
impl<V> DisplayByStep for NurbsCurve<V>
|
||||
where
|
||||
V: Homogeneous<f64>,
|
||||
V::Point: ConstStepLength + DisplayByStep,
|
||||
{
|
||||
fn fmt(&self, idx: usize, f: &mut Formatter<'_>) -> Result {
|
||||
let (knots, multi) = self.knot_vec().to_single_multi();
|
||||
let control_points_instances = self
|
||||
.control_points()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, v)| StepDisplay::new(v.to_point(), idx + 1 + i * V::Point::LENGTH))
|
||||
.collect::<Vec<_>>();
|
||||
let weights = self
|
||||
.control_points()
|
||||
.iter()
|
||||
.map(|v| v.weight())
|
||||
.collect::<Vec<_>>();
|
||||
f.write_fmt(format_args!(
|
||||
"#{idx} = (
|
||||
BOUNDED_CURVE()
|
||||
B_SPLINE_CURVE({degree}, {control_points_list}, .UNSPECIFIED., .U., .U.)
|
||||
B_SPLINE_CURVE_WITH_KNOTS({knot_multiplicities}, {knots}, .UNSPECIFIED.)
|
||||
CURVE()
|
||||
GEOMETRIC_REPRESENTATION_ITEM()
|
||||
RATIONAL_B_SPLINE_CURVE({weights})
|
||||
REPRESENTATION_ITEM('')
|
||||
);\n{control_points_instances}",
|
||||
degree = self.degree(),
|
||||
control_points_list = IndexSliceDisplay(
|
||||
(idx + 1..=idx + self.control_points().len() * V::Point::LENGTH)
|
||||
.step_by(V::Point::LENGTH)
|
||||
),
|
||||
knot_multiplicities = SliceDisplay(&multi),
|
||||
knots = SliceDisplay(&knots),
|
||||
weights = SliceDisplay(&weights),
|
||||
control_points_instances = SliceDisplay(&control_points_instances),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> StepLength for NurbsCurve<V> {
|
||||
#[inline(always)]
|
||||
fn step_length(&self) -> usize { self.control_points().len() + 1 }
|
||||
}
|
||||
|
||||
impl<V> StepCurve for NurbsCurve<V> {}
|
||||
|
||||
impl DisplayByStep for Processor<TrimmedCurve<UnitCircle<Point2>>, Matrix3> {
|
||||
fn fmt(&self, idx: usize, f: &mut Formatter<'_>) -> Result {
|
||||
let transform = *self.transform();
|
||||
let position_idx = idx + 1;
|
||||
let location_idx = idx + 2;
|
||||
let ref_direction_idx = idx + 3;
|
||||
let r0 = transform[0].magnitude();
|
||||
let r1 = transform[1].magnitude();
|
||||
let ref_direction = VectorAsDirection(transform[0].truncate() / r0);
|
||||
let location = transform[2].to_point();
|
||||
if r0.near(&r1) {
|
||||
let r = FloatDisplay(r0);
|
||||
f.write_fmt(format_args!("#{idx} = CIRCLE('', #{position_idx}, {r});\n"))?;
|
||||
} else {
|
||||
let (r0, r1) = (FloatDisplay(r0), FloatDisplay(r1));
|
||||
f.write_fmt(format_args!(
|
||||
"#{idx} = ELLIPSE('', #{position_idx}, {r0}, {r1});\n"
|
||||
))?;
|
||||
}
|
||||
f.write_fmt(format_args!(
|
||||
"#{position_idx} = AXIS2_PLACEMENT_2D('', #{location_idx}, #{ref_direction_idx});\n",
|
||||
))?;
|
||||
DisplayByStep::fmt(&location, location_idx, f)?;
|
||||
DisplayByStep::fmt(&ref_direction, ref_direction_idx, f)
|
||||
}
|
||||
}
|
||||
impl_const_step_length!(Processor<TrimmedCurve<UnitCircle<Point2>>, Matrix3>, 4);
|
||||
|
||||
impl DisplayByStep for Processor<TrimmedCurve<UnitCircle<Point3>>, Matrix4> {
|
||||
fn fmt(&self, idx: usize, f: &mut Formatter<'_>) -> Result {
|
||||
let transform = self.transform();
|
||||
let position_idx = idx + 1;
|
||||
let location_idx = idx + 2;
|
||||
let axis_idx = idx + 3;
|
||||
let ref_direction_idx = idx + 4;
|
||||
let location = transform[3].to_point();
|
||||
let axis = VectorAsDirection(transform[2].truncate().normalize());
|
||||
let r0 = transform[0].magnitude();
|
||||
let r1 = transform[1].magnitude();
|
||||
let ref_direction = VectorAsDirection(transform[0].truncate() / r0);
|
||||
if r0.near(&r1) {
|
||||
let r = FloatDisplay(r0);
|
||||
f.write_fmt(format_args!("#{idx} = CIRCLE('', #{position_idx}, {r});\n"))?;
|
||||
} else {
|
||||
let (r0, r1) = (FloatDisplay(r0), FloatDisplay(r1));
|
||||
f.write_fmt(format_args!(
|
||||
"#{idx} = ELLIPSE('', #{position_idx}, {r0}, {r1});\n"
|
||||
))?;
|
||||
}
|
||||
f.write_fmt(format_args!(
|
||||
"#{position_idx} = AXIS2_PLACEMENT_3D('', #{location_idx}, #{axis_idx}, #{ref_direction_idx});\n",
|
||||
))?;
|
||||
DisplayByStep::fmt(&location, location_idx, f)?;
|
||||
DisplayByStep::fmt(&axis, axis_idx, f)?;
|
||||
DisplayByStep::fmt(&ref_direction, ref_direction_idx, f)
|
||||
}
|
||||
}
|
||||
impl_const_step_length!(Processor<TrimmedCurve<UnitCircle<Point3>>, Matrix4>, 5);
|
||||
|
||||
impl DisplayByStep for Processor<TrimmedCurve<UnitHyperbola<Point2>>, Matrix3> {
|
||||
fn fmt(&self, idx: usize, f: &mut Formatter<'_>) -> Result {
|
||||
let transform = *self.transform();
|
||||
let position_idx = idx + 1;
|
||||
let location_idx = idx + 2;
|
||||
let ref_direction_idx = idx + 3;
|
||||
let r0 = transform[0].magnitude();
|
||||
let r1 = transform[1].magnitude();
|
||||
let ref_direction_raw = VectorAsDirection(transform[0].truncate() / r0);
|
||||
let ref_direction = StepDisplay::new(ref_direction_raw, ref_direction_idx);
|
||||
let location = StepDisplay::new(transform[2].to_point(), location_idx);
|
||||
let (r0, r1) = (FloatDisplay(r0), FloatDisplay(r1));
|
||||
f.write_fmt(format_args!(
|
||||
"#{idx} = HYPERBOLA('', #{position_idx}, {r0}, {r1});
|
||||
#{position_idx} = AXIS2_PLACEMENT_2D('', #{location_idx}, #{ref_direction_idx});
|
||||
{location}{ref_direction}"
|
||||
))
|
||||
}
|
||||
}
|
||||
impl_const_step_length!(Processor<TrimmedCurve<UnitHyperbola<Point2>>, Matrix3>, 4);
|
||||
|
||||
impl DisplayByStep for Processor<TrimmedCurve<UnitHyperbola<Point3>>, Matrix4> {
|
||||
fn fmt(&self, idx: usize, f: &mut Formatter<'_>) -> Result {
|
||||
let transform = self.transform();
|
||||
let position_idx = idx + 1;
|
||||
let location_idx = idx + 2;
|
||||
let axis_idx = idx + 3;
|
||||
let ref_direction_idx = idx + 4;
|
||||
let location = StepDisplay::new(transform[3].to_point(), location_idx);
|
||||
let axis_raw = VectorAsDirection(transform[2].truncate().normalize());
|
||||
let axis = StepDisplay::new(axis_raw, axis_idx);
|
||||
let r0 = transform[0].magnitude();
|
||||
let r1 = transform[1].magnitude();
|
||||
let ref_direction_raw = VectorAsDirection(transform[0].truncate() / r0);
|
||||
let ref_direction = StepDisplay::new(ref_direction_raw, ref_direction_idx);
|
||||
let (r0, r1) = (FloatDisplay(r0), FloatDisplay(r1));
|
||||
f.write_fmt(format_args!(
|
||||
"#{idx} = HYPERBOLA('', #{position_idx}, {r0}, {r1});
|
||||
#{position_idx} = AXIS2_PLACEMENT_3D('', #{location_idx}, #{axis_idx}, #{ref_direction_idx});
|
||||
{location}{axis}{ref_direction}"
|
||||
))
|
||||
}
|
||||
}
|
||||
impl_const_step_length!(Processor<TrimmedCurve<UnitHyperbola<Point3>>, Matrix4>, 5);
|
||||
|
||||
impl DisplayByStep for Processor<TrimmedCurve<UnitParabola<Point2>>, Matrix3> {
|
||||
fn fmt(&self, idx: usize, f: &mut Formatter<'_>) -> Result {
|
||||
let transform = *self.transform();
|
||||
let position_idx = idx + 1;
|
||||
let location_idx = idx + 2;
|
||||
let ref_direction_idx = idx + 3;
|
||||
let r0 = transform[0].magnitude();
|
||||
let r1 = transform[1].magnitude();
|
||||
let focal_dist = FloatDisplay(r1 * r1 / r0);
|
||||
let ref_direction_raw = VectorAsDirection(transform[0].truncate() / r0);
|
||||
let ref_direction = StepDisplay::new(ref_direction_raw, ref_direction_idx);
|
||||
let location = StepDisplay::new(transform[2].to_point(), location_idx);
|
||||
f.write_fmt(format_args!(
|
||||
"#{idx} = PARABOLA('', #{position_idx}, {focal_dist});
|
||||
#{position_idx} = AXIS2_PLACEMENT_2D('', #{location_idx}, #{ref_direction_idx});
|
||||
{location}{ref_direction}"
|
||||
))
|
||||
}
|
||||
}
|
||||
impl_const_step_length!(Processor<TrimmedCurve<UnitParabola<Point2>>, Matrix3>, 4);
|
||||
|
||||
impl DisplayByStep for Processor<TrimmedCurve<UnitParabola<Point3>>, Matrix4> {
|
||||
fn fmt(&self, idx: usize, f: &mut Formatter<'_>) -> Result {
|
||||
let transform = self.transform();
|
||||
let position_idx = idx + 1;
|
||||
let location_idx = idx + 2;
|
||||
let axis_idx = idx + 3;
|
||||
let ref_direction_idx = idx + 4;
|
||||
let location = StepDisplay::new(transform[3].to_point(), location_idx);
|
||||
let axis_raw = VectorAsDirection(transform[2].truncate().normalize());
|
||||
let axis = StepDisplay::new(axis_raw, axis_idx);
|
||||
let r0 = transform[0].magnitude();
|
||||
let r1 = transform[1].magnitude();
|
||||
let focal_dist = FloatDisplay(r1 * r1 / r0);
|
||||
let ref_direction_raw = VectorAsDirection(transform[0].truncate() / r0);
|
||||
let ref_direction = StepDisplay::new(ref_direction_raw, ref_direction_idx);
|
||||
f.write_fmt(format_args!(
|
||||
"#{idx} = PARABOLA('', #{position_idx}, {focal_dist});
|
||||
#{position_idx} = AXIS2_PLACEMENT_3D('', #{location_idx}, #{axis_idx}, #{ref_direction_idx});
|
||||
{location}{axis}{ref_direction}"
|
||||
))
|
||||
}
|
||||
}
|
||||
impl_const_step_length!(Processor<TrimmedCurve<UnitParabola<Point3>>, Matrix4>, 5);
|
||||
|
||||
impl<C, M: One> StepCurve for Processor<C, M> {
|
||||
#[inline(always)]
|
||||
fn same_sense(&self) -> bool { self.orientation() }
|
||||
}
|
||||
|
||||
impl<C, S> DisplayByStep for IntersectionCurve<C, S>
|
||||
where
|
||||
C: StepLength + DisplayByStep,
|
||||
S: StepLength + DisplayByStep,
|
||||
{
|
||||
fn fmt(&self, idx: usize, f: &mut Formatter<'_>) -> Result {
|
||||
let curve_idx = idx + 1;
|
||||
let surface0_idx = curve_idx + self.leader().step_length();
|
||||
let surface1_idx = surface0_idx + self.surface0().step_length();
|
||||
f.write_fmt(format_args!(
|
||||
"#{idx} = INTERSECTION_CURVE('', #{curve_idx}, (#{surface0_idx}, #{surface1_idx}), .CURVE_3D.);\n"
|
||||
))?;
|
||||
self.leader().fmt(curve_idx, f)?;
|
||||
self.surface0().fmt(surface0_idx, f)?;
|
||||
// SolidCraft: the second surface was written at the first one's index (duplicate ids).
|
||||
self.surface1().fmt(surface1_idx, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: StepLength, S: StepLength> StepLength for IntersectionCurve<C, S> {
|
||||
#[inline(always)]
|
||||
fn step_length(&self) -> usize {
|
||||
1 + self.leader().step_length()
|
||||
+ self.surface0().step_length()
|
||||
+ self.surface1().step_length()
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, S> ConstStepLength for IntersectionCurve<C, S>
|
||||
where
|
||||
C: ConstStepLength,
|
||||
S: ConstStepLength,
|
||||
{
|
||||
const LENGTH: usize = 1 + C::LENGTH + S::LENGTH + S::LENGTH;
|
||||
}
|
||||
|
||||
impl<C: StepCurve, S> StepCurve for IntersectionCurve<C, S> {
|
||||
#[inline(always)]
|
||||
fn same_sense(&self) -> bool { self.leader().same_sense() }
|
||||
}
|
||||
|
||||
impl<C, S> DisplayByStep for PCurve<C, S>
|
||||
where
|
||||
C: DisplayByStep,
|
||||
S: DisplayByStep + StepLength,
|
||||
{
|
||||
fn fmt(&self, idx: usize, f: &mut Formatter<'_>) -> Result {
|
||||
let surface_idx = idx + 1;
|
||||
let repr_idx = surface_idx + self.surface().step_length();
|
||||
let context_idx = repr_idx + 1;
|
||||
let curve_idx = repr_idx + 2;
|
||||
let curve = StepDisplay::new(self.curve(), curve_idx);
|
||||
let surface = StepDisplay::new(self.surface(), surface_idx);
|
||||
f.write_fmt(format_args!(
|
||||
"#{idx} = PCURVE('', #{surface_idx}, #{repr_idx});
|
||||
{surface}#{repr_idx} = DEFINITIONAL_REPRESENTATION('', (#{curve_idx}), #{context_idx});
|
||||
#{context_idx} = (
|
||||
GEOMETRIC_REPRESENTATION_CONTEXT(2)
|
||||
PARAMETRIC_REPRESENTATION_CONTEXT()
|
||||
REPRESENTATION_CONTEXT('2D SPACE', '')
|
||||
);
|
||||
{curve}"
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: StepLength, S: StepLength> StepLength for PCurve<C, S> {
|
||||
fn step_length(&self) -> usize { 3 + self.curve().step_length() + self.surface().step_length() }
|
||||
}
|
||||
|
||||
impl<C, S> ConstStepLength for PCurve<C, S>
|
||||
where
|
||||
C: ConstStepLength,
|
||||
S: ConstStepLength,
|
||||
{
|
||||
const LENGTH: usize = 3 + C::LENGTH + S::LENGTH;
|
||||
}
|
||||
|
||||
impl<C: StepCurve, S> StepCurve for PCurve<C, S> {
|
||||
#[inline(always)]
|
||||
fn same_sense(&self) -> bool { self.curve().same_sense() }
|
||||
}
|
||||
|
||||
impl DisplayByStep for Leader {
|
||||
fn fmt(&self, idx: usize, f: &mut Formatter<'_>) -> Result {
|
||||
match self {
|
||||
Leader::Polyline(x) => DisplayByStep::fmt(x, idx, f),
|
||||
Leader::BSpline(x) => DisplayByStep::fmt(x, idx, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StepLength for Leader {
|
||||
fn step_length(&self) -> usize {
|
||||
match self {
|
||||
Leader::Polyline(x) => x.step_length(),
|
||||
Leader::BSpline(x) => x.step_length(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StepCurve for Leader {}
|
||||
|
||||
impl DisplayByStep for ModelingCurve {
|
||||
fn fmt(&self, idx: usize, f: &mut Formatter<'_>) -> Result {
|
||||
match self {
|
||||
ModelingCurve::Line(x) => DisplayByStep::fmt(x, idx, f),
|
||||
ModelingCurve::BSplineCurve(x) => DisplayByStep::fmt(x, idx, f),
|
||||
ModelingCurve::NurbsCurve(x) => DisplayByStep::fmt(x, idx, f),
|
||||
ModelingCurve::IntersectionCurve(x) => DisplayByStep::fmt(x, idx, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StepLength for ModelingCurve {
|
||||
fn step_length(&self) -> usize {
|
||||
match self {
|
||||
ModelingCurve::Line(_) => Line::<Point3>::LENGTH,
|
||||
ModelingCurve::BSplineCurve(x) => x.step_length(),
|
||||
ModelingCurve::NurbsCurve(x) => x.step_length(),
|
||||
ModelingCurve::IntersectionCurve(x) => x.step_length(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StepCurve for ModelingCurve {}
|
||||
|
||||
impl DisplayByStep for Plane {
|
||||
fn fmt(&self, idx: usize, f: &mut Formatter<'_>) -> Result {
|
||||
let axis2_placement_idx = idx + 1;
|
||||
let location_idx = idx + 2;
|
||||
let z_axis_idx = idx + 3;
|
||||
let x_axis_idx = idx + 4;
|
||||
f.write_fmt(format_args!(
|
||||
"#{idx} = PLANE('', #{axis2_placement_idx});
|
||||
#{axis2_placement_idx} = AXIS2_PLACEMENT_3D('', #{location_idx}, #{z_axis_idx}, #{x_axis_idx});
|
||||
{location}{z_axis}{x_axis}",
|
||||
location = StepDisplay::new(self.origin(), location_idx),
|
||||
z_axis = StepDisplay::new(VectorAsDirection(self.normal()), z_axis_idx),
|
||||
x_axis = StepDisplay::new(VectorAsDirection(self.u_axis().normalize()), x_axis_idx)
|
||||
))
|
||||
}
|
||||
}
|
||||
impl_const_step_length!(Plane, 5);
|
||||
|
||||
impl StepSurface for Plane {}
|
||||
|
||||
impl DisplayByStep for Processor<Sphere, Matrix4> {
|
||||
fn fmt(&self, idx: usize, f: &mut Formatter<'_>) -> Result {
|
||||
let sphere = *self.entity();
|
||||
let transform = self.transform();
|
||||
let position_idx = idx + 1;
|
||||
let location_idx = idx + 2;
|
||||
let axis_idx = idx + 3;
|
||||
let ref_direction_idx = idx + 4;
|
||||
let location = transform[3].to_point() + sphere.center().to_vec();
|
||||
let axis = VectorAsDirection(transform[2].truncate().normalize());
|
||||
let r0 = transform[0].magnitude();
|
||||
let r1 = transform[1].magnitude();
|
||||
if !r0.near(&r1) {
|
||||
f.write_str("The transform of sphere includes non-uniform scale.")?;
|
||||
return ERR;
|
||||
}
|
||||
let ref_direction = VectorAsDirection(transform[0].truncate() / r0);
|
||||
let r = FloatDisplay(r0 * sphere.radius());
|
||||
f.write_fmt(format_args!(
|
||||
"#{idx} = SPHERICAL_SURFACE('', #{position_idx}, {r});
|
||||
#{position_idx} = AXIS2_PLACEMENT_3D('', #{location_idx}, #{axis_idx}, #{ref_direction_idx});\n"
|
||||
))?;
|
||||
DisplayByStep::fmt(&location, location_idx, f)?;
|
||||
DisplayByStep::fmt(&axis, axis_idx, f)?;
|
||||
DisplayByStep::fmt(&ref_direction, ref_direction_idx, f)
|
||||
}
|
||||
}
|
||||
impl_const_step_length!(Processor<Sphere, Matrix4>, 5);
|
||||
|
||||
impl DisplayByStep for Sphere {
|
||||
fn fmt(&self, idx: usize, f: &mut Formatter<'_>) -> Result {
|
||||
DisplayByStep::fmt(&Processor::new(*self), idx, f)
|
||||
}
|
||||
}
|
||||
impl_const_step_length!(Sphere, 5);
|
||||
impl StepSurface for Sphere {}
|
||||
|
||||
impl DisplayByStep for Processor<Torus, Matrix4> {
|
||||
fn fmt(&self, idx: usize, f: &mut Formatter<'_>) -> Result {
|
||||
let torus = *self.entity();
|
||||
let transform = self.transform();
|
||||
let position_idx = idx + 1;
|
||||
let location_idx = idx + 2;
|
||||
let axis_idx = idx + 3;
|
||||
let ref_direction_idx = idx + 4;
|
||||
let location = transform[3].to_point() + torus.center().to_vec();
|
||||
let axis = VectorAsDirection(transform[2].truncate().normalize());
|
||||
let r0 = transform[0].magnitude();
|
||||
let r1 = transform[1].magnitude();
|
||||
if !r0.near(&r1) {
|
||||
f.write_str("The transform of sphere includes non-uniform scale.")?;
|
||||
return ERR;
|
||||
}
|
||||
let ref_direction = VectorAsDirection(transform[0].truncate() / r0);
|
||||
let greater = FloatDisplay(r0 * torus.large_radius());
|
||||
let lesser = FloatDisplay(r0 * torus.small_radius());
|
||||
f.write_fmt(format_args!(
|
||||
"#{idx} = TOROIDAL_SURFACE('', #{position_idx}, {greater}, {lesser});
|
||||
#{position_idx} = AXIS2_PLACEMENT_3D('', #{location_idx}, #{axis_idx}, #{ref_direction_idx});\n",
|
||||
))?;
|
||||
DisplayByStep::fmt(&location, location_idx, f)?;
|
||||
DisplayByStep::fmt(&axis, axis_idx, f)?;
|
||||
DisplayByStep::fmt(&ref_direction, ref_direction_idx, f)
|
||||
}
|
||||
}
|
||||
impl_const_step_length!(Processor<Torus, Matrix4>, 5);
|
||||
|
||||
impl StepSurface for Processor<Torus, Matrix4> {
|
||||
#[inline(always)]
|
||||
fn same_sense(&self) -> bool { self.orientation() }
|
||||
}
|
||||
|
||||
impl DisplayByStep for Torus {
|
||||
fn fmt(&self, idx: usize, f: &mut Formatter<'_>) -> Result {
|
||||
DisplayByStep::fmt(&Processor::new(*self), idx, f)
|
||||
}
|
||||
}
|
||||
impl_const_step_length!(Torus, 5);
|
||||
impl StepSurface for Torus {}
|
||||
|
||||
impl<P> DisplayByStep for BSplineSurface<P>
|
||||
where P: Copy + DisplayByStep
|
||||
{
|
||||
fn fmt(&self, idx: usize, f: &mut Formatter<'_>) -> Result {
|
||||
let control_points = self.control_points();
|
||||
let control_points_instances = self
|
||||
.control_points()
|
||||
.iter()
|
||||
.flatten()
|
||||
.enumerate()
|
||||
.map(|(i, p)| StepDisplay::new(*p, idx + i + 1))
|
||||
.collect::<Vec<_>>();
|
||||
let mut counter = 0;
|
||||
let control_points_list = control_points
|
||||
.iter()
|
||||
.map(|slice| {
|
||||
counter += slice.len();
|
||||
IndexSliceDisplay(idx + counter - slice.len() + 1..=idx + counter)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let (uknots, umulti) = self.uknot_vec().to_single_multi();
|
||||
let (vknots, vmulti) = self.vknot_vec().to_single_multi();
|
||||
f.write_fmt(format_args!(
|
||||
"#{idx} = B_SPLINE_SURFACE_WITH_KNOTS('', {u_degree}, {v_degree}, {control_points_list}, .UNSPECIFIED., .U., .U., .U., \
|
||||
{u_multiplicities}, {v_multiplicities}, {u_knots}, {v_knots}, .UNSPECIFIED.);\n{control_points_instances}",
|
||||
u_degree = self.udegree(),
|
||||
v_degree = self.vdegree(),
|
||||
control_points_list = SliceDisplay(&control_points_list),
|
||||
u_multiplicities = SliceDisplay(&umulti),
|
||||
v_multiplicities = SliceDisplay(&vmulti),
|
||||
u_knots = SliceDisplay(&uknots),
|
||||
v_knots = SliceDisplay(&vknots),
|
||||
control_points_instances = SliceDisplay(&control_points_instances),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> StepLength for BSplineSurface<P> {
|
||||
#[inline(always)]
|
||||
fn step_length(&self) -> usize { 1 + self.control_points().iter().map(Vec::len).sum::<usize>() }
|
||||
}
|
||||
impl<P> StepSurface for BSplineSurface<P> {}
|
||||
|
||||
impl<V> DisplayByStep for NurbsSurface<V>
|
||||
where
|
||||
V: Homogeneous<f64>,
|
||||
V::Point: Copy + DisplayByStep,
|
||||
{
|
||||
fn fmt(&self, idx: usize, f: &mut Formatter<'_>) -> Result {
|
||||
let control_points_instances = self
|
||||
.control_points()
|
||||
.iter()
|
||||
.flatten()
|
||||
.enumerate()
|
||||
.map(|(i, v)| StepDisplay::new(v.to_point(), idx + i + 1))
|
||||
.collect::<Vec<_>>();
|
||||
let mut counter = 0;
|
||||
let control_points_list = self
|
||||
.control_points()
|
||||
.iter()
|
||||
.map(|slice| {
|
||||
counter += slice.len();
|
||||
IndexSliceDisplay(idx + counter - slice.len() + 1..=idx + counter)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let weights = self
|
||||
.control_points()
|
||||
.iter()
|
||||
.map(|slice| slice.iter().map(|v| v.weight()).collect::<Vec<_>>())
|
||||
.collect::<Vec<_>>();
|
||||
let weights = weights
|
||||
.iter()
|
||||
.map(|slice| SliceDisplay(slice))
|
||||
.collect::<Vec<_>>();
|
||||
let (uknots, umulti) = self.uknot_vec().to_single_multi();
|
||||
let (vknots, vmulti) = self.vknot_vec().to_single_multi();
|
||||
f.write_fmt(format_args!(
|
||||
"#{idx} = (
|
||||
BOUNDED_SURFACE()
|
||||
B_SPLINE_SURFACE({u_degree}, {v_degree}, {control_points_list}, .UNSPECIFIED., .U., .U., .U.)
|
||||
B_SPLINE_SURFACE_WITH_KNOTS({u_multiplicities}, {v_multiplicities}, {u_knots}, {v_knots}, .UNSPECIFIED.)
|
||||
GEOMETRIC_REPRESENTATION_ITEM()
|
||||
RATIONAL_B_SPLINE_SURFACE({weights})
|
||||
REPRESENTATION_ITEM('')
|
||||
SURFACE()
|
||||
);\n{control_points_instances}",
|
||||
u_degree = self.udegree(),
|
||||
v_degree = self.vdegree(),
|
||||
control_points_list = SliceDisplay(&control_points_list),
|
||||
u_multiplicities = SliceDisplay(&umulti),
|
||||
v_multiplicities = SliceDisplay(&vmulti),
|
||||
u_knots = SliceDisplay(&uknots),
|
||||
v_knots = SliceDisplay(&vknots),
|
||||
control_points_instances = SliceDisplay(&control_points_instances),
|
||||
weights = SliceDisplay(&weights),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> StepLength for NurbsSurface<V> {
|
||||
#[inline(always)]
|
||||
fn step_length(&self) -> usize { 1 + self.control_points().iter().map(Vec::len).sum::<usize>() }
|
||||
}
|
||||
impl<V> StepSurface for NurbsSurface<V> {}
|
||||
|
||||
impl<C> DisplayByStep for ExtrudedCurve<C, Vector3>
|
||||
where C: StepLength + DisplayByStep
|
||||
{
|
||||
fn fmt(&self, idx: usize, f: &mut Formatter<'_>) -> Result {
|
||||
let curve = self.entity_curve();
|
||||
let curve_idx = idx + 1;
|
||||
let vector_idx = idx + 1 + curve.step_length();
|
||||
let vector = self.extruding_vector();
|
||||
f.write_fmt(format_args!(
|
||||
"#{idx} = SURFACE_OF_LINEAR_EXTRUSION('', #{curve_idx}, #{vector_idx});{}{}",
|
||||
StepDisplay::new(curve, curve_idx),
|
||||
StepDisplay::new(vector, vector_idx),
|
||||
))
|
||||
}
|
||||
}
|
||||
impl<C: StepLength> StepLength for ExtrudedCurve<C, Vector3> {
|
||||
fn step_length(&self) -> usize { 1 + self.entity_curve().step_length() + Vector3::LENGTH }
|
||||
}
|
||||
impl<C: ConstStepLength> ConstStepLength for ExtrudedCurve<C, Vector3> {
|
||||
const LENGTH: usize = 1 + C::LENGTH + Vector3::LENGTH;
|
||||
}
|
||||
impl<C> StepSurface for ExtrudedCurve<C, Vector3> {}
|
||||
|
||||
impl<C, T: One> StepSurface for Processor<ExtrudedCurve<C, Vector3>, T> {
|
||||
#[inline(always)]
|
||||
fn same_sense(&self) -> bool { self.orientation() }
|
||||
}
|
||||
|
||||
impl<C> DisplayByStep for RevolutedCurve<C>
|
||||
where C: StepLength + DisplayByStep
|
||||
{
|
||||
fn fmt(&self, idx: usize, f: &mut Formatter<'_>) -> Result {
|
||||
let curve = self.entity_curve();
|
||||
let curve_idx = idx + 1;
|
||||
let axis_idx = curve_idx + curve.step_length();
|
||||
let location_idx = axis_idx + 1;
|
||||
let dir_idx = location_idx + 1;
|
||||
f.write_fmt(format_args!(
|
||||
"#{idx} = SURFACE_OF_REVOLUTION('', #{curve_idx}, #{axis_idx});
|
||||
{curve}#{axis_idx} = AXIS1_PLACEMENT('', #{location_idx}, #{dir_idx});\n{location}{dir}",
|
||||
curve = StepDisplay::new(curve, curve_idx),
|
||||
location = StepDisplay::new(self.origin(), location_idx),
|
||||
dir = StepDisplay::new(VectorAsDirection(self.axis()), dir_idx),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: StepLength> StepLength for RevolutedCurve<C> {
|
||||
#[inline(always)]
|
||||
fn step_length(&self) -> usize { 4 + self.entity_curve().step_length() }
|
||||
}
|
||||
|
||||
impl<C: ConstStepLength> ConstStepLength for RevolutedCurve<C> {
|
||||
const LENGTH: usize = 4 + C::LENGTH;
|
||||
}
|
||||
|
||||
// ISO 10303-42 parameterises surface_of_revolution by (angle, profile); truck by (profile,
|
||||
// angle), so the normals are opposite.
|
||||
impl<C> StepSurface for RevolutedCurve<C> {
|
||||
#[inline(always)]
|
||||
fn same_sense(&self) -> bool { false }
|
||||
}
|
||||
|
||||
impl<C> DisplayByStep for Processor<RevolutedCurve<C>, Matrix4>
|
||||
where C: StepLength + Transformed<Matrix4> + DisplayByStep
|
||||
{
|
||||
// SolidCraft: a transform that commutes with turning about the axis keeps the surface one of
|
||||
// revolution: the transformed profile revolved about the transformed axis. That holds for
|
||||
// uniform scales (the only case taken before) and also for a scale across the axis that
|
||||
// differs from the scale along it, which is how a shell or an offset moves a revolved
|
||||
// cylinder or cone face. Any other transform is still refused.
|
||||
fn fmt(&self, idx: usize, f: &mut Formatter<'_>) -> Result {
|
||||
let surface = self.entity();
|
||||
let transform = *self.transform();
|
||||
if transform.determinant().so_small() {
|
||||
f.write_str("Transform is not regular")?;
|
||||
return ERR;
|
||||
}
|
||||
let a = surface.axis().normalize();
|
||||
let la = transform.transform_vector(a);
|
||||
if la.so_small() {
|
||||
f.write_str("Transform is not regular")?;
|
||||
return ERR;
|
||||
}
|
||||
let axis = la.normalize();
|
||||
// Two directions across the axis, and their images.
|
||||
let seed = if a.x.abs() < 0.9 { Vector3::unit_x() } else { Vector3::unit_y() };
|
||||
let u = (seed - a * a.dot(seed)).normalize();
|
||||
let v = a.cross(u);
|
||||
let (lu, lv) = (transform.transform_vector(u), transform.transform_vector(v));
|
||||
let s = lu.magnitude();
|
||||
// Across the axis: a similarity onto the plane across the image axis.
|
||||
let commutes = !s.so_small()
|
||||
&& (lu.dot(axis) / s).so_small()
|
||||
&& (lv.dot(axis) / s).so_small()
|
||||
&& (lu.dot(lv) / (s * s)).so_small()
|
||||
&& (lv.magnitude() / s).near(&1.0);
|
||||
if !commutes {
|
||||
f.write_str("Transform does not keep the surface one of revolution.")?;
|
||||
return ERR;
|
||||
}
|
||||
let curve = surface.entity_curve().transformed(transform);
|
||||
let origin = transform.transform_point(surface.origin());
|
||||
let surface = RevolutedCurve::by_revolution(curve, origin, axis);
|
||||
DisplayByStep::fmt(&surface, idx, f)
|
||||
}
|
||||
}
|
||||
impl<C: StepLength> StepLength for Processor<RevolutedCurve<C>, Matrix4> {
|
||||
fn step_length(&self) -> usize { self.entity().step_length() }
|
||||
}
|
||||
|
||||
// SolidCraft: the written surface revolves the transformed profile about the transformed
|
||||
// axis; a mirroring transform reverses the turn, and with it the normal (was `!orientation`,
|
||||
// right only without mirroring). The STEP normal is the opposite of truck's (see above).
|
||||
impl<C> StepSurface for Processor<RevolutedCurve<C>, Matrix4> {
|
||||
#[inline(always)]
|
||||
fn same_sense(&self) -> bool { self.orientation() != (self.transform().determinant() >= 0.0) }
|
||||
}
|
||||
|
||||
impl DisplayByStep for ModelingSurface {
|
||||
fn fmt(&self, idx: usize, f: &mut Formatter<'_>) -> Result {
|
||||
match self {
|
||||
ModelingSurface::Plane(x) => DisplayByStep::fmt(x, idx, f),
|
||||
ModelingSurface::BSplineSurface(x) => DisplayByStep::fmt(x, idx, f),
|
||||
ModelingSurface::NurbsSurface(x) => DisplayByStep::fmt(x, idx, f),
|
||||
ModelingSurface::RevolutedCurve(x) => DisplayByStep::fmt(x, idx, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl StepLength for ModelingSurface {
|
||||
fn step_length(&self) -> usize {
|
||||
match self {
|
||||
ModelingSurface::Plane(_) => Plane::LENGTH,
|
||||
ModelingSurface::BSplineSurface(x) => x.step_length(),
|
||||
ModelingSurface::NurbsSurface(x) => x.step_length(),
|
||||
ModelingSurface::RevolutedCurve(x) => x.entity().step_length(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SolidCraft: was the default (`true`), which dropped an inverted revolution's orientation.
|
||||
impl StepSurface for ModelingSurface {
|
||||
fn same_sense(&self) -> bool {
|
||||
match self {
|
||||
ModelingSurface::RevolutedCurve(x) => StepSurface::same_sense(x),
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
-388
@@ -1,388 +0,0 @@
|
||||
use std::fmt::{Debug, Display, Formatter, Result};
|
||||
|
||||
use truck_topology::compress::*;
|
||||
|
||||
use self::topology::PreStepModel;
|
||||
|
||||
const ERR: Result = Err(std::fmt::Error);
|
||||
|
||||
#[cfg(feature = "derive")]
|
||||
pub use truck_derivers::{DisplayByStep, StepLength};
|
||||
|
||||
/// display boolean number to step file
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct BooleanDisplay(pub bool);
|
||||
|
||||
impl Display for BooleanDisplay {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||
match self.0 {
|
||||
true => f.write_str(".T."),
|
||||
false => f.write_str(".F."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// display float number to step file
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct FloatDisplay(pub f64);
|
||||
|
||||
impl Display for FloatDisplay {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||
let FloatDisplay(x) = *self;
|
||||
if f64::abs(x) < 1.0e-2 && x != 0.0 {
|
||||
f.write_fmt(format_args!("{x:.10E}"))
|
||||
} else {
|
||||
f.write_fmt(format_args!("{x:?}"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// display step slice
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_stepio::out::SliceDisplay;
|
||||
/// let slice = &[1.0, 2.0, 3.0, 4.0];
|
||||
/// let display = SliceDisplay(slice);
|
||||
/// let step_string = display.to_string();
|
||||
/// assert_eq!(step_string, "(1.0, 2.0, 3.0, 4.0)");
|
||||
/// ```
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SliceDisplay<'a, T>(pub &'a [T]);
|
||||
|
||||
impl<'a> Display for SliceDisplay<'a, f64> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||
f.write_str("(")?;
|
||||
self.0.iter().enumerate().try_for_each(|(i, x)| {
|
||||
if i != 0 {
|
||||
f.write_str(", ")?;
|
||||
}
|
||||
Display::fmt(&FloatDisplay(*x), f)
|
||||
})?;
|
||||
f.write_str(")")
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Display for SliceDisplay<'a, usize> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||
f.write_str("(")?;
|
||||
self.0.iter().enumerate().try_for_each(|(i, x)| {
|
||||
if i != 0 {
|
||||
f.write_str(", ")?;
|
||||
}
|
||||
Display::fmt(x, f)
|
||||
})?;
|
||||
f.write_str(")")
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Display for SliceDisplay<'a, String> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||
f.write_str("(")?;
|
||||
self.0.iter().enumerate().try_for_each(|(i, x)| {
|
||||
if i != 0 {
|
||||
f.write_str(", ")?;
|
||||
}
|
||||
f.write_fmt(format_args!("'{x}'"))
|
||||
})?;
|
||||
f.write_str(")")
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Display for SliceDisplay<'a, SliceDisplay<'a, f64>> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||
f.write_str("(")?;
|
||||
self.0.iter().enumerate().try_for_each(|(i, x)| {
|
||||
if i != 0 {
|
||||
f.write_str(", ")?;
|
||||
}
|
||||
Display::fmt(x, f)
|
||||
})?;
|
||||
f.write_str(")")
|
||||
}
|
||||
}
|
||||
|
||||
/// display index slice
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use truck_stepio::out::*;
|
||||
/// let indices = [1, 10, 100, 1000, 10000];
|
||||
/// let display = IndexSliceDisplay(indices.into_iter());
|
||||
/// let step_string = display.to_string();
|
||||
/// assert_eq!(step_string, "(#1, #10, #100, #1000, #10000)");
|
||||
/// ```
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct IndexSliceDisplay<I>(pub I);
|
||||
|
||||
impl<I: Clone + IntoIterator<Item = usize>> Display for IndexSliceDisplay<I> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||
f.write_str("(")?;
|
||||
self.0
|
||||
.clone()
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.try_for_each(|(i, idx)| {
|
||||
if i != 0 {
|
||||
f.write_fmt(format_args!(", #{idx}"))
|
||||
} else {
|
||||
f.write_fmt(format_args!("#{idx}"))
|
||||
}
|
||||
})?;
|
||||
f.write_str(")")
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, I: Clone + IntoIterator<Item = usize>> Display for SliceDisplay<'a, IndexSliceDisplay<I>> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||
f.write_str("(")?;
|
||||
self.0.iter().enumerate().try_for_each(|(i, x)| {
|
||||
if i != 0 {
|
||||
f.write_str(", ")?;
|
||||
}
|
||||
Display::fmt(x, f)
|
||||
})?;
|
||||
f.write_str(")")
|
||||
}
|
||||
}
|
||||
|
||||
/// trait for outputting by STEP file format.
|
||||
pub trait DisplayByStep {
|
||||
/// formatter
|
||||
fn fmt(&self, idx: usize, f: &mut Formatter<'_>) -> Result;
|
||||
}
|
||||
|
||||
impl<T: DisplayByStep> DisplayByStep for &T {
|
||||
fn fmt(&self, idx: usize, f: &mut Formatter<'_>) -> Result { DisplayByStep::fmt(*self, idx, f) }
|
||||
}
|
||||
|
||||
impl<T: DisplayByStep> DisplayByStep for Box<T> {
|
||||
fn fmt(&self, idx: usize, f: &mut Formatter<'_>) -> Result {
|
||||
DisplayByStep::fmt(self.as_ref(), idx, f)
|
||||
}
|
||||
}
|
||||
|
||||
/// Display struct for outputting some objects to STEP file format.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct StepDisplay<T> {
|
||||
entity: T,
|
||||
idx: usize,
|
||||
}
|
||||
|
||||
impl<'a, T> Display for SliceDisplay<'a, StepDisplay<T>>
|
||||
where StepDisplay<T>: Display
|
||||
{
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||
self.0.iter().try_for_each(|x| Display::fmt(x, f))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> StepDisplay<T> {
|
||||
/// constructor
|
||||
#[inline]
|
||||
pub const fn new(entity: T, idx: usize) -> Self { Self { entity, idx } }
|
||||
/// return entity
|
||||
#[inline]
|
||||
pub const fn entity(&self) -> &T { &self.entity }
|
||||
/// return index
|
||||
#[inline]
|
||||
pub const fn index(&self) -> usize { self.idx }
|
||||
}
|
||||
|
||||
impl<T: DisplayByStep> Display for StepDisplay<T> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result { DisplayByStep::fmt(&self.entity, self.idx, f) }
|
||||
}
|
||||
|
||||
/// Calculate how many lines are used in outputting an object to a STEP file
|
||||
pub trait StepLength {
|
||||
/// Calculate how many lines are used in outputting an object to a STEP file
|
||||
fn step_length(&self) -> usize;
|
||||
}
|
||||
|
||||
impl<T: StepLength> StepLength for &T {
|
||||
#[inline(always)]
|
||||
fn step_length(&self) -> usize { StepLength::step_length(*self) }
|
||||
}
|
||||
|
||||
impl<T: StepLength> StepLength for Box<T> {
|
||||
#[inline(always)]
|
||||
fn step_length(&self) -> usize { self.as_ref().step_length() }
|
||||
}
|
||||
|
||||
/// Constant numbers of lines for outputting an object to a STEP file.
|
||||
/// `x.step_length() == X::LENGTH` must always hold.
|
||||
pub trait ConstStepLength: StepLength {
|
||||
/// the number of line
|
||||
const LENGTH: usize;
|
||||
}
|
||||
|
||||
impl<T: ConstStepLength> ConstStepLength for &T {
|
||||
const LENGTH: usize = T::LENGTH;
|
||||
}
|
||||
|
||||
impl<T: ConstStepLength> ConstStepLength for Box<T> {
|
||||
const LENGTH: usize = T::LENGTH;
|
||||
}
|
||||
|
||||
macro_rules! impl_const_step_length {
|
||||
($type: ty, $len: expr $(,<$($gen: ident),*>)?) => {
|
||||
impl$(<$($gen),*>)? ConstStepLength for $type {
|
||||
const LENGTH: usize = $len;
|
||||
}
|
||||
impl$(<$($gen),*>)? StepLength for $type {
|
||||
#[inline(always)]
|
||||
fn step_length(&self) -> usize { <Self as ConstStepLength>::LENGTH }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Additional information for output to `edge_curve`.
|
||||
pub trait StepCurve {
|
||||
/// the parameter `same_sense`.
|
||||
#[inline(always)]
|
||||
fn same_sense(&self) -> bool { true }
|
||||
}
|
||||
|
||||
impl<T: StepCurve> StepCurve for &T {
|
||||
#[inline(always)]
|
||||
fn same_sense(&self) -> bool { (*self).same_sense() }
|
||||
}
|
||||
|
||||
impl<T: StepCurve> StepCurve for Box<T> {
|
||||
#[inline(always)]
|
||||
fn same_sense(&self) -> bool { self.as_ref().same_sense() }
|
||||
}
|
||||
|
||||
/// Additional information for output to `face_surface`.
|
||||
pub trait StepSurface {
|
||||
/// the parameter `same_sense`.
|
||||
#[inline(always)]
|
||||
fn same_sense(&self) -> bool { true }
|
||||
}
|
||||
|
||||
impl<T: StepSurface> StepSurface for &T {
|
||||
#[inline(always)]
|
||||
fn same_sense(&self) -> bool { (*self).same_sense() }
|
||||
}
|
||||
|
||||
impl<T: StepSurface> StepSurface for Box<T> {
|
||||
#[inline(always)]
|
||||
fn same_sense(&self) -> bool { self.as_ref().same_sense() }
|
||||
}
|
||||
|
||||
/// Describe STEP file header
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct StepHeaderDescriptor {
|
||||
/// file name
|
||||
pub file_name: String,
|
||||
/// time stamp
|
||||
pub time_stamp: String,
|
||||
/// authors
|
||||
pub authors: Vec<String>,
|
||||
/// organization
|
||||
pub organization: Vec<String>,
|
||||
/// organization system
|
||||
pub organization_system: String,
|
||||
/// authorization
|
||||
pub authorization: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct StepHeader {
|
||||
file_name: String,
|
||||
time_stamp: String,
|
||||
authors: Vec<String>,
|
||||
organization: Vec<String>,
|
||||
origination_system: String,
|
||||
authorization: String,
|
||||
schema: String,
|
||||
}
|
||||
|
||||
impl Default for StepHeaderDescriptor {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
file_name: Default::default(),
|
||||
time_stamp: chrono::Utc::now().naive_local().to_string(),
|
||||
authors: Default::default(),
|
||||
organization: Default::default(),
|
||||
organization_system: Default::default(),
|
||||
authorization: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for StepHeader {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||
let empty_string = [String::new()];
|
||||
f.write_fmt(format_args!(
|
||||
"HEADER;
|
||||
FILE_DESCRIPTION(('Shape Data from Truck'), '2;1');
|
||||
FILE_NAME('{}', '{}', ({}), ({}), 'truck', '{}', '{}');
|
||||
FILE_SCHEMA(('{}'));
|
||||
ENDSEC;\n",
|
||||
self.file_name,
|
||||
self.time_stamp,
|
||||
if self.authors.is_empty() {
|
||||
SliceDisplay(&empty_string)
|
||||
} else {
|
||||
SliceDisplay(&self.authors)
|
||||
},
|
||||
if self.organization.is_empty() {
|
||||
SliceDisplay(&empty_string)
|
||||
} else {
|
||||
SliceDisplay(&self.organization)
|
||||
},
|
||||
self.origination_system,
|
||||
self.authorization,
|
||||
self.schema,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Display model with configuations
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct StepModel<'a, P, C, S>(PreStepModel<'a, P, C, S>);
|
||||
|
||||
/// Display models with configuations
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct StepModels<'a, P, C, S> {
|
||||
models: Vec<PreStepModel<'a, P, C, S>>,
|
||||
next_idx: usize,
|
||||
}
|
||||
|
||||
/// Display struct for outputting STEP file format with header.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CompleteStepDisplay<T> {
|
||||
display: T,
|
||||
header: StepHeader,
|
||||
}
|
||||
|
||||
impl<T: Display> Display for CompleteStepDisplay<T> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||
f.write_fmt(format_args!(
|
||||
"ISO-10303-21;\n{}DATA;\n{}ENDSEC;\nEND-ISO-10303-21;\n",
|
||||
self.header, self.display,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> CompleteStepDisplay<T> {
|
||||
/// constructor
|
||||
#[inline]
|
||||
pub fn new(display: T, header: StepHeaderDescriptor) -> Self {
|
||||
CompleteStepDisplay {
|
||||
display,
|
||||
header: StepHeader {
|
||||
file_name: header.file_name,
|
||||
time_stamp: header.time_stamp,
|
||||
authors: header.authors,
|
||||
organization: header.organization,
|
||||
origination_system: header.organization_system,
|
||||
authorization: header.authorization,
|
||||
schema: "ISO-10303-042".to_string(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod geometry;
|
||||
mod topology;
|
||||
pub use geometry::VectorAsDirection;
|
||||
-493
@@ -1,493 +0,0 @@
|
||||
use super::{Result, *};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(super) struct StepShell<'a, P, C, S> {
|
||||
entity: &'a CompressedShell<P, C, S>,
|
||||
idx: usize,
|
||||
face_indices: Vec<usize>,
|
||||
ep_edges: usize,
|
||||
ep_vertices: usize,
|
||||
surface_indices: Vec<usize>,
|
||||
curve_indices: Vec<usize>,
|
||||
ep_points: usize,
|
||||
is_open: bool,
|
||||
}
|
||||
|
||||
impl<'a, P, C, S> StepShell<'a, P, C, S>
|
||||
where
|
||||
P: Copy,
|
||||
C: StepLength,
|
||||
S: StepLength,
|
||||
{
|
||||
fn new(shell: &'a CompressedShell<P, C, S>, idx: usize, is_open: bool) -> Self {
|
||||
let faces = &shell.faces;
|
||||
let edges = &shell.edges;
|
||||
let vertices = &shell.vertices;
|
||||
let mut cursor = idx + 1;
|
||||
let face_indices = faces
|
||||
.iter()
|
||||
.map(|f| {
|
||||
let res = cursor;
|
||||
cursor += match f.boundaries.is_empty() {
|
||||
true => 5,
|
||||
false => 1 + f.boundaries.iter().map(|b| 2 + b.len()).sum::<usize>(),
|
||||
};
|
||||
res
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let ep_edges = cursor;
|
||||
let ep_vertices = ep_edges + edges.len();
|
||||
cursor = ep_vertices + vertices.len();
|
||||
let surface_indices = faces
|
||||
.iter()
|
||||
.map(|f| {
|
||||
let res = cursor;
|
||||
cursor += f.surface.step_length();
|
||||
res
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let curve_indices = edges
|
||||
.iter()
|
||||
.map(|e| {
|
||||
let res = cursor;
|
||||
cursor += e.curve.step_length();
|
||||
res
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let ep_points = cursor;
|
||||
StepShell {
|
||||
entity: shell,
|
||||
idx,
|
||||
face_indices,
|
||||
ep_edges,
|
||||
ep_vertices,
|
||||
surface_indices,
|
||||
curve_indices,
|
||||
ep_points,
|
||||
is_open,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, P, C, S> Display for StepShell<'a, P, C, S>
|
||||
where
|
||||
P: DisplayByStep + Copy,
|
||||
C: DisplayByStep + StepCurve,
|
||||
S: DisplayByStep + StepSurface,
|
||||
{
|
||||
fn fmt(&self, formatter: &mut Formatter<'_>) -> Result {
|
||||
let StepShell {
|
||||
entity,
|
||||
idx,
|
||||
face_indices,
|
||||
ep_edges,
|
||||
ep_vertices,
|
||||
surface_indices,
|
||||
curve_indices,
|
||||
ep_points,
|
||||
is_open,
|
||||
} = self;
|
||||
let faces = &entity.faces;
|
||||
let edges = &entity.edges;
|
||||
let vertices = &entity.vertices;
|
||||
let shell_kind = match is_open {
|
||||
true => "OPEN_SHELL",
|
||||
false => "CLOSED_SHELL",
|
||||
};
|
||||
formatter.write_fmt(format_args!(
|
||||
"#{idx} = {shell_kind}('', {face_indices});\n",
|
||||
face_indices = IndexSliceDisplay(self.face_indices.clone()),
|
||||
))?;
|
||||
faces.iter().enumerate().try_for_each(|(i, f)| {
|
||||
let idx = face_indices[i];
|
||||
let mut cursor = idx + 1;
|
||||
let face_geometry = surface_indices[i];
|
||||
let face_bounds = match f.boundaries.is_empty() {
|
||||
true => vec![cursor],
|
||||
false => {
|
||||
let closure = |b: &Vec<CompressedEdgeIndex>| {
|
||||
let res = cursor;
|
||||
cursor += 2 + b.len();
|
||||
res
|
||||
};
|
||||
f.boundaries.iter().map(closure).collect()
|
||||
}
|
||||
};
|
||||
formatter.write_fmt(format_args!(
|
||||
"#{idx} = FACE_SURFACE('', {face_bound}, #{face_geometry}, {same_sense});\n",
|
||||
same_sense = BooleanDisplay(f.orientation == f.surface.same_sense()),
|
||||
face_bound = IndexSliceDisplay(face_bounds.clone()),
|
||||
))?;
|
||||
cursor = idx + 1;
|
||||
if f.boundaries.is_empty() {
|
||||
let face_bound_idx = cursor;
|
||||
let vertex_loop_idx = cursor + 1;
|
||||
let vertex_idx = cursor + 2;
|
||||
let vertex_geometry = cursor + 3;
|
||||
formatter.write_fmt(format_args!(
|
||||
"#{face_bound_idx} = FACE_BOUND('', #{vertex_loop_idx}, .T.);
|
||||
#{vertex_loop_idx} = VERTEX_LOOP('', #{vertex_idx});
|
||||
#{vertex_idx} = VERTEX_POINT('', #{vertex_geometry});
|
||||
#{vertex_geometry} = POINT_ON_SURFACE('', #{face_geometry}, 0.0, 0.0);\n"
|
||||
))?;
|
||||
}
|
||||
f.boundaries.iter().try_for_each(|b| {
|
||||
let face_bound_idx = cursor;
|
||||
let edge_loop_idx = cursor + 1;
|
||||
let ep_oriented_edges = cursor + 2;
|
||||
cursor += 2 + b.len();
|
||||
formatter.write_fmt(format_args!(
|
||||
"#{face_bound_idx} = FACE_BOUND('', #{edge_loop_idx}, {orientation});
|
||||
#{edge_loop_idx} = EDGE_LOOP('', {oriented_edge_indices});\n",
|
||||
orientation = BooleanDisplay(f.orientation),
|
||||
oriented_edge_indices =
|
||||
IndexSliceDisplay(ep_oriented_edges..ep_oriented_edges + b.len()),
|
||||
))?;
|
||||
b.iter().enumerate().try_for_each(|(j, ce)| {
|
||||
formatter.write_fmt(format_args!(
|
||||
"#{idx} = ORIENTED_EDGE('', *, *, #{edge_element}, {orientation});\n",
|
||||
idx = ep_oriented_edges + j,
|
||||
edge_element = ep_edges + ce.index,
|
||||
orientation = if ce.orientation { ".T." } else { ".F." },
|
||||
))
|
||||
})
|
||||
})
|
||||
})?;
|
||||
edges.iter().enumerate().try_for_each(|(i, e)| {
|
||||
let same_sense = if e.curve.same_sense() { ".T." } else { ".F." };
|
||||
formatter.write_fmt(format_args!(
|
||||
"#{idx} = EDGE_CURVE('', #{edge_start}, #{edge_end}, #{edge_geometry}, {same_sense});\n",
|
||||
idx = ep_edges + i,
|
||||
edge_start = ep_vertices + e.vertices.0,
|
||||
edge_end = ep_vertices + e.vertices.1,
|
||||
edge_geometry = curve_indices[i],
|
||||
))
|
||||
})?;
|
||||
(0..vertices.len()).try_for_each(|i| {
|
||||
formatter.write_fmt(format_args!(
|
||||
"#{idx} = VERTEX_POINT('', #{vertex_geometry});\n",
|
||||
idx = ep_vertices + i,
|
||||
vertex_geometry = ep_points + i,
|
||||
))
|
||||
})?;
|
||||
faces.iter().zip(surface_indices).try_for_each(|(f, idx)| {
|
||||
Display::fmt(&StepDisplay::new(&f.surface, *idx), formatter)
|
||||
})?;
|
||||
edges
|
||||
.iter()
|
||||
.zip(curve_indices)
|
||||
.try_for_each(|(e, idx)| Display::fmt(&StepDisplay::new(&e.curve, *idx), formatter))?;
|
||||
vertices
|
||||
.iter()
|
||||
.enumerate()
|
||||
.try_for_each(|(i, v)| Display::fmt(&StepDisplay::new(*v, ep_points + i), formatter))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, P, C, S> StepLength for StepShell<'a, P, C, S> {
|
||||
fn step_length(&self) -> usize {
|
||||
1 + self.ep_points + self.entity.vertices.len() - self.face_indices[0]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(super) struct StepSolid<'a, P, C, S> {
|
||||
idx: usize,
|
||||
boundaries: Vec<StepShell<'a, P, C, S>>,
|
||||
}
|
||||
|
||||
impl<'a, P, C, S> StepSolid<'a, P, C, S>
|
||||
where
|
||||
P: Copy,
|
||||
C: StepLength,
|
||||
S: StepLength,
|
||||
{
|
||||
fn new(solid: &'a CompressedSolid<P, C, S>, idx: usize) -> Self {
|
||||
let mut cursor = idx + 1;
|
||||
let boundaries = solid
|
||||
.boundaries
|
||||
.iter()
|
||||
.map(|shell| {
|
||||
let res = StepShell::new(shell, cursor, false);
|
||||
cursor += 1 + res.step_length();
|
||||
res
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
StepSolid { idx, boundaries }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, P, C, S> Display for StepSolid<'a, P, C, S>
|
||||
where
|
||||
P: DisplayByStep + Copy,
|
||||
C: DisplayByStep + StepLength + StepCurve,
|
||||
S: DisplayByStep + StepLength + StepSurface,
|
||||
{
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||
let StepSolid { idx, boundaries } = self;
|
||||
match boundaries.len() {
|
||||
0 => {
|
||||
f.pad("empty solid!")?;
|
||||
Err(std::fmt::Error)
|
||||
}
|
||||
1 => {
|
||||
let shell_idx = idx + 1;
|
||||
let step_shell = &boundaries[0];
|
||||
f.write_fmt(format_args!(
|
||||
"#{idx} = MANIFOLD_SOLID_BREP('', #{shell_idx});\n"
|
||||
))?;
|
||||
Display::fmt(step_shell, f)
|
||||
}
|
||||
_ => {
|
||||
let first_shell_idx = boundaries[0].face_indices[0] - 1;
|
||||
f.write_fmt(format_args!(
|
||||
"#{idx} = BREP_WITH_VOIDS('', #{first_shell_idx}, {other_shells});\n",
|
||||
other_shells = IndexSliceDisplay(
|
||||
boundaries[1..]
|
||||
.iter()
|
||||
.map(|step_shell| step_shell.face_indices[0] - 2)
|
||||
),
|
||||
))?;
|
||||
Display::fmt(&boundaries[0], f)?;
|
||||
boundaries[1..].iter().try_for_each(|step_shell| {
|
||||
let oriented_shell_idx = step_shell.face_indices[0] - 2;
|
||||
let shell_idx = step_shell.face_indices[0] - 1;
|
||||
f.write_fmt(format_args!(
|
||||
"#{oriented_shell_idx} = ORIENTED_CLOSED_SHELL('', *, #{shell_idx}, .T.);\n",
|
||||
))?;
|
||||
Display::fmt(step_shell, f)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, P, C, S> StepLength for StepSolid<'a, P, C, S> {
|
||||
fn step_length(&self) -> usize {
|
||||
let b = &self.boundaries;
|
||||
match b.len() {
|
||||
0 => 0,
|
||||
1 => 1 + b[0].step_length(),
|
||||
_ => b.len() + b.iter().map(StepLength::step_length).sum::<usize>(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(super) enum PreStepModel<'a, P, C, S> {
|
||||
/// shell based surface model
|
||||
Shell(StepShell<'a, P, C, S>),
|
||||
/// solid model
|
||||
Solid(StepSolid<'a, P, C, S>),
|
||||
}
|
||||
|
||||
impl<'a, P, C, S> From<&'a CompressedShell<P, C, S>> for PreStepModel<'a, P, C, S>
|
||||
where
|
||||
P: Copy,
|
||||
C: StepLength,
|
||||
S: StepLength,
|
||||
{
|
||||
fn from(shell: &'a CompressedShell<P, C, S>) -> Self {
|
||||
Self::Shell(StepShell::new(shell, 17, true))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, P, C, S> From<&'a CompressedSolid<P, C, S>> for PreStepModel<'a, P, C, S>
|
||||
where
|
||||
P: Copy,
|
||||
C: StepLength,
|
||||
S: StepLength,
|
||||
{
|
||||
fn from(solid: &'a CompressedSolid<P, C, S>) -> Self { Self::Solid(StepSolid::new(solid, 16)) }
|
||||
}
|
||||
|
||||
impl<'a, P, C, S> Display for PreStepModel<'a, P, C, S>
|
||||
where
|
||||
P: DisplayByStep + Copy,
|
||||
C: DisplayByStep + StepLength + StepCurve,
|
||||
S: DisplayByStep + StepLength + StepSurface,
|
||||
{
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||
match self {
|
||||
Self::Shell(x) => {
|
||||
f.write_fmt(format_args!(
|
||||
"#{idx} = SHELL_BASED_SURFACE_MODEL('', (#{shell_idx}));\n",
|
||||
idx = x.idx - 1,
|
||||
shell_idx = x.idx
|
||||
))?;
|
||||
Display::fmt(&x, f)
|
||||
}
|
||||
Self::Solid(x) => Display::fmt(x, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, P, C, S> StepLength for PreStepModel<'a, P, C, S> {
|
||||
fn step_length(&self) -> usize {
|
||||
match self {
|
||||
Self::Shell(x) => 1 + x.step_length(),
|
||||
Self::Solid(x) => x.step_length(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, P, C, S> From<&'a CompressedShell<P, C, S>> for StepModel<'a, P, C, S>
|
||||
where
|
||||
P: Copy,
|
||||
C: StepLength,
|
||||
S: StepLength,
|
||||
{
|
||||
fn from(shell: &'a CompressedShell<P, C, S>) -> Self { Self(shell.into()) }
|
||||
}
|
||||
|
||||
impl<'a, P, C, S> From<&'a CompressedSolid<P, C, S>> for StepModel<'a, P, C, S>
|
||||
where
|
||||
P: Copy,
|
||||
C: StepLength,
|
||||
S: StepLength,
|
||||
{
|
||||
fn from(solid: &'a CompressedSolid<P, C, S>) -> Self { Self(solid.into()) }
|
||||
}
|
||||
|
||||
impl<'a, P, C, S> Display for StepModel<'a, P, C, S>
|
||||
where
|
||||
P: DisplayByStep + Copy,
|
||||
C: DisplayByStep + StepLength + StepCurve,
|
||||
S: DisplayByStep + StepLength + StepSurface,
|
||||
{
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||
f.pad(
|
||||
"#1 = APPLICATION_PROTOCOL_DEFINITION('international standard', 'automotive_design', 2000, #2);
|
||||
#2 = APPLICATION_CONTEXT('core data for automotive mechanical design processes');
|
||||
#3 = SHAPE_DEFINITION_REPRESENTATION(#4, #10);
|
||||
#4 = PRODUCT_DEFINITION_SHAPE('','', #5);
|
||||
#5 = PRODUCT_DEFINITION('design','', #6, #9);
|
||||
#6 = PRODUCT_DEFINITION_FORMATION('','', #7);
|
||||
#7 = PRODUCT('','','', (#8));
|
||||
#8 = PRODUCT_CONTEXT('', #2, 'mechanical');
|
||||
#9 = PRODUCT_DEFINITION_CONTEXT('part definition', #2, 'design');
|
||||
#10 = ADVANCED_BREP_SHAPE_REPRESENTATION('', (#16), #11);
|
||||
#11 = (
|
||||
GEOMETRIC_REPRESENTATION_CONTEXT(3)
|
||||
GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#15))
|
||||
GLOBAL_UNIT_ASSIGNED_CONTEXT((#12, #13, #14))
|
||||
REPRESENTATION_CONTEXT('Context #1', '3D Context with UNIT and UNCERTAINTY')
|
||||
);
|
||||
#12 = ( LENGTH_UNIT() NAMED_UNIT(*) SI_UNIT(.MILLI.,.METRE.) );
|
||||
#13 = ( NAMED_UNIT(*) PLANE_ANGLE_UNIT() SI_UNIT($,.RADIAN.) );
|
||||
#14 = ( NAMED_UNIT(*) SI_UNIT($,.STERADIAN.) SOLID_ANGLE_UNIT() );
|
||||
#15 = UNCERTAINTY_MEASURE_WITH_UNIT(1.0E-6, #12, 'distance_accuracy_value','confusion accuracy');\n"
|
||||
)?;
|
||||
Display::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, P, C, S> Default for StepModels<'a, P, C, S> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
models: Vec::new(),
|
||||
next_idx: 16,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, P, C, S> StepModels<'a, P, C, S>
|
||||
where
|
||||
P: Copy,
|
||||
C: StepLength,
|
||||
S: StepLength,
|
||||
{
|
||||
/// push a shell to step models
|
||||
pub fn push_shell(&mut self, shell: &'a CompressedShell<P, C, S>) {
|
||||
let model = PreStepModel::Shell(StepShell::new(shell, self.next_idx + 1, true));
|
||||
self.next_idx += model.step_length();
|
||||
self.models.push(model)
|
||||
}
|
||||
/// push a solid to step models
|
||||
pub fn push_solid(&mut self, solid: &'a CompressedSolid<P, C, S>) {
|
||||
let model = PreStepModel::Solid(StepSolid::new(solid, self.next_idx));
|
||||
self.next_idx += model.step_length();
|
||||
self.models.push(model)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, P, C, S> FromIterator<&'a CompressedShell<P, C, S>> for StepModels<'a, P, C, S>
|
||||
where
|
||||
P: Copy,
|
||||
C: StepLength,
|
||||
S: StepLength,
|
||||
{
|
||||
fn from_iter<T: IntoIterator<Item = &'a CompressedShell<P, C, S>>>(iter: T) -> Self {
|
||||
let mut next_idx = 16;
|
||||
let models = iter
|
||||
.into_iter()
|
||||
.map(|shell| {
|
||||
let model = PreStepModel::Shell(StepShell::new(shell, next_idx + 1, true));
|
||||
next_idx += model.step_length();
|
||||
model
|
||||
})
|
||||
.collect();
|
||||
Self { models, next_idx }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, P, C, S> FromIterator<&'a CompressedSolid<P, C, S>> for StepModels<'a, P, C, S>
|
||||
where
|
||||
P: Copy,
|
||||
C: StepLength,
|
||||
S: StepLength,
|
||||
{
|
||||
fn from_iter<T: IntoIterator<Item = &'a CompressedSolid<P, C, S>>>(iter: T) -> Self {
|
||||
let mut next_idx = 16;
|
||||
let models = iter
|
||||
.into_iter()
|
||||
.map(|solid| {
|
||||
let model = PreStepModel::Solid(StepSolid::new(solid, next_idx));
|
||||
next_idx += model.step_length();
|
||||
model
|
||||
})
|
||||
.collect();
|
||||
Self { models, next_idx }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, P, C, S> Display for StepModels<'a, P, C, S>
|
||||
where
|
||||
P: DisplayByStep + Copy,
|
||||
C: DisplayByStep + StepLength + StepCurve,
|
||||
S: DisplayByStep + StepLength + StepSurface,
|
||||
{
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||
f.pad(
|
||||
"#1 = APPLICATION_PROTOCOL_DEFINITION('international standard', 'automotive_design', 2000, #2);
|
||||
#2 = APPLICATION_CONTEXT('core data for automotive mechanical design processes');
|
||||
#3 = SHAPE_DEFINITION_REPRESENTATION(#4, #10);
|
||||
#4 = PRODUCT_DEFINITION_SHAPE('','', #5);
|
||||
#5 = PRODUCT_DEFINITION('design','', #6, #9);
|
||||
#6 = PRODUCT_DEFINITION_FORMATION('','', #7);
|
||||
#7 = PRODUCT('','','', (#8));
|
||||
#8 = PRODUCT_CONTEXT('', #2, 'mechanical');
|
||||
#9 = PRODUCT_DEFINITION_CONTEXT('part definition', #2, 'design');\n")?;
|
||||
let models_slice = IndexSliceDisplay(self.models.iter().map(|model| match model {
|
||||
PreStepModel::Shell(x) => x.idx - 1,
|
||||
PreStepModel::Solid(x) => x.idx,
|
||||
}));
|
||||
f.write_fmt(format_args!(
|
||||
"#10 = ADVANCED_BREP_SHAPE_REPRESENTATION('', {models_slice}, #11);\n"
|
||||
))?;
|
||||
f.pad("#11 = (
|
||||
GEOMETRIC_REPRESENTATION_CONTEXT(3)
|
||||
GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#15))
|
||||
GLOBAL_UNIT_ASSIGNED_CONTEXT((#12, #13, #14))
|
||||
REPRESENTATION_CONTEXT('Context #1', '3D Context with UNIT and UNCERTAINTY')
|
||||
);
|
||||
#12 = ( LENGTH_UNIT() NAMED_UNIT(*) SI_UNIT(.MILLI.,.METRE.) );
|
||||
#13 = ( NAMED_UNIT(*) PLANE_ANGLE_UNIT() SI_UNIT($,.RADIAN.) );
|
||||
#14 = ( NAMED_UNIT(*) SI_UNIT($,.STERADIAN.) SOLID_ANGLE_UNIT() );
|
||||
#15 = UNCERTAINTY_MEASURE_WITH_UNIT(1.0E-6, #12, 'distance_accuracy_value','confusion accuracy');\n"
|
||||
)?;
|
||||
self.models
|
||||
.iter()
|
||||
.try_for_each(|model| Display::fmt(model, f))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user