MeshLib C++ Docs
Loading...
Searching...
No Matches
MRFeatures.h
Go to the documentation of this file.
1#pragma once
2
3#include "MRMesh/MRAffineXf.h"
4#include "MRMesh/MRCone3.h"
6#include "MRMesh/MRPlane3.h"
7#include "MRMesh/MRSphere.h"
8#include "MRMesh/MRVector3.h"
9
10#include <cassert>
11#include <optional>
12#include <variant>
13
14namespace MR
15{
16class FeatureObject;
17}
18
19namespace MR::Features
20{
21
22namespace Primitives
23{
24 struct Plane;
25 struct ConeSegment;
26
27 // ---
28
29 // Doubles as a point when the radius is zero.
30 using Sphere = Sphere3<float>;
31
32 struct Plane
33 {
34 Vector3f center;
35
36 // This must be normalized. The sign doesn't matter.
37 Vector3f normal = Vector3f( 1, 0, 0 );
38
39 // Returns an infinite line, with the center in a sane location.
40 [[nodiscard]] MRMESH_API ConeSegment intersectWithPlane( const Plane& other ) const;
41
42 // Intersects the plane with a line, returns a point (zero radius sphere).
43 // Only `center` and `dir` are used from `line` (so if `line` is a cone/cylinder, its axis is used,
44 // and the line is extended to infinity).
45 [[nodiscard]] MRMESH_API Sphere intersectWithLine( const ConeSegment& line ) const;
46
47 friend bool operator==( const Plane&, const Plane& ) = default;
48 };
49
53 struct ConeSegment
54 {
55 // Sanity requirements:
56 // * `dir` must be normalized.
57 // * Both `positiveLength` and `negativeLength` should be non-negative. They can be infinite (both or individually).
58 // * If they are equal (both zero) or at least one of them is infinite, `positiveSideRadius` must be equal to `negativeSideRadius`.
59 // * Both `positiveSideRadius` and `negativeSideRadius` must be non-negative.
60
65
67 float positiveSideRadius = 0;
69 float negativeSideRadius = 0;
70
72 float positiveLength = 0;
74 float negativeLength = 0;
75
76 // If true, the cone has no caps and no volume, and all distances (to the conical surface, that is) are positive.
77 bool hollow = false;
78
79 friend bool operator==( const ConeSegment&, const ConeSegment& ) = default;
80
81 [[nodiscard]] bool isZeroRadius() const { return positiveSideRadius == 0 && negativeSideRadius == 0; }
82 [[nodiscard]] bool isCircle() const { return positiveLength == -negativeLength && std::isfinite( positiveLength ); }
83
84 // Returns the length. Can be infinite.
85 [[nodiscard]] float length() const { return positiveLength + negativeLength; }
86
87 // Returns the center point (unlike `referencePoint`, which can actually be off-center).
88 // For half-infinite objects, returns the finite end.
89 [[nodiscard]] MRMESH_API Sphere centerPoint() const;
90
91 // Extends the object to infinity in one direction. The radius in the extended direction becomes equal to the radius in the opposite direction.
92 [[nodiscard]] MRMESH_API ConeSegment extendToInfinity( bool negative ) const;
93 // Extends the object to infinity in both directions. This is equivalent to `.extendToInfinity(false).extendToInfinity(true)`,
94 // except that calling it with `positiveSideRadius != negativeSideRadius` is illegal and triggers an assertion.
95 [[nodiscard]] MRMESH_API ConeSegment extendToInfinity() const;
96
97 // Untruncates a truncated cone. If it's not a cone at all, returns the object unchanged and triggers an assertion.
98 [[nodiscard]] MRMESH_API ConeSegment untruncateCone() const;
99
100 // Returns a finite axis. For circles, you might want to immediately `extendToInfinity()` it.
101 [[nodiscard]] MRMESH_API ConeSegment axis() const;
102
103 // Returns a center of one of the two base circles.
104 [[nodiscard]] MRMESH_API Sphere basePoint( bool negative ) const;
105 // Returns one of the two base planes.
106 [[nodiscard]] MRMESH_API Plane basePlane( bool negative ) const;
107 // Returns one of the two base circles.
108 [[nodiscard]] MRMESH_API ConeSegment baseCircle( bool negative ) const;
109 };
110
111 using Variant = std::variant<Sphere, ConeSegment, Plane>;
112}
113
114// Those map various MR types to our primitives. Some of those are identity functions.
115
116[[nodiscard]] inline Primitives::Sphere toPrimitive( const Vector3f& point ) { return { point, 0 }; }
117[[nodiscard]] inline Primitives::Sphere toPrimitive( const Sphere3f& sphere ) { return sphere; }
118
121
122[[nodiscard]] inline Primitives::ConeSegment toPrimitive( const Cylinder3f& cyl )
123{
124 float halfLen = cyl.length / 2;
125 return{
126 .referencePoint = cyl.center(),
127 .dir = cyl.direction().normalized(),
128 .positiveSideRadius = cyl.radius, .negativeSideRadius = cyl.radius,
129 .positiveLength = halfLen, .negativeLength = halfLen,
130 };
131}
132[[nodiscard]] inline Primitives::ConeSegment toPrimitive( const Cone3f& cone )
133{
134 return{
135 .referencePoint = cone.center(),
136 .dir = cone.direction().normalized(),
137 .positiveSideRadius = std::tan( cone.angle ) * cone.height, .negativeSideRadius = 0,
138 .positiveLength = cone.height, .negativeLength = 0,
139 };
140}
141
143[[nodiscard]] MRMESH_API Primitives::ConeSegment primitiveCircle( const Vector3f& point, const Vector3f& normal, float rad );
145[[nodiscard]] MRMESH_API Primitives::ConeSegment primitiveCylinder( const Vector3f& a, const Vector3f& b, float rad );
147[[nodiscard]] MRMESH_API Primitives::ConeSegment primitiveCone( const Vector3f& a, const Vector3f& b, float rad );
148
149// Returns null if the object type is unknown. This overload ignores the parent xf.
150[[nodiscard]] MRMESH_API std::optional<Primitives::Variant> primitiveFromObject( const Object& object );
151// Returns null if the object type is unknown. This overload respects the parent's `worldXf()`.
152[[nodiscard]] MRMESH_API std::optional<Primitives::Variant> primitiveFromObjectWithWorldXf( const Object& object );
153// Can return null on some primitive configurations.
154// `infiniteExtent` is how large we make "infinite" objects. Half-infinite objects divide this by 2.
155[[nodiscard]] MRMESH_API std::shared_ptr<FeatureObject> primitiveToObject( const Primitives::Variant& primitive, float infiniteExtent );
156
157// Transform a primitive by an xf.
158// Non-uniform scaling and skewing are not supported.
159[[nodiscard]] MRMESH_API Primitives::Sphere transformPrimitive( const AffineXf3f& xf, const Primitives::Sphere& primitive );
162[[nodiscard]] MRMESH_API Primitives::Variant transformPrimitive( const AffineXf3f& xf, const Primitives::Variant& primitive );
163
165struct MeasureResult
166{
167 enum class Status
168 {
169 ok = 0,
171 notImplemented,
174 badFeaturePair,
176 badRelativeLocation,
178 notFinite,
179 };
180
182 {
183 Status status = Status::notImplemented;
184 [[nodiscard]] operator bool() const { return status == Status::ok; }
185 };
186
188 {
189 // This is a separate field because it can be negative.
190 float distance = 0;
191
194
195 [[nodiscard]] Vector3f closestPointFor( bool b ) const { return b ? closestPointB : closestPointA; }
196
197 [[nodiscard]] float distanceAlongAxis( int i ) const { return distanceAlongAxisAbs( i ) * ( distance < 0 ? -1 : 1 ); }
198 [[nodiscard]] float distanceAlongAxisAbs( int i ) const { return std::abs( closestPointA[i] - closestPointB[i] ); }
199 };
200 // Exact distance.
201 Distance distance;
202
203 // Some approximation of the distance.
204 // For planes and lines, this expects them to be mostly parallel. For everything else, it just takes the feature center.
205 Distance centerDistance;
206
208 {
211 [[nodiscard]] Vector3f pointFor( bool b ) const { return b ? pointB : pointA; }
212
213 Vector3f dirA; // Normalized.
215 [[nodiscard]] Vector3f dirFor( bool b ) const { return b ? dirB : dirA; }
216
218 bool isSurfaceNormalA = false;
219 bool isSurfaceNormalB = false;
220
221 [[nodiscard]] bool isSurfaceNormalFor( bool b ) const { return b ? isSurfaceNormalB : isSurfaceNormalA; }
222
223 [[nodiscard]] MRMESH_API float computeAngleInRadians() const;
224 };
225 Angle angle;
226
227 // The primitives obtained from intersecting those two.
228 std::vector<Primitives::Variant> intersections;
229
230 // Modifies the object to swap A and B;
231 MRMESH_API void swapObjects();
232};
233// `MeasureResult::Status` enum to string.
234[[nodiscard]] MRMESH_API std::string_view toString( MeasureResult::Status status );
235
237namespace Traits
238{
239
240template <typename T>
241struct Unary {};
242template <>
244{
245 MRMESH_API std::string name( const Primitives::Sphere& prim ) const;
246};
247template <>
248struct Unary<Primitives::ConeSegment>
249{
250 MRMESH_API std::string name( const Primitives::ConeSegment& prim ) const;
251};
252template <>
253struct Unary<Primitives::Plane>
254{
255 MRMESH_API std::string name( const Primitives::Plane& prim ) const;
256};
257
258template <typename A, typename B>
259struct Binary {};
260
263template <typename A, typename B>
264concept MeasureSupportedOneWay = requires( const Binary<A, B>& t, const A& a, const B& b )
265{
266 { t.measure( a, b ) } -> std::same_as<MeasureResult>;
267};
268
269// ?? <-> Sphere
270template <>
271struct Binary<Primitives::Sphere, Primitives::Sphere>
272{
273 MRMESH_API MeasureResult measure( const Primitives::Sphere& a, const Primitives::Sphere& b ) const;
274};
275template <>
276struct Binary<Primitives::ConeSegment, Primitives::Sphere>
277{
278 MRMESH_API MeasureResult measure( const Primitives::ConeSegment& a, const Primitives::Sphere& b ) const;
279};
280template <>
281struct Binary<Primitives::Plane, Primitives::Sphere>
282{
283 MRMESH_API MeasureResult measure( const Primitives::Plane& a, const Primitives::Sphere& b ) const;
284};
285
286// ?? <-> Cone
287template <>
292template <>
297
298// ?? <-> Plane
299template <>
304
305}
306
307// Get name of a `Primitives::...` class (can depend on its parameters).
308template <typename T>
309[[nodiscard]] std::string name( const T& primitive )
310{
311 return Traits::Unary<T>{}.name( primitive );
312}
313// Same but for a variant.
314[[nodiscard]] MRMESH_API std::string name( const Primitives::Variant& var );
315
316// Whether you can measure two primitives relative to one another.
317template <typename A, typename B>
319
320// Measures stuff between two primitives. (Two types from `Primitives::...`.)
321template <typename A, typename B>
323[[nodiscard]] MeasureResult measure( const A& a, const B& b )
324{
326 {
327 MeasureResult ret = Traits::Binary<A, B>{}.measure( a, b );
328
329 for ( auto* dist : { &ret.distance, &ret.centerDistance } )
330 {
331 // Catch non-finite distance.
332 if ( *dist && ( !std::isfinite( dist->distance ) || !dist->closestPointA.isFinite() || !dist->closestPointB.isFinite() ) )
333 dist->status = MeasureResult::Status::badRelativeLocation;
334
335 // Check that we got the correct distance here.
336 // Note that the distance is signed, so we apply `abs` to it to compare it properly.
337 if ( *dist )
338 {
339 assert( [&]{
340 float a = ( dist->closestPointB - dist->closestPointA ).length();
341 float b = std::abs( dist->distance );
342 return std::abs( a - b ) <= std::max( std::min( a, b ), 0.01f ) * 0.001f;
343 }() );
344 }
345 }
346
347 // Catch non-finite angle.
348 if ( ret.angle && ( !ret.angle.pointA.isFinite() || !ret.angle.pointB.isFinite() || !ret.angle.dirA.isFinite() || !ret.angle.dirB.isFinite() ) )
349 ret.angle.status = MeasureResult::Status::badRelativeLocation;
350
351 // Check that the angle normals are normalized.
352 assert( ret.angle <= ( std::abs( 1 - ret.angle.dirA.length() ) < 0.0001f ) );
353 assert( ret.angle <= ( std::abs( 1 - ret.angle.dirB.length() ) < 0.0001f ) );
354
355 return ret;
356 }
357 else
358 {
359 static_assert( Traits::MeasureSupportedOneWay<B, A>, "This should never fail." );
360 MeasureResult ret = ( measure )( b, a );
361 ret.swapObjects();
362 return ret;
363 }
364}
365// Same, but with a variant as the first argument.
366template <typename B>
367[[nodiscard]] MeasureResult measure( const Primitives::Variant& a, const B& b )
368{
369 return std::visit( [&]( const auto& elem ){ return (measure)( elem, b ); }, a );
370}
371// Same, but with a variant as the second argument.
372template <typename A>
373[[nodiscard]] MeasureResult measure( const A& a, const Primitives::Variant& b )
374{
375 return std::visit( [&]( const auto& elem ){ return (measure)( a, elem ); }, b );
376}
377// Same, but with variants as both argument.
378[[nodiscard]] MRMESH_API MeasureResult measure( const Primitives::Variant& a, const Primitives::Variant& b );
379
380}
#define MRMESH_API
Definition MRMeshFwd.h:80
length
Definition MRObjectDimensionsEnum.h:14
unsafe new ref MR.Vector3f direction()
unsafe new ref MR.Vector3f center()
unsafe new ref MR.Vector3f direction()
unsafe new ref MR.Vector3f center()
Definition MRFeatures.h:166
Definition MRFeatures.h:54
new unsafe ref float positiveSideRadius
new unsafe ref MR.Vector3f dir
new unsafe ref MR.Vector3f referencePoint
new unsafe ref float negativeSideRadius
Definition MRFeatures.h:33
Definition MRObject.h:62
Definition MRFeatures.h:318
Definition MRFeatures.h:20
std::string name(const T &primitive)
Definition MRFeatures.h:309
MRMESH_API Primitives::ConeSegment primitiveCylinder(const Vector3f &a, const Vector3f &b, float rad)
a and b are centers of the sides.
MeasureResult measure(const A &a, const B &b)
Definition MRFeatures.h:323
MRMESH_API std::shared_ptr< FeatureObject > primitiveToObject(const Primitives::Variant &primitive, float infiniteExtent)
MRMESH_API Primitives::ConeSegment primitiveCircle(const Vector3f &point, const Vector3f &normal, float rad)
normal doesn't need to be normalized.
MRMESH_API std::string_view toString(MeasureResult::Status status)
MRMESH_API Primitives::Sphere transformPrimitive(const AffineXf3f &xf, const Primitives::Sphere &primitive)
MRMESH_API Primitives::ConeSegment primitiveCone(const Vector3f &a, const Vector3f &b, float rad)
a is the center of the base, b is the pointy end.
MRMESH_API std::optional< Primitives::Variant > primitiveFromObjectWithWorldXf(const Object &object)
Primitives::Sphere toPrimitive(const Vector3f &point)
Definition MRFeatures.h:116
MRMESH_API std::optional< Primitives::Variant > primitiveFromObject(const Object &object)
Definition MRCameraOrientationPlugin.h:8
Vector3f normal(const MeshTopology &topology, const VertCoords &points, FaceId f)
computes triangular face normal from its vertices
Definition MRMeshMath.h:221
Definition MRFeatures.h:208
Vector3f pointA
Definition MRFeatures.h:209
Vector3f dirFor(bool b) const
Definition MRFeatures.h:215
MRMESH_API float computeAngleInRadians() const
Vector3f pointB
Definition MRFeatures.h:210
Vector3f dirA
Definition MRFeatures.h:213
bool isSurfaceNormalA
Whether dir{A,B} is a surface normal or a line direction.
Definition MRFeatures.h:218
Vector3f pointFor(bool b) const
Definition MRFeatures.h:211
bool isSurfaceNormalFor(bool b) const
Definition MRFeatures.h:221
Vector3f dirB
Definition MRFeatures.h:214
bool isSurfaceNormalB
Definition MRFeatures.h:219
Definition MRFeatures.h:182
Status status
Definition MRFeatures.h:183
Definition MRFeatures.h:188
float distanceAlongAxis(int i) const
Definition MRFeatures.h:197
Vector3f closestPointA
Definition MRFeatures.h:192
Vector3f closestPointFor(bool b) const
Definition MRFeatures.h:195
float distanceAlongAxisAbs(int i) const
Definition MRFeatures.h:198
Vector3f closestPointB
Definition MRFeatures.h:193
float distance
Definition MRFeatures.h:190
MRMESH_API MeasureResult measure(const Primitives::ConeSegment &a, const Primitives::ConeSegment &b) const
MRMESH_API MeasureResult measure(const Primitives::ConeSegment &a, const Primitives::Sphere &b) const
MRMESH_API MeasureResult measure(const Primitives::Plane &a, const Primitives::ConeSegment &b) const
MRMESH_API MeasureResult measure(const Primitives::Plane &a, const Primitives::Plane &b) const
MRMESH_API MeasureResult measure(const Primitives::Plane &a, const Primitives::Sphere &b) const
MRMESH_API MeasureResult measure(const Primitives::Sphere &a, const Primitives::Sphere &b) const
Definition MRFeatures.h:259
MRMESH_API std::string name(const Primitives::ConeSegment &prim) const
MRMESH_API std::string name(const Primitives::Plane &prim) const
MRMESH_API std::string name(const Primitives::Sphere &prim) const
Definition MRFeatures.h:241
Definition MRSphere.h:11
readonly unsafe float length()
readonly unsafe bool isFinite()