Gamp v0.0.7-36-g24b1eb6
Gamp: Graphics, Audio, Multimedia and Processing
Loading...
Searching...
No Matches
OutlineShape.hpp
Go to the documentation of this file.
1/*
2 * Author: Sven Gothel <sgothel@jausoft.com> (C++, Java) and Rami Santina (Java)
3 * Copyright Gothel Software e.K. and the authors
4 *
5 * SPDX-License-Identifier: MIT
6 *
7 * This Source Code Form is subject to the terms of the MIT License
8 * If a copy of the MIT was not distributed with this file,
9 * you can obtain one at https://opensource.org/license/mit/.
10 */
11#ifndef JAU_GAMP_GRAPH_OUTLINESHAPE_HPP_
12#define JAU_GAMP_GRAPH_OUTLINESHAPE_HPP_
13
14#include <jau/basic_algos.hpp>
15#include <jau/basic_types.hpp>
16#include <jau/debug.hpp>
17#include <jau/enum_util.hpp>
20
21#include <gamp/graph/Graph.hpp>
26
27namespace gamp::graph {
28
29 using namespace jau::math;
30 using namespace jau::math::geom;
31 using namespace jau::enums;
32 using jau::math::geom::plane::AffineTransform;
33
34 /** \addtogroup Gamp_Graph
35 *
36 * @{
37 */
38
39 /**
40 * A Generic shape objects which is defined by a list of Outlines.
41 * This Shape can be transformed to triangulations.
42 * The list of triangles generated are render-able by a Region object.
43 * The triangulation produced by this Shape will define the
44 * closed region defined by the outlines.
45 *
46 * One or more OutlineShape Object can be associated to a region
47 * this is left as a high-level representation of the Objects. For
48 * optimizations, flexibility requirements for future features.
49
50 * <a name="windingrules">
51 * Outline shape general {@link Winding} rules
52 * - Outer boundary-shapes are required as Winding::CCW
53 * - Inner hole-shapes should be Winding::CW
54 * - If unsure
55 * - You may check Winding via getWindingOfLastOutline() or Outline::getWinding() (optional, might be incorrect)
56 * - Use setWindingOfLastOutline(Winding) before {@link #closeLastOutline(boolean)} or {@link #closePath()} } to enforce Winding::CCW, or
57 * - use Outline::setWinding(Winding) on a specific Outline to enforce Winding::CCW.
58 * - If e.g. the Winding has changed for an Outline by above operations, its vertices have been reversed.
59 * - Safe path: Simply create all outer boundary-shapes with Winding::CCW and inner hole-shapes with Winding::CW.
60 *
61 * Example to creating an Outline Shape:
62 * <pre>
63 addVertex(...)
64 addVertex(...)
65 addVertex(...)
66 addEmptyOutline()
67 addVertex(...)
68 addVertex(...)
69 addVertex(...)
70 * </pre>
71 *
72 * The above will create two outlines each with three vertices. By adding these two outlines to
73 * the OutlineShape, we are stating that the combination of the two outlines represent the shape.
74 *
75 * To specify that the shape is curved at a region, the on-curve flag should be set to false
76 * for the vertex that is in the middle of the curved region (if the curved region is defined by 3
77 * vertices (quadratic curve).
78 *
79 * In case the curved region is defined by 4 or more vertices the middle vertices should both have
80 * the on-curve flag set to false.
81 *
82 * Example:
83 * <pre>
84 addVertex(0,0, true);
85 addVertex(0,1, false);
86 addVertex(1,1, false);
87 addVertex(1,0, true);
88 * </pre>
89 *
90 * The above snippet defines a cubic nurbs curve where (0,1 and 1,1)
91 * do not belong to the final rendered shape.
92 *
93 * <i>Implementation Notes:</i><br>
94 * - The first vertex of any outline belonging to the shape should be on-curve
95 * - Intersections between off-curved parts of the outline is not handled
96 *
97 * @see Outline
98 * @see Region
99 */
101 public:
102 typedef uint32_t size_type;
103
104 /// byte-size int32_t limit: 536'870'911 (FIXME: Adjust to actual type, i.e. Vertex = 2x Vec3f?)
105 constexpr static size_type max_elements = std::numeric_limits<uint32_t>::max() / sizeof(uint32_t);
106
107 /** Initial sharpness() value, which can be modified via setSharpness(float). */
108 static constexpr float DEFAULT_SHARPNESS = 0.5f;
109
110 enum class DirtyBits : uint16_t {
111 none = 0,
112 bounds = 1 << 0,
113 vertices = 1 << 1, /// <Modified shape, requires to update the vertices and triangles, here: vertices
114 triangles = 1 << 2, /// <Modified shape, requires to update the vertices and triangles, here: triangulation.
115 convex = 1 << 3, /// <Modified shape, requires to update the convex determination
117 };
119
120 enum class VertexState : uint16_t { undefined = 0, quadratic_nurbs = 1 };
122
123 private:
124 size_type m_outlineVertCapacity;
125 Vec3f m_normal;
126 OutlineList m_outlines;
127 mutable AABBox3f m_bbox;
128 mutable DirtyBits m_dirtyBits;
129 size_type m_addedVertexCount;
130 mutable bool m_complexShape;
131 VertexState m_outlineState;
132 float m_sharpness;
133 VertexList m_vertices;
134 TriangleRefList m_triangles;
135
136 public:
137
139 OutlineShape(size_type capacity, size_type outlineVertCapacity)
140 : m_outlineVertCapacity(capacity),
141 m_normal(0, 0, 1),
142 m_outlines(m_outlineVertCapacity),
143 m_bbox(),
144 m_dirtyBits(DirtyBits::none),
145 m_addedVertexCount(0),
146 m_complexShape(false),
147 m_outlineState(VertexState::undefined),
148 m_sharpness(DEFAULT_SHARPNESS)
149 {
150 m_outlines.emplace_back(outlineVertCapacity);
151 }
152
153
154 /** Normal vector, optionally used by tesselator to add (interleaved) normals. */
155 constexpr const Vec3f& normal() const noexcept { return m_normal; }
156 /** Writing the normal vector, optionally used by tesselator to add (interleaved) normals. */
157 constexpr Vec3f& normal() noexcept { return m_normal; }
158
159 /**
160 * Return the number of newly added vertices during getTriangles(VerticesState)
161 * while transforming the outlines to VerticesState::QUADRATIC_NURBS and triangulation.
162 * @see setIsQuadraticNurbs()
163 */
164 constexpr size_type addedVertexCount() const noexcept { return m_addedVertexCount; }
165
166 /** Sharpness value, defaults to DEFAULT_SHARPNESS. */
167 constexpr float sharpness() const noexcept { return m_sharpness; }
168
169 /** Sets sharpness, defaults to DEFAULT_SHARPNESS. */
170 void setSharpness(float s) noexcept {
171 if( m_sharpness != s ) {
172 clearCache();
173 m_sharpness=s;
174 }
175 }
176
177 /** Clears all data and reset all states as if this instance was newly created */
178 void clear() {
179 m_outlines.clear();
180 m_outlines.emplace_back(m_outlineVertCapacity);
181 m_outlineState = VertexState::undefined;
182 m_bbox.reset();
183 m_vertices.clear();
184 m_triangles.clear();
185 m_addedVertexCount = 0;
186 m_complexShape = false;
187 m_dirtyBits = DirtyBits::none;
188 }
189
190 /** Clears cached triangulated data, i.e. {@link #getTriangles(VerticesState)} and {@link #getVertices()}. */
191 void clearCache() noexcept {
192 m_vertices.clear();
193 m_triangles.clear();
195 }
196
197 constexpr void reserve(size_type newCapacity) { m_outlines.reserve(newCapacity); }
198
199 constexpr DirtyBits dirtyBits() const noexcept { return m_dirtyBits; }
200 constexpr bool verticesDirty() const noexcept { return is_set(m_dirtyBits, DirtyBits::vertices); }
201 constexpr bool trianglesDirty() const noexcept { return is_set(m_dirtyBits, DirtyBits::triangles); }
202 void markClean(DirtyBits flags) noexcept { m_dirtyBits &= ~flags; }
203
204 private:
205 void validateBoundingBox() const noexcept {
206 m_dirtyBits &= ~DirtyBits::bounds;
207 m_bbox.reset();
208 for (const auto & m_outline : m_outlines) {
209 m_bbox.resize(m_outline.bounds());
210 }
211 }
212
213 public:
214 const AABBox3f& bounds() const noexcept {
215 if ( is_set(m_dirtyBits, DirtyBits::bounds) ) {
216 validateBoundingBox();
217 }
218 return m_bbox;
219 }
220
221 bool empty() const noexcept { return m_outlines.empty(); }
222
223 /** Returns the number of {@link Outline}s. */
224 size_type outlineCount() const noexcept {
225 return m_outlines.size();
226 }
227
228 /** Returns the total {@link Outline#getVertexCount() vertex number} of all {@link Outline}s. */
229 size_type vertexCount() const noexcept {
230 size_type res = 0;
231 for(const Outline& o : m_outlines) {
232 res += o.vertexCount();
233 }
234 return res;
235 }
236
237 const OutlineList& outlines() const noexcept { return m_outlines; }
238 OutlineList& outlines() noexcept { return m_outlines; }
239
240 const Outline& outline(size_type i) const noexcept { return m_outlines[i]; }
241 Outline& outline(size_type i) noexcept { return m_outlines[i]; }
242
243 /**
244 * Get the last added outline to the list
245 * of outlines that define the shape
246 * @return the last outline
247 */
248 const Outline& lastOutline() const noexcept {
249 return m_outlines[m_outlines.size()-1];
250 }
251 /**
252 * Get the last added outline to the list
253 * of outlines that define the shape
254 * @return the last outline
255 */
256 Outline& lastOutline() noexcept {
257 return m_outlines[m_outlines.size()-1];
258 }
259
260 /**
261 * Compute the {@link Winding} of the {@link #getLastOutline()} using the {@link VectorUtil#area(ArrayList)} function over all of its vertices.
262 * @return {@link Winding#CCW} or {@link Winding#CW}
263 */
264 Winding windingOfLastOutline() const noexcept {
265 return lastOutline().getWinding();
266 }
267
268 /**
269 * Sets the enforced {@link Winding} of the {@link #getLastOutline()}.
270 */
272 lastOutline().setWinding(enforced);
273 }
274
275 /**
276 * Returns cached or computed result if at least one `polyline` outline(size_type) is a complex shape, see Outline::isComplex().
277 * <p>
278 * A polyline with less than 3 elements is marked a simple shape for simplicity.
279 * </p>
280 * <p>
281 * The result is cached.
282 * </p>
283 * @see #setOverrideConvex(boolean)
284 * @see #clearOverrideConvex()
285 */
286 bool isComplex() const noexcept {
287 if( !is_set(m_dirtyBits, DirtyBits::convexOverride) &&
288 is_set(m_dirtyBits, DirtyBits::convex) )
289 {
290 m_complexShape = false;
291 size_type sz = outlineCount();
292 for(size_type i=0; i<sz && !m_complexShape; ++i) {
293 m_complexShape = outline(i).isComplex();
294 }
295 m_dirtyBits &= ~DirtyBits::convex;
296 }
297 return m_complexShape;
298 }
299 /**
300 * Overrides {@link #isComplex()} using the given value instead of computing via {@link Outline#isComplex()}.
301 * @see #clearOverrideConvex()
302 * @see #isComplex()
303 */
304 void setOverrideConvex(bool convex) noexcept {
305 m_dirtyBits |= DirtyBits::convexOverride;
306 m_complexShape = convex;
307 }
308
309 /**
310 * Clears the {@link #isComplex()} override done by {@link #setOverrideConvex(boolean)}
311 * @see #setOverrideConvex(boolean)
312 * @see #isComplex()
313 */
314 void clearOverrideConvex() noexcept {
315 m_dirtyBits &= ~DirtyBits::convexOverride;
316 m_dirtyBits |= DirtyBits::convex;
317 }
318
319 /**
320 * Add a new empty {@link Outline}
321 * to the end of this shape's outline list.
322 * <p>If the {@link #getLastOutline()} is empty already, no new one will be added.</p>
323 *
324 * After a call to this function all new vertices added
325 * will belong to the new outline
326 */
328 if( !lastOutline().empty() ) {
329 m_outlines.emplace_back(m_outlineVertCapacity);
330 }
331 }
332
333 /**
334 * Appends the {@link Outline} element to the end,
335 * ensuring a clean tail.
336 *
337 * <p>A clean tail is ensured, no double empty Outlines are produced
338 * and a pre-existing empty outline will be replaced with the given one. </p>
339 *
340 * @param outline Outline object to be added
341 * @throws NullPointerException if the {@link Outline} element is null
342 */
344 addOutline(m_outlines.size(), outline);
345 }
346
347 /**
348 * Insert the {@link Outline} element at the given {@code position}.
349 *
350 * <p>If the {@code position} indicates the end of this list,
351 * a clean tail is ensured, no double empty Outlines are produced
352 * and a pre-existing empty outline will be replaced with the given one. </p>
353 *
354 * @param position of the added Outline
355 * @param outline Outline object to be added
356 * @throws NullPointerException if the {@link Outline} element is null
357 * @throws IndexOutOfBoundsException if position is out of range (position < 0 || position > getOutlineNumber())
358 */
359 void addOutline(size_type position, const Outline& outline) {
360 if( m_outlines.size() == position ) {
361 const Outline& last = lastOutline();
362 if( outline.empty() && last.empty() ) {
363 return;
364 }
365 if( last.empty() ) {
366 m_outlines[position-1] = outline;
367 if( !is_set(m_dirtyBits, DirtyBits::bounds) ) {
368 m_bbox.resize(outline.bounds());
369 }
370 // vertices.addAll(outline.getVertices()); // FIXME: can do and remove DIRTY_VERTICES ?
372 return;
373 }
374 }
375 m_outlines.insert(position, outline);
376 if( !is_set(m_dirtyBits, DirtyBits::bounds) ) {
377 m_bbox.resize(outline.bounds());
378 }
380 }
381
382 /**
383 * Insert the {@link OutlineShape} elements of type {@link Outline}, .. at the end of this shape,
384 * using {@link #addOutline(Outline)} for each element.
385 * <p>Closes the current last outline via {@link #closeLastOutline(boolean)} before adding the new ones.</p>
386 * @param outlineShape OutlineShape elements to be added.
387 * @throws NullPointerException if the {@link OutlineShape} is null
388 * @throws IndexOutOfBoundsException if position is out of range (position < 0 || position > getOutlineNumber())
389 */
390 void addOutlineShape(const OutlineShape& outlineShape) {
391 closeLastOutline(true);
392 for(size_type i=0; i<outlineShape.outlineCount(); i++) {
393 addOutline(outlineShape.outline(i));
394 }
395 }
396
397 /**
398 * Replaces the {@link Outline} element at the given {@code position}.
399 * <p>Sets the bounding box dirty, hence a next call to {@link #getBounds()} will validate it.</p>
400 *
401 * @param position of the replaced Outline
402 * @param outline replacement Outline object
403 * @throws NullPointerException if the {@link Outline} element is null
404 * @throws IndexOutOfBoundsException if position is out of range (position < 0 || position >= getOutlineNumber())
405 */
406 void setOutline(size_type position, const Outline& outline) {
407 m_outlines.insert(position, outline);
409 }
410
411 /**
412 * Removes the {@link Outline} element at the given {@code position}.
413 * <p>Sets the bounding box dirty, hence a next call to {@link #getBounds()} will validate it.</p>
414 *
415 * @param position of the to be removed Outline
416 * @throws IndexOutOfBoundsException if position is out of range (position < 0 || position >= getOutlineNumber())
417 */
418 void removeOutline(size_type position) {
420 m_outlines.erase(position);
421 }
422
423 //
424 //
425
426 /**
427 * Adds a vertex to the last open outline to the shape's tail.
428 *
429 * @param v the vertex to be added to the OutlineShape
430 * @see <a href="#windingrules">see winding rules</a>
431 */
432 void addVertex(const Vertex& v) {
433 Outline& lo = lastOutline();
434 lo.addVertex(v);
435 if( !is_set(m_dirtyBits, DirtyBits::bounds) ) {
436 m_bbox.resize(v.coord());
437 }
438 // vertices.add(v); // FIXME: can do and remove DIRTY_VERTICES ?
440 }
441
442 /**
443 * Adds a vertex to the last open outline to the shape at {@code position}
444 *
445 * @param position index within the last open outline, at which the vertex will be added
446 * @param v the vertex to be added to the OutlineShape
447 * @see <a href="#windingrules">see winding rules</a>
448 */
449 void addVertex(size_type position, const Vertex& v) {
450 Outline& lo = lastOutline();
451 lo.addVertex(position, v);
452 if( !is_set(m_dirtyBits, DirtyBits::bounds) ) {
453 m_bbox.resize(v.coord());
454 }
455 // vertices.add(v); // FIXME: can do and remove DIRTY_VERTICES ?
457 }
458
459 /**
460 * Add a 3D {@link Vertex} to the last open outline to the shape's tail.
461 *
462 * @param x the x coordinate
463 * @param y the y coordinate
464 * @param z the z coordinate
465 * @param onCurve flag if this vertex is on the curve or defines a curved region of the shape around this vertex.
466 * @see <a href="#windingrules">see winding rules</a>
467 */
468 void addVertex(float x, float y, float z, bool onCurve) {
469 addVertex(Vertex(x, y, z, onCurve));
470 }
471
472 /**
473 * Add a 3D {@link Vertex} to the last open outline to the shape's tail.
474 *
475 * @param v the Vec3f coordinates
476 * @param onCurve flag if this vertex is on the curve or defines a curved region of the shape around this vertex.
477 * @see <a href="#windingrules">see winding rules</a>
478 */
479 void addVertex(const Vec3f& v, bool onCurve) {
480 addVertex(Vertex(v, onCurve));
481 }
482
483 /**
484 * Add a 2D {@link Vertex} to the last open outline to the shape's tail.
485 *
486 * @param x the x coordinate
487 * @param y the y coordinate
488 * @param onCurve flag if this vertex is on the curve or defines a curved region of the shape around this vertex.
489 * @see <a href="#windingrules">see winding rules</a>
490 */
491 void addVertex(float x, float y, bool onCurve) {
492 addVertex(Vertex(x, y, onCurve));
493 }
494
495 /**
496 * Add a 2D {@link Vertex} to the last open outline to the shape's tail.
497 *
498 * @param v the Vec2f coordinates
499 * @param onCurve flag if this vertex is on the curve or defines a curved region of the shape around this vertex.
500 * @see <a href="#windingrules">see winding rules</a>
501 */
502 void addVertex(const Vec2f& v, bool onCurve) {
503 addVertex(Vertex(v, onCurve));
504 }
505
506 /**
507 * Add a 3D {@link Vertex} to the last open outline to the shape at {@code position}.
508 *
509 * @param position index within the last open outline, at which the vertex will be added
510 * @param x the x coordinate
511 * @param y the y coordniate
512 * @param z the z coordinate
513 * @param onCurve flag if this vertex is on the curve or defines a curved region of the shape around this vertex.
514 * @see <a href="#windingrules">see winding rules</a>
515 */
516 void addVertex(size_type position, float x, float y, float z, bool onCurve) {
517 addVertex(position, Vertex(x, y, z, onCurve));
518 }
519
520 /**
521 * Start a new position for the next line segment at given point x/y (P1).
522 *
523 * @param x point (P1)
524 * @param y point (P1)
525 * @param z point (P1)
526 * @see Path2F#moveTo(float, float)
527 * @see #addPath(com.jogamp.math.geom.plane.Path2F.Iterator, boolean)
528 * @see <a href="#windingrules">see winding rules</a>
529 */
530 void moveTo(float x, float y, float z) {
531 if ( 0 == lastOutline().vertexCount() ) {
532 addVertex(x, y, z, true);
533 } else {
534 closeLastOutline(false);
536 addVertex(x, y, z, true);
537 }
538 }
539
540 /**
541 * Add a line segment, intersecting the last point and the given point x/y (P1).
542 *
543 * @param x final point (P1)
544 * @param y final point (P1)
545 * @param z final point (P1)
546 * @see Path2F#lineTo(float, float)
547 * @see #addPath(com.jogamp.math.geom.plane.Path2F.Iterator, boolean)
548 * @see <a href="#windingrules">see winding rules</a>
549 */
550 void lineTo(float x, float y, float z) {
551 addVertex(x, y, z, true);
552 }
553
554 /**
555 * Add a quadratic curve segment, intersecting the last point and the second given point x2/y2 (P2).
556 *
557 * @param x1 quadratic parametric control point (P1)
558 * @param y1 quadratic parametric control point (P1)
559 * @param z1 quadratic parametric control point (P1)
560 * @param x2 final interpolated control point (P2)
561 * @param y2 final interpolated control point (P2)
562 * @param z2 quadratic parametric control point (P2)
563 * @see Path2F#quadTo(float, float, float, float)
564 * @see #addPath(com.jogamp.math.geom.plane.Path2F.Iterator, boolean)
565 * @see <a href="#windingrules">see winding rules</a>
566 */
567 void quadTo(float x1, float y1, float z1, float x2, float y2, float z2) {
568 addVertex(x1, y1, z1, false);
569 addVertex(x2, y2, z2, true);
570 }
571
572 /**
573 * Add a cubic Bézier curve segment, intersecting the last point and the second given point x3/y3 (P3).
574 *
575 * @param x1 Bézier control point (P1)
576 * @param y1 Bézier control point (P1)
577 * @param z1 Bézier control point (P1)
578 * @param x2 Bézier control point (P2)
579 * @param y2 Bézier control point (P2)
580 * @param z2 Bézier control point (P2)
581 * @param x3 final interpolated control point (P3)
582 * @param y3 final interpolated control point (P3)
583 * @param z3 final interpolated control point (P3)
584 * @see Path2F#cubicTo(float, float, float, float, float, float)
585 * @see #addPath(com.jogamp.math.geom.plane.Path2F.Iterator, boolean)
586 * @see <a href="#windingrules">see winding rules</a>
587 */
588 void cubicTo(float x1, float y1, float z1, float x2, float y2, float z2, float x3, float y3, float z3) {
589 addVertex(x1, y1, z1, false);
590 addVertex(x2, y2, z2, false);
591 addVertex(x3, y3, z3, true);
592 }
593
594 /**
595 * Closes the last outline in the shape.
596 * <p>
597 * Checks whether the last vertex equals to the first of the last outline.
598 * If not equal, it either appends a copy of the first vertex
599 * or prepends a copy of the last vertex, depending on <code>closeTail</code>.
600 * </p>
601 * @param closeTail if true, a copy of the first vertex will be appended,
602 * otherwise a copy of the last vertex will be prepended.
603 */
604 void closeLastOutline(bool closeTail) {
605 if( lastOutline().setClosed( closeTail ) ) {
607 }
608 }
609
610 /**
611 * Closes the current sub-path segment by drawing a straight line back to the coordinates of the last moveTo. If the path is already closed then this method has no effect.
612 * @see Path2F#closePath()
613 * @see #addPath(com.jogamp.math.geom.plane.Path2F.Iterator, boolean)
614 */
615 void closePath() {
616 if ( 0 < lastOutline().vertexCount() ) {
617 closeLastOutline(true);
619 }
620 }
621
622 VertexState outlineState() const noexcept { return m_outlineState; }
623
624 /**
625 * Claim this outline's vertices are all VertexState::quadratic_nurbs,
626 * hence no cubic transformations will be performed.
627 */
628 void setIsQuadraticNurbs() noexcept {
629 m_outlineState = VertexState::quadratic_nurbs;
630 // checkPossibleOverlaps = false;
631 }
632
633 /**
634 * Return a transformed instance with all {@link Outline}s are copied and transformed.
635 * <p>
636 * Note: Triangulated data is lost in returned instance!
637 * </p>
638 */
640 OutlineShape newOutlineShape;
641 size_type osize = m_outlines.size();
642 for(size_type i=0; i<osize; i++) {
643 newOutlineShape.addOutline( m_outlines[i].transform(t) );
644 }
645 return newOutlineShape;
646 }
647
648 /// Returns a copy of this instance with normal() and all outlines() vertices()'s z-axis sign-flipped,
649 /// used to generate a back-face from a front-face shape.
650 OutlineShape flipFace(float zoffset=0) const {
651 OutlineShape nshape = *this;
652 nshape.normal().z *= -1;
653 for(Outline& o : nshape.outlines()) {
654 for(Vertex& v : o.vertices()) {
655 v.coord().z = ( v.coord().z * -1.0f ) + zoffset;
656 }
657 }
658 return nshape;
659 }
660
661 /**
662 * Compare two outline shape's Bounding Box size.
663 * @return <0, 0, >0 if this this object is less than, equal to, or greater than the other object.
664 * @see AABBox3f::size()
665 */
666 int compareTo(const OutlineShape& other) const noexcept {
667 const float thisSize = bounds().size();
668 const float otherSize = other.bounds().size();
669 if( jau::equals2(thisSize, otherSize) ) {
670 return 0;
671 } else if(thisSize < otherSize){
672 return -1;
673 } else {
674 return 1;
675 }
676 }
677
678 /**
679 * @return true if {@code o} equals bounds and vertices in the same order
680 */
681 constexpr bool operator==(const OutlineShape& o) const noexcept {
682 if( this == &o) {
683 return true;
684 }
685 if(outlineState() != o.outlineState()) {
686 return false;
687 }
688 if(outlineCount() != o.outlineCount()) {
689 return false;
690 }
691 if( bounds() != o.bounds() ) {
692 return false;
693 }
694 for (size_type i=outlineCount(); i-- > 0;) {
695 if( outline(i) != o.outline(i) ) {
696 return false;
697 }
698 }
699 return true;
700 }
701
702 private:
703 //
704 // prost-processing
705 //
706
707 void subdivideTriangle(Outline& outline, const Vertex& a, Vertex& b, const Vertex& c, size_type index) {
708 Vec3f v1 = midpoint( a.coord(), b.coord() );
709 Vec3f v3 = midpoint( b.coord(), c.coord() );
710 Vec3f v2 = midpoint( v1, v3 );
711
712 // COLOR
713 // tmpC1.set(a.getColor()).add(b.getColor()).scale(0.5f);
714 // tmpC3.set(b.getColor()).add(b.getColor()).scale(0.5f);
715 // tmpC2.set(tmpC1).add(tmpC1).scale(0.5f);
716
717 //drop off-curve vertex to image on the curve
718 b.coord() = v2;
719 b.onCurve() = true;
720
721 outline.addVertex(index, Vertex(v1, false));
722 outline.addVertex(index+2, Vertex(v3, false));
723
724 m_addedVertexCount += 2;
725 }
726
727 /**
728 * Check overlaps between curved triangles
729 * first check if any vertex in triangle a is in triangle b
730 * second check if edges of triangle a intersect segments of triangle b
731 * if any of the two tests is true we divide current triangle
732 * and add the other to the list of overlaps
733 *
734 * Loop until overlap array is empty. (check only in first pass)
735 */
736 void checkOverlaps() {
737 VertexList overlaps(3);
738 const size_type count = outlineCount();
739 bool firstpass = true;
740 do {
741 for (size_type cc = 0; cc < count; ++cc) {
742 Outline& ol = outline(cc);
743 size_type vertexCount = ol.vertexCount();
744 for(size_type i=0; i < ol.vertexCount(); ++i) {
745 Vertex& currentVertex = ol.vertex(i);
746 if ( !currentVertex.onCurve()) {
747 const Vertex& nextV = ol.vertex((i+1)%vertexCount);
748 const Vertex& prevV = ol.vertex((i+vertexCount-1)%vertexCount);
749 Vertex* overlap;
750
751 // check for overlap even if already set for subdivision
752 // ensuring both triangular overlaps get divided
753 // for pref. only check in first pass
754 // second pass to clear the overlaps array(reduces precision errors)
755 if( firstpass ) {
756 overlap = checkTriOverlaps0(prevV, currentVertex, nextV);
757 } else {
758 overlap = nullptr;
759 }
760 if( overlap || jau::contains(overlaps, currentVertex) ) {
761 jau::eraseFirst(overlaps, currentVertex);
762
763 subdivideTriangle(ol, prevV, currentVertex, nextV, i);
764 i+=3;
765 vertexCount+=2;
766 m_addedVertexCount+=2;
767
768 if(overlap && !overlap->onCurve()) {
769 if(!jau::contains(overlaps, *overlap)) {
770 overlaps.push_back(*overlap);
771 }
772 }
773 }
774 }
775 }
776 }
777 firstpass = false;
778 } while( !overlaps.empty() );
779 }
780
781 Vertex* checkTriOverlaps0(const Vertex& a, const Vertex& b, const Vertex& c) {
782 size_type count = outlineCount();
783 for (size_type cc = 0; cc < count; ++cc) {
784 Outline& ol = outline(cc);
785 size_type vertexCount = ol.vertexCount();
786 for(size_type i=0; i < vertexCount; ++i) {
787 Vertex& currV = ol.vertex(i);
788 if( !currV.onCurve() && currV != a && currV != b && currV != c) {
789 Vertex& nextV = ol.vertex((i+1)%vertexCount);
790 Vertex& prevV = ol.vertex((i+vertexCount-1)%vertexCount);
791
792 //skip neighboring triangles
793 if(prevV != c && nextV != a) {
794 if( isInTriangle3(a.coord(), b.coord(), c.coord(),
795 currV.coord(), nextV.coord(), prevV.coord()) ) {
796 return &currV;
797 }
798 if(testTri2SegIntersection2D(a, b, c, prevV, currV) ||
799 testTri2SegIntersection2D(a, b, c, currV, nextV) ||
800 testTri2SegIntersection2D(a, b, c, prevV, nextV) ) {
801 return &currV;
802 }
803 }
804 }
805 }
806 }
807 return nullptr;
808 }
809
810 void cleanupOutlines() {
811 const bool transformOutlines2Quadratic = VertexState::quadratic_nurbs != m_outlineState;
812 size_type count = outlineCount();
813 for (size_type cc = 0; cc < count; ++cc) {
814 Outline& ol = outline(cc);
815 size_type vertexCount = ol.vertexCount();
816
817 if( transformOutlines2Quadratic ) {
818 for(size_type i=0; i < vertexCount; ++i) {
819 Vertex& currentVertex = ol.vertex(i);
820 size_type j = (i+1)%vertexCount;
821 Vertex& nextVertex = ol.vertex(j);
822 if ( !currentVertex.onCurve() && !nextVertex.onCurve() ) {
823 Vec3f mp = midpoint(currentVertex.coord(), nextVertex.coord());
824 // System.err.println("XXX: Cubic: "+i+": "+currentVertex+", "+j+": "+nextVertex);
825 Vertex v(mp, true);
826 // COLOR: tmpC1.set(currentVertex.getColor()).add(nextVertex.getColor()).scale(0.5f)
827 ++i;
828 ++vertexCount;
829 ++m_addedVertexCount;
830 ol.addVertex(i, v);
831 }
832 }
833 }
834 if( 0 == vertexCount ) {
835 jau::eraseFirst(m_outlines, ol); // empty
836 --cc;
837 --count;
838 } else if( 0 < vertexCount &&
839 ol.vertex(0).coord() == ol.lastVertex().coord() )
840 {
841 ol.removeVertex(vertexCount-1); // closing vertex
842 }
843 }
844 m_outlineState = VertexState::quadratic_nurbs;
845 checkOverlaps();
846 }
847
848 uint32_t generateVertexIds() {
849 uint32_t maxVertexId = 0;
850 for(size_type i=0; i<m_outlines.size(); ++i) {
852 for(auto & vertice : vertices) {
853 vertice.id() = maxVertexId++;
854 }
855 }
856 return maxVertexId;
857 }
858
859 public:
860 /**
861 * Return list of concatenated vertices associated with all
862 * {@code Outline}s of this object.
863 * <p>
864 * Vertices are cached until marked dirty.
865 * </p>
866 * <p>
867 * Should always be called <i>after</i> {@link #getTriangles(VerticesState)},
868 * since the latter will mark all cached vertices dirty!
869 * </p>
870 */
872 bool updated = false;
873 if( is_set(m_dirtyBits, DirtyBits::vertices ) ) {
874 m_vertices.clear();
875 for(const Outline& ol : m_outlines) {
876 const VertexList& v = ol.vertices();
877 m_vertices.push_back(v.begin(), v.end());
878 }
879 m_dirtyBits &= ~DirtyBits::vertices;
880 updated = true;
881 }
883 jau::PLAIN_PRINT(true, "OutlineShape.getVertices().X: %u, updated %d", m_vertices.size(), updated);
884 if( updated ) {
885 size_type i=0;
886 for(Vertex& v : m_vertices) {
887 jau::PLAIN_PRINT(false, "- [%u]: %s", i++, v.toString().c_str());
888 }
889 }
890 }
891 return m_vertices;
892 }
893
894 private:
895 struct SizeDescending {
896 bool operator()(Outline& a, Outline& b) const {
897 return a.compareTo(b) >= 1;
898 }
899 } sizeDescending;
900
901 /**
902 * Sort the outlines from large
903 * to small depending on the AABox
904 */
905 void sortOutlines() {
906 std::sort(m_outlines.begin(), m_outlines.end(), sizeDescending);
907 }
908
909 void triangulateImpl() {
910 if( 0 < m_outlines.size() ) {
911 sortOutlines();
912 generateVertexIds();
913
914 m_triangles.clear();
915 tess::CDTriangulator2D triangulator2d;
916 triangulator2d.setComplexShape( isComplex() );
917 for(Outline& ol : m_outlines) {
918 triangulator2d.addCurve(m_triangles, ol, m_sharpness);
919 }
920 triangulator2d.generate(m_triangles);
921 m_addedVertexCount += triangulator2d.getAddedVerticeCount();
922 triangulator2d.reset();
923 }
924 }
925
926 public:
927 /**
928 * Triangulate the {@link OutlineShape} generating a list of triangles,
929 * while {@link #transformOutlines(VerticesState)} beforehand.
930 *
931 * Triangles are cached until marked dirty.
932 *
933 * @return an arraylist of triangles representing the filled region
934 * which is produced by the combination of the outlines
935 */
937 bool updated = false;
938 if(destinationType != VertexState::quadratic_nurbs) {
939 throw jau::IllegalStateError("destinationType "+to_string(destinationType)+" not supported (currently "+to_string(m_outlineState)+")", E_FILE_LINE);
940 }
941 if( is_set(m_dirtyBits, DirtyBits::triangles ) ) {
942 cleanupOutlines();
943 triangulateImpl();
944 updated = true;
945 m_dirtyBits |= DirtyBits::vertices;
946 m_dirtyBits &= ~DirtyBits::triangles;
947 } else {
948 updated = false;
949 }
951 jau::PLAIN_PRINT(true, "OutlineShape.getTriangles().X: %u, updated %d", m_triangles.size(), updated);
952 if( updated ) {
953 size_type i=0;
954 for(TriangleRef& t : m_triangles) {
955 jau::PLAIN_PRINT(false, "- [%u]: %s", i++, t->toString().c_str());
956 }
957 }
958 }
959 return m_triangles;
960 }
961
962 public:
963
964 std::string toString() const noexcept {
965 std::string r("OutlineShape[");
966 r.append("dirty ").append(to_string(m_dirtyBits))
967 .append(", outlines ").append(std::to_string(outlineCount()))
968 .append(", vertices ").append(std::to_string(vertexCount()))
969 .append("]");
970 return r;
971 }
972
973 private:
974
975 };
976
977
978 /**@}*/
979
980} // namespace gamp::graph
981
982#endif /* JAU_GAMP_GRAPH_OUTLINESHAPE_HPP_ */
#define E_FILE_LINE
static bool DEBUG_MODE
Definition Graph.hpp:24
constexpr size_type addedVertexCount() const noexcept
Return the number of newly added vertices during getTriangles(VerticesState) while transforming the o...
size_type outlineCount() const noexcept
Returns the number of Outlines.
JAU_MAKE_BITFIELD_ENUM_STRING_MEMBER(DirtyBits, bounds, vertices, triangles, convex, convexOverride)
void addVertex(size_type position, float x, float y, float z, bool onCurve)
Add a 3D Vertex to the last open outline to the shape at position.
void addVertex(float x, float y, float z, bool onCurve)
Add a 3D Vertex to the last open outline to the shape's tail.
const AABBox3f & bounds() const noexcept
void setSharpness(float s) noexcept
Sets sharpness, defaults to DEFAULT_SHARPNESS.
void clearCache() noexcept
Clears cached triangulated data, i.e.
static constexpr size_type max_elements
byte-size int32_t limit: 536'870'911 (FIXME: Adjust to actual type, i.e. Vertex = 2x Vec3f?...
VertexState outlineState() const noexcept
void setOverrideConvex(bool convex) noexcept
Overrides isComplex() using the given value instead of computing via Outline#isComplex().
bool isComplex() const noexcept
Returns cached or computed result if at least one polyline outline(size_type) is a complex shape,...
int compareTo(const OutlineShape &other) const noexcept
Compare two outline shape's Bounding Box size.
const VertexList & getVertices()
Return list of concatenated vertices associated with all Outlines of this object.
OutlineList & outlines() noexcept
Winding windingOfLastOutline() const noexcept
Compute the Winding of the getLastOutline() using the VectorUtil#area(ArrayList) function over all of...
@ triangles
<Modified shape, requires to update the vertices and triangles, here: vertices
@ convexOverride
<Modified shape, requires to update the convex determination
@ convex
<Modified shape, requires to update the vertices and triangles, here: triangulation.
JAU_MAKE_ENUM_STRING_MEMBER(VertexState, quadratic_nurbs)
void cubicTo(float x1, float y1, float z1, float x2, float y2, float z2, float x3, float y3, float z3)
Add a cubic Bézier curve segment, intersecting the last point and the second given point x3/y3 (P3).
void removeOutline(size_type position)
Removes the Outline element at the given position.
void addVertex(float x, float y, bool onCurve)
Add a 2D Vertex to the last open outline to the shape's tail.
const Outline & lastOutline() const noexcept
Get the last added outline to the list of outlines that define the shape.
const OutlineList & outlines() const noexcept
const TriangleRefList & getTriangles(VertexState destinationType=VertexState::quadratic_nurbs)
Triangulate the OutlineShape generating a list of triangles, while transformOutlines(VerticesState) b...
void addVertex(const Vec2f &v, bool onCurve)
Add a 2D Vertex to the last open outline to the shape's tail.
size_type vertexCount() const noexcept
Returns the total vertex number of all Outlines.
const Outline & outline(size_type i) const noexcept
std::string toString() const noexcept
void addVertex(const Vec3f &v, bool onCurve)
Add a 3D Vertex to the last open outline to the shape's tail.
constexpr Vec3f & normal() noexcept
Writing the normal vector, optionally used by tesselator to add (interleaved) normals.
void closeLastOutline(bool closeTail)
Closes the last outline in the shape.
OutlineShape(size_type capacity, size_type outlineVertCapacity)
constexpr bool verticesDirty() const noexcept
void addOutline(size_type position, const Outline &outline)
Insert the Outline element at the given position.
void setWindingOfLastOutline(Winding enforced)
Sets the enforced Winding of the getLastOutline().
constexpr void reserve(size_type newCapacity)
void setIsQuadraticNurbs() noexcept
Claim this outline's vertices are all VertexState::quadratic_nurbs, hence no cubic transformations wi...
constexpr bool operator==(const OutlineShape &o) const noexcept
void clear()
Clears all data and reset all states as if this instance was newly created.
bool empty() const noexcept
void quadTo(float x1, float y1, float z1, float x2, float y2, float z2)
Add a quadratic curve segment, intersecting the last point and the second given point x2/y2 (P2).
void markClean(DirtyBits flags) noexcept
void addOutlineShape(const OutlineShape &outlineShape)
Insert the OutlineShape elements of type Outline, .
constexpr const Vec3f & normal() const noexcept
Normal vector, optionally used by tesselator to add (interleaved) normals.
void moveTo(float x, float y, float z)
Start a new position for the next line segment at given point x/y (P1).
void clearOverrideConvex() noexcept
Clears the isComplex() override done by setOverrideConvex(boolean).
void closePath()
Closes the current sub-path segment by drawing a straight line back to the coordinates of the last mo...
void addVertex(size_type position, const Vertex &v)
Adds a vertex to the last open outline to the shape at position @endiliteral.
static constexpr float DEFAULT_SHARPNESS
Initial sharpness() value, which can be modified via setSharpness(float).
constexpr float sharpness() const noexcept
Sharpness value, defaults to DEFAULT_SHARPNESS.
constexpr bool trianglesDirty() const noexcept
Outline & lastOutline() noexcept
Get the last added outline to the list of outlines that define the shape.
OutlineShape transform(const AffineTransform &t) const
Return a transformed instance with all Outlines are copied and transformed.
Outline & outline(size_type i) noexcept
OutlineShape flipFace(float zoffset=0) const
Returns a copy of this instance with normal() and all outlines() vertices()'s z-axis sign-flipped,...
void lineTo(float x, float y, float z)
Add a line segment, intersecting the last point and the given point x/y (P1).
void addEmptyOutline()
Add a new empty Outline to the end of this shape's outline list.
void setOutline(size_type position, const Outline &outline)
Replaces the Outline element at the given position.
void addOutline(const Outline &outline)
Appends the Outline element to the end, ensuring a clean tail.
void addVertex(const Vertex &v)
Adds a vertex to the last open outline to the shape's tail.
constexpr DirtyBits dirtyBits() const noexcept
Define a single continuous stroke by control vertices.
Definition Outline.hpp:51
void addVertex(const Vertex &vertex)
Appends a vertex to the outline loop/strip.
Definition Outline.hpp:235
constexpr const VertexList & vertices() const noexcept
Definition Outline.hpp:91
bool isComplex() const noexcept
Returns cached or computed result if whether this Outlines polyline is a complex shape.
Definition Outline.hpp:203
constexpr bool empty() const noexcept
Definition Outline.hpp:87
void setWinding(Winding enforce)
Sets Winding to this outline.
Definition Outline.hpp:180
int compareTo(const Outline &other) const noexcept
Compare two outline's Bounding Box size.
Definition Outline.hpp:279
Winding getWinding() const noexcept
Returns the cached or computed winding of this Outlines polyline using VectorUtil#area(ArrayList).
Definition Outline.hpp:159
constexpr const Vec3f & coord() const noexcept
Definition PrimTypes.hpp:93
constexpr bool onCurve() const noexcept
Definition PrimTypes.hpp:99
constexpr iterator end() noexcept
Definition darray.hpp:828
constexpr iterator begin() noexcept
Definition darray.hpp:824
value_type z
Definition vec3f.hpp:71
Axis Aligned Bounding Box.
Definition aabbox3f.hpp:43
constexpr AABBox3f & reset() noexcept
Reset this box to the inverse low/high, allowing the next resize(float, float, float) command to hit.
Definition aabbox3f.hpp:91
constexpr float size() const noexcept
Get the size of this aabbox3f where the size is represented by the length of the vector between low a...
Definition aabbox3f.hpp:112
AABBox3f & resize(const AABBox3f &o) noexcept
Resize the aabbox3f to encapsulate another AABox.
Definition aabbox3f.hpp:228
Represents a affine 2x3 transformation matrix in column major order (memory layout).
constexpr bool contains(InputArray &array, const T &value) noexcept
Return true if value is contained in array.
constexpr bool eraseFirst(InputArray &array, const T &value)
constexpr bool is_set(const E mask, const E bits) noexcept
std::enable_if_t< std::is_floating_point_v< T >, bool > constexpr equals2(const T &a, const T &b, const T &epsilon=std::numeric_limits< T >::epsilon()) noexcept
Returns true if both values are equal, i.e.
jau::darray< Outline, jau::nsize_t > OutlineList
Definition Outline.hpp:314
jau::darray< Vertex, uint32_t > VertexList
std::shared_ptr< Triangle > TriangleRef
jau::darray< TriangleRef, uint32_t > TriangleRefList
constexpr Vec3f midpoint(const Vec3f &a, const Vec3f &b) noexcept
Definition geom3f.hpp:126
Vector2F< float > Vec2f
Definition vec2f.hpp:416
Vector3F< float > Vec3f
Definition vec3f.hpp:436
std::string to_string(const math_error_t v) noexcept
Returns std::string representation of math_error_t.
constexpr bool testTri2SegIntersection2D(const Vec3f &a, const Vec3f &b, const Vec3f &c, const Vec3f &d, const Vec3f &e)
Check if a segment intersects with a triangle using FloatUtil#EPSILON w/o considering collinear-case.
Definition geom3f2D.hpp:220
constexpr bool isInTriangle3(const Vec3f &a, const Vec3f &b, const Vec3f &c, const Vec3f &p1, const Vec3f &p2, const Vec3f &p3) noexcept
Check if one of three vertices are in triangle using barycentric coordinates computation.
Definition geom3f.hpp:140
void PLAIN_PRINT(const bool printPrefix, const char *format,...) noexcept
Use for unconditional plain messages, prefix '[elapsed_time] ' if printPrefix == true.
Definition debug.cpp:258