diff options
Diffstat (limited to 'intern/cycles/blender/curves.cpp')
-rw-r--r-- | intern/cycles/blender/curves.cpp | 915 |
1 files changed, 915 insertions, 0 deletions
diff --git a/intern/cycles/blender/curves.cpp b/intern/cycles/blender/curves.cpp new file mode 100644 index 00000000000..fb2b329e61d --- /dev/null +++ b/intern/cycles/blender/curves.cpp @@ -0,0 +1,915 @@ +/* + * Copyright 2011-2013 Blender Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#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/foreach.h" +#include "util/hash.h" +#include "util/log.h" + +CCL_NAMESPACE_BEGIN + +ParticleCurveData::ParticleCurveData() +{ +} + +ParticleCurveData::~ParticleCurveData() +{ +} + +static float shaperadius(float shape, float root, float tip, 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, BL::Mesh *b_mesh, BL::Object *b_ob, ParticleCurveData *CData, bool background) +{ + int curvenum = 0; + int keyno = 0; + + if (!(hair && b_mesh && b_ob && CData)) + return false; + + Transform tfm = get_transform(b_ob->matrix_world()); + Transform itfm = transform_quick_inverse(tfm); + + for (BL::Modifier &b_mod : b_ob->modifiers) { + 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, hair->get_used_shaders().size() - 1); + int display_step = background ? b_part.render_step() : b_part.display_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.display_percentage() / 100.0f); + int totcurves = totchild; + + if (b_part.child_type() == 0 || totchild == 0) + totcurves += totparts; + + if (totcurves == 0) + continue; + + int ren_step = (1 << display_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 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; + b_psys.co_hair(*b_ob, pa_no, step_no, &co_world.x); + 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, + BL::Mesh *b_mesh, + BL::Object *b_ob, + ParticleCurveData *CData, + bool background, + int uv_num) +{ + if (!(hair && b_mesh && b_ob && CData)) + return false; + + CData->curve_uv.clear(); + + for (BL::Modifier &b_mod : b_ob->modifiers) { + 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.display_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; + + 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::uv_layers_iterator l; + b_mesh->uv_layers.begin(l); + + float2 uv = zero_float2(); + if (b_mesh->uv_layers.length()) + b_psys.uv_on_emitter(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, + BL::Mesh *b_mesh, + BL::Object *b_ob, + ParticleCurveData *CData, + bool background, + int vcol_num) +{ + if (!(hair && b_mesh && b_ob && CData)) + return false; + + CData->curve_vcol.clear(); + + for (BL::Modifier &b_mod : b_ob->modifiers) { + 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.display_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; + + 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::vertex_colors_iterator l; + b_mesh->vertex_colors.begin(l); + + float4 vcol = make_float4(0.0f, 0.0f, 0.0f, 1.0f); + if (b_mesh->vertex_colors.length()) + b_psys.mcol_on_emitter(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; + + Attribute *attr_intercept = NULL; + Attribute *attr_length = NULL; + Attribute *attr_random = NULL; + + if (hair->need_attribute(scene, ATTR_STD_CURVE_INTERCEPT)) + attr_intercept = hair->attributes.add(ATTR_STD_CURVE_INTERCEPT); + if (hair->need_attribute(scene, ATTR_STD_CURVE_LENGTH)) + attr_length = hair->attributes.add(ATTR_STD_CURVE_LENGTH); + if (hair->need_attribute(scene, ATTR_STD_CURVE_RANDOM)) + attr_random = hair->attributes.add(ATTR_STD_CURVE_RANDOM); + + /* 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++; + } + } + + if (num_curves > 0) { + VLOG(1) << "Exporting curve segments for mesh " << hair->name; + } + + hair->reserve_curves(hair->num_curves() + num_curves, hair->get_curve_keys().size() + num_keys); + + 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++) { + size_t num_curve_keys = 0; + + 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; + } + hair->add_curve_key(ickey_loc, radius); + if (attr_intercept) + attr_intercept->add(time); + + num_curve_keys++; + } + + if (attr_length != NULL) { + attr_length->add(CData->curve_length[curve]); + } + + if (attr_random != NULL) { + attr_random->add(hash_uint2_to_float(num_curves, 0)); + } + + hair->add_curve(num_keys, CData->psys_shader[sys]); + num_keys += num_curve_keys; + num_curves++; + } + } + + /* check allocation */ + if ((hair->get_curve_keys().size() != num_keys) || (hair->num_curves() != num_curves)) { + VLOG(1) << "Allocation failed, clearing data"; + hair->clear(true); + } +} + +static float4 CurveSegmentMotionCV(ParticleCurveData *CData, int sys, int curve, 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]; + 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 = float3_to_float4(ickey_loc); + mP.w = radius; + return mP; +} + +static float4 LerpCurveSegmentMotionCV(ParticleCurveData *CData, int sys, int curve, 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 lerp(mP, mP2, remainder); +} + +static void export_hair_motion_validate_attribute(Hair *hair, + int motion_step, + int num_motion_keys, + bool have_motion) +{ + Attribute *attr_mP = hair->attributes.find(ATTR_STD_MOTION_VERTEX_POSITION); + const int num_keys = hair->get_curve_keys().size(); + + if (num_motion_keys != num_keys || !have_motion) { + /* No motion or hair "topology" changed, remove attributes again. */ + if (num_motion_keys != num_keys) { + VLOG(1) << "Hair topology changed, removing attribute."; + } + else { + VLOG(1) << "No motion, removing attribute."; + } + hair->attributes.remove(ATTR_STD_MOTION_VERTEX_POSITION); + } + else if (motion_step > 0) { + VLOG(1) << "Filling in new motion vertex position for motion_step " << motion_step; + + /* Motion, fill up previous steps that we might have skipped because + * they had no motion, but we need them anyway now. */ + for (int step = 0; step < motion_step; step++) { + float4 *mP = attr_mP->data_float4() + step * num_keys; + + for (int key = 0; key < num_keys; key++) { + mP[key] = float3_to_float4(hair->get_curve_keys()[key]); + mP[key].w = hair->get_curve_radius()[key]; + } + } + } +} + +static void ExportCurveSegmentsMotion(Hair *hair, ParticleCurveData *CData, int motion_step) +{ + VLOG(1) << "Exporting curve motion segments for hair " << hair->name << ", motion step " + << motion_step; + + /* find attribute */ + Attribute *attr_mP = hair->attributes.find(ATTR_STD_MOTION_VERTEX_POSITION); + bool new_attribute = false; + + /* add new attribute if it doesn't exist already */ + if (!attr_mP) { + VLOG(1) << "Creating new motion vertex position attribute"; + attr_mP = hair->attributes.add(ATTR_STD_MOTION_VERTEX_POSITION); + new_attribute = true; + } + + /* export motion vectors for curve keys */ + size_t numkeys = hair->get_curve_keys().size(); + float4 *mP = attr_mP->data_float4() + motion_step * numkeys; + bool have_motion = false; + int i = 0; + int num_curves = 0; + + 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. */ + int curve_key_end = (num_curves + 1 < (int)hair->get_curve_first_key().size() ? + hair->get_curve_first_key()[num_curves + 1] : + (int)hair->get_curve_keys().size()); + 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 < hair->get_curve_keys().size()) { + mP[i] = CurveSegmentMotionCV(CData, sys, curve, curvekey); + 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 = float3_to_float4(hair->get_curve_keys()[i]); + curve_key.w = hair->get_curve_radius()[i]; + if (len_squared(mP[i] - 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; + mP[i] = LerpCurveSegmentMotionCV(CData, sys, curve, step); + 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(BL::Object b_ob) +{ + /* Test if the object has a particle modifier with hair. */ + for (BL::Modifier &b_mod : b_ob.modifiers) { + if ((b_mod.type() == b_mod.type_PARTICLE_SYSTEM) && + (preview ? b_mod.show_viewport() : b_mod.show_render())) { + 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)) { + return true; + } + } + } + + return false; +} + +/* Old particle hair. */ +void BlenderSync::sync_particle_hair( + Hair *hair, BL::Mesh &b_mesh, BObjectInfo &b_ob_info, bool motion, int motion_step) +{ + if (!b_ob_info.is_real_object_data()) { + return; + } + BL::Object b_ob = b_ob_info.real_object; + + /* obtain general settings */ + if (b_ob.mode() == b_ob.mode_PARTICLE_EDIT || b_ob.mode() == b_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, size; + mesh_texture_space(b_mesh, loc, size); + + Attribute *attr_generated = hair->attributes.add(ATTR_STD_GENERATED); + float3 *generated = attr_generated->data_float3(); + + for (size_t i = 0; i < hair->num_curves(); i++) { + float3 co = hair->get_curve_keys()[hair->get_curve(i).first_key]; + generated[i] = co * size - loc; + } + } + } + + /* create vertex color attributes */ + if (!motion) { + BL::Mesh::vertex_colors_iterator l; + int vcol_num = 0; + + for (b_mesh.vertex_colors.begin(l); l != b_mesh.vertex_colors.end(); ++l, vcol_num++) { + if (!hair->need_attribute(scene, ustring(l->name().c_str()))) + continue; + + ObtainCacheParticleVcol(hair, &b_mesh, &b_ob, &CData, !preview, vcol_num); + + Attribute *attr_vcol = hair->attributes.add( + ustring(l->name().c_str()), TypeRGBA, ATTR_ELEMENT_CURVE); + + float4 *fdata = attr_vcol->data_float4(); + + 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) { + BL::Mesh::uv_layers_iterator l; + int uv_num = 0; + + for (b_mesh.uv_layers.begin(l); l != b_mesh.uv_layers.end(); ++l, uv_num++) { + bool active_render = l->active_render(); + AttributeStandard std = (active_render) ? ATTR_STD_UV : ATTR_STD_NONE; + ustring name = ustring(l->name().c_str()); + + /* 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_float2(); + + if (uv) { + size_t i = 0; + + for (size_t curve = 0; curve < CData.curve_uv.size(); curve++) { + uv[i++] = CData.curve_uv[curve]; + } + } + } + } + } +} + +#ifdef WITH_HAIR_NODES +static float4 hair_point_as_float4(BL::HairPoint b_point) +{ + float4 mP = float3_to_float4(get_float3(b_point.co())); + mP.w = b_point.radius(); + return mP; +} + +static float4 interpolate_hair_points(BL::Hair b_hair, + 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 lerp(hair_point_as_float4(b_hair.points[first_point_index + point_a]), + hair_point_as_float4(b_hair.points[first_point_index + point_b]), + t); +} + +static void export_hair_curves(Scene *scene, Hair *hair, BL::Hair b_hair) +{ + /* TODO: optimize so we can straight memcpy arrays from Blender? */ + + /* Add requested attributes. */ + Attribute *attr_intercept = NULL; + Attribute *attr_length = NULL; + Attribute *attr_random = NULL; + + if (hair->need_attribute(scene, ATTR_STD_CURVE_INTERCEPT)) { + attr_intercept = hair->attributes.add(ATTR_STD_CURVE_INTERCEPT); + } + if (hair->need_attribute(scene, ATTR_STD_CURVE_LENGTH)) { + attr_length = hair->attributes.add(ATTR_STD_CURVE_LENGTH); + } + if (hair->need_attribute(scene, ATTR_STD_CURVE_RANDOM)) { + attr_random = hair->attributes.add(ATTR_STD_CURVE_RANDOM); + } + + /* Reserve memory. */ + const int num_keys = b_hair.points.length(); + const int num_curves = b_hair.curves.length(); + + if (num_curves > 0) { + VLOG(1) << "Exporting curve segments for hair " << hair->name; + } + + hair->reserve_curves(num_curves, num_keys); + + /* Export curves and points. */ + vector<float> points_length; + + for (BL::HairCurve &b_curve : b_hair.curves) { + const int first_point_index = b_curve.first_point_index(); + const int num_points = b_curve.num_points(); + + float3 prev_co = zero_float3(); + float length = 0.0f; + if (attr_intercept) { + points_length.clear(); + points_length.reserve(num_points); + } + + /* Position and radius. */ + for (int i = 0; i < num_points; i++) { + BL::HairPoint b_point = b_hair.points[first_point_index + i]; + + const float3 co = get_float3(b_point.co()); + const float radius = b_point.radius(); + hair->add_curve_key(co, radius); + + if (attr_intercept) { + if (i > 0) { + length += len(co - prev_co); + points_length.push_back(length); + } + prev_co = co; + } + } + + /* Normalized 0..1 attribute along curve. */ + if (attr_intercept) { + for (int i = 0; i < num_points; i++) { + attr_intercept->add((length == 0.0f) ? 0.0f : points_length[i] / length); + } + } + + if (attr_length) { + attr_length->add(length); + } + + /* Random number per curve. */ + if (attr_random != NULL) { + attr_random->add(hash_uint2_to_float(b_curve.index(), 0)); + } + + /* Curve. */ + const int shader_index = 0; + hair->add_curve(first_point_index, shader_index); + } +} + +static void export_hair_curves_motion(Hair *hair, BL::Hair b_hair, int motion_step) +{ + VLOG(1) << "Exporting curve motion segments for hair " << hair->name << ", motion step " + << motion_step; + + /* Find or add attribute. */ + Attribute *attr_mP = hair->attributes.find(ATTR_STD_MOTION_VERTEX_POSITION); + bool new_attribute = false; + + if (!attr_mP) { + VLOG(1) << "Creating new motion vertex position attribute"; + attr_mP = hair->attributes.add(ATTR_STD_MOTION_VERTEX_POSITION); + new_attribute = true; + } + + /* Export motion keys. */ + const int num_keys = hair->get_curve_keys().size(); + float4 *mP = attr_mP->data_float4() + motion_step * num_keys; + bool have_motion = false; + int num_motion_keys = 0; + int curve_index = 0; + + for (BL::HairCurve &b_curve : b_hair.curves) { + const int first_point_index = b_curve.first_point_index(); + const int num_points = b_curve.num_points(); + + Hair::Curve curve = hair->get_curve(curve_index); + curve_index++; + + if (num_points == curve.num_keys) { + /* Number of keys matches. */ + for (int i = 0; i < num_points; i++) { + int point_index = first_point_index + i; + + if (point_index < num_keys) { + mP[num_motion_keys] = hair_point_as_float4(b_hair.points[point_index]); + num_motion_keys++; + + if (!have_motion) { + /* TODO: use epsilon for comparison? Was needed for particles due to + * transform, but ideally should not happen anymore. */ + float4 curve_key = float3_to_float4(hair->get_curve_keys()[i]); + curve_key.w = hair->get_curve_radius()[i]; + have_motion = !(mP[i] == curve_key); + } + } + } + } + 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; + mP[num_motion_keys] = interpolate_hair_points(b_hair, first_point_index, num_points, step); + num_motion_keys++; + } + have_motion = true; + } + } + + /* In case of new attribute, we 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, int motion_step) +{ + /* Convert Blender hair to Cycles curves. */ + BL::Hair b_hair(b_ob_info.object_data); + if (motion) { + export_hair_curves_motion(hair, b_hair, motion_step); + } + else { + export_hair_curves(scene, hair, b_hair); + } +} +#else +void BlenderSync::sync_hair(Hair *hair, BObjectInfo &b_ob_info, bool motion, int motion_step) +{ + (void)hair; + (void)b_ob_info; + (void)motion; + (void)motion_step; +} +#endif + +void BlenderSync::sync_hair(BL::Depsgraph b_depsgraph, 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<Node *> used_shaders = hair->get_used_shaders(); + + Hair new_hair; + new_hair.set_used_shaders(used_shaders); + + if (view_layer.use_hair) { + if (b_ob_info.object_data.is_a(&RNA_Hair)) { + /* Hair object. */ + sync_hair(&new_hair, b_ob_info, false); + } + else { + /* Particle hair. */ + bool need_undeformed = new_hair.need_attribute(scene, ATTR_STD_GENERATED); + BL::Mesh b_mesh = object_to_mesh( + b_data, b_ob_info, b_depsgraph, need_undeformed, Mesh::SUBDIVISION_NONE); + + if (b_mesh) { + sync_particle_hair(&new_hair, b_mesh, b_ob_info, false); + free_object_to_mesh(b_data, b_ob_info, b_mesh); + } + } + } + + /* 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 == "motion_steps" || + socket.name == "used_shaders") { + continue; + } + hair->set_value(socket, new_hair, socket); + } + + hair->attributes.update(std::move(new_hair.attributes)); + + /* 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->curve_keys_is_modified() || hair->curve_radius_is_modified()); + + hair->tag_update(scene, rebuild); +} + +void BlenderSync::sync_hair_motion(BL::Depsgraph b_depsgraph, + BObjectInfo &b_ob_info, + Hair *hair, + 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 (b_ob_info.object_data.is_a(&RNA_Hair)) { + /* Hair object. */ + sync_hair(hair, b_ob_info, true, motion_step); + return; + } + else { + /* Particle hair. */ + BL::Mesh b_mesh = object_to_mesh( + b_data, b_ob_info, b_depsgraph, false, Mesh::SUBDIVISION_NONE); + if (b_mesh) { + sync_particle_hair(hair, b_mesh, b_ob_info, true, motion_step); + free_object_to_mesh(b_data, 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 |