FEAT 3
Finite Element Analysis Toolbox
Loading...
Searching...
No Matches
bezier.hpp
1// FEAT3: Finite Element Analysis Toolbox, Version 3
2// Copyright (C) 2010 by Stefan Turek & the FEAT group
3// FEAT3 is released under the GNU General Public License version 3,
4// see the file 'copyright.txt' in the top level directory for details.
5
6#pragma once
7
8#include <kernel/geometry/atlas/chart.hpp>
9
10namespace FEAT
11{
12 namespace Geometry
13 {
14 namespace Atlas
15 {
18 {
20 static constexpr bool is_explicit = true;
22 static constexpr bool is_implicit = true;
24 static constexpr int world_dim = 2;
26 static constexpr int param_dim = 1;
27 };
28
42 template<typename Mesh_>
43 class Bezier :
44 public ChartCRTP<Bezier<Mesh_>, Mesh_, BezierTraits>
45 {
46 public:
55
57 static constexpr int max_degree = 5;
58
59 protected:
61 std::deque<std::size_t> _vtx_ptr;
63 std::deque<WorldPoint> _world;
65 std::deque<ParamPoint> _param;
67 bool _closed;
70
71 public:
73 explicit Bezier(bool closed = false, DataType orientation = DataType(1)) :
74 BaseClass(),
75 _closed(closed),
76 _orientation(orientation)
77 {
78 }
79
98 explicit Bezier(
99 const std::deque<std::size_t>& vtx_ptr,
100 const std::deque<WorldPoint>& world,
101 const std::deque<ParamPoint>& param,
102 bool closed,
103 DataType orientation = DataType(1)) :
104 BaseClass(),
105 _vtx_ptr(vtx_ptr),
106 _world(world),
107 _param(param),
108 _closed(closed),
109 _orientation(orientation)
110 {
111 // we need at least 2 points
112 XASSERTM(_vtx_ptr.size() > std::size_t(1), "Bezier needs at least 2 points");
113 XASSERTM(_param.empty() || (_param.size() == _vtx_ptr.size()), "Bezier world/param size mismatch");
114
115 // check last vertex pointer
116 XASSERTM(_vtx_ptr.front() == std::size_t(0), "invalid vertex pointer array");
117 XASSERTM(_vtx_ptr.back() == _world.size(), "invalid vertex pointer array");
118
119 // validate inner vertex pointers
120 for(std::size_t i(0); (i+1) < _vtx_ptr.size(); ++i)
121 {
122 XASSERTM(_vtx_ptr[i] < _vtx_ptr[i+1], "invalid vertex pointer array");
123 XASSERTM(int(_vtx_ptr[i+1]-_vtx_ptr[i]) <= max_degree, "invalid spline degree");
124 }
125
127 }
128
129 // use default copy ctor; this one is required by the MeshExtruder !
130 Bezier(const Bezier&) = default;
131
133 virtual String get_type() const override
134 {
135 return "bezier";
136 }
137
144 void push_vertex(const WorldPoint& point)
145 {
146 // update vertex pointer
147 _vtx_ptr.push_back(_world.size());
148 _world.push_back(point);
149 }
150
157 void push_control(const WorldPoint& point)
158 {
159 _world.push_back(point);
160 }
161
168 void push_param(const ParamPoint& param)
169 {
170 this->_param.push_back(param);
171 }
172
177 {
178 this->_closed = true;
179 }
180
186 bool is_closed() const
187 {
188 return this->_closed;
189 }
190
196 int get_orientation() const
197 {
198 return this->_orientation;
199 }
200
207 void set_orientation(int ori)
208 {
209 XASSERTM(Math::abs(ori) == 1, "invalid orientation; must be +1 or -1");
210 this->_orientation = ori;
211 }
212
219 virtual bool can_explicit() const override
220 {
221 return !_param.empty();
222 }
223
229 std::deque<WorldPoint>& get_world_points()
230 {
231 return _world;
232 }
233
239 const std::deque<WorldPoint>& get_world_points() const
240 {
241 return _world;
242 }
243
249 std::deque<std::size_t>& get_vertex_pointers()
250 {
251 return _vtx_ptr;
252 }
253
259 const std::deque<std::size_t>& get_vertex_pointers() const
260 {
261 return _vtx_ptr;
262 }
263
265 virtual void transform(const WorldPoint& origin, const WorldPoint& angles, const WorldPoint& offset) override
266 {
267 if constexpr(Mesh_::shape_dim == 2)
268 {
269 // create rotation matrix
271 rot.set_rotation_2d(angles(0));
272
273 // transform all world points
274 WorldPoint tmp;
275 for(auto& pt : _world)
276 {
277 tmp = pt - origin;
278 pt.set_mat_vec_mult(rot, tmp) += offset;
279 }
280 }
281 else
282 {
283 XABORTM("Trying to call Bezier Chart routines for a 3d mesh");
284 }
285 }
286
296 WorldPoint map_on_segment(const Index i, const DataType t) const
297 {
298 ASSERTM((i+1) < Index(this->_vtx_ptr.size()), "invalid segment index");
299
300 const DataType t1 = DataType(1) - t;
301
302 // get number of points on segment
303 const std::size_t n = _vtx_ptr[i+1] - _vtx_ptr[i];
304
305 // check for simple line segment (1st degree spline)
306 if(n == std::size_t(1))
307 {
308 // perform linear interpolation
309 std::size_t k = _vtx_ptr[i];
310 return (t1 * _world[k]) + (t * _world[k+1]);
311 }
312
313 // temporary work array
315
316 // fetch all points on current segment
317 for(std::size_t k(0); k <= n; ++k)
318 {
319 v[k] = _world[_vtx_ptr[i] + k];
320 }
321
322 // apply recursive Bezier interpolation scheme
323 for(std::size_t j(0); j < n; ++j)
324 {
325 for(std::size_t k(0); (k+j) < n; ++k)
326 {
327 // v_new[k] <- (1-t)*v[k] + t*v[k+1] <==>
328 (v[k] *= t1) += (t * v[k+1]);
329 }
330 }
331
332 // the interpolated result is stored in v[0]
333 return v[0];
334 }
335
348 {
349 // compute first order derivative
350 ASSERTM((i+1) < Index(this->_vtx_ptr.size()), "invalid segment index");
351
352 // our normal vector
353 WorldPoint nu;
354
355 // get number of points on segment
356 const std::size_t n = _vtx_ptr[i+1] - _vtx_ptr[i];
357
358 // check for simple line segment (1st degree spline)
359 if(n == std::size_t(1))
360 {
361 std::size_t k = _vtx_ptr[i];
362 nu[0] = _world[k+1][1] - _world[k][1];
363 nu[1] = _world[k][0] - _world[k+1][0];
364 return nu;
365 }
366
367 // we have a spline of degree > 1
368
369 WorldPoint vals[max_degree+1];
370 WorldPoint der1[max_degree+1];
371
372 // fetch all points on current segment
373 for(std::size_t k(0); k <= n; ++k)
374 {
375 vals[k] = _world[_vtx_ptr[i] + k];
376 der1[k] = DataType(0);
377 }
378
379 const DataType t1 = DataType(1) - t;
380
381 // apply recursive Bezier interpolation scheme
382 for(std::size_t j(0); j < n; ++j)
383 {
384 for(std::size_t k(0); (k+j) < n; ++k)
385 {
386 // update 1st order derivative
387 // v_new'[k] <- (1-t)*v'[k] + t*v'[k+1] - v[k] + v[k+1]
388 (((der1[k] *= t1) += (t * der1[k+1])) -= vals[k]) += vals[k+1];
389
390 // update function value
391 // v_new[k] <- (1-t)*v[k] + t*v[k+1]
392 (vals[k] *= t1) += (t * vals[k+1]);
393 }
394 }
395
396 // rotate to obtain normal
397 nu[0] = _orientation*der1[0][1];
398 nu[1] = -_orientation*der1[0][0];
399 return nu;
400 }
401
417 DataType project_on_segment(const Index i, const WorldPoint& point) const
418 {
419 ASSERTM((i+1) < Index(this->_vtx_ptr.size()), "invalid segment index");
420
421 // get number of points on segment
422 const std::size_t n = _vtx_ptr[i+1] - _vtx_ptr[i];
423
424 // check for simple line segment (1st degree spline)
425 if(n == std::size_t(1))
426 {
427 // get the ends of the line segment
428 const std::size_t k = _vtx_ptr[i];
429 const WorldPoint& x0 = this->_world.at(k);
430 const WorldPoint& x1 = this->_world.at(k+1);
431
432 // compute xe := x1-x0 and xp := p-x0
433 WorldPoint xe = x1 - x0;
434 WorldPoint xp = point - x0;
435
436 // compute t = <xp,xe>/<xe,xe> and clamp it to [0,1]
437 return Math::clamp(Tiny::dot(xp,xe) / Tiny::dot(xe,xe), DataType(0), DataType(1));
438 }
439
440 // apply Selimovic elimination test
441 {
442 // get curve start- and end-points
443 const std::size_t ks = _vtx_ptr[i];
444 const std::size_t ke = _vtx_ptr[i+1];
445 const WorldPoint& xs = this->_world.at(ks);
446 const WorldPoint& xe = this->_world.at(ke);
447 const WorldPoint ps = xs - point;
448 const WorldPoint pe = xe - point;
449
450 // which is closer to our query point: the start or the end-point?
451 if(ps.norm_euclid_sqr() < pe.norm_euclid_sqr())
452 {
453 // test whether the start-point is the projection
454 bool prj_xs = true;
455 for(std::size_t k(ks + 1); k < ke; ++k)
456 {
457 prj_xs = prj_xs && (Tiny::dot(this->_world.at(k) - xs, ps) > DataType(0));
458 }
459
460 if(prj_xs)
461 return DataType(0);
462 }
463 else
464 {
465 // test whether the end-point is the projection
466 bool prj_xe = true;
467 for(std::size_t k(ks + 1); k < ke; ++k)
468 {
469 prj_xe = prj_xe && (Tiny::dot(this->_world.at(k) - xe, pe) > DataType(0));
470 }
471 if(prj_xe)
472 return DataType(1);
473 }
474 }
475
476 //
477 // Algorithm description
478 // ---------------------
479 // This function is meant to find the parameter value 't' of the point
480 // B(t) closest to the given input point 'X' (named 'point'), i.e.
481 //
482 // argmin { (B(t) - X)^2 }
483 // {0 <= t <= 1}
484 //
485 // As Bezier curves a polynomials (and therefore smooth), the parameter
486 // 't' we are looking for fulfills the orthogonal projection property
487 //
488 // f(t) := < B'(t), B(t) - X > = 0
489 //
490 // The algorithm implemented below is a simple Newton iteration applied
491 // onto the non-linear equation above.
492 // For Newton, we require the derivative f'(t) of f(t), which is
493 //
494 // f'(t) = < B"(t), B(t) - X > + < B'(t), B'(t) >
495 //
496 // and the Newton iteration is defined as
497 //
498 // t_{k+1} := t_k - f(t_k) / f'(t_k)
499 //
500 // One problem is to find an appropriate initial guess t_0 for our
501 // algorithm. In many cases, the initial guess t_0 = 1/2 will do the
502 // job. However, if the Bezier curve is S-shaped, this algorithm may
503 // fail, therefore we have to choose another initial guess.
504 // To circumvent this problem, we explicitly test the distance
505 // (B(t) - X)^2 for t in {1/5, 1/2, 4/5} and choose the t with
506 // the minimal distance as an initial guess.
507 //
508 // Unfortunately, there is no guarantee that this algorithm will
509 // converge.
510
511 // temporary work arrays
512 WorldPoint vals[max_degree+1]; // B (t)
513 WorldPoint der1[max_degree+1]; // B'(t)
514 WorldPoint der2[max_degree+1]; // B"(t)
515
516 // choose an appropriate initial parameter value
517 DataType t = DataType(0.5);
518 if(n <= std::size_t(3))
519 {
520 // choose t in {0.2, 0.5, 0.8}
521 DataType d1 = (point - map_on_segment(i, DataType(0.2))).norm_euclid_sqr();
522 DataType d2 = (point - map_on_segment(i, DataType(0.5))).norm_euclid_sqr();
523 DataType d3 = (point - map_on_segment(i, DataType(0.8))).norm_euclid_sqr();
524 if(d1 < Math::min(d2,d3)) t = DataType(0.2);
525 if(d2 < Math::min(d3,d1)) t = DataType(0.5);
526 if(d3 < Math::min(d1,d2)) t = DataType(0.8);
527 }
528 else // n > 3
529 {
530 // choose t in {0.0, 0.25, 0.5, 0.75, 1.0}
531 DataType d1 = (point - map_on_segment(i, DataType(0.00))).norm_euclid_sqr();
532 DataType d2 = (point - map_on_segment(i, DataType(0.25))).norm_euclid_sqr();
533 DataType d3 = (point - map_on_segment(i, DataType(0.50))).norm_euclid_sqr();
534 DataType d4 = (point - map_on_segment(i, DataType(0.75))).norm_euclid_sqr();
535 DataType d5 = (point - map_on_segment(i, DataType(1.00))).norm_euclid_sqr();
536 if(d1 < Math::min(Math::min(d2,d3), Math::min(d4,d5))) t = DataType(0.00);
537 if(d2 < Math::min(Math::min(d3,d4), Math::min(d5,d1))) t = DataType(0.25);
538 if(d3 < Math::min(Math::min(d4,d5), Math::min(d1,d2))) t = DataType(0.50);
539 if(d4 < Math::min(Math::min(d5,d1), Math::min(d2,d3))) t = DataType(0.75);
540 if(d5 < Math::min(Math::min(d1,d2), Math::min(d3,d4))) t = DataType(1.00);
541 }
542
543 // Newton-Iteration
544 for(int iter(0); iter < 10; ++iter)
545 {
546 // pre-compute (1-t)
547 const DataType t1 = DataType(1) - t;
548
549 // fetch all points on current segment
550 for(std::size_t k(0); k <= n; ++k)
551 {
552 vals[k] = _world[_vtx_ptr[i] + k];
553 der1[k] = der2[k] = DataType(0);
554 }
555
556 // apply recursive Bezier interpolation
557 for(std::size_t j(0); j < n; ++j)
558 {
559 for(std::size_t k(0); (k+j) < n; ++k)
560 {
561 // update 2nd order derivative
562 // v_new"[k] <- (1-t)*v"[k] + t*v"[k+1] - 2*v'[k] + 2*v'[k+1]
563 (((der2[k] *= t1) += (t * der2[k+1])) -= DataType(2)*der1[k]) += DataType(2)*der1[k+1];
564
565 // update 1st order derivative
566 // v_new'[k] <- (1-t)*v'[k] + t*v'[k+1] - v[k] + v[k+1]
567 (((der1[k] *= t1) += (t * der1[k+1])) -= vals[k]) += vals[k+1];
568
569 // update function value
570 // v_new[k] <- (1-t)*v[k] + t*v[k+1]
571 (vals[k] *= t1) += (t * vals[k+1]);
572 }
573 }
574
575 // map local point and subtract world point: B(t) - X
576 DataType vpx = vals[0][0] - point[0];
577 DataType vpy = vals[0][1] - point[1];
578
579 // compute first derivatives: B'(t)
580 DataType d1x = der1[0][0];
581 DataType d1y = der1[0][1];
582
583 // compute second derivatives: B"(t)
584 DataType d2x = der2[0][0];
585 DataType d2y = der2[0][1];
586
587 // compute function value: f(t) := < B'(t), B(t) - X >
588 DataType fv = (d1x*vpx + d1y*vpy);
589 if(Math::abs(fv) < DataType(1E-8))
590 return t; // finished!
591
592 // compute function derivative: f'(t) := < B"(t), B(t) - X > + < B'(t), B'(t) >
593 DataType fd = (d2x*vpx + d2y*vpy + d1x*d1x + d1y*d1y);
594 if(Math::abs(fd) < DataType(1E-8))
595 {
596 // This should not happen...
597 break;
598 }
599
600 // compute t-update:
601 DataType tu = -fv / fd;
602
603 // ensure that we do not try to run beyond our segment;
604 // in this case the projected point is one of our segment ends
605 if((t <= DataType(0)) && (tu <= DataType(0))) break;
606 if((t >= DataType(1)) && (tu >= DataType(0))) break;
607
608 // compute new t and clamp to [0,1]
609 t = Math::clamp(t + tu, DataType(0), DataType(1));
610 }
611
612 // Maximum number of iterations reached...
613 return t;
614 }
615
625 void map_param(WorldPoint& point, const ParamPoint& param) const
626 {
627 XASSERTM(!this->_param.empty(), "Bezier has no parameters");
628
629 // find enclosing segment
630 const Index i = Index(this->find_param(param[0]));
631
632 // compute local segment parameter
633 const DataType t = (param[0] - this->_param[i][0]) / (this->_param[i+1][0] - this->_param[i][0]);
634
635 // map on segment
636 point = map_on_segment(i, t);
637 }
638
645 void project_point(WorldPoint& point) const
646 {
647 // create a const copy of our input point
648 const WorldPoint inpoint(point);
649
650 // find the vertex that is closest to our input point and remember the distance
651 point = this->_world.front();
652 DataType min_dist = (inpoint - point).norm_euclid_sqr();
653 for(std::size_t i(1); i < this->_vtx_ptr.size(); ++i)
654 {
655 DataType distance = (inpoint - this->_world.at(this->_vtx_ptr[i])).norm_euclid_sqr();
656 if(distance < min_dist)
657 {
658 point = this->_world.at(this->_vtx_ptr[i]);
659 min_dist = distance;
660 }
661 }
662
663 // compute square root to obtain real distance (and not squared one)
664 min_dist = Math::sqrt(min_dist);
665
666 // loop over all curve segments
667 for(Index i(0); (i+1) < Index(this->_vtx_ptr.size()); ++i)
668 {
669 // we now check the bounding box of the bezier curve segment against a
670 // "test box" of the current minimum distance around our input point
671
672 // can we skip this curve segment?
673 bool gt_x0 = false; // not left of our test box
674 bool lt_x1 = false; // not right of our test box
675 bool gt_y0 = false; // not below our test box
676 bool lt_y1 = false; // not above our test box
677
678 // loop over all points on this curve segment
679 for(std::size_t k(this->_vtx_ptr[i]); k <= this->_vtx_ptr[i+1]; ++k)
680 {
681 // get the vertex/control point - input point
682 const WorldPoint p = this->_world.at(k) - inpoint;
683
684 // check the position of this point
685 gt_x0 = gt_x0 || (p[0] > -min_dist);
686 lt_x1 = lt_x1 || (p[0] < +min_dist);
687 gt_y0 = gt_y0 || (p[1] > -min_dist);
688 lt_y1 = lt_y1 || (p[1] < +min_dist);
689 }
690
691 // we can skip the curve if all points of the curve are on one side of our test box:
692 if(!(gt_x0 && lt_x1 && gt_y0 && lt_y1)) // <==> (!gt_x0 || !lt_x1 || !gt_y0 || !lt_y1)
693 continue;
694
695 // project on current segment
696 const DataType t = project_on_segment(i, inpoint);
697
698 // map point on current segment
699 const WorldPoint x = map_on_segment(i, t);
700
701 // compute distance to original point
702 DataType distance = (x - inpoint).norm_euclid();
703
704 // is that a new projection candidate?
705 if(distance < min_dist)
706 {
707 point = x;
708 min_dist = distance;
709 }
710 }
711 }
712
724 void project_meshpart(Mesh_& mesh, const MeshPart<Mesh_>& meshpart) const
725 {
726 auto& vtx = mesh.get_vertex_set();
727 const auto& target_vtx = meshpart.template get_target_set<0>();
728
729 for(Index i(0); i < meshpart.get_num_entities(0); ++i)
730 {
731 project_point(reinterpret_cast<WorldPoint&>(vtx[target_vtx[i]]));
732 }
733 }
734
736 DataType compute_dist(const WorldPoint& point) const
737 {
738 WorldPoint projected(point);
739 project_point(projected);
740 return (projected - point).norm_euclid();
741 }
742
744 DataType compute_dist(const WorldPoint& point, WorldPoint& grad_dist) const
745 {
746 WorldPoint projected(point);
747 project_point(projected);
748
749 grad_dist = (projected - point);
750
751 return grad_dist.norm_euclid();
752 }
753
756 {
757 WorldPoint grad_dist(DataType(0));
758
759 return compute_signed_dist(point, grad_dist);
760 }
761
763 DataType compute_signed_dist(const WorldPoint& point, WorldPoint& grad_dist) const
764 {
765 const DataType tol = Math::sqrt(Math::eps<DataType>());
766 const DataType sqr_eps = Math::sqr(Math::eps<DataType>());
767 DataType best_distance_sqr(Math::huge<DataType>());
768 DataType best_sign(_orientation);
769
770 WorldPoint projected(DataType(0));
771 WorldPoint best_nu(DataType(0));
772
773 // remember whether the best candidate is a vertex; ~0 means not a vertex
774 Index best_is_vertex = ~Index(0);
775
776 // loop over all line segments
777 for(Index i(0); (i+1) < Index(this->_vtx_ptr.size()); ++i)
778 {
779 // project on current segment
780 const DataType t = project_on_segment(i, point);
781
782 // map point on current segment
783 projected = map_on_segment(i, t);
784
785 WorldPoint difference(projected - point);
786
787 // compute squared distance to original point
788 DataType my_distance_sqr(difference.norm_euclid_sqr());
789
791
792 // If we have a point inside this segment we are happy and can leave
793 if(my_distance_sqr <= sqr_eps)
794 {
795 best_distance_sqr = DataType(0);
796 best_nu = nu;
797 best_sign = DataType(0);
798 best_is_vertex = ~Index(0);
799 break;
800 }
801
802 // Update the projection candidate iff it is the first segment, or the distance is smaller
803 if(i == Index(0) || (my_distance_sqr < best_distance_sqr))
804 {
805 best_distance_sqr = my_distance_sqr;
806 best_nu = nu;
807 if(_closed)
808 best_sign = _orientation * Math::signum(Tiny::dot(nu, difference));
809 grad_dist = difference;
810
811 // is the new candidate a vertex?
812 if(t <= DataType(0))
813 best_is_vertex = i; // start vertex of this curve segment
814 else if(t >= DataType(1))
815 best_is_vertex = i+1; // end vertex of this curve segment
816 else
817 best_is_vertex = ~Index(0); // candidate point is on inside of curve segment
818 }
819 }
820
821 // do we have a closed curve and is our candidate a vertex?
822 if(_closed && (best_is_vertex != ~Index(0)))
823 {
824 // The candidate point is a vertex, so we now have to determine the correct sign, because the
825 // 'best_sign' that we have figured out up to now might be incorrect in this case. The reason
826 // is that the point might be considered 'inside' with respect to one of the segments and
827 // 'outside' with respect to the other one. To figure out the correct sign, we have to find
828 // out whether the candidate vertex is a convex or concave vertex; see below for more details.
829
830 // a closed curve must have at least 2 segments ==> at least 3 vertices
831 XASSERT(this->_vtx_ptr.size() >= std::size_t(3));
832
833 // if this is the first vertex, then the previous line segment is the last one in the list
834 Index prev_seg(0);
835 if(best_is_vertex == Index(0))
836 prev_seg = Index(this->_vtx_ptr.size()-2u);
837 else
838 prev_seg = best_is_vertex - 1u;
839
840 // if this is the last vertex, then the next line segment is the first one in the list
841 Index next_seg(0);
842 if(best_is_vertex == Index(this->_vtx_ptr.size()-1u))
843 next_seg = Index(0);
844 else
845 next_seg = best_is_vertex;
846
847 // Now we want to check whether the angle between the two curve segments is < 180 deg, = 180 deg
848 // or > 180 deg and we do this by computing the normal vectors, then turning the first one by 90 deg,
849 // computing the dot-product and checking that against 0. In the two mighty fine pieces of art below,
850 // 'X' is the common candidate vertex, 'Prev' is the previous segment, 'Next' is the next segment,
851 // 'T' is the tangent of 'Prev' at 'X' and 'N' is the (outer) normal vector of 'Next' at 'X'. By
852 // computing the dot product of 'N' and 'T' we can find out whether the vertex is convex (N*T > 0,
853 // left image) or concave (N*T < 0, right image). Note that the vertex 'X' can only be a candidate
854 // for world points 'W' which lie in the concave area (outside on left image, inside on right image),
855 // because if the world point 'W' were inside the convex area, we would have found a candidate point
856 // on either 'Prev' or 'Next' that is closer to the world point than the vertex 'X', so we would not
857 // be in this if-clause at all.
858 //
859 // outside / inside outside \ inside .
860 // / '-. \ .
861 // Prev / N '-. \ Prev .
862 // W / '-. \ .
863 // .X 'X W .
864 // .-' ' \ / ' .
865 // N .-' ' \ Next Next / ' .
866 // .-' ' \ / ' T .
867 // ' T \ / ' .
868 //
869 // In short: if the candidate vertex 'X' is a convex vertex (N*T > 0), then the world point 'W'
870 // lies on the outside of our Bezier figure and if the candidate vertex 'X' is a concave vertex
871 // (N*T < 0) then the world point 'W' is on the inside of our Bezier figure. Note that if the
872 // vertex is straight (N*T = 0), then we have already figured out the correct sign by checking
873 // it against the normal of either segment before, i.e. 'best_sign' is already correct.
874
875 // so let's compute the normal vectors in that vertex wrt. the two adjacent curve segments
876 WorldPoint prev_normal = get_normal_on_segment(prev_seg, 1.0);
877 WorldPoint next_normal = get_normal_on_segment(next_seg, 0.0);
878
879 // now let's rotate the previous segment normal by 90 degrees
880 WorldPoint prev_tangent({-prev_normal[1], prev_normal[0]});
881
882 // now let's compute the dot-product of the previous tangential and the next normal
883 DataType nu = Tiny::dot(prev_tangent, next_normal);
884
885 if(nu < -tol)
886 {
887 // dot-product (N*T) is negative, so we have an angle of > 180 degrees ==> concave vertex
888 // since this vertex is our candidate, so the projected point must be inside
889 best_sign = +DataType(_orientation);
890 }
891 else if(nu > +tol)
892 {
893 // dot-product is positive, so we have an angle of < 180 degrees ==> convex vertex
894 // since this vertex is our candidate, so the projected point must be outside
895 best_sign = -DataType(_orientation);
896 }
897 // the else-case is redundant, because the sign is already correct
898 }
899
900 // If the point was far enough away from the interface, we can normalize the difference vector
901 if((best_distance_sqr > sqr_eps) && best_sign != DataType(0))
902 {
903 grad_dist.normalize();
904 grad_dist *= -best_sign;
905 }
906 // If we are too close, we take the normal in the projected point. We do not ALWAYS do this because the
907 // projected point might be where the normal is discontinuous, but in this case it is ok.
908 else
909 {
910 grad_dist = DataType(-1)*best_nu;
911 grad_dist.normalize();
912 XASSERT(Math::abs(grad_dist.norm_euclid() - DataType(1)) < tol);
913 }
914
915 // we've done it!
916 return best_sign * Math::sqrt(best_distance_sqr);
917 }
918
920 virtual void write(std::ostream& os, const String& sindent) const override
921 {
922 String sind(sindent), sind2(sindent);
923 if(!sind.empty())
924 {
925 sind.append(" ");
926 sind2.append(" ");
927 }
928
929 os << sindent << "<Bezier dim=\"2\" size=\"" << this->_vtx_ptr.size() << "\"";
930 os << " type=\"" << (this->_closed ? "closed" : "open") << "\"";
931 os << (_orientation == -DataType(1) ? " orientation=\"-1\"" : "" )<<">\n";
932
933 // write points
934 os << sind << "<Points>\n";
935 // write first vertex point
936 os << sind2 << 0;
937 for(int j(0); j < BaseClass::world_dim; ++j)
938 os << " " << _world[_vtx_ptr.front()][j];
939 os << '\n';
940
941 // write remaining points
942 for(std::size_t i(1); i < _vtx_ptr.size(); ++i)
943 {
944 // write number of control points
945 os << sind2 << ( _vtx_ptr[i] - _vtx_ptr[i-1] - std::size_t(1));
946 // write point coordinates
947 for(std::size_t k(_vtx_ptr[i-1]+1); k <= _vtx_ptr[i]; ++k)
948 for(int j(0); j < BaseClass::world_dim; ++j)
949 os << " " << _world[k][j];
950 os << '\n';
951 }
952 os << sind << "</Points>\n";
953
954 // write parameters
955 if(!_param.empty())
956 {
957 os << sind << "<Params>\n";
958 for(std::size_t i(0); i < _param.size(); ++i)
959 os << sind2 << _param[i][0] << '\n';
960 os << sind << "</Params>\n";
961 }
962
963 // write terminator
964 os << sindent << "</Bezier>\n";
965 }
966
967 protected:
968 std::size_t find_param(const DataType x) const
969 {
970 XASSERTM(!_param.empty(),"Bezier has no parameters");
971
972 // check for boundary
973 if(x <= _param.front()[0])
974 return std::size_t(0);
975 if(x >= _param.back()[0])
976 return _param.size()-std::size_t(2);
977
978 // apply binary search
979 std::size_t il(0), ir(_param.size()-1);
980 while(il+1 < ir)
981 {
982 // test median
983 std::size_t im = (il+ir)/2;
984 DataType xm = _param.at(im)[0];
985 if(x < xm)
986 {
987 ir = im;
988 }
989 else
990 {
991 il = im;
992 }
993 }
994
995 // return interval index
996 return (il+1 < _param.size() ? il : (_param.size() - std::size_t(2)));
997 }
998 }; // class Bezier<...>
999
1000 template<typename Mesh_>
1002 public Xml::MarkupParser
1003 {
1004 typedef Bezier<Mesh_> ChartType;
1005
1006 private:
1007 Bezier<Mesh_>& _bezier;
1008 Index _size, _read;
1009
1010 public:
1011 explicit BezierPointsParser(Bezier<Mesh_>& bezier, Index size) :
1012 _bezier(bezier), _size(size), _read(0)
1013 {
1014 }
1015
1016 virtual bool attribs(std::map<String,bool>&) const override
1017 {
1018 return true;
1019 }
1020
1021 virtual void create(int iline, const String& sline, const String&, const std::map<String, String>&, bool closed) override
1022 {
1023 if(closed)
1024 throw Xml::GrammarError(iline, sline, "Invalid closed markup");
1025 }
1026
1027 virtual void close(int iline, const String& sline) override
1028 {
1029 // ensure that we have read all vertices
1030 if(_read < _size)
1031 throw Xml::GrammarError(iline, sline, "Invalid terminator; expected point");
1032 }
1033
1034 virtual std::shared_ptr<MarkupParser> markup(int, const String&, const String&) override
1035 {
1036 // no children allowed
1037 return nullptr;
1038 }
1039
1040 virtual bool content(int iline, const String& sline) override
1041 {
1042 // make sure that we do not read more points than expected
1043 if(_read >= _size)
1044 throw Xml::ContentError(iline, sline, "Invalid content; exprected terminator");
1045
1046 // split line by whitespaces
1047 std::deque<String> scoords = sline.split_by_whitespaces();
1048
1049 // try to parse the control point count
1050 std::size_t num_ctrl(0);
1051 if(!scoords.front().parse(num_ctrl))
1052 throw Xml::ContentError(iline, sline, "Failed to parse control point count");
1053
1054 // the first point must not have control points
1055 if((_read == Index(0)) && (num_ctrl > std::size_t(0)))
1056 throw Xml::ContentError(iline, sline, "First point must be a vertex point");
1057
1058 // check size: 1 + (#ctrl+1) * #coords
1059 if(scoords.size() != ((num_ctrl+1) * std::size_t(ChartType::world_dim) + std::size_t(1)))
1060 throw Xml::ContentError(iline, sline, "Invalid number of coordinates");
1061
1062 typename ChartType::WorldPoint point;
1063
1064 // try to parse the control points
1065 for(int k(0); k < int(num_ctrl); ++k)
1066 {
1067 for(int i(0); i < ChartType::world_dim; ++i)
1068 {
1069 if(!scoords.at(std::size_t(k*ChartType::world_dim+i+1)).parse(point[i]))
1070 throw Xml::ContentError(iline, sline, "Failed to parse control point coordinate");
1071 }
1072 // push the control point
1073 _bezier.push_control(point);
1074 }
1075
1076 // try to parse all coords of the vertex point
1077 for(int i(0); i < ChartType::world_dim; ++i)
1078 {
1079 if(!scoords.at(std::size_t(int(num_ctrl)*ChartType::world_dim+i+1)).parse(point[i]))
1080 throw Xml::ContentError(iline, sline, "Failed to parse vertex point coordinate");
1081 }
1082
1083 // push the vertex point
1084 _bezier.push_vertex(point);
1085
1086 // okay, another point done
1087 ++_read;
1088
1089 return true;
1090 }
1091 }; // BezierParamsParser
1092
1093 template<typename Mesh_>
1095 public Xml::MarkupParser
1096 {
1097 typedef Bezier<Mesh_> ChartType;
1098
1099 private:
1100 Bezier<Mesh_>& _bezier;
1101 Index _size, _read;
1102
1103 public:
1104 explicit BezierParamsParser(Bezier<Mesh_>& bezier, Index size) :
1105 _bezier(bezier), _size(size), _read(0)
1106 {
1107 }
1108
1109 virtual bool attribs(std::map<String,bool>&) const override
1110 {
1111 return true;
1112 }
1113
1114 virtual void create(int iline, const String& sline, const String&, const std::map<String, String>&, bool closed) override
1115 {
1116 if(closed)
1117 throw Xml::GrammarError(iline, sline, "Invalid closed markup");
1118 }
1119
1120 virtual void close(int iline, const String& sline) override
1121 {
1122 // ensure that we have read all vertices
1123 if(_read < _size)
1124 throw Xml::GrammarError(iline, sline, "Invalid terminator; expected point");
1125 }
1126
1127 virtual std::shared_ptr<MarkupParser> markup(int, const String&, const String&) override
1128 {
1129 // no children allowed
1130 return nullptr;
1131 }
1132
1133 virtual bool content(int iline, const String& sline) override
1134 {
1135 // make sure that we do not read more points than expected
1136 if(_read >= _size)
1137 throw Xml::ContentError(iline, sline, "Invalid content; expected terminator");
1138
1139 typename ChartType::ParamPoint point;
1140
1141 // try to parse all coords
1142 if(!sline.parse(point[0]))
1143 throw Xml::ContentError(iline, sline, "Failed to parse param coordinate");
1144
1145 // push
1146 _bezier.push_param(point);
1147
1148 // okay, another point done
1149 ++_read;
1150
1151 return true;
1152 }
1153 }; // class BezierParamsParser
1154
1155 template<typename Mesh_, typename ChartReturn_ = ChartBase<Mesh_>>
1157 public Xml::MarkupParser
1158 {
1159 private:
1160 typedef Bezier<Mesh_> ChartType;
1161 typedef typename ChartType::DataType DataType;
1162 std::unique_ptr<ChartReturn_>& _chart;
1163 std::unique_ptr<Bezier<Mesh_>> _bezier;
1164 Index _size;
1165
1166 public:
1167 explicit BezierChartParser(std::unique_ptr<ChartReturn_>& chart) :
1168 _chart(chart),
1169 _bezier(),
1170 _size(0)
1171 {
1172 }
1173
1174 virtual bool attribs(std::map<String,bool>& attrs) const override
1175 {
1176 attrs.emplace("dim", true);
1177 attrs.emplace("size", true);
1178 attrs.emplace("type", false);
1179 attrs.emplace("orientation", false);
1180 return true;
1181 }
1182
1183 virtual void create(
1184 int iline,
1185 const String& sline,
1186 const String&,
1187 const std::map<String, String>& attrs,
1188 bool closed) override
1189 {
1190 // make sure this one isn't closed
1191 if(closed)
1192 throw Xml::GrammarError(iline, sline, "Invalid closed Bezier markup");
1193
1194 Index dim(0);
1195
1196 // try to parse the dimension
1197 if(!attrs.find("dim")->second.parse(dim))
1198 throw Xml::GrammarError(iline, sline, "Failed to parse Bezier dimension");
1199 if(dim != Index(2))
1200 throw Xml::GrammarError(iline, sline, "Invalid Bezier dimension");
1201
1202 // try to parse the size
1203 if(!attrs.find("size")->second.parse(_size))
1204 throw Xml::GrammarError(iline, sline, "Failed to parse Bezier size");
1205 if(_size < Index(2))
1206 throw Xml::GrammarError(iline, sline, "Invalid Bezier size");
1207
1208 // try to check type
1209 bool poly_closed(false);
1210 auto it = attrs.find("type");
1211 if(it != attrs.end())
1212 {
1213 String stype = it->second;
1214 if(it->second == "closed")
1215 poly_closed = true;
1216 else if (it->second != "open")
1217 throw Xml::ContentError(iline, sline, "Invalid Bezier type; must be either 'closed' or 'open'");
1218 }
1219
1220 DataType orientation(1);
1221 it = attrs.find("orientation");
1222 if(it != attrs.end())
1223 it->second.parse(orientation);
1224
1225 // up to now, everything's fine
1226 _bezier.reset(new ChartType(poly_closed, orientation));
1227 }
1228
1229 virtual void close(int, const String&) override
1230 {
1231 // okay
1232 _chart = std::move(_bezier);
1233 }
1234
1235 virtual bool content(int, const String&) override
1236 {
1237 return false;
1238 }
1239
1240 virtual std::shared_ptr<Xml::MarkupParser> markup(int, const String&, const String& name) override
1241 {
1242 if(name == "Points") return std::make_shared<BezierPointsParser<Mesh_>>(*_bezier, _size);
1243 if(name == "Params") return std::make_shared<BezierParamsParser<Mesh_>>(*_bezier, _size);
1244 return nullptr;
1245 }
1246 }; // class BezierChartParser<...>
1247 } // namespace Atlas
1248 } // namespace Geometry
1249} // namespace FEAT
#define XABORTM(msg)
Abortion macro definition with custom message.
Definition: assertion.hpp:192
#define ASSERTM(expr, msg)
Debug-Assertion macro definition with custom message.
Definition: assertion.hpp:230
#define XASSERT(expr)
Assertion macro definition.
Definition: assertion.hpp:262
#define XASSERTM(expr, msg)
Assertion macro definition with custom message.
Definition: assertion.hpp:263
virtual bool content(int, const String &) override
Called to process a content line.
Definition: bezier.hpp:1235
virtual void close(int, const String &) override
Closes this markup parser node.
Definition: bezier.hpp:1229
virtual std::shared_ptr< Xml::MarkupParser > markup(int, const String &, const String &name) override
Called to process a child markup node.
Definition: bezier.hpp:1240
virtual void create(int iline, const String &sline, const String &, const std::map< String, String > &attrs, bool closed) override
Creates this markup parser node.
Definition: bezier.hpp:1183
virtual bool attribs(std::map< String, bool > &attrs) const override
Specifies the mandatory and optional attributes.
Definition: bezier.hpp:1174
Bezier chart class template.
Definition: bezier.hpp:45
void push_close()
Marks the Bezier chart as closed.
Definition: bezier.hpp:176
void push_param(const ParamPoint &param)
Pushes a new parameter to the spline.
Definition: bezier.hpp:168
WorldPoint map_on_segment(const Index i, const DataType t) const
Maps a local segment parameter point.
Definition: bezier.hpp:296
bool _closed
Specifies whether the spline is closed.
Definition: bezier.hpp:67
const std::deque< WorldPoint > & get_world_points() const
Returns a (const) reference to the internal world points deque.
Definition: bezier.hpp:239
Bezier(bool closed=false, DataType orientation=DataType(1))
default CTOR
Definition: bezier.hpp:73
std::deque< ParamPoint > _param
The parameter values for these world points.
Definition: bezier.hpp:65
void project_point(WorldPoint &point) const
Projects a single world point.
Definition: bezier.hpp:645
DataType compute_dist(const WorldPoint &point, WorldPoint &grad_dist) const
Computes the distance of a point to this chart.
Definition: bezier.hpp:744
std::deque< WorldPoint > _world
The vertex and control points for the spline.
Definition: bezier.hpp:63
virtual bool can_explicit() const override
Specifies whether the chart can perform explicit projection.
Definition: bezier.hpp:219
DataType compute_signed_dist(const WorldPoint &point) const
Computes the signed distance of a point to this chart.
Definition: bezier.hpp:755
virtual void transform(const WorldPoint &origin, const WorldPoint &angles, const WorldPoint &offset) override
Definition: bezier.hpp:265
BaseClass::ParamPoint ParamPoint
Vector type for parameter points aka. domain points.
Definition: bezier.hpp:54
DataType _orientation
Specifies if the chart is to be oriented negatively or not.
Definition: bezier.hpp:69
DataType compute_signed_dist(const WorldPoint &point, WorldPoint &grad_dist) const
Computes the signed distance of a point to this chart.
Definition: bezier.hpp:763
Bezier(const std::deque< std::size_t > &vtx_ptr, const std::deque< WorldPoint > &world, const std::deque< ParamPoint > &param, bool closed, DataType orientation=DataType(1))
Constructor.
Definition: bezier.hpp:98
void set_orientation(int ori)
Sets the orientation code for the curve.
Definition: bezier.hpp:207
void project_meshpart(Mesh_ &mesh, const MeshPart< Mesh_ > &meshpart) const
Projects all mesh points identified by a meshpart.
Definition: bezier.hpp:724
BaseClass::CoordType DataType
Floating point type for coordinates.
Definition: bezier.hpp:50
BaseClass::WorldPoint WorldPoint
Vector type for world points aka. image points.
Definition: bezier.hpp:52
virtual void write(std::ostream &os, const String &sindent) const override
Writes the Chart into a stream in XML format.
Definition: bezier.hpp:920
WorldPoint get_normal_on_segment(const Index i, const DataType t) const
Computes the outer unit normal in a single point.
Definition: bezier.hpp:347
DataType compute_dist(const WorldPoint &point) const
Computes the distance of a point to this chart.
Definition: bezier.hpp:736
DataType project_on_segment(const Index i, const WorldPoint &point) const
Projects a point onto one segment of the spline.
Definition: bezier.hpp:417
const std::deque< std::size_t > & get_vertex_pointers() const
Returns a (const) reference to the internal vertex pointers deque.
Definition: bezier.hpp:259
void push_control(const WorldPoint &point)
Pushes a new control point to the spline.
Definition: bezier.hpp:157
std::deque< std::size_t > _vtx_ptr
The vertex point pointer array.
Definition: bezier.hpp:61
static constexpr int max_degree
maximum allowed spline degree
Definition: bezier.hpp:57
ChartCRTP< Bezier< Mesh_ >, Mesh_, BezierTraits > BaseClass
The CRTP base class.
Definition: bezier.hpp:48
virtual String get_type() const override
Writes the type as String.
Definition: bezier.hpp:133
bool is_closed() const
Checks whether the curve is open or closed.
Definition: bezier.hpp:186
int get_orientation() const
Returns the orientation code for the curve.
Definition: bezier.hpp:196
void push_vertex(const WorldPoint &point)
Pushes a new vertex point to the spline.
Definition: bezier.hpp:144
void map_param(WorldPoint &point, const ParamPoint &param) const
Maps a single parameter point.
Definition: bezier.hpp:625
std::deque< WorldPoint > & get_world_points()
Returns a (const) reference to the internal world points deque.
Definition: bezier.hpp:229
std::deque< std::size_t > & get_vertex_pointers()
Returns a (const) reference to the internal vertex pointers deque.
Definition: bezier.hpp:249
virtual bool content(int iline, const String &sline) override
Called to process a content line.
Definition: bezier.hpp:1133
virtual void close(int iline, const String &sline) override
Closes this markup parser node.
Definition: bezier.hpp:1120
virtual bool attribs(std::map< String, bool > &) const override
Specifies the mandatory and optional attributes.
Definition: bezier.hpp:1109
virtual void create(int iline, const String &sline, const String &, const std::map< String, String > &, bool closed) override
Creates this markup parser node.
Definition: bezier.hpp:1114
virtual std::shared_ptr< MarkupParser > markup(int, const String &, const String &) override
Called to process a child markup node.
Definition: bezier.hpp:1127
virtual void create(int iline, const String &sline, const String &, const std::map< String, String > &, bool closed) override
Creates this markup parser node.
Definition: bezier.hpp:1021
virtual bool attribs(std::map< String, bool > &) const override
Specifies the mandatory and optional attributes.
Definition: bezier.hpp:1016
virtual void close(int iline, const String &sline) override
Closes this markup parser node.
Definition: bezier.hpp:1027
virtual std::shared_ptr< MarkupParser > markup(int, const String &, const String &) override
Called to process a child markup node.
Definition: bezier.hpp:1034
virtual bool content(int iline, const String &sline) override
Called to process a content line.
Definition: bezier.hpp:1040
Chart CRTP base-class template.
Definition: chart.hpp:354
static constexpr int world_dim
the world dimension of this chart
Definition: chart.hpp:377
Class template for partial meshes.
Definition: mesh_part.hpp:90
Index get_num_entities(int dim) const
Returns the number of entities.
Definition: mesh_part.hpp:311
String class implementation.
Definition: string.hpp:47
bool parse(T_ &t) const
Parses the string and stores its value in the provided variable.
Definition: string.hpp:838
std::deque< String > split_by_whitespaces() const
Splits the string by white-spaces.
Definition: string.hpp:519
Tiny Matrix class template.
CUDA_HOST_DEVICE Matrix & set_rotation_2d(T_ angle)
Sets this matrix to a 2D rotation matrix.
Tiny Vector class template.
Xml content error class.
Xml grammar error class.
XML Markup Parser interface.
T_ sqrt(T_ x)
Returns the square-root of a value.
Definition: math.hpp:300
T_ abs(T_ x)
Returns the absolute value.
Definition: math.hpp:275
T_ clamp(T_ x, T_ a, T_ b)
Clamps a value to a range.
Definition: math.hpp:216
T_ sqr(T_ x)
Returns the square of a value.
Definition: math.hpp:95
T_ min(T_ a, T_ b)
Returns the minimum of two values.
Definition: math.hpp:123
T_ signum(T_ x)
Returns the sign of a value.
Definition: math.hpp:250
CUDA_HOST_DEVICE T_ dot(const T_ &a, const T_ &b)
Computes the dot-product of two scalars.
FEAT namespace.
Definition: adjactor.hpp:12
std::uint64_t Index
Index data type.
Bezier chart traits.
Definition: bezier.hpp:18
static constexpr bool is_explicit
we support explicit map
Definition: bezier.hpp:20
static constexpr int world_dim
this is a 2D object
Definition: bezier.hpp:24
static constexpr bool is_implicit
we don't support implicit project
Definition: bezier.hpp:22
static constexpr int param_dim
we have 1D parameters
Definition: bezier.hpp:26