This repository has been archived on 2023-10-09. You can view files and clone it, but cannot push or open issues or pull requests.
Files
blender-archive/source/blender/blenlib/BLI_delaunay_2d.h
Clément Foucault a2c1c368af BLI: Refactor vector types & functions to use templates
This patch implements the vector types (i.e:float2) by making heavy
usage of templating. All vector functions are now outside of the vector
classes (inside the blender::math namespace) and are not vector size
dependent for the most part.

In the ongoing effort to make shaders less GL centric, we are aiming
to share more code between GLSL and C++ to avoid code duplication.

Motivations:
- We are aiming to share UBO and SSBO structures between GLSL and C++.
  This means we will use many of the existing vector types and others we
  currently don't have (uintX, intX). All these variations were asking
  for many more code duplication.
- Deduplicate existing code which is duplicated for each vector size.
- We also want to share small functions. Which means that vector functions
  should be static and not in the class namespace.
- Reduce friction to use these types in new projects due to their
  incompleteness.
- The current state of the BLI_(float|double|mpq)(2|3|4).hh is a bit of a
  let down. Most clases are incomplete, out of sync with each others with
  different codestyles, and some functions that should be static are not
  (i.e: float3::reflect()).

Upsides:
- Still support .x, .y, .z, .w for readability.
- Compact, readable and easilly extendable.
- All of the vector functions are available for all the vectors types and
  can be restricted to certain types. Also template specialization let us
  define exception for special class (like mpq).
- With optimization ON, the compiler unroll the loops and performance is
  the same.

Downsides:
- Might impact debugability. Though I would arge that the bugs are rarelly
  caused by the vector class itself (since the operations are quite trivial)
  but by the type conversions.
- Might impact compile time. I did not saw a significant impact since the
  usage is not really widespread.
- Functions needs to be rewritten to support arbitrary vector length. For
  instance, one can't call len_squared_v3v3 in math::length_squared() and
  call it a day.
- Type cast does not work with the template version of the math:: vector
  functions. Meaning you need to manually cast float * and (float *)[3] to
  float3 for the function calls.
  i.e: math::distance_squared(float3(nearest.co), positions[i]);
- Some parts might loose in readability:
  float3::dot(v1.normalized(), v2.normalized())
  becoming
  math::dot(math::normalize(v1), math::normalize(v2))
  But I propose, when appropriate, to use
  using namespace blender::math; on function local or file scope to
  increase readability. dot(normalize(v1), normalize(v2))

Consideration:
- Include back .length() method. It is quite handy and is more C++
  oriented.
- I considered the GLM library as a candidate for replacement.
  It felt like too much for what we need and would be difficult to
  extend / modify to our needs.
- I used Macros to reduce code in operators declaration and potential
  copy paste bugs. This could reduce debugability and could be reverted.
- This touches delaunay_2d.cc and the intersection code. I would like to
  know @Howard Trickey (howardt) opinion on the matter.
- The noexcept on the copy constructor of mpq(2|3) is being removed.
  But according to @Jacques Lucke (JacquesLucke) it is not a real problem
  for now.

I would like to give a huge thanks to @Jacques Lucke (JacquesLucke) who
helped during this and pushed me to reduce the duplication further.

Reviewed By: brecht, sergey, JacquesLucke

Differential Revision: http://developer.blender.org/D13791
2022-01-12 12:19:39 +01:00

282 lines
10 KiB
C++

