Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/blenkernel/intern/mesh_sample.c')
-rw-r--r--source/blender/blenkernel/intern/mesh_sample.c1683
1 files changed, 1683 insertions, 0 deletions
diff --git a/source/blender/blenkernel/intern/mesh_sample.c b/source/blender/blenkernel/intern/mesh_sample.c
new file mode 100644
index 00000000000..fac28da63fb
--- /dev/null
+++ b/source/blender/blenkernel/intern/mesh_sample.c
@@ -0,0 +1,1683 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * 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.
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file blender/blenkernel/intern/mesh_sample.c
+ * \ingroup bke
+ *
+ * Sample a mesh surface or volume and evaluate samples on deformed meshes.
+ */
+
+#include <limits.h>
+
+#include "MEM_guardedalloc.h"
+
+#include "DNA_key_types.h"
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+
+#include "BLI_utildefines.h"
+#include "BLI_ghash.h"
+#include "BLI_math.h"
+#include "BLI_rand.h"
+#include "BLI_sort.h"
+#include "BLI_task.h"
+
+#include "BKE_bvhutils.h"
+#include "BKE_mesh_sample.h"
+#include "BKE_customdata.h"
+#include "BKE_DerivedMesh.h"
+
+#include "BLI_strict_flags.h"
+
+#define SAMPLE_INDEX_INVALID 0xFFFFFFFF
+
+#define DEFAULT_TASK_SIZE 1024
+
+/* ==== Utility Functions ==== */
+
+static float calc_mesh_area(DerivedMesh *dm)
+{
+ int numtris = dm->getNumLoopTri(dm);
+ MVert *mverts = dm->getVertArray(dm);
+ MLoop *mloops = dm->getLoopArray(dm);
+
+ float totarea = 0.0;
+ const MLoopTri *tri = dm->getLoopTriArray(dm);
+ for (int i = 0; i < numtris; ++i, ++tri) {
+ unsigned int index1 = mloops[tri->tri[0]].v;
+ unsigned int index2 = mloops[tri->tri[1]].v;
+ unsigned int index3 = mloops[tri->tri[2]].v;
+ MVert *v1 = &mverts[index1];
+ MVert *v2 = &mverts[index2];
+ MVert *v3 = &mverts[index3];
+ totarea += area_tri_v3(v1->co, v2->co, v3->co);
+ }
+
+ return totarea;
+}
+
+BLI_INLINE float triangle_weight(DerivedMesh *dm, const MLoopTri *tri, const float *vert_weights, float *r_area)
+{
+ MVert *mverts = dm->getVertArray(dm);
+ MLoop *mloops = dm->getLoopArray(dm);
+ unsigned int index1 = mloops[tri->tri[0]].v;
+ unsigned int index2 = mloops[tri->tri[1]].v;
+ unsigned int index3 = mloops[tri->tri[2]].v;
+ MVert *v1 = &mverts[index1];
+ MVert *v2 = &mverts[index2];
+ MVert *v3 = &mverts[index3];
+
+ float weight = area_tri_v3(v1->co, v2->co, v3->co);
+ if (r_area) {
+ *r_area = weight;
+ }
+
+ if (vert_weights) {
+ float w1 = vert_weights[index1];
+ float w2 = vert_weights[index2];
+ float w3 = vert_weights[index3];
+
+ weight *= (w1 + w2 + w3) / 3.0f;
+ }
+
+ return weight;
+}
+
+float* BKE_mesh_sample_calc_triangle_weights(DerivedMesh *dm, MeshSampleVertexWeightFp vertex_weight_cb, void *userdata, float *r_area)
+{
+ int numverts = dm->getNumVerts(dm);
+ int numtris = dm->getNumLoopTri(dm);
+ int numweights = numtris;
+
+ float *vert_weights = NULL;
+ if (vertex_weight_cb) {
+ vert_weights = MEM_mallocN(sizeof(float) * (size_t)numverts, "mesh sample vertex weights");
+ {
+ MVert *mv = dm->getVertArray(dm);
+ for (int i = 0; i < numtris; ++i, ++mv) {
+ vert_weights[i] = vertex_weight_cb(dm, mv, (unsigned int)i, userdata);
+ }
+ }
+ }
+
+ float *tri_weights = MEM_mallocN(sizeof(float) * (size_t)numweights, "mesh sample triangle weights");
+ /* accumulate weights */
+ float totarea = 0.0;
+ float totweight = 0.0f;
+ {
+ const MLoopTri *mt = dm->getLoopTriArray(dm);
+ for (int i = 0; i < numtris; ++i, ++mt) {
+ tri_weights[i] = totweight;
+
+ float triarea;
+ float triweight = triangle_weight(dm, mt, vert_weights, &triarea);
+ totarea += triarea;
+ totweight += triweight;
+ }
+ }
+
+ if (vert_weights) {
+ MEM_freeN(vert_weights);
+ }
+
+ /* normalize */
+ if (totweight > 0.0f) {
+ float norm = 1.0f / totweight;
+ const MLoopTri *mt = dm->getLoopTriArray(dm);
+ for (int i = 0; i < numtris; ++i, ++mt) {
+ tri_weights[i] *= norm;
+ }
+ }
+ else {
+ /* invalid weights, remove to avoid invalid binary search */
+ MEM_freeN(tri_weights);
+ tri_weights = NULL;
+ }
+
+ if (r_area) {
+ *r_area = totarea;
+ }
+ return tri_weights;
+}
+
+void BKE_mesh_sample_weights_from_loc(MeshSample *sample, DerivedMesh *dm, int face_index, const float loc[3])
+{
+ MFace *face = &dm->getTessFaceArray(dm)[face_index];
+ unsigned int index[4] = { face->v1, face->v2, face->v3, face->v4 };
+ MVert *mverts = dm->getVertArray(dm);
+
+ float *v1 = mverts[face->v1].co;
+ float *v2 = mverts[face->v2].co;
+ float *v3 = mverts[face->v3].co;
+ float *v4 = face->v4 ? mverts[face->v4].co : NULL;
+ float w[4];
+ int tri[3];
+
+ interp_weights_quad_v3_index(tri, w, v1, v2, v3, v4, loc);
+
+ sample->orig_verts[0] = index[tri[0]];
+ sample->orig_verts[1] = index[tri[1]];
+ sample->orig_verts[2] = index[tri[2]];
+ sample->orig_weights[0] = w[tri[0]];
+ sample->orig_weights[1] = w[tri[1]];
+ sample->orig_weights[2] = w[tri[2]];
+}
+
+/* ==== Evaluate ==== */
+
+bool BKE_mesh_sample_is_valid(const struct MeshSample *sample)
+{
+ const unsigned int *v = sample->orig_verts;
+
+ if (BKE_mesh_sample_is_volume_sample(sample))
+ {
+ /* volume sample stores position in the weight vector directly */
+ return true;
+ }
+
+ if (v[0] == SAMPLE_INDEX_INVALID || v[1] == SAMPLE_INDEX_INVALID || v[2] == SAMPLE_INDEX_INVALID)
+ {
+ /* must have 3 valid indices */
+ return false;
+ }
+
+ return true;
+}
+
+bool BKE_mesh_sample_is_volume_sample(const MeshSample *sample)
+{
+ const unsigned int *v = sample->orig_verts;
+ return v[0] == SAMPLE_INDEX_INVALID && v[1] == SAMPLE_INDEX_INVALID && v[2] == SAMPLE_INDEX_INVALID;
+}
+
+bool BKE_mesh_sample_eval(DerivedMesh *dm, const MeshSample *sample, float loc[3], float nor[3], float tang[3])
+{
+ MVert *mverts = dm->getVertArray(dm);
+ unsigned int totverts = (unsigned int)dm->getNumVerts(dm);
+ MVert *v1, *v2, *v3;
+
+ zero_v3(loc);
+ zero_v3(nor);
+ zero_v3(tang);
+
+ if (BKE_mesh_sample_is_volume_sample(sample)) {
+ /* VOLUME SAMPLE */
+
+ if (is_zero_v3(sample->orig_weights))
+ return false;
+
+ copy_v3_v3(loc, sample->orig_weights);
+ return true;
+ }
+ else {
+ /* SURFACE SAMPLE */
+ if (sample->orig_verts[0] >= totverts ||
+ sample->orig_verts[1] >= totverts ||
+ sample->orig_verts[2] >= totverts)
+ return false;
+
+ v1 = &mverts[sample->orig_verts[0]];
+ v2 = &mverts[sample->orig_verts[1]];
+ v3 = &mverts[sample->orig_verts[2]];
+
+ { /* location */
+ madd_v3_v3fl(loc, v1->co, sample->orig_weights[0]);
+ madd_v3_v3fl(loc, v2->co, sample->orig_weights[1]);
+ madd_v3_v3fl(loc, v3->co, sample->orig_weights[2]);
+ }
+
+ { /* normal */
+ float vnor[3];
+
+ normal_short_to_float_v3(vnor, v1->no);
+ madd_v3_v3fl(nor, vnor, sample->orig_weights[0]);
+ normal_short_to_float_v3(vnor, v2->no);
+ madd_v3_v3fl(nor, vnor, sample->orig_weights[1]);
+ normal_short_to_float_v3(vnor, v3->no);
+ madd_v3_v3fl(nor, vnor, sample->orig_weights[2]);
+
+ normalize_v3(nor);
+ }
+
+ { /* tangent */
+ float edge[3];
+
+ /* XXX simply using the v1-v2 edge as a tangent vector for now ...
+ * Eventually mikktspace generated tangents (CD_TANGENT tessface layer)
+ * should be used for consistency, but requires well-defined tessface
+ * indices for the mesh surface samples.
+ */
+
+ sub_v3_v3v3(edge, v2->co, v1->co);
+ /* make edge orthogonal to nor */
+ madd_v3_v3fl(edge, nor, -dot_v3v3(edge, nor));
+ normalize_v3_v3(tang, edge);
+ }
+
+ return true;
+ }
+}
+
+bool BKE_mesh_sample_shapekey(Key *key, KeyBlock *kb, const MeshSample *sample, float loc[3])
+{
+ float *v1, *v2, *v3;
+
+ (void)key; /* Unused in release builds. */
+
+ BLI_assert(key->elemsize == 3 * sizeof(float));
+ BLI_assert(sample->orig_verts[0] < (unsigned int)kb->totelem);
+ BLI_assert(sample->orig_verts[1] < (unsigned int)kb->totelem);
+ BLI_assert(sample->orig_verts[2] < (unsigned int)kb->totelem);
+
+ v1 = (float *)kb->data + sample->orig_verts[0] * 3;
+ v2 = (float *)kb->data + sample->orig_verts[1] * 3;
+ v3 = (float *)kb->data + sample->orig_verts[2] * 3;
+
+ zero_v3(loc);
+ madd_v3_v3fl(loc, v1, sample->orig_weights[0]);
+ madd_v3_v3fl(loc, v2, sample->orig_weights[1]);
+ madd_v3_v3fl(loc, v3, sample->orig_weights[2]);
+
+ /* TODO use optional vgroup weights to determine if a shapeky actually affects the sample */
+ return true;
+}
+
+void BKE_mesh_sample_clear(MeshSample *sample)
+{
+ memset(sample, 0, sizeof(MeshSample));
+}
+
+
+/* ==== Generator Types ==== */
+
+typedef void (*GeneratorFreeFp)(struct MeshSampleGenerator *gen);
+
+typedef void (*GeneratorBindFp)(struct MeshSampleGenerator *gen);
+typedef void (*GeneratorUnbindFp)(struct MeshSampleGenerator *gen);
+
+typedef void* (*GeneratorThreadContextCreateFp)(const struct MeshSampleGenerator *gen, int start);
+typedef void (*GeneratorThreadContextFreeFp)(const struct MeshSampleGenerator *gen, void *thread_ctx);
+typedef bool (*GeneratorMakeSampleFp)(const struct MeshSampleGenerator *gen, void *thread_ctx, struct MeshSample *sample);
+typedef unsigned int (*GeneratorGetMaxSamplesFp)(const struct MeshSampleGenerator *gen);
+
+typedef struct MeshSampleGenerator
+{
+ GeneratorFreeFp free;
+
+ GeneratorBindFp bind;
+ GeneratorUnbindFp unbind;
+
+ GeneratorThreadContextCreateFp thread_context_create;
+ GeneratorThreadContextFreeFp thread_context_free;
+ GeneratorMakeSampleFp make_sample;
+ GeneratorGetMaxSamplesFp get_max_samples;
+
+ /* bind target */
+ DerivedMesh *dm;
+
+ void *default_ctx;
+ int task_size;
+} MeshSampleGenerator;
+
+static void sample_generator_init(MeshSampleGenerator *gen,
+ GeneratorFreeFp free,
+ GeneratorBindFp bind,
+ GeneratorUnbindFp unbind,
+ GeneratorThreadContextCreateFp thread_context_create,
+ GeneratorThreadContextFreeFp thread_context_free,
+ GeneratorMakeSampleFp make_sample,
+ GeneratorGetMaxSamplesFp get_max_samples)
+{
+ gen->free = free;
+ gen->bind = bind;
+ gen->unbind = unbind;
+ gen->thread_context_create = thread_context_create;
+ gen->thread_context_free = thread_context_free;
+ gen->make_sample = make_sample;
+ gen->get_max_samples = get_max_samples;
+
+ gen->dm = NULL;
+
+ gen->default_ctx = NULL;
+ gen->task_size = DEFAULT_TASK_SIZE;
+}
+
+/* ------------------------------------------------------------------------- */
+
+typedef struct MSurfaceSampleGenerator_Vertices {
+ MeshSampleGenerator base;
+
+ /* bind data */
+ int (*vert_loop_map)[3];
+} MSurfaceSampleGenerator_Vertices;
+
+static void generator_vertices_free(MSurfaceSampleGenerator_Vertices *gen)
+{
+ MEM_freeN(gen);
+}
+
+static void generator_vertices_bind(MSurfaceSampleGenerator_Vertices *gen)
+{
+ DerivedMesh *dm = gen->base.dm;
+ const int num_verts = dm->getNumVerts(dm);
+
+ DM_ensure_normals(dm);
+
+ int (*vert_loop_map)[3] = MEM_mallocN(sizeof(int) * 3 * (unsigned int)num_verts, "vertex loop map");
+ for (int i = 0; i < num_verts; ++i) {
+ vert_loop_map[i][0] = -1;
+ vert_loop_map[i][1] = -1;
+ vert_loop_map[i][2] = -1;
+ }
+
+ const int num_polys = dm->getNumPolys(dm);
+ const MLoop *mloops = dm->getLoopArray(dm);
+ const MPoly *mp = dm->getPolyArray(dm);
+ for (int i = 0; i < num_polys; ++i, ++mp) {
+ if (mp->totloop < 3) {
+ continue;
+ }
+
+ const MLoop *ml = mloops + mp->loopstart;
+ for (int k = 0; k < mp->totloop; ++k, ++ml) {
+ int *vmap = vert_loop_map[ml->v];
+ if (vmap[0] < 0) {
+ vmap[0] = mp->loopstart + k;
+ vmap[1] = mp->loopstart + (k + 1) % mp->totloop;
+ vmap[2] = mp->loopstart + (k + 2) % mp->totloop;
+ }
+ }
+ }
+
+ gen->vert_loop_map = vert_loop_map;
+}
+
+static void generator_vertices_unbind(MSurfaceSampleGenerator_Vertices *gen)
+{
+ if (gen->vert_loop_map) {
+ MEM_freeN(gen->vert_loop_map);
+ }
+}
+
+static void* generator_vertices_thread_context_create(const MSurfaceSampleGenerator_Vertices *UNUSED(gen), int start)
+{
+ int *cur_vert = MEM_callocN(sizeof(int), "generator_vertices_thread_context");
+ *cur_vert = start;
+ return cur_vert;
+}
+
+static void generator_vertices_thread_context_free(const MSurfaceSampleGenerator_Vertices *UNUSED(gen), void *thread_ctx)
+{
+ MEM_freeN(thread_ctx);
+}
+
+static bool generator_vertices_make_loop_sample(DerivedMesh *dm, const int *loops, MeshSample *sample)
+{
+ const MLoop *mloops = dm->getLoopArray(dm);
+
+ if (loops[0] < 0) {
+ return false;
+ }
+
+ sample->orig_poly = -1;
+
+ sample->orig_loops[0] = (unsigned int)loops[0];
+ sample->orig_loops[1] = (unsigned int)loops[1];
+ sample->orig_loops[2] = (unsigned int)loops[2];
+
+ sample->orig_verts[0] = mloops[loops[0]].v;
+ sample->orig_verts[1] = mloops[loops[1]].v;
+ sample->orig_verts[2] = mloops[loops[2]].v;
+
+ sample->orig_weights[0] = 1.0f;
+ sample->orig_weights[1] = 0.0f;
+ sample->orig_weights[2] = 0.0f;
+
+ return true;
+}
+
+static bool generator_vertices_make_sample(const MSurfaceSampleGenerator_Vertices *gen, void *thread_ctx, MeshSample *sample)
+{
+ DerivedMesh *dm = gen->base.dm;
+ const int num_verts = dm->getNumVerts(dm);
+
+ int cur_vert = *(int *)thread_ctx;
+ bool found_vert = false;
+ for (; cur_vert < num_verts && !found_vert; ++cur_vert) {
+ found_vert |= generator_vertices_make_loop_sample(dm, gen->vert_loop_map[cur_vert], sample);
+ }
+
+ *(int *)thread_ctx = cur_vert;
+ return found_vert;
+}
+
+static unsigned int generator_vertices_get_max_samples(const MSurfaceSampleGenerator_Vertices *gen)
+{
+ return (unsigned int)gen->base.dm->getNumVerts(gen->base.dm);
+}
+
+MeshSampleGenerator *BKE_mesh_sample_gen_surface_vertices(void)
+{
+ MSurfaceSampleGenerator_Vertices *gen;
+
+ gen = MEM_callocN(sizeof(MSurfaceSampleGenerator_Vertices), "MSurfaceSampleGenerator_Vertices");
+ sample_generator_init(&gen->base,
+ (GeneratorFreeFp)generator_vertices_free,
+ (GeneratorBindFp)generator_vertices_bind,
+ (GeneratorUnbindFp) generator_vertices_unbind,
+ (GeneratorThreadContextCreateFp)generator_vertices_thread_context_create,
+ (GeneratorThreadContextFreeFp)generator_vertices_thread_context_free,
+ (GeneratorMakeSampleFp)generator_vertices_make_sample,
+ (GeneratorGetMaxSamplesFp)generator_vertices_get_max_samples);
+
+ return &gen->base;
+}
+
+/* ------------------------------------------------------------------------- */
+
+//#define USE_DEBUG_COUNT
+
+typedef struct MSurfaceSampleGenerator_Random {
+ MeshSampleGenerator base;
+
+ unsigned int seed;
+ bool use_area_weight;
+ MeshSampleVertexWeightFp vertex_weight_cb;
+ void *userdata;
+
+ /* bind data */
+ float *tri_weights;
+ float *vertex_weights;
+
+#ifdef USE_DEBUG_COUNT
+ int *debug_count;
+#endif
+} MSurfaceSampleGenerator_Random;
+
+static void generator_random_free(MSurfaceSampleGenerator_Random *gen)
+{
+ MEM_freeN(gen);
+}
+
+static void generator_random_bind(MSurfaceSampleGenerator_Random *gen)
+{
+ DerivedMesh *dm = gen->base.dm;
+
+ DM_ensure_normals(dm);
+
+ if (gen->use_area_weight) {
+ gen->tri_weights = BKE_mesh_sample_calc_triangle_weights(dm, gen->vertex_weight_cb, gen->userdata, NULL);
+
+#ifdef USE_DEBUG_COUNT
+ gen->debug_count = MEM_callocN(sizeof(int) * (size_t)dm->getNumLoopTri(dm), "surface sample debug counts");
+#endif
+ }
+}
+
+static void generator_random_unbind(MSurfaceSampleGenerator_Random *gen)
+{
+#ifdef USE_DEBUG_COUNT
+ if (gen->debug_count) {
+ if (gen->tri_weights) {
+ int num = gen->dm->getNumLoopTri(gen->dm);
+ int i;
+ int totsamples = 0;
+
+ printf("Surface Sampling (n=%d):\n", num);
+ for (i = 0; i < num; ++i)
+ totsamples += gen->debug_count[i];
+
+ for (i = 0; i < num; ++i) {
+ float weight = i > 0 ? gen->tri_weights[i] - gen->tri_weights[i-1] : gen->tri_weights[i];
+ int samples = gen->debug_count[i];
+ printf(" %d: W = %f, N = %d/%d = %f\n", i, weight, samples, totsamples, (float)samples / (float)totsamples);
+ }
+ }
+ MEM_freeN(gen->debug_count);
+ }
+#endif
+ if (gen->tri_weights)
+ MEM_freeN(gen->tri_weights);
+ if (gen->vertex_weights)
+ MEM_freeN(gen->vertex_weights);
+}
+
+static void* generator_random_thread_context_create(const MSurfaceSampleGenerator_Random *gen, int start)
+{
+ RNG *rng = BLI_rng_new(gen->seed);
+ // 3 RNG gets per sample
+ BLI_rng_skip(rng, start * 3);
+ return rng;
+}
+
+static void generator_random_thread_context_free(const MSurfaceSampleGenerator_Random *UNUSED(gen), void *thread_ctx)
+{
+ BLI_rng_free(thread_ctx);
+}
+
+/* Find the index in "sum" array before "value" is crossed. */
+BLI_INLINE int weight_array_binary_search(const float *sum, int size, float value)
+{
+ int mid, low = 0, high = size - 1;
+
+ if (value <= 0.0f)
+ return 0;
+
+ while (low < high) {
+ mid = (low + high) >> 1;
+
+ if (sum[mid] < value && value <= sum[mid+1])
+ return mid;
+
+ if (sum[mid] >= value)
+ high = mid - 1;
+ else if (sum[mid] < value)
+ low = mid + 1;
+ else
+ return mid;
+ }
+
+ return low;
+}
+
+static bool generator_random_make_sample(const MSurfaceSampleGenerator_Random *gen, void *thread_ctx, MeshSample *sample)
+{
+ DerivedMesh *dm = gen->base.dm;
+ RNG *rng = thread_ctx;
+ const MLoop *mloops = dm->getLoopArray(dm);
+ const MLoopTri *mtris = dm->getLoopTriArray(dm);
+ int tottris = dm->getNumLoopTri(dm);
+ int totweights = tottris;
+
+ int triindex;
+ float a, b;
+ const MLoopTri *mtri;
+
+ if (gen->tri_weights)
+ triindex = weight_array_binary_search(gen->tri_weights, totweights, BLI_rng_get_float(rng));
+ else
+ triindex = BLI_rng_get_int(rng) % totweights;
+#ifdef USE_DEBUG_COUNT
+ if (gen->debug_count)
+ gen->debug_count[triindex] += 1;
+#endif
+ a = BLI_rng_get_float(rng);
+ b = BLI_rng_get_float(rng);
+
+ mtri = &mtris[triindex];
+
+ sample->orig_verts[0] = mloops[mtri->tri[0]].v;
+ sample->orig_verts[1] = mloops[mtri->tri[1]].v;
+ sample->orig_verts[2] = mloops[mtri->tri[2]].v;
+
+ if (a + b > 1.0f) {
+ a = 1.0f - a;
+ b = 1.0f - b;
+ }
+ sample->orig_weights[0] = 1.0f - (a + b);
+ sample->orig_weights[1] = a;
+ sample->orig_weights[2] = b;
+
+ return true;
+}
+
+MeshSampleGenerator *BKE_mesh_sample_gen_surface_random(unsigned int seed, bool use_area_weight,
+ MeshSampleVertexWeightFp vertex_weight_cb, void *userdata)
+{
+ MSurfaceSampleGenerator_Random *gen;
+
+ gen = MEM_callocN(sizeof(MSurfaceSampleGenerator_Random), "MSurfaceSampleGenerator_Random");
+ sample_generator_init(&gen->base,
+ (GeneratorFreeFp)generator_random_free,
+ (GeneratorBindFp)generator_random_bind,
+ (GeneratorUnbindFp) generator_random_unbind,
+ (GeneratorThreadContextCreateFp)generator_random_thread_context_create,
+ (GeneratorThreadContextFreeFp)generator_random_thread_context_free,
+ (GeneratorMakeSampleFp)generator_random_make_sample,
+ NULL);
+
+ gen->seed = seed;
+ gen->use_area_weight = use_area_weight;
+ gen->vertex_weight_cb = vertex_weight_cb;
+ gen->userdata = userdata;
+
+ return &gen->base;
+}
+
+/* ------------------------------------------------------------------------- */
+
+typedef struct MSurfaceSampleGenerator_RayCast {
+ MeshSampleGenerator base;
+
+ MeshSampleRayFp ray_cb;
+ MeshSampleThreadContextCreateFp thread_context_create_cb;
+ MeshSampleThreadContextFreeFp thread_context_free_cb;
+ void *userdata;
+
+ /* bind data */
+ BVHTreeFromMesh bvhdata;
+} MSurfaceSampleGenerator_RayCast;
+
+static void generator_raycast_free(MSurfaceSampleGenerator_RayCast *gen)
+{
+ MEM_freeN(gen);
+}
+
+static void generator_raycast_bind(MSurfaceSampleGenerator_RayCast *gen)
+{
+ DerivedMesh *dm = gen->base.dm;
+
+ DM_ensure_tessface(dm);
+
+ memset(&gen->bvhdata, 0, sizeof(gen->bvhdata));
+
+ if (dm->getNumTessFaces(dm) == 0)
+ return;
+
+ bvhtree_from_mesh_faces(&gen->bvhdata, dm, 0.0f, 4, 6);
+}
+
+static void generator_raycast_unbind(MSurfaceSampleGenerator_RayCast *gen)
+{
+ free_bvhtree_from_mesh(&gen->bvhdata);
+}
+
+static void* generator_raycast_thread_context_create(const MSurfaceSampleGenerator_RayCast *gen, int start)
+{
+ if (gen->thread_context_create_cb) {
+ return gen->thread_context_create_cb(gen->userdata, start);
+ }
+ else {
+ return NULL;
+ }
+}
+
+static void generator_raycast_thread_context_free(const MSurfaceSampleGenerator_RayCast *gen, void *thread_ctx)
+{
+ if (gen->thread_context_free_cb) {
+ return gen->thread_context_free_cb(gen->userdata, thread_ctx);
+ }
+}
+
+static bool generator_raycast_make_sample(const MSurfaceSampleGenerator_RayCast *gen, void *thread_ctx, MeshSample *sample)
+{
+ float ray_start[3], ray_end[3], ray_dir[3], dist;
+ BVHTreeRayHit hit;
+
+ if (!gen->ray_cb(gen->userdata, thread_ctx, ray_start, ray_end))
+ return false;
+
+ sub_v3_v3v3(ray_dir, ray_end, ray_start);
+ dist = normalize_v3(ray_dir);
+
+ hit.index = -1;
+ hit.dist = dist;
+
+ if (BLI_bvhtree_ray_cast(gen->bvhdata.tree, ray_start, ray_dir, 0.0f,
+ &hit, gen->bvhdata.raycast_callback, (BVHTreeFromMesh *)(&gen->bvhdata)) >= 0) {
+
+ BKE_mesh_sample_weights_from_loc(sample, gen->base.dm, hit.index, hit.co);
+
+ return true;
+ }
+ else
+ return false;
+}
+
+MeshSampleGenerator *BKE_mesh_sample_gen_surface_raycast(
+ MeshSampleThreadContextCreateFp thread_context_create_cb,
+ MeshSampleThreadContextFreeFp thread_context_free_cb,
+ MeshSampleRayFp ray_cb,
+ void *userdata)
+{
+ MSurfaceSampleGenerator_RayCast *gen;
+
+ gen = MEM_callocN(sizeof(MSurfaceSampleGenerator_RayCast), "MSurfaceSampleGenerator_RayCast");
+ sample_generator_init(&gen->base,
+ (GeneratorFreeFp)generator_raycast_free,
+ (GeneratorBindFp)generator_raycast_bind,
+ (GeneratorUnbindFp) generator_raycast_unbind,
+ (GeneratorThreadContextCreateFp)generator_raycast_thread_context_create,
+ (GeneratorThreadContextFreeFp)generator_raycast_thread_context_free,
+ (GeneratorMakeSampleFp)generator_raycast_make_sample,
+ NULL);
+
+ gen->thread_context_create_cb = thread_context_create_cb;
+ gen->thread_context_free_cb = thread_context_free_cb;
+ gen->ray_cb = ray_cb;
+ gen->userdata = userdata;
+
+ return &gen->base;
+}
+
+/* ------------------------------------------------------------------------- */
+
+#define MAX_CIRCLE_PACKING 0.906899682
+#define SQRT_3 1.732050808
+
+typedef struct IndexedMeshSample {
+ unsigned int orig_verts[3];
+ float orig_weights[3];
+ float co[3];
+ int cell_index[3];
+} IndexedMeshSample;
+
+typedef struct MSurfaceSampleGenerator_PoissonDisk {
+ MeshSampleGenerator base;
+
+ MeshSampleGenerator *uniform_gen;
+ unsigned int max_samples;
+ float mindist_squared;
+ /* Size of grid cells is mindist/sqrt(3),
+ * so that each cell contains at most one valid sample.
+ */
+ float cellsize;
+ /* Transform mesh space to grid space */
+ float grid_scale;
+
+ /* bind data */
+
+ /* offset and size of the grid */
+ int grid_offset[3];
+ int grid_size[3];
+
+ IndexedMeshSample *uniform_samples;
+ unsigned int num_uniform_samples;
+
+ struct GHash *cell_table;
+} MSurfaceSampleGenerator_PoissonDisk;
+
+typedef struct MeshSampleCell {
+ int cell_index[3];
+ unsigned int sample_start;
+ unsigned int sample;
+} MeshSampleCell;
+
+typedef struct MSurfaceSampleGenerator_PoissonDisk_ThreadContext {
+ unsigned int trial;
+ GHashIterator iter;
+} MSurfaceSampleGenerator_PoissonDisk_ThreadContext;
+
+BLI_INLINE void poissondisk_loc_from_grid(const MSurfaceSampleGenerator_PoissonDisk *gen, float loc[3], const int grid[3])
+{
+ copy_v3_fl3(loc, grid[0] + gen->grid_offset[0], grid[1] + gen->grid_offset[1], grid[2] + gen->grid_offset[2]);
+ mul_v3_fl(loc, gen->cellsize);
+}
+
+BLI_INLINE void poissondisk_grid_from_loc(const MSurfaceSampleGenerator_PoissonDisk *gen, int grid[3], const float loc[3])
+{
+ float gridco[3];
+ mul_v3_v3fl(gridco, loc, gen->grid_scale);
+ grid[0] = (int)floorf(gridco[0]) - gen->grid_offset[0];
+ grid[1] = (int)floorf(gridco[1]) - gen->grid_offset[1];
+ grid[2] = (int)floorf(gridco[2]) - gen->grid_offset[2];
+}
+
+static void generator_poissondisk_free(MSurfaceSampleGenerator_PoissonDisk *gen)
+{
+ BKE_mesh_sample_free_generator(gen->uniform_gen);
+ MEM_freeN(gen);
+}
+
+static void generator_poissondisk_uniform_sample_eval(
+ void *__restrict userdata,
+ const int iter,
+ const ParallelRangeTLS *__restrict UNUSED(tls))
+{
+ void *(*ptrs)[3] = userdata;
+ MSurfaceSampleGenerator_PoissonDisk *gen = (*ptrs)[0];
+ const MeshSample *samples = (*ptrs)[1];
+ DerivedMesh *dm = (*ptrs)[2];
+
+ IndexedMeshSample *isample = &gen->uniform_samples[iter];
+ const MeshSample *sample = &samples[iter];
+
+ memcpy(isample->orig_verts, sample->orig_verts, sizeof(isample->orig_verts));
+ memcpy(isample->orig_weights, sample->orig_weights, sizeof(isample->orig_weights));
+ float nor[3], tang[3];
+ BKE_mesh_sample_eval(dm, sample, isample->co, nor, tang);
+
+ poissondisk_grid_from_loc(gen, isample->cell_index, isample->co);
+}
+
+BLI_INLINE void copy_cell_index(int r[3], const int a[3])
+{
+ r[0] = a[0];
+ r[1] = a[1];
+ r[2] = a[2];
+}
+
+BLI_INLINE int cmp_cell_index(const int a[3], const int b[3])
+{
+ int d0 = a[0] - b[0];
+ int d1 = a[1] - b[1];
+ int d2 = a[2] - b[2];
+ if (d0 == 0)
+ {
+ if (d1 == 0)
+ {
+ if (d2 == 0)
+ {
+ return 0;
+ }
+ else
+ {
+ return d2 > 0 ? 1 : -1;
+ }
+ }
+ else
+ {
+ return d1 > 0 ? 1 : -1;
+ }
+ }
+ else
+ {
+ return d0 > 0 ? 1 : -1;
+ }
+}
+
+static int cmp_indexed_mesh_sample(const void *a, const void *b)
+{
+ return cmp_cell_index(((const IndexedMeshSample *)a)->cell_index, ((const IndexedMeshSample *)b)->cell_index);
+}
+
+BLI_INLINE bool cell_index_eq(const int *a, const int *b)
+{
+ return a[0] == b[0] && a[1] == b[1] && a[2] == b[2];
+}
+
+/* hash key function */
+static unsigned int cell_hash_key(const void *key)
+{
+ const int *cell_index = (const int *)key;
+ unsigned int hash0 = BLI_ghashutil_inthash(cell_index[0]);
+ unsigned int hash1 = BLI_ghashutil_inthash(cell_index[1]);
+ unsigned int hash2 = BLI_ghashutil_inthash(cell_index[2]);
+ return BLI_ghashutil_combine_hash(hash0, BLI_ghashutil_combine_hash(hash1, hash2));
+}
+
+/* hash function: return false when equal */
+static bool cell_hash_neq(const void *a, const void *b)
+{
+ return !cell_index_eq((const int *)a, (const int *)b);
+}
+
+static unsigned int generator_poissondisk_get_max_samples(const MSurfaceSampleGenerator_PoissonDisk *gen)
+{
+ static const unsigned int hard_max = UINT_MAX;
+
+ const double usable_area = calc_mesh_area(gen->base.dm) * MAX_CIRCLE_PACKING;
+ const double circle_area = M_PI * gen->mindist_squared;
+ if (circle_area * (double)hard_max < usable_area) {
+ return hard_max;
+ }
+
+ return (unsigned int)(usable_area / circle_area);
+}
+
+static void generator_poissondisk_bind(MSurfaceSampleGenerator_PoissonDisk *gen)
+{
+ DerivedMesh *dm = gen->base.dm;
+ static const unsigned int uniform_sample_ratio = 10;
+
+ // Determine cell size
+ {
+ float min[3], max[3];
+ INIT_MINMAX(min, max);
+ dm->getMinMax(dm, min, max);
+ mul_v3_fl(min, gen->grid_scale);
+ mul_v3_fl(max, gen->grid_scale);
+ /* grid size gets an empty 2 cell margin to simplify neighbor lookups */
+ gen->grid_offset[0] = (int)floorf(min[0]) - 2;
+ gen->grid_offset[1] = (int)floorf(min[1]) - 2;
+ gen->grid_offset[2] = (int)floorf(min[2]) - 2;
+ gen->grid_size[0] = (int)floorf(max[0]) - gen->grid_offset[0] + 4;
+ gen->grid_size[1] = (int)floorf(max[1]) - gen->grid_offset[1] + 4;
+ gen->grid_size[2] = (int)floorf(max[2]) - gen->grid_offset[2] + 4;
+ }
+
+ // Generate initial uniform random point set
+ unsigned int max_pd_samples = generator_poissondisk_get_max_samples(gen);
+ gen->num_uniform_samples = MIN2(max_pd_samples * uniform_sample_ratio, gen->max_samples);
+ if (gen->num_uniform_samples > 0) {
+ BKE_mesh_sample_generator_bind(gen->uniform_gen, dm);
+
+ gen->uniform_samples = MEM_mallocN(sizeof(IndexedMeshSample) * gen->num_uniform_samples, "poisson disk uniform samples");
+
+ MeshSample *samples = MEM_mallocN(sizeof(MeshSample) * gen->num_uniform_samples, "poisson disk uniform samples");
+ BKE_mesh_sample_generate_batch(gen->uniform_gen, samples, (int)gen->num_uniform_samples);
+ void *ptrs[3] = { gen, samples, dm };
+ {
+ ParallelRangeSettings settings;
+ BLI_parallel_range_settings_defaults(&settings);
+ settings.use_threading = true;
+ BLI_task_parallel_range(0, (int)gen->num_uniform_samples, &ptrs, generator_poissondisk_uniform_sample_eval, &settings);
+ }
+ MEM_freeN(samples);
+
+ BKE_mesh_sample_generator_unbind(gen->uniform_gen);
+ }
+
+ // Sort points by cell hash
+ {
+ qsort(gen->uniform_samples, gen->num_uniform_samples, sizeof(IndexedMeshSample), cmp_indexed_mesh_sample);
+ }
+
+ // Build a hash table for indexing cells
+ {
+ gen->cell_table = BLI_ghash_new(cell_hash_key, cell_hash_neq, "MeshSampleCell hash table");
+ int cur_cell_index[3] = {-1, -1, -1};
+ const IndexedMeshSample *sample = gen->uniform_samples;
+ for (unsigned int i = 0; i < gen->num_uniform_samples; ++i, ++sample) {
+ BLI_assert(cmp_cell_index(cur_cell_index, sample->cell_index) <= 0);
+ if (cmp_cell_index(cur_cell_index, sample->cell_index) < 0) {
+ copy_cell_index(cur_cell_index, sample->cell_index);
+
+ MeshSampleCell *cell = MEM_mallocN(sizeof(*cell), "MeshSampleCell");
+ copy_cell_index(cell->cell_index, cur_cell_index);
+ cell->sample_start = (unsigned int)i;
+ cell->sample = SAMPLE_INDEX_INVALID;
+ BLI_ghash_insert(gen->cell_table, cell->cell_index, cell);
+ }
+ }
+ }
+
+#if 0
+ for (unsigned int i = 0; i < gen->num_uniform_samples; ++i) {
+ const IndexedMeshSample *s = &gen->uniform_samples[i];
+ printf("%d: (%.3f, %.3f, %.3f) | %d\n", i, s->co[0], s->co[1], s->co[2], (int)s->cell_hash);
+ }
+#endif
+}
+
+static void generator_poissondisk_unbind(MSurfaceSampleGenerator_PoissonDisk *gen)
+{
+ if (gen->cell_table) {
+ BLI_ghash_free(gen->cell_table, NULL, MEM_freeN);
+ }
+
+ if (gen->uniform_samples) {
+ MEM_freeN(gen->uniform_samples);
+ }
+}
+
+static void* generator_poissondisk_thread_context_create(const MSurfaceSampleGenerator_PoissonDisk *gen, int UNUSED(start))
+{
+ MSurfaceSampleGenerator_PoissonDisk_ThreadContext *ctx = MEM_mallocN(sizeof(*ctx), "thread context");
+ ctx->trial = 0;
+ BLI_ghashIterator_init(&ctx->iter, gen->cell_table);
+ return ctx;
+}
+
+static void generator_poissondisk_thread_context_free(const MSurfaceSampleGenerator_PoissonDisk *UNUSED(gen), void *thread_ctx)
+{
+ MEM_freeN(thread_ctx);
+}
+
+static bool generator_poissondisk_make_sample(const MSurfaceSampleGenerator_PoissonDisk *gen, void *thread_ctx, MeshSample *sample)
+{
+ static const unsigned int max_trials = 5;
+
+ MSurfaceSampleGenerator_PoissonDisk_ThreadContext *ctx = thread_ctx;
+
+ // Offset of cells whose samples can potentially overlap a given cell
+ // Four corners are excluded because their samples can never overlap
+ const int neighbors[][3] = {
+ {-1, -2, -2}, { 0, -2, -2}, { 1, -2, -2},
+ {-2, -1, -2}, {-1, -1, -2}, { 0, -1, -2}, { 1, -1, -2}, { 2, -1, -2},
+ {-2, 0, -2}, {-1, 0, -2}, { 0, 0, -2}, { 1, 0, -2}, { 2, 0, -2},
+ {-2, 1, -2}, {-1, 1, -2}, { 0, 1, -2}, { 1, -2, -2}, { 2, 1, -2},
+ {-2, 2, -2}, {-1, 2, -2}, { 0, 2, -2}, { 1, -2, -2}, { 2, 2, -2},
+
+ {-2, -2, -1}, {-1, -2, -1}, { 0, -2, -1}, { 1, -2, -1}, { 2, -2, -1},
+ {-2, -1, -1}, {-1, -1, -1}, { 0, -1, -1}, { 1, -1, -1}, { 2, -1, -1},
+ {-2, 0, -1}, {-1, 0, -1}, { 0, 0, -1}, { 1, 0, -1}, { 2, 0, -1},
+ {-2, 1, -1}, {-1, 1, -1}, { 0, 1, -1}, { 1, -2, -1}, { 2, 1, -1},
+ {-2, 2, -1}, {-1, 2, -1}, { 0, 2, -1}, { 1, -2, -1}, { 2, 2, -1},
+
+ {-2, -2, 0}, {-1, -2, 0}, { 0, -2, 0}, { 1, -2, 0}, { 2, -2, 0},
+ {-2, -1, 0}, {-1, -1, 0}, { 0, -1, 0}, { 1, -1, 0}, { 2, -1, 0},
+ {-2, 0, 0}, {-1, 0, 0}, { 1, 0, 0}, { 2, 0, 0},
+ {-2, 1, 0}, {-1, 1, 0}, { 0, 1, 0}, { 1, -2, 0}, { 2, 1, 0},
+ {-2, 2, 0}, {-1, 2, 0}, { 0, 2, 0}, { 1, -2, 0}, { 2, 2, 0},
+
+ {-2, -2, 1}, {-1, -2, 1}, { 0, -2, 1}, { 1, -2, 1}, { 2, -2, 1},
+ {-2, -1, 1}, {-1, -1, 1}, { 0, -1, 1}, { 1, -1, 1}, { 2, -1, 1},
+ {-2, 0, 1}, {-1, 0, 1}, { 0, 0, 1}, { 1, 0, 1}, { 2, 0, 1},
+ {-2, 1, 1}, {-1, 1, 1}, { 0, 1, 1}, { 1, -2, 1}, { 2, 1, 1},
+ {-2, 2, 1}, {-1, 2, 1}, { 0, 2, 1}, { 1, -2, 1}, { 2, 2, 1},
+
+ {-2, -2, 2}, {-1, -2, 2}, { 0, -2, 2}, { 1, -2, 2}, { 2, -2, 2},
+ {-2, -1, 2}, {-1, -1, 2}, { 0, -1, 2}, { 1, -1, 2}, { 2, -1, 2},
+ {-2, 0, 2}, {-1, 0, 2}, { 0, 0, 2}, { 1, 0, 2}, { 2, 0, 2},
+ {-2, 1, 2}, {-1, 1, 2}, { 0, 1, 2}, { 1, -2, 2}, { 2, 1, 2},
+ {-1, 2, 2}, { 0, 2, 2}, { 1, -2, 2}
+ };
+ const int num_neighbors = ARRAY_SIZE(neighbors);
+
+ bool found_sample = false;
+ for (; ctx->trial < max_trials; ++ctx->trial) {
+ while (!BLI_ghashIterator_done(&ctx->iter)) {
+ MeshSampleCell *cell = BLI_ghashIterator_getValue(&ctx->iter);
+ BLI_ghashIterator_step(&ctx->iter);
+
+ if (cell->sample != SAMPLE_INDEX_INVALID) {
+ continue;
+ }
+
+ bool cell_valid = true;
+
+ unsigned int sample_index = cell->sample_start + ctx->trial;
+ const IndexedMeshSample *isample = &gen->uniform_samples[sample_index];
+ /* Check if we ran out of sample candidates for this cell */
+ if (sample_index >= (unsigned int)gen->num_uniform_samples ||
+ !cell_index_eq(isample->cell_index, cell->cell_index)) {
+
+ cell_valid = false;
+ // TODO remove from hash table?
+ UNUSED_VARS(cell_valid);
+
+ }
+ else {
+
+ /* Check the sample candidate */
+ const int *idx = cell->cell_index;
+
+ bool conflict = false;
+ for (int i = 0; i < num_neighbors; ++i) {
+ const int *idx_offset = neighbors[i];
+ const int nidx[3] = {idx[0] + idx_offset[0], idx[1] + idx_offset[1], idx[2] + idx_offset[2]};
+ const MeshSampleCell *ncell = BLI_ghash_lookup(gen->cell_table, nidx);
+ if (ncell) {
+ if (ncell->sample != SAMPLE_INDEX_INVALID) {
+ const IndexedMeshSample *nsample = &gen->uniform_samples[ncell->sample];
+ BLI_assert(cell_index_eq(nsample->cell_index, ncell->cell_index));
+ if (len_squared_v3v3(isample->co, nsample->co) < gen->mindist_squared) {
+ conflict = true;
+ break;
+ }
+ }
+ }
+ }
+ if (!conflict) {
+ cell->sample = sample_index;
+
+ memcpy(sample->orig_verts, isample->orig_verts, sizeof(sample->orig_verts));
+ memcpy(sample->orig_weights, isample->orig_weights, sizeof(sample->orig_weights));
+ memset(sample->orig_loops, 0, sizeof(sample->orig_loops));
+ sample->orig_poly = 0;
+
+ found_sample = true;
+ }
+
+ }
+
+ if (found_sample) {
+ break;
+ }
+ }
+
+ if (found_sample) {
+ break;
+ }
+ else {
+ BLI_ghashIterator_init(&ctx->iter, gen->cell_table);
+ }
+ }
+
+ return found_sample;
+}
+
+MeshSampleGenerator *BKE_mesh_sample_gen_surface_poissondisk(unsigned int seed, float mindist, unsigned int max_samples,
+ MeshSampleVertexWeightFp vertex_weight_cb, void *userdata)
+{
+ MSurfaceSampleGenerator_PoissonDisk *gen;
+
+ gen = MEM_callocN(sizeof(MSurfaceSampleGenerator_PoissonDisk), "MSurfaceSampleGenerator_PoissonDisk");
+ sample_generator_init(&gen->base,
+ (GeneratorFreeFp)generator_poissondisk_free,
+ (GeneratorBindFp)generator_poissondisk_bind,
+ (GeneratorUnbindFp) generator_poissondisk_unbind,
+ (GeneratorThreadContextCreateFp)generator_poissondisk_thread_context_create,
+ (GeneratorThreadContextFreeFp)generator_poissondisk_thread_context_free,
+ (GeneratorMakeSampleFp)generator_poissondisk_make_sample,
+ (GeneratorGetMaxSamplesFp)generator_poissondisk_get_max_samples);
+
+ gen->uniform_gen = BKE_mesh_sample_gen_surface_random(seed, true, vertex_weight_cb, userdata);
+ gen->max_samples = max_samples;
+ gen->mindist_squared = mindist * mindist;
+ gen->cellsize = mindist / SQRT_3;
+ gen->grid_scale = SQRT_3 / mindist;
+
+ return &gen->base;
+}
+
+/* ------------------------------------------------------------------------- */
+
+typedef struct MVolumeSampleGenerator_Random {
+ MeshSampleGenerator base;
+
+ unsigned int seed;
+ float density;
+
+ /* bind data */
+ BVHTreeFromMesh bvhdata;
+ float min[3], max[3], extent[3], volume;
+ int max_samples_per_ray;
+} MVolumeSampleGenerator_Random;
+
+typedef struct MVolumeSampleGenerator_Random_ThreadContext {
+ const BVHTreeFromMesh *bvhdata;
+ RNG *rng;
+
+ /* current ray intersections */
+ BVHTreeRayHit *ray_hits;
+ int tothits, allochits;
+
+ /* current segment index and sample number */
+ int cur_seg, cur_tot, cur_sample;
+} MVolumeSampleGenerator_Random_ThreadContext;
+
+static void generator_volume_random_free(MVolumeSampleGenerator_Random *gen)
+{
+ MEM_freeN(gen);
+}
+
+static void generator_volume_random_bind(MVolumeSampleGenerator_Random *gen)
+{
+ DerivedMesh *dm = gen->base.dm;
+
+ memset(&gen->bvhdata, 0, sizeof(gen->bvhdata));
+
+ DM_ensure_tessface(dm);
+
+ if (dm->getNumTessFaces(dm) == 0)
+ return;
+
+ bvhtree_from_mesh_faces(&gen->bvhdata, dm, 0.0f, 4, 6);
+
+ INIT_MINMAX(gen->min, gen->max);
+ dm->getMinMax(dm, gen->min, gen->max);
+ sub_v3_v3v3(gen->extent, gen->max, gen->min);
+ gen->volume = gen->extent[0] * gen->extent[1] * gen->extent[2];
+ gen->max_samples_per_ray = max_ii(1, (int)powf(gen->volume, 1.0f/3.0f)) >> 1;
+}
+
+static void generator_volume_random_unbind(MVolumeSampleGenerator_Random *gen)
+{
+ free_bvhtree_from_mesh(&gen->bvhdata);
+}
+
+BLI_INLINE unsigned int hibit(unsigned int n) {
+ n |= (n >> 1);
+ n |= (n >> 2);
+ n |= (n >> 4);
+ n |= (n >> 8);
+ n |= (n >> 16);
+ return n ^ (n >> 1);
+}
+
+static void generator_volume_hits_reserve(MVolumeSampleGenerator_Random_ThreadContext *ctx, int tothits)
+{
+ if (tothits > ctx->allochits) {
+ ctx->allochits = (int)hibit((unsigned int)tothits) << 1;
+ ctx->ray_hits = MEM_reallocN(ctx->ray_hits, (size_t)ctx->allochits * sizeof(BVHTreeRayHit));
+ }
+}
+
+static void *generator_volume_random_thread_context_create(const MVolumeSampleGenerator_Random *gen, int start)
+{
+ MVolumeSampleGenerator_Random_ThreadContext *ctx = MEM_callocN(sizeof(*ctx), "thread context");
+
+ ctx->bvhdata = &gen->bvhdata;
+
+ ctx->rng = BLI_rng_new(gen->seed);
+ // 11 RNG gets per sample
+ BLI_rng_skip(ctx->rng, start * 11);
+
+ generator_volume_hits_reserve(ctx, 64);
+
+ return ctx;
+}
+
+static void generator_volume_random_thread_context_free(const MVolumeSampleGenerator_Random *UNUSED(gen), void *thread_ctx)
+{
+ MVolumeSampleGenerator_Random_ThreadContext *ctx = thread_ctx;
+ BLI_rng_free(ctx->rng);
+
+ if (ctx->ray_hits) {
+ MEM_freeN(ctx->ray_hits);
+ }
+
+ MEM_freeN(ctx);
+}
+
+static void generator_volume_ray_cb(void *userdata, int index, const BVHTreeRay *ray, BVHTreeRayHit *hit)
+{
+ MVolumeSampleGenerator_Random_ThreadContext *ctx = userdata;
+
+ ctx->bvhdata->raycast_callback((void *)ctx->bvhdata, index, ray, hit);
+
+ if (hit->index >= 0) {
+ ++ctx->tothits;
+ generator_volume_hits_reserve(ctx, ctx->tothits);
+
+ memcpy(&ctx->ray_hits[ctx->tothits-1], hit, sizeof(BVHTreeRayHit));
+ }
+}
+
+typedef struct Ray {
+ float start[3];
+ float end[3];
+} Ray;
+
+static void generator_volume_random_cast_ray(MVolumeSampleGenerator_Random_ThreadContext *ctx, const Ray* ray)
+{
+ float dir[3];
+
+ sub_v3_v3v3(dir, ray->end, ray->start);
+ normalize_v3(dir);
+
+ ctx->tothits = 0;
+ BLI_bvhtree_ray_cast_all(ctx->bvhdata->tree, ray->start, dir, 0.0f, BVH_RAYCAST_DIST_MAX,
+ generator_volume_ray_cb, ctx);
+
+ ctx->cur_seg = 0;
+ ctx->cur_tot = 0;
+ ctx->cur_sample = 0;
+}
+
+static void generator_volume_init_segment(const MVolumeSampleGenerator_Random *gen, MVolumeSampleGenerator_Random_ThreadContext *ctx)
+{
+ BVHTreeRayHit *a, *b;
+ float length;
+
+ BLI_assert(ctx->cur_seg + 1 < ctx->tothits);
+ a = &ctx->ray_hits[ctx->cur_seg];
+ b = &ctx->ray_hits[ctx->cur_seg + 1];
+
+ length = len_v3v3(a->co, b->co);
+ ctx->cur_tot = min_ii(gen->max_samples_per_ray, (int)ceilf(length * gen->density));
+ ctx->cur_sample = 0;
+}
+
+static void generator_volume_get_ray(RNG *rng, Ray *ray)
+{
+ /* bounding box margin to get clean ray intersections */
+ static const float margin = 0.01f;
+
+ ray->start[0] = BLI_rng_get_float(rng);
+ ray->start[1] = BLI_rng_get_float(rng);
+ ray->start[2] = 0.0f;
+ ray->end[0] = BLI_rng_get_float(rng);
+ ray->end[1] = BLI_rng_get_float(rng);
+ ray->end[2] = 1.0f;
+
+ int axis = BLI_rng_get_int(rng) % 3;
+ switch (axis) {
+ case 0: break;
+ case 1:
+ SHIFT3(float, ray->start[0], ray->start[1], ray->start[2]);
+ SHIFT3(float, ray->end[0], ray->end[1], ray->end[2]);
+ break;
+ case 2:
+ SHIFT3(float, ray->start[2], ray->start[1], ray->start[0]);
+ SHIFT3(float, ray->end[2], ray->end[1], ray->end[0]);
+ break;
+ }
+
+ mul_v3_fl(ray->start, 1.0f + 2.0f*margin);
+ add_v3_fl(ray->start, -margin);
+ mul_v3_fl(ray->end, 1.0f + 2.0f*margin);
+ add_v3_fl(ray->end, -margin);
+}
+
+static void generator_volume_ray_to_bbox(const MVolumeSampleGenerator_Random *gen, Ray *ray)
+{
+ madd_v3_v3v3v3(ray->start, gen->min, ray->start, gen->extent);
+ madd_v3_v3v3v3(ray->end, gen->min, ray->end, gen->extent);
+}
+
+static bool generator_volume_random_make_sample(const MVolumeSampleGenerator_Random *gen, void *thread_ctx, MeshSample *sample)
+{
+ MVolumeSampleGenerator_Random_ThreadContext *ctx = thread_ctx;
+
+ Ray ray1, ray2;
+ // Do all RNG gets at the beggining for keeping consistent state
+ generator_volume_get_ray(ctx->rng, &ray1);
+ generator_volume_get_ray(ctx->rng, &ray2);
+ float t = BLI_rng_get_float(ctx->rng);
+
+ if (ctx->cur_seg + 1 >= ctx->tothits) {
+ generator_volume_ray_to_bbox(gen, &ray1);
+ generator_volume_random_cast_ray(ctx, &ray1);
+ if (ctx->tothits < 2)
+ return false;
+ }
+
+ if (ctx->cur_sample >= ctx->cur_tot) {
+ ctx->cur_seg += 2;
+
+ if (ctx->cur_seg + 1 >= ctx->tothits) {
+ generator_volume_ray_to_bbox(gen, &ray2);
+ generator_volume_random_cast_ray(ctx, &ray2);
+ if (ctx->tothits < 2)
+ return false;
+ }
+
+ generator_volume_init_segment(gen, ctx);
+ }
+ BVHTreeRayHit *a = &ctx->ray_hits[ctx->cur_seg];
+ BVHTreeRayHit *b = &ctx->ray_hits[ctx->cur_seg + 1];
+
+ if (ctx->cur_sample < ctx->cur_tot) {
+
+ sample->orig_verts[0] = SAMPLE_INDEX_INVALID;
+ sample->orig_verts[1] = SAMPLE_INDEX_INVALID;
+ sample->orig_verts[2] = SAMPLE_INDEX_INVALID;
+
+ interp_v3_v3v3(sample->orig_weights, a->co, b->co, t);
+
+ ctx->cur_sample += 1;
+
+ return true;
+ }
+
+ return false;
+}
+
+MeshSampleGenerator *BKE_mesh_sample_gen_volume_random_bbray(unsigned int seed, float density)
+{
+ MVolumeSampleGenerator_Random *gen;
+
+ gen = MEM_callocN(sizeof(MVolumeSampleGenerator_Random), "MVolumeSampleGenerator_Random");
+ sample_generator_init(&gen->base,
+ (GeneratorFreeFp)generator_volume_random_free,
+ (GeneratorBindFp)generator_volume_random_bind,
+ (GeneratorUnbindFp) generator_volume_random_unbind,
+ (GeneratorThreadContextCreateFp)generator_volume_random_thread_context_create,
+ (GeneratorThreadContextFreeFp)generator_volume_random_thread_context_free,
+ (GeneratorMakeSampleFp)generator_volume_random_make_sample,
+ NULL);
+
+ gen->seed = seed;
+ gen->density = density;
+
+ return &gen->base;
+}
+
+/* ------------------------------------------------------------------------- */
+
+void BKE_mesh_sample_free_generator(MeshSampleGenerator *gen)
+{
+ if (gen->default_ctx) {
+ if (gen->thread_context_free) {
+ gen->thread_context_free(gen, gen->default_ctx);
+ }
+ }
+
+ BKE_mesh_sample_generator_unbind(gen);
+
+ gen->free(gen);
+}
+
+
+/* ==== Sampling ==== */
+
+void BKE_mesh_sample_generator_bind(MeshSampleGenerator *gen, DerivedMesh *dm)
+{
+ BLI_assert(gen->dm == NULL && "Generator already bound");
+
+ gen->dm = dm;
+ if (gen->bind) {
+ gen->bind(gen);
+ }
+}
+
+void BKE_mesh_sample_generator_unbind(MeshSampleGenerator *gen)
+{
+ if (gen->dm) {
+ if (gen->unbind) {
+ gen->unbind(gen);
+ }
+ gen->dm = NULL;
+ }
+}
+
+unsigned int BKE_mesh_sample_gen_get_max_samples(const MeshSampleGenerator *gen)
+{
+ if (gen->get_max_samples) {
+ return gen->get_max_samples(gen);
+ }
+ return 0;
+}
+
+bool BKE_mesh_sample_generate(MeshSampleGenerator *gen, struct MeshSample *sample)
+{
+ if (!gen->default_ctx && gen->thread_context_create) {
+ gen->default_ctx = gen->thread_context_create(gen, 0);
+ }
+
+ return gen->make_sample(gen, gen->default_ctx, sample);
+}
+
+typedef struct MeshSamplePoolData {
+ const MeshSampleGenerator *gen;
+ int output_stride;
+} MeshSamplePoolData;
+
+typedef struct MeshSampleTaskData {
+ void *thread_ctx;
+ void *output_buffer;
+ int count;
+ int result;
+} MeshSampleTaskData;
+
+static void mesh_sample_generate_task_run(TaskPool * __restrict pool, void *taskdata_, int UNUSED(threadid))
+{
+ MeshSamplePoolData *pooldata = BLI_task_pool_userdata(pool);
+ const MeshSampleGenerator *gen = pooldata->gen;
+ const int output_stride = pooldata->output_stride;
+
+ GeneratorMakeSampleFp make_sample = gen->make_sample;
+ MeshSampleTaskData *taskdata = taskdata_;
+ void *thread_ctx = taskdata->thread_ctx;
+ const int count = taskdata->count;
+ MeshSample *sample = taskdata->output_buffer;
+
+ int i = 0;
+ for (; i < count; ++i, sample = (MeshSample *)((char *)sample + output_stride)) {
+ if (!make_sample(gen, thread_ctx, sample)) {
+ break;
+ }
+ }
+
+ taskdata->result = i;
+}
+
+int BKE_mesh_sample_generate_batch_ex(MeshSampleGenerator *gen,
+ void *output_buffer, int output_stride, int count,
+ bool use_threads)
+{
+ if (use_threads) {
+ TaskScheduler *scheduler = BLI_task_scheduler_get();
+
+ MeshSamplePoolData pool_data;
+ pool_data.gen = gen;
+ pool_data.output_stride = output_stride;
+ TaskPool *task_pool = BLI_task_pool_create(scheduler, &pool_data);
+
+ const int num_tasks = (count + gen->task_size - 1) / gen->task_size;
+ MeshSampleTaskData *task_data = MEM_callocN(sizeof(MeshSampleTaskData) * (unsigned int)num_tasks, "mesh sample task data");
+
+ {
+ MeshSampleTaskData *td = task_data;
+ int start = 0;
+ for (int i = 0; i < num_tasks; ++i, ++td, start += gen->task_size) {
+ if (gen->thread_context_create) {
+ td->thread_ctx = gen->thread_context_create(gen, start);
+ }
+ td->output_buffer = (char *)output_buffer + start * output_stride;
+ td->count = min_ii(count - start, gen->task_size);
+
+ BLI_task_pool_push(task_pool, mesh_sample_generate_task_run, td, false, TASK_PRIORITY_LOW);
+ }
+ }
+
+ BLI_task_pool_work_and_wait(task_pool);
+ BLI_task_pool_free(task_pool);
+
+ int totresult = 0;
+ {
+ MeshSampleTaskData *td = task_data;
+ for (int i = 0; i < num_tasks; ++i, ++td) {
+ totresult += td->result;
+ }
+ }
+
+ if (gen->thread_context_free) {
+ MeshSampleTaskData *td = task_data;
+ for (int i = 0; i < num_tasks; ++i, ++td) {
+ if (td->thread_ctx) {
+ gen->thread_context_free(gen, td->thread_ctx);
+ }
+ }
+ }
+ MEM_freeN(task_data);
+
+ return totresult;
+ }
+ else {
+ void *thread_ctx = NULL;
+ if (gen->thread_context_create) {
+ thread_ctx = gen->thread_context_create(gen, 0);
+ }
+
+ MeshSample *sample = output_buffer;
+ int i = 0;
+ for (; i < count; ++i, sample = (MeshSample *)((char *)sample + output_stride)) {
+ if (!gen->make_sample(gen, thread_ctx, sample)) {
+ break;
+ }
+ }
+
+ if (thread_ctx && gen->thread_context_free) {
+ gen->thread_context_free(gen, thread_ctx);
+ }
+
+ return i;
+ }
+}
+
+int BKE_mesh_sample_generate_batch(MeshSampleGenerator *gen,
+ MeshSample *output_buffer, int count)
+{
+ return BKE_mesh_sample_generate_batch_ex(gen, output_buffer, sizeof(MeshSample), count, true);
+}
+
+/* ==== Utilities ==== */
+
+#include "DNA_particle_types.h"
+
+#include "BKE_bvhutils.h"
+#include "BKE_particle.h"
+
+bool BKE_mesh_sample_from_particle(MeshSample *sample, ParticleSystem *psys, DerivedMesh *dm, ParticleData *pa)
+{
+ MVert *mverts;
+ MFace *mface;
+ float mapfw[4];
+ int mapindex;
+ float *co1 = NULL, *co2 = NULL, *co3 = NULL, *co4 = NULL;
+ float vec[3];
+ float w[4];
+
+ if (!psys_get_index_on_dm(psys, dm, pa, &mapindex, mapfw))
+ return false;
+
+ mface = dm->getTessFaceData(dm, mapindex, CD_MFACE);
+ mverts = dm->getVertDataArray(dm, CD_MVERT);
+
+ co1 = mverts[mface->v1].co;
+ co2 = mverts[mface->v2].co;
+ co3 = mverts[mface->v3].co;
+
+ if (mface->v4) {
+ co4 = mverts[mface->v4].co;
+
+ interp_v3_v3v3v3v3(vec, co1, co2, co3, co4, mapfw);
+ }
+ else {
+ interp_v3_v3v3v3(vec, co1, co2, co3, mapfw);
+ }
+
+ /* test both triangles of the face */
+ interp_weights_tri_v3(w, co1, co2, co3, vec);
+ if (w[0] <= 1.0f && w[1] <= 1.0f && w[2] <= 1.0f) {
+ sample->orig_verts[0] = mface->v1;
+ sample->orig_verts[1] = mface->v2;
+ sample->orig_verts[2] = mface->v3;
+
+ copy_v3_v3(sample->orig_weights, w);
+ return true;
+ }
+ else if (mface->v4) {
+ interp_weights_tri_v3(w, co3, co4, co1, vec);
+ sample->orig_verts[0] = mface->v3;
+ sample->orig_verts[1] = mface->v4;
+ sample->orig_verts[2] = mface->v1;
+
+ copy_v3_v3(sample->orig_weights, w);
+ return true;
+ }
+ else
+ return false;
+}
+
+bool BKE_mesh_sample_to_particle(MeshSample *sample, ParticleSystem *UNUSED(psys), DerivedMesh *dm, BVHTreeFromMesh *bvhtree, ParticleData *pa)
+{
+ BVHTreeNearest nearest;
+ float vec[3], nor[3], tang[3];
+
+ BKE_mesh_sample_eval(dm, sample, vec, nor, tang);
+
+ nearest.index = -1;
+ nearest.dist_sq = FLT_MAX;
+ BLI_bvhtree_find_nearest(bvhtree->tree, vec, &nearest, bvhtree->nearest_callback, bvhtree);
+ if (nearest.index >= 0) {
+ MFace *mface = dm->getTessFaceData(dm, nearest.index, CD_MFACE);
+ MVert *mverts = dm->getVertDataArray(dm, CD_MVERT);
+
+ float *co1 = mverts[mface->v1].co;
+ float *co2 = mverts[mface->v2].co;
+ float *co3 = mverts[mface->v3].co;
+ float *co4 = mface->v4 ? mverts[mface->v4].co : NULL;
+
+ pa->num = nearest.index;
+ pa->num_dmcache = DMCACHE_NOTFOUND;
+
+ interp_weights_quad_v3(pa->fuv, co1, co2, co3, co4, vec);
+ pa->foffset = 0.0f; /* XXX any sensible way to reconstruct this? */
+
+ return true;
+ }
+ else
+ return false;
+}