Blender V4.3
abc_writer_hair.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
9#include "abc_writer_hair.h"
11
12#include "DNA_mesh_types.h"
13#include "DNA_meshdata_types.h"
14#include "DNA_object_types.h"
15
16#include "BLI_math_matrix.h"
17#include "BLI_math_vector.h"
18
19#include "BKE_customdata.hh"
21#include "BKE_mesh_runtime.hh"
22#include "BKE_object.hh"
23#include "BKE_particle.h"
24
25#include "CLG_log.h"
26static CLG_LogRef LOG = {"io.alembic"};
27
28using Alembic::Abc::P3fArraySamplePtr;
29using Alembic::AbcGeom::OCurves;
30using Alembic::AbcGeom::OCurvesSchema;
31using Alembic::AbcGeom::ON3fGeomParam;
32using Alembic::AbcGeom::OV2fGeomParam;
33
34namespace blender::io::alembic {
35
37 : ABCAbstractWriter(args), uv_warning_shown_(false)
38{
39}
40
42{
43 CLOG_INFO(&LOG, 2, "exporting %s", args_.abc_path.c_str());
44 abc_curves_ = OCurves(args_.abc_parent, args_.abc_name, timesample_index_);
45 abc_curves_schema_ = abc_curves_.getSchema();
46}
47
48Alembic::Abc::OObject ABCHairWriter::get_alembic_object() const
49{
50 return abc_curves_;
51}
52
53Alembic::Abc::OCompoundProperty ABCHairWriter::abc_prop_for_custom_props()
54{
55 return abc_schema_prop_for_custom_props(abc_curves_schema_);
56}
57
59{
60 /* We assume that hair particles are always animated. */
61 return true;
62}
63
65{
66 const Mesh *mesh = BKE_object_get_evaluated_mesh(context.object);
67 if (!mesh) {
68 return;
69 }
70 BKE_mesh_tessface_ensure(const_cast<Mesh *>(mesh));
71
72 std::vector<Imath::V3f> verts;
73 std::vector<int32_t> hvertices;
74 std::vector<Imath::V2f> uv_values;
75 std::vector<Imath::V3f> norm_values;
76
77 ParticleSystem *psys = context.particle_system;
78 if (psys->pathcache) {
79 ParticleSettings *part = psys->part;
80 bool export_children = psys->childcache && part->childtype != 0;
81
82 if (!export_children || part->draw & PART_DRAW_PARENT) {
83 write_hair_sample(
84 context, const_cast<Mesh *>(mesh), verts, norm_values, uv_values, hvertices);
85 }
86
87 if (export_children) {
88 write_hair_child_sample(
89 context, const_cast<Mesh *>(mesh), verts, norm_values, uv_values, hvertices);
90 }
91 }
92
93 Alembic::Abc::P3fArraySample iPos(verts);
94 OCurvesSchema::Sample sample(iPos, hvertices);
95 sample.setBasis(Alembic::AbcGeom::kNoBasis);
96 sample.setType(Alembic::AbcGeom::kLinear);
97 sample.setWrap(Alembic::AbcGeom::kNonPeriodic);
98
99 if (!uv_values.empty()) {
100 OV2fGeomParam::Sample uv_smp;
101 uv_smp.setVals(uv_values);
102 sample.setUVs(uv_smp);
103 }
104
105 if (!norm_values.empty()) {
106 ON3fGeomParam::Sample norm_smp;
107 norm_smp.setVals(norm_values);
108 sample.setNormals(norm_smp);
109 }
110
111 update_bounding_box(context.object);
112 sample.setSelfBounds(bounding_box_);
113 abc_curves_schema_.set(sample);
114}
115
116void ABCHairWriter::write_hair_sample(const HierarchyContext &context,
117 Mesh *mesh,
118 std::vector<Imath::V3f> &verts,
119 std::vector<Imath::V3f> &norm_values,
120 std::vector<Imath::V2f> &uv_values,
121 std::vector<int32_t> &hvertices)
122{
123 /* Get untransformed vertices, there's a xform under the hair. */
124 float inv_mat[4][4];
125 invert_m4_m4_safe(inv_mat, context.object->object_to_world().ptr());
126
128 &mesh->fdata_legacy, CD_MTFACE, mesh->totface_legacy);
129 const MFace *mface = (const MFace *)CustomData_get_layer(&mesh->fdata_legacy, CD_MFACE);
130 const Span<float3> positions = mesh->vert_positions();
131 const Span<float3> vert_normals = mesh->vert_normals();
132
133 if ((!mtface || !mface) && !uv_warning_shown_) {
134 std::fprintf(stderr,
135 "Warning, no UV set found for underlying geometry of %s.\n",
136 context.object->id.name + 2);
137 uv_warning_shown_ = true;
138 }
139
140 ParticleSystem *psys = context.particle_system;
141 ParticleSettings *part = psys->part;
142 ParticleData *pa = psys->particles;
143 int k;
144
145 ParticleCacheKey **cache = psys->pathcache;
146 ParticleCacheKey *path;
147 float normal[3];
148 Imath::V3f tmp_nor;
149
150 for (int p = 0; p < psys->totpart; p++, pa++) {
151 /* underlying info for faces-only emission */
152 path = cache[p];
153
154 /* Write UV and normal vectors */
155 if (part->from == PART_FROM_FACE && mtface) {
156 const int num = pa->num_dmcache >= 0 ? pa->num_dmcache : pa->num;
157
158 if (num < mesh->totface_legacy) {
159 /* TODO(Sybren): check whether the null check here and if(mface) are actually required
160 */
161 const MFace *face = mface == nullptr ? nullptr : &mface[num];
162 MTFace *tface = mtface + num;
163
164 if (mface) {
165 float r_uv[2], mapfw[4], vec[3];
166
167 psys_interpolate_uvs(tface, face->v4, pa->fuv, r_uv);
168 uv_values.emplace_back(r_uv[0], r_uv[1]);
169
171 reinterpret_cast<const float(*)[3]>(positions.data()),
172 reinterpret_cast<const float(*)[3]>(vert_normals.data()),
173 face,
174 tface,
175 nullptr,
176 mapfw,
177 vec,
178 normal,
179 nullptr,
180 nullptr,
181 nullptr);
182
183 copy_yup_from_zup(tmp_nor.getValue(), normal);
184 norm_values.push_back(tmp_nor);
185 }
186 }
187 else {
188 std::fprintf(stderr, "Particle to faces overflow (%d/%d)\n", num, mesh->totface_legacy);
189 }
190 }
191 else if (part->from == PART_FROM_VERT && mtface) {
192 /* vertex id */
193 const int num = (pa->num_dmcache >= 0) ? pa->num_dmcache : pa->num;
194
195 /* iterate over all faces to find a corresponding underlying UV */
196 for (int n = 0; n < mesh->totface_legacy; n++) {
197 const MFace *face = &mface[n];
198 const MTFace *tface = mtface + n;
199 uint vtx[4];
200 vtx[0] = face->v1;
201 vtx[1] = face->v2;
202 vtx[2] = face->v3;
203 vtx[3] = face->v4;
204 bool found = false;
205
206 for (int o = 0; o < 4; o++) {
207 if (o > 2 && vtx[o] == 0) {
208 break;
209 }
210
211 if (vtx[o] == num) {
212 uv_values.emplace_back(tface->uv[o][0], tface->uv[o][1]);
213 copy_v3_v3(normal, vert_normals[vtx[o]]);
214 copy_yup_from_zup(tmp_nor.getValue(), normal);
215 norm_values.push_back(tmp_nor);
216 found = true;
217 break;
218 }
219 }
220
221 if (found) {
222 break;
223 }
224 }
225 }
226
227 int steps = path->segments + 1;
228 hvertices.push_back(steps);
229
230 for (k = 0; k < steps; k++, path++) {
231 float vert[3];
232 copy_v3_v3(vert, path->co);
233 mul_m4_v3(inv_mat, vert);
234
235 /* Convert Z-up to Y-up. */
236 verts.emplace_back(vert[0], vert[2], -vert[1]);
237 }
238 }
239}
240
241void ABCHairWriter::write_hair_child_sample(const HierarchyContext &context,
242 Mesh *mesh,
243 std::vector<Imath::V3f> &verts,
244 std::vector<Imath::V3f> &norm_values,
245 std::vector<Imath::V2f> &uv_values,
246 std::vector<int32_t> &hvertices)
247{
248 /* Get untransformed vertices, there's a xform under the hair. */
249 float inv_mat[4][4];
250 invert_m4_m4_safe(inv_mat, context.object->object_to_world().ptr());
251
252 const MFace *mface = (const MFace *)CustomData_get_layer(&mesh->fdata_legacy, CD_MFACE);
254 &mesh->fdata_legacy, CD_MTFACE, mesh->totface_legacy);
255 const Span<float3> positions = mesh->vert_positions();
256 const Span<float3> vert_normals = mesh->vert_normals();
257
258 ParticleSystem *psys = context.particle_system;
259 ParticleSettings *part = psys->part;
260 ParticleCacheKey **cache = psys->childcache;
261 ParticleCacheKey *path;
262
263 ChildParticle *pc = psys->child;
264
265 for (int p = 0; p < psys->totchild; p++, pc++) {
266 path = cache[p];
267
268 if (part->from == PART_FROM_FACE && part->childtype != PART_CHILD_PARTICLES && mtface) {
269 const int num = pc->num;
270 if (num < 0) {
271 CLOG_WARN(
272 &LOG,
273 "Child particle of hair system %s has unknown face index of geometry of %s, skipping "
274 "child hair.",
275 psys->name,
276 context.object->id.name + 2);
277 continue;
278 }
279
280 const MFace *face = &mface[num];
281 MTFace *tface = mtface + num;
282
283 float r_uv[2], tmpnor[3], mapfw[4], vec[3];
284
285 psys_interpolate_uvs(tface, face->v4, pc->fuv, r_uv);
286 uv_values.emplace_back(r_uv[0], r_uv[1]);
287
289 reinterpret_cast<const float(*)[3]>(positions.data()),
290 reinterpret_cast<const float(*)[3]>(vert_normals.data()),
291 face,
292 tface,
293 nullptr,
294 mapfw,
295 vec,
296 tmpnor,
297 nullptr,
298 nullptr,
299 nullptr);
300
301 /* Convert Z-up to Y-up. */
302 norm_values.emplace_back(tmpnor[0], tmpnor[2], -tmpnor[1]);
303 }
304 else {
305 if (!uv_values.empty()) {
306 uv_values.push_back(uv_values[pc->parent]);
307 }
308 if (!norm_values.empty()) {
309 norm_values.push_back(norm_values[pc->parent]);
310 }
311 }
312
313 int steps = path->segments + 1;
314 hvertices.push_back(steps);
315
316 for (int k = 0; k < steps; k++) {
317 float vert[3];
318 copy_v3_v3(vert, path->co);
319 mul_m4_v3(inv_mat, vert);
320
321 /* Convert Z-up to Y-up. */
322 verts.emplace_back(vert[0], vert[2], -vert[1]);
323
324 path++;
325 }
326 }
327}
328
329} // namespace blender::io::alembic
CustomData interface, see also DNA_customdata_types.h.
const void * CustomData_get_layer(const CustomData *data, eCustomDataType type)
void * CustomData_get_layer_for_write(CustomData *data, eCustomDataType type, int totelem)
void BKE_mesh_tessface_ensure(Mesh *mesh)
General operations, lookup, etc. for blender objects.
Mesh * BKE_object_get_evaluated_mesh(const Object *object_eval)
void psys_interpolate_uvs(const struct MTFace *tface, int quad, const float w[4], float uvco[2])
void psys_interpolate_face(struct Mesh *mesh, const float(*vert_positions)[3], const float(*vert_normals)[3], const struct MFace *mface, struct MTFace *tface, const float(*orcodata)[3], float w[4], float vec[3], float nor[3], float utan[3], float vtan[3], float orco[3])
void mul_m4_v3(const float M[4][4], float r[3])
void invert_m4_m4_safe(float inverse[4][4], const float mat[4][4])
MINLINE void copy_v3_v3(float r[3], const float a[3])
unsigned int uint
#define CLOG_WARN(clg_ref,...)
Definition CLG_log.h:181
#define CLOG_INFO(clg_ref, level,...)
Definition CLG_log.h:179
Object is a sort of wrapper for general info.
@ PART_FROM_VERT
@ PART_FROM_FACE
@ PART_DRAW_PARENT
@ PART_CHILD_PARTICLES
static CLG_LogRef LOG
constexpr const T * data() const
Definition BLI_span.hh:216
Alembic::Abc::OCompoundProperty abc_schema_prop_for_custom_props(T abc_schema)
const ABCWriterConstructorArgs args_
virtual void update_bounding_box(Object *object)
virtual Alembic::Abc::OObject get_alembic_object() const override
ABCHairWriter(const ABCWriterConstructorArgs &args)
virtual void do_write(HierarchyContext &context) override
virtual bool check_is_animated(const HierarchyContext &context) const override
Alembic::Abc::OCompoundProperty abc_prop_for_custom_props() override
virtual void create_alembic_objects(const HierarchyContext *context) override
static float verts[][3]
#define LOG(severity)
Definition log.h:33
BLI_INLINE void copy_yup_from_zup(float yup[3], const float zup[3])
static const int steps
float uv[4][2]
ChildParticle * child
ParticleData * particles
ParticleSettings * part
struct ParticleCacheKey ** childcache
struct ParticleCacheKey ** pathcache