Added a mesh_inset Blenlib function.

Based on python code from Henrik Dick, this libray function
calculates a Straight Skeleton, and hence, deals properly
with cases where the advancing inset geometry would overlap
or pass through opposite edges. It still needs work but the
basic tests pass.
This function will be used for edge and face bevels but is
not yet hooked up for that.
This commit is contained in:
2022-09-08 10:04:20 -04:00
parent 81341d1e94
commit 3b2bc5d146
4 changed files with 3452 additions and 0 deletions

View File

@@ -0,0 +1,48 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bli
*
* This header file contains a C++ interface to a 3D mesh inset algorithm
* which is based on a 2D Straight Skeleton construction.
*/
#include "BLI_array.hh"
#include "BLI_math_vec_types.hh"
#include "BLI_span.hh"
#include "BLI_vector.hh"
namespace blender::meshinset {
class MeshInset_Input {
public:
/** The vertices. Can be a superset of the needed vertices. */
Span<float3> vert;
/** The faces, each a CCW ordering of vertex indices. */
Span<Vector<int>> face;
/** The contours to inset; ints are vert indices; contour is on left side of implied edges. */
Span<Vector<int>> contour;
float inset_amount;
bool need_ids;
};
class MeshInset_Result {
public:
/** The output vertices. A subset (perhaps) of input vertices, plus some new ones. */
Array<float3> vert;
/** The output faces, each a CCW ordering of the output vertices. */
Array<Vector<int>> face;
/** The output contours -- where the input contours ended up. */
Array<Vector<int>> contour;
/** Maps output vertex indices to input vertex indices, -1 if there is none. */
Array<int> orig_vert;
/** Maps output faces tot input faces that they were part of. */
Array<int> orig_face;
};
MeshInset_Result mesh_inset_calc(const MeshInset_Input &input);
} // namespace blender::meshinset

View File

