/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation * * SPDX-License-Identifier: Apache-2.0 */ #include #include "blender/attribute_convert.h" #include "blender/sync.h" #include "blender/util.h" #include "scene/attribute.h" #include "scene/camera.h" #include "scene/curves.h" #include "scene/hair.h" #include "scene/object.h" #include "scene/scene.h" #include "util/color.h" #include "util/hash.h" #include "util/log.h" #include "DNA_meshdata_types.h" #include "DNA_modifier_types.h" #include "DNA_particle_types.h" #include "BLI_listbase.h" #include "BLI_math_matrix.h" #include "BLI_math_vector.h" #include "BKE_attribute.hh" #include "BKE_curves.hh" #include "BKE_customdata.hh" #include "BKE_mesh_legacy_convert.hh" #include "BKE_particle.h" namespace blender { struct ModifierData; } CCL_NAMESPACE_BEGIN ParticleCurveData::ParticleCurveData() = default; ParticleCurveData::~ParticleCurveData() = default; static float shaperadius(const float shape, const float root, const float tip, const float time) { assert(time >= 0.0f); assert(time <= 1.0f); float radius = 1.0f - time; if (shape != 0.0f) { if (shape < 0.0f) { radius = powf(radius, 1.0f + shape); } else { radius = powf(radius, 1.0f / (1.0f - shape)); } } return (radius * (root - tip)) + tip; } /* curve functions */ static bool ObtainCacheParticleData(Hair *hair, const blender::Mesh *b_mesh, const blender::Object *b_ob, ParticleCurveData *CData, bool background) { int curvenum = 0; int keyno = 0; if (!(hair && b_mesh && b_ob && CData)) { return false; } const Transform tfm = get_transform(b_ob->object_to_world()); const Transform itfm = transform_inverse(tfm); for (const blender::ModifierData &b_mod : b_ob->modifiers) { if ((b_mod.type == blender::eModifierType_ParticleSystem) && (background ? ((b_mod.mode & blender::eModifierMode_Render) != 0) : ((b_mod.mode & blender::eModifierMode_Realtime) != 0))) { const auto *psmd = reinterpret_cast(&b_mod); blender::ParticleSystem *b_psys = psmd->psys; blender::ParticleSettings *b_part = b_psys->part; if ((b_part->ren_as == blender::PART_DRAW_PATH) && (b_part->type == blender::PART_HAIR)) { const int shader = clamp(b_part->omat - 1, 0, hair->get_used_shaders().size() - 1); const int display_step = background ? b_part->ren_step : b_part->draw_step; const int totparts = b_psys->totpart; const int totchild = background ? b_psys->totchild : (int)((float)b_psys->totchild * (float)b_part->disp / 100.0f); int totcurves = totchild; if (b_part->childtype == 0 || totchild == 0) { totcurves += totparts; } if (totcurves == 0) { continue; } int ren_step = (1 << display_step) + 1; if (b_part->kink == blender::PART_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); const float radius = b_part->rad_scale * 0.5f; CData->psys_rootradius.push_back_slow(radius * b_part->rad_root); CData->psys_tipradius.push_back_slow(radius * b_part->rad_tip); CData->psys_shape.push_back_slow(b_part->shape); CData->psys_closetip.push_back_slow((b_part->shape_flag & blender::PART_SHAPE_CLOSE_TIP) != 0); int pa_no = 0; if (!(b_part->childtype == 0) && totchild != 0) { pa_no = totparts; } const 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 prev_co_world = zero_float3(); float3 prev_co_object = zero_float3(); for (int step_no = 0; step_no < ren_step; step_no++) { float3 co_world = prev_co_world; BKE_particle_co_hair(b_psys, b_ob, pa_no, step_no, &co_world.x); const float3 co_object = transform_point(&itfm, co_world); if (step_no > 0) { const float step_length = len(co_object - prev_co_object); curve_length += step_length; } CData->curvekey_co.push_back_slow(co_object); CData->curvekey_time.push_back_slow(curve_length); prev_co_object = co_object; prev_co_world = co_world; keynum++; } keyno += keynum; CData->curve_keynum.push_back_slow(keynum); CData->curve_length.push_back_slow(curve_length); curvenum++; } } } } return true; } static bool ObtainCacheParticleUV(Hair *hair, const blender::Mesh *b_mesh, const blender::Object *b_ob, ParticleCurveData *CData, bool background, const int uv_num) { if (!(hair && b_mesh && b_ob && CData)) { return false; } CData->curve_uv.clear(); for (blender::ModifierData &b_mod : b_ob->modifiers) { if ((b_mod.type == blender::eModifierType_ParticleSystem) && (background ? ((b_mod.mode & blender::eModifierMode_Render) != 0) : ((b_mod.mode & blender::eModifierMode_Realtime) != 0))) { auto *psmd = reinterpret_cast(&b_mod); blender::ParticleSystem *b_psys = psmd->psys; blender::ParticleSettings *b_part = b_psys->part; if ((b_part->ren_as == blender::PART_DRAW_PATH) && (b_part->type == blender::PART_HAIR)) { const int totparts = b_psys->totpart; const int totchild = background ? b_psys->totchild : (int)((float)b_psys->totchild * (float)b_part->disp / 100.0f); int totcurves = totchild; if (b_part->childtype == 0 || totchild == 0) { totcurves += totparts; } if (totcurves == 0) { continue; } int pa_no = 0; if (!(b_part->childtype == 0) && totchild != 0) { pa_no = totparts; } const int num_add = (totparts + totchild - pa_no); CData->curve_uv.reserve(CData->curve_uv.size() + num_add); blender::MutableSpan b_psys_particles(b_psys->particles, b_psys->totpart); auto *b_pa = b_psys_particles.begin(); for (; pa_no < totparts + totchild; pa_no++) { /* Add UVs */ float2 uv = zero_float2(); BKE_particle_uv_on_emitter(b_psys, nullptr, 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; } } } } } return true; } static bool ObtainCacheParticleVcol(Hair *hair, const blender::Mesh *b_mesh, const blender::Object *b_ob, ParticleCurveData *CData, bool background, const int vcol_num) { if (!(hair && b_mesh && b_ob && CData)) { return false; } CData->curve_vcol.clear(); for (blender::ModifierData &b_mod : b_ob->modifiers) { if ((b_mod.type == blender::eModifierType_ParticleSystem) && (background ? ((b_mod.mode & blender::eModifierMode_Render) != 0) : ((b_mod.mode & blender::eModifierMode_Realtime) != 0))) { auto *psmd = reinterpret_cast(&b_mod); blender::ParticleSystem *b_psys = psmd->psys; blender::ParticleSettings *b_part = b_psys->part; if ((b_part->ren_as == blender::PART_DRAW_PATH) && (b_part->type == blender::PART_HAIR)) { const int totparts = b_psys->totpart; const int totchild = background ? b_psys->totchild : (int)((float)b_psys->totchild * (float)b_part->disp / 100.0f); int totcurves = totchild; if (b_part->childtype == 0 || totchild == 0) { totcurves += totparts; } if (totcurves == 0) { continue; } int pa_no = 0; if (!(b_part->childtype == 0) && totchild != 0) { pa_no = totparts; } const int num_add = (totparts + totchild - pa_no); CData->curve_vcol.reserve(CData->curve_vcol.size() + num_add); blender::MutableSpan b_psys_particles(b_psys->particles, b_psys->totpart); auto *b_pa = b_psys_particles.begin(); for (; pa_no < totparts + totchild; pa_no++) { /* Add vertex colors */ float4 vcol = make_float4(0.0f, 0.0f, 0.0f, 1.0f); BKE_particle_mcol_on_emitter(b_psys, nullptr, 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; } } } } } return true; } static void ExportCurveSegments(Scene *scene, Hair *hair, ParticleCurveData *CData) { int num_keys = 0; int num_curves = 0; if (hair->num_curves()) { return; } /* compute and reserve size of arrays */ for (int sys = 0; sys < CData->psys_firstcurve.size(); sys++) { for (int curve = CData->psys_firstcurve[sys]; curve < CData->psys_firstcurve[sys] + CData->psys_curvenum[sys]; curve++) { num_keys += CData->curve_keynum[curve]; num_curves++; } } hair->resize_curves(hair->num_curves() + num_curves, hair->num_keys() + num_keys); packed_normal *attr_normal = nullptr; float *attr_intercept = nullptr; float *attr_length = nullptr; float *attr_random = nullptr; if (hair->need_attribute(scene, ATTR_STD_VERTEX_NORMAL)) { attr_normal = hair->attributes.add(ATTR_STD_VERTEX_NORMAL)->data_for_write(); } if (hair->need_attribute(scene, ATTR_STD_CURVE_INTERCEPT)) { attr_intercept = hair->attributes.add(ATTR_STD_CURVE_INTERCEPT)->data_for_write(); } if (hair->need_attribute(scene, ATTR_STD_CURVE_LENGTH)) { attr_length = hair->attributes.add(ATTR_STD_CURVE_LENGTH)->data_for_write(); } if (hair->need_attribute(scene, ATTR_STD_CURVE_RANDOM)) { attr_random = hair->attributes.add(ATTR_STD_CURVE_RANDOM)->data_for_write(); } int *curve_first_key = hair->get_curve_first_key().data(); int *curve_shader = hair->get_curve_shader().data(); packed_float3 *curve_keys = hair->get_position_for_write(); float *curve_radius = hair->get_radius_for_write(); num_keys = 0; num_curves = 0; /* actually export */ for (int sys = 0; sys < CData->psys_firstcurve.size(); sys++) { for (int curve = CData->psys_firstcurve[sys]; curve < CData->psys_firstcurve[sys] + CData->psys_curvenum[sys]; curve++) { curve_first_key[num_curves] = num_keys; for (int curvekey = CData->curve_firstkey[curve]; curvekey < CData->curve_firstkey[curve] + CData->curve_keynum[curve]; curvekey++) { const float3 ickey_loc = CData->curvekey_co[curvekey]; const float curve_time = CData->curvekey_time[curvekey]; const float curve_length = CData->curve_length[curve]; const float time = (curve_length > 0.0f) ? curve_time / curve_length : 0.0f; float radius = shaperadius( CData->psys_shape[sys], CData->psys_rootradius[sys], CData->psys_tipradius[sys], time); if (CData->psys_closetip[sys] && (curvekey == CData->curve_firstkey[curve] + CData->curve_keynum[curve] - 1)) { radius = 0.0f; } curve_keys[num_keys] = ickey_loc; curve_radius[num_keys] = radius; if (attr_intercept) { attr_intercept[num_keys] = time; } if (attr_normal) { /* NOTE: the geometry normals are not computed for legacy particle hairs. This hair * system is expected to be deprecated. */ attr_normal[num_keys] = packed_normal(make_float3(0.0f, 0.0f, 0.0f)); } num_keys++; } if (attr_length != nullptr) { attr_length[num_curves] = CData->curve_length[curve]; } if (attr_random != nullptr) { attr_random[num_curves] = hash_uint2_to_float(num_curves, 0); } curve_shader[num_curves] = CData->psys_shader[sys]; num_curves++; } } hair->tag_position_modified(); hair->tag_radius_modified(); hair->tag_curve_first_key_modified(); hair->tag_curve_shader_modified(); /* check allocation */ if ((hair->num_keys() != num_keys) || (hair->num_curves() != num_curves)) { LOG_ERROR << "Hair memory allocation failed, clearing data."; hair->clear(true); } } static float4 CurveSegmentMotionCV(ParticleCurveData *CData, const int sys, const int curve, const int curvekey) { const float3 ickey_loc = CData->curvekey_co[curvekey]; const float curve_time = CData->curvekey_time[curvekey]; const float curve_length = CData->curve_length[curve]; const float time = (curve_length > 0.0f) ? curve_time / curve_length : 0.0f; float radius = shaperadius( CData->psys_shape[sys], CData->psys_rootradius[sys], CData->psys_tipradius[sys], time); if (CData->psys_closetip[sys] && (curvekey == CData->curve_firstkey[curve] + CData->curve_keynum[curve] - 1)) { radius = 0.0f; } /* curve motion keys store both position and radius in float4 */ float4 mP = make_float4(ickey_loc); mP.w = radius; return mP; } static float4 LerpCurveSegmentMotionCV(ParticleCurveData *CData, const int sys, const int curve, const float step) { assert(step >= 0.0f); assert(step <= 1.0f); const int first_curve_key = CData->curve_firstkey[curve]; const float curve_key_f = step * (CData->curve_keynum[curve] - 1); int curvekey = (int)floorf(curve_key_f); const float remainder = curve_key_f - curvekey; if (remainder == 0.0f) { return CurveSegmentMotionCV(CData, sys, curve, first_curve_key + curvekey); } int curvekey2 = curvekey + 1; if (curvekey2 >= (CData->curve_keynum[curve] - 1)) { curvekey2 = (CData->curve_keynum[curve] - 1); curvekey = curvekey2 - 1; } const float4 mP = CurveSegmentMotionCV(CData, sys, curve, first_curve_key + curvekey); const float4 mP2 = CurveSegmentMotionCV(CData, sys, curve, first_curve_key + curvekey2); return mix(mP, mP2, remainder); } static void export_hair_motion_validate_attribute(Hair *hair, const int motion_step, const int num_motion_keys, bool have_motion) { Attribute *attr_P = hair->attributes.find(ATTR_STD_POSITION); Attribute *attr_R = hair->attributes.find(ATTR_STD_RADIUS); const int num_keys = hair->num_keys(); if (num_motion_keys != num_keys || !have_motion) { /* No motion or hair "topology" changed, remove motion steps. */ if (num_motion_keys != num_keys) { LOG_DEBUG << "Hair topology changed, removing motion attribute."; } attr_P->remove_motion(); attr_R->remove_motion(); } else if (motion_step > 0) { /* Motion, fill up previous steps that we might have skipped because * they had no motion, but we need them anyway now. */ for (int step = 1; step <= motion_step; step++) { packed_float3 *mP = attr_P->data_for_write(step); std::copy_n(hair->get_position(), num_keys, mP); float *mR = attr_R->data_for_write(step); std::copy_n(hair->get_radius(), num_keys, mR); } } } static void ExportCurveSegmentsMotion(Hair *hair, ParticleCurveData *CData, const int motion_step) { /* Set motion steps on position and radius attributes. */ Attribute *attr_P = hair->attributes.find(ATTR_STD_POSITION); Attribute *attr_R = hair->attributes.find(ATTR_STD_RADIUS); bool new_attribute = false; if (!attr_P->has_motion()) { attr_P->add_motion(hair); attr_R->add_motion(hair); new_attribute = true; } /* export motion vectors for curve keys */ const int attr_step = motion_step + 1; packed_float3 *mP = attr_P->data_for_write(attr_step); float *mR = attr_R->data_for_write(attr_step); bool have_motion = false; int i = 0; int num_curves = 0; const int num_keys = hair->num_keys(); for (int sys = 0; sys < CData->psys_firstcurve.size(); sys++) { for (int curve = CData->psys_firstcurve[sys]; curve < CData->psys_firstcurve[sys] + CData->psys_curvenum[sys]; curve++) { /* Curve lengths may not match! Curves can be clipped. */ const int curve_key_end = (num_curves + 1 < (int)hair->get_curve_first_key().size() ? hair->get_curve_first_key()[num_curves + 1] : num_keys); const int num_center_curve_keys = curve_key_end - hair->get_curve_first_key()[num_curves]; const int is_num_keys_different = CData->curve_keynum[curve] - num_center_curve_keys; if (!is_num_keys_different) { for (int curvekey = CData->curve_firstkey[curve]; curvekey < CData->curve_firstkey[curve] + CData->curve_keynum[curve]; curvekey++) { if (i < num_keys) { const float4 cv = CurveSegmentMotionCV(CData, sys, curve, curvekey); mP[i] = make_float3(cv); mR[i] = cv.w; if (!have_motion) { /* unlike mesh coordinates, these tend to be slightly different * between frames due to particle transforms into/out of object * space, so we use an epsilon to detect actual changes */ float4 curve_key = make_float4(hair->get_position()[i]); curve_key.w = hair->get_radius()[i]; if (len_squared(cv - curve_key) > 1e-5f * 1e-5f) { have_motion = true; } } } i++; } } else { /* Number of keys has changed. Generate an interpolated version * to preserve motion blur. */ const float step_size = num_center_curve_keys > 1 ? 1.0f / (num_center_curve_keys - 1) : 0.0f; for (int step_index = 0; step_index < num_center_curve_keys; ++step_index) { const float step = step_index * step_size; const float4 cv = LerpCurveSegmentMotionCV(CData, sys, curve, step); mP[i] = make_float3(cv); mR[i] = cv.w; i++; } have_motion = true; } num_curves++; } } /* In case of new attribute, we verify if there really was any motion. */ if (new_attribute) { export_hair_motion_validate_attribute(hair, motion_step, i, have_motion); } } /* Hair Curve Sync */ bool BlenderSync::object_has_particle_hair(blender::Object *b_ob) { /* Test if the object has a particle modifier with hair. */ for (blender::ModifierData &b_mod : b_ob->modifiers) { if ((b_mod.type == blender::eModifierType_ParticleSystem) && (preview ? ((b_mod.mode & blender::eModifierMode_Realtime) != 0) : ((b_mod.mode & blender::eModifierMode_Render) != 0))) { auto *psmd = reinterpret_cast(&b_mod); blender::ParticleSystem *b_psys = psmd->psys; blender::ParticleSettings *b_part = b_psys->part; if ((b_part->ren_as == blender::PART_DRAW_PATH) && (b_part->type == blender::PART_HAIR)) { return true; } } } return false; } /* Old particle hair. */ void BlenderSync::sync_particle_hair(Hair *hair, const blender::Mesh &b_mesh, BObjectInfo &b_ob_info, bool motion, const int motion_step) { if (!b_ob_info.is_real_object_data()) { return; } const blender::Object &b_ob = *b_ob_info.real_object; /* obtain general settings */ if (b_ob.mode == blender::OB_MODE_PARTICLE_EDIT || b_ob.mode == blender::OB_MODE_EDIT) { return; } /* Extract particle hair data - should be combined with connecting to mesh later. */ ParticleCurveData CData; ObtainCacheParticleData(hair, &b_mesh, &b_ob, &CData, !preview); /* add hair geometry */ if (motion) { ExportCurveSegmentsMotion(hair, &CData, motion_step); } else { ExportCurveSegments(scene, hair, &CData); } /* generated coordinates from first key. we should ideally get this from * blender to handle deforming objects */ if (!motion) { if (hair->need_attribute(scene, ATTR_STD_GENERATED)) { float3 loc; float3 size; mesh_texture_space(b_mesh, loc, size); Attribute *attr_generated = hair->attributes.add(ATTR_STD_GENERATED); packed_float3 *generated = attr_generated->data_for_write(); for (size_t i = 0; i < hair->num_curves(); i++) { const float3 co = hair->get_position()[hair->get_curve(i).first_key]; generated[i] = co * size - loc; } } } /* create vertex color attributes */ if (!motion) { blender::Vector vcol_names; b_mesh.attributes().foreach_attribute([&](const blender::bke::AttributeIter &iter) { if (iter.data_type != blender::bke::AttrType::ColorByte) { return; } if (iter.domain != blender::bke::AttrDomain::Corner) { return; } vcol_names.append(iter.name); }); for (const int vcol_num : vcol_names.index_range()) { const ustring name = ustring(std::string_view(vcol_names[vcol_num])); if (!hair->need_attribute(scene, name)) { continue; } ObtainCacheParticleVcol(hair, &b_mesh, &b_ob, &CData, !preview, vcol_num); Attribute *attr_vcol = hair->attributes.add(name, TypeRGBA, ATTR_ELEMENT_CURVE); float4 *fdata = attr_vcol->data_for_write(); if (fdata) { size_t i = 0; /* Encode vertex color using the sRGB curve. */ for (size_t curve = 0; curve < CData.curve_vcol.size(); curve++) { fdata[i++] = color_srgb_to_linear_v4(CData.curve_vcol[curve]); } } } } /* create UV attributes */ if (!motion) { const blender::VectorSet uv_names = b_mesh.uv_map_names(); const ustring default_name = ustring(std::string_view(b_mesh.default_uv_map_name())); for (const int uv_num : uv_names.index_range()) { const ustring name = ustring(std::string_view(uv_names[uv_num])); const bool active_render = name == default_name; const AttributeStandard std = (active_render) ? ATTR_STD_UV : ATTR_STD_NONE; /* UV map */ if (hair->need_attribute(scene, name) || hair->need_attribute(scene, std)) { Attribute *attr_uv; ObtainCacheParticleUV(hair, &b_mesh, &b_ob, &CData, !preview, uv_num); if (active_render) { attr_uv = hair->attributes.add(std, name); } else { attr_uv = hair->attributes.add(name, TypeFloat2, ATTR_ELEMENT_CURVE); } float2 *uv = attr_uv->data_for_write(); if (uv) { size_t i = 0; for (size_t curve = 0; curve < CData.curve_uv.size(); curve++) { uv[i++] = CData.curve_uv[curve]; } } } } } hair->curve_shape = scene->params.hair_shape; } static void attr_create_motion_from_velocity(Hair *hair, const blender::Span src, const float motion_scale) { const int num_curve_keys = hair->num_keys(); /* Override motion steps to fixed number. */ hair->set_motion_steps(3); /* Set motion steps on position attribute. Radius doesn't change for * velocity-based motion. */ Attribute *attr_P = hair->attributes.find(ATTR_STD_POSITION); attr_P->add_motion(hair); const packed_float3 *P = hair->get_position(); /* Only export previous and next frame, we don't have any in between data. */ const float motion_times[2] = {-1.0f, 1.0f}; for (int step = 1; step <= 2; step++) { const float relative_time = motion_times[step - 1] * 0.5f * motion_scale; packed_float3 *mP = attr_P->data_for_write(step); for (int i = 0; i < num_curve_keys; i++) { mP[i] = float3(P[i]) + make_float3(src[i][0], src[i][1], src[i][2]) * relative_time; } } } static AttributeElement blender_domain_to_attr_element(const blender::bke::AttrDomain b_domain) { switch (b_domain) { case blender::bke::AttrDomain::Point: return ATTR_ELEMENT_CURVE_KEY; break; case blender::bke::AttrDomain::Curve: return ATTR_ELEMENT_CURVE; break; default: assert(false); return ATTR_ELEMENT_NONE; } } static void attr_create_generic(Scene *scene, Hair *hair, const blender::bke::CurvesGeometry &b_curves, const bool need_motion, const float motion_scale) { const blender::bke::AttributeAccessor b_attributes = b_curves.attributes(); AttributeSet &attributes = hair->attributes; static const ustring u_velocity("velocity"); const bool need_uv = hair->need_attribute(scene, ATTR_STD_UV); bool have_uv = false; b_attributes.foreach_attribute([&](const blender::bke::AttributeIter &iter) { const ustring name{std::string_view(iter.name)}; const blender::bke::AttrDomain b_domain = iter.domain; const blender::bke::AttrType b_data_type = iter.data_type; if (need_motion && name == u_velocity) { const blender::VArraySpan b_attr = *iter.get( blender::bke::AttrDomain::Point); attr_create_motion_from_velocity(hair, b_attr, motion_scale); return; } /* Weak, use first float2 attribute as standard UV. */ if (need_uv && !have_uv && b_data_type == blender::bke::AttrType::Float2 && b_domain == blender::bke::AttrDomain::Curve) { Attribute *attr = attributes.add(ATTR_STD_UV, name); const blender::VArraySpan b_attr = *iter.get(); static_assert(sizeof(blender::float2) == sizeof(float2)); const blender::Span src = b_attr.cast(); std::copy(src.begin(), src.end(), attr->data_for_write()); have_uv = true; return; } if (!hair->need_attribute(scene, name)) { return; } if (attributes.find(name)) { return; } const blender::bke::GAttributeReader b_attr = iter.get(); blender::bke::attribute_math::to_static_type(b_attr.varray.type(), [&]() { using Converter = typename ccl::AttributeConverter; using CyclesT = typename Converter::CyclesT; if constexpr (!std::is_void_v) { const blender::VArray src_varray = b_attr.varray.typed(); const blender::CommonVArrayInfo info = b_attr.varray.common_info(); if (info.type == blender::CommonVArrayInfo::Type::Single) { const auto &single_value = *static_cast(info.data); Attribute *attr = attributes.add(name, Converter::type_desc, ATTR_ELEMENT_MESH); CyclesT *data = reinterpret_cast(attr->data_for_write()); *data = Converter::convert(single_value); return; } const AttributeElement element = blender_domain_to_attr_element(b_attr.domain); if constexpr (Converter::layout_compatible) { if (src_varray.is_span() && b_attr.sharing_info) { attributes.add_shared(name, Converter::type_desc, element, info.data, src_varray.size(), b_attr.sharing_info); return; } } Attribute *attr = attributes.add(name, Converter::type_desc, element); CyclesT *data = reinterpret_cast(attr->data_for_write()); const blender::VArraySpan src = src_varray; for (const int i : src.index_range()) { data[i] = Converter::convert(src[i]); } } }); }); } static float4 curve_point_as_float4(const blender::Span b_positions, const blender::Span b_radius, const int index) { float4 mP = make_float4( b_positions[index][0], b_positions[index][1], b_positions[index][2], 0.0f); mP.w = b_radius.is_empty() ? 0.005f : b_radius[index]; return mP; } static float4 interpolate_curve_points(const blender::Span b_positions, const blender::Span b_radius, const int first_point_index, const int num_points, const float step) { const float curve_t = step * (num_points - 1); const int point_a = clamp((int)curve_t, 0, num_points - 1); const int point_b = min(point_a + 1, num_points - 1); const float t = curve_t - (float)point_a; return mix(curve_point_as_float4(b_positions, b_radius, first_point_index + point_a), curve_point_as_float4(b_positions, b_radius, first_point_index + point_b), t); } static void export_hair_curves(Scene *scene, Hair *hair, const blender::bke::CurvesGeometry &b_curves, const bool need_motion, const float motion_scale) { const blender::Span positions = b_curves.positions(); const blender::OffsetIndices points_by_curve = b_curves.points_by_curve(); const blender::bke::AttributeAccessor b_attributes = b_curves.attributes(); hair->resize_curves(points_by_curve.size(), positions.size()); /* Sync positions, sharing with Blender when possible. */ sync_attribute_from_blender( hair->attributes, ATTR_STD_POSITION, b_attributes.lookup("position", blender::bke::AttrDomain::Point), positions.size()); hair->tag_position_modified(); /* Sync radius, sharing with Blender when possible, or filling default. */ if (sync_attribute_from_blender( hair->attributes, ATTR_STD_RADIUS, b_attributes.lookup("radius", blender::bke::AttrDomain::Point), positions.size())) { hair->tag_radius_modified(); } else { float *curve_radius = hair->get_radius_for_write(); std::fill(curve_radius, curve_radius + positions.size(), 0.005f); } int *curve_first_key = hair->get_curve_first_key().data(); int *curve_shader = hair->get_curve_shader().data(); /* Add requested attributes. */ float *attr_intercept = nullptr; float *attr_length = nullptr; if (hair->need_attribute(scene, ATTR_STD_VERTEX_NORMAL)) { /* Get geometry normals. */ packed_normal *attr_normal = hair->attributes.add(ATTR_STD_VERTEX_NORMAL)->data_for_write(); vector point_normals(positions.size()); blender::bke::curves_normals_point_domain_calc( b_curves, {point_normals.data(), int64_t(point_normals.size())}); for (const int i : positions.index_range()) { attr_normal[i] = packed_normal( make_float3(point_normals[i][0], point_normals[i][1], point_normals[i][2])); } } if (hair->need_attribute(scene, ATTR_STD_CURVE_INTERCEPT)) { attr_intercept = hair->attributes.add(ATTR_STD_CURVE_INTERCEPT)->data_for_write(); } if (hair->need_attribute(scene, ATTR_STD_CURVE_LENGTH)) { attr_length = hair->attributes.add(ATTR_STD_CURVE_LENGTH)->data_for_write(); } if (hair->need_attribute(scene, ATTR_STD_CURVE_RANDOM)) { float *attr_random = hair->attributes.add(ATTR_STD_CURVE_RANDOM)->data_for_write(); for (const int i : points_by_curve.index_range()) { attr_random[i] = hash_uint2_to_float(i, 0); } } std::copy(points_by_curve.data().data(), points_by_curve.data().data() + points_by_curve.size(), curve_first_key); std::fill(curve_shader, curve_shader + points_by_curve.size(), 0); /* Compute per-curve length and per-point intercept along the curve. */ if (attr_length || attr_intercept) { for (const int curve : points_by_curve.index_range()) { const blender::IndexRange points = points_by_curve[curve]; float3 prev_co = zero_float3(); float length = 0.0f; for (const int point : points) { const float3 co = make_float3( positions[point][0], positions[point][1], positions[point][2]); if (point != points.first()) { length += len(co - prev_co); } prev_co = co; if (attr_intercept) { attr_intercept[point] = length; } } /* Normalized 0..1 attribute along curve. */ if (attr_intercept && length > 0.0f) { for (const int point : points.drop_front(1)) { attr_intercept[point] /= length; } } /* Curve length. */ if (attr_length) { attr_length[curve] = length; } } } attr_create_generic(scene, hair, b_curves, need_motion, motion_scale); } static bool hair_topology_matches(const Hair *hair, const blender::bke::CurvesGeometry &b_curves) { const blender::OffsetIndices points_by_curve = b_curves.points_by_curve(); if (size_t(b_curves.points_num()) != hair->num_keys() || size_t(points_by_curve.size()) != hair->num_curves()) { return false; } for (const int i : points_by_curve.index_range()) { if (points_by_curve[i].size() != hair->get_curve(i).num_keys) { return false; } } return true; } static void export_hair_curves_motion(Hair *hair, const blender::bke::CurvesGeometry &b_curves, const int motion_step) { /* Set motion steps on position and radius attributes. */ Attribute *attr_P = hair->attributes.find(ATTR_STD_POSITION); Attribute *attr_R = hair->attributes.find(ATTR_STD_RADIUS); bool new_attribute = false; if (!attr_P->has_motion()) { attr_P->add_motion(hair); attr_R->add_motion(hair); new_attribute = true; } const size_t num_keys = hair->num_keys(); const int attr_step = motion_step + 1; const blender::bke::AttributeAccessor b_attributes = b_curves.attributes(); bool have_motion = false; int num_motion_keys = 0; if (hair_topology_matches(hair, b_curves)) { /* Fast path: topology unchanged, sync the whole step from Blender, sharing * the buffer when possible. */ sync_attribute_motion_step_from_blender( *attr_P, attr_step, b_attributes.lookup("position", blender::bke::AttrDomain::Point)); if (!sync_attribute_motion_step_from_blender( *attr_R, attr_step, b_attributes.lookup("radius", blender::bke::AttrDomain::Point))) { float *mR = attr_R->data_for_write(attr_step); std::fill(mR, mR + num_keys, 0.005f); } /* If a buffer is shared from Blender and unchanged across frames, the * pointer matches the center step's, so the memcmp is skipped. */ const packed_float3 *motion_P = attr_P->data(attr_step); const packed_float3 *center_P = hair->get_position(); const float *motion_R = attr_R->data(attr_step); const float *center_R = hair->get_radius(); have_motion = (motion_P != center_P && std::memcmp(motion_P, center_P, num_keys * sizeof(packed_float3)) != 0) || (motion_R != center_R && std::memcmp(motion_R, center_R, num_keys * sizeof(float)) != 0); num_motion_keys = num_keys; } else { /* Slow path: topology has changed, copy with per-curve interpolation. */ const size_t num_curves = hair->num_curves(); packed_float3 *mP = attr_P->data_for_write(attr_step); float *mR = attr_R->data_for_write(attr_step); int curve_index = 0; const blender::Span b_positions = b_curves.positions(); const blender::OffsetIndices points_by_curve = b_curves.points_by_curve(); const blender::VArraySpan b_radius = *b_attributes.lookup( "radius", blender::bke::AttrDomain::Point); for (const int i : points_by_curve.index_range()) { const blender::IndexRange points = points_by_curve[i]; if (curve_index >= num_curves) { break; } const Hair::Curve curve = hair->get_curve(curve_index); curve_index++; if (points.size() == curve.num_keys) { /* Number of keys matches. */ for (const int i : points.index_range()) { const int point = points[i]; if (num_motion_keys < num_keys) { const float4 cv = curve_point_as_float4(b_positions, b_radius, point); mP[num_motion_keys] = make_float3(cv); mR[num_motion_keys] = cv.w; if (!have_motion) { float4 curve_key = make_float4(hair->get_position()[num_motion_keys]); curve_key.w = hair->get_radius()[num_motion_keys]; have_motion = !(cv == curve_key); } num_motion_keys++; } } } else { /* Number of keys has changed. Generate an interpolated version * to preserve motion blur. */ const float step_size = curve.num_keys > 1 ? 1.0f / (curve.num_keys - 1) : 0.0f; for (int i = 0; i < curve.num_keys; i++) { const float step = i * step_size; const float4 cv = interpolate_curve_points( b_positions, b_radius, points.start(), points.size(), step); mP[num_motion_keys] = make_float3(cv); mR[num_motion_keys] = cv.w; num_motion_keys++; } have_motion = true; } } } /* In case of new attribute, verify if there really was any motion. */ if (new_attribute) { export_hair_motion_validate_attribute(hair, motion_step, num_motion_keys, have_motion); } } /* Hair object. */ void BlenderSync::sync_hair(Hair *hair, BObjectInfo &b_ob_info, bool motion, const int motion_step) { /* Motion blur attribute is relative to seconds, we need it relative to frames. */ const bool need_motion = object_need_motion_attribute(b_ob_info, scene); const float motion_scale = (need_motion) ? scene->motion_shutter_time() / (b_scene->r.frs_sec / b_scene->r.frs_sec_base) : 0.0f; /* Convert Blender hair to Cycles curves. */ const blender::bke::CurvesGeometry &b_curves( blender::id_cast(b_ob_info.object_data)->geometry.wrap()); if (motion) { export_hair_curves_motion(hair, b_curves, motion_step); } else { export_hair_curves(scene, hair, b_curves, need_motion, motion_scale); } const blender::VArray b_types = b_curves.curve_types(); /* This does not handle cases where the curve type is not the same across all curves */ if (!b_types.is_empty() && b_types[0] == blender::CURVE_TYPE_POLY) { hair->curve_shape = CURVE_THICK_LINEAR; } else { hair->curve_shape = scene->params.hair_shape; } } void BlenderSync::sync_hair(BObjectInfo &b_ob_info, Hair *hair) { /* make a copy of the shaders as the caller in the main thread still need them for syncing the * attributes */ array used_shaders = hair->get_used_shaders(); Hair new_hair; new_hair.set_used_shaders(used_shaders); if (view_layer.use_hair) { if (GS(b_ob_info.object_data->name) == blender::ID_CV) { /* Hair object. */ sync_hair(&new_hair, b_ob_info, false); } else { /* Particle hair. */ blender::Mesh *b_mesh = object_to_mesh(b_ob_info); if (b_mesh) { sync_particle_hair(&new_hair, *b_mesh, b_ob_info, false); free_object_to_mesh(b_ob_info, *b_mesh); } } if (scene->need_motion() == Scene::MOTION_PASS_INTERACTIVE && hair->num_keys() == new_hair.num_keys()) { new_hair.set_motion_steps(2); Attribute *attr_P = hair->attributes.find(ATTR_STD_POSITION); Attribute *new_attr_P = new_hair.attributes.find(ATTR_STD_POSITION); if (attr_P->has_motion()) { new_attr_P->take_motion_from(*attr_P); } else { new_attr_P->add_motion(&new_hair); new_hair.copy_center_to_motion_step(0); } } } /* update original sockets */ for (const SocketType &socket : new_hair.type->inputs) { /* Those sockets are updated in sync_object, so do not modify them. */ if (socket.name == "use_motion_blur" || socket.name == "used_shaders") { continue; } hair->set_value(socket, new_hair, socket); } hair->attributes.update(std::move(new_hair.attributes)); hair->curve_shape = new_hair.curve_shape; /* tag update */ /* Compares curve_keys rather than strands in order to handle quick hair * adjustments in dynamic BVH - other methods could probably do this better. */ const bool rebuild = (hair->position_is_modified() || hair->radius_is_modified()); hair->tag_update(scene, rebuild); } void BlenderSync::sync_hair_motion(BObjectInfo &b_ob_info, Hair *hair, const int motion_step) { /* Skip if nothing exported. */ if (hair->num_keys() == 0) { return; } /* Export deformed coordinates. */ if (ccl::BKE_object_is_deform_modified(b_ob_info, *b_scene, preview)) { if (GS(b_ob_info.object_data->name) == blender::ID_CV) { /* Hair object. */ sync_hair(hair, b_ob_info, true, motion_step); return; } /* Particle hair. */ blender::Mesh *b_mesh = object_to_mesh(b_ob_info); if (b_mesh) { sync_particle_hair(hair, *b_mesh, b_ob_info, true, motion_step); free_object_to_mesh(b_ob_info, *b_mesh); return; } } /* No deformation on this frame, copy coordinates if other frames did have it. */ hair->copy_center_to_motion_step(motion_step); } CCL_NAMESPACE_END