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:
-rw-r--r--intern/cycles/blender/blender_curves.cpp522
-rw-r--r--intern/cycles/blender/blender_mesh.cpp6
-rw-r--r--intern/cycles/blender/blender_util.h5
-rw-r--r--release/scripts/startup/bl_ui/__init__.py1
-rw-r--r--release/scripts/startup/bl_ui/properties_data_modifier.py23
-rw-r--r--release/scripts/startup/bl_ui/properties_hair_common.py61
-rw-r--r--release/scripts/startup/bl_ui/space_view3d_toolbar.py1
-rw-r--r--source/blender/CMakeLists.txt1
-rw-r--r--source/blender/blenkernel/BKE_hair.h238
-rw-r--r--source/blender/blenkernel/BKE_mesh_sample.h116
-rw-r--r--source/blender/blenkernel/BKE_particle.h2
-rw-r--r--source/blender/blenkernel/CMakeLists.txt5
-rw-r--r--source/blender/blenkernel/intern/hair.c721
-rw-r--r--source/blender/blenkernel/intern/hair_draw.c314
-rw-r--r--source/blender/blenkernel/intern/library.c4
-rw-r--r--source/blender/blenkernel/intern/mesh_sample.c1673
-rw-r--r--source/blender/blenkernel/intern/particle.c11
-rw-r--r--source/blender/blenlib/BLI_math_geom.h2
-rw-r--r--source/blender/blenlib/intern/math_geom.c65
-rw-r--r--source/blender/blenloader/intern/readfile.c30
-rw-r--r--source/blender/blenloader/intern/writefile.c25
-rw-r--r--source/blender/draw/CMakeLists.txt1
-rw-r--r--source/blender/draw/engines/eevee/eevee_materials.c347
-rw-r--r--source/blender/draw/engines/workbench/shaders/workbench_prepass_vert.glsl3
-rw-r--r--source/blender/draw/engines/workbench/workbench_deferred.c2
-rw-r--r--source/blender/draw/engines/workbench/workbench_forward.c4
-rw-r--r--source/blender/draw/intern/draw_cache.c14
-rw-r--r--source/blender/draw/intern/draw_cache.h6
-rw-r--r--source/blender/draw/intern/draw_cache_impl.h9
-rw-r--r--source/blender/draw/intern/draw_cache_impl_hair.c556
-rw-r--r--source/blender/draw/intern/draw_cache_impl_particles.c140
-rw-r--r--source/blender/draw/intern/draw_common.h22
-rw-r--r--source/blender/draw/intern/draw_hair.c130
-rw-r--r--source/blender/draw/intern/draw_hair_private.h22
-rw-r--r--source/blender/draw/intern/draw_manager.c6
-rw-r--r--source/blender/draw/modes/object_mode.c19
-rw-r--r--source/blender/draw/modes/shaders/common_hair_lib.glsl70
-rw-r--r--source/blender/editors/object/object_intern.h1
-rw-r--r--source/blender/editors/object/object_modifier.c88
-rw-r--r--source/blender/editors/object/object_ops.c1
-rw-r--r--source/blender/editors/space_outliner/outliner_draw.c3
-rw-r--r--source/blender/gpu/GPU_texture.h2
-rw-r--r--source/blender/makesdna/DNA_hair_types.h136
-rw-r--r--source/blender/makesdna/DNA_meshdata_types.h7
-rw-r--r--source/blender/makesdna/DNA_modifier_types.h29
-rw-r--r--source/blender/makesdna/intern/makesdna.c2
-rw-r--r--source/blender/makesrna/RNA_access.h3
-rw-r--r--source/blender/makesrna/intern/CMakeLists.txt2
-rw-r--r--source/blender/makesrna/intern/makesrna.c2
-rw-r--r--source/blender/makesrna/intern/rna_hair.c222
-rw-r--r--source/blender/makesrna/intern/rna_internal.h2
-rw-r--r--source/blender/makesrna/intern/rna_mesh_sample.c73
-rw-r--r--source/blender/makesrna/intern/rna_modifier.c145
-rw-r--r--source/blender/modifiers/CMakeLists.txt1
-rw-r--r--source/blender/modifiers/MOD_modifiertypes.h1
-rw-r--r--source/blender/modifiers/intern/MOD_hair.c169
-rw-r--r--source/blender/modifiers/intern/MOD_util.c1
-rw-r--r--tests/gtests/CMakeLists.txt1
-rw-r--r--tests/gtests/blenkernel/BKE_mesh_sample_test.cc334
-rw-r--r--tests/gtests/blenkernel/BKE_mesh_test_util.cc319
-rw-r--r--tests/gtests/blenkernel/BKE_mesh_test_util.h17
-rw-r--r--tests/gtests/blenkernel/CMakeLists.txt57
62 files changed, 6407 insertions, 388 deletions
diff --git a/intern/cycles/blender/blender_curves.cpp b/intern/cycles/blender/blender_curves.cpp
index a1fd153b4fd..9e0921a8f6c 100644
--- a/intern/cycles/blender/blender_curves.cpp
+++ b/intern/cycles/blender/blender_curves.cpp
@@ -110,154 +110,342 @@ static void InterpolateKeySegments(int seg,
curveinterp_v3_v3v3v3v3(keyloc, &ckey_loc1, &ckey_loc2, &ckey_loc3, &ckey_loc4, t);
}
-static bool ObtainCacheParticleData(Mesh *mesh,
- BL::Mesh *b_mesh,
- BL::Object *b_ob,
- ParticleCurveData *CData,
- bool background)
+static void ObtainCacheDataFromParticleSystem(Mesh *mesh,
+ BL::Object *b_ob,
+ BL::ParticleSystemModifier *b_psmd,
+ ParticleCurveData *CData,
+ bool background,
+ int *curvenum,
+ int *keyno)
{
- int curvenum = 0;
- int keyno = 0;
-
- if(!(mesh && b_mesh && b_ob && CData))
- return false;
+ BL::ParticleSystem b_psys((const PointerRNA)b_psmd->particle_system().ptr);
+ BL::ParticleSettings b_part((const PointerRNA)b_psys.settings().ptr);
Transform tfm = get_transform(b_ob->matrix_world());
Transform itfm = transform_quick_inverse(tfm);
- BL::Object::modifiers_iterator b_mod;
- for(b_ob->modifiers.begin(b_mod); b_mod != b_ob->modifiers.end(); ++b_mod) {
- if((b_mod->type() == b_mod->type_PARTICLE_SYSTEM) && (background ? b_mod->show_render() : b_mod->show_viewport())) {
- BL::ParticleSystemModifier psmd((const PointerRNA)b_mod->ptr);
- BL::ParticleSystem b_psys((const PointerRNA)psmd.particle_system().ptr);
- BL::ParticleSettings b_part((const PointerRNA)b_psys.settings().ptr);
-
- if((b_part.render_type() == BL::ParticleSettings::render_type_PATH) && (b_part.type() == BL::ParticleSettings::type_HAIR)) {
- int shader = clamp(b_part.material()-1, 0, mesh->used_shaders.size()-1);
- int draw_step = background ? b_part.render_step() : b_part.draw_step();
- int totparts = b_psys.particles.length();
- int totchild = background ? b_psys.child_particles.length() : (int)((float)b_psys.child_particles.length() * (float)b_part.draw_percentage() / 100.0f);
- int totcurves = totchild;
-
- if(b_part.child_type() == 0 || totchild == 0)
- totcurves += totparts;
-
- if(totcurves == 0)
+ if((b_part.type() != BL::ParticleSettings::type_HAIR) ||
+ (b_part.render_type() != BL::ParticleSettings::render_type_PATH))
+ {
+ return;
+ }
+
+ int shader = clamp(b_part.material()-1, 0, mesh->used_shaders.size()-1);
+ int draw_step = background ? b_part.render_step() : b_part.draw_step();
+ int totparts = b_psys.particles.length();
+ int totchild = background ? b_psys.child_particles.length() : (int)((float)b_psys.child_particles.length() * (float)b_part.draw_percentage() / 100.0f);
+ int totcurves = totchild;
+
+ if(b_part.child_type() == 0 || totchild == 0)
+ totcurves += totparts;
+
+ if(totcurves == 0)
+ return;
+
+ int ren_step = (1 << draw_step) + 1;
+ if(b_part.kink() == BL::ParticleSettings::kink_SPIRAL)
+ ren_step += b_part.kink_extra_steps();
+
+ CData->psys_firstcurve.push_back_slow(*curvenum);
+ CData->psys_curvenum.push_back_slow(totcurves);
+ CData->psys_shader.push_back_slow(shader);
+
+ float radius = b_part.radius_scale() * 0.5f;
+
+ CData->psys_rootradius.push_back_slow(radius * b_part.root_radius());
+ CData->psys_tipradius.push_back_slow(radius * b_part.tip_radius());
+ CData->psys_shape.push_back_slow(b_part.shape());
+ CData->psys_closetip.push_back_slow(b_part.use_close_tip());
+
+ int pa_no = 0;
+ if(!(b_part.child_type() == 0) && totchild != 0)
+ pa_no = totparts;
+
+ int num_add = (totparts+totchild - pa_no);
+ CData->curve_firstkey.reserve(CData->curve_firstkey.size() + num_add);
+ CData->curve_keynum.reserve(CData->curve_keynum.size() + num_add);
+ CData->curve_length.reserve(CData->curve_length.size() + num_add);
+ CData->curvekey_co.reserve(CData->curvekey_co.size() + num_add*ren_step);
+ CData->curvekey_time.reserve(CData->curvekey_time.size() + num_add*ren_step);
+
+ for(; pa_no < totparts+totchild; pa_no++) {
+ int keynum = 0;
+ CData->curve_firstkey.push_back_slow(*keyno);
+
+ float curve_length = 0.0f;
+ float3 pcKey;
+ for(int step_no = 0; step_no < ren_step; step_no++) {
+ float nco[3];
+ b_psys.co_hair(*b_ob, pa_no, step_no, nco);
+ float3 cKey = make_float3(nco[0], nco[1], nco[2]);
+ cKey = transform_point(&itfm, cKey);
+ if(step_no > 0) {
+ float step_length = len(cKey - pcKey);
+ if(step_length == 0.0f)
continue;
-
- int ren_step = (1 << draw_step) + 1;
- if(b_part.kink() == BL::ParticleSettings::kink_SPIRAL)
- ren_step += b_part.kink_extra_steps();
-
- CData->psys_firstcurve.push_back_slow(curvenum);
- CData->psys_curvenum.push_back_slow(totcurves);
- CData->psys_shader.push_back_slow(shader);
-
- float radius = b_part.radius_scale() * 0.5f;
-
- CData->psys_rootradius.push_back_slow(radius * b_part.root_radius());
- CData->psys_tipradius.push_back_slow(radius * b_part.tip_radius());
- CData->psys_shape.push_back_slow(b_part.shape());
- CData->psys_closetip.push_back_slow(b_part.use_close_tip());
-
- int pa_no = 0;
- if(!(b_part.child_type() == 0) && totchild != 0)
- pa_no = totparts;
-
- int num_add = (totparts+totchild - pa_no);
- CData->curve_firstkey.reserve(CData->curve_firstkey.size() + num_add);
- CData->curve_keynum.reserve(CData->curve_keynum.size() + num_add);
- CData->curve_length.reserve(CData->curve_length.size() + num_add);
- CData->curvekey_co.reserve(CData->curvekey_co.size() + num_add*ren_step);
- CData->curvekey_time.reserve(CData->curvekey_time.size() + num_add*ren_step);
-
- for(; pa_no < totparts+totchild; pa_no++) {
- int keynum = 0;
- CData->curve_firstkey.push_back_slow(keyno);
-
- float curve_length = 0.0f;
- float3 pcKey;
- for(int step_no = 0; step_no < ren_step; step_no++) {
- float nco[3];
- b_psys.co_hair(*b_ob, pa_no, step_no, nco);
- float3 cKey = make_float3(nco[0], nco[1], nco[2]);
- cKey = transform_point(&itfm, cKey);
- if(step_no > 0) {
- float step_length = len(cKey - pcKey);
- if(step_length == 0.0f)
- continue;
- curve_length += step_length;
- }
- CData->curvekey_co.push_back_slow(cKey);
- CData->curvekey_time.push_back_slow(curve_length);
- pcKey = cKey;
- keynum++;
- }
- keyno += keynum;
-
- CData->curve_keynum.push_back_slow(keynum);
- CData->curve_length.push_back_slow(curve_length);
- curvenum++;
- }
+ curve_length += step_length;
}
+ CData->curvekey_co.push_back_slow(cKey);
+ CData->curvekey_time.push_back_slow(curve_length);
+ pcKey = cKey;
+ keynum++;
}
+ *keyno += keynum;
+
+ CData->curve_keynum.push_back_slow(keynum);
+ CData->curve_length.push_back_slow(curve_length);
+ (*curvenum)++;
}
-
- return true;
}
-static bool ObtainCacheParticleUV(Mesh *mesh,
- BL::Mesh *b_mesh,
- BL::Object *b_ob,
- ParticleCurveData *CData,
- bool background,
- int uv_num)
+static void ObtainCacheUVFromParticleSystem(BL::Mesh *b_mesh,
+ BL::ParticleSystemModifier *b_psmd,
+ ParticleCurveData *CData,
+ bool background,
+ int uv_num)
{
- if(!(mesh && b_mesh && b_ob && CData))
- return false;
-
- CData->curve_uv.clear();
+ BL::ParticleSystem b_psys((const PointerRNA)b_psmd->particle_system().ptr);
+ BL::ParticleSettings b_part((const PointerRNA)b_psys.settings().ptr);
- BL::Object::modifiers_iterator b_mod;
- for(b_ob->modifiers.begin(b_mod); b_mod != b_ob->modifiers.end(); ++b_mod) {
- if((b_mod->type() == b_mod->type_PARTICLE_SYSTEM) && (background ? b_mod->show_render() : b_mod->show_viewport())) {
- BL::ParticleSystemModifier psmd((const PointerRNA)b_mod->ptr);
- BL::ParticleSystem b_psys((const PointerRNA)psmd.particle_system().ptr);
- BL::ParticleSettings b_part((const PointerRNA)b_psys.settings().ptr);
+ if((b_part.type() != BL::ParticleSettings::type_HAIR) ||
+ (b_part.render_type() != BL::ParticleSettings::render_type_PATH))
+ {
+ return;
+ }
- if((b_part.render_type() == BL::ParticleSettings::render_type_PATH) && (b_part.type() == BL::ParticleSettings::type_HAIR)) {
- int totparts = b_psys.particles.length();
- int totchild = background ? b_psys.child_particles.length() : (int)((float)b_psys.child_particles.length() * (float)b_part.draw_percentage() / 100.0f);
- int totcurves = totchild;
+ int totparts = b_psys.particles.length();
+ int totchild = background ? b_psys.child_particles.length() : (int)((float)b_psys.child_particles.length() * (float)b_part.draw_percentage() / 100.0f);
+ int totcurves = totchild;
+
+ if(b_part.child_type() == 0 || totchild == 0)
+ totcurves += totparts;
+
+ if(totcurves == 0)
+ return;
+
+ int pa_no = 0;
+ if(!(b_part.child_type() == 0) && totchild != 0)
+ pa_no = totparts;
+
+ int num_add = (totparts+totchild - pa_no);
+ CData->curve_uv.reserve(CData->curve_uv.size() + num_add);
+
+ BL::ParticleSystem::particles_iterator b_pa;
+ b_psys.particles.begin(b_pa);
+ for(; pa_no < totparts+totchild; pa_no++) {
+ /* Add UVs */
+ BL::Mesh::tessface_uv_textures_iterator l;
+ b_mesh->tessface_uv_textures.begin(l);
+
+ float3 uv = make_float3(0.0f, 0.0f, 0.0f);
+ if(b_mesh->tessface_uv_textures.length())
+ b_psys.uv_on_emitter(*b_psmd, *b_pa, pa_no, uv_num, &uv.x);
+ CData->curve_uv.push_back_slow(uv);
+
+ if(pa_no < totparts && b_pa != b_psys.particles.end())
+ ++b_pa;
+ }
+}
- if(b_part.child_type() == 0 || totchild == 0)
- totcurves += totparts;
+static void ObtainCacheVColFromParticleSystem(BL::Mesh *b_mesh,
+ BL::ParticleSystemModifier *b_psmd,
+ ParticleCurveData *CData,
+ bool background,
+ int vcol_num)
+{
+ BL::ParticleSystem b_psys((const PointerRNA)b_psmd->particle_system().ptr);
+ BL::ParticleSettings b_part((const PointerRNA)b_psys.settings().ptr);
- if(totcurves == 0)
- continue;
+ if((b_part.type() != BL::ParticleSettings::type_HAIR) ||
+ (b_part.render_type() != BL::ParticleSettings::render_type_PATH))
+ {
+ return;
+ }
- int pa_no = 0;
- if(!(b_part.child_type() == 0) && totchild != 0)
- pa_no = totparts;
+ int totparts = b_psys.particles.length();
+ int totchild = background ? b_psys.child_particles.length() : (int)((float)b_psys.child_particles.length() * (float)b_part.draw_percentage() / 100.0f);
+ int totcurves = totchild;
+
+ if(b_part.child_type() == 0 || totchild == 0)
+ totcurves += totparts;
+
+ if(totcurves == 0)
+ return;
+
+ int pa_no = 0;
+ if(!(b_part.child_type() == 0) && totchild != 0)
+ pa_no = totparts;
+
+ int num_add = (totparts+totchild - pa_no);
+ CData->curve_vcol.reserve(CData->curve_vcol.size() + num_add);
+
+ BL::ParticleSystem::particles_iterator b_pa;
+ b_psys.particles.begin(b_pa);
+ for(; pa_no < totparts+totchild; pa_no++) {
+ /* Add vertex colors */
+ BL::Mesh::tessface_vertex_colors_iterator l;
+ b_mesh->tessface_vertex_colors.begin(l);
+
+ float3 vcol = make_float3(0.0f, 0.0f, 0.0f);
+ if(b_mesh->tessface_vertex_colors.length())
+ b_psys.mcol_on_emitter(*b_psmd, *b_pa, pa_no, vcol_num, &vcol.x);
+ CData->curve_vcol.push_back_slow(vcol);
+
+ if(pa_no < totparts && b_pa != b_psys.particles.end())
+ ++b_pa;
+ }
+}
- int num_add = (totparts+totchild - pa_no);
- CData->curve_uv.reserve(CData->curve_uv.size() + num_add);
+static void ObtainCacheDataFromHairSystem(BL::Object *b_ob,
+ BL::HairSystem *b_hsys,
+ BL::Mesh *b_scalp,
+ int shader,
+ bool /*background*/,
+ ParticleCurveData *CData,
+ int *curvenum,
+ int *keyno)
+{
+ Transform tfm = get_transform(b_ob->matrix_world());
+ Transform itfm = transform_quick_inverse(tfm);
+
+ void *hair_cache = BKE_hair_export_cache_new();
+ BKE_hair_export_cache_update(hair_cache, b_hsys->ptr.data, 0, b_scalp->ptr.data, 0xFFFFFFFF);
+
+ int totcurves, totverts;
+ BKE_hair_render_get_buffer_size(hair_cache, &totcurves, &totverts);
+
+ if(totcurves == 0)
+ {
+ BKE_hair_export_cache_free(hair_cache);
+ return;
+ }
+
+ CData->psys_firstcurve.push_back_slow(*curvenum);
+ CData->psys_curvenum.push_back_slow(totcurves);
+
+ // Material
+ CData->psys_shader.push_back_slow(shader);
+
+ {
+ // Cycles settings
+// PointerRNA cpsys = RNA_pointer_get(&b_hsys->ptr, "cycles");
+// float radius = get_float(cpsys, "radius_scale") * 0.5f;
+// CData->psys_rootradius.push_back_slow(radius * get_float(cpsys, "root_width"));
+// CData->psys_tipradius.push_back_slow(radius * get_float(cpsys, "tip_width"));
+// CData->psys_shape.push_back_slow(get_float(cpsys, "shape"));
+// CData->psys_closetip.push_back_slow(get_boolean(cpsys, "use_closetip"));
+ float radius = 0.01f * 0.5f;
+ CData->psys_rootradius.push_back_slow(radius * 1.0f);
+ CData->psys_tipradius.push_back_slow(radius * 0.0f);
+ CData->psys_shape.push_back_slow(0.0f);
+ CData->psys_closetip.push_back_slow(true);
+ }
+
+ // Allocate buffers
+ int *firstkey_data;
+ int *keynum_data;
+ float *length_data;
+ float3 *co_data;
+ float *time_data;
+ {
+ const size_t firstkey_start = CData->curve_firstkey.size();
+ const size_t keynum_start = CData->curve_keynum.size();
+ const size_t length_start = CData->curve_length.size();
+ const size_t co_start = CData->curvekey_co.size();
+ const size_t time_start = CData->curvekey_time.size();
+ CData->curve_firstkey.resize(firstkey_start + totcurves);
+ CData->curve_keynum.resize(keynum_start + totcurves);
+ CData->curve_length.resize(length_start + totcurves);
+ CData->curvekey_co.resize(co_start + totverts);
+ CData->curvekey_time.resize(time_start + totverts);
+ firstkey_data = CData->curve_firstkey.data() + firstkey_start;
+ keynum_data = CData->curve_keynum.data() + keynum_start;
+ length_data = CData->curve_length.data() + length_start;
+ co_data = CData->curvekey_co.data() + co_start;
+ time_data = CData->curvekey_time.data() + time_start;
+ }
+
+ // Import render curves from hair system
+ BKE_hair_render_fill_buffers(
+ hair_cache,
+ (int)sizeof(float3),
+ firstkey_data,
+ keynum_data,
+ (float*)co_data);
+
+ // Compute curve length and key times
+ for(int c = 0; c < totcurves; ++c) {
+ const int firstkey = firstkey_data[c];
+ const int keynum = keynum_data[c];
+
+ float curve_length = 0.0f;
+ float3 pcKey;
+ for(int v = 0; v < keynum; v++) {
+ float3 cKey = co_data[firstkey + v];
+ cKey = transform_point(&itfm, cKey);
+ if(v > 0) {
+ float step_length = len(cKey - pcKey);
+ if(step_length == 0.0f)
+ continue;
+ curve_length += step_length;
+ }
+
+ co_data[firstkey + v] = cKey;
+ time_data[v] = curve_length;
+ pcKey = cKey;
+ }
+
+ firstkey_data[c] = *keyno;
+ length_data[c] = curve_length;
+ *keyno += keynum;
+ }
+ *curvenum += totcurves;
+
+ BKE_hair_export_cache_free(hair_cache);
+}
- BL::ParticleSystem::particles_iterator b_pa;
- b_psys.particles.begin(b_pa);
- for(; pa_no < totparts+totchild; pa_no++) {
- /* Add UVs */
- BL::Mesh::tessface_uv_textures_iterator l;
- b_mesh->tessface_uv_textures.begin(l);
+static bool ObtainCacheDataFromObject(Mesh *mesh,
+ BL::Object *b_ob,
+ ParticleCurveData *CData,
+ bool background)
+{
+ int curvenum = 0;
+ int keyno = 0;
- float3 uv = make_float3(0.0f, 0.0f, 0.0f);
- if(b_mesh->tessface_uv_textures.length())
- b_psys.uv_on_emitter(psmd, *b_pa, pa_no, uv_num, &uv.x);
- CData->curve_uv.push_back_slow(uv);
+ if(!(mesh && b_ob && CData))
+ return false;
- if(pa_no < totparts && b_pa != b_psys.particles.end())
- ++b_pa;
- }
+ BL::Object::modifiers_iterator b_mod;
+ for(b_ob->modifiers.begin(b_mod); b_mod != b_ob->modifiers.end(); ++b_mod) {
+ if (background ? b_mod->show_render() : b_mod->show_viewport())
+ {
+ if((b_mod->type() == b_mod->type_PARTICLE_SYSTEM)) {
+ BL::ParticleSystemModifier b_psmd((const PointerRNA)b_mod->ptr);
+ ObtainCacheDataFromParticleSystem(mesh,
+ b_ob,
+ &b_psmd,
+ CData,
+ background,
+ &curvenum,
+ &keyno);
+ }
+ if((b_mod->type() == b_mod->type_HAIR)) {
+ BL::HairModifier b_hmd((const PointerRNA)b_mod->ptr);
+ BL::HairSystem b_hsys = b_hmd.hair_system();
+
+ const int material_index = 1; /* TODO */
+ int shader = clamp(material_index - 1, 0, mesh->used_shaders.size()-1);
+
+ BL::Mesh b_scalp(b_ob->data());
+
+ ObtainCacheDataFromHairSystem(b_ob,
+ &b_hsys,
+ &b_scalp,
+ shader,
+ background,
+ CData,
+ &curvenum,
+ &keyno);
}
}
}
@@ -265,58 +453,52 @@ static bool ObtainCacheParticleUV(Mesh *mesh,
return true;
}
-static bool ObtainCacheParticleVcol(Mesh *mesh,
+static bool ObtainCacheUVFromObject(Mesh *mesh,
BL::Mesh *b_mesh,
BL::Object *b_ob,
ParticleCurveData *CData,
bool background,
- int vcol_num)
+ int uv_num)
{
if(!(mesh && b_mesh && b_ob && CData))
return false;
- CData->curve_vcol.clear();
+ CData->curve_uv.clear();
BL::Object::modifiers_iterator b_mod;
for(b_ob->modifiers.begin(b_mod); b_mod != b_ob->modifiers.end(); ++b_mod) {
- if((b_mod->type() == b_mod->type_PARTICLE_SYSTEM) && (background ? b_mod->show_render() : b_mod->show_viewport())) {
- BL::ParticleSystemModifier psmd((const PointerRNA)b_mod->ptr);
- BL::ParticleSystem b_psys((const PointerRNA)psmd.particle_system().ptr);
- BL::ParticleSettings b_part((const PointerRNA)b_psys.settings().ptr);
-
- if((b_part.render_type() == BL::ParticleSettings::render_type_PATH) && (b_part.type() == BL::ParticleSettings::type_HAIR)) {
- int totparts = b_psys.particles.length();
- int totchild = background ? b_psys.child_particles.length() : (int)((float)b_psys.child_particles.length() * (float)b_part.draw_percentage() / 100.0f);
- int totcurves = totchild;
-
- if(b_part.child_type() == 0 || totchild == 0)
- totcurves += totparts;
-
- if(totcurves == 0)
- continue;
-
- int pa_no = 0;
- if(!(b_part.child_type() == 0) && totchild != 0)
- pa_no = totparts;
+ if (background ? b_mod->show_render() : b_mod->show_viewport())
+ {
+ if((b_mod->type() == b_mod->type_PARTICLE_SYSTEM)) {
+ BL::ParticleSystemModifier b_psmd((const PointerRNA)b_mod->ptr);
+ ObtainCacheUVFromParticleSystem(b_mesh, &b_psmd, CData, background, uv_num);
+ }
+ }
+ }
- int num_add = (totparts+totchild - pa_no);
- CData->curve_vcol.reserve(CData->curve_vcol.size() + num_add);
+ return true;
+}
- BL::ParticleSystem::particles_iterator b_pa;
- b_psys.particles.begin(b_pa);
- for(; pa_no < totparts+totchild; pa_no++) {
- /* Add vertex colors */
- BL::Mesh::tessface_vertex_colors_iterator l;
- b_mesh->tessface_vertex_colors.begin(l);
+static bool ObtainCacheVcolFromObject(Mesh *mesh,
+ BL::Mesh *b_mesh,
+ BL::Object *b_ob,
+ ParticleCurveData *CData,
+ bool background,
+ int vcol_num)
+{
+ if(!(mesh && b_mesh && b_ob && CData))
+ return false;
- float3 vcol = make_float3(0.0f, 0.0f, 0.0f);
- if(b_mesh->tessface_vertex_colors.length())
- b_psys.mcol_on_emitter(psmd, *b_pa, pa_no, vcol_num, &vcol.x);
- CData->curve_vcol.push_back_slow(vcol);
+ CData->curve_vcol.clear();
- if(pa_no < totparts && b_pa != b_psys.particles.end())
- ++b_pa;
- }
+ BL::Object::modifiers_iterator b_mod;
+ for(b_ob->modifiers.begin(b_mod); b_mod != b_ob->modifiers.end(); ++b_mod) {
+ if (background ? b_mod->show_render() : b_mod->show_viewport())
+ {
+ if((b_mod->type() == b_mod->type_PARTICLE_SYSTEM))
+ {
+ BL::ParticleSystemModifier b_psmd((const PointerRNA)b_mod->ptr);
+ ObtainCacheVColFromParticleSystem(b_mesh, &b_psmd, CData, background, vcol_num);
}
}
}
@@ -906,7 +1088,7 @@ void BlenderSync::sync_curves(Mesh *mesh,
ParticleCurveData CData;
- ObtainCacheParticleData(mesh, &b_mesh, &b_ob, &CData, !preview);
+ ObtainCacheDataFromObject(mesh, &b_ob, &CData, !preview);
/* add hair geometry to mesh */
if(primitive == CURVE_TRIANGLES) {
@@ -942,7 +1124,7 @@ void BlenderSync::sync_curves(Mesh *mesh,
/* generated coordinates from first key. we should ideally get this from
* blender to handle deforming objects */
- if(!motion) {
+ if(b_mesh && !motion) {
if(mesh->need_attribute(scene, ATTR_STD_GENERATED)) {
float3 loc, size;
mesh_texture_space(b_mesh, loc, size);
@@ -967,7 +1149,7 @@ void BlenderSync::sync_curves(Mesh *mesh,
}
/* create vertex color attributes */
- if(!motion) {
+ if(b_mesh && !motion) {
BL::Mesh::tessface_vertex_colors_iterator l;
int vcol_num = 0;
@@ -975,7 +1157,7 @@ void BlenderSync::sync_curves(Mesh *mesh,
if(!mesh->need_attribute(scene, ustring(l->name().c_str())))
continue;
- ObtainCacheParticleVcol(mesh, &b_mesh, &b_ob, &CData, !preview, vcol_num);
+ ObtainCacheVcolFromObject(mesh, &b_mesh, &b_ob, &CData, !preview, vcol_num);
if(primitive == CURVE_TRIANGLES) {
Attribute *attr_vcol = mesh->attributes.add(
@@ -1004,7 +1186,7 @@ void BlenderSync::sync_curves(Mesh *mesh,
}
/* create UV attributes */
- if(!motion) {
+ if(b_mesh && !motion) {
BL::Mesh::tessface_uv_textures_iterator l;
int uv_num = 0;
@@ -1017,7 +1199,7 @@ void BlenderSync::sync_curves(Mesh *mesh,
if(mesh->need_attribute(scene, name) || mesh->need_attribute(scene, std)) {
Attribute *attr_uv;
- ObtainCacheParticleUV(mesh, &b_mesh, &b_ob, &CData, !preview, uv_num);
+ ObtainCacheUVFromObject(mesh, &b_mesh, &b_ob, &CData, !preview, uv_num);
if(primitive == CURVE_TRIANGLES) {
if(active_render)
diff --git a/intern/cycles/blender/blender_mesh.cpp b/intern/cycles/blender/blender_mesh.cpp
index 8a6480a9a42..1d0e8fc6ace 100644
--- a/intern/cycles/blender/blender_mesh.cpp
+++ b/intern/cycles/blender/blender_mesh.cpp
@@ -1188,10 +1188,12 @@ Mesh *BlenderSync::sync_mesh(BL::Depsgraph& b_depsgraph,
create_mesh_volume_attributes(scene, b_ob, mesh, b_scene.frame_current());
}
+ }
- if(view_layer.use_hair && mesh->subdivision_type == Mesh::SUBDIVISION_NONE)
- sync_curves(mesh, b_mesh, b_ob, false);
+ if(view_layer.use_hair && mesh->subdivision_type == Mesh::SUBDIVISION_NONE)
+ sync_curves(mesh, b_mesh, b_ob, false);
+ if(b_mesh) {
/* free derived mesh */
b_data.meshes.remove(b_mesh, false, true, false);
}
diff --git a/intern/cycles/blender/blender_util.h b/intern/cycles/blender/blender_util.h
index 4e754d22984..4cf36e1bd88 100644
--- a/intern/cycles/blender/blender_util.h
+++ b/intern/cycles/blender/blender_util.h
@@ -36,6 +36,11 @@ void BKE_image_user_frame_calc(void *iuser, int cfra, int fieldnr);
void BKE_image_user_file_path(void *iuser, void *ima, char *path);
unsigned char *BKE_image_get_pixels_for_frame(void *image, int frame);
float *BKE_image_get_float_pixels_for_frame(void *image, int frame);
+void* BKE_hair_export_cache_new(void);
+int BKE_hair_export_cache_update(void *cache, const void *hsys, int subdiv, void *scalp, int requested_data);
+void BKE_hair_export_cache_free(void *hcache);
+void BKE_hair_render_get_buffer_size(void* hcache, int *r_totcurves, int *r_totverts);
+void BKE_hair_render_fill_buffers(void* hcache, int vertco_stride, int *r_curvestart, int *r_curvelen, float *r_vertco);
}
CCL_NAMESPACE_BEGIN
diff --git a/release/scripts/startup/bl_ui/__init__.py b/release/scripts/startup/bl_ui/__init__.py
index 89aed37f055..3b0cc2527a8 100644
--- a/release/scripts/startup/bl_ui/__init__.py
+++ b/release/scripts/startup/bl_ui/__init__.py
@@ -44,6 +44,7 @@ _modules = [
"properties_data_lightprobe",
"properties_data_speaker",
"properties_data_workspace",
+ "properties_hair_common",
"properties_mask_common",
"properties_material",
"properties_material_gpencil",
diff --git a/release/scripts/startup/bl_ui/properties_data_modifier.py b/release/scripts/startup/bl_ui/properties_data_modifier.py
index 93e789bbd19..e2a537acadd 100644
--- a/release/scripts/startup/bl_ui/properties_data_modifier.py
+++ b/release/scripts/startup/bl_ui/properties_data_modifier.py
@@ -20,7 +20,7 @@
import bpy
from bpy.types import Panel
from bpy.app.translations import pgettext_iface as iface_
-
+from .properties_hair_common import draw_hair_display_settings
class ModifierButtonsPanel:
bl_space_type = 'PROPERTIES'
@@ -1577,6 +1577,27 @@ class DATA_PT_modifiers(ModifierButtonsPanel, Panel):
if md.rest_source == 'BIND':
layout.operator("object.correctivesmooth_bind", text="Unbind" if is_bind else "Bind")
+ def HAIR(self, layout, ob, md):
+ hsys = md.hair_system
+
+ split = layout.split()
+
+ col = split.column()
+ col.label("Follicles:")
+ col.prop(md, "follicle_seed")
+ col.prop(md, "follicle_count")
+ col.operator("object.hair_generate_follicles", text="Generate")
+
+ col = split.column()
+
+ col.separator()
+
+ col.prop(hsys, "material_slot", text="")
+
+ col = layout.column()
+ col.label("Display Settings:")
+ draw_hair_display_settings(col, md.draw_settings)
+
def WEIGHTED_NORMAL(self, layout, ob, md):
layout.label("Weighting Mode:")
split = layout.split(align=True)
diff --git a/release/scripts/startup/bl_ui/properties_hair_common.py b/release/scripts/startup/bl_ui/properties_hair_common.py
new file mode 100644
index 00000000000..4ebfa464923
--- /dev/null
+++ b/release/scripts/startup/bl_ui/properties_hair_common.py
@@ -0,0 +1,61 @@
+# ##### 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 #####
+
+# <pep8-80 compliant>
+
+import bpy
+
+
+def draw_hair_display_settings(layout, settings):
+ col = layout.column(align=True)
+ col.label("Follicles:")
+ col.prop(settings, "follicle_mode", expand=True)
+
+ col = layout.column(align=True)
+ col.label("Guide Curves:")
+ col.prop(settings, "guide_mode", expand=True)
+
+ layout.prop(settings, "shape")
+
+ col = layout.column(align=True)
+ col.prop(settings, "root_radius")
+ col.prop(settings, "tip_radius")
+
+ col = layout.column()
+ col.prop(settings, "radius_scale")
+ col.prop(settings, "use_close_tip")
+
+
+class HAIR_PT_display_settings:
+ # subclasses must define...
+ # ~ bl_space_type = 'PROPERTIES'
+ # ~ bl_region_type = 'WINDOW'
+ bl_label = "Hair Display Settings"
+
+ def draw(self, context):
+ settings = context.draw_hair_display_settings
+ draw_hair_display_settings(self.layout, hair_display_settings)
+
+
+classes = (
+)
+
+if __name__ == "__main__": # only for live edit.
+ from bpy.utils import register_class
+ for cls in classes:
+ register_class(cls)
diff --git a/release/scripts/startup/bl_ui/space_view3d_toolbar.py b/release/scripts/startup/bl_ui/space_view3d_toolbar.py
index 66f15f6a8ce..febe95c5522 100644
--- a/release/scripts/startup/bl_ui/space_view3d_toolbar.py
+++ b/release/scripts/startup/bl_ui/space_view3d_toolbar.py
@@ -78,7 +78,6 @@ def is_not_gpencil_edit_mode(context):
)
return not is_gpmode
-
# ********** default tools for editmode_mesh ****************
diff --git a/source/blender/CMakeLists.txt b/source/blender/CMakeLists.txt
index 2c9b1efe2f4..f46283aced5 100644
--- a/source/blender/CMakeLists.txt
+++ b/source/blender/CMakeLists.txt
@@ -49,6 +49,7 @@ set(SRC_DNA_INC
${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_shader_fx_types.h
${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_gpu_types.h
${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_group_types.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_hair_types.h
${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_image_types.h
${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_ipo_types.h
${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_key_types.h
diff --git a/source/blender/blenkernel/BKE_hair.h b/source/blender/blenkernel/BKE_hair.h
new file mode 100644
index 00000000000..e24effd9227
--- /dev/null
+++ b/source/blender/blenkernel/BKE_hair.h
@@ -0,0 +1,238 @@
+/*
+ * ***** 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.
+ *
+ * The Original Code is Copyright (C) Blender Foundation
+ * All rights reserved.
+ *
+ * The Original Code is: all of this file.
+ *
+ * Contributor(s): Lukas Toenne
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+#ifndef __BKE_HAIR_H__
+#define __BKE_HAIR_H__
+
+/** \file blender/blenkernel/BKE_hair.h
+ * \ingroup bke
+ */
+
+#include "BLI_utildefines.h"
+
+static const unsigned int HAIR_CURVE_INDEX_NONE = 0xFFFFFFFF;
+
+struct HairFollicle;
+struct HairPattern;
+struct HairSystem;
+struct HairDrawSettings;
+struct HairCurveData;
+struct Mesh;
+struct MeshSample;
+struct MLoop;
+struct Object;
+
+/* Create a new hair system instance */
+struct HairSystem* BKE_hair_new(void);
+/* Copy an existing hair system */
+struct HairSystem* BKE_hair_copy(struct HairSystem *hsys);
+/* Delete a hair system */
+void BKE_hair_free(struct HairSystem *hsys);
+
+/* === Fiber curves === */
+
+/* Allocate buffers for defining fiber curves
+ * \param totcurves Number of fiber curves to allocate
+ */
+void BKE_hair_fiber_curves_begin(struct HairSystem *hsys, int totcurves);
+
+/* Set properties of a fiber curve
+ * \param index Index of the fiber curve
+ * \param mesh_sample Origin of the fiber curve on the scalp mesh.
+ * \param numverts Number of vertices in this fiber curve
+ */
+void BKE_hair_set_fiber_curve(struct HairSystem *hsys, int index, int numverts,
+ float taper_length, float taper_thickness);
+
+/* Finalize fiber curve update */
+void BKE_hair_fiber_curves_end(struct HairSystem *hsys);
+
+/* Set properties of a fiber curve vertex
+ * \param index Index of the fiber curve vertex.
+ * \param flag Flags to set on the vertex.
+ * \param co Location of the vertex in object space.
+ */
+void BKE_hair_set_fiber_vertex(struct HairSystem *hsys, int index, int flag, const float co[3]);
+
+/* Set the hair fiber curve data used by the hair system.
+ */
+void BKE_hair_set_fiber_curves(struct HairSystem *hsys, struct HairCurveData *curves);
+
+/* Remove all fiber curves.
+ */
+void BKE_hair_clear_fiber_curves(struct HairSystem *hsys);
+
+/* === Follicles === */
+
+/* Calculate surface area of a scalp mesh */
+float BKE_hair_calc_surface_area(const struct Mesh *scalp);
+
+/* Calculate a density value based on surface area and sample count */
+float BKE_hair_calc_density_from_count(float area, int count);
+/* Calculate maximum sample count based on surface area and density */
+int BKE_hair_calc_max_count_from_density(float area, float density);
+
+/* Calculate a density value based on a minimum distance */
+float BKE_hair_calc_density_from_min_distance(float min_distance);
+/* Calculate a minimum distance based on density */
+float BKE_hair_calc_min_distance_from_density(float density);
+
+/* Distribute hair follicles on a scalp mesh */
+void BKE_hair_generate_follicles(
+ struct HairSystem* hsys,
+ struct Mesh *scalp,
+ unsigned int seed,
+ int count);
+
+/* Distribute hair follicles on a scalp mesh.
+ * Optional per-loop weights control follicle density on the scalp.
+ */
+void BKE_hair_generate_follicles_ex(
+ struct HairSystem* hsys,
+ struct Mesh *scalp,
+ unsigned int seed,
+ int count,
+ const float *loop_weights);
+
+bool BKE_hair_bind_follicles(struct HairSystem *hsys, const struct Mesh *scalp);
+
+/* === Draw Settings === */
+
+struct HairDrawSettings* BKE_hair_draw_settings_new(void);
+struct HairDrawSettings* BKE_hair_draw_settings_copy(struct HairDrawSettings *draw_settings);
+void BKE_hair_draw_settings_free(struct HairDrawSettings *draw_settings);
+
+/* === Export === */
+
+/* Intermediate data for export */
+typedef struct HairExportCache
+{
+ /* Per fiber curve data */
+ int totcurves;
+ struct HairFiberCurve *fiber_curves;
+
+ /* Per fiber vertex data */
+ int totverts;
+ struct HairFiberVertex *fiber_verts;
+ float (*fiber_tangents)[3]; /* Tangent vectors on fiber curves */
+ float (*fiber_normals)[3]; /* Normal vectors on fiber curves */
+
+ /* Per follicle data */
+ int totfollicles;
+ float (*follicle_root_position)[3]; /* Root position of each follicle */
+ const struct HairFollicle *follicles;
+} HairExportCache;
+
+/* Identifiers for data stored in hair export caches */
+typedef enum eHairExportCacheUpdateFlags
+{
+ /* Follicle placement on the scalp mesh */
+ HAIR_EXPORT_FOLLICLE_ROOT_POSITIONS = (1 << 0),
+ /* Follicle curve index */
+ HAIR_EXPORT_FOLLICLE_BINDING = (1 << 1),
+ /* Fiber vertex positions (deform only) */
+ HAIR_EXPORT_FIBER_VERTICES = (1 << 2),
+ /* Fiber curve number and vertex counts (topology changes) */
+ HAIR_EXPORT_FIBER_CURVES = (1 << 3),
+
+ HAIR_EXPORT_ALL =
+ HAIR_EXPORT_FOLLICLE_ROOT_POSITIONS |
+ HAIR_EXPORT_FOLLICLE_BINDING |
+ HAIR_EXPORT_FIBER_VERTICES |
+ HAIR_EXPORT_FIBER_CURVES,
+ HAIR_EXPORT_FIBERS =
+ HAIR_EXPORT_FIBER_VERTICES |
+ HAIR_EXPORT_FIBER_CURVES,
+ HAIR_EXPORT_FOLLICLES =
+ HAIR_EXPORT_FOLLICLE_ROOT_POSITIONS |
+ HAIR_EXPORT_FOLLICLE_BINDING,
+} eHairExportCacheUpdateFlags;
+
+/* Create a new export cache.
+ * This can be used to construct full fiber data for rendering.
+ */
+struct HairExportCache* BKE_hair_export_cache_new(void);
+
+/* Update an existing export cache to ensure it contains the requested data.
+ * Returns flags for data that has been updated.
+ */
+int BKE_hair_export_cache_update(struct HairExportCache *cache, const struct HairSystem *hsys,
+ int subdiv, struct Mesh *scalp, int requested_data);
+
+/* Free the given export cache */
+void BKE_hair_export_cache_free(struct HairExportCache *cache);
+
+/* Invalidate all data in a hair export cache */
+void BKE_hair_export_cache_clear(struct HairExportCache *cache);
+
+/* Invalidate part of the data in a hair export cache.
+ *
+ * Note some parts may get invalidated automatically based on internal dependencies.
+ */
+void BKE_hair_export_cache_invalidate(struct HairExportCache *cache, int invalidate);
+
+/* === Draw Cache === */
+
+enum {
+ BKE_HAIR_BATCH_DIRTY_FIBERS = (1 << 0),
+ BKE_HAIR_BATCH_DIRTY_STRANDS = (1 << 1),
+ BKE_HAIR_BATCH_DIRTY_ALL = 0xFFFF,
+};
+void BKE_hair_batch_cache_dirty(struct HairSystem* hsys, int mode);
+void BKE_hair_batch_cache_free(struct HairSystem* hsys);
+
+void BKE_hair_get_texture_buffer_size(
+ const struct HairExportCache *cache,
+ int *r_size,
+ int *r_strand_map_start,
+ int *r_strand_vertex_start,
+ int *r_fiber_start);
+void BKE_hair_get_texture_buffer(
+ const struct HairExportCache *cache,
+ void *texbuffer);
+
+/* === Render API === */
+
+/* Calculate required size for render buffers. */
+void BKE_hair_render_get_buffer_size(
+ const struct HairExportCache* cache,
+ int subdiv,
+ int *r_totcurves,
+ int *r_totverts);
+
+/* Create render data in existing buffers.
+ * Buffers must be large enough according to BKE_hair_get_render_buffer_size.
+ */
+void BKE_hair_render_fill_buffers(
+ const struct HairExportCache* cache,
+ int subdiv,
+ int vertco_stride,
+ int *r_curvestart,
+ int *r_curvelen,
+ float *r_vertco);
+
+#endif
diff --git a/source/blender/blenkernel/BKE_mesh_sample.h b/source/blender/blenkernel/BKE_mesh_sample.h
new file mode 100644
index 00000000000..8599b5a38ad
--- /dev/null
+++ b/source/blender/blenkernel/BKE_mesh_sample.h
@@ -0,0 +1,116 @@
+/*
+ * ***** 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 *****
+ */
+
+#ifndef __BKE_MESH_SAMPLE_H__
+#define __BKE_MESH_SAMPLE_H__
+
+/** \file BKE_mesh_sample.h
+ * \ingroup bke
+ */
+
+struct Mesh;
+struct Key;
+struct KeyBlock;
+struct Mesh;
+struct MFace;
+struct MVert;
+struct MPoly;
+
+struct MeshSample;
+struct MeshSampleGenerator;
+
+typedef struct MeshSampleGenerator MeshSampleGenerator;
+
+typedef void* (*MeshSampleThreadContextCreateFp)(void *userdata, int start);
+typedef void (*MeshSampleThreadContextFreeFp)(void *userdata, void *thread_ctx);
+typedef bool (*MeshSampleRayFp)(void *userdata, void *thread_ctx, float ray_start[3], float ray_end[3]);
+
+/* ==== Utility Functions ==== */
+
+float* BKE_mesh_sample_calc_triangle_weights(struct Mesh *mesh, const float *loop_weights, float *r_area);
+
+void BKE_mesh_sample_weights_from_loc(struct MeshSample *sample, struct Mesh *mesh, int looptri_index, const float loc[3]);
+
+
+/* ==== Evaluate ==== */
+
+bool BKE_mesh_sample_is_valid(const struct MeshSample *sample);
+bool BKE_mesh_sample_is_volume_sample(const struct MeshSample *sample);
+
+/* Evaluate position and normal on the given mesh */
+bool BKE_mesh_sample_eval(const struct Mesh *mesh, const struct MeshSample *sample, float loc[3], float nor[3], float tang[3]);
+
+/* Evaluate position for the given shapekey */
+bool BKE_mesh_sample_shapekey(struct Key *key, struct KeyBlock *kb, const struct MeshSample *sample, float loc[3]);
+
+void BKE_mesh_sample_clear(struct MeshSample *sample);
+
+
+/* ==== Generator Types ==== */
+
+struct MeshSampleGenerator *BKE_mesh_sample_gen_surface_vertices(void);
+
+/* vertex_weight_cb is optional */
+struct MeshSampleGenerator *BKE_mesh_sample_gen_surface_random(unsigned int seed, bool use_area_weight, const float *loop_weights);
+
+struct MeshSampleGenerator *BKE_mesh_sample_gen_surface_raycast(
+ MeshSampleThreadContextCreateFp thread_context_create_cb,
+ MeshSampleThreadContextFreeFp thread_context_free_cb,
+ MeshSampleRayFp ray_cb,
+ void *userdata);
+
+struct MeshSampleGenerator *BKE_mesh_sample_gen_surface_poissondisk(unsigned int seed, float mindist, unsigned int max_samples, const float *loop_weights);
+
+struct MeshSampleGenerator *BKE_mesh_sample_gen_volume_random_bbray(unsigned int seed, float density);
+
+void BKE_mesh_sample_free_generator(struct MeshSampleGenerator *gen);
+
+
+/* ==== Sampling ==== */
+
+void BKE_mesh_sample_generator_bind(struct MeshSampleGenerator *gen, struct Mesh *mesh);
+void BKE_mesh_sample_generator_unbind(struct MeshSampleGenerator *gen);
+
+unsigned int BKE_mesh_sample_gen_get_max_samples(const struct MeshSampleGenerator *gen);
+
+/* Generate a single sample.
+ * Not threadsafe!
+ */
+bool BKE_mesh_sample_generate(struct MeshSampleGenerator *gen, struct MeshSample *sample);
+
+/* Generate a large number of samples.
+ */
+int BKE_mesh_sample_generate_batch_ex(struct MeshSampleGenerator *gen,
+ void *output_buffer, int output_stride, int count,
+ bool use_threads);
+
+int BKE_mesh_sample_generate_batch(struct MeshSampleGenerator *gen,
+ MeshSample *output_buffer, int count);
+
+/* ==== Utilities ==== */
+
+struct ParticleSystem;
+struct ParticleData;
+struct BVHTreeFromMesh;
+
+bool BKE_mesh_sample_from_particle(struct MeshSample *sample, struct ParticleSystem *psys, struct Mesh *mesh, struct ParticleData *pa);
+bool BKE_mesh_sample_to_particle(struct MeshSample *sample, struct ParticleSystem *psys, struct Mesh *mesh, struct BVHTreeFromMesh *bvhtree, struct ParticleData *pa);
+
+#endif /* __BKE_MESH_SAMPLE_H__ */
diff --git a/source/blender/blenkernel/BKE_particle.h b/source/blender/blenkernel/BKE_particle.h
index 3486027e628..1c4f5e14a7a 100644
--- a/source/blender/blenkernel/BKE_particle.h
+++ b/source/blender/blenkernel/BKE_particle.h
@@ -50,6 +50,7 @@ struct Main;
struct Object;
struct Scene;
struct Depsgraph;
+struct Mesh;
struct ModifierData;
struct MTFace;
struct MCol;
@@ -433,6 +434,7 @@ void psys_interpolate_face(struct MVert *mvert, struct MFace *mface, struct MTFa
float orco[3]);
float psys_particle_value_from_verts(struct Mesh *mesh, short from, struct ParticleData *pa, float *values);
void psys_get_from_key(struct ParticleKey *key, float loc[3], float vel[3], float rot[4], float *time);
+int psys_get_index_on_mesh(struct ParticleSystem *psys, struct Mesh *mesh, ParticleData *pa, int *mapindex, float mapfw[4]);
/* BLI_bvhtree_ray_cast callback */
void BKE_psys_collision_neartest_cb(void *userdata, int index, const struct BVHTreeRay *ray, struct BVHTreeRayHit *hit);
diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt
index 7169597f100..2640abab15b 100644
--- a/source/blender/blenkernel/CMakeLists.txt
+++ b/source/blender/blenkernel/CMakeLists.txt
@@ -118,6 +118,8 @@ set(SRC
intern/freestyle.c
intern/gpencil.c
intern/gpencil_modifier.c
+ intern/hair.c
+ intern/hair_draw.c
intern/icons.c
intern/icons_rasterize.c
intern/idcode.c
@@ -149,6 +151,7 @@ set(SRC
intern/mesh_merge.c
intern/mesh_remap.c
intern/mesh_runtime.c
+ intern/mesh_sample.c
intern/mesh_tangent.c
intern/mesh_validate.c
intern/modifier.c
@@ -264,6 +267,7 @@ set(SRC
BKE_global.h
BKE_gpencil.h
BKE_gpencil_modifier.h
+ BKE_hair.h
BKE_icons.h
BKE_idcode.h
BKE_idprop.h
@@ -288,6 +292,7 @@ set(SRC
BKE_mesh_mapping.h
BKE_mesh_remap.h
BKE_mesh_runtime.h
+ BKE_mesh_sample.h
BKE_mesh_tangent.h
BKE_modifier.h
BKE_movieclip.h
diff --git a/source/blender/blenkernel/intern/hair.c b/source/blender/blenkernel/intern/hair.c
new file mode 100644
index 00000000000..5020f9774a9
--- /dev/null
+++ b/source/blender/blenkernel/intern/hair.c
@@ -0,0 +1,721 @@
+/*
+ * ***** 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.
+ *
+ * The Original Code is Copyright (C) Blender Foundation
+ * All rights reserved.
+ *
+ * The Original Code is: all of this file.
+ *
+ * Contributor(s): Lukas Toenne
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file blender/blenkernel/intern/hair.c
+ * \ingroup bke
+ */
+
+#include <limits.h>
+#include <string.h>
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_kdtree.h"
+#include "BLI_listbase.h"
+#include "BLI_math.h"
+#include "BLI_rand.h"
+#include "BLI_sort.h"
+#include "BLI_string_utf8.h"
+#include "BLI_string_utils.h"
+
+#include "DNA_hair_types.h"
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+#include "DNA_object_types.h"
+
+#include "BKE_hair.h"
+#include "BKE_library.h"
+#include "BKE_mesh.h"
+#include "BKE_mesh_sample.h"
+
+#include "BLT_translation.h"
+
+HairSystem* BKE_hair_new(void)
+{
+ HairSystem *hair = MEM_callocN(sizeof(HairSystem), "hair system");
+
+ hair->pattern = MEM_callocN(sizeof(HairPattern), "hair pattern");
+
+ return hair;
+}
+
+HairSystem* BKE_hair_copy(HairSystem *hsys)
+{
+ HairSystem *nhsys = MEM_dupallocN(hsys);
+
+ if (hsys->pattern)
+ {
+ nhsys->pattern = MEM_dupallocN(hsys->pattern);
+ nhsys->pattern->follicles = MEM_dupallocN(hsys->pattern->follicles);
+ }
+
+ if (hsys->curve_data.curves)
+ {
+ nhsys->curve_data.curves = MEM_dupallocN(hsys->curve_data.curves);
+ }
+ if (hsys->curve_data.verts)
+ {
+ nhsys->curve_data.verts = MEM_dupallocN(hsys->curve_data.verts);
+ }
+
+ nhsys->draw_batch_cache = NULL;
+ nhsys->draw_texture_cache = NULL;
+
+ return nhsys;
+}
+
+void BKE_hair_free(HairSystem *hsys)
+{
+ BKE_hair_batch_cache_free(hsys);
+
+ if (hsys->curve_data.curves)
+ {
+ MEM_freeN(hsys->curve_data.curves);
+ }
+ if (hsys->curve_data.verts)
+ {
+ MEM_freeN(hsys->curve_data.verts);
+ }
+
+ if (hsys->pattern)
+ {
+ if (hsys->pattern->follicles)
+ {
+ MEM_freeN(hsys->pattern->follicles);
+ }
+ MEM_freeN(hsys->pattern);
+ }
+
+ MEM_freeN(hsys);
+}
+
+/* Calculate surface area of a scalp mesh */
+float BKE_hair_calc_surface_area(const Mesh *scalp)
+{
+ BLI_assert(scalp != NULL);
+
+ int numpolys = scalp->totpoly;
+ MPoly *mpolys = scalp->mpoly;
+ MLoop *mloops = scalp->mloop;
+ MVert *mverts = scalp->mvert;
+
+ float area = 0.0f;
+ for (int i = 0; i < numpolys; ++i)
+ {
+ area += BKE_mesh_calc_poly_area(&mpolys[i], mloops + mpolys[i].loopstart, mverts);
+ }
+ return area;
+}
+
+/* Calculate a density value based on surface area and sample count */
+float BKE_hair_calc_density_from_count(float area, int count)
+{
+ return area > 0.0f ? count / area : 0.0f;
+}
+
+/* Calculate maximum sample count based on surface area and density */
+int BKE_hair_calc_max_count_from_density(float area, float density)
+{
+ return (int)(density * area);
+}
+
+/* Calculate a density value based on a minimum distance */
+float BKE_hair_calc_density_from_min_distance(float min_distance)
+{
+ // max. circle packing density (sans pi factor): 1 / (2 * sqrt(3))
+ static const float max_factor = 0.288675135;
+
+ return min_distance > 0.0f ? max_factor / (min_distance * min_distance) : 0.0f;
+}
+
+/* Calculate a minimum distance based on density */
+float BKE_hair_calc_min_distance_from_density(float density)
+{
+ // max. circle packing density (sans pi factor): 1 / (2 * sqrt(3))
+ static const float max_factor = 0.288675135;
+
+ return density > 0.0f ? sqrt(max_factor / density) : 0.0f;
+}
+
+/* Distribute hair follicles on a scalp mesh */
+void BKE_hair_generate_follicles(
+ HairSystem* hsys,
+ struct Mesh *scalp,
+ unsigned int seed,
+ int count)
+{
+ BKE_hair_generate_follicles_ex(hsys, scalp, seed, count, NULL);
+}
+
+/* Distribute hair follicles on a scalp mesh.
+ * Optional per-loop weights control follicle density on the scalp.
+ */
+void BKE_hair_generate_follicles_ex(
+ HairSystem* hsys,
+ struct Mesh *scalp,
+ unsigned int seed,
+ int count,
+ const float *loop_weights)
+{
+ HairPattern *pattern = hsys->pattern;
+
+ // Limit max_count to theoretical limit based on area
+ float scalp_area = BKE_hair_calc_surface_area(scalp);
+ float density = BKE_hair_calc_density_from_count(scalp_area, count);
+ float min_distance = BKE_hair_calc_min_distance_from_density(density);
+
+ if (pattern->follicles)
+ {
+ MEM_freeN(pattern->follicles);
+ }
+ pattern->follicles = MEM_callocN(sizeof(HairFollicle) * count, "hair follicles");
+
+ {
+ MeshSampleGenerator *gen = BKE_mesh_sample_gen_surface_poissondisk(seed, min_distance, count, loop_weights);
+
+ BKE_mesh_sample_generator_bind(gen, scalp);
+
+ static const bool use_threads = false;
+ pattern->num_follicles = BKE_mesh_sample_generate_batch_ex(
+ gen,
+ &pattern->follicles->mesh_sample,
+ sizeof(HairFollicle),
+ count,
+ use_threads);
+
+ BKE_mesh_sample_free_generator(gen);
+ }
+
+ hsys->flag |= HAIR_SYSTEM_UPDATE_FOLLICLE_BINDING;
+ BKE_hair_batch_cache_dirty(hsys, BKE_HAIR_BATCH_DIRTY_ALL);
+}
+
+/* ================================= */
+
+void BKE_hair_fiber_curves_begin(HairSystem *hsys, int totcurves)
+{
+ if (totcurves != hsys->curve_data.totcurves)
+ {
+ hsys->curve_data.curves = MEM_reallocN(hsys->curve_data.curves, sizeof(HairFiberCurve) * totcurves);
+ hsys->curve_data.totcurves = totcurves;
+
+ hsys->flag |= HAIR_SYSTEM_UPDATE_FOLLICLE_BINDING;
+ BKE_hair_batch_cache_dirty(hsys, BKE_HAIR_BATCH_DIRTY_ALL);
+ }
+}
+
+void BKE_hair_set_fiber_curve(HairSystem *hsys, int index, int numverts,
+ float taper_length, float taper_thickness)
+{
+ BLI_assert(index <= hsys->curve_data.totcurves);
+
+ HairFiberCurve *curve = &hsys->curve_data.curves[index];
+ curve->numverts = numverts;
+ curve->taper_length = taper_length;
+ curve->taper_thickness = taper_thickness;
+
+ hsys->flag |= HAIR_SYSTEM_UPDATE_FOLLICLE_BINDING;
+ BKE_hair_batch_cache_dirty(hsys, BKE_HAIR_BATCH_DIRTY_ALL);
+}
+
+/* Calculate vertex start indices on all curves based on length.
+ * Returns the total number of vertices.
+ */
+static int hair_curve_calc_vertstart(HairSystem *hsys)
+{
+ /* Recalculate vertex count and start offsets in curves */
+ int vertstart = 0;
+ for (int i = 0; i < hsys->curve_data.totcurves; ++i)
+ {
+ hsys->curve_data.curves[i].vertstart = vertstart;
+ vertstart += hsys->curve_data.curves[i].numverts;
+ }
+
+ return vertstart;
+}
+
+void BKE_hair_fiber_curves_end(HairSystem *hsys)
+{
+ const int totverts = hair_curve_calc_vertstart(hsys);
+
+ if (totverts != hsys->curve_data.totverts)
+ {
+ hsys->curve_data.verts = MEM_reallocN(hsys->curve_data.verts, sizeof(HairFiberVertex) * totverts);
+ hsys->curve_data.totverts = totverts;
+
+ BKE_hair_batch_cache_dirty(hsys, BKE_HAIR_BATCH_DIRTY_ALL);
+ }
+}
+
+void BKE_hair_set_fiber_vertex(HairSystem *hsys, int index, int flag, const float co[3])
+{
+ BLI_assert(index <= hsys->curve_data.totverts);
+
+ HairFiberVertex *vertex = &hsys->curve_data.verts[index];
+ vertex->flag = flag;
+ copy_v3_v3(vertex->co, co);
+
+ BKE_hair_batch_cache_dirty(hsys, BKE_HAIR_BATCH_DIRTY_ALL);
+}
+
+void BKE_hair_set_fiber_curves(HairSystem *hsys, HairCurveData *curves)
+{
+ if (hsys->curve_data.curves)
+ {
+ MEM_freeN(hsys->curve_data.curves);
+ }
+ hsys->curve_data.curves = MEM_dupallocN(hsys->curve_data.curves);
+ hsys->curve_data.totcurves = curves->totcurves;
+
+ if (hsys->curve_data.verts)
+ {
+ MEM_freeN(hsys->curve_data.verts);
+ }
+ hsys->curve_data.verts = MEM_dupallocN(hsys->curve_data.verts);
+ hsys->curve_data.totverts = curves->totverts;
+
+#ifndef NDEBUG
+ const int vertcount = hair_curve_calc_vertstart(hsys);
+ BLI_assert(vertcount <= hsys->curve_data.totverts);
+#endif
+
+ hsys->flag |= HAIR_SYSTEM_UPDATE_FOLLICLE_BINDING;
+ BKE_hair_batch_cache_dirty(hsys, BKE_HAIR_BATCH_DIRTY_ALL);
+}
+
+void BKE_hair_clear_fiber_curves(HairSystem *hsys)
+{
+ if (hsys->curve_data.curves)
+ {
+ MEM_freeN(hsys->curve_data.curves);
+ hsys->curve_data.curves = NULL;
+ }
+ hsys->curve_data.totcurves = 0;
+
+ if (hsys->curve_data.verts)
+ {
+ MEM_freeN(hsys->curve_data.verts);
+ hsys->curve_data.verts = NULL;
+ }
+ hsys->curve_data.totverts = 0;
+
+ hsys->flag &= ~HAIR_SYSTEM_UPDATE_FOLLICLE_BINDING;
+ BKE_hair_batch_cache_dirty(hsys, BKE_HAIR_BATCH_DIRTY_ALL);
+}
+
+/* ================================= */
+
+bool BKE_hair_bind_follicles(HairSystem *hsys, const Mesh *scalp)
+{
+ if (!(hsys->flag & HAIR_SYSTEM_UPDATE_FOLLICLE_BINDING))
+ {
+ return true;
+ }
+ hsys->flag &= ~HAIR_SYSTEM_UPDATE_FOLLICLE_BINDING;
+
+ HairPattern *pattern = hsys->pattern;
+ if (!pattern)
+ {
+ return true;
+ }
+
+ const int num_strands = hsys->curve_data.totcurves;
+ /* Need at least one curve for binding */
+ if (num_strands == 0)
+ {
+ HairFollicle *follicle = pattern->follicles;
+ for (int i = 0; i < pattern->num_follicles; ++i, ++follicle)
+ {
+ for (int k = 0; k < 4; ++k)
+ {
+ follicle->curve = HAIR_CURVE_INDEX_NONE;
+ }
+ }
+ return false;
+ }
+
+ KDTree *tree = BLI_kdtree_new(num_strands);
+ for (int c = 0; c < num_strands; ++c)
+ {
+ const int vertstart = hsys->curve_data.curves[c].vertstart;
+ const float *rootco = hsys->curve_data.verts[vertstart].co;
+ BLI_kdtree_insert(tree, c, rootco);
+ }
+ BLI_kdtree_balance(tree);
+
+ {
+ HairFollicle *follicle = pattern->follicles;
+ for (int i = 0; i < pattern->num_follicles; ++i, ++follicle)
+ {
+ float loc[3], nor[3], tang[3];
+ if (BKE_mesh_sample_eval(scalp, &follicle->mesh_sample, loc, nor, tang))
+ {
+ follicle->curve = BLI_kdtree_find_nearest(tree, loc, NULL);
+ }
+ }
+ }
+
+ BLI_kdtree_free(tree);
+
+ return true;
+}
+
+/* === Export === */
+
+/* Returns number of vertices in a curve after subdivision */
+BLI_INLINE int hair_get_strand_subdiv_length(int orig_length, int subdiv)
+{
+ return ((orig_length - 1) << subdiv) + 1;
+}
+
+/* Returns total number of vertices after subdivision */
+BLI_INLINE int hair_get_strand_subdiv_numverts(int numstrands, int numverts, int subdiv)
+{
+ return ((numverts - numstrands) << subdiv) + numstrands;
+}
+
+/* Subdivide a curve */
+static int hair_curve_subdivide(const HairFiberCurve* curve, const HairFiberVertex* verts,
+ int subdiv, HairFiberVertex *r_verts)
+{
+ {
+ /* Move vertex positions from the dense array to their initial configuration for subdivision.
+ * Also add offset to ensure the curve starts on the scalp surface.
+ */
+ const int step = (1 << subdiv);
+ BLI_assert(curve->numverts > 0);
+
+ HairFiberVertex *dst = r_verts;
+ for (int i = 0; i < curve->numverts; ++i) {
+ copy_v3_v3(dst->co, verts[i].co);
+ dst += step;
+ }
+ }
+
+ /* Subdivide */
+ for (int d = 0; d < subdiv; ++d) {
+ const int num_edges = (curve->numverts - 1) << d;
+ const int hstep = 1 << (subdiv - d - 1);
+ const int step = 1 << (subdiv - d);
+
+ /* Calculate edge points */
+ {
+ int index = 0;
+ for (int k = 0; k < num_edges; ++k, index += step) {
+ add_v3_v3v3(r_verts[index + hstep].co, r_verts[index].co, r_verts[index + step].co);
+ mul_v3_fl(r_verts[index + hstep].co, 0.5f);
+ }
+ }
+
+ /* Move original points */
+ {
+ int index = step;
+ for (int k = 1; k < num_edges; ++k, index += step) {
+ add_v3_v3v3(r_verts[index].co, r_verts[index - hstep].co, r_verts[index + hstep].co);
+ mul_v3_fl(r_verts[index].co, 0.5f);
+ }
+ }
+ }
+
+ const int num_verts = ((curve->numverts - 1) << subdiv) + 1;
+ return num_verts;
+}
+
+/* Calculate tangent and normal vector changes from one segment to the next */
+static void hair_curve_transport_frame(const float co1[3], const float co2[3],
+ float prev_tang[3], float prev_nor[3],
+ float r_tang[3], float r_nor[3])
+{
+ /* segment direction */
+ sub_v3_v3v3(r_tang, co2, co1);
+ normalize_v3(r_tang);
+
+ /* rotate the frame */
+ float rot[3][3];
+ rotation_between_vecs_to_mat3(rot, prev_tang, r_tang);
+ mul_v3_m3v3(r_nor, rot, prev_nor);
+
+ copy_v3_v3(prev_tang, r_tang);
+ copy_v3_v3(prev_nor, r_nor);
+}
+
+/* Calculate tangent and normal vectors for all vertices on a curve */
+static void hair_curve_calc_vectors(const HairFiberVertex* verts, int numverts,
+ float (*r_tangents)[3], float (*r_normals)[3])
+{
+ BLI_assert(numverts >= 2);
+
+ float prev_tang[3] = {0.0f, 0.0f, 1.0f};
+ float prev_nor[3] = {1.0f, 0.0f, 0.0f};
+
+ hair_curve_transport_frame(
+ verts[0].co, verts[1].co,
+ prev_tang, prev_nor,
+ r_tangents[0], r_normals[0]);
+
+ for (int i = 1; i < numverts - 1; ++i)
+ {
+ hair_curve_transport_frame(
+ verts[i-1].co, verts[i+1].co,
+ prev_tang, prev_nor,
+ r_tangents[i], r_normals[i]);
+ }
+
+ hair_curve_transport_frame(
+ verts[numverts-2].co, verts[numverts-1].co,
+ prev_tang, prev_nor,
+ r_tangents[numverts-1], r_normals[numverts-1]);
+}
+
+/* Create a new export cache.
+ * This can be used to construct full fiber data for rendering.
+ */
+
+HairExportCache* BKE_hair_export_cache_new(void)
+{
+ HairExportCache *cache = MEM_callocN(sizeof(HairExportCache), "hair export cache");
+ return cache;
+}
+
+/* Returns flags for missing data parts */
+
+static int hair_export_cache_get_required_updates(const HairExportCache *cache)
+{
+ int data = 0;
+ if (!cache->fiber_curves)
+ {
+ data |= HAIR_EXPORT_FIBER_CURVES;
+ }
+ if (!cache->fiber_verts || !cache->fiber_normals || !cache->fiber_tangents)
+ {
+ data |= HAIR_EXPORT_FIBER_VERTICES;
+ }
+ if (!cache->follicles)
+ {
+ data |= HAIR_EXPORT_FOLLICLE_BINDING;
+ }
+ if (!cache->follicle_root_position)
+ {
+ data |= HAIR_EXPORT_FOLLICLE_ROOT_POSITIONS;
+ }
+ return data;
+}
+
+/* Include data dependencies of the given flags */
+
+static int hair_export_cache_get_dependencies(int data)
+{
+ /* Ordering here is important to account for recursive dependencies */
+
+ if (data & HAIR_EXPORT_FIBER_CURVES)
+ data |= HAIR_EXPORT_FIBER_VERTICES | HAIR_EXPORT_FOLLICLE_BINDING;
+
+ if (data & HAIR_EXPORT_FOLLICLE_BINDING)
+ data |= HAIR_EXPORT_FOLLICLE_ROOT_POSITIONS;
+
+ return data;
+}
+
+/* Update an existing export cache to ensure it contains the requested data.
+ * Returns flags for data that has been updated.
+ */
+
+int BKE_hair_export_cache_update(HairExportCache *cache, const HairSystem *hsys,
+ int subdiv, Mesh *scalp, int requested_data)
+{
+ /* Include dependencies */
+ int data = hair_export_cache_get_dependencies(requested_data);
+
+ int uncached = hair_export_cache_get_required_updates(cache);
+ /* Invalid data should already include all dependencies */
+ BLI_assert(uncached == hair_export_cache_get_dependencies(uncached));
+
+ /* Only update invalidated parts */
+ data &= uncached;
+
+ if (data & HAIR_EXPORT_FIBER_CURVES)
+ {
+ /* Cache subdivided curves */
+ const int totcurves = cache->totcurves = hsys->curve_data.totcurves;
+ cache->fiber_curves = MEM_reallocN_id(cache->fiber_curves, sizeof(HairFiberCurve) * totcurves, "hair export curves");
+
+ int totverts = 0;
+ for (int i = 0; i < totcurves; ++i) {
+ const HairFiberCurve *curve_orig = &hsys->curve_data.curves[i];
+ HairFiberCurve *curve = &cache->fiber_curves[i];
+
+ memcpy(curve, curve_orig, sizeof(HairFiberCurve));
+ curve->numverts = hair_get_strand_subdiv_length(curve_orig->numverts, subdiv);
+ curve->vertstart = totverts;
+
+ totverts += curve->numverts;
+ }
+ cache->totverts = totverts;
+ }
+
+ if (data & HAIR_EXPORT_FIBER_VERTICES)
+ {
+ const int totcurves = cache->totcurves;
+ const int totverts = cache->totverts;
+ cache->fiber_verts = MEM_reallocN_id(cache->fiber_verts, sizeof(HairFiberVertex) * totverts, "hair export verts");
+ cache->fiber_tangents = MEM_reallocN_id(cache->fiber_tangents, sizeof(float[3]) * totverts, "hair export tangents");
+ cache->fiber_normals = MEM_reallocN_id(cache->fiber_normals, sizeof(float[3]) * totverts, "hair export normals");
+
+ for (int i = 0; i < totcurves; ++i) {
+ const HairFiberCurve *curve_orig = &hsys->curve_data.curves[i];
+ const HairFiberVertex *verts_orig = &hsys->curve_data.verts[curve_orig->vertstart];
+ const HairFiberCurve *curve = &cache->fiber_curves[i];
+ HairFiberVertex *verts = &cache->fiber_verts[curve->vertstart];
+ float (*tangents)[3] = &cache->fiber_tangents[curve->vertstart];
+ float (*normals)[3] = &cache->fiber_normals[curve->vertstart];
+
+ hair_curve_subdivide(curve_orig, verts_orig, subdiv, verts);
+
+ hair_curve_calc_vectors(verts, curve->numverts, tangents, normals);
+ }
+ }
+
+ if (hsys->pattern)
+ {
+ if (data & HAIR_EXPORT_FOLLICLE_BINDING)
+ {
+ cache->follicles = hsys->pattern->follicles;
+ cache->totfollicles = hsys->pattern->num_follicles;
+ }
+
+ if (data & HAIR_EXPORT_FOLLICLE_ROOT_POSITIONS)
+ {
+ const int totfibercurves = cache->totfollicles;
+
+ cache->follicle_root_position = MEM_reallocN_id(cache->follicle_root_position, sizeof(float[3]) * totfibercurves, "fiber root position");
+ const HairFollicle *follicle = hsys->pattern->follicles;
+ for (int i = 0; i < totfibercurves; ++i, ++follicle) {
+ /* Cache fiber root position */
+ float nor[3], tang[3];
+ BKE_mesh_sample_eval(scalp, &follicle->mesh_sample, cache->follicle_root_position[i], nor, tang);
+ }
+ }
+ }
+ else
+ {
+ cache->follicles = NULL;
+ cache->totfollicles = 0;
+
+ if (cache->follicle_root_position)
+ {
+ MEM_freeN(cache->follicle_root_position);
+ cache->follicle_root_position = NULL;
+ }
+ }
+
+ return data;
+}
+
+/* Free the given export cache */
+
+void BKE_hair_export_cache_free(HairExportCache *cache)
+{
+ if (cache->fiber_curves)
+ {
+ MEM_freeN(cache->fiber_curves);
+ }
+ if (cache->fiber_verts)
+ {
+ MEM_freeN(cache->fiber_verts);
+ }
+ if (cache->fiber_tangents)
+ {
+ MEM_freeN(cache->fiber_tangents);
+ }
+ if (cache->fiber_normals)
+ {
+ MEM_freeN(cache->fiber_normals);
+ }
+ if (cache->follicle_root_position)
+ {
+ MEM_freeN(cache->follicle_root_position);
+ }
+ MEM_freeN(cache);
+}
+
+/* Invalidate all data in a hair export cache */
+
+void BKE_hair_export_cache_clear(HairExportCache *cache)
+{
+ /* Invalidate everything */
+ BKE_hair_export_cache_invalidate(cache, HAIR_EXPORT_ALL);
+}
+
+/* Invalidate part of the data in a hair export cache.
+ *
+ * Note some parts may get invalidated automatically based on internal dependencies.
+ */
+
+void BKE_hair_export_cache_invalidate(HairExportCache *cache, int invalidate)
+{
+ /* Include dependencies */
+ int data = hair_export_cache_get_dependencies(invalidate);
+
+ if (data & HAIR_EXPORT_FIBER_CURVES)
+ {
+ if (cache->fiber_curves)
+ {
+ MEM_freeN(cache->fiber_curves);
+ cache->fiber_curves = 0;
+ }
+ }
+ if (data & HAIR_EXPORT_FIBER_VERTICES)
+ {
+ if (cache->fiber_verts)
+ {
+ MEM_freeN(cache->fiber_verts);
+ cache->fiber_verts = NULL;
+ }
+ if (cache->fiber_tangents)
+ {
+ MEM_freeN(cache->fiber_tangents);
+ cache->fiber_tangents = NULL;
+ }
+ if (cache->fiber_normals)
+ {
+ MEM_freeN(cache->fiber_normals);
+ cache->fiber_tangents = NULL;
+ }
+ }
+ if (data & HAIR_EXPORT_FOLLICLE_BINDING)
+ {
+ cache->follicles = NULL;
+ }
+ if (data & HAIR_EXPORT_FOLLICLE_ROOT_POSITIONS)
+ {
+ if (cache->follicle_root_position)
+ {
+ MEM_freeN(cache->follicle_root_position);
+ cache->follicle_root_position = NULL;
+ }
+ }
+}
diff --git a/source/blender/blenkernel/intern/hair_draw.c b/source/blender/blenkernel/intern/hair_draw.c
new file mode 100644
index 00000000000..e88e3cdb3ca
--- /dev/null
+++ b/source/blender/blenkernel/intern/hair_draw.c
@@ -0,0 +1,314 @@
+/*
+ * ***** 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.
+ *
+ * The Original Code is Copyright (C) Blender Foundation
+ * All rights reserved.
+ *
+ * The Original Code is: all of this file.
+ *
+ * Contributor(s): Lukas Toenne
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file blender/blenkernel/intern/hair_draw.c
+ * \ingroup bke
+ */
+
+#include <string.h>
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_math.h"
+#include "BLI_kdtree.h"
+#include "BLI_rand.h"
+
+#include "DNA_hair_types.h"
+
+#include "BKE_mesh_sample.h"
+#include "BKE_hair.h"
+
+/* === Draw Settings === */
+
+HairDrawSettings* BKE_hair_draw_settings_new(void)
+{
+ HairDrawSettings *draw_settings = MEM_callocN(sizeof(HairDrawSettings), "hair draw settings");
+
+ draw_settings->follicle_mode = HAIR_DRAW_FOLLICLE_POINTS;
+ draw_settings->fiber_mode = HAIR_DRAW_FIBER_CURVES;
+ draw_settings->shape_flag = HAIR_DRAW_CLOSE_TIP;
+ draw_settings->shape = 0.0f;
+ draw_settings->root_radius = 1.0f;
+ draw_settings->tip_radius = 0.0f;
+ draw_settings->radius_scale = 0.01f;
+
+ return draw_settings;
+}
+
+HairDrawSettings* BKE_hair_draw_settings_copy(HairDrawSettings *draw_settings)
+{
+ HairDrawSettings *ndraw_settings = MEM_dupallocN(draw_settings);
+ return ndraw_settings;
+}
+
+void BKE_hair_draw_settings_free(HairDrawSettings *draw_settings)
+{
+ MEM_freeN(draw_settings);
+}
+
+/* === Draw Cache === */
+
+typedef struct HairFiberTextureBuffer {
+ unsigned int parent_index[4];
+ float parent_weight[4];
+ float root_position[3];
+ int pad;
+} HairFiberTextureBuffer;
+BLI_STATIC_ASSERT_ALIGN(HairFiberTextureBuffer, 8)
+
+typedef struct HairStrandVertexTextureBuffer {
+ float co[3];
+ float nor[3];
+ float tang[3];
+ float len;
+} HairStrandVertexTextureBuffer;
+BLI_STATIC_ASSERT_ALIGN(HairStrandVertexTextureBuffer, 8)
+
+typedef struct HairStrandMapTextureBuffer {
+ unsigned int vertex_start;
+ unsigned int vertex_count;
+
+ /* Shape attributes */
+ float taper_length; /* Distance at which final thickness is reached */
+ float taper_thickness; /* Relative thickness of the strand */
+} HairStrandMapTextureBuffer;
+BLI_STATIC_ASSERT_ALIGN(HairStrandMapTextureBuffer, 8)
+
+static void hair_get_strand_buffer(
+ const HairExportCache *cache,
+ HairStrandMapTextureBuffer *strand_map_buffer,
+ HairStrandVertexTextureBuffer *strand_vertex_buffer)
+{
+ for (int i = 0; i < cache->totcurves; ++i) {
+ const HairFiberCurve *curve = &cache->fiber_curves[i];
+ const HairFiberVertex *verts = &cache->fiber_verts[curve->vertstart];
+ const float (*tangents)[3] = &cache->fiber_tangents[curve->vertstart];
+ const float (*normals)[3] = &cache->fiber_normals[curve->vertstart];
+ HairStrandMapTextureBuffer *smap = &strand_map_buffer[i];
+ HairStrandVertexTextureBuffer *svert = &strand_vertex_buffer[curve->vertstart];
+
+ smap->vertex_start = curve->vertstart;
+ smap->vertex_count = curve->numverts;
+ smap->taper_length = curve->taper_length;
+ smap->taper_thickness = curve->taper_thickness;
+
+ float len = 0.0f;
+ for (int j = 0; j < curve->numverts; ++j)
+ {
+ copy_v3_v3(svert[j].co, verts[j].co);
+ copy_v3_v3(svert[j].tang, tangents[j]);
+ copy_v3_v3(svert[j].nor, normals[j]);
+
+ if (j > 0)
+ {
+ len += len_v3v3(verts[j-1].co, verts[j].co);
+ }
+ svert[j].len = len;
+ }
+ }
+}
+
+static void hair_get_fiber_buffer(const HairExportCache *cache,
+ HairFiberTextureBuffer *fiber_buf)
+{
+ const int totfibers = cache->totfollicles;
+ const HairFollicle *follicle = cache->follicles;
+ HairFiberTextureBuffer *fb = fiber_buf;
+ for (int i = 0; i < totfibers; ++i, ++fb, ++follicle) {
+ copy_v3_v3(fb->root_position, cache->follicle_root_position[i]);
+
+ fb->parent_index[0] = follicle->curve;
+ fb->parent_index[1] = HAIR_CURVE_INDEX_NONE;
+ fb->parent_index[2] = HAIR_CURVE_INDEX_NONE;
+ fb->parent_index[3] = HAIR_CURVE_INDEX_NONE;
+ fb->parent_weight[0] = 1.0f;
+ fb->parent_weight[1] = 0.0f;
+ fb->parent_weight[2] = 0.0f;
+ fb->parent_weight[3] = 0.0f;
+ }
+}
+
+void BKE_hair_get_texture_buffer_size(
+ const HairExportCache *cache,
+ int *r_size,
+ int *r_strand_map_start,
+ int *r_strand_vertex_start,
+ int *r_fiber_start)
+{
+ *r_strand_map_start = 0;
+ *r_strand_vertex_start = *r_strand_map_start + cache->totcurves * sizeof(HairStrandMapTextureBuffer);
+ *r_fiber_start = *r_strand_vertex_start + cache->totverts * sizeof(HairStrandVertexTextureBuffer);
+ *r_size = *r_fiber_start + cache->totfollicles * sizeof(HairFiberTextureBuffer);
+}
+
+void BKE_hair_get_texture_buffer(
+ const HairExportCache *cache,
+ void *buffer)
+{
+ int size, strand_map_start, strand_vertex_start, fiber_start;
+ BKE_hair_get_texture_buffer_size(cache, &size, &strand_map_start, &strand_vertex_start, &fiber_start);
+
+ HairStrandMapTextureBuffer *strand_map = (HairStrandMapTextureBuffer*)((char*)buffer + strand_map_start);
+ HairStrandVertexTextureBuffer *strand_verts = (HairStrandVertexTextureBuffer*)((char*)buffer + strand_vertex_start);
+ HairFiberTextureBuffer *fibers = (HairFiberTextureBuffer*)((char*)buffer + fiber_start);
+
+ hair_get_strand_buffer(
+ cache,
+ strand_map,
+ strand_verts);
+ hair_get_fiber_buffer(
+ cache,
+ fibers);
+}
+
+void (*BKE_hair_batch_cache_dirty_cb)(HairSystem* hsys, int mode) = NULL;
+void (*BKE_hair_batch_cache_free_cb)(HairSystem* hsys) = NULL;
+
+void BKE_hair_batch_cache_dirty(HairSystem* hsys, int mode)
+{
+ if (hsys->draw_batch_cache) {
+ BKE_hair_batch_cache_dirty_cb(hsys, mode);
+ }
+}
+
+void BKE_hair_batch_cache_free(HairSystem* hsys)
+{
+ if (hsys->draw_batch_cache || hsys->draw_texture_cache) {
+ BKE_hair_batch_cache_free_cb(hsys);
+ }
+}
+
+/* === Fiber Curve Interpolation === */
+
+/* NOTE: Keep this code in sync with the GLSL version!
+ * see common_hair_fibers_lib.glsl
+ */
+
+/* Subdivide a curve */
+static int hair_curve_subdivide(
+ const HairFiberCurve* curve,
+ const HairFiberVertex* verts,
+ int subdiv,
+ int vertco_stride,
+ float *r_vertco)
+{
+ {
+ /* Move vertex positions from the dense array to their initial configuration for subdivision.
+ * Also add offset to ensure the curve starts on the scalp surface.
+ */
+ const int step = (1 << subdiv) * vertco_stride;
+ BLI_assert(curve->numverts > 0);
+
+ float *dst = r_vertco;
+ for (int i = 0; i < curve->numverts; ++i) {
+ copy_v3_v3(dst, verts[i].co);
+ dst = POINTER_OFFSET(dst, step);
+ }
+ }
+
+ /* Subdivide */
+ for (int d = 0; d < subdiv; ++d) {
+ const int num_edges = (curve->numverts - 1) << d;
+ const int hstep = (1 << (subdiv - d - 1)) * vertco_stride;
+ const int step = (1 << (subdiv - d)) * vertco_stride;
+
+ /* Calculate edge points */
+ {
+ float *p = r_vertco;
+ for (int k = 0; k < num_edges; ++k) {
+ float *ps = POINTER_OFFSET(p, step);
+ float *ph = POINTER_OFFSET(p, hstep);
+ add_v3_v3v3(ph, p, ps);
+ mul_v3_fl(ph, 0.5f);
+ p = ps;
+ }
+ }
+
+ /* Move original points */
+ {
+ float *p = r_vertco;
+ for (int k = 1; k < num_edges; ++k) {
+ float *ps = POINTER_OFFSET(p, step);
+ float *ph = POINTER_OFFSET(p, hstep);
+ float *hp = POINTER_OFFSET(p, -hstep);
+ add_v3_v3v3(p, hp, ph);
+ mul_v3_fl(p, 0.5f);
+ p = ps;
+ }
+ }
+ }
+
+ const int num_verts = ((curve->numverts - 1) << subdiv) + 1;
+ return num_verts;
+}
+
+/* === Render API === */
+
+/* Calculate required size for render buffers. */
+void BKE_hair_render_get_buffer_size(
+ const HairExportCache* cache,
+ int subdiv,
+ int *r_totcurves,
+ int *r_totverts)
+{
+ *r_totcurves = cache->totfollicles;
+
+ const int subdiv_factor = 1 << subdiv;
+ for (int i = 0; i < cache->totfollicles; ++i)
+ {
+ const int numverts = cache->fiber_curves[cache->follicles[i].curve].numverts;
+ *r_totverts = (numverts - 1) * subdiv_factor + 1;
+ }
+}
+
+/* Create render data in existing buffers.
+ * Buffers must be large enough according to BKE_hair_get_render_buffer_size.
+ */
+void BKE_hair_render_fill_buffers(
+ const HairExportCache* cache,
+ int subdiv,
+ int vertco_stride,
+ int *r_curvestart,
+ int *r_curvelen,
+ float *r_vertco)
+{
+ int vertstart = 0;
+ float *vert = r_vertco;
+ for (int i = 0; i < cache->totfollicles; ++i)
+ {
+ const HairFiberCurve *curve = &cache->fiber_curves[cache->follicles[i].curve];
+ const HairFiberVertex *verts = &cache->fiber_verts[curve->vertstart];
+ const int numverts = curve->numverts;
+ r_curvestart[i] = vertstart;
+ r_curvelen[i] = numverts;
+
+ hair_curve_subdivide(curve, verts, subdiv, vertco_stride, vert);
+
+ vertstart += numverts;
+ vert = POINTER_OFFSET(vert, vertco_stride * numverts);
+ }
+}
diff --git a/source/blender/blenkernel/intern/library.c b/source/blender/blenkernel/intern/library.c
index d81dddb2b98..07bd97b379b 100644
--- a/source/blender/blenkernel/intern/library.c
+++ b/source/blender/blenkernel/intern/library.c
@@ -47,8 +47,8 @@
#include "DNA_brush_types.h"
#include "DNA_cachefile_types.h"
#include "DNA_camera_types.h"
-#include "DNA_group_types.h"
#include "DNA_gpencil_types.h"
+#include "DNA_group_types.h"
#include "DNA_ipo_types.h"
#include "DNA_key_types.h"
#include "DNA_lamp_types.h"
@@ -1057,6 +1057,8 @@ void BKE_main_lib_objects_recalc_all(Main *bmain)
DEG_id_type_tag(bmain, ID_OB);
}
+BLI_STATIC_ASSERT(MAX_LIBARRAY == INDEX_ID_NULL + 1, "MAX_LIBARRAY must be large enough for all ID types")
+
/**
* puts into array *lb pointers to all the ListBase structs in main,
* and returns the number of them as the function result. This is useful for
diff --git a/source/blender/blenkernel/intern/mesh_sample.c b/source/blender/blenkernel/intern/mesh_sample.c
new file mode 100644
index 00000000000..4071adbb679
--- /dev/null
+++ b/source/blender/blenkernel/intern/mesh_sample.c
@@ -0,0 +1,1673 @@
+/*
+ * ***** 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_customdata.h"
+#include "BKE_mesh_sample.h"
+#include "BKE_mesh.h"
+#include "BKE_mesh_runtime.h"
+
+#include "BLI_strict_flags.h"
+
+#define SAMPLE_INDEX_INVALID 0xFFFFFFFF
+
+#define DEFAULT_TASK_SIZE 1024
+
+/* ==== Utility Functions ==== */
+
+static float calc_mesh_area(Mesh *mesh)
+{
+ int numtris = BKE_mesh_runtime_looptri_len(mesh);
+ MVert *mverts = mesh->mvert;
+ MLoop *mloops = mesh->mloop;
+
+ float totarea = 0.0;
+ const MLoopTri *tri = BKE_mesh_runtime_looptri_ensure(mesh);
+ 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(Mesh *mesh, const MLoopTri *tri, const float *loop_weights, float *r_area)
+{
+ const MVert *mverts = mesh->mvert;
+ const MLoop *mloops = mesh->mloop;
+ const unsigned int index1 = tri->tri[0];
+ const unsigned int index2 = tri->tri[1];
+ const unsigned int index3 = tri->tri[2];
+ const MVert *v1 = &mverts[mloops[index1].v];
+ const MVert *v2 = &mverts[mloops[index2].v];
+ const MVert *v3 = &mverts[mloops[index3].v];
+
+ float weight = area_tri_v3(v1->co, v2->co, v3->co);
+ if (r_area) {
+ *r_area = weight;
+ }
+
+ if (loop_weights) {
+ float w1 = loop_weights[index1];
+ float w2 = loop_weights[index2];
+ float w3 = loop_weights[index3];
+
+ weight *= (w1 + w2 + w3) / 3.0f;
+ }
+
+ return weight;
+}
+
+float* BKE_mesh_sample_calc_triangle_weights(Mesh *mesh, const float *loop_weights, float *r_area)
+{
+ int numtris = BKE_mesh_runtime_looptri_len(mesh);
+ int numweights = numtris;
+
+ 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 = BKE_mesh_runtime_looptri_ensure(mesh);
+ for (int i = 0; i < numtris; ++i, ++mt) {
+ tri_weights[i] = totweight;
+
+ float triarea;
+ float triweight = triangle_weight(mesh, mt, loop_weights, &triarea);
+ totarea += triarea;
+ totweight += triweight;
+ }
+ }
+
+ /* normalize */
+ if (totweight > 0.0f) {
+ float norm = 1.0f / totweight;
+ const MLoopTri *mt = BKE_mesh_runtime_looptri_ensure(mesh);
+ 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, Mesh *mesh, int looptri_index, const float loc[3])
+{
+ const MLoop *mloops = mesh->mloop;
+ const MLoopTri *looptri = &BKE_mesh_runtime_looptri_ensure(mesh)[looptri_index];
+ const MVert *mverts = mesh->mvert;
+
+ const unsigned int i1 = mloops[looptri->tri[0]].v;
+ const unsigned int i2 = mloops[looptri->tri[1]].v;
+ const unsigned int i3 = mloops[looptri->tri[2]].v;
+ const float *v1 = mverts[i1].co;
+ const float *v2 = mverts[i2].co;
+ const float *v3 = mverts[i3].co;
+ float w[3];
+
+ interp_weights_tri_v3(w, v1, v2, v3, loc);
+
+ sample->orig_verts[0] = i1;
+ sample->orig_verts[1] = i2;
+ sample->orig_verts[2] = i3;
+ sample->orig_loops[0] = looptri->tri[0];
+ sample->orig_loops[1] = looptri->tri[1];
+ sample->orig_loops[2] = looptri->tri[2];
+ sample->orig_poly = looptri->poly;
+ copy_v3_v3(sample->orig_weights, w);
+}
+
+/* ==== 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;
+}
+
+/* Evaluate position and normal on the given mesh */
+
+bool BKE_mesh_sample_eval(const Mesh *mesh, const MeshSample *sample, float loc[3], float nor[3], float tang[3])
+{
+ const MVert *mverts = mesh->mvert;
+ const unsigned int totverts = (unsigned int)mesh->totvert;
+ const 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;
+ }
+}
+
+/* Evaluate position for the given shapekey */
+
+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 */
+ Mesh *mesh;
+
+ 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->mesh = 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)
+{
+ Mesh *mesh = gen->base.mesh;
+ const int num_verts = mesh->totvert;
+
+ BKE_mesh_ensure_normals(mesh);
+
+ 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 = mesh->totpoly;
+ const MLoop *mloops = mesh->mloop;
+ const MPoly *mp = mesh->mpoly;
+ 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(const Mesh *mesh, const int *loops, MeshSample *sample)
+{
+ const MLoop *mloops = mesh->mloop;
+
+ if (loops[0] < 0) {
+ return false;
+ }
+
+ sample->orig_poly = SAMPLE_INDEX_INVALID;
+
+ 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)
+{
+ const Mesh *mesh = gen->base.mesh;
+ const int num_verts = mesh->totvert;
+
+ 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(mesh, 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.mesh->totvert;
+}
+
+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;
+ const float *loop_weights;
+
+ /* 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)
+{
+ Mesh *mesh = gen->base.mesh;
+
+ BKE_mesh_ensure_normals(mesh);
+
+ if (gen->use_area_weight) {
+ gen->tri_weights = BKE_mesh_sample_calc_triangle_weights(mesh, gen->loop_weights, 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)
+{
+ Mesh *mesh = gen->base.mesh;
+ RNG *rng = thread_ctx;
+ const MLoop *mloops = mesh->mloop;
+ const MLoopTri *mtris = BKE_mesh_runtime_looptri_ensure(mesh);
+ int tottris = BKE_mesh_runtime_looptri_len(mesh);
+ 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;
+ sample->orig_loops[0] = mtri->tri[0];
+ sample->orig_loops[1] = mtri->tri[1];
+ sample->orig_loops[2] = mtri->tri[2];
+ sample->orig_poly = mtri->poly;
+
+ 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, const float *loop_weights)
+{
+ 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->loop_weights = loop_weights;
+
+ 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)
+{
+ Mesh *mesh = gen->base.mesh;
+
+ memset(&gen->bvhdata, 0, sizeof(gen->bvhdata));
+
+ BKE_mesh_runtime_looptri_ensure(mesh);
+ if (BKE_mesh_runtime_looptri_len(mesh) == 0)
+ return;
+
+ BKE_bvhtree_from_mesh_get(&gen->bvhdata, mesh, BVHTREE_FROM_LOOPTRI, 2);
+}
+
+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.mesh, 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 {
+ MeshSample base;
+ 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];
+ Mesh *mesh = (*ptrs)[2];
+
+ IndexedMeshSample *isample = &gen->uniform_samples[iter];
+ const MeshSample *sample = &samples[iter];
+
+ memcpy(&isample->base, sample, sizeof(MeshSample));
+ float nor[3], tang[3];
+ BKE_mesh_sample_eval(mesh, 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.mesh) * 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)
+{
+ Mesh *mesh = gen->base.mesh;
+ static const unsigned int uniform_sample_ratio = 10;
+
+ // Determine cell size
+ {
+ float min[3], max[3];
+ INIT_MINMAX(min, max);
+ BKE_mesh_minmax(mesh, 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, mesh);
+
+ 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, mesh };
+ {
+ 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, &isample->base, sizeof(*sample));
+
+ 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, const float *loop_weights)
+{
+ 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, loop_weights);
+ 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)
+{
+ Mesh *mesh = gen->base.mesh;
+
+ memset(&gen->bvhdata, 0, sizeof(gen->bvhdata));
+
+ BKE_mesh_runtime_looptri_ensure(mesh);
+
+ if (BKE_mesh_runtime_looptri_len(mesh) == 0)
+ return;
+
+ BKE_bvhtree_from_mesh_get(&gen->bvhdata, mesh, BVHTREE_FROM_LOOPTRI, 2);
+
+ INIT_MINMAX(gen->min, gen->max);
+ BKE_mesh_minmax(mesh, 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;
+ sample->orig_loops[0] = SAMPLE_INDEX_INVALID;
+ sample->orig_loops[1] = SAMPLE_INDEX_INVALID;
+ sample->orig_loops[2] = SAMPLE_INDEX_INVALID;
+ sample->orig_poly = 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, Mesh *mesh)
+{
+ BLI_assert(gen->mesh == NULL && "Generator already bound");
+
+ gen->mesh = mesh;
+ if (gen->bind) {
+ gen->bind(gen);
+ }
+}
+
+void BKE_mesh_sample_generator_unbind(MeshSampleGenerator *gen)
+{
+ if (gen->mesh) {
+ if (gen->unbind) {
+ gen->unbind(gen);
+ }
+ gen->mesh = 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, Mesh *mesh, 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_mesh(psys, mesh, pa, &mapindex, mapfw))
+ return false;
+
+ mface = mesh->mface;
+ mverts = mesh->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), Mesh *mesh, BVHTreeFromMesh *bvhtree, ParticleData *pa)
+{
+ BVHTreeNearest nearest;
+ float vec[3], nor[3], tang[3];
+
+ BKE_mesh_sample_eval(mesh, 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 = mesh->mface;
+ MVert *mverts = mesh->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;
+}
diff --git a/source/blender/blenkernel/intern/particle.c b/source/blender/blenkernel/intern/particle.c
index 82514f0c92d..be97e62d16a 100644
--- a/source/blender/blenkernel/intern/particle.c
+++ b/source/blender/blenkernel/intern/particle.c
@@ -1437,7 +1437,7 @@ int psys_particle_dm_face_lookup(
return DMCACHE_NOTFOUND;
}
-static int psys_map_index_on_dm(Mesh *mesh, int from, int index, int index_dmcache, const float fw[4], float UNUSED(foffset), int *mapindex, float mapfw[4])
+static int psys_map_index_on_mesh(Mesh *mesh, int from, int index, int index_dmcache, const float fw[4], float UNUSED(foffset), int *mapindex, float mapfw[4])
{
if (index < 0)
return 0;
@@ -1497,6 +1497,11 @@ static int psys_map_index_on_dm(Mesh *mesh, int from, int index, int index_dmcac
return 1;
}
+int psys_get_index_on_mesh(ParticleSystem *psys, Mesh *mesh, ParticleData *pa, int *mapindex, float mapfw[4])
+{
+ return psys_map_index_on_mesh(mesh, psys->part->from, pa->num, pa->num_dmcache, pa->fuv, pa->foffset, mapindex, mapfw);
+}
+
/* interprets particle data to get a point on a mesh in object space */
void psys_particle_on_dm(Mesh *mesh_final, int from, int index, int index_dmcache,
const float fw[4], float foffset, float vec[3], float nor[3], float utan[3], float vtan[3],
@@ -1506,7 +1511,7 @@ void psys_particle_on_dm(Mesh *mesh_final, int from, int index, int index_dmcach
float (*orcodata)[3];
int mapindex;
- if (!psys_map_index_on_dm(mesh_final, from, index, index_dmcache, fw, foffset, &mapindex, mapfw)) {
+ if (!psys_map_index_on_mesh(mesh_final, from, index, index_dmcache, fw, foffset, &mapindex, mapfw)) {
if (vec) { vec[0] = vec[1] = vec[2] = 0.0; }
if (nor) { nor[0] = nor[1] = 0.0; nor[2] = 1.0; }
if (orco) { orco[0] = orco[1] = orco[2] = 0.0; }
@@ -1571,7 +1576,7 @@ float psys_particle_value_from_verts(Mesh *mesh, short from, ParticleData *pa, f
float mapfw[4];
int mapindex;
- if (!psys_map_index_on_dm(mesh, from, pa->num, pa->num_dmcache, pa->fuv, pa->foffset, &mapindex, mapfw))
+ if (!psys_map_index_on_mesh(mesh, from, pa->num, pa->num_dmcache, pa->fuv, pa->foffset, &mapindex, mapfw))
return 0.0f;
return psys_interpolate_value_from_verts(mesh, from, mapindex, mapfw, values);
diff --git a/source/blender/blenlib/BLI_math_geom.h b/source/blender/blenlib/BLI_math_geom.h
index 640f3143009..55e0bbfa786 100644
--- a/source/blender/blenlib/BLI_math_geom.h
+++ b/source/blender/blenlib/BLI_math_geom.h
@@ -376,6 +376,8 @@ bool clip_segment_v3_plane_n(
/****************************** Interpolation ********************************/
void interp_weights_tri_v3(float w[3], const float a[3], const float b[3], const float c[3], const float p[3]);
void interp_weights_quad_v3(float w[4], const float a[3], const float b[3], const float c[3], const float d[3], const float p[3]);
+/* also returns three indices of the triangle actually used */
+void interp_weights_quad_v3_index(int tri[3], float w[4], const float v1[3], const float v2[3], const float v3[3], const float v4[3], const float co[3]);
void interp_weights_poly_v3(float w[], float v[][3], const int n, const float co[3]);
void interp_weights_poly_v2(float w[], float v[][2], const int n, const float co[2]);
diff --git a/source/blender/blenlib/intern/math_geom.c b/source/blender/blenlib/intern/math_geom.c
index c04fd79f7aa..5055a29b79d 100644
--- a/source/blender/blenlib/intern/math_geom.c
+++ b/source/blender/blenlib/intern/math_geom.c
@@ -3272,6 +3272,71 @@ void interp_weights_quad_v3(float w[4], const float v1[3], const float v2[3], co
}
}
+void interp_weights_quad_v3_index(int tri[3], float w[4], const float v1[3], const float v2[3], const float v3[3], const float v4[3], const float co[3])
+{
+ float w2[3];
+
+ w[0] = w[1] = w[2] = w[3] = 0.0f;
+ tri[0] = tri[1] = tri[2] = -1;
+
+ /* first check for exact match */
+ if (equals_v3v3(co, v1)) {
+ w[0] = 1.0f;
+ tri[0] = 0; tri[1] = 1; tri[2] = 3;
+ }
+ else if (equals_v3v3(co, v2)) {
+ w[1] = 1.0f;
+ tri[0] = 0; tri[1] = 1; tri[2] = 3;
+ }
+ else if (equals_v3v3(co, v3)) {
+ w[2] = 1.0f;
+ tri[0] = 1; tri[1] = 2; tri[2] = 3;
+ }
+ else if (v4 && equals_v3v3(co, v4)) {
+ w[3] = 1.0f;
+ tri[0] = 1; tri[1] = 2; tri[2] = 3;
+ }
+ else {
+ /* otherwise compute barycentric interpolation weights */
+ float n1[3], n2[3], n[3];
+ bool degenerate;
+
+ sub_v3_v3v3(n1, v1, v3);
+ if (v4) {
+ sub_v3_v3v3(n2, v2, v4);
+ }
+ else {
+ sub_v3_v3v3(n2, v2, v3);
+ }
+ cross_v3_v3v3(n, n1, n2);
+
+ /* OpenGL seems to split this way, so we do too */
+ if (v4) {
+ degenerate = barycentric_weights(v1, v2, v4, co, n, w);
+ SWAP(float, w[2], w[3]);
+ tri[0] = 0; tri[1] = 1; tri[2] = 3;
+
+ if (degenerate || (w[0] < 0.0f)) {
+ /* if w[1] is negative, co is on the other side of the v1-v3 edge,
+ * so we interpolate using the other triangle */
+ degenerate = barycentric_weights(v2, v3, v4, co, n, w2);
+
+ if (!degenerate) {
+ w[0] = 0.0f;
+ w[1] = w2[0];
+ w[2] = w2[1];
+ w[3] = w2[2];
+ tri[0] = 1; tri[1] = 2; tri[2] = 3;
+ }
+ }
+ }
+ else {
+ barycentric_weights(v1, v2, v3, co, n, w);
+ tri[0] = 0; tri[1] = 1; tri[2] = 2;
+ }
+ }
+}
+
/* return 1 of point is inside triangle, 2 if it's on the edge, 0 if point is outside of triangle */
int barycentric_inside_triangle_v2(const float w[3])
{
diff --git a/source/blender/blenloader/intern/readfile.c b/source/blender/blenloader/intern/readfile.c
index a990e9213c3..9655d69ca4a 100644
--- a/source/blender/blenloader/intern/readfile.c
+++ b/source/blender/blenloader/intern/readfile.c
@@ -73,6 +73,7 @@
#include "DNA_group_types.h"
#include "DNA_gpencil_types.h"
#include "DNA_gpencil_modifier_types.h"
+#include "DNA_hair_types.h"
#include "DNA_shader_fx_types.h"
#include "DNA_ipo_types.h"
#include "DNA_key_types.h"
@@ -5095,6 +5096,25 @@ static void direct_link_pose(FileData *fd, bPose *pose)
}
}
+static void direct_link_hair(FileData *fd, HairSystem* hsys)
+{
+ if (!hsys) {
+ return;
+ }
+
+ hsys->pattern = newdataadr(fd, hsys->pattern);
+ if ( hsys->pattern )
+ {
+ hsys->pattern->follicles = newdataadr(fd, hsys->pattern->follicles);
+ }
+
+ hsys->curve_data.curves = newdataadr(fd, hsys->curve_data.curves);
+ hsys->curve_data.verts = newdataadr(fd, hsys->curve_data.verts);
+
+ hsys->draw_batch_cache = NULL;
+ hsys->draw_texture_cache = NULL;
+}
+
static void direct_link_modifiers(FileData *fd, ListBase *lb)
{
ModifierData *md;
@@ -5418,6 +5438,16 @@ static void direct_link_modifiers(FileData *fd, ListBase *lb)
}
}
}
+ else if (md->type == eModifierType_Hair) {
+ HairModifierData *hmd = (HairModifierData *)md;
+
+ hmd->hair_system = newdataadr(fd, hmd->hair_system);
+ direct_link_hair(fd, hmd->hair_system);
+
+ hmd->draw_settings = newdataadr(fd, hmd->draw_settings);
+
+ BLI_listbase_clear(&hmd->fiber_curves); // runtime
+ }
}
}
diff --git a/source/blender/blenloader/intern/writefile.c b/source/blender/blenloader/intern/writefile.c
index 2d7b5f13b07..f5ee0bdb244 100644
--- a/source/blender/blenloader/intern/writefile.c
+++ b/source/blender/blenloader/intern/writefile.c
@@ -121,6 +121,7 @@
#include "DNA_gpencil_modifier_types.h"
#include "DNA_shader_fx_types.h"
#include "DNA_fileglobal_types.h"
+#include "DNA_hair_types.h"
#include "DNA_key_types.h"
#include "DNA_lattice_types.h"
#include "DNA_lamp_types.h"
@@ -1617,6 +1618,18 @@ static void write_fmaps(WriteData *wd, ListBase *fbase)
}
}
+static void write_hair(WriteData *wd, HairSystem *hsys)
+{
+ if ( hsys->pattern )
+ {
+ writestruct(wd, DATA, HairPattern, 1, hsys->pattern);
+ writestruct(wd, DATA, HairFollicle, hsys->pattern->num_follicles, hsys->pattern->follicles);
+ }
+
+ writestruct(wd, DATA, HairFiberCurve, hsys->curve_data.totcurves, hsys->curve_data.curves);
+ writestruct(wd, DATA, HairFiberVertex, hsys->curve_data.totverts, hsys->curve_data.verts);
+}
+
static void write_modifiers(WriteData *wd, ListBase *modbase)
{
ModifierData *md;
@@ -1788,6 +1801,18 @@ static void write_modifiers(WriteData *wd, ListBase *modbase)
}
}
}
+ else if (md->type == eModifierType_Hair) {
+ HairModifierData *hmd = (HairModifierData *)md;
+
+ if (hmd->hair_system) {
+ writestruct(wd, DATA, HairSystem, 1, hmd->hair_system);
+ write_hair(wd, hmd->hair_system);
+ }
+ if (hmd->draw_settings)
+ {
+ writestruct(wd, DATA, HairDrawSettings, 1, hmd->draw_settings);
+ }
+ }
}
}
diff --git a/source/blender/draw/CMakeLists.txt b/source/blender/draw/CMakeLists.txt
index d672645dea0..d8420d6a127 100644
--- a/source/blender/draw/CMakeLists.txt
+++ b/source/blender/draw/CMakeLists.txt
@@ -58,6 +58,7 @@ set(SRC
intern/draw_cache.c
intern/draw_cache_impl_curve.c
intern/draw_cache_impl_displist.c
+ intern/draw_cache_impl_hair.c
intern/draw_cache_impl_lattice.c
intern/draw_cache_impl_mesh.c
intern/draw_cache_impl_metaball.c
diff --git a/source/blender/draw/engines/eevee/eevee_materials.c b/source/blender/draw/engines/eevee/eevee_materials.c
index 9ef64477aca..1eb63e0f0eb 100644
--- a/source/blender/draw/engines/eevee/eevee_materials.c
+++ b/source/blender/draw/engines/eevee/eevee_materials.c
@@ -31,6 +31,7 @@
#include "BLI_rand.h"
#include "BLI_string_utils.h"
+#include "BKE_hair.h"
#include "BKE_particle.h"
#include "BKE_paint.h"
#include "BKE_pbvh.h"
@@ -39,6 +40,7 @@
#include "DNA_world_types.h"
#include "DNA_modifier_types.h"
#include "DNA_view3d_types.h"
+#include "DNA_hair_types.h"
#include "GPU_material.h"
@@ -812,6 +814,7 @@ struct GPUMaterial *EEVEE_material_hair_get(
options |= eevee_material_shadow_option(shadow_method);
GPUMaterial *mat = DRW_shader_find_from_material(ma, engine, options, true);
+
if (mat) {
return mat;
}
@@ -862,14 +865,16 @@ static struct DRWShadingGroup *EEVEE_default_shading_group_create(
**/
static struct DRWShadingGroup *EEVEE_default_shading_group_get(
EEVEE_ViewLayerData *sldata, EEVEE_Data *vedata,
- Object *ob, ParticleSystem *psys, ModifierData *md,
+ Object *ob,
+ ParticleSystem *psys, ModifierData *md,
+ HairSystem *hsys, const HairDrawSettings *hdraw, struct Mesh *scalp,
bool is_hair, bool is_flat_normal, bool use_ssr, int shadow_method)
{
static int ssr_id;
ssr_id = (use_ssr) ? 1 : -1;
int options = VAR_MAT_MESH;
- BLI_assert(!is_hair || (ob && psys && md));
+ BLI_assert(!is_hair || (ob && psys && md) || (ob && hsys && hdraw && scalp));
if (is_hair) options |= VAR_MAT_HAIR;
if (is_flat_normal) options |= VAR_MAT_FLAT;
@@ -894,10 +899,19 @@ static struct DRWShadingGroup *EEVEE_default_shading_group_get(
}
if (is_hair) {
- DRWShadingGroup *shgrp = DRW_shgroup_hair_create(ob, psys, md,
- vedata->psl->default_pass[options],
- e_data.default_lit[options]);
+ DRWShadingGroup *shgrp = NULL;
+ if (psys && md) {
+ shgrp = DRW_shgroup_particle_hair_create(ob, psys, md,
+ vedata->psl->default_pass[options],
+ e_data.default_lit[options]);
+ }
+ else if (hsys && hdraw && scalp) {
+ shgrp = DRW_shgroup_hair_create(ob, hsys, scalp, hdraw,
+ vedata->psl->default_pass[options],
+ e_data.default_lit[options]);
+ }
add_standard_uniforms(shgrp, sldata, vedata, &ssr_id, NULL, true, true, false, false, false);
+
return shgrp;
}
else {
@@ -1266,7 +1280,7 @@ static void material_opaque(
if (*shgrp == NULL) {
bool use_ssr = ((effects->enabled_effects & EFFECT_SSR) != 0);
*shgrp = EEVEE_default_shading_group_get(sldata, vedata,
- NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL,
false, use_flat_nor, use_ssr, linfo->shadow_method);
DRW_shgroup_uniform_vec3(*shgrp, "basecol", color_p, 1);
DRW_shgroup_uniform_float(*shgrp, "metallic", metal_p, 1);
@@ -1409,6 +1423,217 @@ static void material_transparent(
}
}
+static void material_particle_hair(
+ EEVEE_Data *vedata,
+ EEVEE_ViewLayerData *sldata,
+ Object *ob,
+ ParticleSystem *psys,
+ ModifierData *md)
+{
+ EEVEE_PassList *psl = ((EEVEE_Data *)vedata)->psl;
+ EEVEE_StorageList *stl = ((EEVEE_Data *)vedata)->stl;
+ const DRWContextState *draw_ctx = DRW_context_state_get();
+ Scene *scene = draw_ctx->scene;
+ bool use_ssr = ((stl->effects->enabled_effects & EFFECT_SSR) != 0);
+
+ if (!psys_check_enabled(ob, psys, false)) {
+ return;
+ }
+ if (!DRW_check_psys_visible_within_active_context(ob, psys)) {
+ return;
+ }
+ ParticleSettings *part = psys->part;
+ const int draw_as = (part->draw_as == PART_DRAW_REND) ? part->ren_as : part->draw_as;
+ if (draw_as != PART_DRAW_PATH) {
+ return;
+ }
+
+ DRWShadingGroup *shgrp = NULL;
+ Material *ma = give_current_material(ob, part->omat);
+
+ if (ma == NULL) {
+ ma = &defmaterial;
+ }
+
+ float *color_p = &ma->r;
+ float *metal_p = &ma->metallic;
+ float *spec_p = &ma->spec;
+ float *rough_p = &ma->roughness;
+
+ shgrp = DRW_shgroup_particle_hair_create(
+ ob, psys, md,
+ psl->depth_pass,
+ e_data.default_hair_prepass_sh);
+
+ shgrp = DRW_shgroup_particle_hair_create(
+ ob, psys, md,
+ psl->depth_pass_clip,
+ e_data.default_hair_prepass_clip_sh);
+ DRW_shgroup_uniform_block(shgrp, "clip_block", sldata->clip_ubo);
+
+ shgrp = NULL;
+ if (ma->use_nodes && ma->nodetree) {
+ static int ssr_id;
+ ssr_id = (use_ssr) ? 1 : -1;
+ static float half = 0.5f;
+ static float error_col[3] = {1.0f, 0.0f, 1.0f};
+ static float compile_col[3] = {0.5f, 0.5f, 0.5f};
+ struct GPUMaterial *gpumat = EEVEE_material_hair_get(scene, ma, sldata->lamps->shadow_method);
+
+ switch (GPU_material_status(gpumat)) {
+ case GPU_MAT_SUCCESS:
+ {
+ bool use_diffuse = GPU_material_flag_get(gpumat, GPU_MATFLAG_DIFFUSE);
+ bool use_glossy = GPU_material_flag_get(gpumat, GPU_MATFLAG_GLOSSY);
+ bool use_refract = GPU_material_flag_get(gpumat, GPU_MATFLAG_REFRACT);
+
+ shgrp = DRW_shgroup_material_particle_hair_create(
+ ob, psys, md,
+ psl->material_pass,
+ gpumat);
+
+ add_standard_uniforms(shgrp, sldata, vedata, &ssr_id, NULL,
+ use_diffuse, use_glossy, use_refract, false, false);
+ break;
+ }
+ case GPU_MAT_QUEUED:
+ {
+ color_p = compile_col;
+ metal_p = spec_p = rough_p = &half;
+ break;
+ }
+ case GPU_MAT_FAILED:
+ default:
+ color_p = error_col;
+ metal_p = spec_p = rough_p = &half;
+ break;
+ }
+ }
+
+ /* Fallback to default shader */
+ if (shgrp == NULL) {
+ shgrp = EEVEE_default_shading_group_get(sldata, vedata,
+ ob, psys, md, NULL, NULL, NULL,
+ true, false, use_ssr,
+ sldata->lamps->shadow_method);
+ DRW_shgroup_uniform_vec3(shgrp, "basecol", color_p, 1);
+ DRW_shgroup_uniform_float(shgrp, "metallic", metal_p, 1);
+ DRW_shgroup_uniform_float(shgrp, "specular", spec_p, 1);
+ DRW_shgroup_uniform_float(shgrp, "roughness", rough_p, 1);
+ }
+
+ /* Shadows */
+ DRW_shgroup_particle_hair_create(
+ ob, psys, md,
+ psl->shadow_pass,
+ e_data.default_hair_prepass_sh);
+}
+
+static void material_hair(
+ EEVEE_Data *vedata,
+ EEVEE_ViewLayerData *sldata,
+ Object *ob,
+ HairSystem *hsys,
+ const HairDrawSettings *draw_set,
+ Material *ma,
+ struct Mesh *scalp)
+{
+ EEVEE_PassList *psl = ((EEVEE_Data *)vedata)->psl;
+ EEVEE_StorageList *stl = ((EEVEE_Data *)vedata)->stl;
+ const bool use_ssr = ((stl->effects->enabled_effects & EFFECT_SSR) != 0);
+ const DRWContextState *draw_ctx = DRW_context_state_get();
+ Scene *scene = draw_ctx->scene;
+ float mat[4][4];
+ copy_m4_m4(mat, ob->obmat);
+
+ if (draw_set->fiber_mode != HAIR_DRAW_FIBER_CURVES)
+ {
+ return;
+ }
+
+ if (ma == NULL) {
+ ma = &defmaterial;
+ }
+
+ {
+ /*DRWShadingGroup *shgrp =*/ DRW_shgroup_hair_create(
+ ob, hsys, scalp, draw_set,
+ psl->depth_pass,
+ e_data.default_hair_prepass_sh);
+ }
+ {
+ DRWShadingGroup *shgrp = DRW_shgroup_hair_create(
+ ob, hsys, scalp, draw_set,
+ psl->depth_pass_clip,
+ e_data.default_hair_prepass_clip_sh);
+ DRW_shgroup_uniform_block(shgrp, "clip_block", sldata->clip_ubo);
+ }
+
+ {
+ float *color_p = &ma->r;
+ float *metal_p = &ma->metallic;
+ float *spec_p = &ma->spec;
+ float *rough_p = &ma->roughness;
+
+ DRWShadingGroup *shgrp = NULL;
+ if (ma->use_nodes && ma->nodetree) {
+ static int ssr_id;
+ ssr_id = (use_ssr) ? 1 : -1;
+ static float half = 0.5f;
+ static float error_col[3] = {1.0f, 0.0f, 1.0f};
+ static float compile_col[3] = {0.5f, 0.5f, 0.5f};
+ struct GPUMaterial *gpumat = EEVEE_material_hair_get(scene, ma, sldata->lamps->shadow_method);
+
+ switch (GPU_material_status(gpumat)) {
+ case GPU_MAT_SUCCESS:
+ {
+ bool use_diffuse = GPU_material_flag_get(gpumat, GPU_MATFLAG_DIFFUSE);
+ bool use_glossy = GPU_material_flag_get(gpumat, GPU_MATFLAG_GLOSSY);
+ bool use_refract = GPU_material_flag_get(gpumat, GPU_MATFLAG_REFRACT);
+
+ shgrp = DRW_shgroup_material_hair_create(
+ ob, hsys, scalp, draw_set,
+ psl->material_pass,
+ gpumat);
+
+ add_standard_uniforms(shgrp, sldata, vedata, &ssr_id, NULL,
+ use_diffuse, use_glossy, use_refract, false, false);
+ break;
+ }
+ case GPU_MAT_QUEUED:
+ {
+ color_p = compile_col;
+ metal_p = spec_p = rough_p = &half;
+ break;
+ }
+ case GPU_MAT_FAILED:
+ default:
+ color_p = error_col;
+ metal_p = spec_p = rough_p = &half;
+ break;
+ }
+ }
+
+ /* Fallback to default shader */
+ if (shgrp == NULL) {
+ shgrp = EEVEE_default_shading_group_get(sldata, vedata,
+ ob, NULL, NULL, hsys, draw_set, scalp,
+ true, false, use_ssr,
+ sldata->lamps->shadow_method);
+ DRW_shgroup_uniform_vec3(shgrp, "basecol", color_p, 1);
+ DRW_shgroup_uniform_float(shgrp, "metallic", metal_p, 1);
+ DRW_shgroup_uniform_float(shgrp, "specular", spec_p, 1);
+ DRW_shgroup_uniform_float(shgrp, "roughness", rough_p, 1);
+ }
+ }
+
+ /* Shadows */
+ DRW_shgroup_hair_create(
+ ob, hsys, scalp, draw_set,
+ psl->shadow_pass,
+ e_data.default_hair_prepass_sh);
+}
+
void EEVEE_materials_cache_populate(EEVEE_Data *vedata, EEVEE_ViewLayerData *sldata, Object *ob, bool *cast_shadow)
{
EEVEE_PassList *psl = vedata->psl;
@@ -1600,112 +1825,24 @@ void EEVEE_materials_cache_populate(EEVEE_Data *vedata, EEVEE_ViewLayerData *sld
void EEVEE_hair_cache_populate(EEVEE_Data *vedata, EEVEE_ViewLayerData *sldata, Object *ob, bool *cast_shadow)
{
- EEVEE_PassList *psl = vedata->psl;
- EEVEE_StorageList *stl = vedata->stl;
const DRWContextState *draw_ctx = DRW_context_state_get();
- Scene *scene = draw_ctx->scene;
-
- bool use_ssr = ((stl->effects->enabled_effects & EFFECT_SSR) != 0);
if (ob->type == OB_MESH) {
if (ob != draw_ctx->object_edit) {
for (ModifierData *md = ob->modifiers.first; md; md = md->next) {
- if (md->type != eModifierType_ParticleSystem) {
- continue;
- }
- ParticleSystem *psys = ((ParticleSystemModifierData *)md)->psys;
- if (!psys_check_enabled(ob, psys, false)) {
- continue;
- }
- if (!DRW_check_psys_visible_within_active_context(ob, psys)) {
- continue;
- }
- ParticleSettings *part = psys->part;
- const int draw_as = (part->draw_as == PART_DRAW_REND) ? part->ren_as : part->draw_as;
- if (draw_as != PART_DRAW_PATH) {
- continue;
- }
-
- DRWShadingGroup *shgrp = NULL;
- Material *ma = give_current_material(ob, part->omat);
-
- if (ma == NULL) {
- ma = &defmaterial;
- }
-
- float *color_p = &ma->r;
- float *metal_p = &ma->metallic;
- float *spec_p = &ma->spec;
- float *rough_p = &ma->roughness;
-
- shgrp = DRW_shgroup_hair_create(
- ob, psys, md,
- psl->depth_pass,
- e_data.default_hair_prepass_sh);
-
- shgrp = DRW_shgroup_hair_create(
- ob, psys, md,
- psl->depth_pass_clip,
- e_data.default_hair_prepass_clip_sh);
- DRW_shgroup_uniform_block(shgrp, "clip_block", sldata->clip_ubo);
-
- shgrp = NULL;
- if (ma->use_nodes && ma->nodetree) {
- static int ssr_id;
- ssr_id = (use_ssr) ? 1 : -1;
- static float half = 0.5f;
- static float error_col[3] = {1.0f, 0.0f, 1.0f};
- static float compile_col[3] = {0.5f, 0.5f, 0.5f};
- struct GPUMaterial *gpumat = EEVEE_material_hair_get(scene, ma, sldata->lamps->shadow_method);
-
- switch (GPU_material_status(gpumat)) {
- case GPU_MAT_SUCCESS:
- {
- bool use_diffuse = GPU_material_flag_get(gpumat, GPU_MATFLAG_DIFFUSE);
- bool use_glossy = GPU_material_flag_get(gpumat, GPU_MATFLAG_GLOSSY);
- bool use_refract = GPU_material_flag_get(gpumat, GPU_MATFLAG_REFRACT);
-
- shgrp = DRW_shgroup_material_hair_create(
- ob, psys, md,
- psl->material_pass,
- gpumat);
-
- add_standard_uniforms(shgrp, sldata, vedata, &ssr_id, NULL,
- use_diffuse, use_glossy, use_refract, false, false);
- break;
- }
- case GPU_MAT_QUEUED:
- {
- color_p = compile_col;
- metal_p = spec_p = rough_p = &half;
- break;
- }
- case GPU_MAT_FAILED:
- default:
- color_p = error_col;
- metal_p = spec_p = rough_p = &half;
- break;
- }
+ if (md->type == eModifierType_ParticleSystem) {
+ ParticleSystem *psys = ((ParticleSystemModifierData *)md)->psys;
+ material_particle_hair(vedata, sldata, ob, psys, md);
+ *cast_shadow = true;
}
-
- /* Fallback to default shader */
- if (shgrp == NULL) {
- shgrp = EEVEE_default_shading_group_get(sldata, vedata,
- ob, psys, md,
- true, false, use_ssr,
- sldata->lamps->shadow_method);
- DRW_shgroup_uniform_vec3(shgrp, "basecol", color_p, 1);
- DRW_shgroup_uniform_float(shgrp, "metallic", metal_p, 1);
- DRW_shgroup_uniform_float(shgrp, "specular", spec_p, 1);
- DRW_shgroup_uniform_float(shgrp, "roughness", rough_p, 1);
+ else if (md->type == eModifierType_Hair) {
+ HairModifierData *hmd = (HairModifierData *)md;
+
+ const int material_index = 1; /* TODO */
+ Material *material = give_current_material(ob, material_index);
+
+ material_hair(vedata, sldata, ob, hmd->hair_system, hmd->draw_settings, material, ob->data);
}
-
- /* Shadows */
- DRW_shgroup_hair_create(
- ob, psys, md,
- psl->shadow_pass,
- e_data.default_hair_prepass_sh);
- *cast_shadow = true;
}
}
}
diff --git a/source/blender/draw/engines/workbench/shaders/workbench_prepass_vert.glsl b/source/blender/draw/engines/workbench/shaders/workbench_prepass_vert.glsl
index 66b529fcf5e..dfc45c8d04c 100644
--- a/source/blender/draw/engines/workbench/shaders/workbench_prepass_vert.glsl
+++ b/source/blender/draw/engines/workbench/shaders/workbench_prepass_vert.glsl
@@ -23,6 +23,8 @@ out vec3 normal_viewport;
out vec2 uv_interp;
#endif
+out int DEBUG;
+
/* From http://libnoise.sourceforge.net/noisegen/index.html */
float integer_noise(int n)
{
@@ -34,6 +36,7 @@ float integer_noise(int n)
void main()
{
#ifdef HAIR_SHADER
+ DEBUG = hair_get_strand_id();
# ifdef V3D_SHADING_TEXTURE_COLOR
vec2 uv = hair_get_customdata_vec2(u);
# endif
diff --git a/source/blender/draw/engines/workbench/workbench_deferred.c b/source/blender/draw/engines/workbench/workbench_deferred.c
index 23fbbe56c16..0442d6aef56 100644
--- a/source/blender/draw/engines/workbench/workbench_deferred.c
+++ b/source/blender/draw/engines/workbench/workbench_deferred.c
@@ -633,7 +633,7 @@ static void workbench_cache_populate_particles(WORKBENCH_Data *vedata, Object *o
struct GPUShader *shader = (color_type != V3D_SHADING_TEXTURE_COLOR) ?
wpd->prepass_solid_hair_sh :
wpd->prepass_texture_hair_sh;
- DRWShadingGroup *shgrp = DRW_shgroup_hair_create(
+ DRWShadingGroup *shgrp = DRW_shgroup_particle_hair_create(
ob, psys, md,
psl->prepass_hair_pass,
shader);
diff --git a/source/blender/draw/engines/workbench/workbench_forward.c b/source/blender/draw/engines/workbench/workbench_forward.c
index c81ecc492db..6d595ec00bd 100644
--- a/source/blender/draw/engines/workbench/workbench_forward.c
+++ b/source/blender/draw/engines/workbench/workbench_forward.c
@@ -425,7 +425,7 @@ static void workbench_forward_cache_populate_particles(WORKBENCH_Data *vedata, O
struct GPUShader *shader = (color_type != V3D_SHADING_TEXTURE_COLOR)
? wpd->transparent_accum_hair_sh
: wpd->transparent_accum_texture_hair_sh;
- DRWShadingGroup *shgrp = DRW_shgroup_hair_create(
+ DRWShadingGroup *shgrp = DRW_shgroup_particle_hair_create(
ob, psys, md,
psl->transparent_accum_pass,
shader);
@@ -444,7 +444,7 @@ static void workbench_forward_cache_populate_particles(WORKBENCH_Data *vedata, O
if (SPECULAR_HIGHLIGHT_ENABLED(wpd) || MATCAP_ENABLED(wpd)) {
DRW_shgroup_uniform_vec2(shgrp, "invertedViewportSize", DRW_viewport_invert_size_get(), 1);
}
- shgrp = DRW_shgroup_hair_create(ob, psys, md,
+ shgrp = DRW_shgroup_particle_hair_create(ob, psys, md,
vedata->psl->object_outline_pass,
e_data.object_outline_hair_sh);
DRW_shgroup_uniform_int(shgrp, "object_id", &material->object_id, 1);
diff --git a/source/blender/draw/intern/draw_cache.c b/source/blender/draw/intern/draw_cache.c
index c3fa9f5c1aa..85f38e2f5eb 100644
--- a/source/blender/draw/intern/draw_cache.c
+++ b/source/blender/draw/intern/draw_cache.c
@@ -3366,6 +3366,20 @@ GPUBatch *DRW_cache_particles_get_prim(int type)
return NULL;
}
+/* -------------------------------------------------------------------- */
+
+/** \name Hair */
+
+GPUBatch *DRW_cache_hair_get_fibers(struct HairSystem *hsys, const struct HairExportCache *hair_export)
+{
+ return DRW_hair_batch_cache_get_fibers(hsys, hair_export);
+}
+
+GPUBatch *DRW_cache_hair_get_follicle_points(struct HairSystem *hsys, const struct HairExportCache *hair_export)
+{
+ return DRW_hair_batch_cache_get_follicle_points(hsys, hair_export);
+}
+
/* 3D cursor */
GPUBatch *DRW_cache_cursor_get(bool crosshair_lines)
{
diff --git a/source/blender/draw/intern/draw_cache.h b/source/blender/draw/intern/draw_cache.h
index 7d0996b3059..06789e892d3 100644
--- a/source/blender/draw/intern/draw_cache.h
+++ b/source/blender/draw/intern/draw_cache.h
@@ -31,6 +31,8 @@ struct GPUMaterial;
struct ModifierData;
struct Object;
struct PTCacheEdit;
+struct HairSystem;
+struct HairExportCache;
void DRW_shape_cache_free(void);
void DRW_shape_cache_reset(void);
@@ -193,6 +195,10 @@ struct GPUBatch *DRW_cache_particles_get_edit_tip_points(
struct Object *object, struct ParticleSystem *psys, struct PTCacheEdit *edit);
struct GPUBatch *DRW_cache_particles_get_prim(int type);
+/* Hair */
+struct GPUBatch *DRW_cache_hair_get_fibers(struct HairSystem *hsys, const struct HairExportCache *hair_export);
+struct GPUBatch *DRW_cache_hair_get_follicle_points(struct HairSystem *hsys, const struct HairExportCache *hair_export);
+
/* Metaball */
struct GPUBatch *DRW_cache_mball_surface_get(struct Object *ob);
struct GPUBatch **DRW_cache_mball_surface_shaded_get(struct Object *ob, struct GPUMaterial **gpumat_array, uint gpumat_array_len);
diff --git a/source/blender/draw/intern/draw_cache_impl.h b/source/blender/draw/intern/draw_cache_impl.h
index d4dbe5db80d..0068e84055b 100644
--- a/source/blender/draw/intern/draw_cache_impl.h
+++ b/source/blender/draw/intern/draw_cache_impl.h
@@ -36,6 +36,8 @@ struct ListBase;
struct ModifierData;
struct ParticleSystem;
struct PTCacheEdit;
+struct HairSystem;
+struct HairExportCache;
struct Curve;
struct Lattice;
@@ -62,6 +64,9 @@ void DRW_particle_batch_cache_free(struct ParticleSystem *psys);
void DRW_gpencil_batch_cache_dirty(struct bGPdata *gpd);
void DRW_gpencil_batch_cache_free(struct bGPdata *gpd);
+void DRW_hair_batch_cache_dirty(struct HairSystem *hsys, int mode);
+void DRW_hair_batch_cache_free(struct HairSystem *hsys);
+
/* Curve */
struct GPUBatch *DRW_curve_batch_cache_get_wire_edge(struct Curve *cu, struct CurveCache *ob_curve_cache);
struct GPUBatch *DRW_curve_batch_cache_get_normal_edge(
@@ -146,4 +151,8 @@ struct GPUBatch *DRW_particles_batch_cache_get_edit_inner_points(
struct GPUBatch *DRW_particles_batch_cache_get_edit_tip_points(
struct Object *object, struct ParticleSystem *psys, struct PTCacheEdit *edit);
+/* Hair */
+struct GPUBatch *DRW_hair_batch_cache_get_fibers(struct HairSystem *hsys, const struct HairExportCache *hair_export);
+struct GPUBatch *DRW_hair_batch_cache_get_follicle_points(struct HairSystem *hsys, const struct HairExportCache *hair_export);
+
#endif /* __DRAW_CACHE_IMPL_H__ */
diff --git a/source/blender/draw/intern/draw_cache_impl_hair.c b/source/blender/draw/intern/draw_cache_impl_hair.c
new file mode 100644
index 00000000000..9f5afed49f1
--- /dev/null
+++ b/source/blender/draw/intern/draw_cache_impl_hair.c
@@ -0,0 +1,556 @@
+/*
+ * ***** 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.
+ *
+ * The Original Code is Copyright (C) 2017 by Blender Foundation.
+ * All rights reserved.
+ *
+ * Contributor(s): Blender Foundation, Mike Erwin, Dalai Felinto
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file draw_cache_impl_strands.c
+ * \ingroup draw
+ *
+ * \brief Strands API for render engines
+ */
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_utildefines.h"
+#include "BLI_math_vector.h"
+#include "BLI_ghash.h"
+
+#include "DNA_hair_types.h"
+#include "DNA_scene_types.h"
+
+#include "BKE_hair.h"
+#include "BKE_mesh_sample.h"
+
+#include "DEG_depsgraph.h"
+
+#include "GPU_batch.h"
+#include "GPU_extensions.h"
+#include "GPU_texture.h"
+
+#include "draw_common.h"
+#include "draw_cache_impl.h" /* own include */
+#include "draw_hair_private.h"
+
+#include "DRW_render.h"
+
+/* ---------------------------------------------------------------------- */
+/* Hair GPUBatch Cache */
+
+/* GPUBatch cache management. */
+
+typedef struct HairBatchCache {
+ ParticleHairCache hair;
+
+ bool is_dirty;
+} HairBatchCache;
+
+static void hair_batch_cache_clear(HairSystem *hsys);
+
+static bool hair_batch_cache_valid(HairSystem *hsys)
+{
+ HairBatchCache *cache = hsys->draw_batch_cache;
+
+ if (cache == NULL) {
+ return false;
+ }
+
+ if (cache->is_dirty) {
+ return false;
+ }
+
+ return true;
+}
+
+static void hair_batch_cache_init(HairSystem *hsys)
+{
+ HairBatchCache *cache = hsys->draw_batch_cache;
+
+ if (!cache) {
+ cache = hsys->draw_batch_cache = MEM_callocN(sizeof(*cache), __func__);
+ }
+ else {
+ memset(cache, 0, sizeof(*cache));
+ }
+
+ cache->is_dirty = false;
+}
+
+static HairBatchCache *hair_batch_cache_get(HairSystem *hsys)
+{
+ // Hair follicle binding needs to be updated after changes
+ BLI_assert(!(hsys->flag & HAIR_SYSTEM_UPDATE_FOLLICLE_BINDING));
+
+ if (!hair_batch_cache_valid(hsys)) {
+ hair_batch_cache_clear(hsys);
+ hair_batch_cache_init(hsys);
+ }
+ return hsys->draw_batch_cache;
+}
+
+void DRW_hair_batch_cache_dirty(HairSystem *hsys, int mode)
+{
+ HairBatchCache *cache = hsys->draw_batch_cache;
+ if (cache == NULL) {
+ return;
+ }
+ switch (mode) {
+ case BKE_HAIR_BATCH_DIRTY_ALL:
+ cache->is_dirty = true;
+ break;
+ default:
+ BLI_assert(0);
+ }
+}
+
+static void hair_batch_cache_clear(HairSystem *hsys)
+{
+ HairBatchCache *cache = hsys->draw_batch_cache;
+ if (cache) {
+ particle_batch_cache_clear_hair(&cache->hair);
+ }
+}
+
+void DRW_hair_batch_cache_free(HairSystem *hsys)
+{
+ hair_batch_cache_clear(hsys);
+ MEM_SAFE_FREE(hsys->draw_batch_cache);
+}
+
+static void hair_batch_cache_ensure_count(
+ const HairExportCache *hair_export,
+ ParticleHairCache *cache)
+{
+ cache->strands_len = hair_export->totfollicles;
+ cache->elems_len = hair_export->totverts - hair_export->totcurves;
+ cache->point_len = hair_export->totverts;
+}
+
+static void hair_batch_cache_fill_segments_proc_pos(
+ const HairExportCache *hair_export,
+ GPUVertBufRaw *attr_step)
+{
+ for (int i = 0; i < hair_export->totcurves; i++) {
+ const HairFiberCurve *curve = &hair_export->fiber_curves[i];
+ const HairFiberVertex *verts = &hair_export->fiber_verts[curve->vertstart];
+ if (curve->numverts < 2) {
+ continue;
+ }
+ float total_len = 0.0f;
+ const float *co_prev = NULL;
+ float *seg_data_first;
+ for (int j = 0; j < curve->numverts; j++) {
+ float *seg_data = (float *)GPU_vertbuf_raw_step(attr_step);
+ copy_v3_v3(seg_data, verts[j].co);
+ if (co_prev) {
+ total_len += len_v3v3(co_prev, verts[j].co);
+ }
+ else {
+ seg_data_first = seg_data;
+ }
+ seg_data[3] = total_len;
+ co_prev = verts[j].co;
+ }
+ if (total_len > 0.0f) {
+ /* Divide by total length to have a [0-1] number. */
+ for (int j = 0; j < curve->numverts; j++, seg_data_first += 4) {
+ seg_data_first[3] /= total_len;
+ }
+ }
+ }
+}
+
+static void hair_batch_cache_ensure_procedural_pos(
+ const HairExportCache *hair_export,
+ ParticleHairCache *cache)
+{
+ if (cache->proc_point_buf != NULL) {
+ return;
+ }
+
+ /* initialize vertex format */
+ GPUVertFormat format = {0};
+ uint pos_id = GPU_vertformat_attr_add(&format, "posTime", GPU_COMP_F32, 4, GPU_FETCH_FLOAT);
+
+ cache->proc_point_buf = GPU_vertbuf_create_with_format(&format);
+ GPU_vertbuf_data_alloc(cache->proc_point_buf, cache->point_len);
+
+ GPUVertBufRaw pos_step;
+ GPU_vertbuf_attr_get_raw_data(cache->proc_point_buf, pos_id, &pos_step);
+
+ hair_batch_cache_fill_segments_proc_pos(hair_export, &pos_step);
+
+ /* Create vbo immediatly to bind to texture buffer. */
+ GPU_vertbuf_use(cache->proc_point_buf);
+
+ cache->point_tex = GPU_texture_create_from_vertbuf(cache->proc_point_buf);
+}
+
+static void hair_pack_mcol(MCol *mcol, unsigned short r_scol[3])
+{
+ /* Convert to linear ushort and swizzle */
+ r_scol[0] = unit_float_to_ushort_clamp(BLI_color_from_srgb_table[mcol->b]);
+ r_scol[1] = unit_float_to_ushort_clamp(BLI_color_from_srgb_table[mcol->g]);
+ r_scol[2] = unit_float_to_ushort_clamp(BLI_color_from_srgb_table[mcol->r]);
+}
+
+static int hair_batch_cache_fill_strands_data(
+ const HairExportCache *hair_export,
+ GPUVertBufRaw *data_step,
+ GPUVertBufRaw *uv_step, int num_uv_layers,
+ GPUVertBufRaw *col_step, int num_col_layers)
+{
+ int curr_point = 0;
+ for (int i = 0; i < hair_export->totcurves; i++) {
+ const HairFiberCurve *curve = &hair_export->fiber_curves[i];
+ if (curve->numverts < 2) {
+ continue;
+ }
+
+ uint *seg_data = (uint *)GPU_vertbuf_raw_step(data_step);
+ const uint numseg = curve->numverts - 1;
+ *seg_data = (curr_point & 0xFFFFFF) | (numseg << 24);
+ curr_point += curve->numverts;
+
+ float (*uv)[2] = NULL;
+ MCol *mcol = NULL;
+
+#if 0
+ particle_calculate_uvs(
+ psys, psmd,
+ is_simple, num_uv_layers,
+ is_child ? psys->child[i].parent : i,
+ is_child ? i : -1,
+ mtfaces,
+ *r_parent_uvs, &uv);
+
+ particle_calculate_mcol(
+ psys, psmd,
+ is_simple, num_col_layers,
+ is_child ? psys->child[i].parent : i,
+ is_child ? i : -1,
+ mcols,
+ *r_parent_mcol, &mcol);
+#else
+ /* XXX dummy uvs and mcols, TODO */
+ uv = MEM_mallocN(sizeof(*uv) * num_uv_layers, __func__);
+ mcol = MEM_mallocN(sizeof(*mcol) * num_col_layers, __func__);
+ for (int k = 0; k < num_uv_layers; k++) {
+ zero_v3(uv[k]);
+ }
+ for (int k = 0; k < num_col_layers; k++) {
+ mcol[k].a = 255;
+ mcol[k].r = 255;
+ mcol[k].g = 0;
+ mcol[k].b = 255;
+ }
+#endif
+
+ for (int k = 0; k < num_uv_layers; k++) {
+ float *t_uv = (float *)GPU_vertbuf_raw_step(uv_step + k);
+ copy_v2_v2(t_uv, uv[k]);
+ }
+ for (int k = 0; k < num_col_layers; k++) {
+ unsigned short *scol = (unsigned short *)GPU_vertbuf_raw_step(col_step + k);
+ hair_pack_mcol(&mcol[k], scol);
+ }
+
+ if (uv) {
+ MEM_freeN(uv);
+ }
+ if (mcol) {
+ MEM_freeN(mcol);
+ }
+ }
+ return curr_point;
+}
+
+static void hair_batch_cache_ensure_procedural_strand_data(
+ const HairExportCache *hair_export,
+ ParticleHairCache *cache)
+{
+ int active_uv = 0;
+ int active_col = 0;
+
+#if 0 // TODO
+ ParticleSystemModifierData *psmd = (ParticleSystemModifierData *)md;
+
+ if (psmd != NULL && psmd->mesh_final != NULL) {
+ if (CustomData_has_layer(&psmd->mesh_final->ldata, CD_MLOOPUV)) {
+ cache->num_uv_layers = CustomData_number_of_layers(&psmd->mesh_final->ldata, CD_MLOOPUV);
+ active_uv = CustomData_get_active_layer(&psmd->mesh_final->ldata, CD_MLOOPUV);
+ }
+ if (CustomData_has_layer(&psmd->mesh_final->ldata, CD_MLOOPCOL)) {
+ cache->num_col_layers = CustomData_number_of_layers(&psmd->mesh_final->ldata, CD_MLOOPCOL);
+ active_col = CustomData_get_active_layer(&psmd->mesh_final->ldata, CD_MLOOPCOL);
+ }
+ }
+#endif
+
+ GPUVertBufRaw data_step;
+ GPUVertBufRaw uv_step[MAX_MTFACE];
+ GPUVertBufRaw col_step[MAX_MCOL];
+
+ MTFace *mtfaces[MAX_MTFACE] = {NULL};
+ MCol *mcols[MAX_MCOL] = {NULL};
+
+ GPUVertFormat format_data = {0};
+ uint data_id = GPU_vertformat_attr_add(&format_data, "data", GPU_COMP_U32, 1, GPU_FETCH_INT);
+
+ GPUVertFormat format_uv = {0};
+ uint uv_id = GPU_vertformat_attr_add(&format_uv, "uv", GPU_COMP_F32, 2, GPU_FETCH_FLOAT);
+
+ GPUVertFormat format_col = {0};
+ uint col_id = GPU_vertformat_attr_add(&format_col, "col", GPU_COMP_U16, 4, GPU_FETCH_INT_TO_FLOAT_UNIT);
+
+ memset(cache->uv_layer_names, 0, sizeof(cache->uv_layer_names));
+ memset(cache->col_layer_names, 0, sizeof(cache->col_layer_names));
+
+ /* Strand Data */
+ cache->proc_strand_buf = GPU_vertbuf_create_with_format(&format_data);
+ GPU_vertbuf_data_alloc(cache->proc_strand_buf, cache->strands_len);
+ GPU_vertbuf_attr_get_raw_data(cache->proc_strand_buf, data_id, &data_step);
+
+#if 0 // TODO
+ /* UV layers */
+ for (int i = 0; i < cache->num_uv_layers; i++) {
+ cache->proc_uv_buf[i] = GPU_vertbuf_create_with_format(&format_uv);
+ GPU_vertbuf_data_alloc(cache->proc_uv_buf[i], cache->strands_len);
+ GPU_vertbuf_attr_get_raw_data(cache->proc_uv_buf[i], uv_id, &uv_step[i]);
+
+ const char *name = CustomData_get_layer_name(&psmd->mesh_final->ldata, CD_MLOOPUV, i);
+ uint hash = BLI_ghashutil_strhash_p(name);
+ int n = 0;
+ BLI_snprintf(cache->uv_layer_names[i][n++], MAX_LAYER_NAME_LEN, "u%u", hash);
+ BLI_snprintf(cache->uv_layer_names[i][n++], MAX_LAYER_NAME_LEN, "a%u", hash);
+
+ if (i == active_uv) {
+ BLI_snprintf(cache->uv_layer_names[i][n], MAX_LAYER_NAME_LEN, "u");
+ }
+ }
+ /* Vertex colors */
+ for (int i = 0; i < cache->num_col_layers; i++) {
+ cache->proc_col_buf[i] = GPU_vertbuf_create_with_format(&format_col);
+ GPU_vertbuf_data_alloc(cache->proc_col_buf[i], cache->strands_len);
+ GPU_vertbuf_attr_get_raw_data(cache->proc_col_buf[i], col_id, &col_step[i]);
+
+ const char *name = CustomData_get_layer_name(&psmd->mesh_final->ldata, CD_MLOOPCOL, i);
+ uint hash = BLI_ghashutil_strhash_p(name);
+ int n = 0;
+ BLI_snprintf(cache->col_layer_names[i][n++], MAX_LAYER_NAME_LEN, "c%u", hash);
+
+ /* We only do vcols auto name that are not overridden by uvs */
+ if (CustomData_get_named_layer_index(&psmd->mesh_final->ldata, CD_MLOOPUV, name) == -1) {
+ BLI_snprintf(cache->col_layer_names[i][n++], MAX_LAYER_NAME_LEN, "a%u", hash);
+ }
+
+ if (i == active_col) {
+ BLI_snprintf(cache->col_layer_names[i][n], MAX_LAYER_NAME_LEN, "c");
+ }
+ }
+
+ if (cache->num_uv_layers || cache->num_col_layers) {
+ BKE_mesh_tessface_ensure(psmd->mesh_final);
+ if (cache->num_uv_layers) {
+ for (int j = 0; j < cache->num_uv_layers; j++) {
+ mtfaces[j] = (MTFace *)CustomData_get_layer_n(&psmd->mesh_final->fdata, CD_MTFACE, j);
+ }
+ }
+ if (cache->num_col_layers) {
+ for (int j = 0; j < cache->num_col_layers; j++) {
+ mcols[j] = (MCol *)CustomData_get_layer_n(&psmd->mesh_final->fdata, CD_MCOL, j);
+ }
+ }
+ }
+#endif
+
+ hair_batch_cache_fill_strands_data(
+ hair_export,
+ &data_step,
+ uv_step, cache->num_uv_layers,
+ col_step, cache->num_col_layers);
+
+ /* Create vbo immediatly to bind to texture buffer. */
+ GPU_vertbuf_use(cache->proc_strand_buf);
+ cache->strand_tex = GPU_texture_create_from_vertbuf(cache->proc_strand_buf);
+
+ for (int i = 0; i < cache->num_uv_layers; i++) {
+ GPU_vertbuf_use(cache->proc_uv_buf[i]);
+ cache->uv_tex[i] = GPU_texture_create_from_vertbuf(cache->proc_uv_buf[i]);
+ }
+ for (int i = 0; i < cache->num_col_layers; i++) {
+ GPU_vertbuf_use(cache->proc_col_buf[i]);
+ cache->col_tex[i] = GPU_texture_create_from_vertbuf(cache->proc_col_buf[i]);
+ }
+}
+
+static void hair_batch_cache_ensure_final_count(
+ const HairExportCache *hair_export,
+ ParticleHairFinalCache *cache,
+ int subdiv,
+ int thickness_res)
+{
+ const int totverts = hair_export->totverts;
+ const int totcurves = hair_export->totcurves;
+ cache->strands_len = hair_export->totfollicles;
+ /* +1 for primitive restart */
+ cache->elems_len = (((totverts - totcurves) << subdiv) + totcurves) * thickness_res;
+ cache->point_len = ((totverts - totcurves) << subdiv) + totcurves;
+}
+
+static void hair_batch_cache_ensure_procedural_final_points(
+ ParticleHairCache *cache,
+ int subdiv)
+{
+ /* Same format as point_tex. */
+ GPUVertFormat format = { 0 };
+ GPU_vertformat_attr_add(&format, "pos", GPU_COMP_F32, 4, GPU_FETCH_FLOAT);
+
+ cache->final[subdiv].proc_point_buf = GPU_vertbuf_create_with_format(&format);
+
+ /* Create a destination buffer for the tranform feedback. Sized appropriately */
+ /* Thoses are points! not line segments. */
+ GPU_vertbuf_data_alloc(cache->final[subdiv].proc_point_buf, cache->final[subdiv].point_len);
+
+ /* Create vbo immediatly to bind to texture buffer. */
+ GPU_vertbuf_use(cache->final[subdiv].proc_point_buf);
+
+ cache->final[subdiv].proc_tex = GPU_texture_create_from_vertbuf(cache->final[subdiv].proc_point_buf);
+}
+
+static int hair_batch_cache_fill_segments_indices(
+ const HairExportCache *hair_export,
+ const int subdiv,
+ const int thickness_res,
+ GPUIndexBufBuilder *elb)
+{
+ int curr_point = 0;
+ for (int i = 0; i < hair_export->totcurves; i++) {
+ const HairFiberCurve *curve = &hair_export->fiber_curves[i];
+ if (curve->numverts < 2) {
+ continue;
+ }
+
+ const int res = (((curve->numverts - 1) << subdiv) + 1) * thickness_res;
+ for (int k = 0; k < res; k++) {
+ GPU_indexbuf_add_generic_vert(elb, curr_point++);
+ }
+ GPU_indexbuf_add_primitive_restart(elb);
+ }
+ return curr_point;
+}
+
+static void hair_batch_cache_ensure_procedural_indices(
+ const HairExportCache *hair_export,
+ ParticleHairCache *cache,
+ int thickness_res,
+ int subdiv)
+{
+ BLI_assert(thickness_res <= MAX_THICKRES); /* Cylinder strip not currently supported. */
+
+ if (cache->final[subdiv].proc_hairs[thickness_res - 1] != NULL) {
+ return;
+ }
+
+ int element_count = cache->final[subdiv].elems_len;
+ GPUPrimType prim_type = (thickness_res == 1) ? GPU_PRIM_LINE_STRIP : GPU_PRIM_TRI_STRIP;
+
+ static GPUVertFormat format = { 0 };
+ GPU_vertformat_clear(&format);
+
+ /* initialize vertex format */
+ GPU_vertformat_attr_add(&format, "dummy", GPU_COMP_U8, 1, GPU_FETCH_INT_TO_FLOAT_UNIT);
+
+ GPUVertBuf *vbo = GPU_vertbuf_create_with_format(&format);
+ GPU_vertbuf_data_alloc(vbo, 1);
+
+ GPUIndexBufBuilder elb;
+ GPU_indexbuf_init_ex(&elb, prim_type, element_count, element_count, true);
+
+ hair_batch_cache_fill_segments_indices(hair_export, subdiv, thickness_res, &elb);
+
+ cache->final[subdiv].proc_hairs[thickness_res - 1] = GPU_batch_create_ex(
+ prim_type,
+ vbo,
+ GPU_indexbuf_build(&elb),
+ GPU_BATCH_OWNS_VBO | GPU_BATCH_OWNS_INDEX);
+}
+
+/* Ensure all textures and buffers needed for GPU accelerated drawing. */
+bool hair_ensure_procedural_data(
+ Object *UNUSED(object),
+ HairSystem *hsys,
+ struct Mesh *scalp,
+ ParticleHairCache **r_hair_cache,
+ int subdiv,
+ int thickness_res)
+{
+ bool need_ft_update = false;
+
+ HairExportCache *hair_export = BKE_hair_export_cache_new();
+ BKE_hair_export_cache_update(hair_export, hsys, subdiv, scalp, HAIR_EXPORT_ALL);
+
+ HairBatchCache *cache = hair_batch_cache_get(hsys);
+ *r_hair_cache = &cache->hair;
+
+ /* Refreshed on combing and simulation. */
+ if (cache->hair.proc_point_buf == NULL) {
+ hair_batch_cache_ensure_count(hair_export, &cache->hair);
+
+ hair_batch_cache_ensure_procedural_pos(hair_export, &cache->hair);
+ need_ft_update = true;
+ }
+
+ /* Refreshed if active layer or custom data changes. */
+ if (cache->hair.strand_tex == NULL) {
+ hair_batch_cache_ensure_procedural_strand_data(hair_export, &cache->hair);
+ }
+
+ /* Refreshed only on subdiv count change. */
+ if (cache->hair.final[subdiv].proc_point_buf == NULL) {
+ hair_batch_cache_ensure_final_count(hair_export, &cache->hair.final[subdiv], subdiv, thickness_res);
+
+ hair_batch_cache_ensure_procedural_final_points(&cache->hair, subdiv);
+ need_ft_update = true;
+ }
+ if (cache->hair.final[subdiv].proc_hairs[thickness_res - 1] == NULL) {
+ hair_batch_cache_ensure_procedural_indices(hair_export, &cache->hair, thickness_res, subdiv);
+ }
+
+ BKE_hair_export_cache_free(hair_export);
+
+ return need_ft_update;
+}
+
+GPUBatch *DRW_hair_batch_cache_get_fibers(HairSystem *hsys, const HairExportCache *hair_export)
+{
+ // TODO
+ UNUSED_VARS(hsys, hair_export);
+ return NULL;
+}
+
+GPUBatch *DRW_hair_batch_cache_get_follicle_points(HairSystem *hsys, const HairExportCache *hair_export)
+{
+ // TODO
+ UNUSED_VARS(hsys, hair_export);
+ return NULL;
+}
diff --git a/source/blender/draw/intern/draw_cache_impl_particles.c b/source/blender/draw/intern/draw_cache_impl_particles.c
index b56396261d3..d3dacc8e537 100644
--- a/source/blender/draw/intern/draw_cache_impl_particles.c
+++ b/source/blender/draw/intern/draw_cache_impl_particles.c
@@ -162,37 +162,6 @@ static void particle_batch_cache_clear_point(ParticlePointCache *point_cache)
GPU_VERTBUF_DISCARD_SAFE(point_cache->pos);
}
-static void particle_batch_cache_clear_hair(ParticleHairCache *hair_cache)
-{
- /* TODO more granular update tagging. */
- GPU_VERTBUF_DISCARD_SAFE(hair_cache->proc_point_buf);
- DRW_TEXTURE_FREE_SAFE(hair_cache->point_tex);
-
- GPU_VERTBUF_DISCARD_SAFE(hair_cache->proc_strand_buf);
- DRW_TEXTURE_FREE_SAFE(hair_cache->strand_tex);
-
- for (int i = 0; i < MAX_MTFACE; ++i) {
- GPU_VERTBUF_DISCARD_SAFE(hair_cache->proc_uv_buf[i]);
- DRW_TEXTURE_FREE_SAFE(hair_cache->uv_tex[i]);
- }
- for (int i = 0; i < MAX_MCOL; ++i) {
- GPU_VERTBUF_DISCARD_SAFE(hair_cache->proc_col_buf[i]);
- DRW_TEXTURE_FREE_SAFE(hair_cache->col_tex[i]);
- }
- for (int i = 0; i < MAX_HAIR_SUBDIV; ++i) {
- GPU_VERTBUF_DISCARD_SAFE(hair_cache->final[i].proc_buf);
- DRW_TEXTURE_FREE_SAFE(hair_cache->final[i].proc_tex);
- for (int j = 0; j < MAX_THICKRES; ++j) {
- GPU_BATCH_DISCARD_SAFE(hair_cache->final[i].proc_hairs[j]);
- }
- }
-
- /* "Normal" legacy hairs */
- GPU_BATCH_DISCARD_SAFE(hair_cache->hairs);
- GPU_VERTBUF_DISCARD_SAFE(hair_cache->pos);
- GPU_INDEXBUF_DISCARD_SAFE(hair_cache->indices);
-}
-
static void particle_batch_cache_clear(ParticleSystem *psys)
{
ParticleBatchCache *cache = psys->batch_cache;
@@ -654,19 +623,26 @@ static void particle_batch_cache_fill_segments_proc_pos(
}
static int particle_batch_cache_fill_segments_indices(
+ const ParticleSystem *psys,
ParticleCacheKey **path_cache,
const int start_index,
const int num_path_keys,
- const int res,
+ const int subdiv,
+ const int thickness_res,
GPUIndexBufBuilder *elb)
{
+ const ParticleSettings *part = psys->part;
+ const int points_per_curve = (1 << (part->draw_step + subdiv)) + 1;
+ const int points_per_hair = points_per_curve * thickness_res;
+
int curr_point = start_index;
for (int i = 0; i < num_path_keys; i++) {
ParticleCacheKey *path = path_cache[i];
if (path->segments <= 0) {
continue;
}
- for (int k = 0; k < res; k++) {
+
+ for (int k = 0; k < points_per_hair; k++) {
GPU_indexbuf_add_generic_vert(elb, curr_point++);
}
GPU_indexbuf_add_primitive_restart(elb);
@@ -749,24 +725,93 @@ static int particle_batch_cache_fill_strands_data(
return curr_point;
}
+static void ensure_seg_pt_final_count(
+ const ParticleSystem *psys,
+ ParticleHairCache *hair_cache,
+ int subdiv,
+ int thickness_res)
+{
+ ParticleHairFinalCache *final_cache = &hair_cache->final[subdiv];
+
+ const ParticleSettings *part = psys->part;
+ const int points_per_curve = (1 << (part->draw_step + subdiv)) + 1;
+
+ final_cache->strands_len = hair_cache->strands_len;
+ final_cache->point_len = points_per_curve * final_cache->strands_len;
+
+ /* +1 for primitive restart */
+ final_cache->elems_len = (points_per_curve * thickness_res + 1) * final_cache->strands_len;
+}
+
+#define USE_POSITION_HAIR_INDEX
+
static void particle_batch_cache_ensure_procedural_final_points(
+ const ParticleSystem *psys,
ParticleHairCache *cache,
int subdiv)
{
+
/* Same format as point_tex. */
- GPUVertFormat format = { 0 };
+#ifdef USE_POSITION_HAIR_INDEX
+ static GPUVertFormat format = { 0 };
+ GPU_vertformat_clear(&format);
+ uint pos_id = GPU_vertformat_attr_add(&format, "pos", GPU_COMP_F32, 4, GPU_FETCH_FLOAT);
+#else
+ static GPUVer format = { 0 };
GPU_vertformat_attr_add(&format, "pos", GPU_COMP_F32, 4, GPU_FETCH_FLOAT);
+#endif
- cache->final[subdiv].proc_buf = GPU_vertbuf_create_with_format(&format);
+ cache->final[subdiv].proc_point_buf = GPU_vertbuf_create_with_format(&format);
/* Create a destination buffer for the tranform feedback. Sized appropriately */
/* Thoses are points! not line segments. */
- GPU_vertbuf_data_alloc(cache->final[subdiv].proc_buf, cache->final[subdiv].strands_res * cache->strands_len);
+ GPU_vertbuf_data_alloc(cache->final[subdiv].proc_point_buf, cache->final[subdiv].point_len);
+
+#ifdef USE_POSITION_HAIR_INDEX
+ GPUVertBufRaw data_step;
+ GPU_vertbuf_attr_get_raw_data(cache->final[subdiv].proc_point_buf, pos_id, &data_step);
+ const int points_per_curve = (1 << (psys->part->draw_step + subdiv)) + 1;
+ for (int i = 0; i < cache->final[subdiv].strands_len; i++) {
+ for (int j = 0; j < points_per_curve; ++j) {
+ uint *data = (uint *)GPU_vertbuf_raw_step(&data_step);
+ *data = (uint)i;
+ }
+ }
+#endif
+
+ /* Create vbo immediatly to bind to texture buffer. */
+ GPU_vertbuf_use(cache->final[subdiv].proc_point_buf);
+
+ cache->final[subdiv].proc_tex = GPU_texture_create_from_vertbuf(cache->final[subdiv].proc_point_buf);
+}
+
+static void particle_batch_cache_ensure_procedural_final_hair_index(
+ const ParticleSystem *psys,
+ ParticleHairCache *cache,
+ int subdiv)
+{
+ /* Same format as point_tex. */
+ GPUVertFormat format = { 0 };
+ uint hair_index_id = GPU_vertformat_attr_add(&format, "hair_index", GPU_COMP_U32, 1, GPU_FETCH_INT);
+
+ cache->final[subdiv].proc_hair_index_buf = GPU_vertbuf_create_with_format(&format);
+
+ GPU_vertbuf_data_alloc(cache->final[subdiv].proc_hair_index_buf, cache->final[subdiv].point_len);
+
+ GPUVertBufRaw data_step;
+ GPU_vertbuf_attr_get_raw_data(cache->final[subdiv].proc_hair_index_buf, hair_index_id, &data_step);
+ const int points_per_curve = (1 << (psys->part->draw_step + subdiv)) + 1;
+ for (int i = 0; i < cache->final[subdiv].strands_len; i++) {
+ for (int j = 0; j < points_per_curve; ++j) {
+ uint *data = (uint *)GPU_vertbuf_raw_step(&data_step);
+ *data = (uint)i;
+ }
+ }
/* Create vbo immediatly to bind to texture buffer. */
- GPU_vertbuf_use(cache->final[subdiv].proc_buf);
+ GPU_vertbuf_use(cache->final[subdiv].proc_hair_index_buf);
- cache->final[subdiv].proc_tex = GPU_texture_create_from_vertbuf(cache->final[subdiv].proc_buf);
+ cache->final[subdiv].hair_index_tex = GPU_texture_create_from_vertbuf(cache->final[subdiv].proc_hair_index_buf);
}
static void particle_batch_cache_ensure_procedural_strand_data(
@@ -940,9 +985,7 @@ static void particle_batch_cache_ensure_procedural_indices(
return;
}
- int verts_per_hair = cache->final[subdiv].strands_res * thickness_res;
- /* +1 for primitive restart */
- int element_count = (verts_per_hair + 1) * cache->strands_len;
+ int element_count = cache->final[subdiv].elems_len;
GPUPrimType prim_type = (thickness_res == 1) ? GPU_PRIM_LINE_STRIP : GPU_PRIM_TRI_STRIP;
static GPUVertFormat format = { 0 };
@@ -959,7 +1002,7 @@ static void particle_batch_cache_ensure_procedural_indices(
if (edit != NULL && edit->pathcache != NULL) {
particle_batch_cache_fill_segments_indices(
- edit->pathcache, 0, edit->totcached, verts_per_hair, &elb);
+ psys, edit->pathcache, 0, edit->totcached, subdiv, thickness_res, &elb);
}
else {
int curr_point = 0;
@@ -967,12 +1010,12 @@ static void particle_batch_cache_ensure_procedural_indices(
(!psys->childcache || (psys->part->draw & PART_DRAW_PARENT)))
{
curr_point = particle_batch_cache_fill_segments_indices(
- psys->pathcache, 0, psys->totpart, verts_per_hair, &elb);
+ psys, psys->pathcache, 0, psys->totpart, subdiv, thickness_res, &elb);
}
if (psys->childcache) {
const int child_count = psys->totchild * psys->part->disp / 100;
curr_point = particle_batch_cache_fill_segments_indices(
- psys->childcache, curr_point, child_count, verts_per_hair, &elb);
+ psys, psys->childcache, curr_point, child_count, subdiv, thickness_res, &elb);
}
}
@@ -1555,12 +1598,9 @@ bool particles_ensure_procedural_data(
ParticleDrawSource source;
drw_particle_get_hair_source(object, psys, md, NULL, &source);
- ParticleSettings *part = source.psys->part;
ParticleBatchCache *cache = particle_batch_cache_get(source.psys);
*r_hair_cache = &cache->hair;
- (*r_hair_cache)->final[subdiv].strands_res = 1 << (part->draw_step + subdiv);
-
/* Refreshed on combing and simulation. */
if ((*r_hair_cache)->proc_point_buf == NULL) {
ensure_seg_pt_count(source.edit, source.psys, &cache->hair);
@@ -1574,8 +1614,10 @@ bool particles_ensure_procedural_data(
}
/* Refreshed only on subdiv count change. */
- if ((*r_hair_cache)->final[subdiv].proc_buf == NULL) {
- particle_batch_cache_ensure_procedural_final_points(&cache->hair, subdiv);
+ if ((*r_hair_cache)->final[subdiv].proc_point_buf == NULL) {
+ ensure_seg_pt_final_count(psys, &cache->hair, subdiv, thickness_res);
+ particle_batch_cache_ensure_procedural_final_points(psys, &cache->hair, subdiv);
+ particle_batch_cache_ensure_procedural_final_hair_index(psys, &cache->hair, subdiv);
need_ft_update = true;
}
if ((*r_hair_cache)->final[subdiv].proc_hairs[thickness_res - 1] == NULL) {
diff --git a/source/blender/draw/intern/draw_common.h b/source/blender/draw/intern/draw_common.h
index 0ad1402f29e..54b8655b1df 100644
--- a/source/blender/draw/intern/draw_common.h
+++ b/source/blender/draw/intern/draw_common.h
@@ -26,11 +26,17 @@
#ifndef __DRAW_COMMON_H__
#define __DRAW_COMMON_H__
+struct Mesh;
struct DRWPass;
struct DRWShadingGroup;
struct GPUBatch;
struct GPUMaterial;
+struct GPUShader;
+struct GPUTexture;
+struct HairDrawSettings;
+struct HairSystem;
struct Object;
+struct Scene;
struct ViewLayer;
struct ModifierData;
struct ParticleSystem;
@@ -162,16 +168,28 @@ void DRW_shgroup_armature_edit(struct Object *ob, struct DRWArmaturePasses passe
/* This creates a shading group with display hairs.
* The draw call is already added by this function, just add additional uniforms. */
-struct DRWShadingGroup *DRW_shgroup_hair_create(
+struct DRWShadingGroup *DRW_shgroup_particle_hair_create(
struct Object *object, struct ParticleSystem *psys, struct ModifierData *md,
struct DRWPass *hair_pass,
struct GPUShader *shader);
-struct DRWShadingGroup *DRW_shgroup_material_hair_create(
+struct DRWShadingGroup *DRW_shgroup_material_particle_hair_create(
struct Object *object, struct ParticleSystem *psys, struct ModifierData *md,
struct DRWPass *hair_pass,
struct GPUMaterial *material);
+struct DRWShadingGroup *DRW_shgroup_hair_create(
+ struct Object *object, struct HairSystem *hsys,
+ struct Mesh *scalp, const struct HairDrawSettings *draw_set,
+ struct DRWPass *hair_pass,
+ struct GPUShader *shader);
+
+struct DRWShadingGroup *DRW_shgroup_material_hair_create(
+ struct Object *object, struct HairSystem *hsys,
+ struct Mesh *scalp, const struct HairDrawSettings *draw_set,
+ struct DRWPass *hair_pass,
+ struct GPUMaterial *material);
+
void DRW_hair_init(void);
void DRW_hair_update(void);
void DRW_hair_free(void);
diff --git a/source/blender/draw/intern/draw_hair.c b/source/blender/draw/intern/draw_hair.c
index 6ede573199f..e2680a1821e 100644
--- a/source/blender/draw/intern/draw_hair.c
+++ b/source/blender/draw/intern/draw_hair.c
@@ -34,12 +34,14 @@
#include "BLI_utildefines.h"
#include "BLI_string_utils.h"
+#include "DNA_hair_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_modifier_types.h"
#include "DNA_particle_types.h"
#include "DNA_customdata_types.h"
+#include "BKE_hair.h"
#include "BKE_mesh.h"
#include "BKE_particle.h"
#include "BKE_pointcache.h"
@@ -85,7 +87,38 @@ void DRW_hair_init(void)
g_tf_pass = DRW_pass_create("Update Hair Pass", DRW_STATE_TRANS_FEEDBACK);
}
-static DRWShadingGroup *drw_shgroup_create_hair_procedural_ex(
+void particle_batch_cache_clear_hair(ParticleHairCache *hair_cache)
+{
+ /* TODO more granular update tagging. */
+ GPU_VERTBUF_DISCARD_SAFE(hair_cache->proc_point_buf);
+ DRW_TEXTURE_FREE_SAFE(hair_cache->point_tex);
+
+ GPU_VERTBUF_DISCARD_SAFE(hair_cache->proc_strand_buf);
+ DRW_TEXTURE_FREE_SAFE(hair_cache->strand_tex);
+
+ for (int i = 0; i < MAX_MTFACE; ++i) {
+ GPU_VERTBUF_DISCARD_SAFE(hair_cache->proc_uv_buf[i]);
+ DRW_TEXTURE_FREE_SAFE(hair_cache->uv_tex[i]);
+ }
+ for (int i = 0; i < MAX_MCOL; ++i) {
+ GPU_VERTBUF_DISCARD_SAFE(hair_cache->proc_col_buf[i]);
+ DRW_TEXTURE_FREE_SAFE(hair_cache->col_tex[i]);
+ }
+ for (int i = 0; i < MAX_HAIR_SUBDIV; ++i) {
+ GPU_VERTBUF_DISCARD_SAFE(hair_cache->final[i].proc_point_buf);
+ DRW_TEXTURE_FREE_SAFE(hair_cache->final[i].proc_tex);
+ for (int j = 0; j < MAX_THICKRES; ++j) {
+ GPU_BATCH_DISCARD_SAFE(hair_cache->final[i].proc_hairs[j]);
+ }
+ }
+
+ /* "Normal" legacy hairs */
+ GPU_BATCH_DISCARD_SAFE(hair_cache->hairs);
+ GPU_VERTBUF_DISCARD_SAFE(hair_cache->pos);
+ GPU_INDEXBUF_DISCARD_SAFE(hair_cache->indices);
+}
+
+static DRWShadingGroup *drw_shgroup_create_particle_hair_procedural_ex(
Object *object, ParticleSystem *psys, ModifierData *md,
DRWPass *hair_pass,
struct GPUMaterial *gpu_mat, GPUShader *gpu_shader)
@@ -126,7 +159,7 @@ static DRWShadingGroup *drw_shgroup_create_hair_procedural_ex(
}
DRW_shgroup_uniform_texture(shgrp, "hairPointBuffer", hair_cache->final[subdiv].proc_tex);
- DRW_shgroup_uniform_int(shgrp, "hairStrandsRes", &hair_cache->final[subdiv].strands_res, 1);
+ DRW_shgroup_uniform_texture(shgrp, "hairIndexBuffer", hair_cache->final[subdiv].hair_index_tex);
DRW_shgroup_uniform_int_copy(shgrp, "hairThicknessRes", thickness_res);
DRW_shgroup_uniform_float(shgrp, "hairRadShape", &part->shape, 1);
DRW_shgroup_uniform_float_copy(shgrp, "hairRadRoot", part->rad_root * part->rad_scale * 0.5f);
@@ -137,33 +170,110 @@ static DRWShadingGroup *drw_shgroup_create_hair_procedural_ex(
/* Transform Feedback subdiv. */
if (need_ft_update) {
- int final_points_len = hair_cache->final[subdiv].strands_res * hair_cache->strands_len;
GPUShader *tf_shader = hair_refine_shader_get(PART_REFINE_CATMULL_ROM);
DRWShadingGroup *tf_shgrp = DRW_shgroup_transform_feedback_create(tf_shader, g_tf_pass,
- hair_cache->final[subdiv].proc_buf);
+ hair_cache->final[subdiv].proc_point_buf);
DRW_shgroup_uniform_texture(tf_shgrp, "hairPointBuffer", hair_cache->point_tex);
DRW_shgroup_uniform_texture(tf_shgrp, "hairStrandBuffer", hair_cache->strand_tex);
- DRW_shgroup_uniform_int(tf_shgrp, "hairStrandsRes", &hair_cache->final[subdiv].strands_res, 1);
- DRW_shgroup_call_procedural_points_add(tf_shgrp, final_points_len, NULL);
+ DRW_shgroup_call_procedural_points_add(tf_shgrp, hair_cache->final[subdiv].point_len, NULL);
}
return shgrp;
}
-DRWShadingGroup *DRW_shgroup_hair_create(
+DRWShadingGroup *DRW_shgroup_particle_hair_create(
Object *object, ParticleSystem *psys, ModifierData *md,
DRWPass *hair_pass,
GPUShader *shader)
{
- return drw_shgroup_create_hair_procedural_ex(object, psys, md, hair_pass, NULL, shader);
+ return drw_shgroup_create_particle_hair_procedural_ex(object, psys, md, hair_pass, NULL, shader);
}
-DRWShadingGroup *DRW_shgroup_material_hair_create(
+DRWShadingGroup *DRW_shgroup_material_particle_hair_create(
Object *object, ParticleSystem *psys, ModifierData *md,
DRWPass *hair_pass,
struct GPUMaterial *material)
{
- return drw_shgroup_create_hair_procedural_ex(object, psys, md, hair_pass, material, NULL);
+ return drw_shgroup_create_particle_hair_procedural_ex(object, psys, md, hair_pass, material, NULL);
+}
+
+static DRWShadingGroup *drw_shgroup_create_hair_procedural_ex(
+ Object *object, HairSystem *hsys, Mesh *scalp, const HairDrawSettings *draw_set,
+ DRWPass *hair_pass,
+ struct GPUMaterial *gpu_mat, GPUShader *gpu_shader)
+{
+ /* TODO(fclem): Pass the scene as parameter */
+ const DRWContextState *draw_ctx = DRW_context_state_get();
+ Scene *scene = draw_ctx->scene;
+
+ int subdiv = scene->r.hair_subdiv;
+ int thickness_res = (scene->r.hair_type == SCE_HAIR_SHAPE_STRAND) ? 1 : 2;
+
+ ParticleHairCache *hair_cache;
+ bool need_ft_update = hair_ensure_procedural_data(object, hsys, scalp, &hair_cache, subdiv, thickness_res);
+
+ DRWShadingGroup *shgrp;
+ if (gpu_mat) {
+ shgrp = DRW_shgroup_material_create(gpu_mat, hair_pass);
+ }
+ else if (gpu_shader) {
+ shgrp = DRW_shgroup_create(gpu_shader, hair_pass);
+ }
+ else {
+ shgrp = NULL;
+ BLI_assert(0);
+ }
+
+ /* TODO optimize this. Only bind the ones GPUMaterial needs. */
+ for (int i = 0; i < hair_cache->num_uv_layers; ++i) {
+ for (int n = 0; hair_cache->uv_layer_names[i][n][0] != '\0'; ++n) {
+ DRW_shgroup_uniform_texture(shgrp, hair_cache->uv_layer_names[i][n], hair_cache->uv_tex[i]);
+ }
+ }
+ for (int i = 0; i < hair_cache->num_col_layers; ++i) {
+ for (int n = 0; hair_cache->col_layer_names[i][n][0] != '\0'; ++n) {
+ DRW_shgroup_uniform_texture(shgrp, hair_cache->col_layer_names[i][n], hair_cache->col_tex[i]);
+ }
+ }
+
+ DRW_shgroup_uniform_texture(shgrp, "hairPointBuffer", hair_cache->final[subdiv].proc_tex);
+ DRW_shgroup_uniform_int_copy(shgrp, "hairThicknessRes", thickness_res);
+ DRW_shgroup_uniform_float(shgrp, "hairRadShape", &draw_set->shape, 1);
+ DRW_shgroup_uniform_float_copy(shgrp, "hairRadRoot", draw_set->root_radius * draw_set->radius_scale * 0.5f);
+ DRW_shgroup_uniform_float_copy(shgrp, "hairRadTip", draw_set->tip_radius * draw_set->radius_scale * 0.5f);
+ DRW_shgroup_uniform_bool_copy(shgrp, "hairCloseTip", (draw_set->shape_flag & PART_SHAPE_CLOSE_TIP) != 0);
+ /* TODO(fclem): Until we have a better way to cull the hair and render with orco, bypass culling test. */
+ DRW_shgroup_call_object_add_no_cull(shgrp, hair_cache->final[subdiv].proc_hairs[thickness_res - 1], object);
+
+ /* Transform Feedback subdiv. */
+ if (need_ft_update) {
+ GPUShader *tf_shader = hair_refine_shader_get(PART_REFINE_CATMULL_ROM);
+ DRWShadingGroup *tf_shgrp = DRW_shgroup_transform_feedback_create(tf_shader, g_tf_pass,
+ hair_cache->final[subdiv].proc_point_buf);
+ DRW_shgroup_uniform_texture(tf_shgrp, "hairPointBuffer", hair_cache->point_tex);
+ DRW_shgroup_uniform_texture(tf_shgrp, "hairStrandBuffer", hair_cache->strand_tex);
+ DRW_shgroup_call_procedural_points_add(tf_shgrp, hair_cache->final[subdiv].point_len, NULL);
+ }
+
+ return shgrp;
+}
+
+DRWShadingGroup *DRW_shgroup_hair_create(
+ Object *object, HairSystem *hsys,
+ Mesh *scalp, const HairDrawSettings *draw_set,
+ DRWPass *hair_pass,
+ GPUShader *shader)
+{
+ return drw_shgroup_create_hair_procedural_ex(object, hsys, scalp, draw_set, hair_pass, NULL, shader);
+}
+
+DRWShadingGroup *DRW_shgroup_material_hair_create(
+ Object *object, HairSystem *hsys,
+ Mesh *scalp, const HairDrawSettings *draw_set,
+ DRWPass *hair_pass,
+ struct GPUMaterial *material)
+{
+ return drw_shgroup_create_hair_procedural_ex(object, hsys, scalp, draw_set, hair_pass, material, NULL);
}
void DRW_hair_update(void)
diff --git a/source/blender/draw/intern/draw_hair_private.h b/source/blender/draw/intern/draw_hair_private.h
index 41f91e02459..395939838f0 100644
--- a/source/blender/draw/intern/draw_hair_private.h
+++ b/source/blender/draw/intern/draw_hair_private.h
@@ -39,16 +39,22 @@ struct Object;
struct ParticleSystem;
struct ModifierData;
struct ParticleHairCache;
+struct HairSystem;
typedef struct ParticleHairFinalCache {
/* Output of the subdivision stage: vertex buff sized to subdiv level. */
- GPUVertBuf *proc_buf;
+ GPUVertBuf *proc_point_buf;
GPUTexture *proc_tex;
- /* Just contains a huge index buffer used to draw the final hair. */
+ GPUVertBuf *proc_hair_index_buf; /* Hair strand index for each vertex */
+ GPUTexture *hair_index_tex;
+
+ /* Just contains a huge index buffer used to draw the final hair. */
GPUBatch *proc_hairs[MAX_THICKRES];
- int strands_res; /* points per hair, at least 2 */
+ int strands_len;
+ int elems_len;
+ int point_len;
} ParticleHairFinalCache;
typedef struct ParticleHairCache {
@@ -81,6 +87,8 @@ typedef struct ParticleHairCache {
int point_len;
} ParticleHairCache;
+void particle_batch_cache_clear_hair(struct ParticleHairCache *hair_cache);
+
bool particles_ensure_procedural_data(
struct Object *object,
struct ParticleSystem *psys,
@@ -89,4 +97,12 @@ bool particles_ensure_procedural_data(
int subdiv,
int thickness_res);
+bool hair_ensure_procedural_data(
+ struct Object *object,
+ struct HairSystem *hsys,
+ struct Mesh *scalp,
+ struct ParticleHairCache **r_hair_cache,
+ int subdiv,
+ int thickness_res);
+
#endif /* __DRAW_HAIR_PRIVATE_H__ */
diff --git a/source/blender/draw/intern/draw_manager.c b/source/blender/draw/intern/draw_manager.c
index 5140e379aaf..ca95e12de2d 100644
--- a/source/blender/draw/intern/draw_manager.c
+++ b/source/blender/draw/intern/draw_manager.c
@@ -2464,6 +2464,9 @@ void DRW_engines_register(void)
/* BKE: gpencil.c */
extern void *BKE_gpencil_batch_cache_dirty_cb;
extern void *BKE_gpencil_batch_cache_free_cb;
+ /* BKE: hair.c */
+ extern void *BKE_hair_batch_cache_dirty_cb;
+ extern void *BKE_hair_batch_cache_free_cb;
BKE_mball_batch_cache_dirty_cb = DRW_mball_batch_cache_dirty;
BKE_mball_batch_cache_free_cb = DRW_mball_batch_cache_free;
@@ -2482,6 +2485,9 @@ void DRW_engines_register(void)
BKE_gpencil_batch_cache_dirty_cb = DRW_gpencil_batch_cache_dirty;
BKE_gpencil_batch_cache_free_cb = DRW_gpencil_batch_cache_free;
+
+ BKE_hair_batch_cache_dirty_cb = DRW_hair_batch_cache_dirty;
+ BKE_hair_batch_cache_free_cb = DRW_hair_batch_cache_free;
}
}
diff --git a/source/blender/draw/modes/object_mode.c b/source/blender/draw/modes/object_mode.c
index 26e1428cb8b..e0a78c48a4c 100644
--- a/source/blender/draw/modes/object_mode.c
+++ b/source/blender/draw/modes/object_mode.c
@@ -114,6 +114,7 @@ typedef struct OBJECT_PassList {
struct DRWPass *bone_envelope;
struct DRWPass *bone_axes;
struct DRWPass *particle;
+ struct DRWPass *hair;
struct DRWPass *lightprobes;
/* use for empty/background images */
struct DRWPass *reference_image;
@@ -247,6 +248,9 @@ typedef struct OBJECT_PrivateData {
/* Texture Space */
DRWShadingGroup *texspace;
+ /* Hair Systems */
+ DRWShadingGroup *hair_verts;
+
/* Outlines id offset */
int id_ofs_active;
int id_ofs_select;
@@ -1351,6 +1355,20 @@ static void OBJECT_cache_init(void *vedata)
}
{
+ /* Hair */
+ psl->hair = DRW_pass_create(
+ "Hair Pass",
+ DRW_STATE_WRITE_COLOR | DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_LESS | DRW_STATE_BLEND |
+ DRW_STATE_POINT | DRW_STATE_WIRE);
+
+ GPUShader *sh_verts = GPU_shader_get_builtin_shader(GPU_SHADER_3D_POINT_UNIFORM_SIZE_UNIFORM_COLOR_AA);
+ stl->g_data->hair_verts = DRW_shgroup_create(sh_verts, psl->hair);
+ DRW_shgroup_uniform_vec4(stl->g_data->hair_verts, "color", ts.colorVertex, 1);
+ DRW_shgroup_uniform_float(stl->g_data->hair_verts, "size", &ts.sizeVertex, 1);
+ DRW_shgroup_state_enable(stl->g_data->hair_verts, DRW_STATE_POINT);
+ }
+
+ {
/* Empty/Background Image Pass */
psl->reference_image = DRW_pass_create(
"Refrence Image Pass",
@@ -2623,6 +2641,7 @@ static void OBJECT_draw_scene(void *vedata)
DRW_draw_pass(psl->lightprobes);
}
+ DRW_draw_pass(psl->hair);
DRW_draw_pass(psl->ob_center);
if (DRW_state_is_fbo()) {
diff --git a/source/blender/draw/modes/shaders/common_hair_lib.glsl b/source/blender/draw/modes/shaders/common_hair_lib.glsl
index 552690ba972..75e58adf374 100644
--- a/source/blender/draw/modes/shaders/common_hair_lib.glsl
+++ b/source/blender/draw/modes/shaders/common_hair_lib.glsl
@@ -6,13 +6,6 @@
**/
/**
- * hairStrandsRes: Number of points per hair strand.
- * 2 - no subdivision
- * 3+ - 1 or more interpolated points per hair.
- **/
-uniform int hairStrandsRes = 8;
-
-/**
* hairThicknessRes : Subdiv around the hair.
* 1 - Wire Hair: Only one pixel thick, independant of view distance.
* 2 - Polystrip Hair: Correct width, flat if camera is parallel.
@@ -33,6 +26,7 @@ uniform samplerBuffer hairPointBuffer; /* RGBA32F */
/* -- Per strands data -- */
uniform usamplerBuffer hairStrandBuffer; /* R32UI */
+uniform usamplerBuffer hairIndexBuffer; /* R32UI */
/* Not used, use one buffer per uv layer */
//uniform samplerBuffer hairUVBuffer; /* RG32F */
@@ -49,6 +43,13 @@ void unpack_strand_data(uint data, out int strand_offset, out int strand_segment
#endif
}
+int hair_get_strand_id(void)
+{
+ //return gl_VertexID / (hairStrandsRes * hairThicknessRes);
+ uint strand_index = texelFetch(hairIndexBuffer, gl_VertexID).x;
+ return int(strand_index);
+}
+
/* -- Subdivision stage -- */
/**
* We use a transform feedback to preprocess the strands and add more subdivision to it.
@@ -59,40 +60,40 @@ void unpack_strand_data(uint data, out int strand_offset, out int strand_segment
**/
#ifdef HAIR_PHASE_SUBDIV
-int hair_get_base_id(float local_time, int strand_segments, out float interp_time)
+/**
+ * Calculate segment and local time for interpolation
+ */
+void hair_get_interp_time(float local_time, int strand_segments, out int interp_segment, out float interp_time)
{
float time_per_strand_seg = 1.0 / float(strand_segments);
float ratio = local_time / time_per_strand_seg;
+ interp_segment = int(ratio);
interp_time = fract(ratio);
-
- return int(ratio);
}
void hair_get_interp_attribs(out vec4 data0, out vec4 data1, out vec4 data2, out vec4 data3, out float interp_time)
{
- float local_time = float(gl_VertexID % hairStrandsRes) / float(hairStrandsRes - 1);
-
- int hair_id = gl_VertexID / hairStrandsRes;
- uint strand_data = texelFetch(hairStrandBuffer, hair_id).x;
-
+ int strand_index = hair_get_strand_id();
+ uint strand_data = texelFetch(hairStrandBuffer, strand_index).x;
int strand_offset, strand_segments;
unpack_strand_data(strand_data, strand_offset, strand_segments);
- int id = hair_get_base_id(local_time, strand_segments, interp_time);
+ float local_time = float(gl_VertexID - strand_offset) / float(strand_segments);
+ int interp_segment;
+ hair_get_interp_time(local_time, strand_segments, interp_segment, interp_time);
+ int interp_point = interp_segment + strand_offset;
- int ofs_id = id + strand_offset;
+ data0 = texelFetch(hairPointBuffer, interp_point - 1);
+ data1 = texelFetch(hairPointBuffer, interp_point);
+ data2 = texelFetch(hairPointBuffer, interp_point + 1);
+ data3 = texelFetch(hairPointBuffer, interp_point + 2);
- data0 = texelFetch(hairPointBuffer, ofs_id - 1);
- data1 = texelFetch(hairPointBuffer, ofs_id);
- data2 = texelFetch(hairPointBuffer, ofs_id + 1);
- data3 = texelFetch(hairPointBuffer, ofs_id + 2);
-
- if (id <= 0) {
+ if (interp_segment <= 0) {
/* root points. Need to reconstruct previous data. */
data0 = data1 * 2.0 - data2;
}
- if (id + 1 >= strand_segments) {
+ if (interp_segment + 1 >= strand_segments) {
/* tip points. Need to reconstruct next data. */
data3 = data2 * 2.0 - data1;
}
@@ -105,11 +106,6 @@ void hair_get_interp_attribs(out vec4 data0, out vec4 data1, out vec4 data2, out
**/
#ifndef HAIR_PHASE_SUBDIV
-int hair_get_strand_id(void)
-{
- return gl_VertexID / (hairStrandsRes * hairThicknessRes);
-}
-
int hair_get_base_id(void)
{
return gl_VertexID / hairThicknessRes;
@@ -165,25 +161,25 @@ void hair_get_pos_tan_binor_time(
vec2 hair_get_customdata_vec2(const samplerBuffer cd_buf)
{
- int id = hair_get_strand_id();
- return texelFetch(cd_buf, id).rg;
+ int strand_index = hair_get_strand_id();
+ return texelFetch(cd_buf, strand_index).rg;
}
vec3 hair_get_customdata_vec3(const samplerBuffer cd_buf)
{
- int id = hair_get_strand_id();
- return texelFetch(cd_buf, id).rgb;
+ int strand_index = hair_get_strand_id();
+ return texelFetch(cd_buf, strand_index).rgb;
}
vec4 hair_get_customdata_vec4(const samplerBuffer cd_buf)
{
- int id = hair_get_strand_id();
- return texelFetch(cd_buf, id).rgba;
+ int strand_index = hair_get_strand_id();
+ return texelFetch(cd_buf, strand_index).rgba;
}
-vec3 hair_get_strand_pos(void)
+vec3 hair_get_strand_pos()
{
- int id = hair_get_strand_id() * hairStrandsRes;
+ int id = hair_get_base_id();
return texelFetch(hairPointBuffer, id).point_position;
}
diff --git a/source/blender/editors/object/object_intern.h b/source/blender/editors/object/object_intern.h
index ef8653541f0..2cc8671d84b 100644
--- a/source/blender/editors/object/object_intern.h
+++ b/source/blender/editors/object/object_intern.h
@@ -175,6 +175,7 @@ void OBJECT_OT_skin_radii_equalize(struct wmOperatorType *ot);
void OBJECT_OT_skin_armature_create(struct wmOperatorType *ot);
void OBJECT_OT_laplaciandeform_bind(struct wmOperatorType *ot);
void OBJECT_OT_surfacedeform_bind(struct wmOperatorType *ot);
+void OBJECT_OT_hair_generate_follicles(struct wmOperatorType *ot);
/* grease pencil modifiers */
void OBJECT_OT_gpencil_modifier_add(struct wmOperatorType *ot);
diff --git a/source/blender/editors/object/object_modifier.c b/source/blender/editors/object/object_modifier.c
index 43f651b0532..8384755c679 100644
--- a/source/blender/editors/object/object_modifier.c
+++ b/source/blender/editors/object/object_modifier.c
@@ -37,6 +37,7 @@
#include "DNA_anim_types.h"
#include "DNA_armature_types.h"
#include "DNA_curve_types.h"
+#include "DNA_hair_types.h"
#include "DNA_key_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
@@ -58,12 +59,14 @@
#include "BKE_DerivedMesh.h"
#include "BKE_effect.h"
#include "BKE_global.h"
+#include "BKE_hair.h"
#include "BKE_key.h"
#include "BKE_lattice.h"
#include "BKE_main.h"
#include "BKE_mesh.h"
#include "BKE_mesh_mapping.h"
#include "BKE_mesh_runtime.h"
+#include "BKE_mesh_sample.h"
#include "BKE_modifier.h"
#include "BKE_multires.h"
#include "BKE_report.h"
@@ -2417,3 +2420,88 @@ void OBJECT_OT_surfacedeform_bind(wmOperatorType *ot)
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
edit_modifier_properties(ot);
}
+
+/************************ Hair follicle generate operator *********************/
+
+static int hair_generate_follicles_poll(bContext *C)
+{
+ return edit_modifier_poll_generic(C, &RNA_HairModifier, 0);
+}
+
+static int hair_generate_follicles_exec(bContext *C, wmOperator *op)
+{
+ Object *ob = ED_object_active_context(C);
+ HairModifierData *hmd = (HairModifierData *)edit_modifier_property_get(op, ob, eModifierType_Hair);
+
+ if (!hmd)
+ return OPERATOR_CANCELLED;
+
+ BLI_assert(hmd->hair_system != NULL);
+
+ struct Depsgraph *depsgraph = CTX_data_depsgraph(C);
+
+ BLI_assert(ob && ob->type == OB_MESH);
+ Mesh *scalp = (Mesh *)DEG_get_evaluated_id(depsgraph, ob->data);
+ HairSystem *hsys = hmd->hair_system;
+
+ BKE_hair_generate_follicles(
+ hsys,
+ scalp,
+ (unsigned int)hmd->follicle_seed,
+ hmd->follicle_count);
+
+ {
+ const int numverts = 5;
+ const float hairlen = 0.05f;
+ const float taper_length = 0.02f;
+ const float taper_thickness = 0.8f;
+ BKE_hair_fiber_curves_begin(hsys, hsys->pattern->num_follicles);
+ for (int i = 0; i < hsys->pattern->num_follicles; ++i)
+ {
+ BKE_hair_set_fiber_curve(hsys, i, numverts, taper_length, taper_thickness);
+ }
+ BKE_hair_fiber_curves_end(hsys);
+ for (int i = 0; i < hsys->pattern->num_follicles; ++i)
+ {
+ float loc[3], nor[3], tan[3];
+ BKE_mesh_sample_eval(scalp, &hsys->pattern->follicles[i].mesh_sample, loc, nor, tan);
+ for (int j = 0; j < numverts; ++j)
+ {
+ madd_v3_v3fl(loc, nor, hairlen / (numverts-1));
+ BKE_hair_set_fiber_vertex(hsys, i * numverts + j, 0, loc);
+ }
+ }
+ }
+
+ BKE_hair_bind_follicles(hmd->hair_system, scalp);
+
+ DEG_id_tag_update(&ob->id, OB_RECALC_DATA);
+ WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, ob);
+
+ return OPERATOR_FINISHED;
+}
+
+static int hair_generate_follicles_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
+{
+ if (edit_modifier_invoke_properties(C, op))
+ return hair_generate_follicles_exec(C, op);
+ else
+ return OPERATOR_CANCELLED;
+}
+
+void OBJECT_OT_hair_generate_follicles(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Hair Follicles Generate";
+ ot->description = "Generate hair follicles for a hair modifier";
+ ot->idname = "OBJECT_OT_hair_generate_follicles";
+
+ /* api callbacks */
+ ot->poll = hair_generate_follicles_poll;
+ ot->invoke = hair_generate_follicles_invoke;
+ ot->exec = hair_generate_follicles_exec;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
+ edit_modifier_properties(ot);
+}
diff --git a/source/blender/editors/object/object_ops.c b/source/blender/editors/object/object_ops.c
index ac2eb60456f..16ea0791adf 100644
--- a/source/blender/editors/object/object_ops.c
+++ b/source/blender/editors/object/object_ops.c
@@ -263,6 +263,7 @@ void ED_operatortypes_object(void)
WM_operatortype_append(OBJECT_OT_data_transfer);
WM_operatortype_append(OBJECT_OT_datalayout_transfer);
WM_operatortype_append(OBJECT_OT_surfacedeform_bind);
+ WM_operatortype_append(OBJECT_OT_hair_generate_follicles);
WM_operatortype_append(OBJECT_OT_hide_view_clear);
WM_operatortype_append(OBJECT_OT_hide_view_set);
diff --git a/source/blender/editors/space_outliner/outliner_draw.c b/source/blender/editors/space_outliner/outliner_draw.c
index 211c9e1a392..5244c364b80 100644
--- a/source/blender/editors/space_outliner/outliner_draw.c
+++ b/source/blender/editors/space_outliner/outliner_draw.c
@@ -1039,6 +1039,9 @@ TreeElementIcon tree_element_get_icon(TreeStoreElem *tselem, TreeElement *te)
case eModifierType_WeightedNormal:
data.icon = ICON_MOD_NORMALEDIT;
break;
+ case eModifierType_Hair:
+ data.icon = ICON_STRANDS;
+ break;
/* Default */
case eModifierType_None:
case eModifierType_ShapeKey:
diff --git a/source/blender/gpu/GPU_texture.h b/source/blender/gpu/GPU_texture.h
index 23b88645e33..f5f00d29f1f 100644
--- a/source/blender/gpu/GPU_texture.h
+++ b/source/blender/gpu/GPU_texture.h
@@ -107,10 +107,10 @@ typedef enum GPUTextureFormat {
/* Texture only format */
GPU_RGB16F,
+ GPU_RGB32F,
#if 0
GPU_RGBA16_SNORM,
GPU_RGBA8_SNORM,
- GPU_RGB32F,
GPU_RGB32I,
GPU_RGB32UI,
GPU_RGB16_SNORM,
diff --git a/source/blender/makesdna/DNA_hair_types.h b/source/blender/makesdna/DNA_hair_types.h
new file mode 100644
index 00000000000..790d7952f4a
--- /dev/null
+++ b/source/blender/makesdna/DNA_hair_types.h
@@ -0,0 +1,136 @@
+/*
+ * ***** 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.
+ *
+ * Contributor(s): Blender Foundation
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ *
+ */
+
+/** \file DNA_hair_types.h
+ * \ingroup DNA
+ */
+
+#ifndef __DNA_HAIR_TYPES_H__
+#define __DNA_HAIR_TYPES_H__
+
+#include "DNA_defs.h"
+#include "DNA_listBase.h"
+#include "DNA_ID.h"
+#include "DNA_meshdata_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Root point (follicle) of a hair on a surface */
+typedef struct HairFollicle {
+ MeshSample mesh_sample; /* Sample on the scalp mesh for the root vertex */
+ unsigned int curve; /* Index of the curve used by the fiber */
+ int pad;
+} HairFollicle;
+
+/* Collection of hair roots on a surface */
+typedef struct HairPattern {
+ struct HairFollicle *follicles;
+ int num_follicles;
+ int pad;
+} HairPattern;
+
+typedef struct HairFiberCurve {
+ int vertstart; /* Offset in the vertex array where the curve starts */
+ int numverts; /* Number of vertices in the curve */
+
+ /* Shape */
+ float taper_length; /* Distance at which final thickness is reached */
+ float taper_thickness; /* Relative thickness of the strand */
+} HairFiberCurve;
+
+typedef struct HairFiberVertex {
+ int flag;
+ float co[3];
+} HairFiberVertex;
+
+/* Hair curve data */
+typedef struct HairCurveData
+{
+ /* Curves for shaping hair fibers */
+ struct HairFiberCurve *curves;
+ /* Control vertices on curves */
+ struct HairFiberVertex *verts;
+ /* Number of curves */
+ int totcurves;
+ /* Number of curve vertices */
+ int totverts;
+} HairCurveData;
+
+typedef struct HairSystem {
+ int flag;
+ int pad;
+
+ /* Set of hair follicles on the scalp mesh */
+ struct HairPattern *pattern;
+
+ /* Curve data */
+ HairCurveData curve_data;
+
+ /* Data buffers for drawing */
+ void *draw_batch_cache;
+ /* Texture buffer for drawing */
+ void *draw_texture_cache;
+} HairSystem;
+
+typedef enum eHairSystemFlag
+{
+ /* Curve positions have changed, rebind hair follicles */
+ HAIR_SYSTEM_UPDATE_FOLLICLE_BINDING = (1 << 8),
+} eHairSystemFlag;
+
+typedef struct HairDrawSettings
+{
+ short follicle_mode;
+ short fiber_mode;
+ short shape_flag;
+ short pad;
+
+ float shape;
+ float root_radius;
+ float tip_radius;
+ float radius_scale;
+} HairDrawSettings;
+
+typedef enum eHairDrawFollicleMode
+{
+ HAIR_DRAW_FOLLICLE_NONE = 0,
+ HAIR_DRAW_FOLLICLE_POINTS = 1,
+} eHairDrawFollicleMode;
+
+typedef enum eHairDrawFiberMode
+{
+ HAIR_DRAW_FIBER_NONE = 0,
+ HAIR_DRAW_FIBER_CURVES = 1,
+} eHairDrawFiberMode;
+
+typedef enum eHairDrawShapeFlag {
+ HAIR_DRAW_CLOSE_TIP = (1<<0),
+} eHairDrawShapeFlag;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __DNA_HAIR_TYPES_H__ */
diff --git a/source/blender/makesdna/DNA_meshdata_types.h b/source/blender/makesdna/DNA_meshdata_types.h
index 7d79242016f..5a69db4e0b4 100644
--- a/source/blender/makesdna/DNA_meshdata_types.h
+++ b/source/blender/makesdna/DNA_meshdata_types.h
@@ -380,6 +380,13 @@ enum {
FREESTYLE_FACE_MARK = 1,
};
+typedef struct MeshSample {
+ unsigned int orig_verts[3];
+ float orig_weights[3]; /* also used as volume sample location */
+ unsigned int orig_poly;
+ unsigned int orig_loops[3];
+} MeshSample;
+
/* mvert->flag */
enum {
/* SELECT = (1 << 0), */
diff --git a/source/blender/makesdna/DNA_modifier_types.h b/source/blender/makesdna/DNA_modifier_types.h
index 02e70a323c7..5000a4d248a 100644
--- a/source/blender/makesdna/DNA_modifier_types.h
+++ b/source/blender/makesdna/DNA_modifier_types.h
@@ -91,6 +91,7 @@ typedef enum ModifierType {
eModifierType_MeshSequenceCache = 52,
eModifierType_SurfaceDeform = 53,
eModifierType_WeightedNormal = 54,
+ eModifierType_Hair = 55,
NUM_MODIFIER_TYPES
} ModifierType;
@@ -1692,4 +1693,32 @@ enum {
#define MOD_MESHSEQ_READ_ALL \
(MOD_MESHSEQ_READ_VERT | MOD_MESHSEQ_READ_POLY | MOD_MESHSEQ_READ_UV | MOD_MESHSEQ_READ_COLOR)
+/* Hair modifier */
+typedef struct HairModifierFiberCurve {
+ struct HairModifierFiberCurve *next, *prev;
+
+ /* Index for the mesh sample buffer */
+ int mesh_sample_index;
+ /* Number of vertices in the curve */
+ int numverts;
+ /* Vertex array */
+ struct HairFiberVertex *verts;
+} HairModifierFiberCurve;
+
+typedef struct HairModifierData {
+ ModifierData modifier;
+
+ int flag;
+ int pad;
+
+ struct HairSystem *hair_system;
+ struct HairDrawSettings *draw_settings;
+
+ /* Follicle distribution parameters */
+ int follicle_seed;
+ int follicle_count;
+
+ ListBase fiber_curves;
+} HairModifierData;
+
#endif /* __DNA_MODIFIER_TYPES_H__ */
diff --git a/source/blender/makesdna/intern/makesdna.c b/source/blender/makesdna/intern/makesdna.c
index 7b27ec05865..14d42662cfc 100644
--- a/source/blender/makesdna/intern/makesdna.c
+++ b/source/blender/makesdna/intern/makesdna.c
@@ -131,6 +131,7 @@ static const char *includefiles[] = {
"DNA_layer_types.h",
"DNA_workspace_types.h",
"DNA_lightprobe_types.h",
+ "DNA_hair_types.h",
/* see comment above before editing! */
@@ -1357,5 +1358,6 @@ int main(int argc, char **argv)
#include "DNA_layer_types.h"
#include "DNA_workspace_types.h"
#include "DNA_lightprobe_types.h"
+#include "DNA_hair_types.h"
/* end of list */
diff --git a/source/blender/makesrna/RNA_access.h b/source/blender/makesrna/RNA_access.h
index 8036f9edaa7..9001e21c76e 100644
--- a/source/blender/makesrna/RNA_access.h
+++ b/source/blender/makesrna/RNA_access.h
@@ -277,6 +277,9 @@ extern StructRNA RNA_GPencilSculptBrush;
extern StructRNA RNA_GaussianBlurSequence;
extern StructRNA RNA_GlowSequence;
extern StructRNA RNA_GreasePencil;
+extern StructRNA RNA_HairGroup;
+extern StructRNA RNA_HairModifier;
+extern StructRNA RNA_HairPattern;
extern StructRNA RNA_Header;
extern StructRNA RNA_HemiLight;
extern StructRNA RNA_Histogram;
diff --git a/source/blender/makesrna/intern/CMakeLists.txt b/source/blender/makesrna/intern/CMakeLists.txt
index ec240c222a1..bd603868278 100644
--- a/source/blender/makesrna/intern/CMakeLists.txt
+++ b/source/blender/makesrna/intern/CMakeLists.txt
@@ -52,6 +52,7 @@ set(DEFSRC
rna_gpencil_modifier.c
rna_shader_fx.c
rna_group.c
+ rna_hair.c
rna_image.c
rna_key.c
rna_lamp.c
@@ -62,6 +63,7 @@ set(DEFSRC
rna_mask.c
rna_material.c
rna_mesh.c
+ rna_mesh_sample.c
rna_meta.c
rna_modifier.c
rna_movieclip.c
diff --git a/source/blender/makesrna/intern/makesrna.c b/source/blender/makesrna/intern/makesrna.c
index b0713987e16..f63ba0729e3 100644
--- a/source/blender/makesrna/intern/makesrna.c
+++ b/source/blender/makesrna/intern/makesrna.c
@@ -3405,6 +3405,7 @@ static RNAProcessItem PROCESS_ITEMS[] = {
{"rna_fluidsim.c", NULL, RNA_def_fluidsim},
{"rna_gpencil.c", NULL, RNA_def_gpencil},
{"rna_group.c", NULL, RNA_def_collections},
+ {"rna_hair.c", NULL, RNA_def_hair},
{"rna_image.c", "rna_image_api.c", RNA_def_image},
{"rna_key.c", NULL, RNA_def_key},
{"rna_lamp.c", NULL, RNA_def_light},
@@ -3414,6 +3415,7 @@ static RNAProcessItem PROCESS_ITEMS[] = {
{"rna_main.c", "rna_main_api.c", RNA_def_main},
{"rna_material.c", "rna_material_api.c", RNA_def_material},
{"rna_mesh.c", "rna_mesh_api.c", RNA_def_mesh},
+ {"rna_mesh_sample.c", NULL, RNA_def_mesh_sample},
{"rna_meta.c", "rna_meta_api.c", RNA_def_meta},
{"rna_modifier.c", NULL, RNA_def_modifier},
{"rna_gpencil_modifier.c", NULL, RNA_def_greasepencil_modifier},
diff --git a/source/blender/makesrna/intern/rna_hair.c b/source/blender/makesrna/intern/rna_hair.c
new file mode 100644
index 00000000000..39bf28dff42
--- /dev/null
+++ b/source/blender/makesrna/intern/rna_hair.c
@@ -0,0 +1,222 @@
+/*
+ * ***** 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.
+ *
+ * Contributor(s): Blender Foundation.
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file blender/makesrna/intern/rna_hair.c
+ * \ingroup RNA
+ */
+
+#include <stdlib.h>
+
+#include "RNA_define.h"
+#include "RNA_enum_types.h"
+
+#include "rna_internal.h"
+
+#include "DNA_hair_types.h"
+
+#include "WM_types.h"
+
+#ifdef RNA_RUNTIME
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_listbase.h"
+
+#include "DNA_mesh_types.h"
+#include "DNA_object_types.h"
+
+#include "BKE_context.h"
+#include "BKE_hair.h"
+#include "BKE_main.h"
+
+#include "DEG_depsgraph.h"
+#include "DEG_depsgraph_query.h"
+
+#include "RNA_access.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+static void UNUSED_FUNCTION(rna_HairSystem_update)(Main *UNUSED(bmain), Scene *UNUSED(scene), PointerRNA *ptr)
+{
+ DEG_id_tag_update(ptr->id.data, OB_RECALC_DATA);
+}
+
+static void rna_HairDrawSettings_update(Main *UNUSED(bmain), Scene *UNUSED(scene), PointerRNA *ptr)
+{
+#if 0
+ /* XXX Only need to update render engines
+ * However, that requires finding all hair systems using these draw settings,
+ * then flagging the cache as dirty.
+ */
+ BKE_hair_batch_cache_dirty(hsys, BKE_HAIR_BATCH_DIRTY_ALL);
+#else
+ DEG_id_tag_update(ptr->id.data, OB_RECALC_DATA);
+#endif
+ WM_main_add_notifier(NC_OBJECT | ND_DRAW, ptr->id.data);
+}
+
+static void rna_HairSystem_generate_follicles(
+ HairSystem *hsys,
+ struct bContext *C,
+ Object *scalp,
+ int seed,
+ int count)
+{
+ if (!scalp)
+ {
+ return;
+ }
+
+ struct Depsgraph *depsgraph = CTX_data_depsgraph(C);
+
+ BLI_assert(scalp && scalp->type == OB_MESH);
+ Mesh *scalp_mesh = (Mesh *)DEG_get_evaluated_id(depsgraph, scalp->data);
+
+ BKE_hair_generate_follicles(hsys, scalp_mesh, (unsigned int)seed, count);
+}
+
+#else
+
+static void rna_def_hair_follicle(BlenderRNA *brna)
+{
+ StructRNA *srna;
+ PropertyRNA *prop;
+
+ srna = RNA_def_struct(brna, "HairFollicle", NULL);
+ RNA_def_struct_ui_text(srna, "Hair Follicle", "Single follicle on a surface");
+ RNA_def_struct_sdna(srna, "HairFollicle");
+
+ prop = RNA_def_property(srna, "mesh_sample", PROP_POINTER, PROP_NONE);
+ RNA_def_property_struct_type(prop, "MeshSample");
+}
+
+static void rna_def_hair_pattern(BlenderRNA *brna)
+{
+ StructRNA *srna;
+ PropertyRNA *prop;
+
+ srna = RNA_def_struct(brna, "HairPattern", NULL);
+ RNA_def_struct_ui_text(srna, "Hair Pattern", "Set of hair follicles distributed on a surface");
+ RNA_def_struct_sdna(srna, "HairPattern");
+ RNA_def_struct_ui_icon(srna, ICON_STRANDS);
+
+ prop = RNA_def_property(srna, "follicles", PROP_COLLECTION, PROP_NONE);
+ RNA_def_property_collection_sdna(prop, NULL, "follicles", "num_follicles");
+ RNA_def_property_struct_type(prop, "HairFollicle");
+ RNA_def_property_ui_text(prop, "Follicles", "Hair fiber follicles");
+}
+
+static void rna_def_hair_system(BlenderRNA *brna)
+{
+ StructRNA *srna;
+ FunctionRNA *func;
+ PropertyRNA *prop, *parm;
+
+ srna = RNA_def_struct(brna, "HairSystem", NULL);
+ RNA_def_struct_ui_text(srna, "Hair System", "Hair rendering and deformation data");
+ RNA_def_struct_sdna(srna, "HairSystem");
+ RNA_def_struct_ui_icon(srna, ICON_STRANDS);
+
+ prop = RNA_def_property(srna, "pattern", PROP_POINTER, PROP_NONE);
+ RNA_def_property_struct_type(prop, "HairPattern");
+ RNA_def_property_ui_text(prop, "Pattern", "Hair pattern");
+
+ func = RNA_def_function(srna, "generate_follicles", "rna_HairSystem_generate_follicles");
+ RNA_def_function_flag(func, FUNC_USE_CONTEXT);
+ parm = RNA_def_pointer(func, "scalp", "Object", "Scalp", "Scalp object on which to place hair follicles");
+ RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
+ parm = RNA_def_int(func, "seed", 0, 0, INT_MAX, "Seed", "Seed value for random numbers", 0, INT_MAX);
+ parm = RNA_def_int(func, "count", 0, 0, INT_MAX, "Count", "Maximum number of follicles to generate", 1, 1e5);
+ RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
+}
+
+static void rna_def_hair_draw_settings(BlenderRNA *brna)
+{
+ StructRNA *srna;
+ PropertyRNA *prop;
+
+ static const EnumPropertyItem follicle_mode_items[] = {
+ {HAIR_DRAW_FOLLICLE_NONE, "NONE", 0, "None", ""},
+ {HAIR_DRAW_FOLLICLE_POINTS, "POINTS", 0, "Points", "Draw a point for each follicle"},
+ {0, NULL, 0, NULL, NULL}
+ };
+
+ static const EnumPropertyItem fiber_mode_items[] = {
+ {HAIR_DRAW_FIBER_NONE, "NONE", 0, "None", ""},
+ {HAIR_DRAW_FIBER_CURVES, "CURVES", 0, "Curves", "Draw fiber curves"},
+ {0, NULL, 0, NULL, NULL}
+ };
+
+ srna = RNA_def_struct(brna, "HairDrawSettings", NULL);
+ RNA_def_struct_ui_text(srna, "Hair Draw Settings", "Settings for drawing hair systems");
+ RNA_def_struct_sdna(srna, "HairDrawSettings");
+
+ prop = RNA_def_property(srna, "follicle_mode", PROP_ENUM, PROP_NONE);
+ RNA_def_property_enum_items(prop, follicle_mode_items);
+ RNA_def_property_ui_text(prop, "Follicle Mode", "Draw follicles on the scalp surface");
+ RNA_def_property_update(prop, 0, "rna_HairDrawSettings_update");
+
+ prop = RNA_def_property(srna, "fiber_mode", PROP_ENUM, PROP_NONE);
+ RNA_def_property_enum_items(prop, fiber_mode_items);
+ RNA_def_property_ui_text(prop, "Fiber Mode", "Draw fiber curves");
+ RNA_def_property_update(prop, 0, "rna_HairDrawSettings_update");
+
+ /* hair shape */
+ prop = RNA_def_property(srna, "use_close_tip", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "shape_flag", HAIR_DRAW_CLOSE_TIP);
+ RNA_def_property_ui_text(prop, "Close Tip", "Set tip radius to zero");
+ RNA_def_property_update(prop, 0, "rna_HairDrawSettings_update");
+
+ prop = RNA_def_property(srna, "shape", PROP_FLOAT, PROP_FACTOR);
+ RNA_def_property_range(prop, -1.0f, 1.0f);
+ RNA_def_property_ui_text(prop, "Shape", "Strand shape parameter");
+ RNA_def_property_update(prop, 0, "rna_HairDrawSettings_update");
+
+ prop = RNA_def_property(srna, "root_radius", PROP_FLOAT, PROP_NONE);
+ RNA_def_property_range(prop, 0.0f, FLT_MAX);
+ RNA_def_property_ui_range(prop, 0.0f, 10.0f, 0.1, 2);
+ RNA_def_property_ui_text(prop, "Root", "Strand width at the root");
+ RNA_def_property_update(prop, 0, "rna_HairDrawSettings_update");
+
+ prop = RNA_def_property(srna, "tip_radius", PROP_FLOAT, PROP_NONE);
+ RNA_def_property_range(prop, 0.0f, FLT_MAX);
+ RNA_def_property_ui_range(prop, 0.0f, 10.0f, 0.1, 2);
+ RNA_def_property_ui_text(prop, "Tip", "Strand width at the tip");
+ RNA_def_property_update(prop, 0, "rna_HairDrawSettings_update");
+
+ prop = RNA_def_property(srna, "radius_scale", PROP_FLOAT, PROP_NONE);
+ RNA_def_property_range(prop, 0.0f, FLT_MAX);
+ RNA_def_property_ui_range(prop, 0.0f, 10.0f, 0.1, 2);
+ RNA_def_property_ui_text(prop, "Scaling", "Multiplier of radius properties");
+ RNA_def_property_update(prop, 0, "rna_HairDrawSettings_update");
+}
+
+void RNA_def_hair(BlenderRNA *brna)
+{
+ rna_def_hair_follicle(brna);
+ rna_def_hair_pattern(brna);
+ rna_def_hair_system(brna);
+ rna_def_hair_draw_settings(brna);
+}
+
+#endif
diff --git a/source/blender/makesrna/intern/rna_internal.h b/source/blender/makesrna/intern/rna_internal.h
index a88623e5b5b..d5923a05b80 100644
--- a/source/blender/makesrna/intern/rna_internal.h
+++ b/source/blender/makesrna/intern/rna_internal.h
@@ -163,6 +163,7 @@ void RNA_def_linestyle(struct BlenderRNA *brna);
void RNA_def_main(struct BlenderRNA *brna);
void RNA_def_material(struct BlenderRNA *brna);
void RNA_def_mesh(struct BlenderRNA *brna);
+void RNA_def_mesh_sample(struct BlenderRNA *brna);
void RNA_def_meta(struct BlenderRNA *brna);
void RNA_def_modifier(struct BlenderRNA *brna);
void RNA_def_nla(struct BlenderRNA *brna);
@@ -201,6 +202,7 @@ void RNA_def_world(struct BlenderRNA *brna);
void RNA_def_movieclip(struct BlenderRNA *brna);
void RNA_def_tracking(struct BlenderRNA *brna);
void RNA_def_mask(struct BlenderRNA *brna);
+void RNA_def_hair(struct BlenderRNA *brna);
/* Common Define functions */
diff --git a/source/blender/makesrna/intern/rna_mesh_sample.c b/source/blender/makesrna/intern/rna_mesh_sample.c
new file mode 100644
index 00000000000..05e22fc48a2
--- /dev/null
+++ b/source/blender/makesrna/intern/rna_mesh_sample.c
@@ -0,0 +1,73 @@
+/*
+ * ***** 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/makesrna/intern/rna_mesh_sample.c
+ * \ingroup RNA
+ */
+
+#include <stdlib.h>
+
+#include "MEM_guardedalloc.h"
+
+#include "DNA_meshdata_types.h"
+
+#include "BLI_utildefines.h"
+
+#include "BKE_mesh_sample.h"
+
+#include "RNA_access.h"
+#include "RNA_define.h"
+#include "RNA_types.h"
+
+#include "rna_internal.h"
+
+#include "WM_types.h"
+
+
+#ifdef RNA_RUNTIME
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+
+
+#else
+
+static void rna_def_mesh_sample(BlenderRNA *brna)
+{
+ StructRNA *srna;
+ PropertyRNA *prop;
+
+ srna = RNA_def_struct(brna, "MeshSample", NULL);
+ RNA_def_struct_sdna(srna, "MeshSample");
+ RNA_def_struct_ui_text(srna, "Mesh Sample", "Point on a mesh that follows deformation");
+
+ prop = RNA_def_property(srna, "vertex_indices", PROP_INT, PROP_UNSIGNED);
+ RNA_def_property_int_sdna(prop, NULL, "orig_verts");
+ RNA_def_property_clear_flag(prop, PROP_EDITABLE);
+ RNA_def_property_ui_text(prop, "Vertex Indices", "Index of the mesh vertices used for interpolation");
+}
+
+void RNA_def_mesh_sample(BlenderRNA *brna)
+{
+ rna_def_mesh_sample(brna);
+}
+
+#endif
diff --git a/source/blender/makesrna/intern/rna_modifier.c b/source/blender/makesrna/intern/rna_modifier.c
index f0898064860..5d9ca8c6f4e 100644
--- a/source/blender/makesrna/intern/rna_modifier.c
+++ b/source/blender/makesrna/intern/rna_modifier.c
@@ -115,6 +115,7 @@ const EnumPropertyItem rna_enum_object_modifier_type_items[] = {
{eModifierType_DynamicPaint, "DYNAMIC_PAINT", ICON_MOD_DYNAMICPAINT, "Dynamic Paint", ""},
{eModifierType_Explode, "EXPLODE", ICON_MOD_EXPLODE, "Explode", ""},
{eModifierType_Fluidsim, "FLUID_SIMULATION", ICON_MOD_FLUIDSIM, "Fluid Simulation", ""},
+ {eModifierType_Hair, "HAIR", ICON_STRANDS, "Hair", ""},
{eModifierType_Ocean, "OCEAN", ICON_MOD_OCEAN, "Ocean", ""},
{eModifierType_ParticleInstance, "PARTICLE_INSTANCE", ICON_MOD_PARTICLES, "Particle Instance", ""},
{eModifierType_ParticleSystem, "PARTICLE_SYSTEM", ICON_MOD_PARTICLES, "Particle System", ""},
@@ -281,12 +282,17 @@ const EnumPropertyItem rna_enum_axis_flag_xyz_items[] = {
#ifdef RNA_RUNTIME
+#include "BLI_listbase.h"
+
#include "DNA_particle_types.h"
#include "DNA_curve_types.h"
#include "DNA_smoke_types.h"
+#include "DNA_hair_types.h"
+#include "DNA_meshdata_types.h"
#include "BKE_cachefile.h"
#include "BKE_context.h"
+#include "BKE_hair.h"
#include "BKE_library.h"
#include "BKE_mesh_runtime.h"
#include "BKE_modifier.h"
@@ -295,6 +301,7 @@ const EnumPropertyItem rna_enum_axis_flag_xyz_items[] = {
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_build.h"
+#include "DEG_depsgraph_query.h"
#ifdef WITH_ALEMBIC
# include "ABC_alembic.h"
@@ -417,6 +424,8 @@ static StructRNA *rna_Modifier_refine(struct PointerRNA *ptr)
return &RNA_SurfaceDeformModifier;
case eModifierType_WeightedNormal:
return &RNA_WeightedNormalModifier;
+ case eModifierType_Hair:
+ return &RNA_HairModifier;
/* Default */
case eModifierType_None:
case eModifierType_ShapeKey:
@@ -1174,6 +1183,58 @@ static void rna_ParticleInstanceModifier_particle_system_set(PointerRNA *ptr, co
CLAMP_MIN(psmd->psys, 1);
}
+static void rna_Hair_fiber_curves_clear(HairModifierData *hmd)
+{
+ for (HairModifierFiberCurve* curve = hmd->fiber_curves.first; curve; curve = curve->next)
+ {
+ if (curve->verts)
+ {
+ MEM_freeN(curve->verts);
+ }
+ }
+ BLI_freelistN(&hmd->fiber_curves);
+}
+
+static void rna_Hair_fiber_curves_new(HairModifierData *hmd, ReportList *UNUSED(reports), int numverts)
+{
+ HairModifierFiberCurve *curve = MEM_callocN(sizeof(HairModifierFiberCurve), "hair fiber curve");
+ curve->numverts = numverts;
+ curve->verts = MEM_callocN(sizeof(HairFiberVertex) * numverts, "hair fiber curve vertices");
+
+ BLI_addtail(&hmd->fiber_curves, curve);
+}
+
+static void rna_Hair_fiber_curves_apply(ID *id, HairModifierData *hmd, bContext *C, ReportList *UNUSED(reports))
+{
+ const int totcurves = BLI_listbase_count(&hmd->fiber_curves);
+ int i = 0;
+
+ BKE_hair_fiber_curves_begin(hmd->hair_system, totcurves);
+ i = 0;
+ for (HairModifierFiberCurve *curve = hmd->fiber_curves.first; curve; curve = curve->next, ++i)
+ {
+ BKE_hair_set_fiber_curve(hmd->hair_system, i, curve->numverts, 0.1, 1.0);
+ }
+ BKE_hair_fiber_curves_end(hmd->hair_system);
+
+ i = 0;
+ for (HairModifierFiberCurve *curve = hmd->fiber_curves.first; curve; curve = curve->next)
+ {
+ for (int j = 0; j < curve->numverts; ++j, ++i)
+ {
+ BKE_hair_set_fiber_vertex(hmd->hair_system, i, curve->verts[j].flag, curve->verts[j].co);
+ }
+ }
+
+ {
+ Mesh *scalp = (Mesh *)DEG_get_evaluated_id(CTX_data_depsgraph(C), ((Object*)id)->data);
+ BKE_hair_bind_follicles(hmd->hair_system, scalp);
+ }
+
+ DEG_id_tag_update(id, OB_RECALC_DATA);
+ WM_main_add_notifier(NC_OBJECT | ND_MODIFIER, id);
+}
+
#else
static PropertyRNA *rna_def_property_subdivision_common(StructRNA *srna, const char type[])
@@ -4916,6 +4977,52 @@ static void rna_def_modifier_surfacedeform(BlenderRNA *brna)
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
}
+static void rna_def_modifier_hair_fiber_curve(BlenderRNA *brna)
+{
+ StructRNA *srna;
+ PropertyRNA *prop;
+
+ srna = RNA_def_struct(brna, "HairModifierFiberCurve", NULL);
+ RNA_def_struct_ui_text(srna, "Hair Modifier Fiber Curve", "");
+ RNA_def_struct_sdna(srna, "HairModifierFiberCurve");
+
+ prop = RNA_def_property(srna, "vertices", PROP_COLLECTION, PROP_NONE);
+ RNA_def_property_collection_sdna(prop, NULL, "verts", "numverts");
+ RNA_def_property_struct_type(prop, "HairModifierFiberVertex");
+ RNA_def_property_ui_text(prop, "Vertices", "Fiber vertices");
+
+
+ srna = RNA_def_struct(brna, "HairModifierFiberVertex", NULL);
+ RNA_def_struct_ui_text(srna, "Hair Modifier Fiber Vertex", "");
+ RNA_def_struct_sdna(srna, "HairFiberVertex");
+
+ prop = RNA_def_property(srna, "location", PROP_FLOAT, PROP_TRANSLATION);
+ RNA_def_property_float_sdna(prop, NULL, "co");
+ RNA_def_property_ui_text(prop, "Location", "Location of the vertex relative to the root");
+}
+
+static void rna_def_modifier_hair_fiber_curves_api(BlenderRNA *brna, PropertyRNA *cprop)
+{
+ StructRNA *srna;
+ FunctionRNA *func;
+ PropertyRNA *parm;
+
+ RNA_def_property_srna(cprop, "HairModifierFiberCurves");
+ srna = RNA_def_struct(brna, "HairModifierFiberCurves", NULL);
+ RNA_def_struct_ui_text(srna, "Hair Modifier Fiber Curves", "");
+ RNA_def_struct_sdna(srna, "HairModifierData");
+
+ /*func =*/ RNA_def_function(srna, "clear", "rna_Hair_fiber_curves_clear");
+
+ func = RNA_def_function(srna, "new", "rna_Hair_fiber_curves_new");
+ RNA_def_function_flag(func, FUNC_USE_REPORTS);
+ parm = RNA_def_int(func, "vertex_count", 2, 0, INT_MAX, "Vertex Count", "Number of vertices", 2, 1000);
+ RNA_def_property_flag(parm, PARM_REQUIRED);
+
+ func = RNA_def_function(srna, "apply", "rna_Hair_fiber_curves_apply");
+ RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_CONTEXT | FUNC_USE_REPORTS);
+}
+
static void rna_def_modifier_weightednormal(BlenderRNA *brna)
{
StructRNA *srna;
@@ -4978,6 +5085,43 @@ static void rna_def_modifier_weightednormal(BlenderRNA *brna)
RNA_def_property_update(prop, 0, "rna_Modifier_update");
}
+static void rna_def_modifier_hair(BlenderRNA *brna)
+{
+ StructRNA *srna;
+ PropertyRNA *prop;
+
+ srna = RNA_def_struct(brna, "HairModifier", "Modifier");
+ RNA_def_struct_ui_text(srna, "Hair Modifier", "");
+ RNA_def_struct_sdna(srna, "HairModifierData");
+ RNA_def_struct_ui_icon(srna, ICON_STRANDS);
+
+ prop = RNA_def_property(srna, "hair_system", PROP_POINTER, PROP_NONE);
+ RNA_def_property_ui_text(prop, "Hair", "Hair data");
+ RNA_def_property_clear_flag(prop, PROP_EDITABLE);
+
+ prop = RNA_def_property(srna, "follicle_seed", PROP_INT, PROP_NONE);
+ RNA_def_property_range(prop, 0, INT_MAX);
+ RNA_def_property_ui_text(prop, "Seed", "Follicle distribution random seed value");
+
+ prop = RNA_def_property(srna, "follicle_count", PROP_INT, PROP_NONE);
+ RNA_def_property_int_default(prop, 100000);
+ RNA_def_property_range(prop, 0, INT_MAX);
+ RNA_def_property_ui_range(prop, 1, 1e5, 1, 1);
+ RNA_def_property_ui_text(prop, "Follicle Count", "Maximum number of follicles");
+
+ prop = RNA_def_property(srna, "draw_settings", PROP_POINTER, PROP_NONE);
+ RNA_def_property_ui_text(prop, "Draw Settings", "Hair draw settings");
+ RNA_def_property_clear_flag(prop, PROP_EDITABLE);
+
+ prop = RNA_def_property(srna, "fiber_curves", PROP_COLLECTION, PROP_NONE);
+ RNA_def_property_collection_sdna(prop, NULL, "fiber_curves", NULL);
+ RNA_def_property_struct_type(prop, "HairModifierFiberCurve");
+ RNA_def_property_ui_text(prop, "Fiber Curves", "Fiber curve data");
+ rna_def_modifier_hair_fiber_curves_api(brna, prop);
+
+ rna_def_modifier_hair_fiber_curve(brna);
+}
+
void RNA_def_modifier(BlenderRNA *brna)
{
StructRNA *srna;
@@ -5100,6 +5244,7 @@ void RNA_def_modifier(BlenderRNA *brna)
rna_def_modifier_meshseqcache(brna);
rna_def_modifier_surfacedeform(brna);
rna_def_modifier_weightednormal(brna);
+ rna_def_modifier_hair(brna);
}
#endif
diff --git a/source/blender/modifiers/CMakeLists.txt b/source/blender/modifiers/CMakeLists.txt
index 045a5d31fcb..43dc8507ffb 100644
--- a/source/blender/modifiers/CMakeLists.txt
+++ b/source/blender/modifiers/CMakeLists.txt
@@ -63,6 +63,7 @@ set(SRC
intern/MOD_explode.c
intern/MOD_fluidsim.c
intern/MOD_fluidsim_util.c
+ intern/MOD_hair.c
intern/MOD_hook.c
intern/MOD_laplaciandeform.c
intern/MOD_laplaciansmooth.c
diff --git a/source/blender/modifiers/MOD_modifiertypes.h b/source/blender/modifiers/MOD_modifiertypes.h
index 3511b0edbec..d6e4e1868b2 100644
--- a/source/blender/modifiers/MOD_modifiertypes.h
+++ b/source/blender/modifiers/MOD_modifiertypes.h
@@ -87,6 +87,7 @@ extern ModifierTypeInfo modifierType_CorrectiveSmooth;
extern ModifierTypeInfo modifierType_MeshSequenceCache;
extern ModifierTypeInfo modifierType_SurfaceDeform;
extern ModifierTypeInfo modifierType_WeightedNormal;
+extern ModifierTypeInfo modifierType_Hair;
/* MOD_util.c */
void modifier_type_init(ModifierTypeInfo *types[]);
diff --git a/source/blender/modifiers/intern/MOD_hair.c b/source/blender/modifiers/intern/MOD_hair.c
new file mode 100644
index 00000000000..11edff95da6
--- /dev/null
+++ b/source/blender/modifiers/intern/MOD_hair.c
@@ -0,0 +1,169 @@
+/*
+ * ***** 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.
+ *
+ * The Original Code is Copyright (C) 2005 by the Blender Foundation.
+ * All rights reserved.
+ *
+ * Contributor(s): Daniel Dunbar
+ * Ton Roosendaal,
+ * Ben Batt,
+ * Brecht Van Lommel,
+ * Campbell Barton
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ *
+ */
+
+/** \file blender/modifiers/intern/MOD_hair.c
+ * \ingroup modifiers
+ */
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_utildefines.h"
+#include "BLI_listbase.h"
+
+#include "DNA_object_types.h"
+#include "DNA_hair_types.h"
+
+#include "BKE_hair.h"
+#include "BKE_library.h"
+#include "BKE_library_query.h"
+#include "BKE_modifier.h"
+
+#include "DEG_depsgraph_build.h"
+
+#include "MOD_util.h"
+
+
+static void initData(ModifierData *md)
+{
+ HairModifierData *hmd = (HairModifierData *) md;
+
+ hmd->hair_system = BKE_hair_new();
+
+ hmd->flag |= 0;
+
+ hmd->follicle_count = 100000;
+
+ hmd->draw_settings = BKE_hair_draw_settings_new();
+}
+
+static void copyData(const ModifierData *md, ModifierData *target, const int flag)
+{
+ const HairModifierData *hmd = (HairModifierData *) md;
+ HairModifierData *tfmd = (HairModifierData *) target;
+
+ modifier_copyData_generic(md, target, flag);
+
+ if (hmd->hair_system) {
+ tfmd->hair_system = BKE_hair_copy(hmd->hair_system);
+ }
+ if (hmd->draw_settings)
+ {
+ tfmd->draw_settings = BKE_hair_draw_settings_copy(hmd->draw_settings);
+ }
+}
+
+static void freeData(ModifierData *md)
+{
+ HairModifierData *hmd = (HairModifierData *) md;
+
+ if (hmd->hair_system) {
+ BKE_hair_free(hmd->hair_system);
+ }
+ if (hmd->draw_settings)
+ {
+ BKE_hair_draw_settings_free(hmd->draw_settings);
+ }
+ for (HairModifierFiberCurve *curve = hmd->fiber_curves.first; curve; curve = curve->next)
+ {
+ if (curve->verts)
+ {
+ MEM_freeN(curve->verts);
+ }
+ }
+ BLI_freelistN(&hmd->fiber_curves);
+}
+
+static struct Mesh *applyModifier(ModifierData *md, const ModifierEvalContext *ctx,
+ struct Mesh *mesh)
+{
+ HairModifierData *hmd = (HairModifierData *) md;
+
+ UNUSED_VARS(hmd, ctx);
+
+ return mesh;
+}
+
+static void foreachObjectLink(
+ ModifierData *md,
+ Object *ob,
+ ObjectWalkFunc walk,
+ void *userData)
+{
+ HairModifierData *hmd = (HairModifierData *) md;
+ UNUSED_VARS(ob, walk, userData, hmd);
+}
+
+static void foreachIDLink(
+ ModifierData *md,
+ Object *ob,
+ IDWalkFunc walk,
+ void *userData)
+{
+ HairModifierData *hmd = (HairModifierData *) md;
+ UNUSED_VARS(hmd);
+
+ foreachObjectLink(md, ob, (ObjectWalkFunc)walk, userData);
+}
+
+ModifierTypeInfo modifierType_Hair = {
+ /* name */ "Hair",
+ /* structName */ "HairModifierData",
+ /* structSize */ sizeof(HairModifierData),
+ /* type */ eModifierTypeType_NonGeometrical,
+ /* flags */ eModifierTypeFlag_AcceptsMesh |
+ eModifierTypeFlag_SupportsEditmode,
+
+ /* copyData */ copyData,
+
+ /* deformVerts_DM */ NULL,
+ /* deformMatrices_DM */ NULL,
+ /* deformVertsEM_DM */ NULL,
+ /* deformMatricesEM_DM*/NULL,
+ /* applyModifier_DM */ NULL,
+ /* applyModifierEM_DM */NULL,
+
+ /* deformVerts */ NULL,
+ /* deformMatrices */ NULL,
+ /* deformVertsEM */ NULL,
+ /* deformMatricesEM */ NULL,
+ /* applyModifier */ applyModifier,
+ /* applyModifierEM */ NULL,
+
+ /* initData */ initData,
+ /* requiredDataMask */ NULL,
+ /* freeData */ freeData,
+ /* isDisabled */ NULL,
+ /* updateDepsgraph */ NULL,
+ /* dependsOnTime */ NULL,
+ /* dependsOnNormals */ NULL,
+ /* foreachObjectLink */ foreachObjectLink,
+ /* foreachIDLink */ foreachIDLink,
+ /* foreachTexLink */ NULL,
+};
diff --git a/source/blender/modifiers/intern/MOD_util.c b/source/blender/modifiers/intern/MOD_util.c
index 721474a62f3..b240dd18119 100644
--- a/source/blender/modifiers/intern/MOD_util.c
+++ b/source/blender/modifiers/intern/MOD_util.c
@@ -291,5 +291,6 @@ void modifier_type_init(ModifierTypeInfo *types[])
INIT_TYPE(MeshSequenceCache);
INIT_TYPE(SurfaceDeform);
INIT_TYPE(WeightedNormal);
+ INIT_TYPE(Hair);
#undef INIT_TYPE
}
diff --git a/tests/gtests/CMakeLists.txt b/tests/gtests/CMakeLists.txt
index 3ab64d87309..0040f934dc2 100644
--- a/tests/gtests/CMakeLists.txt
+++ b/tests/gtests/CMakeLists.txt
@@ -13,6 +13,7 @@ if(WITH_GTESTS)
add_subdirectory(testing)
add_subdirectory(blenlib)
+ add_subdirectory(blenkernel)
add_subdirectory(guardedalloc)
add_subdirectory(bmesh)
if(WITH_ALEMBIC)
diff --git a/tests/gtests/blenkernel/BKE_mesh_sample_test.cc b/tests/gtests/blenkernel/BKE_mesh_sample_test.cc
new file mode 100644
index 00000000000..d95266754a8
--- /dev/null
+++ b/tests/gtests/blenkernel/BKE_mesh_sample_test.cc
@@ -0,0 +1,334 @@
+/* Apache License, Version 2.0 */
+
+#include <iostream>
+#include <fstream>
+#include <sstream>
+
+#include "testing/testing.h"
+
+#include "BKE_mesh_test_util.h"
+
+extern "C" {
+#include "MEM_guardedalloc.h"
+
+#include "BLI_math.h"
+#include "BLI_path_util.h"
+#include "BLI_rand.h"
+
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+
+#include "BKE_appdir.h"
+#include "BKE_global.h"
+#include "BKE_main.h"
+#include "BKE_mesh.h"
+#include "BKE_mesh_sample.h"
+}
+
+#define TEST_MESH_OUTPUT_FILE "mesh_dump_"
+
+static const float verts[][3] = { {-1, -1, -1}, {1, -1, -1}, {-1, 1, -1}, {1, 1, -1},
+ {-1, -1, 1}, {1, -1, 1}, {-1, 1, 1}, {1, 1, 1} };
+static const int faces[] = { 0, 1, 3, 2,
+ 4, 5, 7, 6,
+ 0, 1, 5, 4,
+ 2, 3, 7, 6,
+ 0, 2, 6, 4,
+ 1, 3, 7, 5,
+ };
+static const int face_lengths[] = { 4, 4, 4, 4, 4, 4 };
+
+class MeshSampleTest : public ::testing::Test
+{
+public:
+ static const unsigned int m_seed;
+ static const int m_numsamples;
+
+ std::string get_testname() const;
+
+ void load_mesh(const float (*verts)[3], int numverts,
+ const int (*edges)[2], int numedges,
+ const int *faces, const int *face_lengths, int numfaces);
+ void load_mesh(const char *filename);
+ void unload_mesh();
+
+ void generate_samples_simple(struct MeshSampleGenerator *gen);
+ void generate_samples_batch(struct MeshSampleGenerator *gen);
+ void generate_samples_batch_threaded(struct MeshSampleGenerator *gen);
+ void compare_samples(const struct MeshSample *ground_truth);
+ void test_samples(struct MeshSampleGenerator *gen, const struct MeshSample *ground_truth, int count);
+
+ void dump_samples();
+
+protected:
+ void SetUp() override;
+ void TearDown() override;
+
+protected:
+ Mesh *m_mesh;
+
+ MeshSample *m_samples;
+};
+
+const unsigned int MeshSampleTest::m_seed = 8343;
+const int MeshSampleTest::m_numsamples = 100000;
+
+std::string MeshSampleTest::get_testname() const
+{
+ std::stringstream testname;
+ testname << ::testing::UnitTest::GetInstance()->current_test_info()->name();
+ return testname.str();
+}
+
+void MeshSampleTest::load_mesh(const float (*verts)[3], int numverts,
+ const int (*edges)[2], int numedges,
+ const int *faces, const int *face_lengths, int numfaces)
+{
+ m_mesh = BKE_mesh_test_from_data(verts, numverts, edges, numedges, faces, face_lengths, numfaces);
+}
+
+void MeshSampleTest::load_mesh(const char *filename)
+{
+ const char *folder = BKE_appdir_folder_id(BLENDER_DATAFILES, "tests");
+ char path[FILE_MAX];
+
+ BLI_make_file_string(G.main->name, path, folder, filename);
+
+ if (path[0]) {
+ m_mesh = BKE_mesh_test_from_csv(path);
+ }
+}
+
+void MeshSampleTest::unload_mesh()
+{
+ if (m_mesh) {
+ BKE_mesh_free(m_mesh);
+ MEM_freeN(m_mesh);
+ m_mesh = NULL;
+ }
+}
+
+void MeshSampleTest::SetUp()
+{
+ load_mesh("suzanne.csv");
+ if (!m_mesh) {
+ int numverts = ARRAY_SIZE(verts);
+ int numfaces = ARRAY_SIZE(face_lengths);
+ load_mesh(verts, numverts, NULL, 0, faces, face_lengths, numfaces);
+ }
+
+ m_samples = (MeshSample *)MEM_mallocN(sizeof(MeshSample) * m_numsamples, "mesh samples");
+}
+
+void MeshSampleTest::TearDown()
+{
+ if (m_samples) {
+ MEM_freeN(m_samples);
+ m_samples = NULL;
+ }
+
+ unload_mesh();
+}
+
+
+void MeshSampleTest::dump_samples()
+{
+#ifdef TEST_MESH_OUTPUT_FILE
+ int numverts = m_mesh->totvert;
+
+ int dbg_numverts = numverts + m_numsamples;
+ float (*dbg_verts)[3] = (float (*)[3])MEM_mallocN(sizeof(float[3]) * dbg_numverts, "vertices");
+ for (int i = 0; i < numverts; ++i) {
+ copy_v3_v3(dbg_verts[i], m_mesh->mvert[i].co);
+ }
+ for (int i = 0; i < m_numsamples; ++i) {
+ float nor[3], tang[3];
+ BKE_mesh_sample_eval(m_mesh, &m_samples[i], dbg_verts[numverts + i], nor, tang);
+ }
+ int *dbg_faces = (int *)MEM_mallocN(sizeof(int) * m_mesh->totloop, "faces");
+ int *dbg_face_lengths = (int *)MEM_mallocN(sizeof(int) * m_mesh->totpoly, "face_lengths");
+ int loopstart = 0;
+ for (int i = 0; i < m_mesh->totpoly; ++i) {
+ const MPoly *mp = &m_mesh->mpoly[i];
+ dbg_face_lengths[i] = mp->totloop;
+ for (int k = 0; k < mp->totloop; ++k) {
+ dbg_faces[loopstart + k] = m_mesh->mloop[mp->loopstart + k].v;
+ }
+ loopstart += mp->totloop;
+ }
+ Mesh *dbg_mesh = BKE_mesh_test_from_data(dbg_verts, dbg_numverts, NULL, 0, dbg_faces, dbg_face_lengths, m_mesh->totpoly);
+ MEM_freeN(dbg_verts);
+ MEM_freeN(dbg_faces);
+ MEM_freeN(dbg_face_lengths);
+
+ std::stringstream filename;
+ filename << TEST_MESH_OUTPUT_FILE << get_testname() << ".py";
+ std::fstream s(filename.str(), s.trunc | s.out);
+
+ BKE_mesh_test_dump_mesh(dbg_mesh, get_testname().c_str(), s);
+
+ BKE_mesh_free(dbg_mesh);
+ MEM_freeN(dbg_mesh);
+#endif
+}
+
+void MeshSampleTest::compare_samples(const MeshSample *ground_truth)
+{
+ for (int i = 0; i < m_numsamples; ++i) {
+ EXPECT_EQ(ground_truth[i].orig_verts[0], m_samples[i].orig_verts[0]);
+ EXPECT_EQ(ground_truth[i].orig_verts[1], m_samples[i].orig_verts[1]);
+ EXPECT_EQ(ground_truth[i].orig_verts[2], m_samples[i].orig_verts[2]);
+
+ EXPECT_EQ(ground_truth[i].orig_weights[0], m_samples[i].orig_weights[0]);
+ EXPECT_EQ(ground_truth[i].orig_weights[1], m_samples[i].orig_weights[1]);
+ EXPECT_EQ(ground_truth[i].orig_weights[2], m_samples[i].orig_weights[2]);
+ }
+}
+
+void MeshSampleTest::generate_samples_simple(MeshSampleGenerator *gen)
+{
+ for (int i = 0; i < m_numsamples; ++i) {
+ BKE_mesh_sample_generate(gen, &m_samples[i]);
+ }
+}
+
+void MeshSampleTest::generate_samples_batch(MeshSampleGenerator *gen)
+{
+ BKE_mesh_sample_generate_batch_ex(gen, m_samples, sizeof(MeshSample), m_numsamples, false);
+}
+
+void MeshSampleTest::generate_samples_batch_threaded(MeshSampleGenerator *gen)
+{
+ BKE_mesh_sample_generate_batch_ex(gen, m_samples, sizeof(MeshSample), m_numsamples, true);
+}
+
+void MeshSampleTest::test_samples(MeshSampleGenerator *gen, const MeshSample *ground_truth, int count)
+{
+ BKE_mesh_sample_generator_bind(gen, m_mesh);
+
+ if (ground_truth) {
+ EXPECT_EQ(count, m_numsamples) << "Ground truth size does not match number of samples";
+ if (count != m_numsamples) {
+ return;
+ }
+
+ generate_samples_simple(gen);
+ compare_samples(ground_truth);
+ }
+ else {
+ generate_samples_simple(gen);
+ // Use simple sample generation as ground truth if not provided explicitly
+ ground_truth = m_samples;
+ }
+
+ generate_samples_batch(gen);
+ compare_samples(ground_truth);
+
+ generate_samples_batch_threaded(gen);
+ compare_samples(ground_truth);
+
+ BKE_mesh_sample_generator_unbind(gen);
+}
+
+TEST_F(MeshSampleTest, SurfaceVertices)
+{
+ MeshSampleGenerator *gen = BKE_mesh_sample_gen_surface_vertices();
+ ASSERT_TRUE(gen != NULL) << "No generator created";
+
+ test_samples(gen, NULL, 0);
+ dump_samples();
+
+ BKE_mesh_sample_free_generator(gen);
+}
+
+TEST_F(MeshSampleTest, SurfaceRandom)
+{
+ MeshSampleGenerator *gen = BKE_mesh_sample_gen_surface_random(m_seed, true, NULL, NULL);
+ ASSERT_TRUE(gen != NULL) << "No generator created";
+
+ test_samples(gen, NULL, 0);
+ dump_samples();
+
+ BKE_mesh_sample_free_generator(gen);
+}
+
+const float poisson_disk_mindist = 0.01f;
+
+TEST_F(MeshSampleTest, SurfacePoissonDisk)
+{
+ MeshSampleGenerator *gen = BKE_mesh_sample_gen_surface_poissondisk(m_seed, poisson_disk_mindist, 10000000, NULL, NULL);
+ ASSERT_TRUE(gen != NULL) << "No generator created";
+
+ test_samples(gen, NULL, 0);
+ dump_samples();
+
+ BKE_mesh_sample_free_generator(gen);
+}
+
+extern "C" {
+
+static const unsigned int raycast_seed = 85344;
+static const float raycast_radius = 100.0f;
+
+static void* raycast_thread_context_create(void *UNUSED(userdata), int start)
+{
+ RNG *rng = BLI_rng_new(raycast_seed);
+ BLI_rng_skip(rng, start * 2);
+ return rng;
+}
+
+static void raycast_thread_context_free(void *UNUSED(userdata), void *thread_ctx)
+{
+ BLI_rng_free((RNG *)thread_ctx);
+}
+
+static bool raycast_ray(void *UNUSED(userdata), void *thread_ctx, float ray_start[3], float ray_end[3])
+{
+ RNG *rng = (RNG *)thread_ctx;
+ float v[3];
+ {
+ float r;
+ v[2] = (2.0f * BLI_rng_get_float(rng)) - 1.0f;
+ float a = (float)(M_PI * 2.0) * BLI_rng_get_float(rng);
+ if ((r = 1.0f - (v[2] * v[2])) > 0.0f) {
+ r = sqrtf(r);
+ v[0] = r * cosf(a);
+ v[1] = r * sinf(a);
+ }
+ else {
+ v[2] = 1.0f;
+ }
+ }
+
+ mul_v3_fl(v, raycast_radius);
+ copy_v3_v3(ray_start, v);
+ negate_v3_v3(ray_end, v);
+ return true;
+}
+
+} /*extern "C"*/
+
+TEST_F(MeshSampleTest, SurfaceRaycast)
+{
+ MeshSampleGenerator *gen = BKE_mesh_sample_gen_surface_raycast(
+ raycast_thread_context_create, raycast_thread_context_free, raycast_ray, NULL);
+ ASSERT_TRUE(gen != NULL) << "No generator created";
+
+ test_samples(gen, NULL, 0);
+ dump_samples();
+
+ BKE_mesh_sample_free_generator(gen);
+}
+
+static const float volume_bbray_density = 0.1f;
+
+TEST_F(MeshSampleTest, VolumeBBRay)
+{
+ MeshSampleGenerator *gen = BKE_mesh_sample_gen_volume_random_bbray(m_seed, volume_bbray_density);
+ ASSERT_TRUE(gen != NULL) << "No generator created";
+
+ test_samples(gen, NULL, 0);
+ dump_samples();
+
+ BKE_mesh_sample_free_generator(gen);
+}
diff --git a/tests/gtests/blenkernel/BKE_mesh_test_util.cc b/tests/gtests/blenkernel/BKE_mesh_test_util.cc
new file mode 100644
index 00000000000..6dbed2c4533
--- /dev/null
+++ b/tests/gtests/blenkernel/BKE_mesh_test_util.cc
@@ -0,0 +1,319 @@
+/* Apache License, Version 2.0 */
+
+#include <fstream>
+#include <iostream>
+#include <iomanip>
+
+#include "BKE_mesh_test_util.h"
+
+extern "C" {
+#include "MEM_guardedalloc.h"
+
+#include "BLI_math.h"
+
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+
+#include "BKE_customdata.h"
+#include "BKE_mesh.h"
+}
+
+static void mesh_update(Mesh *mesh, int calc_edges, int calc_tessface)
+{
+ bool tessface_input = false;
+
+ if (mesh->totface > 0 && mesh->totpoly == 0) {
+ BKE_mesh_convert_mfaces_to_mpolys(mesh);
+
+ /* would only be converting back again, don't bother */
+ tessface_input = true;
+ }
+
+ if (calc_edges || ((mesh->totpoly || mesh->totface) && mesh->totedge == 0))
+ BKE_mesh_calc_edges(mesh, calc_edges, true);
+
+ if (calc_tessface) {
+ if (tessface_input == false) {
+ BKE_mesh_tessface_calc(mesh);
+ }
+ }
+ else {
+ /* default state is not to have tessface's so make sure this is the case */
+ BKE_mesh_tessface_clear(mesh);
+ }
+
+ BKE_mesh_calc_normals(mesh);
+}
+
+static void mesh_add_verts(Mesh *mesh, int len)
+{
+ CustomData vdata;
+
+ if (len == 0)
+ return;
+
+ int totvert = mesh->totvert + len;
+ CustomData_copy(&mesh->vdata, &vdata, CD_MASK_MESH, CD_DEFAULT, totvert);
+ CustomData_copy_data(&mesh->vdata, &vdata, 0, 0, mesh->totvert);
+
+ if (!CustomData_has_layer(&vdata, CD_MVERT))
+ CustomData_add_layer(&vdata, CD_MVERT, CD_CALLOC, NULL, totvert);
+
+ CustomData_free(&mesh->vdata, mesh->totvert);
+ mesh->vdata = vdata;
+ BKE_mesh_update_customdata_pointers(mesh, false);
+
+ /* scan the input list and insert the new vertices */
+
+ /* set final vertex list size */
+ mesh->totvert = totvert;
+}
+
+static void mesh_add_edges(Mesh *mesh, int len)
+{
+ CustomData edata;
+ MEdge *medge;
+ int i, totedge;
+
+ if (len == 0)
+ return;
+
+ totedge = mesh->totedge + len;
+
+ /* update customdata */
+ CustomData_copy(&mesh->edata, &edata, CD_MASK_MESH, CD_DEFAULT, totedge);
+ CustomData_copy_data(&mesh->edata, &edata, 0, 0, mesh->totedge);
+
+ if (!CustomData_has_layer(&edata, CD_MEDGE))
+ CustomData_add_layer(&edata, CD_MEDGE, CD_CALLOC, NULL, totedge);
+
+ CustomData_free(&mesh->edata, mesh->totedge);
+ mesh->edata = edata;
+ BKE_mesh_update_customdata_pointers(mesh, false); /* new edges don't change tessellation */
+
+ /* set default flags */
+ medge = &mesh->medge[mesh->totedge];
+ for (i = 0; i < len; i++, medge++)
+ medge->flag = ME_EDGEDRAW | ME_EDGERENDER;
+
+ mesh->totedge = totedge;
+}
+
+static void mesh_add_loops(Mesh *mesh, int len)
+{
+ CustomData ldata;
+ int totloop;
+
+ if (len == 0)
+ return;
+
+ totloop = mesh->totloop + len; /* new face count */
+
+ /* update customdata */
+ CustomData_copy(&mesh->ldata, &ldata, CD_MASK_MESH, CD_DEFAULT, totloop);
+ CustomData_copy_data(&mesh->ldata, &ldata, 0, 0, mesh->totloop);
+
+ if (!CustomData_has_layer(&ldata, CD_MLOOP))
+ CustomData_add_layer(&ldata, CD_MLOOP, CD_CALLOC, NULL, totloop);
+
+ CustomData_free(&mesh->ldata, mesh->totloop);
+ mesh->ldata = ldata;
+ BKE_mesh_update_customdata_pointers(mesh, true);
+
+ mesh->totloop = totloop;
+}
+
+static void mesh_add_polys(Mesh *mesh, int len)
+{
+ CustomData pdata;
+ MPoly *mpoly;
+ int i, totpoly;
+
+ if (len == 0)
+ return;
+
+ totpoly = mesh->totpoly + len; /* new face count */
+
+ /* update customdata */
+ CustomData_copy(&mesh->pdata, &pdata, CD_MASK_MESH, CD_DEFAULT, totpoly);
+ CustomData_copy_data(&mesh->pdata, &pdata, 0, 0, mesh->totpoly);
+
+ if (!CustomData_has_layer(&pdata, CD_MPOLY))
+ CustomData_add_layer(&pdata, CD_MPOLY, CD_CALLOC, NULL, totpoly);
+
+ CustomData_free(&mesh->pdata, mesh->totpoly);
+ mesh->pdata = pdata;
+ BKE_mesh_update_customdata_pointers(mesh, true);
+
+ /* set default flags */
+ mpoly = &mesh->mpoly[mesh->totpoly];
+ for (i = 0; i < len; i++, mpoly++)
+ mpoly->flag = ME_FACE_SEL;
+
+ mesh->totpoly = totpoly;
+}
+
+Mesh* BKE_mesh_test_from_data(
+ const float (*verts)[3], int numverts,
+ const int (*edges)[2], int numedges,
+ const int *loops, const int *face_lengths, int numfaces)
+{
+ Mesh *me = (Mesh *)MEM_callocN(sizeof(Mesh), "Mesh");
+
+ BKE_mesh_init(me);
+
+ int numloops = 0;
+ for (int i = 0; i < numfaces; ++i) {
+ numloops += face_lengths[i];
+ }
+
+ mesh_add_verts(me, numverts);
+ mesh_add_edges(me, numedges);
+ mesh_add_loops(me, numloops);
+ mesh_add_polys(me, numfaces);
+
+ {
+ MVert *v = me->mvert;
+ for (int i = 0; i < numverts; ++i, ++v) {
+ copy_v3_v3(v->co, verts[i]);
+ }
+ }
+
+ {
+ MEdge *e = me->medge;
+ for (int i = 0; i < numedges; ++i, ++e) {
+ e->v1 = edges[i][0];
+ e->v2 = edges[i][1];
+ }
+ }
+
+ {
+ MLoop *l = me->mloop;
+ for (int i = 0; i < numloops; ++i, ++l) {
+ l->v = loops[i];
+ }
+ }
+
+ {
+ MPoly *p = me->mpoly;
+ int loopstart = 0;
+ for (int i = 0; i < numfaces; ++i, ++p) {
+ int totloop = face_lengths[i];
+ p->loopstart = loopstart;
+ p->totloop = totloop;
+
+ loopstart += totloop;
+ }
+ }
+
+ if (numfaces > 0 && numedges == 0) {
+ mesh_update(me, true, false);
+ }
+
+ return me;
+}
+
+Mesh* BKE_mesh_test_from_csv(const char *filename)
+{
+ std::ifstream ifs (filename, std::ifstream::in);
+
+ char delim;
+
+ int numverts, numloops, numfaces;
+ float (*verts)[3] = NULL;
+ int *loops = NULL;
+ int *face_lengths = NULL;
+
+ ifs >> numverts;
+ ifs >> delim;
+ verts = (float (*)[3])MEM_mallocN(sizeof(float[3]) * numverts, "verts");
+ for (int i = 0; i < numverts; ++i) {
+ ifs >> verts[i][0];
+ ifs >> delim;
+ ifs >> verts[i][1];
+ ifs >> delim;
+ ifs >> verts[i][2];
+ ifs >> delim;
+ }
+
+ ifs >> numloops;
+ ifs >> delim;
+ loops = (int *)MEM_mallocN(sizeof(int) * numloops, "loops");
+ for (int i = 0; i < numloops; ++i) {
+ ifs >> loops[i];
+ ifs >> delim;
+ }
+
+ ifs >> numfaces;
+ ifs >> delim;
+ face_lengths = (int *)MEM_mallocN(sizeof(int) * numfaces, "face_lengths");
+ for (int i = 0; i < numfaces; ++i) {
+ ifs >> face_lengths[i];
+ ifs >> delim;
+ }
+
+ return BKE_mesh_test_from_data(verts, numverts, NULL, 0, loops, face_lengths, numfaces);
+}
+
+void BKE_mesh_test_dump_verts(Mesh *me, std::ostream &str)
+{
+ int numverts = me->totvert;
+
+ str << std::setprecision(5);
+
+ MVert *v = me->mvert;
+ str << "[";
+ for (int i = 0; i < numverts; ++i, ++v) {
+ str << "(" << v->co[0] << ", " << v->co[1] << ", " << v->co[2] << "), ";
+ }
+ str << "]";
+}
+
+void BKE_mesh_test_dump_edges(Mesh *me, std::ostream &str)
+{
+ int numedges = me->totedge;
+
+ MEdge *e = me->medge;
+ str << "[";
+ for (int i = 0; i < numedges; ++i, ++e) {
+ str << "(" << e->v1 << ", " << e->v2 << "), ";
+ }
+ str << "]";
+}
+
+void BKE_mesh_test_dump_faces(Mesh *me, std::ostream &str)
+{
+ int numpolys = me->totpoly;
+
+ MPoly *p = me->mpoly;
+ str << "[";
+ for (int i = 0; i < numpolys; ++i, ++p) {
+ int totloop = p->totloop;
+ MLoop *l = me->mloop + p->loopstart;
+
+ str << "(";
+ for (int k = 0; k < totloop; ++k, ++l) {
+ str << l->v << ", ";
+ }
+ str << "), ";
+ }
+ str << "]";
+}
+
+void BKE_mesh_test_dump_mesh(Mesh *me, const char *name, std::ostream &str)
+{
+ str << "import bpy\n";
+ str << "from bpy_extras.object_utils import object_data_add\n";
+ str << "mesh = bpy.data.meshes.new(name=\"" << name << "\")\n";
+
+ str << "mesh.from_pydata(";
+ str << "vertices=";
+ BKE_mesh_test_dump_verts(me, str);
+ str << ", edges=";
+ BKE_mesh_test_dump_edges(me, str);
+ str << ", faces=";
+ BKE_mesh_test_dump_faces(me, str);
+ str << ")\n";
+
+ str << "object_data_add(bpy.context, mesh)\n";
+}
diff --git a/tests/gtests/blenkernel/BKE_mesh_test_util.h b/tests/gtests/blenkernel/BKE_mesh_test_util.h
new file mode 100644
index 00000000000..6ab5f9b1236
--- /dev/null
+++ b/tests/gtests/blenkernel/BKE_mesh_test_util.h
@@ -0,0 +1,17 @@
+/* Apache License, Version 2.0 */
+
+#include <iosfwd>
+
+struct Mesh;
+
+struct Mesh* BKE_mesh_test_from_data(
+ const float (*verts)[3], int numverts,
+ const int (*edges)[2], int numedges,
+ const int *loops, const int *face_lengths, int numfaces);
+
+struct Mesh* BKE_mesh_test_from_csv(const char *filename);
+
+void BKE_mesh_test_dump_verts(struct Mesh *me, std::ostream &str);
+void BKE_mesh_test_dump_edges(struct Mesh *me, std::ostream &str);
+void BKE_mesh_test_dump_faces(struct Mesh *me, std::ostream &str);
+void BKE_mesh_test_dump_mesh(struct Mesh *me, const char *name, std::ostream &str);
diff --git a/tests/gtests/blenkernel/CMakeLists.txt b/tests/gtests/blenkernel/CMakeLists.txt
new file mode 100644
index 00000000000..f6b83fdc6b8
--- /dev/null
+++ b/tests/gtests/blenkernel/CMakeLists.txt
@@ -0,0 +1,57 @@
+# ***** 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.
+#
+# The Original Code is Copyright (C) 2014, Blender Foundation
+# All rights reserved.
+#
+# Contributor(s): Sergey Sharybin
+#
+# ***** END GPL LICENSE BLOCK *****
+
+set(INC
+ .
+ ..
+ ../../../source/blender/blenlib
+ ../../../source/blender/blenkernel
+ ../../../source/blender/makesdna
+ ../../../source/blender/depsgraph
+ ../../../intern/guardedalloc
+)
+
+include_directories(${INC})
+
+set(SRC
+ BKE_mesh_test_util.cc
+ BKE_mesh_sample_test.cc
+
+ BKE_mesh_test_util.h
+)
+
+setup_libdirs()
+get_property(BLENDER_SORTED_LIBS GLOBAL PROPERTY BLENDER_SORTED_LIBS_PROP)
+
+if(WITH_BUILDINFO)
+ set(_buildinfo_src "$<TARGET_OBJECTS:buildinfoobj>")
+else()
+ set(_buildinfo_src "")
+endif()
+
+# For motivation on doubling BLENDER_SORTED_LIBS, see ../bmesh/CMakeLists.txt
+BLENDER_SRC_GTEST(blenkernel "${SRC};${_buildinfo_src}" "${BLENDER_SORTED_LIBS};${BLENDER_SORTED_LIBS}")
+
+unset(_buildinfo_src)
+
+setup_liblinks(blenkernel_test)