@@ -107,6 +107,7 @@ set(SRC
intern/math_vector_inline.c
intern/memory_utils.c
intern/mesh_boolean.cc
intern/mesh_inset.cc
intern/mesh_intersect.cc
intern/noise.c
intern/noise.cc
@@ -275,6 +276,7 @@ set(SRC
BLI_memory_utils.hh
BLI_mempool.h
BLI_mesh_boolean.hh
BLI_mesh_inset.hh
BLI_mesh_intersect.hh
BLI_mmap.h
BLI_multi_value_map.hh
@@ -473,6 +475,7 @@ if(WITH_GTESTS)
tests/BLI_memiter_test.cc
tests/BLI_memory_utils_test.cc
tests/BLI_mesh_boolean_test.cc
tests/BLI_mesh_inset_test.cc
tests/BLI_mesh_intersect_test.cc
tests/BLI_multi_value_map_test.cc
tests/BLI_path_util_test.cc

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,343 @@
/* SPDX-License-Identifier: Apache-2.0 */
#include "testing/testing.h"
#include <fstream>
#include <iostream>
#include <sstream>
#include "BLI_array.hh"
#include "BLI_mesh_inset.hh"
#include "BLI_vector.hh"
namespace blender::meshinset {
namespace test {
class SpecArrays {
public:
Array<float3> vert;
Array<Vector<int>> face;
Array<Vector<int>> contour;
};
/* The spec should have the form:
* #verts #faces #contours
* <float> <float> <float> [#verts lines]
* <int> <int> ... <int> [#faces lines]
* <int> <int> ... <int> [#contours lines]
*/
static SpecArrays fill_input_from_string(const char *spec)
{
SpecArrays ans;
std::istringstream ss(spec);
std::string line;
getline(ss, line);
std::istringstream hdrss(line);
int nverts, nfaces, ncontours;
hdrss >> nverts >> nfaces >> ncontours;
if (nverts == 0) {
return SpecArrays();
}
ans.vert = Array<float3>(nverts);
ans.face = Array<Vector<int>>(nfaces);
ans.contour = Array<Vector<int>>(ncontours);
int i = 0;
while (i < nverts && getline(ss, line)) {
std::istringstream iss(line);
float x, y, z;
iss >> x >> y >> z;
ans.vert[i] = float3(x, y, z);
i++;
}
i = 0;
while (i < nfaces && getline(ss, line)) {
std::istringstream fss(line);
int v;
while (fss >> v) {
ans.face[i].append(v);
}
i++;
}
i = 0;
while (i < ncontours && getline(ss, line)) {
std::istringstream css(line);
int v;
while (css >> v) {
ans.contour[i].append(v);
}
i++;
}
return ans;
}
class InputHolder {
SpecArrays spec_arrays_;
public:
MeshInset_Input input;
InputHolder(const char *spec, float amount)
{
spec_arrays_ = fill_input_from_string(spec);
input.vert = spec_arrays_.vert.as_span();
input.face = spec_arrays_.face.as_span();
input.contour = spec_arrays_.contour.as_span();
input.inset_amount = amount;
input.need_ids = false;
}
};
TEST(mesh_inset, Tri)
{
const char *spec = R"(3 1 1
0.0 0.0 0.0
1.0 0.0 0.0
0.5 0.5 0.0
0 1 2
0 1 2
)";
InputHolder in1(spec, 0.1);
MeshInset_Result out1 = mesh_inset_calc(in1.input);
EXPECT_EQ(out1.vert.size(), 6);
EXPECT_EQ(out1.face.size(), 4);
InputHolder in2(spec, 0.3);
MeshInset_Result out2 = mesh_inset_calc(in2.input);
EXPECT_EQ(out2.vert.size(), 4);
EXPECT_EQ(out2.face.size(), 3);
}
/* An asymmetrical quadrilateral. */
TEST(mesh_inset, Quad)
{
const char *spec = R"(4 1 1
-1.0 -1.0 0.0
1.1 -1.0 0.0
0.9 0.9 0.0
-0.5 1.0 0.0
0 1 2 3
0 1 2 3
)";
InputHolder in1(spec, 0.3);
MeshInset_Result out1 = mesh_inset_calc(in1.input);
EXPECT_EQ(out1.vert.size(), 8);
EXPECT_EQ(out1.face.size(), 5);
InputHolder in2(spec, .85);
MeshInset_Result out2 = mesh_inset_calc(in2.input);
EXPECT_EQ(out2.vert.size(), 8);
EXPECT_EQ(out2.face.size(), 5);
InputHolder in3(spec, .88);
MeshInset_Result out3 = mesh_inset_calc(in3.input);
EXPECT_EQ(out3.vert.size(), 6);
EXPECT_EQ(out3.face.size(), 4);
}
TEST(mesh_inset, Square)
{
const char *spec = R"(4 1 1
0.0 0.0 0.0
1.0 0.0 0.0
1.0 1.0 0.0
0.0 1.0 0.0
0 1 2 3
0 1 2 3
)";
InputHolder in1(spec, 0.3);
MeshInset_Result out1 = mesh_inset_calc(in1.input);
EXPECT_EQ(out1.vert.size(), 8);
EXPECT_EQ(out1.face.size(), 5);
InputHolder in2(spec, 1.0);
MeshInset_Result out2 = mesh_inset_calc(in2.input);
/* Note: current code wants all 3-valence vertices in
* straight skeleton, so the center doesn't collapse to
* a single vertex, but rather two vertices with a zero
* length edge between them. */
EXPECT_EQ(out2.vert.size(), 6);
EXPECT_EQ(out2.face.size(), 4);
}
TEST(mesh_inset, Pentagon)
{
const char *spec = R"(5 1 1
0.0 0.0 0.0
1.0 0.0 0.0
1.0 1.0 0.0
0.5 1.5 0.0
0.0 1.0 0.0
0 1 2 3 4
0 1 2 3 4
)";
InputHolder in1(spec, 0.2);
MeshInset_Result out1 = mesh_inset_calc(in1.input);
EXPECT_EQ(out1.vert.size(), 10);
EXPECT_EQ(out1.face.size(), 6);
InputHolder in2(spec, 1.0);
MeshInset_Result out2 = mesh_inset_calc(in2.input);
/* Because code wants all valence-3 vertices in the skeleton,
* there is a zero-length edge in this output. */
EXPECT_EQ(out2.vert.size(), 8);
EXPECT_EQ(out2.face.size(), 5);
}
TEST(mesh_inset, Hexagon)
{
const char *spec = R"(6 1 1
0.0 1.0 0.0
0.125 0.0 0.0
0.625 -0.75 0.0
1.5 -1.0 0.0
2.875 0.0 0.0
3.0 1.0 0.0
0 1 2 3 4 5
0 1 2 3 4 5
)";
InputHolder in1(spec, 0.4);
MeshInset_Result out1 = mesh_inset_calc(in1.input);
EXPECT_EQ(out1.vert.size(), 12);
EXPECT_EQ(out1.face.size(), 7);
InputHolder in2(spec, 0.67);
MeshInset_Result out2 = mesh_inset_calc(in2.input);
EXPECT_EQ(out2.vert.size(), 12);
EXPECT_EQ(out2.face.size(), 7);
InputHolder in3(spec, 0.85);
MeshInset_Result out3 = mesh_inset_calc(in3.input);
EXPECT_EQ(out3.vert.size(), 12);
EXPECT_EQ(out3.face.size(), 7);
InputHolder in4(spec, 0.945);
MeshInset_Result out4 = mesh_inset_calc(in4.input);
EXPECT_EQ(out4.vert.size(), 12);
EXPECT_EQ(out4.face.size(), 7);
InputHolder in5(spec, 0.97);
MeshInset_Result out5 = mesh_inset_calc(in5.input);
EXPECT_EQ(out5.vert.size(), 10);
EXPECT_EQ(out5.face.size(), 6);
}
TEST(mesh_inset, Splitter)
{
const char *spec = R"(5 1 1
0.0 0.0 0.0
1.5 0.1 0.0
1.75 0.8 0.0
0.8 0.6 0.0
0.0 1.0 0.0
0 1 2 3 4
0 1 2 3 4
)";
InputHolder in1(spec, 0.25);
MeshInset_Result out1 = mesh_inset_calc(in1.input);
EXPECT_EQ(out1.vert.size(), 10);
EXPECT_EQ(out1.face.size(), 6);
InputHolder in2(spec, 0.29);
MeshInset_Result out2 = mesh_inset_calc(in2.input);
EXPECT_EQ(out2.vert.size(), 12);
EXPECT_EQ(out2.face.size(), 7);
InputHolder in3(spec, 0.40);
MeshInset_Result out3 = mesh_inset_calc(in3.input);
EXPECT_EQ(out3.vert.size(), 8);
EXPECT_EQ(out3.face.size(), 5);
}
TEST(mesh_inset, Flipper)
{
const char *spec = R"(20 1 1
0.0 0.0 0.0
1.5 0.0 0.0
1.375 0.025 0.0
1.25 0.06 0.0
1.125 0.11 0.0
1.0 0.2 0.0
1.0 1.0 0.0
0.79 1.0 0.0
0.75 0.95 0.0
0.71 1.0 0.0
0.585 1.0 0.0
0.55 0.9 0.0
0.515 1.0 0.0
0.38 1.0 0.0
0.35 0.85 0.0
0.32 1.0 0.0
0.175 1.0 0.0
0.15 0.8 0.0
0.125 1.0 0.0
0.0 1.0 0.0
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
)";
InputHolder in1(spec, 0.01);
MeshInset_Result out1 = mesh_inset_calc(in1.input);
EXPECT_EQ(out1.vert.size(), 40);
EXPECT_EQ(out1.face.size(), 21);
InputHolder in2(spec, 0.06);
MeshInset_Result out2 = mesh_inset_calc(in2.input);
EXPECT_EQ(out2.vert.size(), 40);
EXPECT_EQ(out2.face.size(), 21);
InputHolder in3(spec, 0.07);
MeshInset_Result out3 = mesh_inset_calc(in3.input);
EXPECT_EQ(out3.vert.size(), 40);
EXPECT_EQ(out3.face.size(), 21);
InputHolder in4(spec, 0.08);
MeshInset_Result out4 = mesh_inset_calc(in4.input);
EXPECT_EQ(out4.vert.size(), 40);
EXPECT_EQ(out4.face.size(), 21);
InputHolder in5(spec, 0.087);
MeshInset_Result out5 = mesh_inset_calc(in5.input);
EXPECT_EQ(out5.vert.size(), 40);
EXPECT_EQ(out5.face.size(), 21);
InputHolder in6(spec, 0.0878);
MeshInset_Result out6 = mesh_inset_calc(in6.input);
EXPECT_EQ(out6.vert.size(), 40);
EXPECT_EQ(out6.face.size(), 21);
InputHolder in7(spec, 0.11);
MeshInset_Result out7 = mesh_inset_calc(in7.input);
EXPECT_EQ(out7.vert.size(), 42);
EXPECT_EQ(out7.face.size(), 22);
InputHolder in8(spec, 0.24);
MeshInset_Result out8 = mesh_inset_calc(in8.input);
EXPECT_EQ(out8.vert.size(), 42);
EXPECT_EQ(out8.face.size(), 22);
InputHolder in9(spec, 0.255);
MeshInset_Result out9 = mesh_inset_calc(in9.input);
EXPECT_EQ(out9.vert.size(), 42);
EXPECT_EQ(out9.face.size(), 22);
InputHolder in10(spec, 0.30);
MeshInset_Result out10 = mesh_inset_calc(in10.input);
EXPECT_EQ(out10.vert.size(), 40);
EXPECT_EQ(out10.face.size(), 21);
InputHolder in11(spec, 0.35);
MeshInset_Result out11 = mesh_inset_calc(in11.input);
EXPECT_EQ(out11.vert.size(), 38);
EXPECT_EQ(out11.face.size(), 20);
}
} // namespace test
} // namespace blender::meshinset