883 lines
30 KiB
C++
883 lines
30 KiB
C++
/*
|
|
* 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 "render/attribute.h"
|
|
#include "render/camera.h"
|
|
#include "render/curves.h"
|
|
#include "render/hair.h"
|
|
#include "render/object.h"
|
|
#include "render/scene.h"
|
|
|
|
#include "blender/blender_sync.h"
|
|
#include "blender/blender_util.h"
|
|
|
|
#include "util/util_foreach.h"
|
|
#include "util/util_hash.h"
|
|
#include "util/util_logging.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);
|
|
|
|
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, hair->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 = make_float3(0.0f, 0.0f, 0.0f);
|
|
float3 prev_co_object = make_float3(0.0f, 0.0f, 0.0f);
|
|
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();
|
|
|
|
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.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 = make_float2(0.0f, 0.0f);
|
|
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();
|
|
|
|
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.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_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_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->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_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->curve_keys.size() != num_keys) || (hair->num_curves() != num_curves)) {
|
|
VLOG(1) << "Allocation failed, clearing data";
|
|
hair->clear();
|
|
}
|
|
}
|
|
|
|
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->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->curve_keys[key]);
|
|
mP[key].w = hair->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->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->curve_first_key.size() ?
|
|
hair->curve_first_key[num_curves + 1] :
|
|
(int)hair->curve_keys.size());
|
|
const int num_center_curve_keys = curve_key_end - hair->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->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->curve_keys[i]);
|
|
curve_key.w = hair->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. */
|
|
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) &&
|
|
(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, BL::Object &b_ob, bool motion, int motion_step)
|
|
{
|
|
/* 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->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];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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_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_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;
|
|
|
|
BL::Hair::curves_iterator b_curve_iter;
|
|
for (b_hair.curves.begin(b_curve_iter); b_curve_iter != b_hair.curves.end(); ++b_curve_iter) {
|
|
BL::HairCurve b_curve = *b_curve_iter;
|
|
const int first_point_index = b_curve.first_point_index();
|
|
const int num_points = b_curve.num_points();
|
|
|
|
float3 prev_co = make_float3(0.0f, 0.0f, 0.0f);
|
|
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);
|
|
}
|
|
}
|
|
|
|
/* 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->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;
|
|
|
|
BL::Hair::curves_iterator b_curve_iter;
|
|
for (b_hair.curves.begin(b_curve_iter); b_curve_iter != b_hair.curves.end(); ++b_curve_iter) {
|
|
BL::HairCurve b_curve = *b_curve_iter;
|
|
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->curve_keys[i]);
|
|
curve_key.w = hair->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, BL::Object &b_ob, bool motion, int motion_step)
|
|
{
|
|
/* Convert Blender hair to Cycles curves. */
|
|
BL::Hair b_hair(b_ob.data());
|
|
if (motion) {
|
|
export_hair_curves_motion(hair, b_hair, motion_step);
|
|
}
|
|
else {
|
|
export_hair_curves(scene, hair, b_hair);
|
|
}
|
|
}
|
|
|
|
void BlenderSync::sync_hair(BL::Depsgraph b_depsgraph,
|
|
BL::Object b_ob,
|
|
Hair *hair,
|
|
const vector<Shader *> &used_shaders)
|
|
{
|
|
/* Compares curve_keys rather than strands in order to handle quick hair
|
|
* adjustments in dynamic BVH - other methods could probably do this better. */
|
|
array<float3> oldcurve_keys;
|
|
array<float> oldcurve_radius;
|
|
oldcurve_keys.steal_data(hair->curve_keys);
|
|
oldcurve_radius.steal_data(hair->curve_radius);
|
|
|
|
hair->clear();
|
|
hair->used_shaders = used_shaders;
|
|
|
|
if (view_layer.use_hair) {
|
|
if (b_ob.type() == BL::Object::type_HAIR) {
|
|
/* Hair object. */
|
|
sync_hair(hair, b_ob, false);
|
|
}
|
|
else {
|
|
/* Particle hair. */
|
|
bool need_undeformed = hair->need_attribute(scene, ATTR_STD_GENERATED);
|
|
BL::Mesh b_mesh = object_to_mesh(
|
|
b_data, b_ob, b_depsgraph, need_undeformed, Mesh::SUBDIVISION_NONE);
|
|
|
|
if (b_mesh) {
|
|
sync_particle_hair(hair, b_mesh, b_ob, false);
|
|
free_object_to_mesh(b_data, b_ob, b_mesh);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* tag update */
|
|
const bool rebuild = ((oldcurve_keys != hair->curve_keys) ||
|
|
(oldcurve_radius != hair->curve_radius));
|
|
|
|
hair->tag_update(scene, rebuild);
|
|
}
|
|
|
|
void BlenderSync::sync_hair_motion(BL::Depsgraph b_depsgraph,
|
|
BL::Object b_ob,
|
|
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, b_scene, preview)) {
|
|
if (b_ob.type() == BL::Object::type_HAIR) {
|
|
/* Hair object. */
|
|
sync_hair(hair, b_ob, true, motion_step);
|
|
return;
|
|
}
|
|
else {
|
|
/* Particle hair. */
|
|
BL::Mesh b_mesh = object_to_mesh(b_data, b_ob, b_depsgraph, false, Mesh::SUBDIVISION_NONE);
|
|
if (b_mesh) {
|
|
sync_particle_hair(hair, b_mesh, b_ob, true, motion_step);
|
|
free_object_to_mesh(b_data, b_ob, 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
|