/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#pragma once
/** \file
* \ingroup bli
*
* This header file contains both a C interface and a C++ interface
* to the 2D Constrained Delaunay Triangulation library routine.
*/
#ifdef __cplusplus
extern "C" {
#endif
/**
* Interface for Constrained Delaunay Triangulation (CDT) in 2D.
*
* The input is a set of vertices, edges between those vertices,
* and faces using those vertices.
* Those inputs are called "constraints". The output must contain
* those constraints, or at least edges, points, and vertices that
* may be pieced together to form the constraints. Part of the
* work of doing the CDT is to detect intersections and mergers
* among the input elements, so these routines are also useful
* for doing 2D intersection.
*
* The output is a triangulation of the plane that includes the
* constraints in the above sense, and also satisfies the
* "Delaunay condition" as modified to take into account that
* the constraints must be there: for every non-constrained edge
* in the output, there is a circle through the endpoints that
* does not contain any of the vertices directly connected to
* those endpoints. What this means in practice is that as
* much as possible the triangles look "nice" -- not too long
* and skinny.
*
* Optionally, the output can be a subset of the triangulation
* (but still containing all of the constraints), to get the
* effect of 2D intersection.
*
* The underlying method is incremental, but we need to know
* beforehand a bounding box for all of the constraints.
* This code can be extended in the future to allow for
* deletion of constraints, if there is a use in Blender
* for dynamically maintaining a triangulation.
*/
/**
* Input to Constrained Delaunay Triangulation.
* There are verts_len vertices, whose coordinates
* are given by vert_coords. For the rest of the input,
* vertices are referred to by indices into that array.
* Edges and Faces are optional. If provided, they will
* appear in the output triangulation ("constraints").
* One can provide faces and not edges -- the edges
* implied by the faces will be inferred.
*
* The edges are given by pairs of vertex indices.
* The faces are given in a triple `(faces, faces_start_table, faces_len_table)`
* to represent a list-of-lists as follows:
* the vertex indices for a counterclockwise traversal of
* face number `i` starts at `faces_start_table[i]` and has `faces_len_table[i]`
* elements.
*
* The edges implied by the faces are automatically added
* and need not be put in the edges array, which is intended
* as a way to specify edges that are not part of any face.
*
* Some notes about some special cases and how they are handled:
* - Input faces can have any number of vertices greater than 2. Depending
* on the output option, ngons may be triangulated or they may remain
* as ngons.
* - Input faces may have repeated vertices. Output faces will not,
* except when the CDT_CONSTRAINTS output option is used.
* - Input faces may have edges that self-intersect, but currently the labeling
* of which output faces have which input faces may not be done correctly,
* since the labeling relies on the inside being on the left of edges
* as one traverses the face. Output faces will not self-intersect.
* - Input edges, including those implied by the input faces, may have
* zero-length or near-zero-length edges (nearness as determined by epsilon),
* but those edges will not be in the output.
* - Input edges (including face edges) can overlap or nearly overlap each other.
* The output edges will not overlap, but instead be divided into as many
* edges as necessary to represent each overlap regime.
* - Input vertices may be coincide with, or nearly coincide with (as determined
* by epsilon) other input vertices. Only one representative will survive
* in the output. If an input vertex is within epsilon of an edge (including
* an added triangulation edge), it will be snapped to that edge, so the
* output coordinates may not exactly match the input coordinates in all cases.
* - Wire edges (those not part of faces) and isolated vertices are allowed in
* the input. If they are inside faces, they will be incorporated into the
* triangulation of those faces.
*
* Epsilon is used for "is it near enough" distance calculations.
* If zero is supplied for epsilon, an internal value of 1e-8 used
* instead, since this code will not work correctly if it is not allowed
* to merge "too near" vertices.
*
* Normally the output will contain mappings from outputs to inputs.
* If this is not needed, set need_ids to false and the execution may be much
* faster in some circumstances.
*/
typedef struct CDT_input {
int verts_len;
int edges_len;
int faces_len;
float (*vert_coords)[2];
int (*edges)[2];
int *faces;
int *faces_start_table;
int *faces_len_table;
float epsilon;
bool need_ids;
} CDT_input;
/**
* A representation of the triangulation for output.
* See #CDT_input for the representation of the output
* vertices, edges, and faces, all represented in
* a similar way to the input.
*
* The output may have merged some input vertices together,
* if they were closer than some epsilon distance.
* The output edges may be overlapping sub-segments of some
* input edges; or they may be new edges for the triangulation.
* The output faces may be pieces of some input faces, or they
* may be new.
*
* In the same way that faces lists-of-lists were represented by
* a run-together array and a "start" and "len" extra array,
* similar triples are used to represent the output to input
* mapping of vertices, edges, and faces.
* These are only set if need_ids is true in the input.
*
* Those triples are:
* - verts_orig, verts_orig_start_table, verts_orig_len_table
* - edges_orig, edges_orig_start_table, edges_orig_len_table
* - faces_orig, faces_orig_start_table, faces_orig_len_table
*
* For edges, the edges_orig triple can also say which original face
* edge is part of a given output edge. See the comment below
* on the C++ interface for how to decode the entries in the edges_orig
* table.
*/
typedef struct CDT_result {
int verts_len;
int edges_len;
int faces_len;
int face_edge_offset;
float (*vert_coords)[2];
int (*edges)[2];
int *faces;
int *faces_start_table;
int *faces_len_table;
int *verts_orig;
int *verts_orig_start_table;
int *verts_orig_len_table;
int *edges_orig;
int *edges_orig_start_table;
int *edges_orig_len_table;
int *faces_orig;
int *faces_orig_start_table;
int *faces_orig_len_table;
} CDT_result;
/** What triangles and edges of CDT are desired when getting output? */
typedef enum CDT_output_type {
/** All triangles, outer boundary is convex hull. */
CDT_FULL,
/** All triangles fully enclosed by constraint edges or faces. */
CDT_INSIDE,
/** Like previous, but detect holes and omit those from output. */
CDT_INSIDE_WITH_HOLES,
/** Only point, edge, and face constraints, and their intersections. */
CDT_CONSTRAINTS,
/**
* Like CDT_CONSTRAINTS, but keep enough
* edges so that any output faces that came from input faces can be made as valid
* #BMesh faces in Blender: that is,
* no vertex appears more than once and no isolated holes in faces.
*/
CDT_CONSTRAINTS_VALID_BMESH,
/** Like previous, but detect holes and omit those from output. */
CDT_CONSTRAINTS_VALID_BMESH_WITH_HOLES,
} CDT_output_type;
/**
* API interface to CDT.
* This returns a pointer to an allocated CDT_result.
* When the caller is finished with it, the caller
* should use #BLI_delaunay_2d_cdt_free() to free it.
*/
CDT_result *BLI_delaunay_2d_cdt_calc(const CDT_input *input, const CDT_output_type output_type);
void BLI_delaunay_2d_cdt_free(CDT_result *result);
#ifdef __cplusplus
}
/* C++ Interface. */
# include "BLI_array.hh"
# include "BLI_math_mpq.hh"
# include "BLI_math_vec_mpq_types.hh"
# include "BLI_math_vec_types.hh"
# include "BLI_vector.hh"
namespace blender::meshintersect {
/** #vec2<Arith_t> is a 2d vector with #Arith_t as the type for coordinates. */
template<typename Arith_t> struct vec2_impl;
template<> struct vec2_impl<double> {
typedef double2 type;
};
# ifdef WITH_GMP
template<> struct vec2_impl<mpq_class> {
typedef mpq2 type;
};
# endif
template<typename Arith_t> using vec2 = typename vec2_impl<Arith_t>::type;
template<typename Arith_t> class CDT_input {
public:
Array<vec2<Arith_t>> vert;
Array<std::pair<int, int>> edge;
Array<Vector<int>> face;
Arith_t epsilon{0};
bool need_ids{true};
};
template<typename Arith_t> class CDT_result {
public:
Array<vec2<Arith_t>> vert;
Array<std::pair<int, int>> edge;
Array<Vector<int>> face;
/* The orig vectors are only populated if the need_ids input field is true. */
/** For each output vert, which input verts correspond to it? */
Array<Vector<int>> vert_orig;
/**
* For each output edge, which input edges does it overlap?
* The input edge ids are encoded as follows:
* if the value is less than face_edge_offset, then it is
* an index into the input edge[] array.
* else let (a, b) = the quotient and remainder of dividing
* the edge index by face_edge_offset; "a" will be the input face + 1,
* and "b" will be a position within that face.
*/
Array<Vector<int>> edge_orig;
/** For each output face, which original faces does it overlap? */
Array<Vector<int>> face_orig;
/** Used to encode edge_orig (see above). */
int face_edge_offset;
};
CDT_result<double> delaunay_2d_calc(const CDT_input<double> &input, CDT_output_type output_type);
# ifdef WITH_GMP
CDT_result<mpq_class> delaunay_2d_calc(const CDT_input<mpq_class> &input,
CDT_output_type output_type);
# endif
} /* namespace blender::meshintersect */
#endif /* __cplusplus */