diff options
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 = ½ + break; + } + case GPU_MAT_FAILED: + default: + color_p = error_col; + metal_p = spec_p = rough_p = ½ + 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 = ½ + break; + } + case GPU_MAT_FAILED: + default: + color_p = error_col; + metal_p = spec_p = rough_p = ½ + 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 = ½ - break; - } - case GPU_MAT_FAILED: - default: - color_p = error_col; - metal_p = spec_p = rough_p = ½ - 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) |