Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender')
-rw-r--r--source/blender/CMakeLists.txt2
-rw-r--r--source/blender/blenkernel/BKE_context.h1
-rw-r--r--source/blender/blenkernel/BKE_groom.h125
-rw-r--r--source/blender/blenkernel/BKE_hair.h132
-rw-r--r--source/blender/blenkernel/BKE_library.h2
-rw-r--r--source/blender/blenkernel/BKE_main.h1
-rw-r--r--source/blender/blenkernel/BKE_mesh_sample.h113
-rw-r--r--source/blender/blenkernel/BKE_particle.h1
-rw-r--r--source/blender/blenkernel/CMakeLists.txt7
-rw-r--r--source/blender/blenkernel/intern/context.c3
-rw-r--r--source/blender/blenkernel/intern/groom.c865
-rw-r--r--source/blender/blenkernel/intern/hair.c388
-rw-r--r--source/blender/blenkernel/intern/hair_draw.c360
-rw-r--r--source/blender/blenkernel/intern/idcode.c2
-rw-r--r--source/blender/blenkernel/intern/library.c17
-rw-r--r--source/blender/blenkernel/intern/library_query.c10
-rw-r--r--source/blender/blenkernel/intern/library_remap.c5
-rw-r--r--source/blender/blenkernel/intern/mesh_sample.c1683
-rw-r--r--source/blender/blenkernel/intern/object.c16
-rw-r--r--source/blender/blenkernel/intern/object_update.c8
-rw-r--r--source/blender/blenkernel/intern/particle.c5
-rw-r--r--source/blender/blenlib/BLI_math_geom.h2
-rw-r--r--source/blender/blenlib/intern/math_geom.c65
-rw-r--r--source/blender/blenloader/intern/readfile.c85
-rw-r--r--source/blender/blenloader/intern/writefile.c55
-rw-r--r--source/blender/blentranslation/BLT_translation.h2
-rw-r--r--source/blender/bmesh/CMakeLists.txt1
-rw-r--r--source/blender/bmesh/intern/bmesh_opdefines.c21
-rw-r--r--source/blender/bmesh/intern/bmesh_operators_private.h1
-rw-r--r--source/blender/bmesh/intern/bmesh_walkers_impl.c13
-rw-r--r--source/blender/bmesh/operators/bmo_face_island.c238
-rw-r--r--source/blender/depsgraph/intern/builder/deg_builder_nodes.cc16
-rw-r--r--source/blender/depsgraph/intern/builder/deg_builder_nodes.h1
-rw-r--r--source/blender/depsgraph/intern/builder/deg_builder_relations.cc16
-rw-r--r--source/blender/depsgraph/intern/builder/deg_builder_relations.h1
-rw-r--r--source/blender/depsgraph/intern/depsgraph_tag.cc1
-rw-r--r--source/blender/draw/CMakeLists.txt7
-rw-r--r--source/blender/draw/engines/eevee/eevee_materials.c334
-rw-r--r--source/blender/draw/engines/eevee/eevee_private.h41
-rw-r--r--source/blender/draw/engines/eevee/shaders/hair_lib.glsl330
-rw-r--r--source/blender/draw/engines/eevee/shaders/lit_surface_vert.glsl17
-rw-r--r--source/blender/draw/engines/eevee/shaders/prepass_vert.glsl16
-rw-r--r--source/blender/draw/intern/draw_cache.c61
-rw-r--r--source/blender/draw/intern/draw_cache.h17
-rw-r--r--source/blender/draw/intern/draw_cache_impl.h23
-rw-r--r--source/blender/draw/intern/draw_cache_impl_groom.c610
-rw-r--r--source/blender/draw/intern/draw_cache_impl_hair.c404
-rw-r--r--source/blender/draw/intern/draw_common.h28
-rw-r--r--source/blender/draw/intern/draw_hair.c87
-rw-r--r--source/blender/draw/intern/draw_manager.c16
-rw-r--r--source/blender/draw/modes/draw_mode_engines.h3
-rw-r--r--source/blender/draw/modes/edit_groom_mode.c304
-rw-r--r--source/blender/draw/modes/object_mode.c64
-rw-r--r--source/blender/draw/modes/shaders/edit_groom_overlay_frag.glsl22
-rw-r--r--source/blender/draw/modes/shaders/edit_groom_overlay_loosevert_vert.glsl39
-rw-r--r--source/blender/editors/CMakeLists.txt1
-rw-r--r--source/blender/editors/groom/CMakeLists.txt50
-rw-r--r--source/blender/editors/groom/editgroom.c137
-rw-r--r--source/blender/editors/groom/editgroom_region.c194
-rw-r--r--source/blender/editors/groom/editgroom_select.c442
-rw-r--r--source/blender/editors/groom/groom_hair.c114
-rw-r--r--source/blender/editors/groom/groom_intern.h48
-rw-r--r--source/blender/editors/groom/groom_ops.c86
-rw-r--r--source/blender/editors/include/ED_groom.h66
-rw-r--r--source/blender/editors/include/ED_screen.h1
-rw-r--r--source/blender/editors/include/ED_view3d.h12
-rw-r--r--source/blender/editors/include/UI_icons.h3
-rw-r--r--source/blender/editors/object/object_add.c37
-rw-r--r--source/blender/editors/object/object_edit.c11
-rw-r--r--source/blender/editors/object/object_intern.h2
-rw-r--r--source/blender/editors/object/object_modes.c1
-rw-r--r--source/blender/editors/object/object_modifier.c121
-rw-r--r--source/blender/editors/object/object_ops.c2
-rw-r--r--source/blender/editors/object/object_relations.c4
-rw-r--r--source/blender/editors/screen/screen_ops.c9
-rw-r--r--source/blender/editors/space_api/spacetypes.c6
-rw-r--r--source/blender/editors/space_buttons/buttons_context.c7
-rw-r--r--source/blender/editors/space_outliner/outliner_draw.c5
-rw-r--r--source/blender/editors/space_outliner/outliner_intern.h2
-rw-r--r--source/blender/editors/space_outliner/outliner_tree.c9
-rw-r--r--source/blender/editors/space_view3d/space_view3d.c3
-rw-r--r--source/blender/editors/space_view3d/view3d_iterators.c72
-rw-r--r--source/blender/editors/space_view3d/view3d_select.c138
-rw-r--r--source/blender/editors/transform/transform.h1
-rw-r--r--source/blender/editors/transform/transform_conversions.c234
-rw-r--r--source/blender/editors/transform/transform_generics.c5
-rw-r--r--source/blender/gpu/GPU_texture.h2
-rw-r--r--source/blender/makesdna/DNA_ID.h2
-rw-r--r--source/blender/makesdna/DNA_groom_types.h136
-rw-r--r--source/blender/makesdna/DNA_hair_types.h121
-rw-r--r--source/blender/makesdna/DNA_meshdata_types.h7
-rw-r--r--source/blender/makesdna/DNA_modifier_types.h19
-rw-r--r--source/blender/makesdna/DNA_object_types.h7
-rw-r--r--source/blender/makesdna/DNA_scene_types.h21
-rw-r--r--source/blender/makesdna/intern/makesdna.c4
-rw-r--r--source/blender/makesrna/RNA_access.h5
-rw-r--r--source/blender/makesrna/intern/CMakeLists.txt3
-rw-r--r--source/blender/makesrna/intern/makesrna.c3
-rw-r--r--source/blender/makesrna/intern/rna_ID.c1
-rw-r--r--source/blender/makesrna/intern/rna_groom.c231
-rw-r--r--source/blender/makesrna/intern/rna_hair.c231
-rw-r--r--source/blender/makesrna/intern/rna_internal.h3
-rw-r--r--source/blender/makesrna/intern/rna_main_api.c3
-rw-r--r--source/blender/makesrna/intern/rna_mesh_sample.c73
-rw-r--r--source/blender/makesrna/intern/rna_modifier.c39
-rw-r--r--source/blender/makesrna/intern/rna_object.c2
-rw-r--r--source/blender/makesrna/intern/rna_scene.c4
-rw-r--r--source/blender/makesrna/intern/rna_sculpt_paint.c41
-rw-r--r--source/blender/modifiers/CMakeLists.txt1
-rw-r--r--source/blender/modifiers/MOD_modifiertypes.h1
-rw-r--r--source/blender/modifiers/intern/MOD_fur.c162
-rw-r--r--source/blender/modifiers/intern/MOD_util.c1
-rw-r--r--source/blender/windowmanager/WM_types.h2
-rw-r--r--source/blender/windowmanager/intern/wm_keymap.c3
114 files changed, 9510 insertions, 128 deletions
diff --git a/source/blender/CMakeLists.txt b/source/blender/CMakeLists.txt
index 316ab531a05..8a93e9c94d6 100644
--- a/source/blender/CMakeLists.txt
+++ b/source/blender/CMakeLists.txt
@@ -48,7 +48,9 @@ set(SRC_DNA_INC
${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_genfile.h
${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_gpencil_types.h
${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_gpu_types.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_groom_types.h
${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_group_types.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_hair_types.h
${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_image_types.h
${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_ipo_types.h
${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_key_types.h
diff --git a/source/blender/blenkernel/BKE_context.h b/source/blender/blenkernel/BKE_context.h
index 34192db318e..947ed7cd600 100644
--- a/source/blender/blenkernel/BKE_context.h
+++ b/source/blender/blenkernel/BKE_context.h
@@ -114,6 +114,7 @@ enum {
CTX_MODE_EDIT_ARMATURE,
CTX_MODE_EDIT_METABALL,
CTX_MODE_EDIT_LATTICE,
+ CTX_MODE_EDIT_GROOM,
CTX_MODE_POSE,
CTX_MODE_SCULPT,
CTX_MODE_PAINT_WEIGHT,
diff --git a/source/blender/blenkernel/BKE_groom.h b/source/blender/blenkernel/BKE_groom.h
new file mode 100644
index 00000000000..e88513b5480
--- /dev/null
+++ b/source/blender/blenkernel/BKE_groom.h
@@ -0,0 +1,125 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) Blender Foundation
+ * All rights reserved.
+ *
+ * The Original Code is: all of this file.
+ *
+ * Contributor(s): Lukas Toenne
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+#ifndef __BKE_GROOM_H__
+#define __BKE_GROOM_H__
+
+/** \file BKE_groom.h
+ * \ingroup bke
+ */
+
+struct EvaluationContext;
+struct Groom;
+struct GroomBundle;
+struct Main;
+struct Object;
+struct Scene;
+
+void BKE_groom_init(struct Groom *groom);
+void *BKE_groom_add(struct Main *bmain, const char *name);
+
+void BKE_groom_free(struct Groom *groom);
+void BKE_groom_bundle_curve_cache_clear(struct GroomBundle *bundle);
+
+void BKE_groom_copy_data(struct Main *bmain, struct Groom *groom_dst, const struct Groom *groom_src, const int flag);
+struct Groom *BKE_groom_copy(struct Main *bmain, const struct Groom *groom);
+
+void BKE_groom_make_local(struct Main *bmain, struct Groom *groom, const bool lib_local);
+
+bool BKE_groom_minmax(struct Groom *groom, float min[3], float max[3]);
+void BKE_groom_boundbox_calc(struct Groom *groom, float r_loc[3], float r_size[3]);
+
+
+/* === Curve cache === */
+
+void BKE_groom_curve_cache_update(struct Groom *groom);
+void BKE_groom_curve_cache_clear(struct Groom *groom);
+
+
+/* === Scalp regions === */
+
+/* Try to bind bundles to their scalp regions */
+void BKE_groom_bind_scalp_regions(struct Groom *groom, bool force_rebind);
+
+bool BKE_groom_bundle_bind(struct Groom *groom, struct GroomBundle *bundle, bool force_rebind);
+void BKE_groom_bundle_unbind(struct GroomBundle *bundle);
+
+
+/* === Hair System === */
+
+/* Create follicles and guide curve origins on the scalp surface for hair fiber rendering */
+void BKE_groom_hair_distribute(struct Groom *groom, unsigned int seed, int hair_count, int guide_curve_count);
+
+/* Calculate guide curve shapes based on groom bundle deformation */
+void BKE_groom_hair_update_guide_curves(struct Groom *groom);
+
+
+/* === Depsgraph evaluation === */
+
+void BKE_groom_eval_geometry(const struct EvaluationContext *eval_ctx, struct Groom *groom);
+
+
+/* === Draw Cache === */
+
+enum {
+ BKE_GROOM_BATCH_DIRTY_ALL = 0,
+ BKE_GROOM_BATCH_DIRTY_SELECT,
+};
+void BKE_groom_batch_cache_dirty(struct Groom *groom, int mode);
+void BKE_groom_batch_cache_free(struct Groom *groom);
+
+/* === Iterators === */
+
+/* Utility class for iterating over groom elements */
+typedef struct GroomIterator
+{
+ int isection; /* section index */
+ struct GroomSection *section; /* section data pointer */
+
+ int ivertex; /* vertex index */
+ int isectionvertex; /* vertex index for the inner loop */
+ struct GroomSectionVertex *vertex; /* vertex data pointer */
+} GroomIterator;
+
+#define GROOM_ITER_SECTIONS(iter, bundle) \
+ for (iter.isection = 0, iter.section = (bundle)->sections; \
+ iter.isection < (bundle)->totsections; \
+ ++iter.isection, ++iter.section)
+
+#define GROOM_ITER_SECTION_LOOPS(iter, sectionvar, vertexvar, bundle) \
+ for (iter.isection = 0, iter.section = (bundle)->sections, iter.ivertex = 0, iter.vertex = (bundle)->verts; \
+ iter.isection < (bundle)->totsections; \
+ ++iter.isection, ++iter.section) \
+ for (iter.isectionvertex = 0; \
+ iter.isectionvertex < (bundle)->numloopverts; \
+ ++iter.isectionvertex, ++iter.vertex)
+
+/* === Utility functions (DerivedMesh SOON TO BE DEPRECATED!) === */
+struct DerivedMesh;
+struct DerivedMesh* BKE_groom_get_scalp(struct Groom *groom);
+
+#endif /* __BKE_GROOM_H__ */
diff --git a/source/blender/blenkernel/BKE_hair.h b/source/blender/blenkernel/BKE_hair.h
new file mode 100644
index 00000000000..01e4d3e0abe
--- /dev/null
+++ b/source/blender/blenkernel/BKE_hair.h
@@ -0,0 +1,132 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) Blender Foundation
+ * All rights reserved.
+ *
+ * The Original Code is: all of this file.
+ *
+ * Contributor(s): Lukas Toenne
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+#ifndef __BKE_HAIR_H__
+#define __BKE_HAIR_H__
+
+/** \file blender/blenkernel/BKE_hair.h
+ * \ingroup bke
+ */
+
+#include "BLI_utildefines.h"
+
+static const unsigned int HAIR_STRAND_INDEX_NONE = 0xFFFFFFFF;
+
+struct HairFollicle;
+struct HairPattern;
+struct HairSystem;
+struct HairDrawSettings;
+struct DerivedMesh;
+struct MeshSample;
+struct Object;
+
+/* Create a new hair system instance */
+struct HairSystem* BKE_hair_new(void);
+/* Copy an existing hair system */
+struct HairSystem* BKE_hair_copy(struct HairSystem *hsys);
+/* Delete a hair system */
+void BKE_hair_free(struct HairSystem *hsys);
+
+/* === Guide Strands === */
+
+/* Allocate buffers for defining guide curves
+ * \param totcurves Number of guide curves to allocate
+ */
+void BKE_hair_guide_curves_begin(struct HairSystem *hsys, int totcurves);
+
+/* Set properties of a guide curve
+ * \param index Index of the guide guide curve
+ * \param mesh_sample Origin of the guide curve on the scalp mesh.
+ * \param numverts Number of vertices in this guide curve
+ */
+void BKE_hair_set_guide_curve(struct HairSystem *hsys, int index, const struct MeshSample *mesh_sample, int numverts);
+
+/* Finalize guide curve update */
+void BKE_hair_guide_curves_end(struct HairSystem *hsys);
+
+/* Set properties of a guide curve vertex
+ * \param index Index of the guide curve vertex.
+ * \param flag Flags to set on the vertex.
+ * \param co Location of the vertex in object space.
+ */
+void BKE_hair_set_guide_vertex(struct HairSystem *hsys, int index, int flag, const float co[3]);
+
+/* === Follicles === */
+
+/* Calculate surface area of a scalp mesh */
+float BKE_hair_calc_surface_area(struct DerivedMesh *scalp);
+
+/* Calculate a density value based on surface area and sample count */
+float BKE_hair_calc_density_from_count(float area, int count);
+/* Calculate maximum sample count based on surface area and density */
+int BKE_hair_calc_max_count_from_density(float area, float density);
+
+/* Calculate a density value based on a minimum distance */
+float BKE_hair_calc_density_from_min_distance(float min_distance);
+/* Calculate a minimum distance based on density */
+float BKE_hair_calc_min_distance_from_density(float density);
+
+/* Distribute hair follicles on a scalp mesh */
+void BKE_hair_generate_follicles(
+ struct HairSystem* hsys,
+ struct DerivedMesh *scalp,
+ unsigned int seed,
+ int count);
+
+void BKE_hair_bind_follicles(struct HairSystem *hsys, struct DerivedMesh *scalp);
+
+/* === Draw Settings === */
+
+struct HairDrawSettings* BKE_hair_draw_settings_new(void);
+struct HairDrawSettings* BKE_hair_draw_settings_copy(struct HairDrawSettings *draw_settings);
+void BKE_hair_draw_settings_free(struct HairDrawSettings *draw_settings);
+
+/* === Draw Cache === */
+
+enum {
+ BKE_HAIR_BATCH_DIRTY_FIBERS = (1 << 0),
+ BKE_HAIR_BATCH_DIRTY_STRANDS = (1 << 1),
+ BKE_HAIR_BATCH_DIRTY_ALL = 0xFFFF,
+};
+void BKE_hair_batch_cache_dirty(struct HairSystem* hsys, int mode);
+void BKE_hair_batch_cache_free(struct HairSystem* hsys);
+
+int* BKE_hair_get_fiber_lengths(const struct HairSystem* hsys, int subdiv);
+void BKE_hair_get_texture_buffer_size(
+ const struct HairSystem* hsys,
+ int subdiv,
+ int *r_size,
+ int *r_strand_map_start,
+ int *r_strand_vertex_start,
+ int *r_fiber_start);
+void BKE_hair_get_texture_buffer(
+ const struct HairSystem* hsys,
+ struct DerivedMesh *scalp,
+ int subdiv,
+ void *texbuffer);
+
+#endif
diff --git a/source/blender/blenkernel/BKE_library.h b/source/blender/blenkernel/BKE_library.h
index f3df8b9b363..5abf9139226 100644
--- a/source/blender/blenkernel/BKE_library.h
+++ b/source/blender/blenkernel/BKE_library.h
@@ -159,7 +159,7 @@ void id_clear_lib_data_ex(struct Main *bmain, struct ID *id, const bool id_in_ma
struct ListBase *which_libbase(struct Main *mainlib, short type);
-#define MAX_LIBARRAY 37
+#define MAX_LIBARRAY 38
int set_listbasepointers(struct Main *main, struct ListBase *lb[MAX_LIBARRAY]);
/* Main API */
diff --git a/source/blender/blenkernel/BKE_main.h b/source/blender/blenkernel/BKE_main.h
index aac43768acf..3e767e85142 100644
--- a/source/blender/blenkernel/BKE_main.h
+++ b/source/blender/blenkernel/BKE_main.h
@@ -127,6 +127,7 @@ typedef struct Main {
ListBase linestyle;
ListBase cachefiles;
ListBase workspaces;
+ ListBase grooms;
char id_tag_update[MAX_LIBARRAY];
diff --git a/source/blender/blenkernel/BKE_mesh_sample.h b/source/blender/blenkernel/BKE_mesh_sample.h
new file mode 100644
index 00000000000..94560d8c017
--- /dev/null
+++ b/source/blender/blenkernel/BKE_mesh_sample.h
@@ -0,0 +1,113 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+#ifndef __BKE_MESH_SAMPLE_H__
+#define __BKE_MESH_SAMPLE_H__
+
+/** \file BKE_mesh_sample.h
+ * \ingroup bke
+ */
+
+struct DerivedMesh;
+struct Key;
+struct KeyBlock;
+struct MFace;
+struct MVert;
+
+struct MeshSample;
+struct MeshSampleGenerator;
+
+typedef struct MeshSampleGenerator MeshSampleGenerator;
+typedef float (*MeshSampleVertexWeightFp)(struct DerivedMesh *dm, struct MVert *vert, unsigned int index, void *userdata);
+typedef void* (*MeshSampleThreadContextCreateFp)(void *userdata, int start);
+typedef void (*MeshSampleThreadContextFreeFp)(void *userdata, void *thread_ctx);
+typedef bool (*MeshSampleRayFp)(void *userdata, void *thread_ctx, float ray_start[3], float ray_end[3]);
+
+/* ==== Utility Functions ==== */
+
+float* BKE_mesh_sample_calc_triangle_weights(struct DerivedMesh *dm, MeshSampleVertexWeightFp vertex_weight_cb, void *userdata, float *r_area);
+
+void BKE_mesh_sample_weights_from_loc(struct MeshSample *sample, struct DerivedMesh *dm, int face_index, const float loc[3]);
+
+
+/* ==== Evaluate ==== */
+
+bool BKE_mesh_sample_is_valid(const struct MeshSample *sample);
+bool BKE_mesh_sample_is_volume_sample(const struct MeshSample *sample);
+
+bool BKE_mesh_sample_eval(struct DerivedMesh *dm, const struct MeshSample *sample, float loc[3], float nor[3], float tang[3]);
+bool BKE_mesh_sample_shapekey(struct Key *key, struct KeyBlock *kb, const struct MeshSample *sample, float loc[3]);
+
+void BKE_mesh_sample_clear(struct MeshSample *sample);
+
+
+/* ==== Generator Types ==== */
+
+struct MeshSampleGenerator *BKE_mesh_sample_gen_surface_vertices(void);
+
+/* vertex_weight_cb is optional */
+struct MeshSampleGenerator *BKE_mesh_sample_gen_surface_random(unsigned int seed, bool use_area_weight,
+ MeshSampleVertexWeightFp vertex_weight_cb, void *userdata);
+
+struct MeshSampleGenerator *BKE_mesh_sample_gen_surface_raycast(
+ MeshSampleThreadContextCreateFp thread_context_create_cb,
+ MeshSampleThreadContextFreeFp thread_context_free_cb,
+ MeshSampleRayFp ray_cb,
+ void *userdata);
+
+struct MeshSampleGenerator *BKE_mesh_sample_gen_surface_poissondisk(unsigned int seed, float mindist, unsigned int max_samples,
+ MeshSampleVertexWeightFp vertex_weight_cb, void *userdata);
+
+struct MeshSampleGenerator *BKE_mesh_sample_gen_volume_random_bbray(unsigned int seed, float density);
+
+void BKE_mesh_sample_free_generator(struct MeshSampleGenerator *gen);
+
+
+/* ==== Sampling ==== */
+
+void BKE_mesh_sample_generator_bind(struct MeshSampleGenerator *gen, struct DerivedMesh *dm);
+void BKE_mesh_sample_generator_unbind(struct MeshSampleGenerator *gen);
+
+unsigned int BKE_mesh_sample_gen_get_max_samples(const struct MeshSampleGenerator *gen);
+
+/* Generate a single sample.
+ * Not threadsafe!
+ */
+bool BKE_mesh_sample_generate(struct MeshSampleGenerator *gen, struct MeshSample *sample);
+
+/* Generate a large number of samples.
+ */
+int BKE_mesh_sample_generate_batch_ex(struct MeshSampleGenerator *gen,
+ void *output_buffer, int output_stride, int count,
+ bool use_threads);
+
+int BKE_mesh_sample_generate_batch(struct MeshSampleGenerator *gen,
+ MeshSample *output_buffer, int count);
+
+/* ==== Utilities ==== */
+
+struct ParticleSystem;
+struct ParticleData;
+struct BVHTreeFromMesh;
+
+bool BKE_mesh_sample_from_particle(struct MeshSample *sample, struct ParticleSystem *psys, struct DerivedMesh *dm, struct ParticleData *pa);
+bool BKE_mesh_sample_to_particle(struct MeshSample *sample, struct ParticleSystem *psys, struct DerivedMesh *dm, struct BVHTreeFromMesh *bvhtree, struct ParticleData *pa);
+
+#endif /* __BKE_MESH_SAMPLE_H__ */
diff --git a/source/blender/blenkernel/BKE_particle.h b/source/blender/blenkernel/BKE_particle.h
index 8cd76097401..dd563617674 100644
--- a/source/blender/blenkernel/BKE_particle.h
+++ b/source/blender/blenkernel/BKE_particle.h
@@ -425,6 +425,7 @@ void psys_interpolate_face(struct MVert *mvert, struct MFace *mface, struct MTFa
float orco[3], float ornor[3]);
float psys_particle_value_from_verts(struct DerivedMesh *dm, short from, struct ParticleData *pa, float *values);
void psys_get_from_key(struct ParticleKey *key, float loc[3], float vel[3], float rot[4], float *time);
+int psys_get_index_on_dm(struct ParticleSystem *psys, struct DerivedMesh *dm, ParticleData *pa, int *mapindex, float mapfw[4]);
/* BLI_bvhtree_ray_cast callback */
void BKE_psys_collision_neartest_cb(void *userdata, int index, const struct BVHTreeRay *ray, struct BVHTreeRayHit *hit);
diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt
index 6a1c3ea883c..b6250ad86f4 100644
--- a/source/blender/blenkernel/CMakeLists.txt
+++ b/source/blender/blenkernel/CMakeLists.txt
@@ -115,7 +115,10 @@ set(SRC
intern/font.c
intern/freestyle.c
intern/gpencil.c
+ intern/groom.c
intern/group.c
+ intern/hair.c
+ intern/hair_draw.c
intern/icons.c
intern/idcode.c
intern/idprop.c
@@ -141,6 +144,7 @@ set(SRC
intern/mesh_evaluate.c
intern/mesh_mapping.c
intern/mesh_remap.c
+ intern/mesh_sample.c
intern/mesh_tangent.c
intern/mesh_validate.c
intern/modifier.c
@@ -252,7 +256,9 @@ set(SRC
BKE_freestyle.h
BKE_global.h
BKE_gpencil.h
+ BKE_groom.h
BKE_group.h
+ BKE_hair.h
BKE_icons.h
BKE_idcode.h
BKE_idprop.h
@@ -275,6 +281,7 @@ set(SRC
BKE_mesh.h
BKE_mesh_mapping.h
BKE_mesh_remap.h
+ BKE_mesh_sample.h
BKE_mesh_tangent.h
BKE_modifier.h
BKE_movieclip.h
diff --git a/source/blender/blenkernel/intern/context.c b/source/blender/blenkernel/intern/context.c
index d550f1945db..809db09eb86 100644
--- a/source/blender/blenkernel/intern/context.c
+++ b/source/blender/blenkernel/intern/context.c
@@ -1016,6 +1016,8 @@ int CTX_data_mode_enum_ex(const Object *obedit, const Object *ob, const eObjectM
return CTX_MODE_EDIT_METABALL;
case OB_LATTICE:
return CTX_MODE_EDIT_LATTICE;
+ case OB_GROOM:
+ return CTX_MODE_EDIT_GROOM;
}
}
else {
@@ -1050,6 +1052,7 @@ static const char *data_mode_strings[] = {
"armature_edit",
"mball_edit",
"lattice_edit",
+ "groom_edit",
"posemode",
"sculpt_mode",
"weightpaint",
diff --git a/source/blender/blenkernel/intern/groom.c b/source/blender/blenkernel/intern/groom.c
new file mode 100644
index 00000000000..e64fd5ca297
--- /dev/null
+++ b/source/blender/blenkernel/intern/groom.c
@@ -0,0 +1,865 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) Blender Foundation
+ * All rights reserved.
+ *
+ * The Original Code is: all of this file.
+ *
+ * Contributor(s): Lukas Toenne
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file blender/blenkernel/intern/gpencil.c
+ * \ingroup bke
+ */
+
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_blenlib.h"
+#include "BLI_math.h"
+#include "BLI_utildefines.h"
+#include "BLI_string_utils.h"
+
+#include "BLT_translation.h"
+
+#include "DNA_groom_types.h"
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+
+#include "BKE_animsys.h"
+#include "BKE_customdata.h"
+#include "BKE_cdderivedmesh.h"
+#include "BKE_global.h"
+#include "BKE_groom.h"
+#include "BKE_hair.h"
+#include "BKE_bvhutils.h"
+#include "BKE_library.h"
+#include "BKE_main.h"
+#include "BKE_mesh_sample.h"
+#include "BKE_object.h"
+#include "BKE_object_facemap.h"
+
+#include "DEG_depsgraph.h"
+
+#include "bmesh.h"
+
+
+void BKE_groom_init(Groom *groom)
+{
+ BLI_assert(MEMCMP_STRUCT_OFS_IS_ZERO(groom, id));
+
+ groom->bb = BKE_boundbox_alloc_unit();
+
+ groom->curve_res = 12;
+
+ groom->hair_system = BKE_hair_new();
+ groom->hair_draw_settings = BKE_hair_draw_settings_new();
+}
+
+void *BKE_groom_add(Main *bmain, const char *name)
+{
+ Groom *groom = BKE_libblock_alloc(bmain, ID_GM, name, 0);
+
+ BKE_groom_init(groom);
+
+ return groom;
+}
+
+void BKE_groom_bundle_curve_cache_clear(GroomBundle *bundle)
+{
+ if (bundle->curvecache)
+ {
+ MEM_freeN(bundle->curvecache);
+ bundle->curvecache = NULL;
+ bundle->curvesize = 0;
+ bundle->totcurvecache = 0;
+ }
+}
+
+static void groom_bundles_free(ListBase *bundles)
+{
+ for (GroomBundle *bundle = bundles->first; bundle; bundle = bundle->next)
+ {
+ BKE_groom_bundle_curve_cache_clear(bundle);
+
+ if (bundle->sections)
+ {
+ MEM_freeN(bundle->sections);
+ }
+ if (bundle->verts)
+ {
+ MEM_freeN(bundle->verts);
+ }
+ }
+ BLI_freelistN(bundles);
+}
+
+/** Free (or release) any data used by this groom (does not free the groom itself). */
+void BKE_groom_free(Groom *groom)
+{
+ BKE_groom_batch_cache_free(groom);
+
+ if (groom->editgroom)
+ {
+ EditGroom *edit = groom->editgroom;
+
+ groom_bundles_free(&edit->bundles);
+
+ MEM_freeN(edit);
+ groom->editgroom = NULL;
+ }
+
+ MEM_SAFE_FREE(groom->bb);
+
+ if (groom->hair_system)
+ {
+ BKE_hair_free(groom->hair_system);
+ }
+ if (groom->hair_draw_settings)
+ {
+ BKE_hair_draw_settings_free(groom->hair_draw_settings);
+ }
+
+ groom_bundles_free(&groom->bundles);
+
+ BKE_animdata_free(&groom->id, false);
+}
+
+/**
+ * Only copy internal data of Groom ID from source to already allocated/initialized destination.
+ * You probably never want to use that directly, use id_copy or BKE_id_copy_ex for typical needs.
+ *
+ * WARNING! This function will not handle ID user count!
+ *
+ * \param flag Copying options (see BKE_library.h's LIB_ID_COPY_... flags for more).
+ */
+void BKE_groom_copy_data(Main *UNUSED(bmain), Groom *groom_dst, const Groom *groom_src, const int UNUSED(flag))
+{
+ groom_dst->bb = MEM_dupallocN(groom_src->bb);
+
+ BLI_duplicatelist(&groom_dst->bundles, &groom_src->bundles);
+ for (GroomBundle *bundle = groom_dst->bundles.first; bundle; bundle = bundle->next)
+ {
+ if (bundle->curvecache)
+ {
+ bundle->curvecache = MEM_dupallocN(bundle->curvecache);
+ }
+ if (bundle->sections)
+ {
+ bundle->sections = MEM_dupallocN(bundle->sections);
+ }
+ if (bundle->verts)
+ {
+ bundle->verts = MEM_dupallocN(bundle->verts);
+ }
+ }
+
+ groom_dst->editgroom = NULL;
+
+ if (groom_dst->hair_system)
+ {
+ groom_dst->hair_system = BKE_hair_copy(groom_dst->hair_system);
+ }
+ if (groom_dst->hair_draw_settings)
+ {
+ groom_dst->hair_draw_settings = BKE_hair_draw_settings_copy(groom_dst->hair_draw_settings);
+ }
+}
+
+Groom *BKE_groom_copy(Main *bmain, const Groom *groom)
+{
+ Groom *groom_copy;
+ BKE_id_copy_ex(bmain, &groom->id, (ID **)&groom_copy, 0, false);
+ return groom_copy;
+}
+
+void BKE_groom_make_local(Main *bmain, Groom *groom, const bool lib_local)
+{
+ BKE_id_make_local_generic(bmain, &groom->id, true, lib_local);
+}
+
+
+bool BKE_groom_minmax(Groom *groom, float min[3], float max[3])
+{
+ // TODO
+ UNUSED_VARS(groom, min, max);
+ return true;
+}
+
+void BKE_groom_boundbox_calc(Groom *groom, float r_loc[3], float r_size[3])
+{
+ if (groom->bb == NULL)
+ {
+ groom->bb = MEM_callocN(sizeof(BoundBox), "boundbox");
+ }
+
+ float mloc[3], msize[3];
+ if (!r_loc)
+ {
+ r_loc = mloc;
+ }
+ if (!r_size)
+ {
+ r_size = msize;
+ }
+
+ float min[3], max[3];
+ INIT_MINMAX(min, max);
+ if (!BKE_groom_minmax(groom, min, max)) {
+ min[0] = min[1] = min[2] = -1.0f;
+ max[0] = max[1] = max[2] = 1.0f;
+ }
+
+ mid_v3_v3v3(r_loc, min, max);
+
+ r_size[0] = (max[0] - min[0]) / 2.0f;
+ r_size[1] = (max[1] - min[1]) / 2.0f;
+ r_size[2] = (max[2] - min[2]) / 2.0f;
+
+ BKE_boundbox_init_from_minmax(groom->bb, min, max);
+ groom->bb->flag &= ~BOUNDBOX_DIRTY;
+}
+
+
+/* === Scalp regions === */
+
+void BKE_groom_bind_scalp_regions(Groom *groom, bool force_rebind)
+{
+ if (groom->editgroom)
+ {
+ for (GroomBundle *bundle = groom->editgroom->bundles.first; bundle; bundle = bundle->next)
+ {
+ BKE_groom_bundle_bind(groom, bundle, force_rebind);
+ }
+ }
+ else
+ {
+ for (GroomBundle *bundle = groom->bundles.first; bundle; bundle = bundle->next)
+ {
+ BKE_groom_bundle_bind(groom, bundle, force_rebind);
+ }
+ }
+}
+
+static bool groom_shape_rebuild(GroomBundle *bundle, int numshapeverts, Object *scalp_ob)
+{
+ BLI_assert(bundle->scalp_region != NULL);
+ BLI_assert(scalp_ob->type == OB_MESH);
+
+ bool result = true;
+ float (*shape)[2] = MEM_mallocN(sizeof(*shape) * numshapeverts, "groom section shape");
+
+ Mesh *me = scalp_ob->data;
+ // XXX MeshSample will use Mesh instead of DerivedMesh in the future
+ DerivedMesh *dm = CDDM_from_mesh(me);
+
+ /* last sample is the center position */
+ MeshSample *center_sample = &bundle->scalp_region[numshapeverts];
+ float center_co[3], center_nor[3], center_tang[3], center_binor[3];
+ if (!BKE_mesh_sample_eval(dm, center_sample, center_co, center_nor, center_tang))
+ {
+ result = false;
+ goto cleanup;
+ }
+ cross_v3_v3v3(center_binor, center_nor, center_tang);
+
+ MeshSample *sample = bundle->scalp_region;
+ GroomSectionVertex *vert0 = bundle->verts;
+ for (int i = 0; i < numshapeverts; ++i, ++sample, ++vert0)
+ {
+ /* 3D position of the shape vertex origin on the mesh */
+ float co[3], nor[3], tang[3];
+ if (!BKE_mesh_sample_eval(dm, sample, co, nor, tang))
+ {
+ result = false;
+ goto cleanup;
+ }
+ /* Get relative offset from the center */
+ sub_v3_v3(co, center_co);
+ /* Convert mesh surface positions to 2D shape
+ * by projecting onto the normal plane
+ */
+ shape[i][0] = dot_v3v3(co, center_binor);
+ shape[i][1] = dot_v3v3(co, center_tang);
+ }
+
+ bundle->numshapeverts = numshapeverts;
+ bundle->totverts = numshapeverts * bundle->totsections;
+ bundle->verts = MEM_reallocN_id(bundle->verts, sizeof(*bundle->verts) * bundle->totverts, "groom bundle vertices");
+ /* Set the shape for all sections */
+ GroomSectionVertex *vert = bundle->verts;
+ for (int i = 0; i < bundle->totsections; ++i)
+ {
+ for (int j = 0; j < numshapeverts; ++j, ++vert)
+ {
+ copy_v2_v2(vert->co, shape[j]);
+ vert->flag = 0;
+ }
+ }
+
+cleanup:
+ MEM_freeN(shape);
+ dm->release(dm);
+
+ return result;
+}
+
+static BMesh *groom_create_scalp_bmesh(Mesh *me)
+{
+ const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(me);
+
+ BMesh *bm = BM_mesh_create(&allocsize, &((struct BMeshCreateParams){
+ .use_toolflags = true,
+ }));
+
+ BM_mesh_bm_from_me(bm, me, (&(struct BMeshFromMeshParams){
+ .calc_face_normal = true, .use_shapekey = false,
+ }));
+
+ return bm;
+}
+
+static bool groom_bundle_region_from_mesh_fmap(GroomBundle *bundle, Object *scalp_ob)
+{
+ BLI_assert(scalp_ob->type == OB_MESH);
+
+ BKE_groom_bundle_curve_cache_clear(bundle);
+
+ Mesh *me = scalp_ob->data;
+ const int scalp_fmap_nr = BKE_object_facemap_name_index(scalp_ob, bundle->scalp_facemap_name);
+ const int cd_fmap_offset = CustomData_get_offset(&me->pdata, CD_FACEMAP);
+ if (scalp_fmap_nr < 0 || cd_fmap_offset < 0)
+ {
+ return false;
+ }
+
+ BMesh *bm = groom_create_scalp_bmesh(me);
+ bool result = true;
+
+ /* Tag faces in the face map for the BMO walker */
+ {
+ BMFace *f;
+ BMIter iter;
+ BM_ITER_MESH(f, &iter, bm, BM_FACES_OF_MESH)
+ {
+ int fmap = BM_ELEM_CD_GET_INT(f, cd_fmap_offset);
+ BM_elem_flag_set(f, BM_ELEM_TAG, fmap == scalp_fmap_nr);
+ }
+ }
+
+ BMOperator op;
+ BMO_op_initf(bm, &op, (BMO_FLAG_DEFAULTS & ~BMO_FLAG_RESPECT_HIDE),
+ "face_island_boundary faces=%hf", BM_ELEM_TAG);
+
+ BMO_op_exec(bm, &op);
+ if (BMO_error_occurred(bm))
+ {
+ result = false;
+ goto finalize;
+ }
+
+ const int numshapeverts = BMO_slot_buffer_count(op.slots_out, "boundary");
+ bundle->scalp_region = MEM_callocN(sizeof(*bundle->scalp_region) * (numshapeverts + 1), "groom bundle scalp region");
+
+ float center_co[3]; /* average vertex location for placing the center */
+ {
+ BMLoop *l;
+ BMOIter oiter;
+ MeshSample *sample = bundle->scalp_region;
+ zero_v3(center_co);
+ BMO_ITER (l, &oiter, op.slots_out, "boundary", BM_LOOP)
+ {
+ sample->orig_poly = BM_elem_index_get(l->f);
+ sample->orig_loops[0] = BM_elem_index_get(l);
+ sample->orig_verts[0] = BM_elem_index_get(l->v);
+ sample->orig_weights[0] = 1.0f;
+ BLI_assert(BKE_mesh_sample_is_valid(sample));
+
+ add_v3_v3(center_co, l->v->co);
+
+ ++sample;
+ }
+ if (numshapeverts > 0)
+ {
+ mul_v3_fl(center_co, 1.0f / (float)(numshapeverts));
+ }
+ }
+
+ {
+ /* BVH tree for binding the region center location */
+ DerivedMesh *dm = CDDM_from_mesh(me);
+ DM_ensure_tessface(dm);
+ BVHTreeFromMesh bvhtree;
+ //bvhtree_from_mesh_looptri(&bvhtree, dm, 0.0f, 4, 6);
+ bvhtree_from_mesh_faces(&bvhtree, dm, 0.0f, 4, 6);
+ if (bvhtree.tree != NULL) {
+ BVHTreeNearest nearest;
+ nearest.index = -1;
+ nearest.dist_sq = FLT_MAX;
+
+ BLI_bvhtree_find_nearest(bvhtree.tree, center_co, &nearest, bvhtree.nearest_callback, &bvhtree);
+ if (nearest.index >= 0)
+ {
+ /* last sample is the center position */
+ MeshSample *center_sample = &bundle->scalp_region[numshapeverts];
+ BKE_mesh_sample_weights_from_loc(center_sample, dm, nearest.index, nearest.co);
+ BLI_assert(BKE_mesh_sample_is_valid(center_sample));
+ }
+ }
+ else
+ {
+ result = false;
+ }
+
+ free_bvhtree_from_mesh(&bvhtree);
+ dm->release(dm);
+ }
+
+finalize:
+ if (result == true)
+ {
+ groom_shape_rebuild(bundle, numshapeverts, scalp_ob);
+ }
+ else
+ {
+ if (bundle->scalp_region)
+ {
+ MEM_freeN(bundle->scalp_region);
+ bundle->scalp_region = NULL;
+ }
+ }
+
+ BMO_op_finish(bm, &op);
+ BM_mesh_free(bm);
+
+ return result;
+}
+
+bool BKE_groom_bundle_bind(Groom *groom, GroomBundle *bundle, bool force_rebind)
+{
+ if (bundle->scalp_region && !force_rebind)
+ {
+ return true;
+ }
+
+ BKE_groom_bundle_unbind(bundle);
+ if (!groom->scalp_object)
+ {
+ return false;
+ }
+ if (!BKE_object_facemap_find_name(groom->scalp_object, bundle->scalp_facemap_name))
+ {
+ return false;
+ }
+
+ if (groom->scalp_object->type == OB_MESH)
+ {
+ groom_bundle_region_from_mesh_fmap(bundle, groom->scalp_object);
+ }
+
+ return (bundle->scalp_region != NULL);
+}
+
+void BKE_groom_bundle_unbind(GroomBundle *bundle)
+{
+ if (bundle->scalp_region)
+ {
+ MEM_freeN(bundle->scalp_region);
+ bundle->scalp_region = NULL;
+ }
+}
+
+
+/* === Hair System === */
+
+/* Distribute points on the scalp to use as guide curve origins,
+ * then interpolate guide curves from bundles
+ */
+static void groom_generate_guide_curves(
+ Groom *groom,
+ DerivedMesh *scalp,
+ unsigned int seed,
+ int guide_curve_count)
+{
+ struct HairSystem *hsys = groom->hair_system;
+
+ MeshSample *guide_samples = MEM_mallocN(sizeof(*guide_samples) * guide_curve_count, "guide samples");
+ int num_guides;
+ {
+ /* Random distribution of points on the scalp mesh */
+
+ float scalp_area = BKE_hair_calc_surface_area(scalp);
+ float density = BKE_hair_calc_density_from_count(scalp_area, guide_curve_count);
+ float min_distance = BKE_hair_calc_min_distance_from_density(density);
+ MeshSampleGenerator *gen = BKE_mesh_sample_gen_surface_poissondisk(
+ seed,
+ min_distance,
+ guide_curve_count,
+ NULL,
+ NULL);
+
+ BKE_mesh_sample_generator_bind(gen, scalp);
+
+ static const bool use_threads = false;
+ num_guides = BKE_mesh_sample_generate_batch_ex(
+ gen,
+ guide_samples,
+ sizeof(MeshSample),
+ guide_curve_count,
+ use_threads);
+
+ BKE_mesh_sample_free_generator(gen);
+ }
+
+ BKE_hair_guide_curves_begin(hsys, num_guides);
+
+ for (int i = 0; i < num_guides; ++i)
+ {
+ BKE_hair_set_guide_curve(hsys, i, &guide_samples[i], );
+ }
+
+ BKE_hair_guide_curves_end(hsys);
+
+ MEM_freeN(guide_samples);
+}
+
+void BKE_groom_hair_distribute(Groom *groom, unsigned int seed, int hair_count, int guide_curve_count)
+{
+ struct HairSystem *hsys = groom->hair_system;
+
+ BLI_assert(groom->scalp_object);
+ DerivedMesh *scalp = object_get_derived_final(groom->scalp_object, false);
+ if (!scalp)
+ {
+ return;
+ }
+
+ BKE_hair_generate_follicles(hsys, scalp, seed, hair_count);
+
+ unsigned int guide_seed = BLI_ghashutil_combine_hash(seed, BLI_ghashutil_strhash("groom guide curves"));
+ groom_bundle_generate_guide_curves(groom, scalp, guide_seed, guide_curve_count);
+}
+
+
+/* === Curve cache === */
+
+/* forward differencing method for cubic polynomial eval */
+static void groom_forward_diff_cubic(float a, float b, float c, float d, float *p, int it, int stride)
+{
+ float f = (float)it;
+ a *= 1.0f / (f*f*f);
+ b *= 1.0f / (f*f);
+ c *= 1.0f / (f);
+
+ float q0 = d;
+ float q1 = a + b + c;
+ float q2 = 6 * a + 2 * b;
+ float q3 = 6 * a;
+
+ for (int i = 0; i <= it; i++) {
+ *p = q0;
+ p = POINTER_OFFSET(p, stride);
+ q0 += q1;
+ q1 += q2;
+ q2 += q3;
+ }
+}
+
+/* cubic bspline section eval */
+static void groom_eval_curve_cache_section(GroomCurveCache *cache, int curve_res, const float *co0, const float *co1, const float *co2, const float *co3)
+{
+ float a, b, c, d;
+ for (int k = 0; k < 3; ++k)
+ {
+ /* define tangents from segment direction */
+ float n1, n2;
+
+ if (co0)
+ {
+ n1 = 0.5f * (co2[k] - co0[k]);
+ }
+ else
+ {
+ n1 = co2[k] - co1[k];
+ }
+
+ if (co3)
+ {
+ n2 = 0.5f * (co3[k] - co1[k]);
+ }
+ else
+ {
+ n2 = co2[k] - co1[k];
+ }
+
+ /* Hermite spline interpolation */
+ a = 2.0f * (co1[k] - co2[k]) + n1 + n2;
+ b = 3.0f * (co2[k] - co1[k]) - 2.0f * n1 - n2;
+ c = n1;
+ d = co1[k];
+
+ groom_forward_diff_cubic(a, b, c, d, cache->co + k, curve_res, sizeof(*cache));
+ }
+}
+
+static void groom_eval_center_curve_section(
+ GroomBundle *bundle,
+ int curve_res)
+{
+ BLI_assert(bundle->totsections >= 2);
+ BLI_assert(curve_res >= 1);
+
+ /* last cache curve is the center curve */
+ GroomCurveCache *cache = bundle->curvecache + bundle->curvesize * bundle->numshapeverts;
+ for (int i = 0; i < bundle->totsections-1; ++i, cache += curve_res)
+ {
+ const GroomSection *section = &bundle->sections[i];
+ const float *co0 = (i > 0) ? section[-1].center : NULL;
+ const float *co1 = section[0].center;
+ const float *co2 = section[1].center;
+ const float *co3 = (i < bundle->totsections - 2) ? section[2].center : NULL;
+ groom_eval_curve_cache_section(cache, curve_res, co0, co1, co2, co3);
+ }
+}
+
+static void groom_eval_shape_curves(
+ GroomBundle *bundle,
+ int curve_res)
+{
+ BLI_assert(bundle->totsections >= 2);
+ BLI_assert(curve_res >= 1);
+
+ for (int i = 0; i < bundle->numshapeverts; ++i)
+ {
+ GroomCurveCache *cache = bundle->curvecache + i * bundle->curvesize;
+ for (int j = 0; j < bundle->totsections-1; ++j, cache += curve_res)
+ {
+ const GroomSection *section = &bundle->sections[j];
+ const float *co0 = NULL, *co1 = NULL, *co2 =NULL, *co3 = NULL;
+
+ float vec0[3], vec1[3], vec2[3], vec3[3];
+ if (j > 0)
+ {
+ const GroomSectionVertex *v0 = &bundle->verts[(j-1) * bundle->numshapeverts + i];
+ float tmp[3] = {0.0f, 0.0f, 0.0f};
+ copy_v2_v2(tmp, v0->co);
+ mul_v3_m3v3(vec0, section[-1].mat, tmp);
+ add_v3_v3(vec0, section[-1].center);
+ co0 = vec0;
+ }
+ {
+ const GroomSectionVertex *v1 = &bundle->verts[(j) * bundle->numshapeverts + i];
+ float tmp[3] = {0.0f, 0.0f, 0.0f};
+ copy_v2_v2(tmp, v1->co);
+ mul_v3_m3v3(vec1, section[0].mat, tmp);
+ add_v3_v3(vec1, section[0].center);
+ co1 = vec1;
+ }
+ {
+ const GroomSectionVertex *v2 = &bundle->verts[(j+1) * bundle->numshapeverts + i];
+ float tmp[3] = {0.0f, 0.0f, 0.0f};
+ copy_v2_v2(tmp, v2->co);
+ mul_v3_m3v3(vec2, section[+1].mat, tmp);
+ add_v3_v3(vec2, section[+1].center);
+ co2 = vec2;
+ }
+ if (j < bundle->totsections - 2)
+ {
+ const GroomSectionVertex *v3 = &bundle->verts[(j+2) * bundle->numshapeverts + i];
+ float tmp[3] = {0.0f, 0.0f, 0.0f};
+ copy_v2_v2(tmp, v3->co);
+ mul_v3_m3v3(vec3, section[+2].mat, tmp);
+ add_v3_v3(vec3, section[+2].center);
+ co3 = vec3;
+ }
+
+ groom_eval_curve_cache_section(cache, curve_res, co0, co1, co2, co3);
+ }
+ }
+}
+
+static void groom_eval_curve_step(float mat[3][3], const float mat_prev[3][3], const float co0[3], const float co1[3])
+{
+ float dir[3];
+ sub_v3_v3v3(dir, co1, co0);
+ normalize_v3(dir);
+
+ float dir_prev[3];
+ normalize_v3_v3(dir_prev, mat_prev[2]);
+ float rot[3][3];
+ rotation_between_vecs_to_mat3(rot, dir_prev, dir);
+
+ mul_m3_m3m3(mat, rot, mat_prev);
+}
+
+static void groom_eval_section_mats(GroomBundle *bundle, int curve_res)
+{
+ const int curvesize = bundle->curvesize;
+ const int numshapeverts = bundle->numshapeverts;
+
+ float mat[3][3];
+ unit_m3(mat); // TODO take from scalp mesh sample
+
+ GroomSection *section = bundle->sections;
+ /* last curve cache is center curve */
+ const GroomCurveCache *cache = bundle->curvecache + bundle->curvesize * numshapeverts;
+
+ /* align to first segment */
+ groom_eval_curve_step(mat, mat, cache[0].co, cache[1].co);
+ copy_m3_m3(section->mat, mat);
+ ++cache;
+ ++section;
+
+ for (int i = 1; i < curvesize - 1; ++i, ++cache)
+ {
+ /* align interior points to average of prev and next segment */
+ groom_eval_curve_step(mat, mat, cache[-1].co, cache[+1].co);
+
+ if (i % curve_res == 0)
+ {
+ /* set section matrix */
+ copy_m3_m3(section->mat, mat);
+ ++section;
+ }
+ }
+
+ /* align to last segment */
+ groom_eval_curve_step(mat, mat, cache[-1].co, cache[0].co);
+ /* last section is not handled in the loop */
+ copy_m3_m3(section->mat, mat);
+}
+
+void BKE_groom_curve_cache_update(Groom *groom)
+{
+ ListBase *bundles = (groom->editgroom ? &groom->editgroom->bundles : &groom->bundles);
+
+ for (GroomBundle *bundle = bundles->first; bundle; bundle = bundle->next)
+ {
+ const int totsections = bundle->totsections;
+ const int numshapeverts = bundle->numshapeverts;
+ const int curve_res = groom->curve_res;
+ if (totsections == 0)
+ {
+ BKE_groom_bundle_curve_cache_clear(bundle);
+
+ /* nothing to do */
+ continue;
+ }
+
+ bundle->curvesize = (totsections-1) * curve_res + 1;
+ bundle->totcurvecache = bundle->curvesize * (numshapeverts + 1);
+ bundle->curvecache = MEM_reallocN_id(bundle->curvecache, sizeof(GroomCurveCache) * bundle->totcurvecache, "groom bundle curve cache");
+
+ if (totsections == 1)
+ {
+ /* degenerate case */
+ copy_v3_v3(bundle->curvecache[numshapeverts].co, bundle->sections[0].center);
+
+ unit_m3(bundle->sections[0].mat);
+
+ for (int i = 0; i < numshapeverts; ++i)
+ {
+ copy_v2_v2(bundle->curvecache[i].co, bundle->verts[i].co);
+ bundle->curvecache[i].co[2] = 0.0f;
+ }
+
+ continue;
+ }
+
+ /* Calculate center curve */
+ groom_eval_center_curve_section(bundle, curve_res);
+
+ /* Calculate coordinate frames for sections */
+ groom_eval_section_mats(bundle, curve_res);
+
+ /* Calculate shape curves */
+ groom_eval_shape_curves(bundle, curve_res);
+ }
+}
+
+void BKE_groom_curve_cache_clear(Groom *groom)
+{
+ for (GroomBundle *bundle = groom->bundles.first; bundle; bundle = bundle->next)
+ {
+ BKE_groom_bundle_curve_cache_clear(bundle);
+ }
+ if (groom->editgroom)
+ {
+ for (GroomBundle *bundle = groom->editgroom->bundles.first; bundle; bundle = bundle->next)
+ {
+ BKE_groom_bundle_curve_cache_clear(bundle);
+ }
+ }
+}
+
+
+/* === Depsgraph evaluation === */
+
+void BKE_groom_eval_geometry(const EvaluationContext *UNUSED(eval_ctx), Groom *groom)
+{
+ if (G.debug & G_DEBUG_DEPSGRAPH) {
+ printf("%s on %s\n", __func__, groom->id.name);
+ }
+
+ /* calculate curves for interpolating shapes */
+ BKE_groom_curve_cache_update(groom);
+
+ /* generate actual guide curves for hair */
+ BKE_groom_hair_update_guide_curves(groom);
+
+ if (groom->bb == NULL || (groom->bb->flag & BOUNDBOX_DIRTY)) {
+ BKE_groom_boundbox_calc(groom, NULL, NULL);
+ }
+}
+
+
+/* === Draw Cache === */
+
+void (*BKE_groom_batch_cache_dirty_cb)(Groom* groom, int mode) = NULL;
+void (*BKE_groom_batch_cache_free_cb)(Groom* groom) = NULL;
+
+void BKE_groom_batch_cache_dirty(Groom* groom, int mode)
+{
+ if (groom->batch_cache)
+ {
+ BKE_groom_batch_cache_dirty_cb(groom, mode);
+ }
+}
+
+void BKE_groom_batch_cache_free(Groom *groom)
+{
+ if (groom->batch_cache)
+ {
+ BKE_groom_batch_cache_free_cb(groom);
+ }
+}
+
+/* === Utility functions (DerivedMesh SOON TO BE DEPRECATED!) === */
+
+struct DerivedMesh* BKE_groom_get_scalp(struct Groom *groom)
+{
+ return groom->scalp_object ? groom->scalp_object->derivedFinal : NULL;
+}
diff --git a/source/blender/blenkernel/intern/hair.c b/source/blender/blenkernel/intern/hair.c
new file mode 100644
index 00000000000..5a9af8aba58
--- /dev/null
+++ b/source/blender/blenkernel/intern/hair.c
@@ -0,0 +1,388 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) Blender Foundation
+ * All rights reserved.
+ *
+ * The Original Code is: all of this file.
+ *
+ * Contributor(s): Lukas Toenne
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file blender/blenkernel/intern/hair.c
+ * \ingroup bke
+ */
+
+#include <limits.h>
+#include <string.h>
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_kdtree.h"
+#include "BLI_listbase.h"
+#include "BLI_math.h"
+#include "BLI_rand.h"
+#include "BLI_sort.h"
+#include "BLI_string_utf8.h"
+#include "BLI_string_utils.h"
+
+#include "DNA_hair_types.h"
+#include "DNA_object_types.h"
+
+#include "BKE_DerivedMesh.h"
+#include "BKE_hair.h"
+#include "BKE_library.h"
+#include "BKE_mesh.h"
+#include "BKE_mesh_sample.h"
+
+#include "BLT_translation.h"
+
+HairSystem* BKE_hair_new(void)
+{
+ HairSystem *hair = MEM_callocN(sizeof(HairSystem), "hair system");
+
+ hair->pattern = MEM_callocN(sizeof(HairPattern), "hair pattern");
+
+ hair->material_index = 1;
+
+ return hair;
+}
+
+HairSystem* BKE_hair_copy(HairSystem *hsys)
+{
+ HairSystem *nhsys = MEM_dupallocN(hsys);
+
+ if (hsys->pattern)
+ {
+ nhsys->pattern = MEM_dupallocN(hsys->pattern);
+ nhsys->pattern->follicles = MEM_dupallocN(hsys->pattern->follicles);
+ }
+
+ if (hsys->curves)
+ {
+ nhsys->curves = MEM_dupallocN(hsys->curves);
+ }
+ if (hsys->verts)
+ {
+ nhsys->verts = MEM_dupallocN(hsys->verts);
+ }
+
+ nhsys->draw_batch_cache = NULL;
+ nhsys->draw_texture_cache = NULL;
+
+ return nhsys;
+}
+
+void BKE_hair_free(HairSystem *hsys)
+{
+ BKE_hair_batch_cache_free(hsys);
+
+ if (hsys->curves)
+ {
+ MEM_freeN(hsys->curves);
+ }
+ if (hsys->verts)
+ {
+ MEM_freeN(hsys->verts);
+ }
+
+ if (hsys->pattern)
+ {
+ if (hsys->pattern->follicles)
+ {
+ MEM_freeN(hsys->pattern->follicles);
+ }
+ MEM_freeN(hsys->pattern);
+ }
+
+ MEM_freeN(hsys);
+}
+
+/* Calculate surface area of a scalp mesh */
+float BKE_hair_calc_surface_area(struct DerivedMesh *scalp)
+{
+ BLI_assert(scalp != NULL);
+
+ int numpolys = scalp->getNumPolys(scalp);
+ MPoly *mpolys = scalp->getPolyArray(scalp);
+ MLoop *mloops = scalp->getLoopArray(scalp);
+ MVert *mverts = scalp->getVertArray(scalp);
+
+ float area = 0.0f;
+ for (int i = 0; i < numpolys; ++i)
+ {
+ area += BKE_mesh_calc_poly_area(&mpolys[i], mloops + mpolys[i].loopstart, mverts);
+ }
+ return area;
+}
+
+/* Calculate a density value based on surface area and sample count */
+float BKE_hair_calc_density_from_count(float area, int count)
+{
+ return area > 0.0f ? count / area : 0.0f;
+}
+
+/* Calculate maximum sample count based on surface area and density */
+int BKE_hair_calc_max_count_from_density(float area, float density)
+{
+ return (int)(density * area);
+}
+
+/* Calculate a density value based on a minimum distance */
+float BKE_hair_calc_density_from_min_distance(float min_distance)
+{
+ // max. circle packing density (sans pi factor): 1 / (2 * sqrt(3))
+ static const float max_factor = 0.288675135;
+
+ return min_distance > 0.0f ? max_factor / (min_distance * min_distance) : 0.0f;
+}
+
+/* Calculate a minimum distance based on density */
+float BKE_hair_calc_min_distance_from_density(float density)
+{
+ // max. circle packing density (sans pi factor): 1 / (2 * sqrt(3))
+ static const float max_factor = 0.288675135;
+
+ return density > 0.0f ? sqrt(max_factor / density) : 0.0f;
+}
+
+/* Distribute hair follicles on a scalp mesh */
+void BKE_hair_generate_follicles(
+ HairSystem* hsys,
+ struct DerivedMesh *scalp,
+ unsigned int seed,
+ int count)
+{
+ HairPattern *pattern = hsys->pattern;
+
+ // Limit max_count to theoretical limit based on area
+ float scalp_area = BKE_hair_calc_surface_area(scalp);
+ float density = BKE_hair_calc_density_from_count(scalp_area, count);
+ float min_distance = BKE_hair_calc_min_distance_from_density(density);
+
+ if (pattern->follicles)
+ {
+ MEM_freeN(pattern->follicles);
+ }
+ pattern->follicles = MEM_callocN(sizeof(HairFollicle) * count, "hair follicles");
+
+ {
+ MeshSampleGenerator *gen = BKE_mesh_sample_gen_surface_poissondisk(seed, min_distance, count, NULL, NULL);
+
+ BKE_mesh_sample_generator_bind(gen, scalp);
+
+ static const bool use_threads = false;
+ pattern->num_follicles = BKE_mesh_sample_generate_batch_ex(
+ gen,
+ &pattern->follicles->mesh_sample,
+ sizeof(HairFollicle),
+ count,
+ use_threads);
+
+ BKE_mesh_sample_free_generator(gen);
+ }
+
+ hsys->flag |= HAIR_SYSTEM_UPDATE_FOLLICLE_BINDING;
+ BKE_hair_batch_cache_dirty(hsys, BKE_HAIR_BATCH_DIRTY_ALL);
+}
+
+/* ================================= */
+
+void BKE_hair_guide_curves_begin(HairSystem *hsys, int totcurves)
+{
+ if (totcurves != hsys->totcurves)
+ {
+ hsys->curves = MEM_reallocN(hsys->curves, sizeof(HairGuideCurve) * totcurves);
+ hsys->totcurves = totcurves;
+
+ hsys->flag |= HAIR_SYSTEM_UPDATE_FOLLICLE_BINDING;
+ BKE_hair_batch_cache_dirty(hsys, BKE_HAIR_BATCH_DIRTY_ALL);
+ }
+}
+
+void BKE_hair_set_guide_curve(HairSystem *hsys, int index, const MeshSample *mesh_sample, int numverts)
+{
+ BLI_assert(index <= hsys->totcurves);
+
+ HairGuideCurve *curve = &hsys->curves[index];
+ memcpy(&curve->mesh_sample, mesh_sample, sizeof(MeshSample));
+ curve->numverts = numverts;
+
+ hsys->flag |= HAIR_SYSTEM_UPDATE_FOLLICLE_BINDING;
+ BKE_hair_batch_cache_dirty(hsys, BKE_HAIR_BATCH_DIRTY_ALL);
+}
+
+void BKE_hair_guide_curves_end(HairSystem *hsys)
+{
+ /* Recalculate vertex count and start offsets in curves */
+ int vertstart = 0;
+ for (int i = 0; i < hsys->totcurves; ++i)
+ {
+ hsys->curves[i].vertstart = vertstart;
+ vertstart += hsys->curves[i].numverts;
+ }
+
+ if (vertstart != hsys->totverts)
+ {
+ hsys->verts = MEM_reallocN(hsys->verts, sizeof(HairGuideVertex) * vertstart);
+ hsys->totverts = vertstart;
+
+ BKE_hair_batch_cache_dirty(hsys, BKE_HAIR_BATCH_DIRTY_ALL);
+ }
+}
+
+void BKE_hair_set_guide_vertex(HairSystem *hsys, int index, int flag, const float co[3])
+{
+ BLI_assert(index <= hsys->totverts);
+
+ HairGuideVertex *vertex = &hsys->verts[index];
+ vertex->flag = flag;
+ copy_v3_v3(vertex->co, co);
+
+ BKE_hair_batch_cache_dirty(hsys, BKE_HAIR_BATCH_DIRTY_ALL);
+}
+
+/* ================================= */
+
+BLI_INLINE void hair_fiber_verify_weights(HairFollicle *follicle)
+{
+ const float *w = follicle->parent_weight;
+
+ BLI_assert(w[0] >= 0.0f && w[1] >= 0.0f && w[2] >= 0.0f && w[3] >= 0.0f);
+ float sum = w[0] + w[1] + w[2] + w[3];
+ float epsilon = 1.0e-2;
+ BLI_assert(sum > 1.0f - epsilon && sum < 1.0f + epsilon);
+ UNUSED_VARS(sum, epsilon);
+
+ BLI_assert(w[0] >= w[1] && w[1] >= w[2] && w[2] >= w[3]);
+}
+
+static void hair_fiber_sort_weights(HairFollicle *follicle)
+{
+ unsigned int *idx = follicle->parent_index;
+ float *w = follicle->parent_weight;
+
+#define FIBERSWAP(a, b) \
+ SWAP(unsigned int, idx[a], idx[b]); \
+ SWAP(float, w[a], w[b]);
+
+ for (int k = 0; k < 3; ++k) {
+ int maxi = k;
+ float maxw = w[k];
+ for (int i = k+1; i < 4; ++i) {
+ if (w[i] > maxw) {
+ maxi = i;
+ maxw = w[i];
+ }
+ }
+ if (maxi != k)
+ FIBERSWAP(k, maxi);
+ }
+
+#undef FIBERSWAP
+}
+
+static void hair_fiber_find_closest_strand(
+ HairFollicle *follicle,
+ const float loc[3],
+ const KDTree *tree,
+ const float (*strandloc)[3])
+{
+ /* Use the 3 closest strands for interpolation.
+ * Note that we have up to 4 possible weights, but we
+ * only look for a triangle with this method.
+ */
+ KDTreeNearest nearest[3];
+ const float *sloc[3] = {NULL};
+ int k, found = BLI_kdtree_find_nearest_n(tree, loc, nearest, 3);
+ for (k = 0; k < found; ++k) {
+ follicle->parent_index[k] = (unsigned int)nearest[k].index;
+ sloc[k] = strandloc[nearest[k].index];
+ }
+ for (; k < 4; ++k) {
+ follicle->parent_index[k] = HAIR_STRAND_INDEX_NONE;
+ follicle->parent_weight[k] = 0.0f;
+ }
+
+ /* calculate barycentric interpolation weights */
+ if (found == 3) {
+ float closest[3];
+ closest_on_tri_to_point_v3(closest, loc, sloc[0], sloc[1], sloc[2]);
+
+ float w[3];
+ interp_weights_tri_v3(w, sloc[0], sloc[1], sloc[2], closest);
+ copy_v3_v3(follicle->parent_weight, w);
+ /* float precisions issues can cause slightly negative weights */
+ CLAMP3(follicle->parent_weight, 0.0f, 1.0f);
+ }
+ else if (found == 2) {
+ follicle->parent_weight[1] = line_point_factor_v3(loc, sloc[0], sloc[1]);
+ follicle->parent_weight[0] = 1.0f - follicle->parent_weight[1];
+ /* float precisions issues can cause slightly negative weights */
+ CLAMP2(follicle->parent_weight, 0.0f, 1.0f);
+ }
+ else if (found == 1) {
+ follicle->parent_weight[0] = 1.0f;
+ }
+
+ hair_fiber_sort_weights(follicle);
+}
+
+void BKE_hair_bind_follicles(HairSystem *hsys, DerivedMesh *scalp)
+{
+ if (!(hsys->flag & HAIR_SYSTEM_UPDATE_FOLLICLE_BINDING))
+ {
+ return;
+ }
+ hsys->flag &= ~HAIR_SYSTEM_UPDATE_FOLLICLE_BINDING;
+
+ HairPattern *pattern = hsys->pattern;
+ const int num_strands = hsys->totcurves;
+ if (num_strands == 0 || !pattern)
+ return;
+
+ float (*strandloc)[3] = MEM_mallocN(sizeof(float) * 3 * num_strands, "strand locations");
+ {
+ for (int i = 0; i < num_strands; ++i) {
+ float nor[3], tang[3];
+ if (!BKE_mesh_sample_eval(scalp, &hsys->curves[i].mesh_sample, strandloc[i], nor, tang)) {
+ zero_v3(strandloc[i]);
+ }
+ }
+ }
+
+ KDTree *tree = BLI_kdtree_new(num_strands);
+ for (int c = 0; c < num_strands; ++c) {
+ BLI_kdtree_insert(tree, c, strandloc[c]);
+ }
+ BLI_kdtree_balance(tree);
+
+ HairFollicle *follicle = pattern->follicles;
+ for (int i = 0; i < pattern->num_follicles; ++i, ++follicle) {
+ float loc[3], nor[3], tang[3];
+ if (BKE_mesh_sample_eval(scalp, &follicle->mesh_sample, loc, nor, tang)) {
+ hair_fiber_find_closest_strand(follicle, loc, tree, strandloc);
+ hair_fiber_verify_weights(follicle);
+ }
+ }
+
+ BLI_kdtree_free(tree);
+ MEM_freeN(strandloc);
+}
+
diff --git a/source/blender/blenkernel/intern/hair_draw.c b/source/blender/blenkernel/intern/hair_draw.c
new file mode 100644
index 00000000000..37de8bc742d
--- /dev/null
+++ b/source/blender/blenkernel/intern/hair_draw.c
@@ -0,0 +1,360 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) Blender Foundation
+ * All rights reserved.
+ *
+ * The Original Code is: all of this file.
+ *
+ * Contributor(s): Lukas Toenne
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file blender/blenkernel/intern/hair_draw.c
+ * \ingroup bke
+ */
+
+#include <string.h>
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_math.h"
+#include "BLI_kdtree.h"
+#include "BLI_rand.h"
+
+#include "DNA_hair_types.h"
+
+#include "BKE_DerivedMesh.h"
+#include "BKE_mesh_sample.h"
+#include "BKE_hair.h"
+
+/* === Draw Settings === */
+
+HairDrawSettings* BKE_hair_draw_settings_new(void)
+{
+ HairDrawSettings *draw_settings = MEM_callocN(sizeof(HairDrawSettings), "hair draw settings");
+
+ draw_settings->follicle_mode = HAIR_DRAW_FOLLICLE_NONE;
+
+ return draw_settings;
+}
+
+HairDrawSettings* BKE_hair_draw_settings_copy(HairDrawSettings *draw_settings)
+{
+ HairDrawSettings *ndraw_settings = MEM_dupallocN(draw_settings);
+ return ndraw_settings;
+}
+
+void BKE_hair_draw_settings_free(HairDrawSettings *draw_settings)
+{
+ MEM_freeN(draw_settings);
+}
+
+/* === Draw Cache === */
+
+static int hair_get_strand_subdiv_numverts(int numstrands, int numverts, int subdiv)
+{
+ return ((numverts - numstrands) << subdiv) + numstrands;
+}
+
+BLI_INLINE int hair_get_strand_subdiv_length(int orig_length, int subdiv)
+{
+ return ((orig_length - 1) << subdiv) + 1;
+}
+
+int* BKE_hair_get_fiber_lengths(const HairSystem *hsys, int subdiv)
+{
+ if (!hsys->pattern) {
+ return NULL;
+ }
+
+ const int totfibers = hsys->pattern->num_follicles;
+ int *fiber_length = MEM_mallocN(sizeof(int) * totfibers, "fiber length");
+
+ const int num_strands = hsys->totcurves;
+ /* Cache subdivided lengths for repeated lookup */
+ int *lengths = MEM_mallocN(sizeof(int) * num_strands, "strand length");
+ for (int i = 0; i < hsys->totcurves; ++i) {
+ lengths[i] = hair_get_strand_subdiv_length(hsys->curves[i].numverts, subdiv);
+ }
+
+ // Calculate the length of the fiber from the weighted average of its guide strands
+ HairFollicle *follicle = hsys->pattern->follicles;
+ for (int i = 0; i < totfibers; ++i, ++follicle) {
+ float fiblen = 0.0f;
+
+ for (int k = 0; k < 4; ++k) {
+ int si = follicle->parent_index[k];
+ float sw = follicle->parent_weight[k];
+ if (si == HAIR_STRAND_INDEX_NONE || sw == 0.0f) {
+ break;
+ }
+ BLI_assert(si < num_strands);
+
+ fiblen += (float)lengths[si] * sw;
+ }
+
+ // use rounded number of segments
+ fiber_length[i] = (int)(fiblen + 0.5f);
+ }
+
+ MEM_freeN(lengths);
+
+ return fiber_length;
+}
+
+typedef struct HairFiberTextureBuffer {
+ unsigned int parent_index[4];
+ float parent_weight[4];
+ float root_position[3];
+ int pad;
+} HairFiberTextureBuffer;
+BLI_STATIC_ASSERT_ALIGN(HairFiberTextureBuffer, 8)
+
+typedef struct HairStrandVertexTextureBuffer {
+ float co[3];
+ float nor[3];
+ float tang[3];
+ int pad;
+} HairStrandVertexTextureBuffer;
+BLI_STATIC_ASSERT_ALIGN(HairStrandVertexTextureBuffer, 8)
+
+typedef struct HairStrandMapTextureBuffer {
+ unsigned int vertex_start;
+ unsigned int vertex_count;
+} HairStrandMapTextureBuffer;
+BLI_STATIC_ASSERT_ALIGN(HairStrandMapTextureBuffer, 8)
+
+static void hair_strand_transport_frame(const float co1[3], const float co2[3],
+ float prev_tang[3], float prev_nor[3],
+ float r_tang[3], float r_nor[3])
+{
+ /* segment direction */
+ sub_v3_v3v3(r_tang, co2, co1);
+ normalize_v3(r_tang);
+
+ /* rotate the frame */
+ float rot[3][3];
+ rotation_between_vecs_to_mat3(rot, prev_tang, r_tang);
+ mul_v3_m3v3(r_nor, rot, prev_nor);
+
+ copy_v3_v3(prev_tang, r_tang);
+ copy_v3_v3(prev_nor, r_nor);
+}
+
+static void hair_strand_calc_vectors(const float (*positions)[3], int num_verts, float rootmat[3][3],
+ HairStrandVertexTextureBuffer *strand)
+{
+ for (int i = 0; i < num_verts; ++i) {
+ copy_v3_v3(strand[i].co, positions[i]);
+ }
+
+ // Calculate tangent and normal vectors
+ {
+ BLI_assert(num_verts >= 2);
+
+ float prev_tang[3], prev_nor[3];
+
+ copy_v3_v3(prev_tang, rootmat[2]);
+ copy_v3_v3(prev_nor, rootmat[0]);
+
+ hair_strand_transport_frame(strand[0].co, strand[1].co,
+ prev_tang, prev_nor,
+ strand[0].tang, strand[0].nor);
+
+ for (int i = 1; i < num_verts - 1; ++i)
+ {
+ hair_strand_transport_frame(strand[i-1].co, strand[i+1].co,
+ prev_tang, prev_nor,
+ strand[i].tang, strand[i].nor);
+ }
+
+ hair_strand_transport_frame(strand[num_verts-2].co, strand[num_verts-1].co,
+ prev_tang, prev_nor,
+ strand[num_verts-1].tang, strand[num_verts-1].nor);
+ }
+}
+
+static int hair_strand_subdivide(const HairSystem *hsys, const HairGuideCurve* curve, int subdiv, float (*verts)[3])
+{
+ {
+ /* Move vertex positions from the dense array to their initial configuration for subdivision. */
+ const int step = (1 << subdiv);
+ float (*dst)[3] = verts;
+ int vertend = curve->vertstart + curve->numverts;
+ for (int i = curve->vertstart; i < vertend; ++i) {
+ copy_v3_v3(*dst, hsys->verts[i].co);
+ dst += step;
+ }
+ }
+
+ /* Subdivide */
+ for (int d = 0; d < subdiv; ++d) {
+ const int num_edges = (curve->numverts - 1) << d;
+ const int hstep = 1 << (subdiv - d - 1);
+ const int step = 1 << (subdiv - d);
+
+ /* Calculate edge points */
+ {
+ int index = 0;
+ for (int k = 0; k < num_edges; ++k, index += step) {
+ add_v3_v3v3(verts[index + hstep], verts[index], verts[index + step]);
+ mul_v3_fl(verts[index + hstep], 0.5f);
+ }
+ }
+
+ /* Move original points */
+ {
+ int index = step;
+ for (int k = 1; k < num_edges; ++k, index += step) {
+ add_v3_v3v3(verts[index], verts[index - hstep], verts[index + hstep]);
+ mul_v3_fl(verts[index], 0.5f);
+ }
+ }
+ }
+
+ const int num_verts = ((curve->numverts - 1) << subdiv) + 1;
+ return num_verts;
+}
+
+static void hair_get_strand_buffer(
+ const HairSystem *hsys,
+ DerivedMesh *scalp,
+ int subdiv,
+ HairStrandMapTextureBuffer *strand_map_buffer,
+ HairStrandVertexTextureBuffer *strand_vertex_buffer)
+{
+ const int numverts = hair_get_strand_subdiv_numverts(hsys->totcurves, hsys->totverts, subdiv);
+
+ float (*vertco)[3] = MEM_mallocN(sizeof(float[3]) * numverts, "strand vertex positions subdivided");
+
+ HairStrandMapTextureBuffer *smap = strand_map_buffer;
+ HairStrandVertexTextureBuffer *svert = strand_vertex_buffer;
+ int vertex_start = 0;
+ for (int i = 0; i < hsys->totcurves; ++i) {
+ const HairGuideCurve *curve = &hsys->curves[i];
+ const int len_orig = curve->numverts;
+ const int len = hair_get_strand_subdiv_length(len_orig, subdiv);
+ smap->vertex_start = vertex_start;
+ smap->vertex_count = len;
+
+ hair_strand_subdivide(hsys, curve, subdiv, vertco + vertex_start);
+
+ {
+ float pos[3];
+ float matrix[3][3];
+ BKE_mesh_sample_eval(scalp, &hsys->curves[i].mesh_sample, pos, matrix[2], matrix[0]);
+ cross_v3_v3v3(matrix[1], matrix[2], matrix[0]);
+ hair_strand_calc_vectors(vertco + vertex_start, len, matrix, svert);
+ }
+
+ vertex_start += len;
+ ++smap;
+ svert += len;
+ }
+
+ MEM_freeN(vertco);
+}
+
+static void hair_get_fiber_buffer(const HairSystem* hsys, DerivedMesh *scalp,
+ HairFiberTextureBuffer *fiber_buf)
+{
+ if (hsys->pattern)
+ {
+ const int totfibers = hsys->pattern->num_follicles;
+ HairFiberTextureBuffer *fb = fiber_buf;
+
+ HairFollicle *follicle = hsys->pattern->follicles;
+ float nor[3], tang[3];
+ for (int i = 0; i < totfibers; ++i, ++fb, ++follicle) {
+ BKE_mesh_sample_eval(scalp, &follicle->mesh_sample, fb->root_position, nor, tang);
+ for (int k = 0; k < 4; ++k)
+ {
+ fb->parent_index[k] = follicle->parent_index[k];
+ fb->parent_weight[k] = follicle->parent_weight[k];
+ }
+ }
+ }
+}
+
+void BKE_hair_get_texture_buffer_size(
+ const HairSystem *hsys,
+ int subdiv,
+ int *r_size,
+ int *r_strand_map_start,
+ int *r_strand_vertex_start,
+ int *r_fiber_start)
+{
+ int numstrands = hsys->totcurves;
+ int numverts_orig = hsys->totverts;
+ int numfibers = hsys->pattern ? hsys->pattern->num_follicles : 0;
+ const int numverts = hair_get_strand_subdiv_numverts(numstrands, numverts_orig, subdiv);
+ *r_strand_map_start = 0;
+ *r_strand_vertex_start = *r_strand_map_start + numstrands * sizeof(HairStrandMapTextureBuffer);
+ *r_fiber_start = *r_strand_vertex_start + numverts * sizeof(HairStrandVertexTextureBuffer);
+ *r_size = *r_fiber_start + numfibers * sizeof(HairFiberTextureBuffer);
+}
+
+void BKE_hair_get_texture_buffer(
+ const HairSystem *hsys,
+ DerivedMesh *scalp,
+ int subdiv,
+ void *buffer)
+{
+ int size, strand_map_start, strand_vertex_start, fiber_start;
+ BKE_hair_get_texture_buffer_size(hsys, subdiv, &size, &strand_map_start, &strand_vertex_start, &fiber_start);
+
+ if (scalp)
+ {
+ HairStrandMapTextureBuffer *strand_map = (HairStrandMapTextureBuffer*)((char*)buffer + strand_map_start);
+ HairStrandVertexTextureBuffer *strand_verts = (HairStrandVertexTextureBuffer*)((char*)buffer + strand_vertex_start);
+ HairFiberTextureBuffer *fibers = (HairFiberTextureBuffer*)((char*)buffer + fiber_start);
+
+ hair_get_strand_buffer(
+ hsys,
+ scalp,
+ subdiv,
+ strand_map,
+ strand_verts);
+ hair_get_fiber_buffer(
+ hsys,
+ scalp,
+ fibers);
+ }
+ else
+ {
+ memset(buffer, 0, size);
+ }
+}
+
+void (*BKE_hair_batch_cache_dirty_cb)(HairSystem* hsys, int mode) = NULL;
+void (*BKE_hair_batch_cache_free_cb)(HairSystem* hsys) = NULL;
+
+void BKE_hair_batch_cache_dirty(HairSystem* hsys, int mode)
+{
+ if (hsys->draw_batch_cache) {
+ BKE_hair_batch_cache_dirty_cb(hsys, mode);
+ }
+}
+
+void BKE_hair_batch_cache_free(HairSystem* hsys)
+{
+ if (hsys->draw_batch_cache || hsys->draw_texture_cache) {
+ BKE_hair_batch_cache_free_cb(hsys);
+ }
+}
diff --git a/source/blender/blenkernel/intern/idcode.c b/source/blender/blenkernel/intern/idcode.c
index 487635f06ad..2c1684557d4 100644
--- a/source/blender/blenkernel/intern/idcode.c
+++ b/source/blender/blenkernel/intern/idcode.c
@@ -63,6 +63,7 @@ static IDType idtypes[] = {
{ ID_CF, "CacheFile", "cache_files", BLT_I18NCONTEXT_ID_CACHEFILE, IDTYPE_FLAGS_ISLINKABLE },
{ ID_CU, "Curve", "curves", BLT_I18NCONTEXT_ID_CURVE, IDTYPE_FLAGS_ISLINKABLE },
{ ID_GD, "GPencil", "grease_pencil", BLT_I18NCONTEXT_ID_GPENCIL, IDTYPE_FLAGS_ISLINKABLE }, /* rename gpencil */
+ { ID_GM, "Groom", "grooms", BLT_I18NCONTEXT_ID_GROOM, IDTYPE_FLAGS_ISLINKABLE },
{ ID_GR, "Group", "groups", BLT_I18NCONTEXT_ID_GROUP, IDTYPE_FLAGS_ISLINKABLE },
{ ID_IM, "Image", "images", BLT_I18NCONTEXT_ID_IMAGE, IDTYPE_FLAGS_ISLINKABLE },
{ ID_IP, "Ipo", "ipos", "", IDTYPE_FLAGS_ISLINKABLE }, /* deprecated */
@@ -281,6 +282,7 @@ int BKE_idcode_to_index(const short idcode)
CASE_IDINDEX(CF);
CASE_IDINDEX(CU);
CASE_IDINDEX(GD);
+ CASE_IDINDEX(GM);
CASE_IDINDEX(GR);
CASE_IDINDEX(IM);
CASE_IDINDEX(KE);
diff --git a/source/blender/blenkernel/intern/library.c b/source/blender/blenkernel/intern/library.c
index 6ec2d223e84..f7f0991238f 100644
--- a/source/blender/blenkernel/intern/library.c
+++ b/source/blender/blenkernel/intern/library.c
@@ -47,8 +47,9 @@
#include "DNA_brush_types.h"
#include "DNA_cachefile_types.h"
#include "DNA_camera_types.h"
-#include "DNA_group_types.h"
#include "DNA_gpencil_types.h"
+#include "DNA_groom_types.h"
+#include "DNA_group_types.h"
#include "DNA_ipo_types.h"
#include "DNA_key_types.h"
#include "DNA_lamp_types.h"
@@ -97,6 +98,7 @@
#include "BKE_global.h"
#include "BKE_group.h"
#include "BKE_gpencil.h"
+#include "BKE_groom.h"
#include "BKE_idcode.h"
#include "BKE_idprop.h"
#include "BKE_image.h"
@@ -475,6 +477,9 @@ bool id_make_local(Main *bmain, ID *id, const bool test, const bool lib_local)
case ID_CF:
if (!test) BKE_cachefile_make_local(bmain, (CacheFile *)id, lib_local);
return true;
+ case ID_GM:
+ if (!test) BKE_groom_make_local(bmain, (Groom *)id, lib_local);
+ return true;
case ID_WS:
case ID_SCR:
/* A bit special: can be appended but not linked. Return false
@@ -659,6 +664,9 @@ bool BKE_id_copy_ex(Main *bmain, const ID *id, ID **r_newid, const int flag, con
case ID_VF:
BKE_vfont_copy_data(bmain, (VFont *)*r_newid, (VFont *)id, flag);
break;
+ case ID_GM:
+ BKE_groom_copy_data(bmain, (Groom *)*r_newid, (Groom *)id, flag);
+ break;
case ID_LI:
case ID_SCR:
case ID_WM:
@@ -744,6 +752,7 @@ void BKE_id_swap(Main *bmain, ID *id_a, ID *id_b)
CASE_SWAP(ID_PAL, Palette);
CASE_SWAP(ID_PC, PaintCurve);
CASE_SWAP(ID_CF, CacheFile);
+ CASE_SWAP(ID_GM, Groom);
case ID_IP:
break; /* Deprecated. */
}
@@ -964,6 +973,8 @@ ListBase *which_libbase(Main *mainlib, short type)
return &(mainlib->cachefiles);
case ID_WS:
return &(mainlib->workspaces);
+ case ID_GM:
+ return &(mainlib->grooms);
}
return NULL;
}
@@ -1056,6 +1067,8 @@ void BKE_main_lib_objects_recalc_all(Main *bmain)
DEG_id_type_tag(bmain, ID_OB);
}
+BLI_STATIC_ASSERT(MAX_LIBARRAY == INDEX_ID_NULL + 1, "MAX_LIBARRAY must be large enough for all ID types")
+
/**
* puts into array *lb pointers to all the ListBase structs in main,
* and returns the number of them as the function result. This is useful for
@@ -1089,6 +1102,7 @@ int set_listbasepointers(Main *main, ListBase **lb)
lb[INDEX_ID_ME] = &(main->mesh);
lb[INDEX_ID_CU] = &(main->curve);
lb[INDEX_ID_MB] = &(main->mball);
+ lb[INDEX_ID_GM] = &(main->grooms);
lb[INDEX_ID_LT] = &(main->latt);
lb[INDEX_ID_LA] = &(main->lamp);
@@ -1180,6 +1194,7 @@ size_t BKE_libblock_get_alloc_info(short type, const char **name)
CASE_RETURN(ID_PC, PaintCurve);
CASE_RETURN(ID_CF, CacheFile);
CASE_RETURN(ID_WS, WorkSpace);
+ CASE_RETURN(ID_GM, Groom);
}
return 0;
#undef CASE_RETURN
diff --git a/source/blender/blenkernel/intern/library_query.c b/source/blender/blenkernel/intern/library_query.c
index bed2244702f..953db6f3cb0 100644
--- a/source/blender/blenkernel/intern/library_query.c
+++ b/source/blender/blenkernel/intern/library_query.c
@@ -38,6 +38,7 @@
#include "DNA_camera_types.h"
#include "DNA_constraint_types.h"
#include "DNA_controller_types.h"
+#include "DNA_groom_types.h"
#include "DNA_group_types.h"
#include "DNA_gpencil_types.h"
#include "DNA_key_types.h"
@@ -659,6 +660,13 @@ void BKE_library_foreach_ID_link(Main *bmain, ID *id, LibraryIDLinkCallback call
break;
}
+ case ID_GM:
+ {
+ Groom *groom = (Groom *) id;
+ CALLBACK_INVOKE(groom->scalp_object, IDWALK_CB_NOP);
+ break;
+ }
+
case ID_MA:
{
Material *material = (Material *) id;
@@ -1133,6 +1141,8 @@ bool BKE_library_id_can_use_idtype(ID *id_owner, const short id_type_used)
return (ELEM(id_type_used, ID_TE, ID_OB));
case ID_LP:
return ELEM(id_type_used, ID_IM);
+ case ID_GM:
+ return true;
case ID_WS:
case ID_IM:
case ID_VF:
diff --git a/source/blender/blenkernel/intern/library_remap.c b/source/blender/blenkernel/intern/library_remap.c
index 483500cf67d..2c6df24f762 100644
--- a/source/blender/blenkernel/intern/library_remap.c
+++ b/source/blender/blenkernel/intern/library_remap.c
@@ -41,6 +41,7 @@
#include "DNA_cachefile_types.h"
#include "DNA_group_types.h"
#include "DNA_gpencil_types.h"
+#include "DNA_groom_types.h"
#include "DNA_ipo_types.h"
#include "DNA_key_types.h"
#include "DNA_lamp_types.h"
@@ -79,6 +80,7 @@
#include "BKE_font.h"
#include "BKE_group.h"
#include "BKE_gpencil.h"
+#include "BKE_groom.h"
#include "BKE_idprop.h"
#include "BKE_image.h"
#include "BKE_ipo.h"
@@ -893,6 +895,9 @@ void BKE_libblock_free_datablock(ID *id, const int UNUSED(flag))
case ID_WS:
BKE_workspace_free((WorkSpace *)id);
break;
+ case ID_GM:
+ BKE_groom_free((Groom *)id);
+ break;
}
}
diff --git a/source/blender/blenkernel/intern/mesh_sample.c b/source/blender/blenkernel/intern/mesh_sample.c
new file mode 100644
index 00000000000..fac28da63fb
--- /dev/null
+++ b/source/blender/blenkernel/intern/mesh_sample.c
@@ -0,0 +1,1683 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file blender/blenkernel/intern/mesh_sample.c
+ * \ingroup bke
+ *
+ * Sample a mesh surface or volume and evaluate samples on deformed meshes.
+ */
+
+#include <limits.h>
+
+#include "MEM_guardedalloc.h"
+
+#include "DNA_key_types.h"
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+
+#include "BLI_utildefines.h"
+#include "BLI_ghash.h"
+#include "BLI_math.h"
+#include "BLI_rand.h"
+#include "BLI_sort.h"
+#include "BLI_task.h"
+
+#include "BKE_bvhutils.h"
+#include "BKE_mesh_sample.h"
+#include "BKE_customdata.h"
+#include "BKE_DerivedMesh.h"
+
+#include "BLI_strict_flags.h"
+
+#define SAMPLE_INDEX_INVALID 0xFFFFFFFF
+
+#define DEFAULT_TASK_SIZE 1024
+
+/* ==== Utility Functions ==== */
+
+static float calc_mesh_area(DerivedMesh *dm)
+{
+ int numtris = dm->getNumLoopTri(dm);
+ MVert *mverts = dm->getVertArray(dm);
+ MLoop *mloops = dm->getLoopArray(dm);
+
+ float totarea = 0.0;
+ const MLoopTri *tri = dm->getLoopTriArray(dm);
+ for (int i = 0; i < numtris; ++i, ++tri) {
+ unsigned int index1 = mloops[tri->tri[0]].v;
+ unsigned int index2 = mloops[tri->tri[1]].v;
+ unsigned int index3 = mloops[tri->tri[2]].v;
+ MVert *v1 = &mverts[index1];
+ MVert *v2 = &mverts[index2];
+ MVert *v3 = &mverts[index3];
+ totarea += area_tri_v3(v1->co, v2->co, v3->co);
+ }
+
+ return totarea;
+}
+
+BLI_INLINE float triangle_weight(DerivedMesh *dm, const MLoopTri *tri, const float *vert_weights, float *r_area)
+{
+ MVert *mverts = dm->getVertArray(dm);
+ MLoop *mloops = dm->getLoopArray(dm);
+ unsigned int index1 = mloops[tri->tri[0]].v;
+ unsigned int index2 = mloops[tri->tri[1]].v;
+ unsigned int index3 = mloops[tri->tri[2]].v;
+ MVert *v1 = &mverts[index1];
+ MVert *v2 = &mverts[index2];
+ MVert *v3 = &mverts[index3];
+
+ float weight = area_tri_v3(v1->co, v2->co, v3->co);
+ if (r_area) {
+ *r_area = weight;
+ }
+
+ if (vert_weights) {
+ float w1 = vert_weights[index1];
+ float w2 = vert_weights[index2];
+ float w3 = vert_weights[index3];
+
+ weight *= (w1 + w2 + w3) / 3.0f;
+ }
+
+ return weight;
+}
+
+float* BKE_mesh_sample_calc_triangle_weights(DerivedMesh *dm, MeshSampleVertexWeightFp vertex_weight_cb, void *userdata, float *r_area)
+{
+ int numverts = dm->getNumVerts(dm);
+ int numtris = dm->getNumLoopTri(dm);
+ int numweights = numtris;
+
+ float *vert_weights = NULL;
+ if (vertex_weight_cb) {
+ vert_weights = MEM_mallocN(sizeof(float) * (size_t)numverts, "mesh sample vertex weights");
+ {
+ MVert *mv = dm->getVertArray(dm);
+ for (int i = 0; i < numtris; ++i, ++mv) {
+ vert_weights[i] = vertex_weight_cb(dm, mv, (unsigned int)i, userdata);
+ }
+ }
+ }
+
+ float *tri_weights = MEM_mallocN(sizeof(float) * (size_t)numweights, "mesh sample triangle weights");
+ /* accumulate weights */
+ float totarea = 0.0;
+ float totweight = 0.0f;
+ {
+ const MLoopTri *mt = dm->getLoopTriArray(dm);
+ for (int i = 0; i < numtris; ++i, ++mt) {
+ tri_weights[i] = totweight;
+
+ float triarea;
+ float triweight = triangle_weight(dm, mt, vert_weights, &triarea);
+ totarea += triarea;
+ totweight += triweight;
+ }
+ }
+
+ if (vert_weights) {
+ MEM_freeN(vert_weights);
+ }
+
+ /* normalize */
+ if (totweight > 0.0f) {
+ float norm = 1.0f / totweight;
+ const MLoopTri *mt = dm->getLoopTriArray(dm);
+ for (int i = 0; i < numtris; ++i, ++mt) {
+ tri_weights[i] *= norm;
+ }
+ }
+ else {
+ /* invalid weights, remove to avoid invalid binary search */
+ MEM_freeN(tri_weights);
+ tri_weights = NULL;
+ }
+
+ if (r_area) {
+ *r_area = totarea;
+ }
+ return tri_weights;
+}
+
+void BKE_mesh_sample_weights_from_loc(MeshSample *sample, DerivedMesh *dm, int face_index, const float loc[3])
+{
+ MFace *face = &dm->getTessFaceArray(dm)[face_index];
+ unsigned int index[4] = { face->v1, face->v2, face->v3, face->v4 };
+ MVert *mverts = dm->getVertArray(dm);
+
+ float *v1 = mverts[face->v1].co;
+ float *v2 = mverts[face->v2].co;
+ float *v3 = mverts[face->v3].co;
+ float *v4 = face->v4 ? mverts[face->v4].co : NULL;
+ float w[4];
+ int tri[3];
+
+ interp_weights_quad_v3_index(tri, w, v1, v2, v3, v4, loc);
+
+ sample->orig_verts[0] = index[tri[0]];
+ sample->orig_verts[1] = index[tri[1]];
+ sample->orig_verts[2] = index[tri[2]];
+ sample->orig_weights[0] = w[tri[0]];
+ sample->orig_weights[1] = w[tri[1]];
+ sample->orig_weights[2] = w[tri[2]];
+}
+
+/* ==== Evaluate ==== */
+
+bool BKE_mesh_sample_is_valid(const struct MeshSample *sample)
+{
+ const unsigned int *v = sample->orig_verts;
+
+ if (BKE_mesh_sample_is_volume_sample(sample))
+ {
+ /* volume sample stores position in the weight vector directly */
+ return true;
+ }
+
+ if (v[0] == SAMPLE_INDEX_INVALID || v[1] == SAMPLE_INDEX_INVALID || v[2] == SAMPLE_INDEX_INVALID)
+ {
+ /* must have 3 valid indices */
+ return false;
+ }
+
+ return true;
+}
+
+bool BKE_mesh_sample_is_volume_sample(const MeshSample *sample)
+{
+ const unsigned int *v = sample->orig_verts;
+ return v[0] == SAMPLE_INDEX_INVALID && v[1] == SAMPLE_INDEX_INVALID && v[2] == SAMPLE_INDEX_INVALID;
+}
+
+bool BKE_mesh_sample_eval(DerivedMesh *dm, const MeshSample *sample, float loc[3], float nor[3], float tang[3])
+{
+ MVert *mverts = dm->getVertArray(dm);
+ unsigned int totverts = (unsigned int)dm->getNumVerts(dm);
+ MVert *v1, *v2, *v3;
+
+ zero_v3(loc);
+ zero_v3(nor);
+ zero_v3(tang);
+
+ if (BKE_mesh_sample_is_volume_sample(sample)) {
+ /* VOLUME SAMPLE */
+
+ if (is_zero_v3(sample->orig_weights))
+ return false;
+
+ copy_v3_v3(loc, sample->orig_weights);
+ return true;
+ }
+ else {
+ /* SURFACE SAMPLE */
+ if (sample->orig_verts[0] >= totverts ||
+ sample->orig_verts[1] >= totverts ||
+ sample->orig_verts[2] >= totverts)
+ return false;
+
+ v1 = &mverts[sample->orig_verts[0]];
+ v2 = &mverts[sample->orig_verts[1]];
+ v3 = &mverts[sample->orig_verts[2]];
+
+ { /* location */
+ madd_v3_v3fl(loc, v1->co, sample->orig_weights[0]);
+ madd_v3_v3fl(loc, v2->co, sample->orig_weights[1]);
+ madd_v3_v3fl(loc, v3->co, sample->orig_weights[2]);
+ }
+
+ { /* normal */
+ float vnor[3];
+
+ normal_short_to_float_v3(vnor, v1->no);
+ madd_v3_v3fl(nor, vnor, sample->orig_weights[0]);
+ normal_short_to_float_v3(vnor, v2->no);
+ madd_v3_v3fl(nor, vnor, sample->orig_weights[1]);
+ normal_short_to_float_v3(vnor, v3->no);
+ madd_v3_v3fl(nor, vnor, sample->orig_weights[2]);
+
+ normalize_v3(nor);
+ }
+
+ { /* tangent */
+ float edge[3];
+
+ /* XXX simply using the v1-v2 edge as a tangent vector for now ...
+ * Eventually mikktspace generated tangents (CD_TANGENT tessface layer)
+ * should be used for consistency, but requires well-defined tessface
+ * indices for the mesh surface samples.
+ */
+
+ sub_v3_v3v3(edge, v2->co, v1->co);
+ /* make edge orthogonal to nor */
+ madd_v3_v3fl(edge, nor, -dot_v3v3(edge, nor));
+ normalize_v3_v3(tang, edge);
+ }
+
+ return true;
+ }
+}
+
+bool BKE_mesh_sample_shapekey(Key *key, KeyBlock *kb, const MeshSample *sample, float loc[3])
+{
+ float *v1, *v2, *v3;
+
+ (void)key; /* Unused in release builds. */
+
+ BLI_assert(key->elemsize == 3 * sizeof(float));
+ BLI_assert(sample->orig_verts[0] < (unsigned int)kb->totelem);
+ BLI_assert(sample->orig_verts[1] < (unsigned int)kb->totelem);
+ BLI_assert(sample->orig_verts[2] < (unsigned int)kb->totelem);
+
+ v1 = (float *)kb->data + sample->orig_verts[0] * 3;
+ v2 = (float *)kb->data + sample->orig_verts[1] * 3;
+ v3 = (float *)kb->data + sample->orig_verts[2] * 3;
+
+ zero_v3(loc);
+ madd_v3_v3fl(loc, v1, sample->orig_weights[0]);
+ madd_v3_v3fl(loc, v2, sample->orig_weights[1]);
+ madd_v3_v3fl(loc, v3, sample->orig_weights[2]);
+
+ /* TODO use optional vgroup weights to determine if a shapeky actually affects the sample */
+ return true;
+}
+
+void BKE_mesh_sample_clear(MeshSample *sample)
+{
+ memset(sample, 0, sizeof(MeshSample));
+}
+
+
+/* ==== Generator Types ==== */
+
+typedef void (*GeneratorFreeFp)(struct MeshSampleGenerator *gen);
+
+typedef void (*GeneratorBindFp)(struct MeshSampleGenerator *gen);
+typedef void (*GeneratorUnbindFp)(struct MeshSampleGenerator *gen);
+
+typedef void* (*GeneratorThreadContextCreateFp)(const struct MeshSampleGenerator *gen, int start);
+typedef void (*GeneratorThreadContextFreeFp)(const struct MeshSampleGenerator *gen, void *thread_ctx);
+typedef bool (*GeneratorMakeSampleFp)(const struct MeshSampleGenerator *gen, void *thread_ctx, struct MeshSample *sample);
+typedef unsigned int (*GeneratorGetMaxSamplesFp)(const struct MeshSampleGenerator *gen);
+
+typedef struct MeshSampleGenerator
+{
+ GeneratorFreeFp free;
+
+ GeneratorBindFp bind;
+ GeneratorUnbindFp unbind;
+
+ GeneratorThreadContextCreateFp thread_context_create;
+ GeneratorThreadContextFreeFp thread_context_free;
+ GeneratorMakeSampleFp make_sample;
+ GeneratorGetMaxSamplesFp get_max_samples;
+
+ /* bind target */
+ DerivedMesh *dm;
+
+ void *default_ctx;
+ int task_size;
+} MeshSampleGenerator;
+
+static void sample_generator_init(MeshSampleGenerator *gen,
+ GeneratorFreeFp free,
+ GeneratorBindFp bind,
+ GeneratorUnbindFp unbind,
+ GeneratorThreadContextCreateFp thread_context_create,
+ GeneratorThreadContextFreeFp thread_context_free,
+ GeneratorMakeSampleFp make_sample,
+ GeneratorGetMaxSamplesFp get_max_samples)
+{
+ gen->free = free;
+ gen->bind = bind;
+ gen->unbind = unbind;
+ gen->thread_context_create = thread_context_create;
+ gen->thread_context_free = thread_context_free;
+ gen->make_sample = make_sample;
+ gen->get_max_samples = get_max_samples;
+
+ gen->dm = NULL;
+
+ gen->default_ctx = NULL;
+ gen->task_size = DEFAULT_TASK_SIZE;
+}
+
+/* ------------------------------------------------------------------------- */
+
+typedef struct MSurfaceSampleGenerator_Vertices {
+ MeshSampleGenerator base;
+
+ /* bind data */
+ int (*vert_loop_map)[3];
+} MSurfaceSampleGenerator_Vertices;
+
+static void generator_vertices_free(MSurfaceSampleGenerator_Vertices *gen)
+{
+ MEM_freeN(gen);
+}
+
+static void generator_vertices_bind(MSurfaceSampleGenerator_Vertices *gen)
+{
+ DerivedMesh *dm = gen->base.dm;
+ const int num_verts = dm->getNumVerts(dm);
+
+ DM_ensure_normals(dm);
+
+ int (*vert_loop_map)[3] = MEM_mallocN(sizeof(int) * 3 * (unsigned int)num_verts, "vertex loop map");
+ for (int i = 0; i < num_verts; ++i) {
+ vert_loop_map[i][0] = -1;
+ vert_loop_map[i][1] = -1;
+ vert_loop_map[i][2] = -1;
+ }
+
+ const int num_polys = dm->getNumPolys(dm);
+ const MLoop *mloops = dm->getLoopArray(dm);
+ const MPoly *mp = dm->getPolyArray(dm);
+ for (int i = 0; i < num_polys; ++i, ++mp) {
+ if (mp->totloop < 3) {
+ continue;
+ }
+
+ const MLoop *ml = mloops + mp->loopstart;
+ for (int k = 0; k < mp->totloop; ++k, ++ml) {
+ int *vmap = vert_loop_map[ml->v];
+ if (vmap[0] < 0) {
+ vmap[0] = mp->loopstart + k;
+ vmap[1] = mp->loopstart + (k + 1) % mp->totloop;
+ vmap[2] = mp->loopstart + (k + 2) % mp->totloop;
+ }
+ }
+ }
+
+ gen->vert_loop_map = vert_loop_map;
+}
+
+static void generator_vertices_unbind(MSurfaceSampleGenerator_Vertices *gen)
+{
+ if (gen->vert_loop_map) {
+ MEM_freeN(gen->vert_loop_map);
+ }
+}
+
+static void* generator_vertices_thread_context_create(const MSurfaceSampleGenerator_Vertices *UNUSED(gen), int start)
+{
+ int *cur_vert = MEM_callocN(sizeof(int), "generator_vertices_thread_context");
+ *cur_vert = start;
+ return cur_vert;
+}
+
+static void generator_vertices_thread_context_free(const MSurfaceSampleGenerator_Vertices *UNUSED(gen), void *thread_ctx)
+{
+ MEM_freeN(thread_ctx);
+}
+
+static bool generator_vertices_make_loop_sample(DerivedMesh *dm, const int *loops, MeshSample *sample)
+{
+ const MLoop *mloops = dm->getLoopArray(dm);
+
+ if (loops[0] < 0) {
+ return false;
+ }
+
+ sample->orig_poly = -1;
+
+ sample->orig_loops[0] = (unsigned int)loops[0];
+ sample->orig_loops[1] = (unsigned int)loops[1];
+ sample->orig_loops[2] = (unsigned int)loops[2];
+
+ sample->orig_verts[0] = mloops[loops[0]].v;
+ sample->orig_verts[1] = mloops[loops[1]].v;
+ sample->orig_verts[2] = mloops[loops[2]].v;
+
+ sample->orig_weights[0] = 1.0f;
+ sample->orig_weights[1] = 0.0f;
+ sample->orig_weights[2] = 0.0f;
+
+ return true;
+}
+
+static bool generator_vertices_make_sample(const MSurfaceSampleGenerator_Vertices *gen, void *thread_ctx, MeshSample *sample)
+{
+ DerivedMesh *dm = gen->base.dm;
+ const int num_verts = dm->getNumVerts(dm);
+
+ int cur_vert = *(int *)thread_ctx;
+ bool found_vert = false;
+ for (; cur_vert < num_verts && !found_vert; ++cur_vert) {
+ found_vert |= generator_vertices_make_loop_sample(dm, gen->vert_loop_map[cur_vert], sample);
+ }
+
+ *(int *)thread_ctx = cur_vert;
+ return found_vert;
+}
+
+static unsigned int generator_vertices_get_max_samples(const MSurfaceSampleGenerator_Vertices *gen)
+{
+ return (unsigned int)gen->base.dm->getNumVerts(gen->base.dm);
+}
+
+MeshSampleGenerator *BKE_mesh_sample_gen_surface_vertices(void)
+{
+ MSurfaceSampleGenerator_Vertices *gen;
+
+ gen = MEM_callocN(sizeof(MSurfaceSampleGenerator_Vertices), "MSurfaceSampleGenerator_Vertices");
+ sample_generator_init(&gen->base,
+ (GeneratorFreeFp)generator_vertices_free,
+ (GeneratorBindFp)generator_vertices_bind,
+ (GeneratorUnbindFp) generator_vertices_unbind,
+ (GeneratorThreadContextCreateFp)generator_vertices_thread_context_create,
+ (GeneratorThreadContextFreeFp)generator_vertices_thread_context_free,
+ (GeneratorMakeSampleFp)generator_vertices_make_sample,
+ (GeneratorGetMaxSamplesFp)generator_vertices_get_max_samples);
+
+ return &gen->base;
+}
+
+/* ------------------------------------------------------------------------- */
+
+//#define USE_DEBUG_COUNT
+
+typedef struct MSurfaceSampleGenerator_Random {
+ MeshSampleGenerator base;
+
+ unsigned int seed;
+ bool use_area_weight;
+ MeshSampleVertexWeightFp vertex_weight_cb;
+ void *userdata;
+
+ /* bind data */
+ float *tri_weights;
+ float *vertex_weights;
+
+#ifdef USE_DEBUG_COUNT
+ int *debug_count;
+#endif
+} MSurfaceSampleGenerator_Random;
+
+static void generator_random_free(MSurfaceSampleGenerator_Random *gen)
+{
+ MEM_freeN(gen);
+}
+
+static void generator_random_bind(MSurfaceSampleGenerator_Random *gen)
+{
+ DerivedMesh *dm = gen->base.dm;
+
+ DM_ensure_normals(dm);
+
+ if (gen->use_area_weight) {
+ gen->tri_weights = BKE_mesh_sample_calc_triangle_weights(dm, gen->vertex_weight_cb, gen->userdata, NULL);
+
+#ifdef USE_DEBUG_COUNT
+ gen->debug_count = MEM_callocN(sizeof(int) * (size_t)dm->getNumLoopTri(dm), "surface sample debug counts");
+#endif
+ }
+}
+
+static void generator_random_unbind(MSurfaceSampleGenerator_Random *gen)
+{
+#ifdef USE_DEBUG_COUNT
+ if (gen->debug_count) {
+ if (gen->tri_weights) {
+ int num = gen->dm->getNumLoopTri(gen->dm);
+ int i;
+ int totsamples = 0;
+
+ printf("Surface Sampling (n=%d):\n", num);
+ for (i = 0; i < num; ++i)
+ totsamples += gen->debug_count[i];
+
+ for (i = 0; i < num; ++i) {
+ float weight = i > 0 ? gen->tri_weights[i] - gen->tri_weights[i-1] : gen->tri_weights[i];
+ int samples = gen->debug_count[i];
+ printf(" %d: W = %f, N = %d/%d = %f\n", i, weight, samples, totsamples, (float)samples / (float)totsamples);
+ }
+ }
+ MEM_freeN(gen->debug_count);
+ }
+#endif
+ if (gen->tri_weights)
+ MEM_freeN(gen->tri_weights);
+ if (gen->vertex_weights)
+ MEM_freeN(gen->vertex_weights);
+}
+
+static void* generator_random_thread_context_create(const MSurfaceSampleGenerator_Random *gen, int start)
+{
+ RNG *rng = BLI_rng_new(gen->seed);
+ // 3 RNG gets per sample
+ BLI_rng_skip(rng, start * 3);
+ return rng;
+}
+
+static void generator_random_thread_context_free(const MSurfaceSampleGenerator_Random *UNUSED(gen), void *thread_ctx)
+{
+ BLI_rng_free(thread_ctx);
+}
+
+/* Find the index in "sum" array before "value" is crossed. */
+BLI_INLINE int weight_array_binary_search(const float *sum, int size, float value)
+{
+ int mid, low = 0, high = size - 1;
+
+ if (value <= 0.0f)
+ return 0;
+
+ while (low < high) {
+ mid = (low + high) >> 1;
+
+ if (sum[mid] < value && value <= sum[mid+1])
+ return mid;
+
+ if (sum[mid] >= value)
+ high = mid - 1;
+ else if (sum[mid] < value)
+ low = mid + 1;
+ else
+ return mid;
+ }
+
+ return low;
+}
+
+static bool generator_random_make_sample(const MSurfaceSampleGenerator_Random *gen, void *thread_ctx, MeshSample *sample)
+{
+ DerivedMesh *dm = gen->base.dm;
+ RNG *rng = thread_ctx;
+ const MLoop *mloops = dm->getLoopArray(dm);
+ const MLoopTri *mtris = dm->getLoopTriArray(dm);
+ int tottris = dm->getNumLoopTri(dm);
+ int totweights = tottris;
+
+ int triindex;
+ float a, b;
+ const MLoopTri *mtri;
+
+ if (gen->tri_weights)
+ triindex = weight_array_binary_search(gen->tri_weights, totweights, BLI_rng_get_float(rng));
+ else
+ triindex = BLI_rng_get_int(rng) % totweights;
+#ifdef USE_DEBUG_COUNT
+ if (gen->debug_count)
+ gen->debug_count[triindex] += 1;
+#endif
+ a = BLI_rng_get_float(rng);
+ b = BLI_rng_get_float(rng);
+
+ mtri = &mtris[triindex];
+
+ sample->orig_verts[0] = mloops[mtri->tri[0]].v;
+ sample->orig_verts[1] = mloops[mtri->tri[1]].v;
+ sample->orig_verts[2] = mloops[mtri->tri[2]].v;
+
+ if (a + b > 1.0f) {
+ a = 1.0f - a;
+ b = 1.0f - b;
+ }
+ sample->orig_weights[0] = 1.0f - (a + b);
+ sample->orig_weights[1] = a;
+ sample->orig_weights[2] = b;
+
+ return true;
+}
+
+MeshSampleGenerator *BKE_mesh_sample_gen_surface_random(unsigned int seed, bool use_area_weight,
+ MeshSampleVertexWeightFp vertex_weight_cb, void *userdata)
+{
+ MSurfaceSampleGenerator_Random *gen;
+
+ gen = MEM_callocN(sizeof(MSurfaceSampleGenerator_Random), "MSurfaceSampleGenerator_Random");
+ sample_generator_init(&gen->base,
+ (GeneratorFreeFp)generator_random_free,
+ (GeneratorBindFp)generator_random_bind,
+ (GeneratorUnbindFp) generator_random_unbind,
+ (GeneratorThreadContextCreateFp)generator_random_thread_context_create,
+ (GeneratorThreadContextFreeFp)generator_random_thread_context_free,
+ (GeneratorMakeSampleFp)generator_random_make_sample,
+ NULL);
+
+ gen->seed = seed;
+ gen->use_area_weight = use_area_weight;
+ gen->vertex_weight_cb = vertex_weight_cb;
+ gen->userdata = userdata;
+
+ return &gen->base;
+}
+
+/* ------------------------------------------------------------------------- */
+
+typedef struct MSurfaceSampleGenerator_RayCast {
+ MeshSampleGenerator base;
+
+ MeshSampleRayFp ray_cb;
+ MeshSampleThreadContextCreateFp thread_context_create_cb;
+ MeshSampleThreadContextFreeFp thread_context_free_cb;
+ void *userdata;
+
+ /* bind data */
+ BVHTreeFromMesh bvhdata;
+} MSurfaceSampleGenerator_RayCast;
+
+static void generator_raycast_free(MSurfaceSampleGenerator_RayCast *gen)
+{
+ MEM_freeN(gen);
+}
+
+static void generator_raycast_bind(MSurfaceSampleGenerator_RayCast *gen)
+{
+ DerivedMesh *dm = gen->base.dm;
+
+ DM_ensure_tessface(dm);
+
+ memset(&gen->bvhdata, 0, sizeof(gen->bvhdata));
+
+ if (dm->getNumTessFaces(dm) == 0)
+ return;
+
+ bvhtree_from_mesh_faces(&gen->bvhdata, dm, 0.0f, 4, 6);
+}
+
+static void generator_raycast_unbind(MSurfaceSampleGenerator_RayCast *gen)
+{
+ free_bvhtree_from_mesh(&gen->bvhdata);
+}
+
+static void* generator_raycast_thread_context_create(const MSurfaceSampleGenerator_RayCast *gen, int start)
+{
+ if (gen->thread_context_create_cb) {
+ return gen->thread_context_create_cb(gen->userdata, start);
+ }
+ else {
+ return NULL;
+ }
+}
+
+static void generator_raycast_thread_context_free(const MSurfaceSampleGenerator_RayCast *gen, void *thread_ctx)
+{
+ if (gen->thread_context_free_cb) {
+ return gen->thread_context_free_cb(gen->userdata, thread_ctx);
+ }
+}
+
+static bool generator_raycast_make_sample(const MSurfaceSampleGenerator_RayCast *gen, void *thread_ctx, MeshSample *sample)
+{
+ float ray_start[3], ray_end[3], ray_dir[3], dist;
+ BVHTreeRayHit hit;
+
+ if (!gen->ray_cb(gen->userdata, thread_ctx, ray_start, ray_end))
+ return false;
+
+ sub_v3_v3v3(ray_dir, ray_end, ray_start);
+ dist = normalize_v3(ray_dir);
+
+ hit.index = -1;
+ hit.dist = dist;
+
+ if (BLI_bvhtree_ray_cast(gen->bvhdata.tree, ray_start, ray_dir, 0.0f,
+ &hit, gen->bvhdata.raycast_callback, (BVHTreeFromMesh *)(&gen->bvhdata)) >= 0) {
+
+ BKE_mesh_sample_weights_from_loc(sample, gen->base.dm, hit.index, hit.co);
+
+ return true;
+ }
+ else
+ return false;
+}
+
+MeshSampleGenerator *BKE_mesh_sample_gen_surface_raycast(
+ MeshSampleThreadContextCreateFp thread_context_create_cb,
+ MeshSampleThreadContextFreeFp thread_context_free_cb,
+ MeshSampleRayFp ray_cb,
+ void *userdata)
+{
+ MSurfaceSampleGenerator_RayCast *gen;
+
+ gen = MEM_callocN(sizeof(MSurfaceSampleGenerator_RayCast), "MSurfaceSampleGenerator_RayCast");
+ sample_generator_init(&gen->base,
+ (GeneratorFreeFp)generator_raycast_free,
+ (GeneratorBindFp)generator_raycast_bind,
+ (GeneratorUnbindFp) generator_raycast_unbind,
+ (GeneratorThreadContextCreateFp)generator_raycast_thread_context_create,
+ (GeneratorThreadContextFreeFp)generator_raycast_thread_context_free,
+ (GeneratorMakeSampleFp)generator_raycast_make_sample,
+ NULL);
+
+ gen->thread_context_create_cb = thread_context_create_cb;
+ gen->thread_context_free_cb = thread_context_free_cb;
+ gen->ray_cb = ray_cb;
+ gen->userdata = userdata;
+
+ return &gen->base;
+}
+
+/* ------------------------------------------------------------------------- */
+
+#define MAX_CIRCLE_PACKING 0.906899682
+#define SQRT_3 1.732050808
+
+typedef struct IndexedMeshSample {
+ unsigned int orig_verts[3];
+ float orig_weights[3];
+ float co[3];
+ int cell_index[3];
+} IndexedMeshSample;
+
+typedef struct MSurfaceSampleGenerator_PoissonDisk {
+ MeshSampleGenerator base;
+
+ MeshSampleGenerator *uniform_gen;
+ unsigned int max_samples;
+ float mindist_squared;
+ /* Size of grid cells is mindist/sqrt(3),
+ * so that each cell contains at most one valid sample.
+ */
+ float cellsize;
+ /* Transform mesh space to grid space */
+ float grid_scale;
+
+ /* bind data */
+
+ /* offset and size of the grid */
+ int grid_offset[3];
+ int grid_size[3];
+
+ IndexedMeshSample *uniform_samples;
+ unsigned int num_uniform_samples;
+
+ struct GHash *cell_table;
+} MSurfaceSampleGenerator_PoissonDisk;
+
+typedef struct MeshSampleCell {
+ int cell_index[3];
+ unsigned int sample_start;
+ unsigned int sample;
+} MeshSampleCell;
+
+typedef struct MSurfaceSampleGenerator_PoissonDisk_ThreadContext {
+ unsigned int trial;
+ GHashIterator iter;
+} MSurfaceSampleGenerator_PoissonDisk_ThreadContext;
+
+BLI_INLINE void poissondisk_loc_from_grid(const MSurfaceSampleGenerator_PoissonDisk *gen, float loc[3], const int grid[3])
+{
+ copy_v3_fl3(loc, grid[0] + gen->grid_offset[0], grid[1] + gen->grid_offset[1], grid[2] + gen->grid_offset[2]);
+ mul_v3_fl(loc, gen->cellsize);
+}
+
+BLI_INLINE void poissondisk_grid_from_loc(const MSurfaceSampleGenerator_PoissonDisk *gen, int grid[3], const float loc[3])
+{
+ float gridco[3];
+ mul_v3_v3fl(gridco, loc, gen->grid_scale);
+ grid[0] = (int)floorf(gridco[0]) - gen->grid_offset[0];
+ grid[1] = (int)floorf(gridco[1]) - gen->grid_offset[1];
+ grid[2] = (int)floorf(gridco[2]) - gen->grid_offset[2];
+}
+
+static void generator_poissondisk_free(MSurfaceSampleGenerator_PoissonDisk *gen)
+{
+ BKE_mesh_sample_free_generator(gen->uniform_gen);
+ MEM_freeN(gen);
+}
+
+static void generator_poissondisk_uniform_sample_eval(
+ void *__restrict userdata,
+ const int iter,
+ const ParallelRangeTLS *__restrict UNUSED(tls))
+{
+ void *(*ptrs)[3] = userdata;
+ MSurfaceSampleGenerator_PoissonDisk *gen = (*ptrs)[0];
+ const MeshSample *samples = (*ptrs)[1];
+ DerivedMesh *dm = (*ptrs)[2];
+
+ IndexedMeshSample *isample = &gen->uniform_samples[iter];
+ const MeshSample *sample = &samples[iter];
+
+ memcpy(isample->orig_verts, sample->orig_verts, sizeof(isample->orig_verts));
+ memcpy(isample->orig_weights, sample->orig_weights, sizeof(isample->orig_weights));
+ float nor[3], tang[3];
+ BKE_mesh_sample_eval(dm, sample, isample->co, nor, tang);
+
+ poissondisk_grid_from_loc(gen, isample->cell_index, isample->co);
+}
+
+BLI_INLINE void copy_cell_index(int r[3], const int a[3])
+{
+ r[0] = a[0];
+ r[1] = a[1];
+ r[2] = a[2];
+}
+
+BLI_INLINE int cmp_cell_index(const int a[3], const int b[3])
+{
+ int d0 = a[0] - b[0];
+ int d1 = a[1] - b[1];
+ int d2 = a[2] - b[2];
+ if (d0 == 0)
+ {
+ if (d1 == 0)
+ {
+ if (d2 == 0)
+ {
+ return 0;
+ }
+ else
+ {
+ return d2 > 0 ? 1 : -1;
+ }
+ }
+ else
+ {
+ return d1 > 0 ? 1 : -1;
+ }
+ }
+ else
+ {
+ return d0 > 0 ? 1 : -1;
+ }
+}
+
+static int cmp_indexed_mesh_sample(const void *a, const void *b)
+{
+ return cmp_cell_index(((const IndexedMeshSample *)a)->cell_index, ((const IndexedMeshSample *)b)->cell_index);
+}
+
+BLI_INLINE bool cell_index_eq(const int *a, const int *b)
+{
+ return a[0] == b[0] && a[1] == b[1] && a[2] == b[2];
+}
+
+/* hash key function */
+static unsigned int cell_hash_key(const void *key)
+{
+ const int *cell_index = (const int *)key;
+ unsigned int hash0 = BLI_ghashutil_inthash(cell_index[0]);
+ unsigned int hash1 = BLI_ghashutil_inthash(cell_index[1]);
+ unsigned int hash2 = BLI_ghashutil_inthash(cell_index[2]);
+ return BLI_ghashutil_combine_hash(hash0, BLI_ghashutil_combine_hash(hash1, hash2));
+}
+
+/* hash function: return false when equal */
+static bool cell_hash_neq(const void *a, const void *b)
+{
+ return !cell_index_eq((const int *)a, (const int *)b);
+}
+
+static unsigned int generator_poissondisk_get_max_samples(const MSurfaceSampleGenerator_PoissonDisk *gen)
+{
+ static const unsigned int hard_max = UINT_MAX;
+
+ const double usable_area = calc_mesh_area(gen->base.dm) * MAX_CIRCLE_PACKING;
+ const double circle_area = M_PI * gen->mindist_squared;
+ if (circle_area * (double)hard_max < usable_area) {
+ return hard_max;
+ }
+
+ return (unsigned int)(usable_area / circle_area);
+}
+
+static void generator_poissondisk_bind(MSurfaceSampleGenerator_PoissonDisk *gen)
+{
+ DerivedMesh *dm = gen->base.dm;
+ static const unsigned int uniform_sample_ratio = 10;
+
+ // Determine cell size
+ {
+ float min[3], max[3];
+ INIT_MINMAX(min, max);
+ dm->getMinMax(dm, min, max);
+ mul_v3_fl(min, gen->grid_scale);
+ mul_v3_fl(max, gen->grid_scale);
+ /* grid size gets an empty 2 cell margin to simplify neighbor lookups */
+ gen->grid_offset[0] = (int)floorf(min[0]) - 2;
+ gen->grid_offset[1] = (int)floorf(min[1]) - 2;
+ gen->grid_offset[2] = (int)floorf(min[2]) - 2;
+ gen->grid_size[0] = (int)floorf(max[0]) - gen->grid_offset[0] + 4;
+ gen->grid_size[1] = (int)floorf(max[1]) - gen->grid_offset[1] + 4;
+ gen->grid_size[2] = (int)floorf(max[2]) - gen->grid_offset[2] + 4;
+ }
+
+ // Generate initial uniform random point set
+ unsigned int max_pd_samples = generator_poissondisk_get_max_samples(gen);
+ gen->num_uniform_samples = MIN2(max_pd_samples * uniform_sample_ratio, gen->max_samples);
+ if (gen->num_uniform_samples > 0) {
+ BKE_mesh_sample_generator_bind(gen->uniform_gen, dm);
+
+ gen->uniform_samples = MEM_mallocN(sizeof(IndexedMeshSample) * gen->num_uniform_samples, "poisson disk uniform samples");
+
+ MeshSample *samples = MEM_mallocN(sizeof(MeshSample) * gen->num_uniform_samples, "poisson disk uniform samples");
+ BKE_mesh_sample_generate_batch(gen->uniform_gen, samples, (int)gen->num_uniform_samples);
+ void *ptrs[3] = { gen, samples, dm };
+ {
+ ParallelRangeSettings settings;
+ BLI_parallel_range_settings_defaults(&settings);
+ settings.use_threading = true;
+ BLI_task_parallel_range(0, (int)gen->num_uniform_samples, &ptrs, generator_poissondisk_uniform_sample_eval, &settings);
+ }
+ MEM_freeN(samples);
+
+ BKE_mesh_sample_generator_unbind(gen->uniform_gen);
+ }
+
+ // Sort points by cell hash
+ {
+ qsort(gen->uniform_samples, gen->num_uniform_samples, sizeof(IndexedMeshSample), cmp_indexed_mesh_sample);
+ }
+
+ // Build a hash table for indexing cells
+ {
+ gen->cell_table = BLI_ghash_new(cell_hash_key, cell_hash_neq, "MeshSampleCell hash table");
+ int cur_cell_index[3] = {-1, -1, -1};
+ const IndexedMeshSample *sample = gen->uniform_samples;
+ for (unsigned int i = 0; i < gen->num_uniform_samples; ++i, ++sample) {
+ BLI_assert(cmp_cell_index(cur_cell_index, sample->cell_index) <= 0);
+ if (cmp_cell_index(cur_cell_index, sample->cell_index) < 0) {
+ copy_cell_index(cur_cell_index, sample->cell_index);
+
+ MeshSampleCell *cell = MEM_mallocN(sizeof(*cell), "MeshSampleCell");
+ copy_cell_index(cell->cell_index, cur_cell_index);
+ cell->sample_start = (unsigned int)i;
+ cell->sample = SAMPLE_INDEX_INVALID;
+ BLI_ghash_insert(gen->cell_table, cell->cell_index, cell);
+ }
+ }
+ }
+
+#if 0
+ for (unsigned int i = 0; i < gen->num_uniform_samples; ++i) {
+ const IndexedMeshSample *s = &gen->uniform_samples[i];
+ printf("%d: (%.3f, %.3f, %.3f) | %d\n", i, s->co[0], s->co[1], s->co[2], (int)s->cell_hash);
+ }
+#endif
+}
+
+static void generator_poissondisk_unbind(MSurfaceSampleGenerator_PoissonDisk *gen)
+{
+ if (gen->cell_table) {
+ BLI_ghash_free(gen->cell_table, NULL, MEM_freeN);
+ }
+
+ if (gen->uniform_samples) {
+ MEM_freeN(gen->uniform_samples);
+ }
+}
+
+static void* generator_poissondisk_thread_context_create(const MSurfaceSampleGenerator_PoissonDisk *gen, int UNUSED(start))
+{
+ MSurfaceSampleGenerator_PoissonDisk_ThreadContext *ctx = MEM_mallocN(sizeof(*ctx), "thread context");
+ ctx->trial = 0;
+ BLI_ghashIterator_init(&ctx->iter, gen->cell_table);
+ return ctx;
+}
+
+static void generator_poissondisk_thread_context_free(const MSurfaceSampleGenerator_PoissonDisk *UNUSED(gen), void *thread_ctx)
+{
+ MEM_freeN(thread_ctx);
+}
+
+static bool generator_poissondisk_make_sample(const MSurfaceSampleGenerator_PoissonDisk *gen, void *thread_ctx, MeshSample *sample)
+{
+ static const unsigned int max_trials = 5;
+
+ MSurfaceSampleGenerator_PoissonDisk_ThreadContext *ctx = thread_ctx;
+
+ // Offset of cells whose samples can potentially overlap a given cell
+ // Four corners are excluded because their samples can never overlap
+ const int neighbors[][3] = {
+ {-1, -2, -2}, { 0, -2, -2}, { 1, -2, -2},
+ {-2, -1, -2}, {-1, -1, -2}, { 0, -1, -2}, { 1, -1, -2}, { 2, -1, -2},
+ {-2, 0, -2}, {-1, 0, -2}, { 0, 0, -2}, { 1, 0, -2}, { 2, 0, -2},
+ {-2, 1, -2}, {-1, 1, -2}, { 0, 1, -2}, { 1, -2, -2}, { 2, 1, -2},
+ {-2, 2, -2}, {-1, 2, -2}, { 0, 2, -2}, { 1, -2, -2}, { 2, 2, -2},
+
+ {-2, -2, -1}, {-1, -2, -1}, { 0, -2, -1}, { 1, -2, -1}, { 2, -2, -1},
+ {-2, -1, -1}, {-1, -1, -1}, { 0, -1, -1}, { 1, -1, -1}, { 2, -1, -1},
+ {-2, 0, -1}, {-1, 0, -1}, { 0, 0, -1}, { 1, 0, -1}, { 2, 0, -1},
+ {-2, 1, -1}, {-1, 1, -1}, { 0, 1, -1}, { 1, -2, -1}, { 2, 1, -1},
+ {-2, 2, -1}, {-1, 2, -1}, { 0, 2, -1}, { 1, -2, -1}, { 2, 2, -1},
+
+ {-2, -2, 0}, {-1, -2, 0}, { 0, -2, 0}, { 1, -2, 0}, { 2, -2, 0},
+ {-2, -1, 0}, {-1, -1, 0}, { 0, -1, 0}, { 1, -1, 0}, { 2, -1, 0},
+ {-2, 0, 0}, {-1, 0, 0}, { 1, 0, 0}, { 2, 0, 0},
+ {-2, 1, 0}, {-1, 1, 0}, { 0, 1, 0}, { 1, -2, 0}, { 2, 1, 0},
+ {-2, 2, 0}, {-1, 2, 0}, { 0, 2, 0}, { 1, -2, 0}, { 2, 2, 0},
+
+ {-2, -2, 1}, {-1, -2, 1}, { 0, -2, 1}, { 1, -2, 1}, { 2, -2, 1},
+ {-2, -1, 1}, {-1, -1, 1}, { 0, -1, 1}, { 1, -1, 1}, { 2, -1, 1},
+ {-2, 0, 1}, {-1, 0, 1}, { 0, 0, 1}, { 1, 0, 1}, { 2, 0, 1},
+ {-2, 1, 1}, {-1, 1, 1}, { 0, 1, 1}, { 1, -2, 1}, { 2, 1, 1},
+ {-2, 2, 1}, {-1, 2, 1}, { 0, 2, 1}, { 1, -2, 1}, { 2, 2, 1},
+
+ {-2, -2, 2}, {-1, -2, 2}, { 0, -2, 2}, { 1, -2, 2}, { 2, -2, 2},
+ {-2, -1, 2}, {-1, -1, 2}, { 0, -1, 2}, { 1, -1, 2}, { 2, -1, 2},
+ {-2, 0, 2}, {-1, 0, 2}, { 0, 0, 2}, { 1, 0, 2}, { 2, 0, 2},
+ {-2, 1, 2}, {-1, 1, 2}, { 0, 1, 2}, { 1, -2, 2}, { 2, 1, 2},
+ {-1, 2, 2}, { 0, 2, 2}, { 1, -2, 2}
+ };
+ const int num_neighbors = ARRAY_SIZE(neighbors);
+
+ bool found_sample = false;
+ for (; ctx->trial < max_trials; ++ctx->trial) {
+ while (!BLI_ghashIterator_done(&ctx->iter)) {
+ MeshSampleCell *cell = BLI_ghashIterator_getValue(&ctx->iter);
+ BLI_ghashIterator_step(&ctx->iter);
+
+ if (cell->sample != SAMPLE_INDEX_INVALID) {
+ continue;
+ }
+
+ bool cell_valid = true;
+
+ unsigned int sample_index = cell->sample_start + ctx->trial;
+ const IndexedMeshSample *isample = &gen->uniform_samples[sample_index];
+ /* Check if we ran out of sample candidates for this cell */
+ if (sample_index >= (unsigned int)gen->num_uniform_samples ||
+ !cell_index_eq(isample->cell_index, cell->cell_index)) {
+
+ cell_valid = false;
+ // TODO remove from hash table?
+ UNUSED_VARS(cell_valid);
+
+ }
+ else {
+
+ /* Check the sample candidate */
+ const int *idx = cell->cell_index;
+
+ bool conflict = false;
+ for (int i = 0; i < num_neighbors; ++i) {
+ const int *idx_offset = neighbors[i];
+ const int nidx[3] = {idx[0] + idx_offset[0], idx[1] + idx_offset[1], idx[2] + idx_offset[2]};
+ const MeshSampleCell *ncell = BLI_ghash_lookup(gen->cell_table, nidx);
+ if (ncell) {
+ if (ncell->sample != SAMPLE_INDEX_INVALID) {
+ const IndexedMeshSample *nsample = &gen->uniform_samples[ncell->sample];
+ BLI_assert(cell_index_eq(nsample->cell_index, ncell->cell_index));
+ if (len_squared_v3v3(isample->co, nsample->co) < gen->mindist_squared) {
+ conflict = true;
+ break;
+ }
+ }
+ }
+ }
+ if (!conflict) {
+ cell->sample = sample_index;
+
+ memcpy(sample->orig_verts, isample->orig_verts, sizeof(sample->orig_verts));
+ memcpy(sample->orig_weights, isample->orig_weights, sizeof(sample->orig_weights));
+ memset(sample->orig_loops, 0, sizeof(sample->orig_loops));
+ sample->orig_poly = 0;
+
+ found_sample = true;
+ }
+
+ }
+
+ if (found_sample) {
+ break;
+ }
+ }
+
+ if (found_sample) {
+ break;
+ }
+ else {
+ BLI_ghashIterator_init(&ctx->iter, gen->cell_table);
+ }
+ }
+
+ return found_sample;
+}
+
+MeshSampleGenerator *BKE_mesh_sample_gen_surface_poissondisk(unsigned int seed, float mindist, unsigned int max_samples,
+ MeshSampleVertexWeightFp vertex_weight_cb, void *userdata)
+{
+ MSurfaceSampleGenerator_PoissonDisk *gen;
+
+ gen = MEM_callocN(sizeof(MSurfaceSampleGenerator_PoissonDisk), "MSurfaceSampleGenerator_PoissonDisk");
+ sample_generator_init(&gen->base,
+ (GeneratorFreeFp)generator_poissondisk_free,
+ (GeneratorBindFp)generator_poissondisk_bind,
+ (GeneratorUnbindFp) generator_poissondisk_unbind,
+ (GeneratorThreadContextCreateFp)generator_poissondisk_thread_context_create,
+ (GeneratorThreadContextFreeFp)generator_poissondisk_thread_context_free,
+ (GeneratorMakeSampleFp)generator_poissondisk_make_sample,
+ (GeneratorGetMaxSamplesFp)generator_poissondisk_get_max_samples);
+
+ gen->uniform_gen = BKE_mesh_sample_gen_surface_random(seed, true, vertex_weight_cb, userdata);
+ gen->max_samples = max_samples;
+ gen->mindist_squared = mindist * mindist;
+ gen->cellsize = mindist / SQRT_3;
+ gen->grid_scale = SQRT_3 / mindist;
+
+ return &gen->base;
+}
+
+/* ------------------------------------------------------------------------- */
+
+typedef struct MVolumeSampleGenerator_Random {
+ MeshSampleGenerator base;
+
+ unsigned int seed;
+ float density;
+
+ /* bind data */
+ BVHTreeFromMesh bvhdata;
+ float min[3], max[3], extent[3], volume;
+ int max_samples_per_ray;
+} MVolumeSampleGenerator_Random;
+
+typedef struct MVolumeSampleGenerator_Random_ThreadContext {
+ const BVHTreeFromMesh *bvhdata;
+ RNG *rng;
+
+ /* current ray intersections */
+ BVHTreeRayHit *ray_hits;
+ int tothits, allochits;
+
+ /* current segment index and sample number */
+ int cur_seg, cur_tot, cur_sample;
+} MVolumeSampleGenerator_Random_ThreadContext;
+
+static void generator_volume_random_free(MVolumeSampleGenerator_Random *gen)
+{
+ MEM_freeN(gen);
+}
+
+static void generator_volume_random_bind(MVolumeSampleGenerator_Random *gen)
+{
+ DerivedMesh *dm = gen->base.dm;
+
+ memset(&gen->bvhdata, 0, sizeof(gen->bvhdata));
+
+ DM_ensure_tessface(dm);
+
+ if (dm->getNumTessFaces(dm) == 0)
+ return;
+
+ bvhtree_from_mesh_faces(&gen->bvhdata, dm, 0.0f, 4, 6);
+
+ INIT_MINMAX(gen->min, gen->max);
+ dm->getMinMax(dm, gen->min, gen->max);
+ sub_v3_v3v3(gen->extent, gen->max, gen->min);
+ gen->volume = gen->extent[0] * gen->extent[1] * gen->extent[2];
+ gen->max_samples_per_ray = max_ii(1, (int)powf(gen->volume, 1.0f/3.0f)) >> 1;
+}
+
+static void generator_volume_random_unbind(MVolumeSampleGenerator_Random *gen)
+{
+ free_bvhtree_from_mesh(&gen->bvhdata);
+}
+
+BLI_INLINE unsigned int hibit(unsigned int n) {
+ n |= (n >> 1);
+ n |= (n >> 2);
+ n |= (n >> 4);
+ n |= (n >> 8);
+ n |= (n >> 16);
+ return n ^ (n >> 1);
+}
+
+static void generator_volume_hits_reserve(MVolumeSampleGenerator_Random_ThreadContext *ctx, int tothits)
+{
+ if (tothits > ctx->allochits) {
+ ctx->allochits = (int)hibit((unsigned int)tothits) << 1;
+ ctx->ray_hits = MEM_reallocN(ctx->ray_hits, (size_t)ctx->allochits * sizeof(BVHTreeRayHit));
+ }
+}
+
+static void *generator_volume_random_thread_context_create(const MVolumeSampleGenerator_Random *gen, int start)
+{
+ MVolumeSampleGenerator_Random_ThreadContext *ctx = MEM_callocN(sizeof(*ctx), "thread context");
+
+ ctx->bvhdata = &gen->bvhdata;
+
+ ctx->rng = BLI_rng_new(gen->seed);
+ // 11 RNG gets per sample
+ BLI_rng_skip(ctx->rng, start * 11);
+
+ generator_volume_hits_reserve(ctx, 64);
+
+ return ctx;
+}
+
+static void generator_volume_random_thread_context_free(const MVolumeSampleGenerator_Random *UNUSED(gen), void *thread_ctx)
+{
+ MVolumeSampleGenerator_Random_ThreadContext *ctx = thread_ctx;
+ BLI_rng_free(ctx->rng);
+
+ if (ctx->ray_hits) {
+ MEM_freeN(ctx->ray_hits);
+ }
+
+ MEM_freeN(ctx);
+}
+
+static void generator_volume_ray_cb(void *userdata, int index, const BVHTreeRay *ray, BVHTreeRayHit *hit)
+{
+ MVolumeSampleGenerator_Random_ThreadContext *ctx = userdata;
+
+ ctx->bvhdata->raycast_callback((void *)ctx->bvhdata, index, ray, hit);
+
+ if (hit->index >= 0) {
+ ++ctx->tothits;
+ generator_volume_hits_reserve(ctx, ctx->tothits);
+
+ memcpy(&ctx->ray_hits[ctx->tothits-1], hit, sizeof(BVHTreeRayHit));
+ }
+}
+
+typedef struct Ray {
+ float start[3];
+ float end[3];
+} Ray;
+
+static void generator_volume_random_cast_ray(MVolumeSampleGenerator_Random_ThreadContext *ctx, const Ray* ray)
+{
+ float dir[3];
+
+ sub_v3_v3v3(dir, ray->end, ray->start);
+ normalize_v3(dir);
+
+ ctx->tothits = 0;
+ BLI_bvhtree_ray_cast_all(ctx->bvhdata->tree, ray->start, dir, 0.0f, BVH_RAYCAST_DIST_MAX,
+ generator_volume_ray_cb, ctx);
+
+ ctx->cur_seg = 0;
+ ctx->cur_tot = 0;
+ ctx->cur_sample = 0;
+}
+
+static void generator_volume_init_segment(const MVolumeSampleGenerator_Random *gen, MVolumeSampleGenerator_Random_ThreadContext *ctx)
+{
+ BVHTreeRayHit *a, *b;
+ float length;
+
+ BLI_assert(ctx->cur_seg + 1 < ctx->tothits);
+ a = &ctx->ray_hits[ctx->cur_seg];
+ b = &ctx->ray_hits[ctx->cur_seg + 1];
+
+ length = len_v3v3(a->co, b->co);
+ ctx->cur_tot = min_ii(gen->max_samples_per_ray, (int)ceilf(length * gen->density));
+ ctx->cur_sample = 0;
+}
+
+static void generator_volume_get_ray(RNG *rng, Ray *ray)
+{
+ /* bounding box margin to get clean ray intersections */
+ static const float margin = 0.01f;
+
+ ray->start[0] = BLI_rng_get_float(rng);
+ ray->start[1] = BLI_rng_get_float(rng);
+ ray->start[2] = 0.0f;
+ ray->end[0] = BLI_rng_get_float(rng);
+ ray->end[1] = BLI_rng_get_float(rng);
+ ray->end[2] = 1.0f;
+
+ int axis = BLI_rng_get_int(rng) % 3;
+ switch (axis) {
+ case 0: break;
+ case 1:
+ SHIFT3(float, ray->start[0], ray->start[1], ray->start[2]);
+ SHIFT3(float, ray->end[0], ray->end[1], ray->end[2]);
+ break;
+ case 2:
+ SHIFT3(float, ray->start[2], ray->start[1], ray->start[0]);
+ SHIFT3(float, ray->end[2], ray->end[1], ray->end[0]);
+ break;
+ }
+
+ mul_v3_fl(ray->start, 1.0f + 2.0f*margin);
+ add_v3_fl(ray->start, -margin);
+ mul_v3_fl(ray->end, 1.0f + 2.0f*margin);
+ add_v3_fl(ray->end, -margin);
+}
+
+static void generator_volume_ray_to_bbox(const MVolumeSampleGenerator_Random *gen, Ray *ray)
+{
+ madd_v3_v3v3v3(ray->start, gen->min, ray->start, gen->extent);
+ madd_v3_v3v3v3(ray->end, gen->min, ray->end, gen->extent);
+}
+
+static bool generator_volume_random_make_sample(const MVolumeSampleGenerator_Random *gen, void *thread_ctx, MeshSample *sample)
+{
+ MVolumeSampleGenerator_Random_ThreadContext *ctx = thread_ctx;
+
+ Ray ray1, ray2;
+ // Do all RNG gets at the beggining for keeping consistent state
+ generator_volume_get_ray(ctx->rng, &ray1);
+ generator_volume_get_ray(ctx->rng, &ray2);
+ float t = BLI_rng_get_float(ctx->rng);
+
+ if (ctx->cur_seg + 1 >= ctx->tothits) {
+ generator_volume_ray_to_bbox(gen, &ray1);
+ generator_volume_random_cast_ray(ctx, &ray1);
+ if (ctx->tothits < 2)
+ return false;
+ }
+
+ if (ctx->cur_sample >= ctx->cur_tot) {
+ ctx->cur_seg += 2;
+
+ if (ctx->cur_seg + 1 >= ctx->tothits) {
+ generator_volume_ray_to_bbox(gen, &ray2);
+ generator_volume_random_cast_ray(ctx, &ray2);
+ if (ctx->tothits < 2)
+ return false;
+ }
+
+ generator_volume_init_segment(gen, ctx);
+ }
+ BVHTreeRayHit *a = &ctx->ray_hits[ctx->cur_seg];
+ BVHTreeRayHit *b = &ctx->ray_hits[ctx->cur_seg + 1];
+
+ if (ctx->cur_sample < ctx->cur_tot) {
+
+ sample->orig_verts[0] = SAMPLE_INDEX_INVALID;
+ sample->orig_verts[1] = SAMPLE_INDEX_INVALID;
+ sample->orig_verts[2] = SAMPLE_INDEX_INVALID;
+
+ interp_v3_v3v3(sample->orig_weights, a->co, b->co, t);
+
+ ctx->cur_sample += 1;
+
+ return true;
+ }
+
+ return false;
+}
+
+MeshSampleGenerator *BKE_mesh_sample_gen_volume_random_bbray(unsigned int seed, float density)
+{
+ MVolumeSampleGenerator_Random *gen;
+
+ gen = MEM_callocN(sizeof(MVolumeSampleGenerator_Random), "MVolumeSampleGenerator_Random");
+ sample_generator_init(&gen->base,
+ (GeneratorFreeFp)generator_volume_random_free,
+ (GeneratorBindFp)generator_volume_random_bind,
+ (GeneratorUnbindFp) generator_volume_random_unbind,
+ (GeneratorThreadContextCreateFp)generator_volume_random_thread_context_create,
+ (GeneratorThreadContextFreeFp)generator_volume_random_thread_context_free,
+ (GeneratorMakeSampleFp)generator_volume_random_make_sample,
+ NULL);
+
+ gen->seed = seed;
+ gen->density = density;
+
+ return &gen->base;
+}
+
+/* ------------------------------------------------------------------------- */
+
+void BKE_mesh_sample_free_generator(MeshSampleGenerator *gen)
+{
+ if (gen->default_ctx) {
+ if (gen->thread_context_free) {
+ gen->thread_context_free(gen, gen->default_ctx);
+ }
+ }
+
+ BKE_mesh_sample_generator_unbind(gen);
+
+ gen->free(gen);
+}
+
+
+/* ==== Sampling ==== */
+
+void BKE_mesh_sample_generator_bind(MeshSampleGenerator *gen, DerivedMesh *dm)
+{
+ BLI_assert(gen->dm == NULL && "Generator already bound");
+
+ gen->dm = dm;
+ if (gen->bind) {
+ gen->bind(gen);
+ }
+}
+
+void BKE_mesh_sample_generator_unbind(MeshSampleGenerator *gen)
+{
+ if (gen->dm) {
+ if (gen->unbind) {
+ gen->unbind(gen);
+ }
+ gen->dm = NULL;
+ }
+}
+
+unsigned int BKE_mesh_sample_gen_get_max_samples(const MeshSampleGenerator *gen)
+{
+ if (gen->get_max_samples) {
+ return gen->get_max_samples(gen);
+ }
+ return 0;
+}
+
+bool BKE_mesh_sample_generate(MeshSampleGenerator *gen, struct MeshSample *sample)
+{
+ if (!gen->default_ctx && gen->thread_context_create) {
+ gen->default_ctx = gen->thread_context_create(gen, 0);
+ }
+
+ return gen->make_sample(gen, gen->default_ctx, sample);
+}
+
+typedef struct MeshSamplePoolData {
+ const MeshSampleGenerator *gen;
+ int output_stride;
+} MeshSamplePoolData;
+
+typedef struct MeshSampleTaskData {
+ void *thread_ctx;
+ void *output_buffer;
+ int count;
+ int result;
+} MeshSampleTaskData;
+
+static void mesh_sample_generate_task_run(TaskPool * __restrict pool, void *taskdata_, int UNUSED(threadid))
+{
+ MeshSamplePoolData *pooldata = BLI_task_pool_userdata(pool);
+ const MeshSampleGenerator *gen = pooldata->gen;
+ const int output_stride = pooldata->output_stride;
+
+ GeneratorMakeSampleFp make_sample = gen->make_sample;
+ MeshSampleTaskData *taskdata = taskdata_;
+ void *thread_ctx = taskdata->thread_ctx;
+ const int count = taskdata->count;
+ MeshSample *sample = taskdata->output_buffer;
+
+ int i = 0;
+ for (; i < count; ++i, sample = (MeshSample *)((char *)sample + output_stride)) {
+ if (!make_sample(gen, thread_ctx, sample)) {
+ break;
+ }
+ }
+
+ taskdata->result = i;
+}
+
+int BKE_mesh_sample_generate_batch_ex(MeshSampleGenerator *gen,
+ void *output_buffer, int output_stride, int count,
+ bool use_threads)
+{
+ if (use_threads) {
+ TaskScheduler *scheduler = BLI_task_scheduler_get();
+
+ MeshSamplePoolData pool_data;
+ pool_data.gen = gen;
+ pool_data.output_stride = output_stride;
+ TaskPool *task_pool = BLI_task_pool_create(scheduler, &pool_data);
+
+ const int num_tasks = (count + gen->task_size - 1) / gen->task_size;
+ MeshSampleTaskData *task_data = MEM_callocN(sizeof(MeshSampleTaskData) * (unsigned int)num_tasks, "mesh sample task data");
+
+ {
+ MeshSampleTaskData *td = task_data;
+ int start = 0;
+ for (int i = 0; i < num_tasks; ++i, ++td, start += gen->task_size) {
+ if (gen->thread_context_create) {
+ td->thread_ctx = gen->thread_context_create(gen, start);
+ }
+ td->output_buffer = (char *)output_buffer + start * output_stride;
+ td->count = min_ii(count - start, gen->task_size);
+
+ BLI_task_pool_push(task_pool, mesh_sample_generate_task_run, td, false, TASK_PRIORITY_LOW);
+ }
+ }
+
+ BLI_task_pool_work_and_wait(task_pool);
+ BLI_task_pool_free(task_pool);
+
+ int totresult = 0;
+ {
+ MeshSampleTaskData *td = task_data;
+ for (int i = 0; i < num_tasks; ++i, ++td) {
+ totresult += td->result;
+ }
+ }
+
+ if (gen->thread_context_free) {
+ MeshSampleTaskData *td = task_data;
+ for (int i = 0; i < num_tasks; ++i, ++td) {
+ if (td->thread_ctx) {
+ gen->thread_context_free(gen, td->thread_ctx);
+ }
+ }
+ }
+ MEM_freeN(task_data);
+
+ return totresult;
+ }
+ else {
+ void *thread_ctx = NULL;
+ if (gen->thread_context_create) {
+ thread_ctx = gen->thread_context_create(gen, 0);
+ }
+
+ MeshSample *sample = output_buffer;
+ int i = 0;
+ for (; i < count; ++i, sample = (MeshSample *)((char *)sample + output_stride)) {
+ if (!gen->make_sample(gen, thread_ctx, sample)) {
+ break;
+ }
+ }
+
+ if (thread_ctx && gen->thread_context_free) {
+ gen->thread_context_free(gen, thread_ctx);
+ }
+
+ return i;
+ }
+}
+
+int BKE_mesh_sample_generate_batch(MeshSampleGenerator *gen,
+ MeshSample *output_buffer, int count)
+{
+ return BKE_mesh_sample_generate_batch_ex(gen, output_buffer, sizeof(MeshSample), count, true);
+}
+
+/* ==== Utilities ==== */
+
+#include "DNA_particle_types.h"
+
+#include "BKE_bvhutils.h"
+#include "BKE_particle.h"
+
+bool BKE_mesh_sample_from_particle(MeshSample *sample, ParticleSystem *psys, DerivedMesh *dm, ParticleData *pa)
+{
+ MVert *mverts;
+ MFace *mface;
+ float mapfw[4];
+ int mapindex;
+ float *co1 = NULL, *co2 = NULL, *co3 = NULL, *co4 = NULL;
+ float vec[3];
+ float w[4];
+
+ if (!psys_get_index_on_dm(psys, dm, pa, &mapindex, mapfw))
+ return false;
+
+ mface = dm->getTessFaceData(dm, mapindex, CD_MFACE);
+ mverts = dm->getVertDataArray(dm, CD_MVERT);
+
+ co1 = mverts[mface->v1].co;
+ co2 = mverts[mface->v2].co;
+ co3 = mverts[mface->v3].co;
+
+ if (mface->v4) {
+ co4 = mverts[mface->v4].co;
+
+ interp_v3_v3v3v3v3(vec, co1, co2, co3, co4, mapfw);
+ }
+ else {
+ interp_v3_v3v3v3(vec, co1, co2, co3, mapfw);
+ }
+
+ /* test both triangles of the face */
+ interp_weights_tri_v3(w, co1, co2, co3, vec);
+ if (w[0] <= 1.0f && w[1] <= 1.0f && w[2] <= 1.0f) {
+ sample->orig_verts[0] = mface->v1;
+ sample->orig_verts[1] = mface->v2;
+ sample->orig_verts[2] = mface->v3;
+
+ copy_v3_v3(sample->orig_weights, w);
+ return true;
+ }
+ else if (mface->v4) {
+ interp_weights_tri_v3(w, co3, co4, co1, vec);
+ sample->orig_verts[0] = mface->v3;
+ sample->orig_verts[1] = mface->v4;
+ sample->orig_verts[2] = mface->v1;
+
+ copy_v3_v3(sample->orig_weights, w);
+ return true;
+ }
+ else
+ return false;
+}
+
+bool BKE_mesh_sample_to_particle(MeshSample *sample, ParticleSystem *UNUSED(psys), DerivedMesh *dm, BVHTreeFromMesh *bvhtree, ParticleData *pa)
+{
+ BVHTreeNearest nearest;
+ float vec[3], nor[3], tang[3];
+
+ BKE_mesh_sample_eval(dm, sample, vec, nor, tang);
+
+ nearest.index = -1;
+ nearest.dist_sq = FLT_MAX;
+ BLI_bvhtree_find_nearest(bvhtree->tree, vec, &nearest, bvhtree->nearest_callback, bvhtree);
+ if (nearest.index >= 0) {
+ MFace *mface = dm->getTessFaceData(dm, nearest.index, CD_MFACE);
+ MVert *mverts = dm->getVertDataArray(dm, CD_MVERT);
+
+ float *co1 = mverts[mface->v1].co;
+ float *co2 = mverts[mface->v2].co;
+ float *co3 = mverts[mface->v3].co;
+ float *co4 = mface->v4 ? mverts[mface->v4].co : NULL;
+
+ pa->num = nearest.index;
+ pa->num_dmcache = DMCACHE_NOTFOUND;
+
+ interp_weights_quad_v3(pa->fuv, co1, co2, co3, co4, vec);
+ pa->foffset = 0.0f; /* XXX any sensible way to reconstruct this? */
+
+ return true;
+ }
+ else
+ return false;
+}
diff --git a/source/blender/blenkernel/intern/object.c b/source/blender/blenkernel/intern/object.c
index 875d716305f..2cb4356a52a 100644
--- a/source/blender/blenkernel/intern/object.c
+++ b/source/blender/blenkernel/intern/object.c
@@ -42,6 +42,7 @@
#include "DNA_constraint_types.h"
#include "DNA_gpencil_types.h"
#include "DNA_group_types.h"
+#include "DNA_groom_types.h"
#include "DNA_key_types.h"
#include "DNA_lamp_types.h"
#include "DNA_lattice_types.h"
@@ -74,6 +75,7 @@
#include "BKE_pbvh.h"
#include "BKE_main.h"
#include "BKE_global.h"
+#include "BKE_groom.h"
#include "BKE_idprop.h"
#include "BKE_armature.h"
#include "BKE_action.h"
@@ -337,6 +339,13 @@ void BKE_object_free_derived_caches(Object *ob)
atomic_fetch_and_or_int32(&cu->bb->flag, BOUNDBOX_DIRTY);
}
}
+ else if (ELEM(ob->type, OB_GROOM)) {
+ Groom *groom = ob->data;
+
+ if (groom && groom->bb) {
+ atomic_fetch_and_or_int32(&groom->bb->flag, BOUNDBOX_DIRTY);
+ }
+ }
if (ob->bb) {
MEM_freeN(ob->bb);
@@ -527,6 +536,11 @@ bool BKE_object_is_in_editmode(const Object *ob)
if (cu->editnurb)
return true;
}
+ else if (ob->type == OB_GROOM) {
+ Groom *groom = ob->data;
+ if (groom->editgroom)
+ return true;
+ }
return false;
}
@@ -602,6 +616,7 @@ static const char *get_obdata_defname(int type)
case OB_SURF: return DATA_("Surf");
case OB_FONT: return DATA_("Text");
case OB_MBALL: return DATA_("Mball");
+ case OB_GROOM: return DATA_("Groom");
case OB_CAMERA: return DATA_("Camera");
case OB_LAMP: return DATA_("Lamp");
case OB_LATTICE: return DATA_("Lattice");
@@ -632,6 +647,7 @@ void *BKE_object_obdata_add_from_type(Main *bmain, int type, const char *name)
case OB_ARMATURE: return BKE_armature_add(bmain, name);
case OB_SPEAKER: return BKE_speaker_add(bmain, name);
case OB_LIGHTPROBE:return BKE_lightprobe_add(bmain, name);
+ case OB_GROOM: return BKE_groom_add(bmain, name);
case OB_EMPTY: return NULL;
default:
printf("%s: Internal error, bad type: %d\n", __func__, type);
diff --git a/source/blender/blenkernel/intern/object_update.c b/source/blender/blenkernel/intern/object_update.c
index c70e07e6c4c..10ccc9fb672 100644
--- a/source/blender/blenkernel/intern/object_update.c
+++ b/source/blender/blenkernel/intern/object_update.c
@@ -61,6 +61,7 @@
#include "BKE_mball.h"
#include "BKE_mesh.h"
#include "BKE_image.h"
+#include "BKE_groom.h"
#include "MEM_guardedalloc.h"
#include "DEG_depsgraph.h"
@@ -323,6 +324,9 @@ void BKE_object_eval_uber_data(const EvaluationContext *eval_ctx,
case OB_MBALL:
BKE_mball_batch_cache_dirty(ob->data, BKE_MBALL_BATCH_DIRTY_ALL);
break;
+ case OB_GROOM:
+ BKE_groom_batch_cache_dirty(ob->data, BKE_GROOM_BATCH_DIRTY_ALL);
+ break;
}
if (DEG_depsgraph_use_copy_on_write()) {
@@ -428,6 +432,10 @@ void BKE_object_data_select_update(const EvaluationContext *UNUSED(eval_ctx),
BKE_lattice_batch_cache_dirty((struct Lattice *)object_data,
BKE_CURVE_BATCH_DIRTY_SELECT);
break;
+ case ID_GM:
+ BKE_groom_batch_cache_dirty((struct Groom *)object_data,
+ BKE_GROOM_BATCH_DIRTY_SELECT);
+ break;
default:
break;
}
diff --git a/source/blender/blenkernel/intern/particle.c b/source/blender/blenkernel/intern/particle.c
index 1917e370aa9..713d523cb1e 100644
--- a/source/blender/blenkernel/intern/particle.c
+++ b/source/blender/blenkernel/intern/particle.c
@@ -1420,6 +1420,11 @@ static int psys_map_index_on_dm(DerivedMesh *dm, int from, int index, int index_
return 1;
}
+int psys_get_index_on_dm(ParticleSystem *psys, DerivedMesh *dm, ParticleData *pa, int *mapindex, float mapfw[4])
+{
+ return psys_map_index_on_dm(dm, psys->part->from, pa->num, pa->num_dmcache, pa->fuv, pa->foffset, mapindex, mapfw);
+}
+
/* interprets particle data to get a point on a mesh in object space */
void psys_particle_on_dm(DerivedMesh *dm_final, int from, int index, int index_dmcache,
const float fw[4], float foffset, float vec[3], float nor[3], float utan[3], float vtan[3],
diff --git a/source/blender/blenlib/BLI_math_geom.h b/source/blender/blenlib/BLI_math_geom.h
index ffe0ce11cef..d58e3022e27 100644
--- a/source/blender/blenlib/BLI_math_geom.h
+++ b/source/blender/blenlib/BLI_math_geom.h
@@ -339,6 +339,8 @@ bool clip_segment_v3_plane_n(
/****************************** Interpolation ********************************/
void interp_weights_tri_v3(float w[3], const float a[3], const float b[3], const float c[3], const float p[3]);
void interp_weights_quad_v3(float w[4], const float a[3], const float b[3], const float c[3], const float d[3], const float p[3]);
+/* also returns three indices of the triangle actually used */
+void interp_weights_quad_v3_index(int tri[3], float w[4], const float v1[3], const float v2[3], const float v3[3], const float v4[3], const float co[3]);
void interp_weights_poly_v3(float w[], float v[][3], const int n, const float co[3]);
void interp_weights_poly_v2(float w[], float v[][2], const int n, const float co[2]);
diff --git a/source/blender/blenlib/intern/math_geom.c b/source/blender/blenlib/intern/math_geom.c
index e179447936a..171af1c8264 100644
--- a/source/blender/blenlib/intern/math_geom.c
+++ b/source/blender/blenlib/intern/math_geom.c
@@ -2976,6 +2976,71 @@ void interp_weights_quad_v3(float w[4], const float v1[3], const float v2[3], co
}
}
+void interp_weights_quad_v3_index(int tri[3], float w[4], const float v1[3], const float v2[3], const float v3[3], const float v4[3], const float co[3])
+{
+ float w2[3];
+
+ w[0] = w[1] = w[2] = w[3] = 0.0f;
+ tri[0] = tri[1] = tri[2] = -1;
+
+ /* first check for exact match */
+ if (equals_v3v3(co, v1)) {
+ w[0] = 1.0f;
+ tri[0] = 0; tri[1] = 1; tri[2] = 3;
+ }
+ else if (equals_v3v3(co, v2)) {
+ w[1] = 1.0f;
+ tri[0] = 0; tri[1] = 1; tri[2] = 3;
+ }
+ else if (equals_v3v3(co, v3)) {
+ w[2] = 1.0f;
+ tri[0] = 1; tri[1] = 2; tri[2] = 3;
+ }
+ else if (v4 && equals_v3v3(co, v4)) {
+ w[3] = 1.0f;
+ tri[0] = 1; tri[1] = 2; tri[2] = 3;
+ }
+ else {
+ /* otherwise compute barycentric interpolation weights */
+ float n1[3], n2[3], n[3];
+ bool degenerate;
+
+ sub_v3_v3v3(n1, v1, v3);
+ if (v4) {
+ sub_v3_v3v3(n2, v2, v4);
+ }
+ else {
+ sub_v3_v3v3(n2, v2, v3);
+ }
+ cross_v3_v3v3(n, n1, n2);
+
+ /* OpenGL seems to split this way, so we do too */
+ if (v4) {
+ degenerate = barycentric_weights(v1, v2, v4, co, n, w);
+ SWAP(float, w[2], w[3]);
+ tri[0] = 0; tri[1] = 1; tri[2] = 3;
+
+ if (degenerate || (w[0] < 0.0f)) {
+ /* if w[1] is negative, co is on the other side of the v1-v3 edge,
+ * so we interpolate using the other triangle */
+ degenerate = barycentric_weights(v2, v3, v4, co, n, w2);
+
+ if (!degenerate) {
+ w[0] = 0.0f;
+ w[1] = w2[0];
+ w[2] = w2[1];
+ w[3] = w2[2];
+ tri[0] = 1; tri[1] = 2; tri[2] = 3;
+ }
+ }
+ }
+ else {
+ barycentric_weights(v1, v2, v3, co, n, w);
+ tri[0] = 0; tri[1] = 1; tri[2] = 2;
+ }
+ }
+}
+
/* return 1 of point is inside triangle, 2 if it's on the edge, 0 if point is outside of triangle */
int barycentric_inside_triangle_v2(const float w[3])
{
diff --git a/source/blender/blenloader/intern/readfile.c b/source/blender/blenloader/intern/readfile.c
index 4308fe0fee3..ac5673e1402 100644
--- a/source/blender/blenloader/intern/readfile.c
+++ b/source/blender/blenloader/intern/readfile.c
@@ -72,8 +72,10 @@
#include "DNA_effect_types.h"
#include "DNA_fileglobal_types.h"
#include "DNA_genfile.h"
+#include "DNA_groom_types.h"
#include "DNA_group_types.h"
#include "DNA_gpencil_types.h"
+#include "DNA_hair_types.h"
#include "DNA_ipo_types.h"
#include "DNA_key_types.h"
#include "DNA_lattice_types.h"
@@ -5214,6 +5216,25 @@ static void direct_link_pose(FileData *fd, bPose *pose)
}
}
+static void direct_link_hair(FileData *fd, HairSystem* hsys)
+{
+ if (!hsys) {
+ return;
+ }
+
+ hsys->pattern = newdataadr(fd, hsys->pattern);
+ if ( hsys->pattern )
+ {
+ hsys->pattern->follicles = newdataadr(fd, hsys->pattern->follicles);
+ }
+
+ hsys->curves = newdataadr(fd, hsys->curves);
+ hsys->verts = newdataadr(fd, hsys->verts);
+
+ hsys->draw_batch_cache = NULL;
+ hsys->draw_texture_cache = NULL;
+}
+
static void direct_link_modifiers(FileData *fd, ListBase *lb)
{
ModifierData *md;
@@ -5536,6 +5557,14 @@ static void direct_link_modifiers(FileData *fd, ListBase *lb)
}
}
}
+ else if (md->type == eModifierType_Fur) {
+ FurModifierData *fmd = (FurModifierData *)md;
+
+ fmd->hair_system = newdataadr(fd, fmd->hair_system);
+ direct_link_hair(fd, fmd->hair_system);
+
+ fmd->draw_settings = newdataadr(fd, fmd->draw_settings);
+ }
}
}
@@ -8394,6 +8423,50 @@ static void direct_link_linestyle(FileData *fd, FreestyleLineStyle *linestyle)
}
}
+/* ************ READ GROOM *************** */
+
+static void lib_link_groom(FileData *fd, Main *bmain)
+{
+ for (Groom *groom = bmain->grooms.first; groom; groom = groom->id.next) {
+ ID *id = (ID *)groom;
+
+ if ((id->tag & LIB_TAG_NEED_LINK) == 0) {
+ continue;
+ }
+ IDP_LibLinkProperty(id->properties, fd);
+ id_us_ensure_real(id);
+
+ groom->scalp_object = newlibadr(fd, id->lib, groom->scalp_object);
+
+ id->tag &= ~LIB_TAG_NEED_LINK;
+ }
+}
+
+static void direct_link_groom(FileData *fd, Groom *groom)
+{
+ groom->adt= newdataadr(fd, groom->adt);
+ direct_link_animdata(fd, groom->adt);
+
+ link_list(fd, &groom->bundles);
+ for (GroomBundle *bundle = groom->bundles.first; bundle; bundle = bundle->next)
+ {
+ bundle->sections = newdataadr(fd, bundle->sections);
+ bundle->verts = newdataadr(fd, bundle->verts);
+ bundle->scalp_region = newdataadr(fd, bundle->scalp_region);
+ bundle->curvecache = NULL;
+ bundle->curvesize = 0;
+ bundle->totcurvecache = 0;
+ }
+
+ groom->hair_system = newdataadr(fd, groom->hair_system);
+ direct_link_hair(fd, groom->hair_system);
+ groom->hair_draw_settings = newdataadr(fd, groom->hair_draw_settings);
+
+ groom->bb = NULL;
+ groom->editgroom = NULL;
+ groom->batch_cache = NULL;
+}
+
/* ************** GENERAL & MAIN ******************** */
@@ -8691,6 +8764,9 @@ static BHead *read_libblock(FileData *fd, Main *main, BHead *bhead, const short
case ID_WS:
direct_link_workspace(fd, (WorkSpace *)id, main);
break;
+ case ID_GM:
+ direct_link_groom(fd, (Groom *)id);
+ break;
}
oldnewmap_free_unused(fd->datamap);
@@ -8873,6 +8949,7 @@ static void lib_link_all(FileData *fd, Main *main)
lib_link_gpencil(fd, main);
lib_link_cachefiles(fd, main);
lib_link_workspaces(fd, main);
+ lib_link_groom(fd, main);
lib_link_library(fd, main); /* only init users */
}
@@ -9502,6 +9579,11 @@ static void expand_particlesettings(FileData *fd, Main *mainvar, ParticleSetting
}
}
+static void expand_groom(FileData *fd, Main *mainvar, Groom *groom)
+{
+ expand_doit(fd, mainvar, groom->scalp_object);
+}
+
static void expand_group(FileData *fd, Main *mainvar, Group *group)
{
GroupObject *go;
@@ -10265,6 +10347,9 @@ void BLO_expand_main(void *fdhandle, Main *mainvar)
case ID_AC:
expand_action(fd, mainvar, (bAction *)id); // XXX deprecated - old animation system
break;
+ case ID_GM:
+ expand_groom(fd, mainvar, (Groom *)id);
+ break;
case ID_GR:
expand_group(fd, mainvar, (Group *)id);
break;
diff --git a/source/blender/blenloader/intern/writefile.c b/source/blender/blenloader/intern/writefile.c
index 7f4e7fdc646..973fac1d13a 100644
--- a/source/blender/blenloader/intern/writefile.c
+++ b/source/blender/blenloader/intern/writefile.c
@@ -118,9 +118,11 @@
#include "DNA_controller_types.h"
#include "DNA_dynamicpaint_types.h"
#include "DNA_genfile.h"
+#include "DNA_groom_types.h"
#include "DNA_group_types.h"
#include "DNA_gpencil_types.h"
#include "DNA_fileglobal_types.h"
+#include "DNA_hair_types.h"
#include "DNA_key_types.h"
#include "DNA_lattice_types.h"
#include "DNA_lamp_types.h"
@@ -1765,6 +1767,18 @@ static void write_fmaps(WriteData *wd, ListBase *fbase)
}
}
+static void write_hair(WriteData *wd, HairSystem *hsys)
+{
+ if ( hsys->pattern )
+ {
+ writestruct(wd, DATA, HairPattern, 1, hsys->pattern);
+ writestruct(wd, DATA, HairFollicle, hsys->pattern->num_follicles, hsys->pattern->follicles);
+ }
+
+ writestruct(wd, DATA, HairGuideCurve, hsys->totcurves, hsys->curves);
+ writestruct(wd, DATA, HairGuideVertex, hsys->totverts, hsys->verts);
+}
+
static void write_modifiers(WriteData *wd, ListBase *modbase)
{
ModifierData *md;
@@ -1936,6 +1950,18 @@ static void write_modifiers(WriteData *wd, ListBase *modbase)
}
}
}
+ else if (md->type == eModifierType_Fur) {
+ FurModifierData *fmd = (FurModifierData *)md;
+
+ if (fmd->hair_system) {
+ writestruct(wd, DATA, HairSystem, 1, fmd->hair_system);
+ write_hair(wd, fmd->hair_system);
+ }
+ if (fmd->draw_settings)
+ {
+ writestruct(wd, DATA, HairDrawSettings, 1, fmd->draw_settings);
+ }
+ }
}
}
@@ -3822,6 +3848,32 @@ static void write_workspace(WriteData *wd, WorkSpace *workspace)
writelist(wd, DATA, TransformOrientation, transform_orientations);
}
+static void write_groom(WriteData *wd, Groom *groom)
+{
+ writestruct(wd, ID_GM, Groom, 1, groom);
+ write_iddata(wd, &groom->id);
+ if (groom->adt) {
+ write_animdata(wd, groom->adt);
+ }
+
+ writelist(wd, DATA, GroomBundle, &groom->bundles);
+ for (GroomBundle *bundle = groom->bundles.first; bundle; bundle = bundle->next)
+ {
+ writestruct(wd, DATA, GroomSection, bundle->totsections, bundle->sections);
+ writestruct(wd, DATA, GroomSectionVertex, bundle->totverts, bundle->verts);
+ writestruct(wd, DATA, MeshSample, bundle->numshapeverts + 1, bundle->scalp_region);
+ }
+
+ if (groom->hair_system) {
+ writestruct(wd, DATA, HairSystem, 1, groom->hair_system);
+ write_hair(wd, groom->hair_system);
+ }
+ if (groom->hair_draw_settings)
+ {
+ writestruct(wd, DATA, HairDrawSettings, 1, groom->hair_draw_settings);
+ }
+}
+
/* Keep it last of write_foodata functions. */
static void write_libraries(WriteData *wd, Main *main)
{
@@ -4124,6 +4176,9 @@ static bool write_file_handle(
case ID_CF:
write_cachefile(wd, (CacheFile *)id);
break;
+ case ID_GM:
+ write_groom(wd, (Groom *)id);
+ break;
case ID_LI:
/* Do nothing, handled below - and should never be reached. */
BLI_assert(0);
diff --git a/source/blender/blentranslation/BLT_translation.h b/source/blender/blentranslation/BLT_translation.h
index ed1993015f6..870375936aa 100644
--- a/source/blender/blentranslation/BLT_translation.h
+++ b/source/blender/blentranslation/BLT_translation.h
@@ -154,6 +154,7 @@ bool BLT_lang_is_ime_supported(void);
#define BLT_I18NCONTEXT_ID_WINDOWMANAGER "WindowManager"
#define BLT_I18NCONTEXT_ID_MOVIECLIP "MovieClip"
#define BLT_I18NCONTEXT_ID_MASK "Mask"
+#define BLT_I18NCONTEXT_ID_GROOM "Groom"
/* Helper for bpy.app.i18n object... */
typedef struct {
@@ -178,6 +179,7 @@ typedef struct {
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_CURVE, "id_curve"), \
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_FREESTYLELINESTYLE, "id_fs_linestyle"), \
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_GPENCIL, "id_gpencil"), \
+ BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_GROOM, "id_groom"), \
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_GROUP, "id_group"), \
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_ID, "id_id"), \
BLT_I18NCONTEXTS_ITEM(BLT_I18NCONTEXT_ID_IMAGE, "id_image"), \
diff --git a/source/blender/bmesh/CMakeLists.txt b/source/blender/bmesh/CMakeLists.txt
index 5245d24a075..a4992222216 100644
--- a/source/blender/bmesh/CMakeLists.txt
+++ b/source/blender/bmesh/CMakeLists.txt
@@ -54,6 +54,7 @@ set(SRC
operators/bmo_dupe.c
operators/bmo_edgenet.c
operators/bmo_extrude.c
+ operators/bmo_face_island.c
operators/bmo_fill_attribute.c
operators/bmo_fill_edgeloop.c
operators/bmo_fill_grid.c
diff --git a/source/blender/bmesh/intern/bmesh_opdefines.c b/source/blender/bmesh/intern/bmesh_opdefines.c
index 4847ae4be42..37a4fb5c29c 100644
--- a/source/blender/bmesh/intern/bmesh_opdefines.c
+++ b/source/blender/bmesh/intern/bmesh_opdefines.c
@@ -2017,6 +2017,26 @@ static BMOpDefine bmo_symmetrize_def = {
BMO_OPTYPE_FLAG_SELECT_VALIDATE),
};
+/*
+ * Face island boundary.
+ */
+static BMOpDefine bmo_face_island_boundary_def = {
+ "face_island_boundary",
+ /* slots_in */
+ {{"faces", BMO_OP_SLOT_ELEMENT_BUF, {BM_FACE}},
+ {{'\0'}},
+ },
+ /* slots_out */
+ {{"boundary", BMO_OP_SLOT_ELEMENT_BUF, {BM_LOOP}},
+ {{'\0'}},
+ },
+ bmo_face_island_boundary_exec,
+ (BMO_OPTYPE_FLAG_UNTAN_MULTIRES |
+ BMO_OPTYPE_FLAG_NORMALS_CALC |
+ BMO_OPTYPE_FLAG_SELECT_FLUSH |
+ BMO_OPTYPE_FLAG_SELECT_VALIDATE),
+};
+
const BMOpDefine *bmo_opdefines[] = {
&bmo_automerge_def,
&bmo_average_vert_facedata_def,
@@ -2052,6 +2072,7 @@ const BMOpDefine *bmo_opdefines[] = {
&bmo_duplicate_def,
&bmo_holes_fill_def,
&bmo_face_attribute_fill_def,
+ &bmo_face_island_boundary_def,
&bmo_offset_edgeloops_def,
&bmo_edgeloop_fill_def,
&bmo_edgenet_fill_def,
diff --git a/source/blender/bmesh/intern/bmesh_operators_private.h b/source/blender/bmesh/intern/bmesh_operators_private.h
index 5548ee7c361..22a9edab321 100644
--- a/source/blender/bmesh/intern/bmesh_operators_private.h
+++ b/source/blender/bmesh/intern/bmesh_operators_private.h
@@ -63,6 +63,7 @@ void bmo_dissolve_degenerate_exec(BMesh *bm, BMOperator *op);
void bmo_duplicate_exec(BMesh *bm, BMOperator *op);
void bmo_edgeloop_fill_exec(BMesh *bm, BMOperator *op);
void bmo_face_attribute_fill_exec(BMesh *bm, BMOperator *op);
+void bmo_face_island_boundary_exec(BMesh *bm, BMOperator *op);
void bmo_holes_fill_exec(BMesh *bm, BMOperator *op);
void bmo_edgenet_fill_exec(BMesh *bm, BMOperator *op);
void bmo_edgenet_prepare_exec(BMesh *bm, BMOperator *op);
diff --git a/source/blender/bmesh/intern/bmesh_walkers_impl.c b/source/blender/bmesh/intern/bmesh_walkers_impl.c
index 279440984bb..45288f1350d 100644
--- a/source/blender/bmesh/intern/bmesh_walkers_impl.c
+++ b/source/blender/bmesh/intern/bmesh_walkers_impl.c
@@ -698,8 +698,6 @@ static void *bmw_IslandboundWalker_step(BMWalker *walker)
{
BMwIslandboundWalker *iwalk, owalk;
BMVert *v;
- BMEdge *e;
- BMFace *f;
BMLoop *l;
/* int found = 0; */
@@ -708,31 +706,24 @@ static void *bmw_IslandboundWalker_step(BMWalker *walker)
iwalk = &owalk;
l = iwalk->curloop;
- e = l->e;
- v = BM_edge_other_vert(e, iwalk->lastv);
+ v = BM_edge_other_vert(l->e, iwalk->lastv);
/* pop off current state */
BMW_state_remove(walker);
- f = l->f;
-
while (1) {
l = BM_loop_other_edge_loop(l, v);
if (BM_loop_is_manifold(l)) {
l = l->radial_next;
- f = l->f;
- e = l->e;
- if (!bmw_mask_check_face(walker, f)) {
+ if (!bmw_mask_check_face(walker, l->f)) {
l = l->radial_next;
break;
}
}
else {
/* treat non-manifold edges as boundaries */
- f = l->f;
- e = l->e;
break;
}
}
diff --git a/source/blender/bmesh/operators/bmo_face_island.c b/source/blender/bmesh/operators/bmo_face_island.c
new file mode 100644
index 00000000000..e52052a28d3
--- /dev/null
+++ b/source/blender/bmesh/operators/bmo_face_island.c
@@ -0,0 +1,238 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) Blender Foundation
+ * All rights reserved.
+ *
+ * The Original Code is: all of this file.
+ *
+ * Contributor(s): Lukas Toenne
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file blender/bmesh/operators/bmo_face_island.c
+ * \ingroup bmesh
+ *
+ * Face island search.
+ */
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_array.h"
+#include "BLI_math.h"
+
+#include "bmesh.h"
+
+#include "intern/bmesh_operators_private.h"
+
+
+#define FACE_MARK 1
+
+static BMLoop* bmo_face_island_find_start_loop(BMesh *bm, BMOperator *op)
+{
+ BMFace *f;
+ BMOIter oiter;
+ BMO_ITER (f, &oiter, op->slots_in, "faces", BM_FACE)
+ {
+ BMLoop *l;
+ BMIter l_iter;
+ BM_ITER_ELEM(l, &l_iter, f, BM_LOOPS_OF_FACE)
+ {
+ BMLoop *lr = l;
+ do
+ {
+ if (!BM_loop_is_manifold(lr)) {
+ /* treat non-manifold edges as boundaries */
+ return lr;
+ }
+ if (!BMO_face_flag_test(bm, lr->f, FACE_MARK))
+ {
+ return lr;
+ }
+ lr = lr->radial_next;
+ }
+ while (lr != l);
+ }
+ }
+ return NULL;
+}
+
+void bmo_face_island_boundary_exec(BMesh *bm, BMOperator *op)
+{
+ BMO_slot_buffer_flag_enable(bm, op->slots_in, "faces", BM_FACE, FACE_MARK);
+
+ BMLoop *l_start = bmo_face_island_find_start_loop(bm, op);
+ if (!l_start)
+ {
+ return;
+ }
+
+ BMLoop **boundary = NULL;
+ BLI_array_declare(boundary);
+
+ BMWalker walker;
+ BMW_init(&walker, bm, BMW_ISLANDBOUND,
+ BMW_MASK_NOP, BMW_MASK_NOP, FACE_MARK,
+ BMW_FLAG_NOP, /* no need to check BMW_FLAG_TEST_HIDDEN, faces are already marked by the bmo */
+ BMW_NIL_LAY);
+
+ for (BMLoop *l_iter = BMW_begin(&walker, l_start); l_iter; l_iter = BMW_step(&walker))
+ {
+ BLI_array_append(boundary, l_iter);
+ }
+ BMW_end(&walker);
+
+ {
+ BMOpSlot *slot = BMO_slot_get(op->slots_out, "boundary");
+ BMO_slot_buffer_from_array(op, slot, (BMHeader **)boundary, BLI_array_len(boundary));
+ BLI_array_free(boundary);
+ }
+
+#if 0
+ BMOIter oiter;
+ BMFace *f;
+ BMFace ***regions = NULL;
+ BMFace **faces = NULL;
+ BLI_array_declare(regions);
+ BLI_array_declare(faces);
+ BMFace *act_face = bm->act_face;
+ BMWalker regwalker;
+ int i;
+
+ const bool use_verts = BMO_slot_bool_get(op->slots_in, "use_verts");
+
+ if (use_verts) {
+ /* tag verts that start out with only 2 edges,
+ * don't remove these later */
+ BMIter viter;
+ BMVert *v;
+
+ BM_ITER_MESH (v, &viter, bm, BM_VERTS_OF_MESH) {
+ BMO_vert_flag_set(bm, v, VERT_MARK, !BM_vert_is_edge_pair(v));
+ }
+ }
+
+ BMO_slot_buffer_flag_enable(bm, op->slots_in, "faces", BM_FACE, FACE_MARK | FACE_TAG);
+
+ /* collect region */
+ BMO_ITER (f, &oiter, op->slots_in, "faces", BM_FACE) {
+ BMFace *f_iter;
+ if (!BMO_face_flag_test(bm, f, FACE_TAG)) {
+ continue;
+ }
+
+ BLI_array_empty(faces);
+ faces = NULL; /* forces different allocatio */
+
+ BMW_init(&regwalker, bm, BMW_ISLAND_MANIFOLD,
+ BMW_MASK_NOP, BMW_MASK_NOP, FACE_MARK,
+ BMW_FLAG_NOP, /* no need to check BMW_FLAG_TEST_HIDDEN, faces are already marked by the bmo */
+ BMW_NIL_LAY);
+
+ for (f_iter = BMW_begin(&regwalker, f); f_iter; f_iter = BMW_step(&regwalker)) {
+ BLI_array_append(faces, f_iter);
+ }
+ BMW_end(&regwalker);
+
+ for (i = 0; i < BLI_array_count(faces); i++) {
+ f_iter = faces[i];
+ BMO_face_flag_disable(bm, f_iter, FACE_TAG);
+ BMO_face_flag_enable(bm, f_iter, FACE_ORIG);
+ }
+
+ if (BMO_error_occurred(bm)) {
+ BMO_error_clear(bm);
+ BMO_error_raise(bm, op, BMERR_DISSOLVEFACES_FAILED, NULL);
+ goto cleanup;
+ }
+
+ BLI_array_append(faces, NULL);
+ BLI_array_append(regions, faces);
+ }
+
+ /* track how many faces we should end up with */
+ int totface_target = bm->totface;
+
+ for (i = 0; i < BLI_array_count(regions); i++) {
+ BMFace *f_new;
+ int tot = 0;
+
+ faces = regions[i];
+ if (!faces[0]) {
+ BMO_error_raise(bm, op, BMERR_DISSOLVEFACES_FAILED,
+ "Could not find boundary of dissolve region");
+ goto cleanup;
+ }
+
+ while (faces[tot])
+ tot++;
+
+ f_new = BM_faces_join(bm, faces, tot, true);
+
+ if (f_new) {
+ /* maintain active face */
+ if (act_face && bm->act_face == NULL) {
+ bm->act_face = f_new;
+ }
+ totface_target -= tot - 1;
+ }
+ else {
+ BMO_error_raise(bm, op, BMERR_DISSOLVEFACES_FAILED,
+ "Could not create merged face");
+ goto cleanup;
+ }
+
+ /* if making the new face failed (e.g. overlapping test)
+ * unmark the original faces for deletion */
+ BMO_face_flag_disable(bm, f_new, FACE_ORIG);
+ BMO_face_flag_enable(bm, f_new, FACE_NEW);
+ }
+
+ /* Typically no faces need to be deleted */
+ if (totface_target != bm->totface) {
+ BMO_op_callf(bm, op->flag, "delete geom=%ff context=%i", FACE_ORIG, DEL_FACES);
+ }
+
+ if (use_verts) {
+ BMIter viter;
+ BMVert *v, *v_next;
+
+ BM_ITER_MESH_MUTABLE (v, v_next, &viter, bm, BM_VERTS_OF_MESH) {
+ if (BMO_vert_flag_test(bm, v, VERT_MARK)) {
+ if (BM_vert_is_edge_pair(v)) {
+ BM_vert_collapse_edge(bm, v->e, v, true, true);
+ }
+ }
+ }
+ }
+
+ if (BMO_error_occurred(bm)) {
+ goto cleanup;
+ }
+
+ BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "region.out", BM_FACE, FACE_NEW);
+
+cleanup:
+ /* free/cleanup */
+ for (i = 0; i < BLI_array_count(regions); i++) {
+ if (regions[i]) MEM_freeN(regions[i]);
+ }
+
+ BLI_array_free(regions);
+#endif
+}
diff --git a/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc b/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc
index a5a28ce0a8c..994674f95fe 100644
--- a/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc
+++ b/source/blender/depsgraph/intern/builder/deg_builder_nodes.cc
@@ -51,6 +51,7 @@ extern "C" {
#include "DNA_curve_types.h"
#include "DNA_effect_types.h"
#include "DNA_gpencil_types.h"
+#include "DNA_groom_types.h"
#include "DNA_group_types.h"
#include "DNA_key_types.h"
#include "DNA_lamp_types.h"
@@ -76,6 +77,7 @@ extern "C" {
#include "BKE_effect.h"
#include "BKE_fcurve.h"
#include "BKE_idcode.h"
+#include "BKE_groom.h"
#include "BKE_group.h"
#include "BKE_key.h"
#include "BKE_lattice.h"
@@ -515,6 +517,7 @@ void DepsgraphNodeBuilder::build_object_data(Object *object)
case OB_SURF:
case OB_MBALL:
case OB_LATTICE:
+ case OB_GROOM:
build_obdata_geom(object);
/* TODO(sergey): Only for until we support granular
* update of curves.
@@ -1099,6 +1102,19 @@ void DepsgraphNodeBuilder::build_obdata_geom(Object *object)
op_node->set_as_entry();
break;
}
+
+ case OB_GROOM:
+ {
+ /* Groom evaluation operations. */
+ op_node = add_operation_node(obdata, DEG_NODE_TYPE_GEOMETRY,
+ function_bind(BKE_groom_eval_geometry,
+ _1,
+ (Groom *)obdata_cow),
+ DEG_OPCODE_PLACEHOLDER,
+ "Geometry Eval");
+ op_node->set_as_entry();
+ break;
+ }
}
op_node = add_operation_node(obdata, DEG_NODE_TYPE_GEOMETRY, NULL,
diff --git a/source/blender/depsgraph/intern/builder/deg_builder_nodes.h b/source/blender/depsgraph/intern/builder/deg_builder_nodes.h
index df94671b4c6..c933e139758 100644
--- a/source/blender/depsgraph/intern/builder/deg_builder_nodes.h
+++ b/source/blender/depsgraph/intern/builder/deg_builder_nodes.h
@@ -43,6 +43,7 @@ struct GHash;
struct ID;
struct Image;
struct FCurve;
+struct Groom;
struct Group;
struct Key;
struct LayerCollection;
diff --git a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc
index ff1728eb89f..7da2c4f54a1 100644
--- a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc
+++ b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc
@@ -51,6 +51,7 @@ extern "C" {
#include "DNA_curve_types.h"
#include "DNA_effect_types.h"
#include "DNA_gpencil_types.h"
+#include "DNA_groom_types.h"
#include "DNA_group_types.h"
#include "DNA_key_types.h"
#include "DNA_lamp_types.h"
@@ -77,6 +78,7 @@ extern "C" {
#include "BKE_effect.h"
#include "BKE_collision.h"
#include "BKE_fcurve.h"
+#include "BKE_groom.h"
#include "BKE_group.h"
#include "BKE_key.h"
#include "BKE_library.h"
@@ -528,6 +530,7 @@ void DepsgraphRelationBuilder::build_object_data(Object *object)
case OB_SURF:
case OB_MBALL:
case OB_LATTICE:
+ case OB_GROOM:
{
build_obdata_geom(object);
break;
@@ -1748,6 +1751,19 @@ void DepsgraphRelationBuilder::build_obdata_geom(Object *object)
{
break;
}
+
+ case OB_GROOM: /* Groom */
+ {
+ Groom *groom = (Groom *)obdata;
+ ComponentKey geometry_key(&groom->id, DEG_NODE_TYPE_GEOMETRY);
+
+ if (groom->scalp_object)
+ {
+ ID *scalp_id = &groom->scalp_object->id;
+ add_relation(ComponentKey(scalp_id, DEG_NODE_TYPE_GEOMETRY), geometry_key, "Scalp Object -> Groom");
+ }
+ break;
+ }
}
/* ShapeKeys */
diff --git a/source/blender/depsgraph/intern/builder/deg_builder_relations.h b/source/blender/depsgraph/intern/builder/deg_builder_relations.h
index df6fb100d22..4ddad462a5a 100644
--- a/source/blender/depsgraph/intern/builder/deg_builder_relations.h
+++ b/source/blender/depsgraph/intern/builder/deg_builder_relations.h
@@ -75,6 +75,7 @@ struct ViewLayer;
struct Tex;
struct World;
struct EffectorWeights;
+struct Groom;
struct PropertyRNA;
diff --git a/source/blender/depsgraph/intern/depsgraph_tag.cc b/source/blender/depsgraph/intern/depsgraph_tag.cc
index 6a6ebd1be44..2091d597ffa 100644
--- a/source/blender/depsgraph/intern/depsgraph_tag.cc
+++ b/source/blender/depsgraph/intern/depsgraph_tag.cc
@@ -97,6 +97,7 @@ void depsgraph_geometry_tag_to_component(const ID *id,
case OB_FONT:
case OB_LATTICE:
case OB_MBALL:
+ case OB_GROOM:
*component_type = DEG_NODE_TYPE_GEOMETRY;
break;
case OB_ARMATURE:
diff --git a/source/blender/draw/CMakeLists.txt b/source/blender/draw/CMakeLists.txt
index eb66e9c20ea..1529ee7022e 100644
--- a/source/blender/draw/CMakeLists.txt
+++ b/source/blender/draw/CMakeLists.txt
@@ -57,11 +57,14 @@ set(SRC
intern/draw_cache.c
intern/draw_cache_impl_curve.c
intern/draw_cache_impl_displist.c
+ intern/draw_cache_impl_groom.c
+ intern/draw_cache_impl_hair.c
intern/draw_cache_impl_lattice.c
intern/draw_cache_impl_mesh.c
intern/draw_cache_impl_metaball.c
intern/draw_cache_impl_particles.c
intern/draw_common.c
+ intern/draw_hair.c
intern/draw_instance_data.c
intern/draw_manager.c
intern/draw_manager_data.c
@@ -73,6 +76,7 @@ set(SRC
intern/draw_view.c
modes/edit_armature_mode.c
modes/edit_curve_mode.c
+ modes/edit_groom_mode.c
modes/edit_lattice_mode.c
modes/edit_mesh_mode.c
modes/edit_mesh_mode_text.c
@@ -146,6 +150,7 @@ data_to_c_simple(engines/eevee/shaders/default_world_frag.glsl SRC)
data_to_c_simple(engines/eevee/shaders/background_vert.glsl SRC)
data_to_c_simple(engines/eevee/shaders/concentric_samples_lib.glsl SRC)
data_to_c_simple(engines/eevee/shaders/common_uniforms_lib.glsl SRC)
+data_to_c_simple(engines/eevee/shaders/hair_lib.glsl SRC)
data_to_c_simple(engines/eevee/shaders/lamps_lib.glsl SRC)
data_to_c_simple(engines/eevee/shaders/lightprobe_lib.glsl SRC)
data_to_c_simple(engines/eevee/shaders/lightprobe_filter_glossy_frag.glsl SRC)
@@ -221,6 +226,8 @@ data_to_c_simple(modes/shaders/edit_mesh_overlay_facefill_frag.glsl SRC)
data_to_c_simple(modes/shaders/edit_curve_overlay_frag.glsl SRC)
data_to_c_simple(modes/shaders/edit_curve_overlay_handle_geom.glsl SRC)
data_to_c_simple(modes/shaders/edit_curve_overlay_loosevert_vert.glsl SRC)
+data_to_c_simple(modes/shaders/edit_groom_overlay_frag.glsl SRC)
+data_to_c_simple(modes/shaders/edit_groom_overlay_loosevert_vert.glsl SRC)
data_to_c_simple(modes/shaders/edit_lattice_overlay_frag.glsl SRC)
data_to_c_simple(modes/shaders/edit_lattice_overlay_loosevert_vert.glsl SRC)
data_to_c_simple(modes/shaders/edit_normals_vert.glsl SRC)
diff --git a/source/blender/draw/engines/eevee/eevee_materials.c b/source/blender/draw/engines/eevee/eevee_materials.c
index fc4439a253c..d146fda5373 100644
--- a/source/blender/draw/engines/eevee/eevee_materials.c
+++ b/source/blender/draw/engines/eevee/eevee_materials.c
@@ -31,6 +31,8 @@
#include "BLI_rand.h"
#include "BLI_string_utils.h"
+#include "BKE_DerivedMesh.h"
+#include "BKE_groom.h"
#include "BKE_particle.h"
#include "BKE_paint.h"
#include "BKE_pbvh.h"
@@ -38,8 +40,11 @@
#include "DNA_world_types.h"
#include "DNA_modifier_types.h"
#include "DNA_view3d_types.h"
+#include "DNA_groom_types.h"
+#include "DNA_hair_types.h"
#include "GPU_material.h"
+#include "GPU_texture.h"
#include "eevee_engine.h"
#include "eevee_lut.h"
@@ -53,6 +58,8 @@ static struct {
struct GPUShader *default_prepass_sh;
struct GPUShader *default_prepass_clip_sh;
+ struct GPUShader *default_prepass_hair_fiber_sh;
+ struct GPUShader *default_prepass_hair_fiber_clip_sh;
struct GPUShader *default_lit[VAR_MAT_MAX];
struct GPUShader *default_background;
struct GPUShader *update_noise_sh;
@@ -99,6 +106,7 @@ extern char datatoc_volumetric_vert_glsl[];
extern char datatoc_volumetric_geom_glsl[];
extern char datatoc_volumetric_frag_glsl[];
extern char datatoc_volumetric_lib_glsl[];
+extern char datatoc_hair_lib_glsl[];
extern Material defmaterial;
extern GlobalsUboStorage ts;
@@ -280,6 +288,9 @@ static char *eevee_get_defines(int options)
if ((options & VAR_MAT_HAIR) != 0) {
BLI_dynstr_appendf(ds, "#define HAIR_SHADER\n");
}
+ if ((options & VAR_MAT_HAIR_FIBERS) != 0) {
+ BLI_dynstr_append(ds, DRW_hair_shader_defines());
+ }
if ((options & VAR_MAT_PROBE) != 0) {
BLI_dynstr_appendf(ds, "#define PROBE_CAPTURE\n");
}
@@ -411,15 +422,20 @@ static void add_standard_uniforms(
static void create_default_shader(int options)
{
+ char *vert_str = BLI_string_joinN(
+ datatoc_hair_lib_glsl,
+ datatoc_lit_surface_vert_glsl);
+
char *frag_str = BLI_string_joinN(
e_data.frag_shader_lib,
datatoc_default_frag_glsl);
char *defines = eevee_get_defines(options);
- e_data.default_lit[options] = DRW_shader_create(datatoc_lit_surface_vert_glsl, NULL, frag_str, defines);
+ e_data.default_lit[options] = DRW_shader_create(vert_str, NULL, frag_str, defines);
MEM_freeN(defines);
+ MEM_freeN(vert_str);
MEM_freeN(frag_str);
}
@@ -529,8 +545,6 @@ static void EEVEE_update_viewvecs(float invproj[4][4], float winmat[4][4], float
void EEVEE_materials_init(EEVEE_ViewLayerData *sldata, EEVEE_StorageList *stl, EEVEE_FramebufferList *fbl)
{
if (!e_data.frag_shader_lib) {
- char *frag_str = NULL;
-
/* Shaders */
e_data.shadow_shader_lib = BLI_string_joinN(
datatoc_common_uniforms_lib_glsl,
@@ -555,7 +569,7 @@ void EEVEE_materials_init(EEVEE_ViewLayerData *sldata, EEVEE_StorageList *stl, E
datatoc_lit_surface_frag_glsl,
datatoc_lit_surface_frag_glsl,
datatoc_volumetric_lib_glsl);
-
+
e_data.frag_shader_lib = BLI_string_joinN(
datatoc_common_view_lib_glsl,
e_data.shadow_shader_lib);
@@ -574,7 +588,11 @@ void EEVEE_materials_init(EEVEE_ViewLayerData *sldata, EEVEE_StorageList *stl, E
datatoc_volumetric_lib_glsl,
datatoc_volumetric_frag_glsl);
- frag_str = BLI_string_joinN(
+ char *hair_fiber_vert_str = BLI_string_joinN(
+ datatoc_hair_lib_glsl,
+ datatoc_prepass_vert_glsl);
+
+ char *frag_str = BLI_string_joinN(
e_data.frag_shader_lib,
datatoc_default_frag_glsl);
@@ -590,7 +608,19 @@ void EEVEE_materials_init(EEVEE_ViewLayerData *sldata, EEVEE_StorageList *stl, E
datatoc_prepass_vert_glsl, NULL, datatoc_prepass_frag_glsl,
"#define CLIP_PLANES\n");
+ e_data.default_prepass_hair_fiber_sh = DRW_shader_create(
+ hair_fiber_vert_str, NULL, datatoc_prepass_frag_glsl, DRW_hair_shader_defines());
+
+ {
+ char defines[256];
+ BLI_snprintf(defines, sizeof(defines), "#define CLIP_PLANES\n%s",
+ DRW_hair_shader_defines());
+ e_data.default_prepass_hair_fiber_clip_sh = DRW_shader_create(
+ hair_fiber_vert_str, NULL, datatoc_prepass_frag_glsl, defines);
+ }
+
MEM_freeN(frag_str);
+ MEM_freeN(hair_fiber_vert_str);
e_data.update_noise_sh = DRW_shader_create_fullscreen(
datatoc_update_noise_frag_glsl, NULL);
@@ -779,25 +809,36 @@ struct GPUMaterial *EEVEE_material_mesh_depth_get(
}
struct GPUMaterial *EEVEE_material_hair_get(
- struct Scene *scene, Material *ma, int shadow_method)
+ struct Scene *scene, Material *ma, int shadow_method, bool use_fibers)
{
const void *engine = &DRW_engine_viewport_eevee_type;
- int options = VAR_MAT_MESH | VAR_MAT_HAIR;
-
+ int options = VAR_MAT_HAIR | VAR_MAT_MESH;
options |= eevee_material_shadow_option(shadow_method);
-
+ if (use_fibers) {
+ options |= VAR_MAT_HAIR_FIBERS;
+ }
GPUMaterial *mat = DRW_shader_find_from_material(ma, engine, options);
if (mat) {
return mat;
}
+ char *vert_str = NULL;
+ {
+ DynStr *ds_vert = BLI_dynstr_new();
+ BLI_dynstr_append(ds_vert, datatoc_hair_lib_glsl);
+ BLI_dynstr_append(ds_vert, datatoc_lit_surface_vert_glsl);
+ vert_str = BLI_dynstr_get_cstring(ds_vert);
+ BLI_dynstr_free(ds_vert);
+ }
+
char *defines = eevee_get_defines(options);
mat = DRW_shader_create_from_material(
scene, ma, engine, options,
- datatoc_lit_surface_vert_glsl, NULL, e_data.frag_shader_lib,
+ vert_str, NULL, e_data.frag_shader_lib,
defines);
+ MEM_freeN(vert_str);
MEM_freeN(defines);
return mat;
@@ -808,7 +849,7 @@ struct GPUMaterial *EEVEE_material_hair_get(
**/
static struct DRWShadingGroup *EEVEE_default_shading_group_create(
EEVEE_ViewLayerData *sldata, EEVEE_Data *vedata, DRWPass *pass,
- bool is_hair, bool is_flat_normal, bool use_blend, bool use_ssr, int shadow_method)
+ bool is_hair, bool is_hair_fibers, bool is_flat_normal, bool use_blend, bool use_ssr, int shadow_method)
{
EEVEE_EffectsInfo *effects = vedata->stl->effects;
static int ssr_id;
@@ -816,6 +857,7 @@ static struct DRWShadingGroup *EEVEE_default_shading_group_create(
int options = VAR_MAT_MESH;
if (is_hair) options |= VAR_MAT_HAIR;
+ if (is_hair_fibers) options |= VAR_MAT_HAIR_FIBERS;
if (is_flat_normal) options |= VAR_MAT_FLAT;
if (use_blend) options |= VAR_MAT_BLEND;
if (((effects->enabled_effects & EFFECT_VOLUMETRIC) != 0) && use_blend) options |= VAR_MAT_VOLUME;
@@ -837,13 +879,14 @@ static struct DRWShadingGroup *EEVEE_default_shading_group_create(
**/
static struct DRWShadingGroup *EEVEE_default_shading_group_get(
EEVEE_ViewLayerData *sldata, EEVEE_Data *vedata,
- bool is_hair, bool is_flat_normal, bool use_ssr, int shadow_method)
+ bool is_hair, bool is_hair_fibers, bool is_flat_normal, bool use_ssr, int shadow_method)
{
static int ssr_id;
ssr_id = (use_ssr) ? 1 : -1;
int options = VAR_MAT_MESH;
if (is_hair) options |= VAR_MAT_HAIR;
+ if (is_hair_fibers) options |= VAR_MAT_HAIR_FIBERS;
if (is_flat_normal) options |= VAR_MAT_FLAT;
options |= eevee_material_shadow_option(shadow_method);
@@ -853,7 +896,8 @@ static struct DRWShadingGroup *EEVEE_default_shading_group_get(
}
if (vedata->psl->default_pass[options] == NULL) {
- DRWState state = DRW_STATE_WRITE_COLOR | DRW_STATE_DEPTH_EQUAL | DRW_STATE_CLIP_PLANES | DRW_STATE_WIRE;
+ //DRWState state = DRW_STATE_WRITE_COLOR | DRW_STATE_DEPTH_EQUAL | DRW_STATE_CLIP_PLANES | DRW_STATE_WIRE;
+ DRWState state = DRW_STATE_WRITE_COLOR | DRW_STATE_DEPTH_EQUAL;
vedata->psl->default_pass[options] = DRW_pass_create("Default Lit Pass", state);
DRWShadingGroup *shgrp = DRW_shgroup_create(e_data.default_lit[options], vedata->psl->default_pass[options]);
@@ -928,20 +972,26 @@ void EEVEE_materials_cache_init(EEVEE_ViewLayerData *sldata, EEVEE_Data *vedata)
DRWState state = DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_LESS | DRW_STATE_WIRE;
psl->depth_pass = DRW_pass_create("Depth Pass", state);
stl->g_data->depth_shgrp = DRW_shgroup_create(e_data.default_prepass_sh, psl->depth_pass);
+ stl->g_data->hair_fibers_depth_shgrp = DRW_shgroup_create(e_data.default_prepass_hair_fiber_sh, psl->depth_pass);
state = DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_LESS | DRW_STATE_CULL_BACK;
psl->depth_pass_cull = DRW_pass_create("Depth Pass Cull", state);
stl->g_data->depth_shgrp_cull = DRW_shgroup_create(e_data.default_prepass_sh, psl->depth_pass_cull);
+ stl->g_data->hair_fibers_depth_shgrp_cull = DRW_shgroup_create(e_data.default_prepass_hair_fiber_sh, psl->depth_pass_cull);
state = DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_LESS | DRW_STATE_CLIP_PLANES | DRW_STATE_WIRE;
psl->depth_pass_clip = DRW_pass_create("Depth Pass Clip", state);
stl->g_data->depth_shgrp_clip = DRW_shgroup_create(e_data.default_prepass_clip_sh, psl->depth_pass_clip);
+ stl->g_data->hair_fibers_depth_shgrp_clip = DRW_shgroup_create(e_data.default_prepass_hair_fiber_clip_sh, psl->depth_pass_clip);
DRW_shgroup_uniform_block(stl->g_data->depth_shgrp_clip, "clip_block", sldata->clip_ubo);
+ DRW_shgroup_uniform_block(stl->g_data->hair_fibers_depth_shgrp_clip, "clip_block", sldata->clip_ubo);
state = DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_LESS | DRW_STATE_CLIP_PLANES | DRW_STATE_CULL_BACK;
psl->depth_pass_clip_cull = DRW_pass_create("Depth Pass Cull Clip", state);
stl->g_data->depth_shgrp_clip_cull = DRW_shgroup_create(e_data.default_prepass_clip_sh, psl->depth_pass_clip_cull);
+ stl->g_data->hair_fibers_depth_shgrp_clip_cull = DRW_shgroup_create(e_data.default_prepass_hair_fiber_clip_sh, psl->depth_pass_clip_cull);
DRW_shgroup_uniform_block(stl->g_data->depth_shgrp_clip_cull, "clip_block", sldata->clip_ubo);
+ DRW_shgroup_uniform_block(stl->g_data->hair_fibers_depth_shgrp_clip_cull, "clip_block", sldata->clip_ubo);
}
{
@@ -1159,7 +1209,7 @@ static void material_opaque(
/* Fallback to default shader */
if (*shgrp == NULL) {
bool use_ssr = ((effects->enabled_effects & EFFECT_SSR) != 0);
- *shgrp = EEVEE_default_shading_group_get(sldata, vedata, false, use_flat_nor, use_ssr, linfo->shadow_method);
+ *shgrp = EEVEE_default_shading_group_get(sldata, vedata, false, false, use_flat_nor, use_ssr, linfo->shadow_method);
DRW_shgroup_uniform_vec3(*shgrp, "basecol", color_p, 1);
DRW_shgroup_uniform_float(*shgrp, "metallic", metal_p, 1);
DRW_shgroup_uniform_float(*shgrp, "specular", spec_p, 1);
@@ -1241,7 +1291,7 @@ static void material_transparent(
if (*shgrp == NULL) {
*shgrp = EEVEE_default_shading_group_create(
sldata, vedata, psl->transparent_pass,
- false, use_flat_nor, true, false, linfo->shadow_method);
+ false, false, use_flat_nor, true, false, linfo->shadow_method);
DRW_shgroup_uniform_vec3(*shgrp, "basecol", color_p, 1);
DRW_shgroup_uniform_float(*shgrp, "metallic", metal_p, 1);
DRW_shgroup_uniform_float(*shgrp, "specular", spec_p, 1);
@@ -1289,6 +1339,178 @@ static void material_transparent(
}
}
+static void material_particle_hair(
+ EEVEE_Data *vedata,
+ EEVEE_ViewLayerData *sldata,
+ Object *ob,
+ ParticleSystem *psys,
+ ModifierData *md)
+{
+ EEVEE_PassList *psl = ((EEVEE_Data *)vedata)->psl;
+ EEVEE_StorageList *stl = ((EEVEE_Data *)vedata)->stl;
+ const DRWContextState *draw_ctx = DRW_context_state_get();
+ Scene *scene = draw_ctx->scene;
+ GHash *material_hash = stl->g_data->hair_material_hash;
+
+ if (!psys_check_enabled(ob, psys, false)) {
+ return;
+ }
+
+ ParticleSettings *part = psys->part;
+ float mat[4][4];
+ unit_m4(mat);
+
+ bool use_hair = false;
+ struct Gwn_Batch *hair_geom = NULL;
+ {
+ int draw_as = (part->draw_as == PART_DRAW_REND) ? part->ren_as : part->draw_as;
+ if (draw_as == PART_DRAW_PATH && (psys->pathcache || psys->childcache)) {
+ use_hair = true;
+ hair_geom = DRW_cache_particles_get_hair(psys, md);
+ }
+ }
+
+ if (use_hair) {
+ Material *ma = give_current_material(ob, part->omat);
+ if (ma == NULL) {
+ ma = &defmaterial;
+ }
+
+ DRW_shgroup_call_add(stl->g_data->depth_shgrp, hair_geom, mat);
+ DRW_shgroup_call_add(stl->g_data->depth_shgrp_clip, hair_geom, mat);
+
+ DRWShadingGroup *shgrp = BLI_ghash_lookup(material_hash, (const void *)ma);
+ if (!shgrp) {
+ float *color_p = &ma->r;
+ float *metal_p = &ma->ray_mirror;
+ float *spec_p = &ma->spec;
+ float *rough_p = &ma->gloss_mir;
+
+ if (ma->use_nodes && ma->nodetree) {
+ struct GPUMaterial *gpumat = EEVEE_material_hair_get(scene, ma, false, sldata->lamps->shadow_method);
+
+ shgrp = DRW_shgroup_material_create(gpumat, psl->material_pass);
+ if (shgrp) {
+ add_standard_uniforms(shgrp, sldata, vedata, NULL, NULL, false, false);
+
+ BLI_ghash_insert(material_hash, ma, shgrp);
+ }
+ else {
+ /* Shader failed : pink color */
+ static float col[3] = {1.0f, 0.0f, 1.0f};
+ static float half = 0.5f;
+
+ color_p = col;
+ metal_p = spec_p = rough_p = &half;
+ }
+ }
+
+ /* Fallback to default shader */
+ if (shgrp == NULL) {
+ bool use_ssr = ((stl->effects->enabled_effects & EFFECT_SSR) != 0);
+ shgrp = EEVEE_default_shading_group_get(sldata, vedata, true, false, false, use_ssr,
+ sldata->lamps->shadow_method);
+ DRW_shgroup_uniform_vec3(shgrp, "basecol", color_p, 1);
+ DRW_shgroup_uniform_float(shgrp, "metallic", metal_p, 1);
+ DRW_shgroup_uniform_float(shgrp, "specular", spec_p, 1);
+ DRW_shgroup_uniform_float(shgrp, "roughness", rough_p, 1);
+
+ BLI_ghash_insert(material_hash, ma, shgrp);
+ }
+ }
+
+ if (shgrp) {
+ DRW_shgroup_call_add(shgrp, hair_geom, mat);
+ }
+ }
+}
+
+static void material_hair(
+ EEVEE_Data *vedata,
+ EEVEE_ViewLayerData *sldata,
+ Object *ob,
+ HairSystem *hsys,
+ DerivedMesh *scalp)
+{
+ EEVEE_PassList *psl = ((EEVEE_Data *)vedata)->psl;
+ EEVEE_StorageList *stl = ((EEVEE_Data *)vedata)->stl;
+ const DRWContextState *draw_ctx = DRW_context_state_get();
+ Scene *scene = draw_ctx->scene;
+ GHash *material_hash = stl->g_data->hair_material_hash;
+ /* TODO */
+ const int subdiv = 0;
+ float mat[4][4];
+ copy_m4_m4(mat, ob->obmat);
+
+ const DRWHairFiberTextureBuffer *fiber_buffer = NULL;
+ struct Gwn_Batch *hair_geom = DRW_cache_hair_get_fibers(hsys, scalp, subdiv, &fiber_buffer);
+
+ if (!hsys->draw_texture_cache) {
+ hsys->draw_texture_cache = DRW_texture_create_2D(fiber_buffer->width, fiber_buffer->height,
+ DRW_TEX_RG_32, 0, fiber_buffer->data);
+ }
+ GPUTexture **fiber_texture = (GPUTexture **)(&hsys->draw_texture_cache);
+
+ Material *ma = give_current_material(ob, hsys->material_index);
+ if (ma == NULL) {
+ ma = &defmaterial;
+ }
+
+ DRW_shgroup_call_add(stl->g_data->hair_fibers_depth_shgrp, hair_geom, mat);
+ DRW_hair_shader_uniforms(stl->g_data->hair_fibers_depth_shgrp, scene,
+ fiber_texture, fiber_buffer);
+
+ DRW_shgroup_call_add(stl->g_data->hair_fibers_depth_shgrp_clip, hair_geom, mat);
+ DRW_hair_shader_uniforms(stl->g_data->hair_fibers_depth_shgrp_clip, scene,
+ fiber_texture, fiber_buffer);
+
+ DRWShadingGroup *shgrp = BLI_ghash_lookup(material_hash, (const void *)ma);
+ if (!shgrp) {
+ float *color_p = &ma->r;
+ float *metal_p = &ma->ray_mirror;
+ float *spec_p = &ma->spec;
+ float *rough_p = &ma->gloss_mir;
+
+ if (ma->use_nodes && ma->nodetree) {
+ struct GPUMaterial *gpumat = EEVEE_material_hair_get(scene, ma, sldata->lamps->shadow_method, true);
+
+ shgrp = DRW_shgroup_material_create(gpumat, psl->material_pass);
+ if (shgrp) {
+ add_standard_uniforms(shgrp, sldata, vedata, NULL, NULL, false, false);
+ BLI_ghash_insert(material_hash, ma, shgrp);
+ }
+ else {
+ /* Shader failed : pink color */
+ static float col[3] = {1.0f, 0.0f, 1.0f};
+ static float half = 0.5f;
+
+ color_p = col;
+ metal_p = spec_p = rough_p = &half;
+ }
+ }
+
+ /* Fallback to default shader */
+ if (shgrp == NULL) {
+ bool use_ssr = ((stl->effects->enabled_effects & EFFECT_SSR) != 0);
+ shgrp = EEVEE_default_shading_group_get(sldata, vedata, true, true, false, use_ssr,
+ sldata->lamps->shadow_method);
+ DRW_shgroup_uniform_vec3(shgrp, "basecol", color_p, 1);
+ DRW_shgroup_uniform_float(shgrp, "metallic", metal_p, 1);
+ DRW_shgroup_uniform_float(shgrp, "specular", spec_p, 1);
+ DRW_shgroup_uniform_float(shgrp, "roughness", rough_p, 1);
+
+ BLI_ghash_insert(material_hash, ma, shgrp);
+ }
+ }
+
+ if (shgrp) {
+ DRW_shgroup_call_add(shgrp, hair_geom, mat);
+
+ DRW_hair_shader_uniforms(shgrp, scene,
+ fiber_texture, fiber_buffer);
+ }
+}
+
void EEVEE_materials_cache_populate(EEVEE_Data *vedata, EEVEE_ViewLayerData *sldata, Object *ob)
{
EEVEE_PassList *psl = vedata->psl;
@@ -1437,84 +1659,22 @@ void EEVEE_materials_cache_populate(EEVEE_Data *vedata, EEVEE_ViewLayerData *sld
if (ob->type == OB_MESH) {
if (ob != draw_ctx->object_edit) {
- material_hash = stl->g_data->hair_material_hash;
-
for (ModifierData *md = ob->modifiers.first; md; md = md->next) {
if (md->type == eModifierType_ParticleSystem) {
ParticleSystem *psys = ((ParticleSystemModifierData *)md)->psys;
-
- if (psys_check_enabled(ob, psys, false)) {
- ParticleSettings *part = psys->part;
- int draw_as = (part->draw_as == PART_DRAW_REND) ? part->ren_as : part->draw_as;
-
- if (draw_as == PART_DRAW_PATH && (psys->pathcache || psys->childcache)) {
- struct Gwn_Batch *hair_geom = DRW_cache_particles_get_hair(psys, md);
- DRWShadingGroup *shgrp = NULL;
- Material *ma = give_current_material(ob, part->omat);
- static float mat[4][4];
-
- unit_m4(mat);
-
- if (ma == NULL) {
- ma = &defmaterial;
- }
-
- float *color_p = &ma->r;
- float *metal_p = &ma->ray_mirror;
- float *spec_p = &ma->spec;
- float *rough_p = &ma->gloss_mir;
-
- DRW_shgroup_call_add(stl->g_data->depth_shgrp, hair_geom, mat);
- DRW_shgroup_call_add(stl->g_data->depth_shgrp_clip, hair_geom, mat);
-
- shgrp = BLI_ghash_lookup(material_hash, (const void *)ma);
-
- if (shgrp) {
- DRW_shgroup_call_add(shgrp, hair_geom, mat);
- }
- else {
- if (ma->use_nodes && ma->nodetree) {
- struct GPUMaterial *gpumat = EEVEE_material_hair_get(scene, ma, sldata->lamps->shadow_method);
-
- shgrp = DRW_shgroup_material_create(gpumat, psl->material_pass);
- if (shgrp) {
- add_standard_uniforms(shgrp, sldata, vedata, NULL, NULL, false, false);
-
- BLI_ghash_insert(material_hash, ma, shgrp);
-
- DRW_shgroup_call_add(shgrp, hair_geom, mat);
- }
- else {
- /* Shader failed : pink color */
- static float col[3] = {1.0f, 0.0f, 1.0f};
- static float half = 0.5f;
-
- color_p = col;
- metal_p = spec_p = rough_p = &half;
- }
- }
-
- /* Fallback to default shader */
- if (shgrp == NULL) {
- bool use_ssr = ((stl->effects->enabled_effects & EFFECT_SSR) != 0);
- shgrp = EEVEE_default_shading_group_get(sldata, vedata, true, false, use_ssr,
- sldata->lamps->shadow_method);
- DRW_shgroup_uniform_vec3(shgrp, "basecol", color_p, 1);
- DRW_shgroup_uniform_float(shgrp, "metallic", metal_p, 1);
- DRW_shgroup_uniform_float(shgrp, "specular", spec_p, 1);
- DRW_shgroup_uniform_float(shgrp, "roughness", rough_p, 1);
-
- BLI_ghash_insert(material_hash, ma, shgrp);
-
- DRW_shgroup_call_add(shgrp, hair_geom, mat);
- }
- }
- }
- }
+ material_particle_hair(vedata, sldata, ob, psys, md);
+ }
+ else if (md->type == eModifierType_Fur) {
+ FurModifierData *fmd = (FurModifierData *)md;
+ material_hair(vedata, sldata, ob, fmd->hair_system, ob->derivedFinal);
}
}
}
}
+ else if (ob->type == OB_GROOM) {
+ Groom *groom = ob->data;
+ material_hair(vedata, sldata, ob, groom->hair_system, BKE_groom_get_scalp(groom));
+ }
}
void EEVEE_materials_cache_finish(EEVEE_Data *vedata)
@@ -1535,6 +1695,8 @@ void EEVEE_materials_free(void)
MEM_SAFE_FREE(e_data.volume_shader_lib);
DRW_SHADER_FREE_SAFE(e_data.default_prepass_sh);
DRW_SHADER_FREE_SAFE(e_data.default_prepass_clip_sh);
+ DRW_SHADER_FREE_SAFE(e_data.default_prepass_hair_fiber_sh);
+ DRW_SHADER_FREE_SAFE(e_data.default_prepass_hair_fiber_clip_sh);
DRW_SHADER_FREE_SAFE(e_data.default_background);
DRW_SHADER_FREE_SAFE(e_data.update_noise_sh);
DRW_TEXTURE_FREE_SAFE(e_data.util_tex);
diff --git a/source/blender/draw/engines/eevee/eevee_private.h b/source/blender/draw/engines/eevee/eevee_private.h
index bf77914832e..d203fadc073 100644
--- a/source/blender/draw/engines/eevee/eevee_private.h
+++ b/source/blender/draw/engines/eevee/eevee_private.h
@@ -101,28 +101,29 @@ enum {
/* Material shader variations */
enum {
- VAR_MAT_MESH = (1 << 0),
- VAR_MAT_PROBE = (1 << 1),
- VAR_MAT_HAIR = (1 << 2),
- VAR_MAT_FLAT = (1 << 3),
- VAR_MAT_BLEND = (1 << 4),
- VAR_MAT_VSM = (1 << 5),
- VAR_MAT_ESM = (1 << 6),
- VAR_MAT_VOLUME = (1 << 7),
+ VAR_MAT_MESH = (1 << 0),
+ VAR_MAT_PROBE = (1 << 1),
+ VAR_MAT_HAIR = (1 << 2),
+ VAR_MAT_FLAT = (1 << 3),
+ VAR_MAT_BLEND = (1 << 4),
+ VAR_MAT_VSM = (1 << 5),
+ VAR_MAT_ESM = (1 << 6),
+ VAR_MAT_VOLUME = (1 << 7),
+ VAR_MAT_HAIR_FIBERS = (1 << 8),
/* Max number of variation */
/* IMPORTANT : Leave it last and set
* it's value accordingly. */
- VAR_MAT_MAX = (1 << 8),
+ VAR_MAT_MAX = (1 << 9),
/* These are options that are not counted in VAR_MAT_MAX
* because they are not cumulative with the others above. */
- VAR_MAT_CLIP = (1 << 9),
- VAR_MAT_HASH = (1 << 10),
- VAR_MAT_MULT = (1 << 11),
- VAR_MAT_SHADOW = (1 << 12),
- VAR_MAT_REFRACT = (1 << 13),
- VAR_MAT_SSS = (1 << 14),
- VAR_MAT_TRANSLUC = (1 << 15),
- VAR_MAT_SSSALBED = (1 << 16),
+ VAR_MAT_CLIP = (1 << 10),
+ VAR_MAT_HASH = (1 << 11),
+ VAR_MAT_MULT = (1 << 12),
+ VAR_MAT_SHADOW = (1 << 13),
+ VAR_MAT_REFRACT = (1 << 14),
+ VAR_MAT_SSS = (1 << 15),
+ VAR_MAT_TRANSLUC = (1 << 16),
+ VAR_MAT_SSSALBED = (1 << 17),
};
/* Shadow Technique */
@@ -746,6 +747,10 @@ typedef struct EEVEE_PrivateData {
struct DRWShadingGroup *depth_shgrp_cull;
struct DRWShadingGroup *depth_shgrp_clip;
struct DRWShadingGroup *depth_shgrp_clip_cull;
+ struct DRWShadingGroup *hair_fibers_depth_shgrp;
+ struct DRWShadingGroup *hair_fibers_depth_shgrp_cull;
+ struct DRWShadingGroup *hair_fibers_depth_shgrp_clip;
+ struct DRWShadingGroup *hair_fibers_depth_shgrp_clip_cull;
struct DRWShadingGroup *refract_depth_shgrp;
struct DRWShadingGroup *refract_depth_shgrp_cull;
struct DRWShadingGroup *refract_depth_shgrp_clip;
@@ -792,7 +797,7 @@ struct GPUMaterial *EEVEE_material_mesh_get(
bool use_blend, bool use_multiply, bool use_refract, bool use_sss, bool use_translucency, int shadow_method);
struct GPUMaterial *EEVEE_material_mesh_volume_get(struct Scene *scene, Material *ma);
struct GPUMaterial *EEVEE_material_mesh_depth_get(struct Scene *scene, Material *ma, bool use_hashed_alpha, bool is_shadow);
-struct GPUMaterial *EEVEE_material_hair_get(struct Scene *scene, Material *ma, int shadow_method);
+struct GPUMaterial *EEVEE_material_hair_get(struct Scene *scene, Material *ma, int shadow_method, bool use_fibers);
void EEVEE_materials_free(void);
void EEVEE_draw_default_passes(EEVEE_PassList *psl);
void EEVEE_update_noise(EEVEE_PassList *psl, EEVEE_FramebufferList *fbl, const double offsets[3]);
diff --git a/source/blender/draw/engines/eevee/shaders/hair_lib.glsl b/source/blender/draw/engines/eevee/shaders/hair_lib.glsl
new file mode 100644
index 00000000000..489ee44dd0f
--- /dev/null
+++ b/source/blender/draw/engines/eevee/shaders/hair_lib.glsl
@@ -0,0 +1,330 @@
+#ifdef HAIR_SHADER_FIBERS
+
+#define M_PI 3.1415926535897932384626433832795
+
+mat4 translate(vec3 co)
+{
+ return mat4(1.0, 0.0, 0.0, 0.0,
+ 0.0, 1.0, 0.0, 0.0,
+ 0.0, 0.0, 1.0, 0.0,
+ co.x, co.y, co.z, 1.0);
+}
+
+mat4 rotateX(float angle)
+{
+ float ca = cos(angle);
+ float sa = sin(angle);
+ return mat4(1.0, 0.0, 0.0, 0.0,
+ 0.0, ca, sa, 0.0,
+ 0.0, -sa, ca, 0.0,
+ 0.0, 0.0, 0.0, 1.0);
+}
+
+mat4 rotateY(float angle)
+{
+ float ca = cos(angle);
+ float sa = sin(angle);
+ return mat4(ca, 0.0, sa, 0.0,
+ 0.0, 1.0, 0.0, 0.0,
+ -sa, 0.0, ca, 0.0,
+ 0.0, 0.0, 0.0, 1.0);
+}
+
+mat4 rotateZ(float angle)
+{
+ float ca = cos(angle);
+ float sa = sin(angle);
+ return mat4(ca, sa, 0.0, 0.0,
+ -sa, ca, 0.0, 0.0,
+ 0.0, 0.0, 1.0, 0.0,
+ 0.0, 0.0, 0.0, 1.0);
+}
+
+/* Hair Displacement */
+
+/* Note: The deformer functions below calculate a new location vector
+ * as well as a new direction (aka "normal"), using the partial derivatives of the transformation.
+ *
+ * Each transformation function can depend on the location L as well as the curve parameter t:
+ *
+ * Lnew = f(L, t)
+ * => dLnew/dt = del f/del L * dL/dt + del f/del t
+ *
+ * The first term is the Jacobian of the function f, dL/dt is the original direction vector.
+ * Some more information can be found here:
+ * https://developer.nvidia.com/gpugems/GPUGems/gpugems_ch42.html
+ */
+
+/* Hairs tend to stick together and run in parallel.
+ * The effect increases with distance from the root,
+ * as the stresses pulling fibers apart decrease.
+ */
+struct ClumpParams
+{
+ /* Relative strand thickness at the tip.
+ * (0.0, 1.0]
+ * 0.0 : Strand clumps into a single line
+ * 1.0 : Strand does not clump at all
+ * (> 1.0 is possible but not recommended)
+ */
+ float thickness;
+};
+
+/* Hairs often don't have a circular cross section, but are somewhat flattened.
+ * This creates the local bending which results in the typical curly hair geometry.
+ */
+struct CurlParams
+{
+ /* Radius of the curls.
+ * >= 0.0
+ */
+ float radius;
+ /* Steepness of curls
+ * < 0.0 : Clockwise curls
+ * > 0.0 : Anti-clockwise curls
+ */
+ float angle;
+};
+
+struct DeformParams
+{
+ /* Strand tapering with distance from the root.
+ * < 1.0 : Taper is concave (recommended)
+ * = 1.0 : Taper is linear
+ * > 1.0 : Taper is convex (not recommended)
+ */
+ float taper;
+
+ ClumpParams clump;
+ CurlParams curl;
+};
+
+void deform_taper(DeformParams params, float t, out float taper, out float dtaper)
+{
+ taper = pow(t, params.taper);
+ dtaper = (t > 0.0) ? taper * params.taper / t : 0.0;
+}
+
+void deform_clump(DeformParams params,
+ float t, float tscale, mat4 target_matrix,
+ inout vec3 co, inout vec3 tang)
+{
+ float taper, dtaper;
+ deform_taper(params, t, taper, dtaper);
+ float factor = (1.0 - params.clump.thickness) * taper;
+ float dfactor = (1.0 - params.clump.thickness) * dtaper;
+
+ vec3 target_co = target_matrix[3].xyz;
+ vec3 target_tang = target_matrix[0].xyz;
+ vec3 nco = co + (target_co - co) * factor;
+ vec3 ntang = normalize(tang + (target_tang - tang) * factor + (target_co - co) * dfactor);
+
+ co = nco;
+ tang = ntang;
+}
+
+void deform_curl(DeformParams params,
+ float t, float tscale,
+ inout mat4 target_matrix)
+{
+ float pitch = 2.0*M_PI * params.curl.radius * tan(params.curl.angle);
+ float turns = tscale / (params.curl.radius * tan(params.curl.angle));
+ float angle = t * turns;
+ mat4 local_mat = rotateX(angle) * translate(vec3(0.0, params.curl.radius, 0.0)) * rotateY(params.curl.angle);
+ target_matrix = target_matrix * local_mat;
+}
+
+void deform_fiber(DeformParams params,
+ float t, float tscale, mat4 target_matrix,
+ inout vec3 loc, inout vec3 tang)
+{
+ deform_curl(params, t, tscale, target_matrix);
+ deform_clump(params, t, tscale, target_matrix, loc, tang);
+}
+
+/*===================================*/
+/* Hair Interpolation */
+
+#define FIBER_RIBBON
+
+uniform sampler2D fiber_data;
+
+uniform int fiber_start;
+uniform int strand_map_start;
+uniform int strand_vertex_start;
+
+uniform float ribbon_width;
+uniform vec2 viewport_size;
+
+#define INDEX_INVALID -1
+
+vec2 read_texdata(int offset)
+{
+ ivec2 offset2 = ivec2(offset % HAIR_SHADER_TEX_WIDTH, offset / HAIR_SHADER_TEX_WIDTH);
+ return texelFetch(fiber_data, offset2, 0).rg;
+}
+
+mat4 mat4_from_vectors(vec3 nor, vec3 tang, vec3 co)
+{
+ tang = normalize(tang);
+ vec3 xnor = normalize(cross(nor, tang));
+ return mat4(vec4(tang, 0.0), vec4(xnor, 0.0), vec4(cross(tang, xnor), 0.0), vec4(co, 1.0));
+}
+
+void get_strand_data(int index, out int start, out int count)
+{
+ int offset = strand_map_start + index;
+ vec2 a = read_texdata(offset);
+
+ start = floatBitsToInt(a.r);
+ count = floatBitsToInt(a.g);
+}
+
+void get_strand_vertex(int index, out vec3 co, out vec3 nor, out vec3 tang)
+{
+ int offset = strand_vertex_start + index * 5;
+ vec2 a = read_texdata(offset);
+ vec2 b = read_texdata(offset + 1);
+ vec2 c = read_texdata(offset + 2);
+ vec2 d = read_texdata(offset + 3);
+ vec2 e = read_texdata(offset + 4);
+
+ co = vec3(a.rg, b.r);
+ nor = vec3(b.g, c.rg);
+ tang = vec3(d.rg, e.r);
+}
+
+void get_strand_root(int index, out vec3 co)
+{
+ int offset = strand_vertex_start + index * 5;
+ vec2 a = read_texdata(offset);
+ vec2 b = read_texdata(offset + 1);
+
+ co = vec3(a.rg, b.r);
+}
+
+void get_fiber_data(int fiber_index, out ivec4 parent_index, out vec4 parent_weight, out vec3 pos)
+{
+ int offset = fiber_start + fiber_index * 6;
+ vec2 a = read_texdata(offset);
+ vec2 b = read_texdata(offset + 1);
+ vec2 c = read_texdata(offset + 2);
+ vec2 d = read_texdata(offset + 3);
+ vec2 e = read_texdata(offset + 4);
+ vec2 f = read_texdata(offset + 5);
+
+ parent_index = ivec4(floatBitsToInt(a.rg), floatBitsToInt(b.rg));
+ parent_weight = vec4(c.rg, d.rg);
+ pos = vec3(e.rg, f.r);
+}
+
+void interpolate_parent_curve_full(int index, float curve_param, out vec3 co, out vec3 nor, out vec3 tang, out vec3 rootco)
+{
+ int start, count;
+ get_strand_data(index, start, count);
+
+ get_strand_root(start, rootco);
+
+#if 0 // Don't have to worry about out-of-bounds segment here, as long as lerpfac becomes 0.0 when curve_param==1.0
+ float maxlen = float(count - 1);
+ float arclength = curve_param * maxlen;
+ int segment = min(int(arclength), count - 2);
+ float lerpfac = arclength - min(floor(arclength), maxlen - 1.0);
+#else
+ float maxlen = float(count - 1);
+ float arclength = curve_param * maxlen;
+ int segment = int(arclength);
+ float lerpfac = arclength - floor(arclength);
+#endif
+
+ vec3 co0, nor0, tang0;
+ vec3 co1, nor1, tang1;
+ get_strand_vertex(start + segment, co0, nor0, tang0);
+ get_strand_vertex(start + segment + 1, co1, nor1, tang1);
+
+ co = mix(co0, co1, lerpfac) - rootco;
+ nor = mix(nor0, nor1, lerpfac);
+ tang = mix(tang0, tang1, lerpfac);
+}
+
+void interpolate_parent_curve(int index, float curve_param, out vec3 co, out vec3 tang)
+{
+ vec3 nor;
+ vec3 rootco;
+ interpolate_parent_curve_full(index, curve_param, co, nor, tang, rootco);
+}
+
+void interpolate_vertex(int fiber_index, float curve_param,
+ out vec3 co, out vec3 tang,
+ out mat4 target_matrix)
+{
+ co = vec3(0.0);
+ tang = vec3(0.0);
+ target_matrix = mat4(1.0);
+
+ ivec4 parent_index;
+ vec4 parent_weight;
+ vec3 rootco;
+ get_fiber_data(fiber_index, parent_index, parent_weight, rootco);
+
+ if (parent_index.x != INDEX_INVALID) {
+ vec3 pco, pnor, ptang, prootco;
+ interpolate_parent_curve_full(parent_index.x, curve_param, pco, pnor, ptang, prootco);
+ co += parent_weight.x * pco;
+ tang += parent_weight.x * normalize(ptang);
+
+ target_matrix = mat4_from_vectors(pnor, ptang, pco + prootco);
+ }
+ if (parent_index.y != INDEX_INVALID) {
+ vec3 pco, ptang;
+ interpolate_parent_curve(parent_index.y, curve_param, pco, ptang);
+ co += parent_weight.y * pco;
+ tang += parent_weight.y * normalize(ptang);
+ }
+ if (parent_index.z != INDEX_INVALID) {
+ vec3 pco, ptang;
+ interpolate_parent_curve(parent_index.z, curve_param, pco, ptang);
+ co += parent_weight.z * pco;
+ tang += parent_weight.z * normalize(ptang);
+ }
+ if (parent_index.w != INDEX_INVALID) {
+ vec3 pco, ptang;
+ interpolate_parent_curve(parent_index.w, curve_param, pco, ptang);
+ co += parent_weight.w * pco;
+ tang += parent_weight.w * normalize(ptang);
+ }
+
+ co += rootco;
+ tang = normalize(tang);
+}
+
+void hair_fiber_get_vertex(int fiber_index, float curve_param, mat4 ModelViewMatrix, out vec3 pos, out vec3 nor, out vec2 view_offset)
+{
+ vec3 target_loc;
+ mat4 target_matrix;
+ interpolate_vertex(fiber_index, curve_param, pos, nor, target_matrix);
+
+ DeformParams deform_params;
+ deform_params.taper = 2.0;
+ deform_params.clump.thickness = 0.15;
+ deform_params.curl.radius = 0.1;
+ deform_params.curl.angle = 0.2;
+ // TODO define proper curve scale, independent of subdivision!
+ //deform_fiber(deform_params, curve_param, 1.0, target_matrix, pos, nor);
+
+#ifdef FIBER_RIBBON
+ float ribbon_side = (float(gl_VertexID % 2) - 0.5) * ribbon_width;
+ {
+ vec4 view_nor = ModelViewMatrix * vec4(nor, 0.0);
+ view_offset = vec2(view_nor.y, -view_nor.x);
+ float L = length(view_offset);
+ if (L > 0.0) {
+ view_offset *= ribbon_side / (L * viewport_size);
+ }
+ }
+#else
+ view_offset = vec2(0.0);
+#endif
+}
+
+#endif /*HAIR_SHADER_FIBERS*/
diff --git a/source/blender/draw/engines/eevee/shaders/lit_surface_vert.glsl b/source/blender/draw/engines/eevee/shaders/lit_surface_vert.glsl
index d25c49098a6..d53852193d5 100644
--- a/source/blender/draw/engines/eevee/shaders/lit_surface_vert.glsl
+++ b/source/blender/draw/engines/eevee/shaders/lit_surface_vert.glsl
@@ -7,8 +7,13 @@ uniform mat3 WorldNormalMatrix;
uniform mat3 NormalMatrix;
#endif
+#ifndef HAIR_SHADER_FIBERS
in vec3 pos;
in vec3 nor;
+#else
+in int fiber_index;
+in float curve_param;
+#endif
out vec3 worldPosition;
out vec3 viewPosition;
@@ -28,7 +33,17 @@ out vec3 viewNormal;
#endif
void main() {
+#ifndef HAIR_SHADER_FIBERS
+ gl_Position = ModelViewProjectionMatrix * vec4(pos, 1.0);
+#else
+ vec3 pos;
+ vec3 nor;
+ vec2 view_offset;
+ hair_fiber_get_vertex(fiber_index, curve_param, ModelViewMatrix, pos, nor, view_offset);
gl_Position = ModelViewProjectionMatrix * vec4(pos, 1.0);
+ gl_Position.xy += view_offset * gl_Position.w;
+#endif
+
viewPosition = (ModelViewMatrix * vec4(pos, 1.0)).xyz;
worldPosition = (ModelMatrix * vec4(pos, 1.0)).xyz;
viewNormal = normalize(NormalMatrix * nor);
@@ -40,4 +55,4 @@ void main() {
#ifdef ATTRIB
pass_attrib(pos);
#endif
-} \ No newline at end of file
+}
diff --git a/source/blender/draw/engines/eevee/shaders/prepass_vert.glsl b/source/blender/draw/engines/eevee/shaders/prepass_vert.glsl
index c756e061d70..974c5724842 100644
--- a/source/blender/draw/engines/eevee/shaders/prepass_vert.glsl
+++ b/source/blender/draw/engines/eevee/shaders/prepass_vert.glsl
@@ -1,17 +1,33 @@
uniform mat4 ModelViewProjectionMatrix;
uniform mat4 ModelMatrix;
+uniform mat4 ModelViewMatrix;
/* keep in sync with DRWManager.view_data */
layout(std140) uniform clip_block {
vec4 ClipPlanes[1];
};
+#ifndef HAIR_SHADER_FIBERS
in vec3 pos;
+#else
+in int fiber_index;
+in float curve_param;
+#endif
void main()
{
+#ifndef HAIR_SHADER_FIBERS
+ gl_Position = ModelViewProjectionMatrix * vec4(pos, 1.0);
+#else
+ vec3 pos;
+ vec3 nor;
+ vec2 view_offset;
+ hair_fiber_get_vertex(fiber_index, curve_param, ModelViewMatrix, pos, nor, view_offset);
gl_Position = ModelViewProjectionMatrix * vec4(pos, 1.0);
+ gl_Position.xy += view_offset * gl_Position.w;
+#endif
+
#ifdef CLIP_PLANES
vec4 worldPosition = (ModelMatrix * vec4(pos, 1.0));
gl_ClipDistance[0] = dot(vec4(worldPosition.xyz, 1.0), ClipPlanes[0]);
diff --git a/source/blender/draw/intern/draw_cache.c b/source/blender/draw/intern/draw_cache.c
index 301a39d053f..06fb71b7e67 100644
--- a/source/blender/draw/intern/draw_cache.c
+++ b/source/blender/draw/intern/draw_cache.c
@@ -2565,6 +2565,37 @@ Gwn_Batch *DRW_cache_lattice_vert_overlay_get(Object *ob)
/* -------------------------------------------------------------------- */
+/** \name Groom
+ * \{ */
+
+Gwn_Batch *DRW_cache_groom_verts_get(Object *ob)
+{
+ BLI_assert(ob->type == OB_GROOM);
+
+ struct Groom *groom = ob->data;
+ return DRW_groom_batch_cache_get_all_verts(groom);
+}
+
+Gwn_Batch *DRW_cache_groom_wire_get(Object *ob)
+{
+ BLI_assert(ob->type == OB_GROOM);
+
+ struct Groom *groom = ob->data;
+ return DRW_groom_batch_cache_get_all_edges(groom);
+}
+
+Gwn_Batch *DRW_cache_groom_vert_overlay_get(Object *ob, int mode)
+{
+ BLI_assert(ob->type == OB_GROOM);
+
+ struct Groom *groom = ob->data;
+ return DRW_groom_batch_cache_get_overlay_verts(groom, mode);
+}
+
+/** \} */
+
+/* -------------------------------------------------------------------- */
+
/** \name Particles
* \{ */
@@ -2713,6 +2744,36 @@ Gwn_Batch *DRW_cache_particles_get_prim(int type)
return NULL;
}
+/* -------------------------------------------------------------------- */
+
+/** \name Hair */
+
+Gwn_Batch *DRW_cache_hair_get_fibers(struct HairSystem *hsys, struct DerivedMesh *scalp, int subdiv,
+ const struct DRWHairFiberTextureBuffer **r_buffer)
+{
+ return DRW_hair_batch_cache_get_fibers(hsys, scalp, subdiv, r_buffer);
+}
+
+Gwn_Batch *DRW_cache_hair_get_follicle_points(struct HairSystem *hsys, struct DerivedMesh *scalp)
+{
+ return DRW_hair_batch_cache_get_follicle_points(hsys, scalp);
+}
+
+Gwn_Batch *DRW_cache_hair_get_follicle_axes(struct HairSystem *hsys, struct DerivedMesh *scalp)
+{
+ return DRW_hair_batch_cache_get_follicle_axes(hsys, scalp);
+}
+
+Gwn_Batch *DRW_cache_hair_get_guide_curve_points(struct HairSystem *hsys, struct DerivedMesh *scalp, int subdiv)
+{
+ return DRW_hair_batch_cache_get_guide_curve_points(hsys, scalp, subdiv);
+}
+
+Gwn_Batch *DRW_cache_hair_get_guide_curve_edges(struct HairSystem *hsys, struct DerivedMesh *scalp, int subdiv)
+{
+ return DRW_hair_batch_cache_get_guide_curve_edges(hsys, scalp, subdiv);
+}
+
/* 3D cursor */
Gwn_Batch *DRW_cache_cursor_get(void)
{
diff --git a/source/blender/draw/intern/draw_cache.h b/source/blender/draw/intern/draw_cache.h
index 2ef57884a44..bae59ff3604 100644
--- a/source/blender/draw/intern/draw_cache.h
+++ b/source/blender/draw/intern/draw_cache.h
@@ -30,6 +30,10 @@ struct Gwn_Batch;
struct GPUMaterial;
struct Object;
struct ModifierData;
+struct Groom;
+struct HairSystem;
+struct DRWHairFiberTextureBuffer;
+struct DerivedMesh;
void DRW_shape_cache_free(void);
@@ -163,11 +167,24 @@ struct Gwn_Batch *DRW_cache_lattice_verts_get(struct Object *ob);
struct Gwn_Batch *DRW_cache_lattice_wire_get(struct Object *ob, bool use_weight);
struct Gwn_Batch *DRW_cache_lattice_vert_overlay_get(struct Object *ob);
+/* Groom */
+struct Gwn_Batch *DRW_cache_groom_verts_get(struct Object *ob);
+struct Gwn_Batch *DRW_cache_groom_wire_get(struct Object *ob);
+struct Gwn_Batch *DRW_cache_groom_vert_overlay_get(struct Object *ob, int mode);
+
/* Particles */
struct Gwn_Batch *DRW_cache_particles_get_hair(struct ParticleSystem *psys, struct ModifierData *md);
struct Gwn_Batch *DRW_cache_particles_get_dots(struct Object *object, struct ParticleSystem *psys);
struct Gwn_Batch *DRW_cache_particles_get_prim(int type);
+/* Hair */
+struct Gwn_Batch *DRW_cache_hair_get_fibers(struct HairSystem *hsys, struct DerivedMesh *scalp, int subdiv,
+ const struct DRWHairFiberTextureBuffer **r_buffer);
+struct Gwn_Batch *DRW_cache_hair_get_follicle_points(struct HairSystem *hsys, struct DerivedMesh *scalp);
+struct Gwn_Batch *DRW_cache_hair_get_follicle_axes(struct HairSystem *hsys, struct DerivedMesh *scalp);
+struct Gwn_Batch *DRW_cache_hair_get_guide_curve_points(struct HairSystem *hsys, struct DerivedMesh *scalp, int subdiv);
+struct Gwn_Batch *DRW_cache_hair_get_guide_curve_edges(struct HairSystem *hsys, struct DerivedMesh *scalp, int subdiv);
+
/* Metaball */
struct Gwn_Batch *DRW_cache_mball_surface_get(struct Object *ob);
diff --git a/source/blender/draw/intern/draw_cache_impl.h b/source/blender/draw/intern/draw_cache_impl.h
index 83cc87307b5..0ac2f11849a 100644
--- a/source/blender/draw/intern/draw_cache_impl.h
+++ b/source/blender/draw/intern/draw_cache_impl.h
@@ -34,6 +34,10 @@ struct Gwn_VertBuf;
struct ListBase;
struct ModifierData;
struct ParticleSystem;
+struct Groom;
+struct HairSystem;
+struct DRWHairFiberTextureBuffer;
+struct DerivedMesh;
struct Curve;
struct Lattice;
@@ -56,6 +60,12 @@ void DRW_lattice_batch_cache_free(struct Lattice *lt);
void DRW_particle_batch_cache_dirty(struct ParticleSystem *psys, int mode);
void DRW_particle_batch_cache_free(struct ParticleSystem *psys);
+void DRW_groom_batch_cache_dirty(struct Groom *groom, int mode);
+void DRW_groom_batch_cache_free(struct Groom *groom);
+
+void DRW_hair_batch_cache_dirty(struct HairSystem *hsys, int mode);
+void DRW_hair_batch_cache_free(struct HairSystem *hsys);
+
/* Curve */
struct Gwn_Batch *DRW_curve_batch_cache_get_wire_edge(struct Curve *cu, struct CurveCache *ob_curve_cache);
struct Gwn_Batch *DRW_curve_batch_cache_get_normal_edge(
@@ -88,6 +98,11 @@ struct Gwn_Batch *DRW_lattice_batch_cache_get_all_edges(struct Lattice *lt, bool
struct Gwn_Batch *DRW_lattice_batch_cache_get_all_verts(struct Lattice *lt);
struct Gwn_Batch *DRW_lattice_batch_cache_get_overlay_verts(struct Lattice *lt);
+/* Groom */
+struct Gwn_Batch *DRW_groom_batch_cache_get_all_edges(struct Groom *groom);
+struct Gwn_Batch *DRW_groom_batch_cache_get_all_verts(struct Groom *groom);
+struct Gwn_Batch *DRW_groom_batch_cache_get_overlay_verts(struct Groom *groom, int mode);
+
/* Mesh */
struct Gwn_Batch **DRW_mesh_batch_cache_get_surface_shaded(
@@ -124,4 +139,12 @@ void DRW_mesh_cache_sculpt_coords_ensure(struct Mesh *me);
struct Gwn_Batch *DRW_particles_batch_cache_get_hair(struct ParticleSystem *psys, struct ModifierData *md);
struct Gwn_Batch *DRW_particles_batch_cache_get_dots(struct Object *object, struct ParticleSystem *psys);
+/* Hair */
+struct Gwn_Batch *DRW_hair_batch_cache_get_fibers(struct HairSystem *hsys, struct DerivedMesh *scalp, int subdiv,
+ const struct DRWHairFiberTextureBuffer **r_buffer);
+struct Gwn_Batch *DRW_hair_batch_cache_get_follicle_points(struct HairSystem *hsys, struct DerivedMesh *scalp);
+struct Gwn_Batch *DRW_hair_batch_cache_get_follicle_axes(struct HairSystem *hsys, struct DerivedMesh *scalp);
+struct Gwn_Batch *DRW_hair_batch_cache_get_guide_curve_points(struct HairSystem *hsys, struct DerivedMesh *scalp, int subdiv);
+struct Gwn_Batch *DRW_hair_batch_cache_get_guide_curve_edges(struct HairSystem *hsys, struct DerivedMesh *scalp, int subdiv);
+
#endif /* __DRAW_CACHE_IMPL_H__ */
diff --git a/source/blender/draw/intern/draw_cache_impl_groom.c b/source/blender/draw/intern/draw_cache_impl_groom.c
new file mode 100644
index 00000000000..581dc707372
--- /dev/null
+++ b/source/blender/draw/intern/draw_cache_impl_groom.c
@@ -0,0 +1,610 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) Blender Foundation
+ * All rights reserved.
+ *
+ * The Original Code is: all of this file.
+ *
+ * Contributor(s): Lukas Toenne
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file draw_cache_impl_groom.c
+ * \ingroup draw
+ *
+ * \brief Groom API for render engines
+ */
+
+#include <string.h>
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_utildefines.h"
+#include "BLI_listbase.h"
+#include "BLI_math.h"
+
+#include "DNA_groom_types.h"
+#include "DNA_scene_types.h"
+#include "DNA_userdef_types.h"
+
+#include "BKE_groom.h"
+
+#include "GPU_batch.h"
+
+#include "draw_cache_impl.h" /* own include */
+
+#define SELECT 1
+
+static void groom_batch_cache_clear(Groom *groom);
+
+/* ---------------------------------------------------------------------- */
+/* Groom Gwn_Batch Cache */
+
+typedef struct GroomBatchCache {
+ Gwn_VertBuf *pos;
+ Gwn_IndexBuf *edges;
+
+ Gwn_Batch *all_verts;
+ Gwn_Batch *all_edges;
+
+ Gwn_Batch *overlay_verts;
+
+ /* settings to determine if cache is invalid */
+ bool is_dirty;
+
+ bool is_editmode;
+} GroomBatchCache;
+
+/* Gwn_Batch cache management. */
+
+static bool groom_batch_cache_valid(Groom *groom)
+{
+ GroomBatchCache *cache = groom->batch_cache;
+
+ if (cache == NULL) {
+ return false;
+ }
+
+ if (cache->is_editmode != (groom->editgroom != NULL)) {
+ return false;
+ }
+
+ if (cache->is_dirty == false) {
+ return true;
+ }
+ else {
+ if (cache->is_editmode) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static void groom_batch_cache_init(Groom *groom)
+{
+ GroomBatchCache *cache = groom->batch_cache;
+
+ if (!cache) {
+ cache = groom->batch_cache = MEM_callocN(sizeof(*cache), __func__);
+ }
+ else {
+ memset(cache, 0, sizeof(*cache));
+ }
+
+ cache->is_editmode = (groom->editgroom != NULL);
+
+ cache->is_dirty = false;
+}
+
+static GroomBatchCache *groom_batch_cache_get(Groom *groom)
+{
+ if (!groom_batch_cache_valid(groom)) {
+ groom_batch_cache_clear(groom);
+ groom_batch_cache_init(groom);
+ }
+ return groom->batch_cache;
+}
+
+void DRW_groom_batch_cache_dirty(Groom *groom, int mode)
+{
+ GroomBatchCache *cache = groom->batch_cache;
+ if (cache == NULL) {
+ return;
+ }
+ switch (mode) {
+ case BKE_GROOM_BATCH_DIRTY_ALL:
+ cache->is_dirty = true;
+ break;
+ case BKE_GROOM_BATCH_DIRTY_SELECT:
+ /* TODO Separate Flag vbo */
+ GWN_BATCH_DISCARD_SAFE(cache->overlay_verts);
+ break;
+ default:
+ BLI_assert(0);
+ }
+}
+
+static void groom_batch_cache_clear(Groom *groom)
+{
+ GroomBatchCache *cache = groom->batch_cache;
+ if (!cache) {
+ return;
+ }
+
+ GWN_BATCH_DISCARD_SAFE(cache->all_verts);
+ GWN_BATCH_DISCARD_SAFE(cache->all_edges);
+ GWN_BATCH_DISCARD_SAFE(cache->overlay_verts);
+
+ GWN_VERTBUF_DISCARD_SAFE(cache->pos);
+ GWN_INDEXBUF_DISCARD_SAFE(cache->edges);
+}
+
+void DRW_groom_batch_cache_free(Groom *groom)
+{
+ groom_batch_cache_clear(groom);
+ MEM_SAFE_FREE(groom->batch_cache);
+}
+
+enum {
+ VFLAG_VERTEX_SELECTED = 1 << 0,
+ VFLAG_VERTEX_ACTIVE = 1 << 1,
+};
+
+BLI_INLINE char make_vertex_flag(bool active, bool selected)
+{
+ char vflag = 0;
+ if (active)
+ {
+ vflag |= VFLAG_VERTEX_ACTIVE;
+ }
+ if (selected)
+ {
+ vflag |= VFLAG_VERTEX_SELECTED;
+ }
+ return vflag;
+}
+
+/* Parts of the groom object to render */
+typedef enum GroomRenderPart
+{
+ GM_RENDER_REGIONS = (1 << 0), /* Draw scalp regions */
+ GM_RENDER_CURVES = (1 << 1), /* Draw center curves of bundles */
+ GM_RENDER_SECTIONS = (1 << 2), /* Draw section curves */
+
+ GM_RENDER_ALL = GM_RENDER_REGIONS | GM_RENDER_CURVES | GM_RENDER_SECTIONS,
+} GroomRenderPart;
+
+static int groom_count_verts(Groom *groom, int parts, bool use_curve_cache)
+{
+ const ListBase *bundles = groom->editgroom ? &groom->editgroom->bundles : &groom->bundles;
+ int vert_len = 0;
+
+ if (parts & GM_RENDER_REGIONS)
+ {
+ // TODO
+ }
+ if (parts & GM_RENDER_CURVES)
+ {
+ for (GroomBundle *bundle = bundles->first; bundle; bundle = bundle->next)
+ {
+ if (use_curve_cache)
+ {
+ vert_len += bundle->curvesize;
+ }
+ else
+ {
+ vert_len += bundle->totsections;
+ }
+ }
+ }
+ if (parts & GM_RENDER_SECTIONS)
+ {
+ for (GroomBundle *bundle = bundles->first; bundle; bundle = bundle->next)
+ {
+ if (use_curve_cache)
+ {
+ vert_len += bundle->curvesize * bundle->numshapeverts;
+ }
+ else
+ {
+ vert_len += bundle->totverts;
+ }
+ }
+ }
+
+ return vert_len;
+}
+
+static int groom_count_edges(Groom *groom, int parts, bool use_curve_cache)
+{
+ const ListBase *bundles = groom->editgroom ? &groom->editgroom->bundles : &groom->bundles;
+ int edge_len = 0;
+
+ if (parts & GM_RENDER_REGIONS)
+ {
+ // TODO
+ }
+ if (parts & GM_RENDER_CURVES)
+ {
+ for (GroomBundle *bundle = bundles->first; bundle; bundle = bundle->next)
+ {
+ if (use_curve_cache)
+ {
+ if (bundle->curvesize > 0)
+ {
+ edge_len += bundle->curvesize - 1;
+ }
+ }
+ else
+ {
+ if (bundle->totsections > 0)
+ {
+ edge_len += bundle->totsections - 1;
+ }
+ }
+ }
+ }
+ if (parts & GM_RENDER_SECTIONS)
+ {
+ for (GroomBundle *bundle = bundles->first; bundle; bundle = bundle->next)
+ {
+ if (bundle->numshapeverts > 1)
+ {
+ // Closed edge loop, 1 edge per vertex
+ if (use_curve_cache)
+ {
+ /* a curve for each shape vertex */
+ int numedges_curves = (bundle->curvesize - 1) * bundle->numshapeverts;
+ /* a loop for each section */
+ int numedges_sections = bundle->numshapeverts * bundle->totsections;
+ edge_len += numedges_curves + numedges_sections;
+ }
+ else
+ {
+ edge_len += bundle->totverts;
+ }
+ }
+ }
+ }
+
+ return edge_len;
+}
+
+#define GM_ATTR_ID_UNUSED 0xFFFFFFFF
+
+static void groom_get_verts(
+ Groom *groom,
+ int parts,
+ bool use_curve_cache,
+ Gwn_VertBuf *vbo,
+ uint id_pos,
+ uint id_flag)
+{
+ int vert_len = groom_count_verts(groom, parts, use_curve_cache);
+ const ListBase *bundles = groom->editgroom ? &groom->editgroom->bundles : &groom->bundles;
+
+ GWN_vertbuf_data_alloc(vbo, vert_len);
+
+ uint idx = 0;
+ if (parts & GM_RENDER_REGIONS)
+ {
+ // TODO
+ }
+ if (parts & GM_RENDER_CURVES)
+ {
+ for (GroomBundle *bundle = bundles->first; bundle; bundle = bundle->next)
+ {
+ if (use_curve_cache)
+ {
+ GroomCurveCache *cache = bundle->curvecache;
+ for (int i = 0; i < bundle->curvesize; ++i, ++cache)
+ {
+ if (id_pos != GM_ATTR_ID_UNUSED)
+ {
+ GWN_vertbuf_attr_set(vbo, id_pos, idx, cache->co);
+ }
+ if (id_flag != GM_ATTR_ID_UNUSED)
+ {
+ char vflag = make_vertex_flag(false, false);
+ GWN_vertbuf_attr_set(vbo, id_flag, idx, &vflag);
+ }
+
+ ++idx;
+ }
+ }
+ else
+ {
+ const bool active = bundle->flag & GM_BUNDLE_SELECT;
+ GroomSection *section = bundle->sections;
+ for (int i = 0; i < bundle->totsections; ++i, ++section)
+ {
+ if (id_pos != GM_ATTR_ID_UNUSED)
+ {
+ GWN_vertbuf_attr_set(vbo, id_pos, idx, section->center);
+ }
+ if (id_flag != GM_ATTR_ID_UNUSED)
+ {
+ char vflag = make_vertex_flag(active, section->flag & GM_SECTION_SELECT);
+ GWN_vertbuf_attr_set(vbo, id_flag, idx, &vflag);
+ }
+
+ ++idx;
+ }
+ }
+ }
+ }
+ if (parts & GM_RENDER_SECTIONS)
+ {
+ for (GroomBundle *bundle = bundles->first; bundle; bundle = bundle->next)
+ {
+ if (use_curve_cache)
+ {
+ GroomCurveCache *cache = bundle->curvecache;
+ for (int i = 0; i < bundle->numshapeverts; ++i)
+ {
+ for (int j = 0; j < bundle->curvesize; ++j, ++cache)
+ {
+ if (id_pos != GM_ATTR_ID_UNUSED)
+ {
+ GWN_vertbuf_attr_set(vbo, id_pos, idx, cache->co);
+ }
+ if (id_flag != GM_ATTR_ID_UNUSED)
+ {
+ char vflag = make_vertex_flag(false, false);
+ GWN_vertbuf_attr_set(vbo, id_flag, idx, &vflag);
+ }
+
+ ++idx;
+ }
+ }
+ }
+ else
+ {
+ GroomSection *section = bundle->sections;
+ GroomSectionVertex *vertex = bundle->verts;
+ for (int i = 0; i < bundle->totsections; ++i, ++section)
+ {
+ const bool active = (bundle->flag & GM_BUNDLE_SELECT) && (section->flag & GM_SECTION_SELECT);
+
+ for (int j = 0; j < bundle->numshapeverts; ++j, ++vertex)
+ {
+ if (id_pos != GM_ATTR_ID_UNUSED)
+ {
+ float co[3] = {vertex->co[0], vertex->co[1], 0.0f};
+ mul_m3_v3(section->mat, co);
+ add_v3_v3(co, section->center);
+ GWN_vertbuf_attr_set(vbo, id_pos, idx, co);
+ }
+ if (id_flag != GM_ATTR_ID_UNUSED)
+ {
+ char vflag = make_vertex_flag(active, vertex->flag & GM_VERTEX_SELECT);
+ GWN_vertbuf_attr_set(vbo, id_flag, idx, &vflag);
+ }
+
+ ++idx;
+ }
+ }
+ }
+ }
+ }
+}
+
+static void groom_get_edges(
+ Groom *groom,
+ int parts,
+ bool use_curve_cache,
+ Gwn_IndexBuf **ibo)
+{
+ Gwn_IndexBufBuilder elb;
+
+ int vert_len = groom_count_verts(groom, parts, use_curve_cache);
+ int edge_len = groom_count_edges(groom, parts, use_curve_cache);
+ const ListBase *bundles = groom->editgroom ? &groom->editgroom->bundles : &groom->bundles;
+
+ GWN_indexbuf_init(&elb, GWN_PRIM_LINES, edge_len, vert_len);
+
+ uint idx = 0;
+ if (parts & GM_RENDER_REGIONS)
+ {
+ // TODO
+ }
+ if (parts & GM_RENDER_CURVES)
+ {
+ for (GroomBundle *bundle = bundles->first; bundle; bundle = bundle->next)
+ {
+ if (use_curve_cache)
+ {
+ GroomCurveCache *cache = bundle->curvecache;
+ for (int i = 0; i < bundle->curvesize - 1; ++i, ++cache)
+ {
+ GWN_indexbuf_add_line_verts(&elb, idx + i, idx + i + 1);
+ }
+ idx += bundle->curvesize;
+ }
+ else
+ {
+ GroomSection *section = bundle->sections;
+ for (int i = 0; i < bundle->totsections - 1; ++i, ++section)
+ {
+ GWN_indexbuf_add_line_verts(&elb, idx + i, idx + i + 1);
+ }
+ idx += bundle->totsections;
+ }
+ }
+ }
+ if (parts & GM_RENDER_SECTIONS)
+ {
+ const int curve_res = groom->curve_res;
+ for (GroomBundle *bundle = bundles->first; bundle; bundle = bundle->next)
+ {
+ const int numshapeverts = bundle->numshapeverts;
+ if (numshapeverts > 1)
+ {
+ if (use_curve_cache)
+ {
+ const int curvesize = bundle->curvesize;
+ if (curvesize == 0)
+ {
+ continue;
+ }
+
+ /* a curve for each shape vertex */
+ for (int i = 0; i < numshapeverts; ++i)
+ {
+ uint idx0 = idx + i * bundle->curvesize;
+ for (int j = 0; j < bundle->curvesize - 1; ++j)
+ {
+ GWN_indexbuf_add_line_verts(
+ &elb,
+ idx0 + j,
+ idx0 + (j+1));
+ }
+ }
+
+ /* a loop for each section */
+ for (int i = 0; i < bundle->totsections; ++i)
+ {
+ uint idx0 = idx + i * curve_res;
+ for (int j = 0; j < numshapeverts - 1; ++j)
+ {
+ GWN_indexbuf_add_line_verts(&elb, idx0 + j * curvesize, idx0 + (j + 1) * curvesize);
+ }
+ // close the loop
+ GWN_indexbuf_add_line_verts(&elb, idx0 + (numshapeverts-1) * curvesize, idx0);
+ }
+
+ idx += bundle->curvesize * bundle->numshapeverts;
+ }
+ else
+ {
+ for (int i = 0; i < bundle->totsections; ++i)
+ {
+ uint idx0 = idx + i * numshapeverts;
+ for (int j = 0; j < numshapeverts - 1; ++j)
+ {
+ GWN_indexbuf_add_line_verts(&elb, idx0 + j, idx0 + j + 1);
+ }
+ // close the loop
+ GWN_indexbuf_add_line_verts(&elb, idx0 + (numshapeverts-1), idx0);
+ }
+
+ idx += bundle->totverts;
+ }
+ }
+ }
+ }
+
+ *ibo = GWN_indexbuf_build(&elb);
+}
+
+/* Gwn_Batch cache usage. */
+static Gwn_VertBuf *groom_batch_cache_get_pos(Groom *groom, GroomBatchCache *cache, int parts)
+{
+ if (cache->pos == NULL) {
+ static Gwn_VertFormat format = { 0 };
+ static struct { uint pos, col; } attr_id;
+
+ GWN_vertformat_clear(&format);
+
+ /* initialize vertex format */
+ attr_id.pos = GWN_vertformat_attr_add(&format, "pos", GWN_COMP_F32, 3, GWN_FETCH_FLOAT);
+
+ cache->pos = GWN_vertbuf_create_with_format(&format);
+
+ groom_get_verts(groom, parts, true, cache->pos, attr_id.pos, GM_ATTR_ID_UNUSED);
+ }
+
+ return cache->pos;
+}
+
+static Gwn_IndexBuf *groom_batch_cache_get_edges(Groom *groom, GroomBatchCache *cache, int parts)
+{
+ if (cache->edges == NULL) {
+ groom_get_edges(groom, parts, true, &cache->edges);
+ }
+
+ return cache->edges;
+}
+
+static void groom_batch_cache_create_overlay_batches(Groom *groom, GroomBatchCache *cache, int parts)
+{
+ if (cache->overlay_verts == NULL) {
+ static Gwn_VertFormat format = { 0 };
+ static struct { uint pos, data; } attr_id;
+ if (format.attrib_ct == 0) {
+ /* initialize vertex format */
+ attr_id.pos = GWN_vertformat_attr_add(&format, "pos", GWN_COMP_F32, 3, GWN_FETCH_FLOAT);
+ attr_id.data = GWN_vertformat_attr_add(&format, "data", GWN_COMP_U8, 1, GWN_FETCH_INT);
+ }
+
+ Gwn_VertBuf *vbo = GWN_vertbuf_create_with_format(&format);
+
+ groom_get_verts(groom, parts, false, vbo, attr_id.pos, attr_id.data);
+
+ cache->overlay_verts = GWN_batch_create_ex(GWN_PRIM_POINTS, vbo, NULL, GWN_BATCH_OWNS_VBO);
+ }
+}
+
+Gwn_Batch *DRW_groom_batch_cache_get_all_edges(Groom *groom)
+{
+ GroomBatchCache *cache = groom_batch_cache_get(groom);
+
+ if (cache->all_edges == NULL) {
+ cache->all_edges = GWN_batch_create(
+ GWN_PRIM_LINES,
+ groom_batch_cache_get_pos(groom, cache, GM_RENDER_ALL),
+ groom_batch_cache_get_edges(groom, cache, GM_RENDER_ALL));
+ }
+
+ return cache->all_edges;
+}
+
+Gwn_Batch *DRW_groom_batch_cache_get_all_verts(Groom *groom)
+{
+ GroomBatchCache *cache = groom_batch_cache_get(groom);
+
+ if (cache->all_verts == NULL) {
+ cache->all_verts = GWN_batch_create(
+ GWN_PRIM_POINTS,
+ groom_batch_cache_get_pos(groom, cache, GM_RENDER_ALL),
+ NULL);
+ }
+
+ return cache->all_verts;
+}
+
+Gwn_Batch *DRW_groom_batch_cache_get_overlay_verts(Groom *groom, int mode)
+{
+ GroomBatchCache *cache = groom_batch_cache_get(groom);
+
+ if (cache->overlay_verts == NULL) {
+ GroomRenderPart parts = 0;
+ switch ((GroomEditMode)mode)
+ {
+ case GM_EDIT_MODE_REGIONS: parts |= GM_RENDER_REGIONS; break;
+ case GM_EDIT_MODE_CURVES: parts |= GM_RENDER_CURVES; break;
+ case GM_EDIT_MODE_SECTIONS: parts |= GM_RENDER_SECTIONS; break;
+ }
+
+ groom_batch_cache_create_overlay_batches(groom, cache, parts);
+ }
+
+ return cache->overlay_verts;
+}
diff --git a/source/blender/draw/intern/draw_cache_impl_hair.c b/source/blender/draw/intern/draw_cache_impl_hair.c
new file mode 100644
index 00000000000..b25d6986b81
--- /dev/null
+++ b/source/blender/draw/intern/draw_cache_impl_hair.c
@@ -0,0 +1,404 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2017 by Blender Foundation.
+ * All rights reserved.
+ *
+ * Contributor(s): Blender Foundation, Mike Erwin, Dalai Felinto
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file draw_cache_impl_strands.c
+ * \ingroup draw
+ *
+ * \brief Strands API for render engines
+ */
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_utildefines.h"
+#include "BLI_math_vector.h"
+#include "BLI_ghash.h"
+
+#include "DNA_hair_types.h"
+#include "DNA_scene_types.h"
+
+#include "BKE_hair.h"
+#include "BKE_mesh_sample.h"
+
+#include "DEG_depsgraph.h"
+
+#include "GPU_batch.h"
+#include "GPU_extensions.h"
+#include "GPU_texture.h"
+
+#include "draw_common.h"
+#include "draw_cache_impl.h" /* own include */
+#include "DRW_render.h"
+
+// timing
+//#define DEBUG_TIME
+#ifdef DEBUG_TIME
+# include "PIL_time_utildefines.h"
+#else
+# define TIMEIT_START(var)
+# define TIMEIT_VALUE(var)
+# define TIMEIT_VALUE_PRINT(var)
+# define TIMEIT_END(var)
+# define TIMEIT_BENCH(expr, id) (expr)
+# define TIMEIT_BLOCK_INIT(var)
+# define TIMEIT_BLOCK_START(var)
+# define TIMEIT_BLOCK_END(var)
+# define TIMEIT_BLOCK_STATS(var)
+#endif
+
+/* ---------------------------------------------------------------------- */
+/* Hair Gwn_Batch Cache */
+
+typedef struct HairBatchCache {
+ Gwn_VertBuf *fiber_verts;
+ Gwn_IndexBuf *fiber_edges;
+ Gwn_Batch *fibers;
+ DRWHairFiberTextureBuffer texbuffer;
+
+ Gwn_VertBuf *follicle_verts;
+ Gwn_IndexBuf *follicle_edges;
+ Gwn_Batch *follicles;
+
+ Gwn_VertBuf *guide_curve_verts;
+ Gwn_IndexBuf *guide_curve_edges;
+ Gwn_Batch *guide_curves;
+
+ /* settings to determine if cache is invalid */
+ bool is_dirty;
+} HairBatchCache;
+
+/* Gwn_Batch cache management. */
+
+static void hair_batch_cache_clear(HairSystem *hsys);
+
+static bool hair_batch_cache_valid(HairSystem *hsys)
+{
+ HairBatchCache *cache = hsys->draw_batch_cache;
+
+ if (cache == NULL) {
+ return false;
+ }
+
+ if (cache->is_dirty) {
+ return false;
+ }
+
+ return true;
+}
+
+static void hair_batch_cache_init(HairSystem *hsys)
+{
+ HairBatchCache *cache = hsys->draw_batch_cache;
+
+ if (!cache) {
+ cache = hsys->draw_batch_cache = MEM_callocN(sizeof(*cache), __func__);
+ }
+ else {
+ memset(cache, 0, sizeof(*cache));
+ }
+
+ cache->is_dirty = false;
+}
+
+static HairBatchCache *hair_batch_cache_get(HairSystem *hsys)
+{
+ if (!hair_batch_cache_valid(hsys)) {
+ hair_batch_cache_clear(hsys);
+ hair_batch_cache_init(hsys);
+ }
+ return hsys->draw_batch_cache;
+}
+
+void DRW_hair_batch_cache_dirty(HairSystem *hsys, int mode)
+{
+ HairBatchCache *cache = hsys->draw_batch_cache;
+ if (cache == NULL) {
+ return;
+ }
+ switch (mode) {
+ case BKE_HAIR_BATCH_DIRTY_ALL:
+ cache->is_dirty = true;
+ break;
+ default:
+ BLI_assert(0);
+ }
+}
+
+static void hair_batch_cache_clear(HairSystem *hsys)
+{
+ HairBatchCache *cache = hsys->draw_batch_cache;
+
+ if (hsys->draw_texture_cache) {
+ GPU_texture_free(hsys->draw_texture_cache);
+ hsys->draw_texture_cache = NULL;
+ }
+
+ if (cache) {
+ GWN_BATCH_DISCARD_SAFE(cache->fibers);
+ GWN_VERTBUF_DISCARD_SAFE(cache->fiber_verts);
+ GWN_INDEXBUF_DISCARD_SAFE(cache->fiber_edges);
+
+ GWN_BATCH_DISCARD_SAFE(cache->follicles);
+ GWN_VERTBUF_DISCARD_SAFE(cache->follicle_verts);
+ GWN_INDEXBUF_DISCARD_SAFE(cache->follicle_edges);
+
+ GWN_BATCH_DISCARD_SAFE(cache->guide_curves);
+ GWN_VERTBUF_DISCARD_SAFE(cache->guide_curve_verts);
+ GWN_INDEXBUF_DISCARD_SAFE(cache->guide_curve_edges);
+
+ {
+ DRWHairFiberTextureBuffer *buffer = &cache->texbuffer;
+ if (buffer->data) {
+ MEM_freeN(buffer->data);
+ buffer->data = NULL;
+ }
+ buffer->fiber_start = 0;
+ buffer->strand_map_start = 0;
+ buffer->strand_vertex_start = 0;
+ buffer->width = 0;
+ buffer->height = 0;
+ }
+ }
+}
+
+void DRW_hair_batch_cache_free(HairSystem *hsys)
+{
+ hair_batch_cache_clear(hsys);
+ MEM_SAFE_FREE(hsys->draw_batch_cache);
+}
+
+static void hair_batch_cache_ensure_fibers(HairSystem *hsys, int subdiv, HairBatchCache *cache)
+{
+ TIMEIT_START(hair_batch_cache_ensure_fibers);
+
+ GWN_VERTBUF_DISCARD_SAFE(cache->fiber_verts);
+ GWN_INDEXBUF_DISCARD_SAFE(cache->fiber_edges);
+
+ const int totfibers = hsys->pattern ? hsys->pattern->num_follicles : 0;
+ int *fiber_lengths = BKE_hair_get_fiber_lengths(hsys, subdiv);
+ int totpoint = 0;
+ for (int i = 0; i < totfibers; ++i) {
+ totpoint += fiber_lengths[i];
+ }
+ const int totseg = totpoint - totfibers;
+
+ static Gwn_VertFormat format = { 0 };
+ static unsigned curve_param_id, fiber_index_id;
+
+ /* initialize vertex format */
+ if (format.attrib_ct == 0) {
+ fiber_index_id = GWN_vertformat_attr_add(&format, "fiber_index", GWN_COMP_I32, 1, GWN_FETCH_INT);
+ curve_param_id = GWN_vertformat_attr_add(&format, "curve_param", GWN_COMP_F32, 1, GWN_FETCH_FLOAT);
+ }
+
+ cache->fiber_verts = GWN_vertbuf_create_with_format(&format);
+
+ Gwn_IndexBufBuilder elb;
+ {
+ TIMEIT_START(data_alloc);
+ Gwn_PrimType prim_type;
+ unsigned prim_ct, vert_ct;
+ prim_type = GWN_PRIM_TRIS;
+ prim_ct = 2 * totseg;
+ vert_ct = 2 * totpoint;
+
+ GWN_vertbuf_data_alloc(cache->fiber_verts, vert_ct);
+ GWN_indexbuf_init(&elb, prim_type, prim_ct, vert_ct);
+ TIMEIT_END(data_alloc);
+ }
+
+ TIMEIT_START(data_fill);
+ TIMEIT_BLOCK_INIT(GWN_vertbuf_attr_set);
+ TIMEIT_BLOCK_INIT(GWN_indexbuf_add_tri_verts);
+ int vi = 0;
+ for (int i = 0; i < totfibers; ++i) {
+ const int fiblen = fiber_lengths[i];
+ const float da = fiblen > 1 ? 1.0f / (fiblen-1) : 0.0f;
+
+ float a = 0.0f;
+ for (int k = 0; k < fiblen; ++k) {
+ TIMEIT_BLOCK_START(GWN_vertbuf_attr_set);
+ GWN_vertbuf_attr_set(cache->fiber_verts, fiber_index_id, vi, &i);
+ GWN_vertbuf_attr_set(cache->fiber_verts, curve_param_id, vi, &a);
+ GWN_vertbuf_attr_set(cache->fiber_verts, fiber_index_id, vi+1, &i);
+ GWN_vertbuf_attr_set(cache->fiber_verts, curve_param_id, vi+1, &a);
+ TIMEIT_BLOCK_END(GWN_vertbuf_attr_set);
+
+ if (k > 0) {
+ TIMEIT_BLOCK_START(GWN_indexbuf_add_tri_verts);
+ GWN_indexbuf_add_tri_verts(&elb, vi-2, vi-1, vi+1);
+ GWN_indexbuf_add_tri_verts(&elb, vi+1, vi, vi-2);
+ TIMEIT_BLOCK_END(GWN_indexbuf_add_tri_verts);
+ }
+
+ vi += 2;
+ a += da;
+ }
+ }
+ TIMEIT_BLOCK_STATS(GWN_vertbuf_attr_set);
+ TIMEIT_BLOCK_STATS(GWN_indexbuf_add_tri_verts);
+#ifdef DEBUG_TIME
+ printf("Total GWN time: %f\n", _timeit_var_GWN_vertbuf_attr_set + _timeit_var_GWN_indexbuf_add_tri_verts);
+#endif
+ fflush(stdout);
+ TIMEIT_END(data_fill);
+
+ if (fiber_lengths)
+ {
+ MEM_freeN(fiber_lengths);
+ }
+
+ TIMEIT_BENCH(cache->fiber_edges = GWN_indexbuf_build(&elb), indexbuf_build);
+
+ TIMEIT_END(hair_batch_cache_ensure_fibers);
+}
+
+static void hair_batch_cache_ensure_fiber_texbuffer(HairSystem *hsys, struct DerivedMesh *scalp, int subdiv, HairBatchCache *cache)
+{
+ DRWHairFiberTextureBuffer *buffer = &cache->texbuffer;
+ static const int elemsize = 8;
+ const int width = GPU_max_texture_size();
+ const int align = width * elemsize;
+
+ // Offsets in bytes
+ int b_size, b_strand_map_start, b_strand_vertex_start, b_fiber_start;
+ BKE_hair_get_texture_buffer_size(hsys, subdiv, &b_size,
+ &b_strand_map_start, &b_strand_vertex_start, &b_fiber_start);
+ // Pad for alignment
+ b_size += align - b_size % align;
+
+ // Convert to element size as texture offsets
+ const int size = b_size / elemsize;
+ const int height = size / width;
+
+ buffer->data = MEM_mallocN(b_size, "hair fiber texture buffer");
+ BKE_hair_get_texture_buffer(hsys, scalp, subdiv, buffer->data);
+
+ buffer->width = width;
+ buffer->height = height;
+ buffer->strand_map_start = b_strand_map_start / elemsize;
+ buffer->strand_vertex_start = b_strand_vertex_start / elemsize;
+ buffer->fiber_start = b_fiber_start / elemsize;
+}
+
+Gwn_Batch *DRW_hair_batch_cache_get_fibers(
+ HairSystem *hsys,
+ struct DerivedMesh *scalp,
+ int subdiv,
+ const DRWHairFiberTextureBuffer **r_buffer)
+{
+ HairBatchCache *cache = hair_batch_cache_get(hsys);
+
+ TIMEIT_START(DRW_hair_batch_cache_get_fibers);
+
+ if (cache->fibers == NULL) {
+ TIMEIT_BENCH(hair_batch_cache_ensure_fibers(hsys, subdiv, cache),
+ hair_batch_cache_ensure_fibers);
+
+ TIMEIT_BENCH(cache->fibers = GWN_batch_create(GWN_PRIM_TRIS, cache->fiber_verts, cache->fiber_edges),
+ GWN_batch_create);
+
+ TIMEIT_BENCH(hair_batch_cache_ensure_fiber_texbuffer(hsys, scalp, subdiv, cache),
+ hair_batch_cache_ensure_fiber_texbuffer);
+ }
+
+ if (r_buffer) {
+ *r_buffer = &cache->texbuffer;
+ }
+
+ TIMEIT_END(DRW_hair_batch_cache_get_fibers);
+
+ return cache->fibers;
+}
+
+static void hair_batch_cache_ensure_follicles(
+ HairSystem *hsys,
+ struct DerivedMesh *scalp,
+ eHairDrawFollicleMode mode,
+ HairBatchCache *cache)
+{
+ GWN_VERTBUF_DISCARD_SAFE(cache->follicle_verts);
+ GWN_INDEXBUF_DISCARD_SAFE(cache->follicle_edges);
+
+ const HairPattern *pattern = hsys->pattern;
+
+ static Gwn_VertFormat format = { 0 };
+ static unsigned pos_id;
+
+ /* initialize vertex format */
+ if (format.attrib_ct == 0) {
+ pos_id = GWN_vertformat_attr_add(&format, "pos", GWN_COMP_F32, 3, GWN_FETCH_FLOAT);
+ }
+
+ cache->follicle_verts = GWN_vertbuf_create_with_format(&format);
+
+ GWN_vertbuf_data_alloc(cache->follicle_verts, pattern->num_follicles);
+
+ HairFollicle *follicle = pattern->follicles;
+ for (int i = 0; i < pattern->num_follicles; ++i, ++follicle) {
+ float co[3], nor[3], tang[3];
+ BKE_mesh_sample_eval(scalp, &follicle->mesh_sample, co, nor, tang);
+
+ GWN_vertbuf_attr_set(cache->follicle_verts, pos_id, (unsigned int)i, co);
+ }
+}
+
+Gwn_Batch *DRW_hair_batch_cache_get_follicle_points(
+ HairSystem *hsys,
+ struct DerivedMesh *scalp)
+{
+ HairBatchCache *cache = hair_batch_cache_get(hsys);
+
+ if (cache->follicles == NULL) {
+ hair_batch_cache_ensure_follicles(hsys, scalp, HAIR_DRAW_FOLLICLE_POINTS, cache);
+
+ cache->follicles = GWN_batch_create(GWN_PRIM_POINTS, cache->follicle_verts, NULL);
+ }
+
+ return cache->follicles;
+
+}
+
+Gwn_Batch *DRW_hair_batch_cache_get_follicle_axes(
+ HairSystem *hsys,
+ struct DerivedMesh *scalp)
+{
+ return NULL;
+}
+
+Gwn_Batch *DRW_hair_batch_cache_get_guide_curve_points(
+ HairSystem *hsys,
+ struct DerivedMesh *scalp,
+ int subdiv)
+{
+ return NULL;
+}
+
+Gwn_Batch *DRW_hair_batch_cache_get_guide_curve_edges(
+ HairSystem *hsys,
+ struct DerivedMesh *scalp,
+ int subdiv)
+{
+ return NULL;
+}
diff --git a/source/blender/draw/intern/draw_common.h b/source/blender/draw/intern/draw_common.h
index ec646693207..3d498cba8b6 100644
--- a/source/blender/draw/intern/draw_common.h
+++ b/source/blender/draw/intern/draw_common.h
@@ -26,10 +26,15 @@
#ifndef __DRAW_COMMON_H__
#define __DRAW_COMMON_H__
+struct DerivedMesh;
struct DRWPass;
struct DRWShadingGroup;
struct Gwn_Batch;
+struct GPUTexture;
+struct HairDrawSettings;
+struct HairSystem;
struct Object;
+struct Scene;
struct ViewLayer;
/* Used as ubo but colors can be directly referenced as well */
@@ -145,4 +150,27 @@ void DRW_shgroup_armature_edit(
bool DRW_pose_mode_armature(
struct Object *ob, struct Object *active_ob);
+/* hair drawing */
+typedef struct DRWHairFiberTextureBuffer {
+ void *data;
+ int strand_map_start;
+ int strand_vertex_start;
+ int fiber_start;
+ int width;
+ int height;
+} DRWHairFiberTextureBuffer;
+
+const char* DRW_hair_shader_defines(void);
+
+void DRW_hair_shader_uniforms(struct DRWShadingGroup *shgrp, struct Scene *scene,
+ struct GPUTexture **fibertex, const struct DRWHairFiberTextureBuffer *texbuffer);
+
+void DRW_shgroup_hair(
+ struct Object *ob,
+ struct HairSystem *hsys,
+ struct HairDrawSettings *draw_settings,
+ struct DerivedMesh *scalp,
+ struct DRWShadingGroup *shgrp_verts,
+ struct DRWShadingGroup *shgrp_edges);
+
#endif /* __DRAW_COMMON_H__ */
diff --git a/source/blender/draw/intern/draw_hair.c b/source/blender/draw/intern/draw_hair.c
new file mode 100644
index 00000000000..8f61c46479c
--- /dev/null
+++ b/source/blender/draw/intern/draw_hair.c
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2016, Blender Foundation.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Contributor(s): Blender Institute
+ *
+ */
+
+/** \file draw_hair.c
+ * \ingroup draw
+ */
+
+#include "BLI_utildefines.h"
+
+#include "DNA_hair_types.h"
+#include "DNA_scene_types.h"
+
+#include "BKE_hair.h"
+
+#include "DRW_render.h"
+
+#include "GPU_extensions.h"
+#include "GPU_texture.h"
+
+#include "draw_common.h"
+
+const char* DRW_hair_shader_defines(void)
+{
+ static char str[256];
+
+ BLI_snprintf(str, sizeof(str), "#define HAIR_SHADER_FIBERS\n#define HAIR_SHADER_TEX_WIDTH %d\n",
+ GPU_max_texture_size());
+
+ return str;
+}
+
+void DRW_hair_shader_uniforms(DRWShadingGroup *shgrp, Scene *UNUSED(scene),
+ GPUTexture **fibertex, const DRWHairFiberTextureBuffer *texbuffer)
+{
+ DRW_shgroup_uniform_vec2(shgrp, "viewport_size", DRW_viewport_size_get(), 1);
+ //DRW_shgroup_uniform_float(shgrp, "ribbon_width", &tsettings->hair_draw_size, 1);
+ static float test = 2.5f;
+ DRW_shgroup_uniform_float(shgrp, "ribbon_width", &test, 1);
+
+ DRW_shgroup_uniform_texture_ref(shgrp, "fiber_data", fibertex);
+ DRW_shgroup_uniform_int(shgrp, "strand_map_start", &texbuffer->strand_map_start, 1);
+ DRW_shgroup_uniform_int(shgrp, "strand_vertex_start", &texbuffer->strand_vertex_start, 1);
+ DRW_shgroup_uniform_int(shgrp, "fiber_start", &texbuffer->fiber_start, 1);
+}
+
+void DRW_shgroup_hair(
+ Object *ob,
+ HairSystem *hsys,
+ HairDrawSettings *draw_settings,
+ struct DerivedMesh *scalp,
+ DRWShadingGroup *shgrp_verts,
+ DRWShadingGroup *UNUSED(shgrp_edges))
+{
+ switch (draw_settings->follicle_mode)
+ {
+ case HAIR_DRAW_FOLLICLE_NONE:
+ break;
+ case HAIR_DRAW_FOLLICLE_POINTS:
+ {
+ struct Gwn_Batch *geom = DRW_cache_hair_get_follicle_points(hsys, scalp);
+ DRW_shgroup_call_add(shgrp_verts, geom, ob->obmat);
+ break;
+ }
+ case HAIR_DRAW_FOLLICLE_AXES:
+ {
+ break;
+ }
+ }
+}
diff --git a/source/blender/draw/intern/draw_manager.c b/source/blender/draw/intern/draw_manager.c
index 8090bcb28ca..752fc52b9b7 100644
--- a/source/blender/draw/intern/draw_manager.c
+++ b/source/blender/draw/intern/draw_manager.c
@@ -947,6 +947,9 @@ static void drw_engines_enable_from_mode(int mode)
case CTX_MODE_EDIT_LATTICE:
use_drw_engine(&draw_engine_edit_lattice_type);
break;
+ case CTX_MODE_EDIT_GROOM:
+ use_drw_engine(&draw_engine_edit_groom_type);
+ break;
case CTX_MODE_POSE:
use_drw_engine(&draw_engine_pose_type);
break;
@@ -1927,6 +1930,7 @@ void DRW_engines_register(void)
DRW_engine_register(&draw_engine_object_type);
DRW_engine_register(&draw_engine_edit_armature_type);
DRW_engine_register(&draw_engine_edit_curve_type);
+ DRW_engine_register(&draw_engine_edit_groom_type);
DRW_engine_register(&draw_engine_edit_lattice_type);
DRW_engine_register(&draw_engine_edit_mesh_type);
DRW_engine_register(&draw_engine_edit_metaball_type);
@@ -1956,6 +1960,12 @@ void DRW_engines_register(void)
/* BKE: particle.c */
extern void *BKE_particle_batch_cache_dirty_cb;
extern void *BKE_particle_batch_cache_free_cb;
+ /* BKE: groom.c */
+ extern void *BKE_groom_batch_cache_dirty_cb;
+ extern void *BKE_groom_batch_cache_free_cb;
+ /* BKE: hair.c */
+ extern void *BKE_hair_batch_cache_dirty_cb;
+ extern void *BKE_hair_batch_cache_free_cb;
BKE_mball_batch_cache_dirty_cb = DRW_mball_batch_cache_dirty;
BKE_mball_batch_cache_free_cb = DRW_mball_batch_cache_free;
@@ -1971,6 +1981,12 @@ void DRW_engines_register(void)
BKE_particle_batch_cache_dirty_cb = DRW_particle_batch_cache_dirty;
BKE_particle_batch_cache_free_cb = DRW_particle_batch_cache_free;
+
+ BKE_groom_batch_cache_dirty_cb = DRW_groom_batch_cache_dirty;
+ BKE_groom_batch_cache_free_cb = DRW_groom_batch_cache_free;
+
+ BKE_hair_batch_cache_dirty_cb = DRW_hair_batch_cache_dirty;
+ BKE_hair_batch_cache_free_cb = DRW_hair_batch_cache_free;
}
}
diff --git a/source/blender/draw/modes/draw_mode_engines.h b/source/blender/draw/modes/draw_mode_engines.h
index 23fedbba5a5..e0f50312c7f 100644
--- a/source/blender/draw/modes/draw_mode_engines.h
+++ b/source/blender/draw/modes/draw_mode_engines.h
@@ -29,6 +29,7 @@
extern DrawEngineType draw_engine_object_type;
extern DrawEngineType draw_engine_edit_armature_type;
extern DrawEngineType draw_engine_edit_curve_type;
+extern DrawEngineType draw_engine_edit_groom_type;
extern DrawEngineType draw_engine_edit_lattice_type;
extern DrawEngineType draw_engine_edit_mesh_type;
extern DrawEngineType draw_engine_edit_metaball_type;
@@ -41,4 +42,4 @@ extern DrawEngineType draw_engine_particle_type;
extern DrawEngineType draw_engine_pose_type;
extern DrawEngineType draw_engine_sculpt_type;
-#endif /* __DRAW_MODE_ENGINES_H__ */ \ No newline at end of file
+#endif /* __DRAW_MODE_ENGINES_H__ */
diff --git a/source/blender/draw/modes/edit_groom_mode.c b/source/blender/draw/modes/edit_groom_mode.c
new file mode 100644
index 00000000000..d12f918c0bc
--- /dev/null
+++ b/source/blender/draw/modes/edit_groom_mode.c
@@ -0,0 +1,304 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) Blender Foundation
+ * All rights reserved.
+ *
+ * The Original Code is: all of this file.
+ *
+ * Contributor(s): Lukas Toenne
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file blender/draw/modes/edit_groom_mode.c
+ * \ingroup draw
+ */
+
+#include "DRW_engine.h"
+#include "DRW_render.h"
+
+/* If builtin shaders are needed */
+#include "GPU_shader.h"
+
+#include "draw_common.h"
+
+#include "draw_mode_engines.h"
+
+/* If needed, contains all global/Theme colors
+ * Add needed theme colors / values to DRW_globals_update() and update UBO
+ * Not needed for constant color. */
+extern struct GPUUniformBuffer *globals_ubo; /* draw_common.c */
+extern struct GlobalsUboStorage ts; /* draw_common.c */
+
+extern char datatoc_common_globals_lib_glsl[];
+extern char datatoc_edit_groom_overlay_loosevert_vert_glsl[];
+extern char datatoc_edit_groom_overlay_frag_glsl[];
+
+extern char datatoc_gpu_shader_3D_vert_glsl[];
+extern char datatoc_gpu_shader_uniform_color_frag_glsl[];
+extern char datatoc_gpu_shader_point_uniform_color_frag_glsl[];
+
+/* *********** LISTS *********** */
+/* All lists are per viewport specific datas.
+ * They are all free when viewport changes engines
+ * or is free itself. Use EDIT_GROOM_engine_init() to
+ * initialize most of them and EDIT_GROOM_cache_init()
+ * for EDIT_GROOM_PassList */
+
+typedef struct EDIT_GROOM_PassList {
+ /* Declare all passes here and init them in
+ * EDIT_GROOM_cache_init().
+ * Only contains (DRWPass *) */
+ struct DRWPass *wire_pass;
+ struct DRWPass *vert_pass;
+} EDIT_GROOM_PassList;
+
+typedef struct EDIT_GROOM_FramebufferList {
+ /* Contains all framebuffer objects needed by this engine.
+ * Only contains (GPUFrameBuffer *) */
+ struct GPUFrameBuffer *fb;
+} EDIT_GROOM_FramebufferList;
+
+typedef struct EDIT_GROOM_TextureList {
+ /* Contains all framebuffer textures / utility textures
+ * needed by this engine. Only viewport specific textures
+ * (not per object). Only contains (GPUTexture *) */
+ struct GPUTexture *texture;
+} EDIT_GROOM_TextureList;
+
+typedef struct EDIT_GROOM_StorageList {
+ /* Contains any other memory block that the engine needs.
+ * Only directly MEM_(m/c)allocN'ed blocks because they are
+ * free with MEM_freeN() when viewport is freed.
+ * (not per object) */
+ struct CustomStruct *block;
+ struct EDIT_GROOM_PrivateData *g_data;
+} EDIT_GROOM_StorageList;
+
+typedef struct EDIT_GROOM_Data {
+ /* Struct returned by DRW_viewport_engine_data_ensure.
+ * If you don't use one of these, just make it a (void *) */
+ // void *fbl;
+ void *engine_type; /* Required */
+ EDIT_GROOM_FramebufferList *fbl;
+ EDIT_GROOM_TextureList *txl;
+ EDIT_GROOM_PassList *psl;
+ EDIT_GROOM_StorageList *stl;
+} EDIT_GROOM_Data;
+
+/* *********** STATIC *********** */
+
+static struct {
+ /* Custom shaders :
+ * Add sources to source/blender/draw/modes/shaders
+ * init in EDIT_GROOM_engine_init();
+ * free in EDIT_GROOM_engine_free(); */
+ GPUShader *wire_sh;
+
+ GPUShader *overlay_vert_sh;
+
+} e_data = {NULL}; /* Engine data */
+
+typedef struct EDIT_GROOM_PrivateData {
+ /* This keeps the references of the shading groups for
+ * easy access in EDIT_GROOM_cache_populate() */
+ DRWShadingGroup *wire_shgrp;
+ DRWShadingGroup *vert_shgrp;
+} EDIT_GROOM_PrivateData; /* Transient data */
+
+/* *********** FUNCTIONS *********** */
+
+/* Init Textures, Framebuffers, Storage and Shaders.
+ * It is called for every frames.
+ * (Optional) */
+static void EDIT_GROOM_engine_init(void *vedata)
+{
+ EDIT_GROOM_TextureList *txl = ((EDIT_GROOM_Data *)vedata)->txl;
+ EDIT_GROOM_FramebufferList *fbl = ((EDIT_GROOM_Data *)vedata)->fbl;
+ EDIT_GROOM_StorageList *stl = ((EDIT_GROOM_Data *)vedata)->stl;
+
+ UNUSED_VARS(txl, fbl, stl);
+
+ /* Init Framebuffers like this: order is attachment order (for color texs) */
+ /*
+ * DRWFboTexture tex[2] = {{&txl->depth, DRW_TEX_DEPTH_24, 0},
+ * {&txl->color, DRW_TEX_RGBA_8, DRW_TEX_FILTER}};
+ */
+
+ /* DRW_framebuffer_init takes care of checking if
+ * the framebuffer is valid and has the right size*/
+ /*
+ * float *viewport_size = DRW_viewport_size_get();
+ * DRW_framebuffer_init(&fbl->occlude_wire_fb,
+ * (int)viewport_size[0], (int)viewport_size[1],
+ * tex, 2);
+ */
+
+ if (!e_data.wire_sh) {
+ e_data.wire_sh = GPU_shader_get_builtin_shader(GPU_SHADER_3D_SMOOTH_COLOR);
+ }
+
+ if (!e_data.overlay_vert_sh) {
+ e_data.overlay_vert_sh = DRW_shader_create_with_lib(
+ datatoc_edit_groom_overlay_loosevert_vert_glsl, NULL,
+ datatoc_edit_groom_overlay_frag_glsl,
+ datatoc_common_globals_lib_glsl, NULL);
+ }
+}
+
+/* Here init all passes and shading groups
+ * Assume that all Passes are NULL */
+static void EDIT_GROOM_cache_init(void *vedata)
+{
+ EDIT_GROOM_PassList *psl = ((EDIT_GROOM_Data *)vedata)->psl;
+ EDIT_GROOM_StorageList *stl = ((EDIT_GROOM_Data *)vedata)->stl;
+
+ if (!stl->g_data) {
+ /* Alloc transient pointers */
+ stl->g_data = MEM_mallocN(sizeof(*stl->g_data), __func__);
+ }
+
+ {
+ psl->wire_pass = DRW_pass_create(
+ "Groom Wire",
+ DRW_STATE_WRITE_COLOR | DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_LESS | DRW_STATE_WIRE);
+ stl->g_data->wire_shgrp = DRW_shgroup_create(e_data.wire_sh, psl->wire_pass);
+
+ psl->vert_pass = DRW_pass_create(
+ "Groom Verts",
+ DRW_STATE_WRITE_COLOR | DRW_STATE_WRITE_DEPTH | DRW_STATE_POINT);
+ stl->g_data->vert_shgrp = DRW_shgroup_create(e_data.overlay_vert_sh, psl->vert_pass);
+
+ DRW_shgroup_uniform_block(stl->g_data->vert_shgrp, "globalsBlock", globals_ubo);
+ }
+}
+
+/* Add geometry to shadingGroups. Execute for each objects */
+static void EDIT_GROOM_cache_populate(void *vedata, Object *ob)
+{
+ EDIT_GROOM_PassList *psl = ((EDIT_GROOM_Data *)vedata)->psl;
+ EDIT_GROOM_StorageList *stl = ((EDIT_GROOM_Data *)vedata)->stl;
+ const DRWContextState *draw_ctx = DRW_context_state_get();
+ Scene *scene = draw_ctx->scene;
+ Object *obedit = draw_ctx->object_edit;
+ GroomEditSettings *editsettings = &scene->toolsettings->groom_edit_settings;
+
+ UNUSED_VARS(psl);
+
+ if (ob->type == OB_GROOM) {
+ if (ob == obedit) {
+ /* Get geometry cache */
+ struct Gwn_Batch *geom;
+
+ geom = DRW_cache_groom_wire_get(ob);
+ DRW_shgroup_call_add(stl->g_data->wire_shgrp, geom, ob->obmat);
+
+ geom = DRW_cache_groom_vert_overlay_get(ob, editsettings->mode);
+ DRW_shgroup_call_add(stl->g_data->vert_shgrp, geom, ob->obmat);
+ }
+ }
+}
+
+/* Optional: Post-cache_populate callback */
+static void EDIT_GROOM_cache_finish(void *vedata)
+{
+ EDIT_GROOM_PassList *psl = ((EDIT_GROOM_Data *)vedata)->psl;
+ EDIT_GROOM_StorageList *stl = ((EDIT_GROOM_Data *)vedata)->stl;
+
+ /* Do something here! dependant on the objects gathered */
+ UNUSED_VARS(psl, stl);
+}
+
+/* Draw time ! Control rendering pipeline from here */
+static void EDIT_GROOM_draw_scene(void *vedata)
+{
+ EDIT_GROOM_PassList *psl = ((EDIT_GROOM_Data *)vedata)->psl;
+ EDIT_GROOM_FramebufferList *fbl = ((EDIT_GROOM_Data *)vedata)->fbl;
+
+ /* Default framebuffer and texture */
+ DefaultFramebufferList *dfbl = DRW_viewport_framebuffer_list_get();
+ DefaultTextureList *dtxl = DRW_viewport_texture_list_get();
+
+ UNUSED_VARS(fbl, dtxl);
+
+ MULTISAMPLE_SYNC_ENABLE(dfbl)
+
+ /* Show / hide entire passes, swap framebuffers ... whatever you fancy */
+ /*
+ * DRW_framebuffer_texture_detach(dtxl->depth);
+ * DRW_framebuffer_bind(fbl->custom_fb);
+ * DRW_draw_pass(psl->pass);
+ * DRW_framebuffer_texture_attach(dfbl->default_fb, dtxl->depth, 0, 0);
+ * DRW_framebuffer_bind(dfbl->default_fb);
+ */
+
+ /* ... or just render passes on default framebuffer. */
+ DRW_draw_pass(psl->wire_pass);
+ DRW_draw_pass(psl->vert_pass);
+
+ MULTISAMPLE_SYNC_DISABLE(dfbl)
+
+ /* If you changed framebuffer, double check you rebind
+ * the default one with its textures attached before finishing */
+}
+
+/* Cleanup when destroying the engine.
+ * This is not per viewport ! only when quitting blender.
+ * Mostly used for freeing shaders */
+static void EDIT_GROOM_engine_free(void)
+{
+ // Currently built-in, dont free
+ DRW_SHADER_FREE_SAFE(e_data.overlay_vert_sh);
+}
+
+/* Create collection settings here.
+ *
+ * Be sure to add this function there :
+ * source/blender/draw/DRW_engine.h
+ * source/blender/blenkernel/intern/layer.c
+ * source/blenderplayer/bad_level_call_stubs/stubs.c
+ *
+ * And relevant collection settings to :
+ * source/blender/makesrna/intern/rna_scene.c
+ * source/blender/blenkernel/intern/layer.c
+ */
+#if 0
+void EDIT_GROOM_collection_settings_create(CollectionEngineSettings *ces)
+{
+ BLI_assert(ces);
+ // BKE_collection_engine_property_add_int(ces, "my_bool_prop", false);
+ // BKE_collection_engine_property_add_int(ces, "my_int_prop", 0);
+ // BKE_collection_engine_property_add_float(ces, "my_float_prop", 0.0f);
+}
+#endif
+
+static const DrawEngineDataSize EDIT_GROOM_data_size = DRW_VIEWPORT_DATA_SIZE(EDIT_GROOM_Data);
+
+DrawEngineType draw_engine_edit_groom_type = {
+ NULL, NULL,
+ N_("EditGroomMode"),
+ &EDIT_GROOM_data_size,
+ &EDIT_GROOM_engine_init,
+ &EDIT_GROOM_engine_free,
+ &EDIT_GROOM_cache_init,
+ &EDIT_GROOM_cache_populate,
+ &EDIT_GROOM_cache_finish,
+ NULL, /* draw_background but not needed by mode engines */
+ &EDIT_GROOM_draw_scene,
+ NULL,
+};
diff --git a/source/blender/draw/modes/object_mode.c b/source/blender/draw/modes/object_mode.c
index 939507a4f21..52db4092fbc 100644
--- a/source/blender/draw/modes/object_mode.c
+++ b/source/blender/draw/modes/object_mode.c
@@ -30,8 +30,10 @@
#include "DNA_armature_types.h"
#include "DNA_camera_types.h"
#include "DNA_curve_types.h"
+#include "DNA_groom_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meta_types.h"
+#include "DNA_modifier_types.h"
#include "DNA_object_force_types.h"
#include "DNA_lightprobe_types.h"
#include "DNA_particle_types.h"
@@ -44,7 +46,9 @@
#include "BKE_camera.h"
#include "BKE_curve.h"
#include "BKE_global.h"
+#include "BKE_groom.h"
#include "BKE_mball.h"
+#include "BKE_modifier.h"
#include "BKE_object.h"
#include "BKE_particle.h"
#include "BKE_image.h"
@@ -100,6 +104,7 @@ typedef struct OBJECT_PassList {
struct DRWPass *bone_wire;
struct DRWPass *bone_envelope;
struct DRWPass *particle;
+ struct DRWPass *hair;
struct DRWPass *lightprobes;
/* use for empty/background images */
struct DRWPass *reference_image;
@@ -223,6 +228,10 @@ typedef struct OBJECT_PrivateData {
DRWShadingGroup *points_select;
DRWShadingGroup *points_select_group;
DRWShadingGroup *points_transform;
+
+ /* Hair Systems */
+ DRWShadingGroup *hair_verts;
+ DRWShadingGroup *hair_edges;
} OBJECT_PrivateData; /* Transient data */
static struct {
@@ -1172,6 +1181,24 @@ static void OBJECT_cache_init(void *vedata)
}
{
+ /* Hair */
+ psl->hair = DRW_pass_create(
+ "Hair Pass",
+ DRW_STATE_WRITE_COLOR | DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_LESS | DRW_STATE_BLEND |
+ DRW_STATE_POINT | DRW_STATE_WIRE);
+
+ GPUShader *sh_verts = GPU_shader_get_builtin_shader(GPU_SHADER_3D_POINT_UNIFORM_SIZE_UNIFORM_COLOR_AA);
+ stl->g_data->hair_verts = DRW_shgroup_create(sh_verts, psl->hair);
+ DRW_shgroup_uniform_vec4(stl->g_data->hair_verts, "color", ts.colorVertex, 1);
+ DRW_shgroup_uniform_float(stl->g_data->hair_verts, "size", &ts.sizeVertex, 1);
+ DRW_shgroup_state_enable(stl->g_data->hair_verts, DRW_STATE_POINT);
+
+ GPUShader *sh_edges = GPU_shader_get_builtin_shader(GPU_SHADER_3D_UNIFORM_COLOR);
+ stl->g_data->hair_edges = DRW_shgroup_create(sh_edges, psl->hair);
+ DRW_shgroup_uniform_vec4(stl->g_data->hair_edges, "color", ts.colorWire, 1);
+ }
+
+ {
/* Empty/Background Image Pass */
psl->reference_image = DRW_pass_create(
"Refrence Image Pass",
@@ -1973,6 +2000,21 @@ static void OBJECT_cache_populate(void *vedata, Object *ob)
}
break;
}
+ case OB_GROOM:
+ {
+ if (ob != draw_ctx->object_edit) {
+ Groom *groom = ob->data;
+ struct Gwn_Batch *geom = DRW_cache_groom_wire_get(ob);
+ if (theme_id == TH_UNDEFINED) {
+ theme_id = DRW_object_wire_theme_get(ob, view_layer, NULL);
+ }
+ DRWShadingGroup *shgroup = shgroup_theme_id_to_wire_or(stl, theme_id, stl->g_data->wire);
+ DRW_shgroup_call_add(shgroup, geom, ob->obmat);
+
+ DRW_shgroup_hair(ob, groom->hair_system, groom->hair_draw_settings, BKE_groom_get_scalp(groom), stl->g_data->hair_verts, stl->g_data->hair_edges);
+ }
+ break;
+ }
case OB_LAMP:
DRW_shgroup_lamp(stl, ob, view_layer);
break;
@@ -2004,6 +2046,27 @@ static void OBJECT_cache_populate(void *vedata, Object *ob)
break;
}
+ {
+ struct DerivedMesh *scalp = ob->derivedFinal;
+ if (scalp)
+ {
+ for (ModifierData *md = ob->modifiers.first; md; md = md->next)
+ {
+ if (md->type == eModifierType_Fur)
+ {
+ FurModifierData *fmd = (FurModifierData*)md;
+
+ if (!modifier_isEnabled(draw_ctx->scene, md, eModifierMode_Realtime))
+ {
+ continue;
+ }
+
+ DRW_shgroup_hair(ob, fmd->hair_system, fmd->draw_settings, scalp, stl->g_data->hair_verts, stl->g_data->hair_edges);
+ }
+ }
+ }
+ }
+
if (ob->pd && ob->pd->forcefield) {
DRW_shgroup_forcefield(stl, ob, view_layer);
}
@@ -2081,6 +2144,7 @@ static void OBJECT_draw_scene(void *vedata)
DRW_draw_pass(psl->bone_solid);
DRW_draw_pass(psl->non_meshes);
DRW_draw_pass(psl->particle);
+ DRW_draw_pass(psl->hair);
DRW_draw_pass(psl->reference_image);
MULTISAMPLE_SYNC_DISABLE(dfbl)
diff --git a/source/blender/draw/modes/shaders/edit_groom_overlay_frag.glsl b/source/blender/draw/modes/shaders/edit_groom_overlay_frag.glsl
new file mode 100644
index 00000000000..e78462b6915
--- /dev/null
+++ b/source/blender/draw/modes/shaders/edit_groom_overlay_frag.glsl
@@ -0,0 +1,22 @@
+
+flat in int vertFlag;
+
+#define VERTEX_SELECTED (1 << 0)
+#define VERTEX_ACTIVE (1 << 1)
+
+out vec4 FragColor;
+
+void main()
+{
+ /* TODO: vertex size */
+
+ if ((vertFlag & VERTEX_SELECTED) != 0) {
+ FragColor = colorVertexSelect;
+ }
+ else if ((vertFlag & VERTEX_ACTIVE) != 0) {
+ FragColor = colorEditMeshActive;
+ }
+ else {
+ FragColor = colorVertex;
+ }
+}
diff --git a/source/blender/draw/modes/shaders/edit_groom_overlay_loosevert_vert.glsl b/source/blender/draw/modes/shaders/edit_groom_overlay_loosevert_vert.glsl
new file mode 100644
index 00000000000..6a26ad70524
--- /dev/null
+++ b/source/blender/draw/modes/shaders/edit_groom_overlay_loosevert_vert.glsl
@@ -0,0 +1,39 @@
+
+/* Draw Groom Vertices */
+
+uniform mat4 ModelViewProjectionMatrix;
+uniform vec2 viewportSize;
+
+in vec3 pos;
+in int data;
+
+/* these are the same for all vertices
+ * and does not need interpolation */
+flat out int vertFlag;
+flat out int clipCase;
+
+/* See fragment shader */
+noperspective out vec4 eData1;
+flat out vec4 eData2;
+
+/* project to screen space */
+vec2 proj(vec4 pos)
+{
+ return (0.5 * (pos.xy / pos.w) + 0.5) * viewportSize;
+}
+
+void main()
+{
+ clipCase = 0;
+
+ vec4 pPos = ModelViewProjectionMatrix * vec4(pos, 1.0);
+
+ /* only vertex position 0 is used */
+ eData1 = eData2 = vec4(1e10);
+ eData2.zw = proj(pPos);
+
+ vertFlag = data;
+
+ gl_PointSize = sizeVertex;
+ gl_Position = pPos;
+}
diff --git a/source/blender/editors/CMakeLists.txt b/source/blender/editors/CMakeLists.txt
index 06f412b7019..e626636e1ab 100644
--- a/source/blender/editors/CMakeLists.txt
+++ b/source/blender/editors/CMakeLists.txt
@@ -27,6 +27,7 @@ if(WITH_BLENDER)
add_subdirectory(armature)
add_subdirectory(curve)
add_subdirectory(gpencil)
+ add_subdirectory(groom)
add_subdirectory(interface)
add_subdirectory(io)
add_subdirectory(lattice)
diff --git a/source/blender/editors/groom/CMakeLists.txt b/source/blender/editors/groom/CMakeLists.txt
new file mode 100644
index 00000000000..e145faf96fc
--- /dev/null
+++ b/source/blender/editors/groom/CMakeLists.txt
@@ -0,0 +1,50 @@
+# ***** BEGIN GPL LICENSE BLOCK *****
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# Contributor(s): Lukas Toenne.
+#
+# ***** END GPL LICENSE BLOCK *****
+
+set(INC
+ ../include
+ ../../blenkernel
+ ../../blenlib
+ ../../blentranslation
+ ../../depsgraph
+ ../../makesdna
+ ../../makesrna
+ ../../windowmanager
+ ../../../../intern/guardedalloc
+)
+
+set(INC_SYS
+)
+
+set(SRC
+ groom_hair.c
+ groom_ops.c
+ editgroom.c
+ editgroom_region.c
+ editgroom_select.c
+
+ groom_intern.h
+)
+
+if(WITH_INTERNATIONAL)
+ add_definitions(-DWITH_INTERNATIONAL)
+endif()
+
+blender_add_lib(bf_editor_groom "${SRC}" "${INC}" "${INC_SYS}")
diff --git a/source/blender/editors/groom/editgroom.c b/source/blender/editors/groom/editgroom.c
new file mode 100644
index 00000000000..47f5d40c09b
--- /dev/null
+++ b/source/blender/editors/groom/editgroom.c
@@ -0,0 +1,137 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) Blender Foundation
+ * All rights reserved.
+ *
+ * The Original Code is: all of this file.
+ *
+ * Contributor(s): Lukas Toenne
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file blender/editors/groom/editgroom.c
+ * \ingroup edgroom
+ */
+
+#include "DNA_groom_types.h"
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_array_utils.h"
+#include "BLI_blenlib.h"
+
+#include "BKE_context.h"
+#include "BKE_global.h"
+#include "BKE_groom.h"
+#include "BKE_library.h"
+#include "BKE_main.h"
+#include "BKE_report.h"
+
+#include "DEG_depsgraph.h"
+#include "DEG_depsgraph_build.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "ED_groom.h"
+#include "ED_object.h"
+#include "ED_screen.h"
+#include "ED_types.h"
+#include "ED_util.h"
+
+#include "groom_intern.h"
+
+#include "UI_interface.h"
+#include "UI_resources.h"
+
+#include "RNA_access.h"
+#include "RNA_define.h"
+#include "RNA_enum_types.h"
+
+/********************** Load/Make/Free ********************/
+
+static void groom_bundles_free(ListBase *bundles)
+{
+ for (GroomBundle *bundle = bundles->first; bundle; bundle = bundle->next)
+ {
+ BKE_groom_bundle_curve_cache_clear(bundle);
+
+ if (bundle->verts)
+ {
+ MEM_freeN(bundle->verts);
+ }
+ if (bundle->sections)
+ {
+ MEM_freeN(bundle->sections);
+ }
+ }
+ BLI_freelistN(bundles);
+}
+
+static void groom_bundles_copy(ListBase *bundles_dst, ListBase *bundles_src)
+{
+ BLI_duplicatelist(bundles_dst, bundles_src);
+ for (GroomBundle *bundle = bundles_dst->first; bundle; bundle = bundle->next)
+ {
+ if (bundle->curvecache)
+ {
+ bundle->curvecache = MEM_dupallocN(bundle->curvecache);
+ }
+ if (bundle->sections)
+ {
+ bundle->sections = MEM_dupallocN(bundle->sections);
+ }
+ if (bundle->verts)
+ {
+ bundle->verts = MEM_dupallocN(bundle->verts);
+ }
+ }
+}
+
+void ED_groom_editgroom_make(Object *obedit)
+{
+ Groom *groom = obedit->data;
+
+ ED_groom_editgroom_free(obedit);
+
+ groom->editgroom = MEM_callocN(sizeof(EditGroom), "editgroom");
+ groom_bundles_copy(&groom->editgroom->bundles, &groom->bundles);
+}
+
+void ED_groom_editgroom_load(Object *obedit)
+{
+ Groom *groom = obedit->data;
+
+ groom_bundles_free(&groom->bundles);
+ groom_bundles_copy(&groom->bundles, &groom->editgroom->bundles);
+}
+
+void ED_groom_editgroom_free(Object *ob)
+{
+ Groom *groom = ob->data;
+
+ if (groom->editgroom) {
+ groom_bundles_free(&groom->editgroom->bundles);
+
+ MEM_freeN(groom->editgroom);
+ groom->editgroom = NULL;
+ }
+}
diff --git a/source/blender/editors/groom/editgroom_region.c b/source/blender/editors/groom/editgroom_region.c
new file mode 100644
index 00000000000..f138eff322a
--- /dev/null
+++ b/source/blender/editors/groom/editgroom_region.c
@@ -0,0 +1,194 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) Blender Foundation
+ * All rights reserved.
+ *
+ * The Original Code is: all of this file.
+ *
+ * Contributor(s): Lukas Toenne
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file blender/editors/groom/editgroom_region.c
+ * \ingroup edgroom
+ */
+
+#include "DNA_groom_types.h"
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_blenlib.h"
+#include "BLI_math.h"
+
+#include "BLT_translation.h"
+
+#include "BKE_context.h"
+#include "BKE_groom.h"
+#include "BKE_library.h"
+
+#include "DEG_depsgraph.h"
+
+#include "RNA_access.h"
+#include "RNA_define.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "ED_object.h"
+#include "ED_screen.h"
+#include "ED_util.h"
+#include "ED_view3d.h"
+#include "ED_groom.h"
+
+#include "groom_intern.h"
+
+/* GROOM_OT_region_add */
+
+static void groom_bundle_section_init(
+ GroomSection *section,
+ GroomSectionVertex *verts,
+ int numverts,
+ float mat[4][4],
+ float x,
+ float y,
+ float z)
+{
+ section->center[0] = x;
+ section->center[1] = y;
+ section->center[2] = z;
+ mul_m4_v3(mat, section->center);
+
+ {
+ const float radius = 0.5f;
+ GroomSectionVertex *vertex = verts;
+ for (int i = 0; i < numverts; ++i, ++vertex)
+ {
+ float angle = 2*M_PI * (float)i / (float)numverts;
+ vertex->co[0] = cos(angle) * radius;
+ vertex->co[1] = sin(angle) * radius;
+ }
+ }
+}
+
+static GroomBundle* groom_add_bundle(float mat[4][4])
+{
+ GroomBundle *bundle = MEM_callocN(sizeof(GroomBundle), "groom bundle");
+
+ bundle->numshapeverts = 6;
+ bundle->totsections = 4;
+ bundle->totverts = bundle->numshapeverts * bundle->totsections;
+ bundle->sections = MEM_mallocN(sizeof(GroomSection) * bundle->totsections, "groom bundle sections");
+ bundle->verts = MEM_mallocN(sizeof(GroomSectionVertex) * bundle->totverts, "groom bundle vertices");
+
+ int numverts = bundle->numshapeverts;
+ groom_bundle_section_init(&bundle->sections[0], &bundle->verts[numverts * 0], numverts, mat, 0.0, 0.0, 0.0);
+ groom_bundle_section_init(&bundle->sections[1], &bundle->verts[numverts * 1], numverts, mat, 0.0, 0.0, 1.0);
+ groom_bundle_section_init(&bundle->sections[2], &bundle->verts[numverts * 2], numverts, mat, 0.4, -0.2, 1.2);
+ groom_bundle_section_init(&bundle->sections[3], &bundle->verts[numverts * 3], numverts, mat, 0.01, 0.7, 1.6);
+
+ return bundle;
+}
+
+static int region_add_exec(bContext *C, wmOperator *op)
+{
+ Object *obedit = CTX_data_edit_object(C);
+ Groom *groom = obedit->data;
+ EditGroom *editgroom = groom->editgroom;
+
+ WM_operator_view3d_unit_defaults(C, op);
+
+ float loc[3], rot[3];
+ unsigned int layer;
+ if (!ED_object_add_generic_get_opts(C, op, 'Z', loc, rot, NULL, &layer, NULL))
+ return OPERATOR_CANCELLED;
+
+ float mat[4][4];
+ ED_object_new_primitive_matrix(C, obedit, loc, rot, mat);
+
+ GroomBundle *bundle = groom_add_bundle(mat);
+ BLI_addtail(&editgroom->bundles, bundle);
+
+ WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, obedit);
+ DEG_id_tag_update(&obedit->id, OB_RECALC_DATA);
+
+ return OPERATOR_FINISHED;
+}
+
+void GROOM_OT_region_add(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Add Region";
+ ot->description = "Add a new region to the groom object";
+ ot->idname = "GROOM_OT_region_add";
+
+ /* api callbacks */
+ ot->exec = region_add_exec;
+ ot->poll = ED_operator_editgroom;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+ ED_object_add_generic_props(ot, false);
+}
+
+/* GROOM_OT_region_bind */
+
+static int region_bind_exec(bContext *C, wmOperator *op)
+{
+ Object *ob = ED_object_context(C);
+ Groom *groom = ob->data;
+ const bool force_rebind = RNA_int_get(op->ptr, "force_rebind");
+
+ GroomBundle *bundle = CTX_data_pointer_get_type(C, "groom_bundle", &RNA_GroomBundle).data;
+ if (!bundle)
+ {
+ bundle = BLI_findlink(&groom->bundles, groom->active_bundle);
+ if (!bundle)
+ {
+ return OPERATOR_CANCELLED;
+ }
+ }
+
+ BKE_groom_bundle_bind(groom, bundle, force_rebind);
+
+ WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob);
+ DEG_id_tag_update(&ob->id, OB_RECALC_DATA);
+
+ return OPERATOR_FINISHED;
+}
+
+void GROOM_OT_region_bind(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Bind Region";
+ ot->description = "Bind a groom bundle to its scalp region";
+ ot->idname = "GROOM_OT_region_bind";
+
+ /* api callbacks */
+ ot->exec = region_bind_exec;
+ ot->poll = ED_operator_scene_editable;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+ RNA_def_boolean(ot->srna, "force_rebind", true, "Force Rebind",
+ "Force rebinding of the groom region even if a binding already exists");
+}
diff --git a/source/blender/editors/groom/editgroom_select.c b/source/blender/editors/groom/editgroom_select.c
new file mode 100644
index 00000000000..9592d3e21fe
--- /dev/null
+++ b/source/blender/editors/groom/editgroom_select.c
@@ -0,0 +1,442 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) Blender Foundation
+ * All rights reserved.
+ *
+ * The Original Code is: all of this file.
+ *
+ * Contributor(s): Lukas Toenne
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file blender/editors/groom/editgroom_select.c
+ * \ingroup edgroom
+ */
+
+#include "DNA_groom_types.h"
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_math.h"
+
+#include "BKE_context.h"
+#include "BKE_groom.h"
+#include "BKE_report.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "ED_screen.h"
+#include "ED_types.h"
+#include "ED_view3d.h"
+#include "ED_groom.h"
+
+#include "groom_intern.h"
+
+#include "RNA_access.h"
+#include "RNA_define.h"
+
+bool ED_groom_select_check_regions(const EditGroom *edit)
+{
+ for (GroomBundle* bundle = edit->bundles.first; bundle; bundle = bundle->next)
+ {
+ if (bundle->flag & GM_BUNDLE_SELECT)
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool ED_groom_select_check_curves(const EditGroom *edit)
+{
+ for (GroomBundle* bundle = edit->bundles.first; bundle; bundle = bundle->next)
+ {
+ GroomSection *section = bundle->sections;
+ for (int i = 0; i < bundle->totsections; ++i, ++section)
+ {
+ if (section->flag & GM_SECTION_SELECT) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+bool ED_groom_select_check_sections(const EditGroom *edit)
+{
+ for (GroomBundle* bundle = edit->bundles.first; bundle; bundle = bundle->next)
+ {
+ GroomSectionVertex *vertex = bundle->verts;
+ for (int i = 0; i < bundle->totverts; ++i, ++vertex)
+ {
+ if (vertex->flag & GM_VERTEX_SELECT)
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+void ED_groom_select_regions(EditGroom *edit, EditGroomSelectCb select_cb, void *userdata)
+{
+ for (GroomBundle* bundle = edit->bundles.first; bundle; bundle = bundle->next)
+ {
+ const bool select = select_cb(userdata, bundle->flag & GM_BUNDLE_SELECT);
+ if (select)
+ {
+ bundle->flag |= GM_BUNDLE_SELECT;
+ }
+ else
+ {
+ bundle->flag &= ~GM_BUNDLE_SELECT;
+ }
+ }
+}
+
+void ED_groom_select_curves(EditGroom *edit, EditGroomSelectCb select_cb, void *userdata)
+{
+ for (GroomBundle* bundle = edit->bundles.first; bundle; bundle = bundle->next)
+ {
+ GroomSection *section = bundle->sections;
+ for (int i = 0; i < bundle->totsections; ++i, ++section)
+ {
+ const bool select = select_cb(userdata, section->flag & GM_SECTION_SELECT);
+ if (select)
+ {
+ section->flag |= GM_SECTION_SELECT;
+ }
+ else
+ {
+ section->flag &= ~GM_SECTION_SELECT;
+ }
+ }
+ }
+}
+
+void ED_groom_select_sections(EditGroom *edit, EditGroomSelectCb select_cb, void *userdata)
+{
+ for (GroomBundle* bundle = edit->bundles.first; bundle; bundle = bundle->next)
+ {
+ GroomSectionVertex *vertex = bundle->verts;
+ for (int i = 0; i < bundle->totverts; ++i, ++vertex)
+ {
+ const bool select = select_cb(userdata, vertex->flag & GM_VERTEX_SELECT);
+ if (select)
+ {
+ vertex->flag |= GM_VERTEX_SELECT;
+ }
+ else
+ {
+ vertex->flag &= ~GM_VERTEX_SELECT;
+ }
+ }
+ }
+}
+
+static bool groom_select_all_cb(void *UNUSED(userdata), bool UNUSED(is_selected))
+{
+ return true;
+}
+
+static bool groom_deselect_all_cb(void *UNUSED(userdata), bool UNUSED(is_selected))
+{
+ return false;
+}
+
+static bool groom_select_swap_cb(void *UNUSED(userdata), bool is_selected)
+{
+ return !is_selected;
+}
+
+static bool groom_has_selected(EditGroom *edit, GroomEditMode mode)
+{
+ switch (mode)
+ {
+ case GM_EDIT_MODE_REGIONS:
+ return ED_groom_select_check_regions(edit);
+ case GM_EDIT_MODE_CURVES:
+ return ED_groom_select_check_curves(edit);
+ case GM_EDIT_MODE_SECTIONS:
+ return ED_groom_select_check_sections(edit);
+ }
+ return false;
+}
+
+static int de_select_all_exec(bContext *C, wmOperator *op)
+{
+ GroomEditMode mode = CTX_data_tool_settings(C)->groom_edit_settings.mode;
+ Object *obedit = CTX_data_edit_object(C);
+ Groom *groom = obedit->data;
+ int action = RNA_enum_get(op->ptr, "action");
+
+ EditGroomSelectCb cb;
+ switch (action) {
+ case SEL_SELECT:
+ cb = groom_select_all_cb;
+ break;
+ case SEL_DESELECT:
+ cb = groom_deselect_all_cb;
+ break;
+ case SEL_INVERT:
+ cb = groom_select_swap_cb;
+ break;
+ case SEL_TOGGLE:
+ {
+ if (groom_has_selected(groom->editgroom, mode)) {
+ cb = groom_deselect_all_cb;
+ }
+ else
+ {
+ cb = groom_select_all_cb;
+ }
+ }
+ }
+
+ switch (mode)
+ {
+ case GM_EDIT_MODE_REGIONS:
+ ED_groom_select_regions(groom->editgroom, cb, NULL);
+ break;
+ case GM_EDIT_MODE_CURVES:
+ ED_groom_select_curves(groom->editgroom, cb, NULL);
+ break;
+ case GM_EDIT_MODE_SECTIONS:
+ ED_groom_select_sections(groom->editgroom, cb, NULL);
+ break;
+ }
+
+ WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data);
+
+ return OPERATOR_FINISHED;
+}
+
+void GROOM_OT_select_all(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "(De)select All";
+ ot->idname = "GROOM_OT_select_all";
+ ot->description = "(De)select all control points";
+
+ /* api callbacks */
+ ot->exec = de_select_all_exec;
+ ot->poll = ED_operator_editgroom;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+ /* properties */
+ WM_operator_properties_select_all(ot);
+}
+
+/****************************** Mouse Selection *************************/
+
+static void select_pick_findnearest_cb(
+ void *userdata,
+ GroomBundle *bundle,
+ GroomSection *section,
+ GroomSectionVertex *vertex,
+ const float screen_co[2])
+{
+ struct
+ {
+ GroomBundle *bundle;
+ GroomSection *section;
+ GroomSectionVertex *vertex;
+ float dist;
+ bool select;
+ float mval_fl[2];
+ } *data = userdata;
+
+ float dist_test = len_manhattan_v2v2(data->mval_fl, screen_co);
+
+ /* bias towards unselected items */
+ if (data->select &&
+ ((vertex && vertex->flag & GM_VERTEX_SELECT) ||
+ (section && section->flag & GM_SECTION_SELECT) ||
+ (bundle && bundle->flag & GM_BUNDLE_SELECT)))
+ {
+ dist_test += 5.0f;
+ }
+
+ if (dist_test < data->dist) {
+ data->dist = dist_test;
+ data->bundle = bundle;
+ data->section = section;
+ data->vertex = vertex;
+ }
+}
+
+static void groom_set_region_select_flags(Groom *groom, int flag)
+{
+ for (GroomBundle *bundle = groom->editgroom->bundles.first; bundle; bundle = bundle->next)
+ {
+ bundle->flag = (bundle->flag & ~GM_BUNDLE_SELECT) | (flag & GM_BUNDLE_SELECT);
+ }
+}
+
+static void groom_set_curve_select_flags(Groom *groom, int flag)
+{
+ for (GroomBundle *bundle = groom->editgroom->bundles.first; bundle; bundle = bundle->next)
+ {
+ GroomSection *section = bundle->sections;
+ for (int i = 0; i < bundle->totsections; ++i, ++section)
+ {
+ section->flag = (section->flag & ~GM_SECTION_SELECT) | (flag & GM_SECTION_SELECT);
+ }
+ }
+}
+
+static void groom_set_section_select_flags(Groom *groom, int flag)
+{
+ for (GroomBundle *bundle = groom->editgroom->bundles.first; bundle; bundle = bundle->next)
+ {
+ GroomSectionVertex *vertex = bundle->verts;
+ for (int i = 0; i < bundle->totverts; ++i, ++vertex)
+ {
+ vertex->flag = (vertex->flag & ~GM_VERTEX_SELECT) | (flag & GM_VERTEX_SELECT);
+ }
+ }
+}
+
+bool ED_groom_select_pick(bContext *C, const int mval[2], bool extend, bool deselect, bool toggle)
+{
+ ViewContext vc;
+ ED_view3d_viewcontext_init(C, &vc);
+ Groom *groom = vc.obedit->data;
+
+ struct
+ {
+ GroomBundle *bundle;
+ GroomSection *section;
+ GroomSectionVertex *vertex;
+ float dist;
+ bool select;
+ float mval_fl[2];
+ } data = {NULL};
+
+ data.dist = ED_view3d_select_dist_px();
+ data.select = true;
+ data.mval_fl[0] = mval[0];
+ data.mval_fl[1] = mval[1];
+
+ ED_view3d_init_mats_rv3d(vc.obedit, vc.rv3d);
+ groom_foreachScreenVert(&vc, select_pick_findnearest_cb, &data, V3D_PROJ_TEST_CLIP_DEFAULT);
+
+ bool found = false;
+ if (data.vertex)
+ {
+ if (extend)
+ {
+ data.vertex->flag |= GM_VERTEX_SELECT;
+ }
+ else if (deselect)
+ {
+ data.vertex->flag &= ~GM_VERTEX_SELECT;
+ }
+ else if (toggle)
+ {
+ data.vertex->flag ^= GM_VERTEX_SELECT;
+ }
+ else
+ {
+ /* deselect all other verts */
+ groom_set_section_select_flags(groom, 0);
+ data.vertex->flag |= GM_VERTEX_SELECT;
+ }
+
+ if (data.vertex->flag & GM_VERTEX_SELECT)
+ {
+ /* set active section */
+ groom_set_region_select_flags(groom, 0);
+ groom_set_curve_select_flags(groom, 0);
+ data.section->flag |= GM_SECTION_SELECT;
+ data.bundle->flag |= GM_BUNDLE_SELECT;
+ }
+
+ found = true;
+ }
+ else if (data.section)
+ {
+ if (extend)
+ {
+ data.section->flag |= GM_SECTION_SELECT;
+ }
+ else if (deselect)
+ {
+ data.section->flag &= ~GM_SECTION_SELECT;
+ }
+ else if (toggle)
+ {
+ data.section->flag ^= GM_SECTION_SELECT;
+ }
+ else
+ {
+ /* deselect all other sections */
+ groom_set_curve_select_flags(groom, 0);
+ data.section->flag |= GM_SECTION_SELECT;
+ }
+
+ if (data.section->flag & GM_SECTION_SELECT)
+ {
+ /* set active region */
+ groom_set_region_select_flags(groom, 0);
+ data.bundle->flag |= GM_BUNDLE_SELECT;
+ }
+
+ found = true;
+ }
+ else if (data.bundle)
+ {
+ if (extend)
+ {
+ data.bundle->flag |= GM_BUNDLE_SELECT;
+ }
+ else if (deselect)
+ {
+ data.bundle->flag &= ~GM_BUNDLE_SELECT;
+ }
+ else if (toggle)
+ {
+ data.bundle->flag ^= GM_BUNDLE_SELECT;
+ }
+ else
+ {
+ /* deselect all other regions */
+ groom_set_region_select_flags(groom, 0);
+ data.bundle->flag |= GM_BUNDLE_SELECT;
+ }
+
+ found = true;
+ }
+
+ if (found)
+ {
+ WM_event_add_notifier(C, NC_GEOM | ND_SELECT, vc.obedit->data);
+ return true;
+ }
+
+ return false;
+}
diff --git a/source/blender/editors/groom/groom_hair.c b/source/blender/editors/groom/groom_hair.c
new file mode 100644
index 00000000000..0a179a9b20e
--- /dev/null
+++ b/source/blender/editors/groom/groom_hair.c
@@ -0,0 +1,114 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) Blender Foundation
+ * All rights reserved.
+ *
+ * The Original Code is: all of this file.
+ *
+ * Contributor(s): Lukas Toenne
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file blender/editors/groom/groom_hair.c
+ * \ingroup groom
+ */
+
+#include "DNA_groom_types.h"
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_listbase.h"
+#include "BLI_math.h"
+
+#include "BLT_translation.h"
+
+#include "BKE_context.h"
+#include "BKE_groom.h"
+#include "BKE_report.h"
+
+#include "DEG_depsgraph.h"
+
+#include "RNA_access.h"
+#include "RNA_define.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "ED_object.h"
+#include "ED_screen.h"
+#include "ED_util.h"
+#include "ED_view3d.h"
+#include "ED_groom.h"
+
+#include "groom_intern.h"
+
+static int groom_object_poll(bContext *C)
+{
+ Object *ob = ED_object_context(C);
+ return ob->type == OB_GROOM;
+}
+
+/* GROOM_OT_hair_distribute */
+
+static int hair_distribute_exec(bContext *C, wmOperator *op)
+{
+ Object *ob = ED_object_context(C);
+ Groom *groom = ob->data;
+ int hair_count = RNA_int_get(op->ptr, "hair_count");
+ int guide_curve_count = RNA_int_get(op->ptr, "guide_curve_count");
+ unsigned int seed = (unsigned int)RNA_int_get(op->ptr, "seed");
+
+ if (!groom->scalp_object)
+ {
+ BKE_reportf(op->reports, RPT_ERROR, "Scalp object needed for creating hair follicles");
+ return OPERATOR_CANCELLED;
+ }
+
+ BKE_groom_hair_distribute(groom, seed, hair_count, guide_curve_count);
+
+ WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob);
+ DEG_id_tag_update(&ob->id, OB_RECALC_DATA);
+
+ return OPERATOR_FINISHED;
+}
+
+void GROOM_OT_hair_distribute(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Distribute Hair";
+ ot->description = "Distribute hair follicles and guide curves on the scalp";
+ ot->idname = "GROOM_OT_hair_distribute";
+
+ /* api callbacks */
+ ot->invoke = WM_operator_props_popup_confirm;
+ ot->exec = hair_distribute_exec;
+ ot->poll = groom_object_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+ RNA_def_int(ot->srna, "hair_count", 1000, 0, INT_MAX,
+ "Hair Count", "Number of hairs to generate", 1, 1e6);
+ RNA_def_int(ot->srna, "guide_curve_count", 10, 0, INT_MAX,
+ "Guide Curve Count", "Number of guide curves to generate", 1, 1e4);
+ RNA_def_int(ot->srna, "seed", 0, 0, INT_MAX,
+ "Seed", "Seed value for randomized follicle distribution", 0, INT_MAX);
+}
diff --git a/source/blender/editors/groom/groom_intern.h b/source/blender/editors/groom/groom_intern.h
new file mode 100644
index 00000000000..b398408ffd2
--- /dev/null
+++ b/source/blender/editors/groom/groom_intern.h
@@ -0,0 +1,48 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) Blender Foundation
+ * All rights reserved.
+ *
+ * The Original Code is: all of this file.
+ *
+ * Contributor(s): Lukas Toenne
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file blender/editors/groom/groom_intern.h
+ * \ingroup edgroom
+ */
+
+
+#ifndef __GROOM_INTERN_H__
+#define __GROOM_INTERN_H__
+
+struct wmOperatorType;
+
+/* editgroom_region.c */
+void GROOM_OT_region_add(struct wmOperatorType *ot);
+void GROOM_OT_region_bind(struct wmOperatorType *ot);
+
+/* editgroom_select.c */
+void GROOM_OT_select_all(struct wmOperatorType *ot);
+
+/* groom_hair.c */
+void GROOM_OT_hair_distribute(struct wmOperatorType *ot);
+
+#endif /* __GROOM_INTERN_H__ */
diff --git a/source/blender/editors/groom/groom_ops.c b/source/blender/editors/groom/groom_ops.c
new file mode 100644
index 00000000000..f5ff8989a6b
--- /dev/null
+++ b/source/blender/editors/groom/groom_ops.c
@@ -0,0 +1,86 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) Blender Foundation
+ * All rights reserved.
+ *
+ * The Original Code is: all of this file.
+ *
+ * Contributor(s): Lukas Toenne
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file blender/editors/groom/groom_ops.c
+ * \ingroup edgroom
+ */
+
+
+#include <stdlib.h>
+#include <math.h>
+
+#include "DNA_groom_types.h"
+#include "DNA_scene_types.h"
+
+#include "RNA_access.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "ED_groom.h"
+#include "ED_object.h"
+#include "ED_screen.h"
+#include "ED_transform.h"
+
+#include "groom_intern.h"
+
+/************************* registration ****************************/
+
+void ED_operatortypes_groom(void)
+{
+ WM_operatortype_append(GROOM_OT_region_add);
+ WM_operatortype_append(GROOM_OT_region_bind);
+
+ WM_operatortype_append(GROOM_OT_select_all);
+
+ WM_operatortype_append(GROOM_OT_hair_distribute);
+}
+
+void ED_operatormacros_groom(void)
+{
+ wmOperatorType *ot;
+ wmOperatorTypeMacro *otmacro;
+
+ UNUSED_VARS(ot, otmacro);
+}
+
+void ED_keymap_groom(wmKeyConfig *keyconf)
+{
+ wmKeyMap *keymap;
+ wmKeyMapItem *kmi;
+
+ keymap = WM_keymap_find(keyconf, "Groom", 0, 0);
+ keymap->poll = ED_operator_editgroom;
+
+ kmi = WM_keymap_add_item(keymap, "GROOM_OT_select_all", AKEY, KM_PRESS, 0, 0);
+ RNA_enum_set(kmi->ptr, "action", SEL_TOGGLE);
+ kmi = WM_keymap_add_item(keymap, "GROOM_OT_select_all", IKEY, KM_PRESS, KM_CTRL, 0);
+ RNA_enum_set(kmi->ptr, "action", SEL_INVERT);
+
+ ED_keymap_proportional_cycle(keyconf, keymap);
+ ED_keymap_proportional_editmode(keyconf, keymap, true);
+}
diff --git a/source/blender/editors/include/ED_groom.h b/source/blender/editors/include/ED_groom.h
new file mode 100644
index 00000000000..079d2ce3681
--- /dev/null
+++ b/source/blender/editors/include/ED_groom.h
@@ -0,0 +1,66 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) Blender Foundation
+ * All rights reserved.
+ *
+ * The Original Code is: all of this file.
+ *
+ * Contributor(s): Lukas Toenne
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file ED_groom.h
+ * \ingroup editors
+ */
+
+#ifndef __ED_GROOM_H__
+#define __ED_GROOM_H__
+
+struct bContext;
+struct Groom;
+struct Object;
+struct wmOperator;
+struct wmKeyConfig;
+struct EditGroom;
+
+/* groom_ops.c */
+void ED_operatortypes_groom(void);
+void ED_operatormacros_groom(void);
+void ED_keymap_groom(struct wmKeyConfig *keyconf);
+
+/* editgroom.c */
+void undo_push_groom(struct bContext *C, const char *name);
+
+void ED_groom_editgroom_load(struct Object *obedit);
+void ED_groom_editgroom_make(struct Object *obedit);
+void ED_groom_editgroom_free(struct Object *obedit);
+
+/* editgroom_select.c */
+bool ED_groom_select_check_regions(const struct EditGroom *edit);
+bool ED_groom_select_check_curves(const struct EditGroom *edit);
+bool ED_groom_select_check_sections(const struct EditGroom *edit);
+
+typedef bool (*EditGroomSelectCb)(void *userdata, bool is_selected);
+void ED_groom_select_regions(struct EditGroom *edit, EditGroomSelectCb select_cb, void *userdata);
+void ED_groom_select_curves(struct EditGroom *edit, EditGroomSelectCb select_cb, void *userdata);
+void ED_groom_select_sections(struct EditGroom *edit, EditGroomSelectCb select_cb, void *userdata);
+
+bool ED_groom_select_pick(struct bContext *C, const int mval[2], bool extend, bool deselect, bool toggle);
+
+#endif /* __ED_GROOM_H__ */
diff --git a/source/blender/editors/include/ED_screen.h b/source/blender/editors/include/ED_screen.h
index 3523e27f6ba..ea8a7c217a2 100644
--- a/source/blender/editors/include/ED_screen.h
+++ b/source/blender/editors/include/ED_screen.h
@@ -254,6 +254,7 @@ int ED_operator_editsurfcurve_region_view3d(struct bContext *C);
int ED_operator_editfont(struct bContext *C);
int ED_operator_editlattice(struct bContext *C);
int ED_operator_editmball(struct bContext *C);
+int ED_operator_editgroom(struct bContext *C);
int ED_operator_uvedit(struct bContext *C);
int ED_operator_uvedit_space_image(struct bContext *C);
int ED_operator_uvmap(struct bContext *C);
diff --git a/source/blender/editors/include/ED_view3d.h b/source/blender/editors/include/ED_view3d.h
index 7f18c10f970..1d4fc8891f5 100644
--- a/source/blender/editors/include/ED_view3d.h
+++ b/source/blender/editors/include/ED_view3d.h
@@ -44,6 +44,9 @@ struct Camera;
struct Depsgraph;
struct EditBone;
struct EvaluationContext;
+struct GroomBundle;
+struct GroomSection;
+struct GroomSectionVertex;
struct ImBuf;
struct MVert;
struct Main;
@@ -197,6 +200,15 @@ void pose_foreachScreenBone(
void (*func)(void *userData, struct bPoseChannel *pchan,
const float screen_co_a[2], const float screen_co_b[2]),
void *userData, const eV3DProjTest clip_flag);
+void groom_foreachScreenVert(
+ struct ViewContext *vc,
+ void (*func)(
+ void *userData,
+ struct GroomBundle *bundle,
+ struct GroomSection *section,
+ struct GroomSectionVertex *vert,
+ const float screen_co[2]),
+ void *userData, const eV3DProjTest clip_flag);
/* *** end iterators *** */
diff --git a/source/blender/editors/include/UI_icons.h b/source/blender/editors/include/UI_icons.h
index 923038a7490..b6714e80e80 100644
--- a/source/blender/editors/include/UI_icons.h
+++ b/source/blender/editors/include/UI_icons.h
@@ -262,8 +262,8 @@ DEF_ICON(GROUP_BONE)
DEF_ICON(GROUP_VERTEX)
DEF_ICON(GROUP_VCOL)
DEF_ICON(GROUP_UVS)
+DEF_ICON(GROOM_DATA)
#ifndef DEF_ICON_BLANK_SKIP
- DEF_ICON(BLANK089)
DEF_ICON(BLANK090)
#endif
DEF_ICON(RNA)
@@ -314,6 +314,7 @@ DEF_ICON(OUTLINER_OB_SPEAKER)
DEF_ICON(OUTLINER_OB_FORCE_FIELD)
DEF_ICON(OUTLINER_OB_GROUP_INSTANCE)
DEF_ICON(OUTLINER_OB_GREASEPENCIL)
+DEF_ICON(OUTLINER_OB_GROOM)
#ifndef DEF_ICON_BLANK_SKIP
DEF_ICON(BLANK123)
DEF_ICON(BLANK124)
diff --git a/source/blender/editors/object/object_add.c b/source/blender/editors/object/object_add.c
index d3f6a42c3c1..3a12d56d9c2 100644
--- a/source/blender/editors/object/object_add.c
+++ b/source/blender/editors/object/object_add.c
@@ -36,6 +36,7 @@
#include "DNA_anim_types.h"
#include "DNA_camera_types.h"
#include "DNA_curve_types.h"
+#include "DNA_groom_types.h"
#include "DNA_group_types.h"
#include "DNA_lamp_types.h"
#include "DNA_key_types.h"
@@ -72,6 +73,7 @@
#include "BKE_displist.h"
#include "BKE_effect.h"
#include "BKE_font.h"
+#include "BKE_groom.h"
#include "BKE_group.h"
#include "BKE_lamp.h"
#include "BKE_lattice.h"
@@ -776,6 +778,41 @@ void OBJECT_OT_metaball_add(wmOperatorType *ot)
ED_object_add_generic_props(ot, true);
}
+/********************* Add Groom Operator ********************/
+
+static int object_groom_add_exec(bContext *C, wmOperator *op)
+{
+ bool enter_editmode;
+ unsigned int layer;
+ float loc[3], rot[3];
+ if (!ED_object_add_generic_get_opts(C, op, 'Z', loc, rot, &enter_editmode, &layer, NULL))
+ return OPERATOR_CANCELLED;
+
+ Object *ob = ED_object_add_type(C, OB_GROOM, NULL, loc, rot, false, layer);
+
+ Groom *groom = ob->data;
+ UNUSED_VARS(groom);
+
+ return OPERATOR_FINISHED;
+}
+
+void OBJECT_OT_groom_add(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Add Groom";
+ ot->description = "Add a groom object to the scene";
+ ot->idname = "OBJECT_OT_groom_add";
+
+ /* api callbacks */
+ ot->exec = object_groom_add_exec;
+ ot->poll = ED_operator_objectmode;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+ ED_object_add_generic_props(ot, true);
+}
+
/********************* Add Text Operator ********************/
static int object_add_text_exec(bContext *C, wmOperator *op)
diff --git a/source/blender/editors/object/object_edit.c b/source/blender/editors/object/object_edit.c
index f4066360805..b4fa27c3fd9 100644
--- a/source/blender/editors/object/object_edit.c
+++ b/source/blender/editors/object/object_edit.c
@@ -95,6 +95,7 @@
#include "ED_armature.h"
#include "ED_curve.h"
+#include "ED_groom.h"
#include "ED_mesh.h"
#include "ED_mball.h"
#include "ED_lattice.h"
@@ -258,6 +259,10 @@ static bool ED_object_editmode_load_ex(Main *bmain, Object *obedit, const bool f
ED_mball_editmball_free(obedit);
}
}
+ else if (obedit->type == OB_GROOM) {
+ ED_groom_editgroom_load(obedit);
+ if (freedata) ED_groom_editgroom_free(obedit);
+ }
return true;
}
@@ -435,6 +440,12 @@ void ED_object_editmode_enter(bContext *C, int flag)
WM_event_add_notifier(C, NC_SCENE | ND_MODE | NS_EDITMODE_CURVE, scene);
}
+ else if (ob->type == OB_GROOM) {
+ ok = 1;
+ ED_groom_editgroom_make(ob);
+
+ WM_event_add_notifier(C, NC_SCENE | ND_MODE | NS_EDITMODE_GROOM, scene);
+ }
if (ok) {
DEG_id_tag_update(&ob->id, OB_RECALC_DATA);
diff --git a/source/blender/editors/object/object_intern.h b/source/blender/editors/object/object_intern.h
index 75117ccdce3..944912ad6b6 100644
--- a/source/blender/editors/object/object_intern.h
+++ b/source/blender/editors/object/object_intern.h
@@ -123,6 +123,7 @@ void OBJECT_OT_lamp_add(struct wmOperatorType *ot);
void OBJECT_OT_effector_add(struct wmOperatorType *ot);
void OBJECT_OT_camera_add(struct wmOperatorType *ot);
void OBJECT_OT_speaker_add(struct wmOperatorType *ot);
+void OBJECT_OT_groom_add(struct wmOperatorType *ot);
void OBJECT_OT_group_instance_add(struct wmOperatorType *ot);
void OBJECT_OT_duplicates_make_real(struct wmOperatorType *ot);
@@ -178,6 +179,7 @@ void OBJECT_OT_skin_radii_equalize(struct wmOperatorType *ot);
void OBJECT_OT_skin_armature_create(struct wmOperatorType *ot);
void OBJECT_OT_laplaciandeform_bind(struct wmOperatorType *ot);
void OBJECT_OT_surfacedeform_bind(struct wmOperatorType *ot);
+void OBJECT_OT_fur_generate_follicles(struct wmOperatorType *ot);
/* object_constraint.c */
void OBJECT_OT_constraint_add(struct wmOperatorType *ot);
diff --git a/source/blender/editors/object/object_modes.c b/source/blender/editors/object/object_modes.c
index f074a56fb86..9e6e6947320 100644
--- a/source/blender/editors/object/object_modes.c
+++ b/source/blender/editors/object/object_modes.c
@@ -99,6 +99,7 @@ bool ED_object_mode_compat_test(const Object *ob, eObjectMode mode)
case OB_SURF:
case OB_FONT:
case OB_MBALL:
+ case OB_GROOM:
if (mode & (OB_MODE_EDIT))
return true;
break;
diff --git a/source/blender/editors/object/object_modifier.c b/source/blender/editors/object/object_modifier.c
index 04243660440..5458e4c173f 100644
--- a/source/blender/editors/object/object_modifier.c
+++ b/source/blender/editors/object/object_modifier.c
@@ -58,11 +58,13 @@
#include "BKE_DerivedMesh.h"
#include "BKE_effect.h"
#include "BKE_global.h"
+#include "BKE_hair.h"
#include "BKE_key.h"
#include "BKE_lattice.h"
#include "BKE_main.h"
#include "BKE_mesh.h"
#include "BKE_mesh_mapping.h"
+#include "BKE_mesh_sample.h"
#include "BKE_modifier.h"
#include "BKE_multires.h"
#include "BKE_report.h"
@@ -2369,3 +2371,122 @@ void OBJECT_OT_surfacedeform_bind(wmOperatorType *ot)
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
edit_modifier_properties(ot);
}
+
+/************************ Fur follicle generate operator *********************/
+
+static void fur_create_guide_curves(struct HairSystem *hsys, unsigned int seed, DerivedMesh *scalp, int count)
+{
+ float area = BKE_hair_calc_surface_area(scalp);
+ float density = BKE_hair_calc_density_from_count(area, count);
+ float min_distance = BKE_hair_calc_min_distance_from_density(density);
+ MeshSampleGenerator *gen = BKE_mesh_sample_gen_surface_poissondisk(seed, min_distance, count, NULL, NULL);
+
+ BKE_mesh_sample_generator_bind(gen, scalp);
+
+ {
+ MeshSample *buffer = MEM_mallocN(sizeof(MeshSample) * count, "mesh sample buffer");
+ int totguides = BKE_mesh_sample_generate_batch(gen, buffer, count);
+
+ {
+ BKE_hair_guide_curves_begin(hsys, totguides);
+
+ MeshSample *sample = buffer;
+ for (int i = 0; i < totguides; ++i, ++sample)
+ {
+ BKE_hair_set_guide_curve(hsys, i, sample, 2);
+ }
+
+ BKE_hair_guide_curves_end(hsys);
+ }
+
+ {
+ int vertstart = 0;
+ MeshSample *sample = buffer;
+ for (int i = 0; i < totguides; ++i, ++sample)
+ {
+ float co[3], nor[3], tang[3];
+ BKE_mesh_sample_eval(scalp, sample, co, nor, tang);
+ BKE_hair_set_guide_vertex(hsys, vertstart, 0, co);
+
+ madd_v3_v3fl(co, nor, 0.1f);
+ BKE_hair_set_guide_vertex(hsys, vertstart + 1, 0, co);
+
+ vertstart += 2;
+ }
+ }
+
+ MEM_freeN(buffer);
+ }
+
+ BKE_mesh_sample_free_generator(gen);
+
+ BKE_hair_bind_follicles(hsys, scalp);
+}
+
+static int fur_generate_follicles_poll(bContext *C)
+{
+ return edit_modifier_poll_generic(C, &RNA_FurModifier, 0);
+}
+
+static int fur_generate_follicles_exec(bContext *C, wmOperator *op)
+{
+ Object *ob = ED_object_active_context(C);
+ FurModifierData *fmd = (FurModifierData *)edit_modifier_property_get(op, ob, eModifierType_Fur);
+
+ if (!fmd)
+ return OPERATOR_CANCELLED;
+
+ BLI_assert(fmd->hair_system != NULL);
+
+ struct Scene *scene = CTX_data_scene(C);
+ EvaluationContext eval_ctx;
+ CTX_data_eval_ctx(C, &eval_ctx);
+
+ CustomDataMask datamask = CD_MASK_BAREMESH;
+ DerivedMesh *dm = mesh_get_derived_final(&eval_ctx, scene, ob, datamask);
+
+ BKE_hair_generate_follicles(
+ fmd->hair_system,
+ dm,
+ (unsigned int)fmd->follicle_seed,
+ fmd->follicle_count);
+
+ {
+ unsigned int guides_seed = fmd->follicle_seed ^ 0xFFFF;
+ fur_create_guide_curves(
+ fmd->hair_system,
+ guides_seed,
+ dm,
+ fmd->guides_count);
+ }
+
+ DEG_id_tag_update(&ob->id, OB_RECALC_DATA);
+ WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, ob);
+
+ return OPERATOR_FINISHED;
+}
+
+static int fur_generate_follicles_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
+{
+ if (edit_modifier_invoke_properties(C, op))
+ return fur_generate_follicles_exec(C, op);
+ else
+ return OPERATOR_CANCELLED;
+}
+
+void OBJECT_OT_fur_generate_follicles(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Fur Follicles Generate";
+ ot->description = "Generate hair follicles for a fur modifier";
+ ot->idname = "OBJECT_OT_fur_generate_follicles";
+
+ /* api callbacks */
+ ot->poll = fur_generate_follicles_poll;
+ ot->invoke = fur_generate_follicles_invoke;
+ ot->exec = fur_generate_follicles_exec;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
+ edit_modifier_properties(ot);
+}
diff --git a/source/blender/editors/object/object_ops.c b/source/blender/editors/object/object_ops.c
index 66dc17fe77d..a35a54001e8 100644
--- a/source/blender/editors/object/object_ops.c
+++ b/source/blender/editors/object/object_ops.c
@@ -117,6 +117,7 @@ void ED_operatortypes_object(void)
WM_operatortype_append(OBJECT_OT_lamp_add);
WM_operatortype_append(OBJECT_OT_camera_add);
WM_operatortype_append(OBJECT_OT_speaker_add);
+ WM_operatortype_append(OBJECT_OT_groom_add);
WM_operatortype_append(OBJECT_OT_add);
WM_operatortype_append(OBJECT_OT_add_named);
WM_operatortype_append(OBJECT_OT_effector_add);
@@ -257,6 +258,7 @@ void ED_operatortypes_object(void)
WM_operatortype_append(OBJECT_OT_data_transfer);
WM_operatortype_append(OBJECT_OT_datalayout_transfer);
WM_operatortype_append(OBJECT_OT_surfacedeform_bind);
+ WM_operatortype_append(OBJECT_OT_fur_generate_follicles);
}
void ED_operatormacros_object(void)
diff --git a/source/blender/editors/object/object_relations.c b/source/blender/editors/object/object_relations.c
index 3443a268ef2..fee39c8f0fb 100644
--- a/source/blender/editors/object/object_relations.c
+++ b/source/blender/editors/object/object_relations.c
@@ -70,6 +70,7 @@
#include "BKE_DerivedMesh.h"
#include "BKE_displist.h"
#include "BKE_global.h"
+#include "BKE_groom.h"
#include "BKE_group.h"
#include "BKE_fcurve.h"
#include "BKE_idprop.h"
@@ -1837,6 +1838,9 @@ static void single_obdata_users(Main *bmain, Scene *scene, ViewLayer *view_layer
case OB_SPEAKER:
ob->data = ID_NEW_SET(ob->data, BKE_speaker_copy(bmain, ob->data));
break;
+ case OB_GROOM:
+ ob->data = ID_NEW_SET(ob->data, BKE_groom_copy(bmain, ob->data));
+ break;
case OB_LIGHTPROBE:
ob->data = ID_NEW_SET(ob->data, BKE_lightprobe_copy(bmain, ob->data));
break;
diff --git a/source/blender/editors/screen/screen_ops.c b/source/blender/editors/screen/screen_ops.c
index 488f240ba48..898010858b9 100644
--- a/source/blender/editors/screen/screen_ops.c
+++ b/source/blender/editors/screen/screen_ops.c
@@ -44,6 +44,7 @@
#include "DNA_object_types.h"
#include "DNA_curve_types.h"
#include "DNA_gpencil_types.h"
+#include "DNA_groom_types.h"
#include "DNA_scene_types.h"
#include "DNA_meta_types.h"
#include "DNA_mask_types.h"
@@ -530,6 +531,14 @@ int ED_operator_editmball(bContext *C)
return 0;
}
+int ED_operator_editgroom(bContext *C)
+{
+ Object *obedit = CTX_data_edit_object(C);
+ if (obedit && obedit->type == OB_GROOM)
+ return NULL != ((Groom *)obedit->data)->editgroom;
+ return 0;
+}
+
int ED_operator_mask(bContext *C)
{
ScrArea *sa = CTX_wm_area(C);
diff --git a/source/blender/editors/space_api/spacetypes.c b/source/blender/editors/space_api/spacetypes.c
index 4455b117370..2f85b915813 100644
--- a/source/blender/editors/space_api/spacetypes.c
+++ b/source/blender/editors/space_api/spacetypes.c
@@ -48,6 +48,7 @@
#include "ED_curve.h"
#include "ED_fileselect.h"
#include "ED_gpencil.h"
+#include "ED_groom.h"
#include "ED_markers.h"
#include "ED_mesh.h"
#include "ED_node.h"
@@ -124,7 +125,8 @@ void ED_spacetypes_init(void)
ED_operatortypes_logic();
ED_operatortypes_mask();
ED_operatortypes_io();
-
+ ED_operatortypes_groom();
+
ED_operatortypes_view2d();
ED_operatortypes_ui();
@@ -175,6 +177,7 @@ void ED_spacemacros_init(void)
ED_operatormacros_sequencer();
ED_operatormacros_paint();
ED_operatormacros_gpencil();
+ ED_operatormacros_groom();
/* register dropboxes (can use macros) */
spacetypes = BKE_spacetypes_list();
@@ -208,6 +211,7 @@ void ED_spacetypes_keymap(wmKeyConfig *keyconf)
ED_keymap_paint(keyconf);
ED_keymap_mask(keyconf);
ED_keymap_marker(keyconf);
+ ED_keymap_groom(keyconf);
ED_keymap_view2d(keyconf);
ED_keymap_ui(keyconf);
diff --git a/source/blender/editors/space_buttons/buttons_context.c b/source/blender/editors/space_buttons/buttons_context.c
index 8866c6b6c40..4b5f01897c1 100644
--- a/source/blender/editors/space_buttons/buttons_context.c
+++ b/source/blender/editors/space_buttons/buttons_context.c
@@ -249,6 +249,7 @@ static int buttons_context_path_data(ButsContextPath *path, int type)
else if (RNA_struct_is_a(ptr->type, &RNA_Lamp) && (type == -1 || type == OB_LAMP)) return 1;
else if (RNA_struct_is_a(ptr->type, &RNA_Speaker) && (type == -1 || type == OB_SPEAKER)) return 1;
else if (RNA_struct_is_a(ptr->type, &RNA_LightProbe) && (type == -1 || type == OB_LIGHTPROBE)) return 1;
+ else if (RNA_struct_is_a(ptr->type, &RNA_Groom) && (type == -1 || type == OB_GROOM)) return 1;
/* try to get an object in the path, no pinning supported here */
else if (buttons_context_path_object(path)) {
ob = path->ptr[path->len - 1].data;
@@ -809,7 +810,7 @@ const char *buttons_context_dir[] = {
"texture", "texture_user", "texture_user_property", "bone", "edit_bone",
"pose_bone", "particle_system", "particle_system_editable", "particle_settings",
"cloth", "soft_body", "fluid", "smoke", "collision", "brush", "dynamic_paint",
- "line_style", "collection", "workspace", NULL
+ "line_style", "collection", "workspace", "groom", NULL
};
int buttons_context(const bContext *C, const char *member, bContextDataResult *result)
@@ -1141,6 +1142,10 @@ int buttons_context(const bContext *C, const char *member, bContextDataResult *r
set_pointer_type(path, result, &RNA_LayerCollection);
return 1;
}
+ else if (CTX_data_equals(member, "groom")) {
+ set_pointer_type(path, result, &RNA_Groom);
+ return 1;
+ }
else {
return 0; /* not found */
}
diff --git a/source/blender/editors/space_outliner/outliner_draw.c b/source/blender/editors/space_outliner/outliner_draw.c
index 15b411280a5..0f9723dea4b 100644
--- a/source/blender/editors/space_outliner/outliner_draw.c
+++ b/source/blender/editors/space_outliner/outliner_draw.c
@@ -1046,6 +1046,9 @@ static void tselem_draw_icon(uiBlock *block, int xmax, float x, float y, TreeSto
case eModifierType_NormalEdit:
ICON_DRAW(ICON_MOD_NORMALEDIT);
break;
+ case eModifierType_Fur:
+ ICON_DRAW(ICON_STRANDS);
+ break;
/* Default */
case eModifierType_None:
case eModifierType_ShapeKey:
@@ -1138,6 +1141,8 @@ static void tselem_draw_icon(uiBlock *block, int xmax, float x, float y, TreeSto
tselem_draw_icon_uibut(&arg, ICON_OUTLINER_OB_CURVE); break;
case OB_MBALL:
tselem_draw_icon_uibut(&arg, ICON_OUTLINER_OB_META); break;
+ case OB_GROOM:
+ tselem_draw_icon_uibut(&arg, ICON_OUTLINER_OB_CURVE); break;
case OB_LATTICE:
tselem_draw_icon_uibut(&arg, ICON_OUTLINER_OB_LATTICE); break;
case OB_ARMATURE:
diff --git a/source/blender/editors/space_outliner/outliner_intern.h b/source/blender/editors/space_outliner/outliner_intern.h
index bedd2f09f3b..13567c2a5a2 100644
--- a/source/blender/editors/space_outliner/outliner_intern.h
+++ b/source/blender/editors/space_outliner/outliner_intern.h
@@ -115,7 +115,7 @@ typedef struct TreeElement {
#define TREESTORE_ID_TYPE(_id) \
(ELEM(GS((_id)->name), ID_SCE, ID_LI, ID_OB, ID_ME, ID_CU, ID_MB, ID_NT, ID_MA, ID_TE, ID_IM, ID_LT, ID_LA, ID_CA) || \
ELEM(GS((_id)->name), ID_KE, ID_WO, ID_SPK, ID_GR, ID_AR, ID_AC, ID_BR, ID_PA, ID_GD, ID_LS, ID_LP) || \
- ELEM(GS((_id)->name), ID_SCR, ID_WM, ID_TXT, ID_VF, ID_SO, ID_CF, ID_PAL, ID_WS)) /* Only in 'blendfile' mode ... :/ */
+ ELEM(GS((_id)->name), ID_SCR, ID_WM, ID_TXT, ID_VF, ID_SO, ID_CF, ID_PAL, ID_WS, ID_GM)) /* Only in 'blendfile' mode ... :/ */
/* TreeElement->flag */
enum {
diff --git a/source/blender/editors/space_outliner/outliner_tree.c b/source/blender/editors/space_outliner/outliner_tree.c
index eff726868c7..9f44b5bfc02 100644
--- a/source/blender/editors/space_outliner/outliner_tree.c
+++ b/source/blender/editors/space_outliner/outliner_tree.c
@@ -40,6 +40,7 @@
#include "DNA_camera_types.h"
#include "DNA_cachefile_types.h"
#include "DNA_gpencil_types.h"
+#include "DNA_groom_types.h"
#include "DNA_group_types.h"
#include "DNA_key_types.h"
#include "DNA_lamp_types.h"
@@ -747,6 +748,14 @@ static void outliner_add_id_contents(SpaceOops *soops, TreeElement *te, TreeStor
outliner_add_element(soops, &te->subtree, mb->mat[a], te, 0, a);
break;
}
+ case ID_GM:
+ {
+ Groom *groom = (Groom *)id;
+
+ if (outliner_animdata_test(groom->adt))
+ outliner_add_element(soops, &te->subtree, groom, te, TSE_ANIM_DATA, 0);
+ break;
+ }
case ID_MA:
{
Material *ma = (Material *)id;
diff --git a/source/blender/editors/space_view3d/space_view3d.c b/source/blender/editors/space_view3d/space_view3d.c
index 9fb602c81d6..74087e6f27b 100644
--- a/source/blender/editors/space_view3d/space_view3d.c
+++ b/source/blender/editors/space_view3d/space_view3d.c
@@ -532,6 +532,9 @@ static void view3d_main_region_init(wmWindowManager *wm, ARegion *ar)
keymap = WM_keymap_find(wm->defaultconf, "Particle", 0, 0);
WM_event_add_keymap_handler(&ar->handlers, keymap);
+ keymap = WM_keymap_find(wm->defaultconf, "Groom", 0, 0);
+ WM_event_add_keymap_handler(&ar->handlers, keymap);
+
/* editfont keymap swallows all... */
keymap = WM_keymap_find(wm->defaultconf, "Font", 0, 0);
WM_event_add_keymap_handler(&ar->handlers, keymap);
diff --git a/source/blender/editors/space_view3d/view3d_iterators.c b/source/blender/editors/space_view3d/view3d_iterators.c
index 4f80270e1e7..d3bee637fd7 100644
--- a/source/blender/editors/space_view3d/view3d_iterators.c
+++ b/source/blender/editors/space_view3d/view3d_iterators.c
@@ -31,9 +31,11 @@
#include "DNA_armature_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
+#include "DNA_groom_types.h"
#include "BLI_utildefines.h"
#include "BLI_rect.h"
+#include "BLI_math.h"
#include "BKE_armature.h"
#include "BKE_curve.h"
@@ -41,6 +43,7 @@
#include "BKE_displist.h"
#include "BKE_editmesh.h"
#include "BKE_context.h"
+#include "BKE_groom.h"
#include "DEG_depsgraph.h"
@@ -399,6 +402,75 @@ void lattice_foreachScreenVert(
/* ------------------------------------------------------------------------ */
+void groom_foreachScreenVert(
+ ViewContext *vc,
+ void (*func)(
+ void *userData,
+ GroomBundle *bundle,
+ GroomSection *section,
+ GroomSectionVertex *vert,
+ const float screen_co[2]),
+ void *userData, const eV3DProjTest clip_flag)
+{
+ GroomEditSettings *edit_settings = &vc->scene->toolsettings->groom_edit_settings;
+ Object *obedit = vc->obedit;
+ Groom *groom = obedit->data;
+ ListBase *bundles = &groom->editgroom->bundles;
+
+ ED_view3d_check_mats_rv3d(vc->rv3d);
+
+ if (clip_flag & V3D_PROJ_TEST_CLIP_BB) {
+ ED_view3d_clipping_local(vc->rv3d, obedit->obmat); /* for local clipping lookups */
+ }
+
+ switch (edit_settings->mode)
+ {
+ case GM_EDIT_MODE_REGIONS:
+ // TODO
+ break;
+
+ case GM_EDIT_MODE_CURVES:
+ for (GroomBundle *bundle = bundles->first; bundle; bundle = bundle->next)
+ {
+ GroomSection *section = bundle->sections;
+ for (int i = 0; i < bundle->totsections; ++i, ++section)
+ {
+ float screen_co[2];
+ if (ED_view3d_project_float_object(vc->ar, section->center, screen_co, clip_flag) == V3D_PROJ_RET_OK)
+ {
+ func(userData, bundle, section, NULL, screen_co);
+ }
+ }
+ }
+ break;
+
+ case GM_EDIT_MODE_SECTIONS:
+ for (GroomBundle *bundle = bundles->first; bundle; bundle = bundle->next)
+ {
+ GroomSectionVertex *vertex = bundle->verts;
+ GroomSection *section = bundle->sections;
+ for (int i = 0; i < bundle->totsections; ++i, ++section)
+ {
+ for (int j = 0; j < bundle->numshapeverts; ++j, ++vertex)
+ {
+ float co[3] = {vertex->co[0], vertex->co[1], 0.0f};
+ mul_m3_v3(section->mat, co);
+ add_v3_v3(co, section->center);
+
+ float screen_co[2];
+ if (ED_view3d_project_float_object(vc->ar, co, screen_co, clip_flag) == V3D_PROJ_RET_OK)
+ {
+ func(userData, bundle, section, vertex, screen_co);
+ }
+ }
+ }
+ }
+ break;
+ }
+}
+
+/* ------------------------------------------------------------------------ */
+
/* ED_view3d_init_mats_rv3d must be called first */
void armature_foreachScreenBone(
struct ViewContext *vc,
diff --git a/source/blender/editors/space_view3d/view3d_select.c b/source/blender/editors/space_view3d/view3d_select.c
index be8e63dffe6..21219bced3c 100644
--- a/source/blender/editors/space_view3d/view3d_select.c
+++ b/source/blender/editors/space_view3d/view3d_select.c
@@ -38,6 +38,7 @@
#include "DNA_action_types.h"
#include "DNA_armature_types.h"
#include "DNA_curve_types.h"
+#include "DNA_groom_types.h"
#include "DNA_meta_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
@@ -63,6 +64,7 @@
#include "BKE_armature.h"
#include "BKE_context.h"
#include "BKE_curve.h"
+#include "BKE_groom.h"
#include "BKE_layer.h"
#include "BKE_mball.h"
#include "BKE_mesh.h"
@@ -90,6 +92,7 @@
#include "ED_screen.h"
#include "ED_sculpt.h"
#include "ED_mball.h"
+#include "ED_groom.h"
#include "UI_interface.h"
@@ -594,6 +597,49 @@ static void do_lasso_select_lattice(ViewContext *vc, const int mcords[][2], shor
lattice_foreachScreenVert(vc, do_lasso_select_lattice__doSelect, &data, V3D_PROJ_TEST_CLIP_DEFAULT);
}
+static void do_lasso_select_groom__doSelect(
+ void *userData,
+ GroomBundle *bundle,
+ GroomSection *section,
+ GroomSectionVertex *vertex,
+ const float screen_co[2])
+{
+ LassoSelectUserData *data = userData;
+
+ if (BLI_rctf_isect_pt_v(data->rect_fl, screen_co) &&
+ BLI_lasso_is_point_inside(data->mcords, data->moves, screen_co[0], screen_co[1], IS_CLIPPED))
+ {
+ if (vertex)
+ {
+ vertex->flag = data->select ? (vertex->flag | GM_VERTEX_SELECT) : (vertex->flag & ~GM_VERTEX_SELECT);
+ }
+ else if (section)
+ {
+ section->flag = data->select ? (section->flag | GM_SECTION_SELECT) : (section->flag & ~GM_SECTION_SELECT);
+ }
+ else if (bundle)
+ {
+ bundle->flag = data->select ? (bundle->flag | GM_BUNDLE_SELECT) : (bundle->flag & ~GM_BUNDLE_SELECT);
+ }
+ }
+}
+
+static void do_lasso_select_groom(ViewContext *vc, const int mcords[][2], short moves, bool extend, bool select)
+{
+ LassoSelectUserData data;
+ rcti rect;
+
+ BLI_lasso_boundbox(&rect, mcords, moves);
+
+ view3d_userdata_lassoselect_init(&data, vc, &rect, mcords, moves, select);
+
+ if (extend == false && select)
+ ED_lattice_flags_set(vc->obedit, 0);
+
+ ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); /* for foreach's screen/vert projection */
+ groom_foreachScreenVert(vc, do_lasso_select_groom__doSelect, &data, V3D_PROJ_TEST_CLIP_DEFAULT);
+}
+
static void do_lasso_select_armature__doSelectBone(void *userData, struct EditBone *ebone, const float screen_co_a[2], const float screen_co_b[2])
{
LassoSelectUserData *data = userData;
@@ -851,6 +897,9 @@ static void view3d_lasso_select(
case OB_MBALL:
do_lasso_select_meta(vc, mcords, moves, extend, select);
break;
+ case OB_GROOM:
+ do_lasso_select_groom(vc, mcords, moves, extend, select);
+ break;
default:
assert(!"lasso select on incorrect object type");
break;
@@ -1774,6 +1823,47 @@ static int do_lattice_box_select(ViewContext *vc, rcti *rect, bool select, bool
return OPERATOR_FINISHED;
}
+static void do_groom_box_select__doSelect(
+ void *userData,
+ GroomBundle *bundle,
+ GroomSection *section,
+ GroomSectionVertex *vertex,
+ const float screen_co[2])
+{
+ BoxSelectUserData *data = userData;
+
+ if (BLI_rctf_isect_pt_v(data->rect_fl, screen_co))
+ {
+ if (vertex)
+ {
+ vertex->flag = data->select ? (vertex->flag | GM_VERTEX_SELECT) : (vertex->flag & ~GM_VERTEX_SELECT);
+ }
+ else if (section)
+ {
+ section->flag = data->select ? (section->flag | GM_SECTION_SELECT) : (section->flag & ~GM_SECTION_SELECT);
+ }
+ else if (bundle)
+ {
+ bundle->flag = data->select ? (bundle->flag | GM_BUNDLE_SELECT) : (bundle->flag & ~GM_BUNDLE_SELECT);
+ }
+ }
+}
+
+static int do_groom_box_select(ViewContext *vc, rcti *rect, bool select, bool extend)
+{
+ BoxSelectUserData data;
+
+ view3d_userdata_boxselect_init(&data, vc, rect, select);
+
+ if (extend == false && select)
+ ED_lattice_flags_set(vc->obedit, 0);
+
+ ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); /* for foreach's screen/vert projection */
+ groom_foreachScreenVert(vc, do_groom_box_select__doSelect, &data, V3D_PROJ_TEST_CLIP_DEFAULT);
+
+ return OPERATOR_FINISHED;
+}
+
static void do_mesh_box_select__doSelectVert(void *userData, BMVert *eve, const float screen_co[2], int UNUSED(index))
{
BoxSelectUserData *data = userData;
@@ -2158,6 +2248,12 @@ static int view3d_borderselect_exec(bContext *C, wmOperator *op)
WM_event_add_notifier(C, NC_GEOM | ND_SELECT, vc.obedit->data);
}
break;
+ case OB_GROOM:
+ ret = do_groom_box_select(&vc, &rect, select, extend);
+ if (ret & OPERATOR_FINISHED) {
+ WM_event_add_notifier(C, NC_GEOM | ND_SELECT, vc.obedit->data);
+ }
+ break;
default:
assert(!"border select on incorrect object type");
break;
@@ -2304,6 +2400,8 @@ static int view3d_select_exec(bContext *C, wmOperator *op)
retval = ED_mball_select_pick(C, location, extend, deselect, toggle);
else if (obedit->type == OB_FONT)
retval = ED_curve_editfont_select_pick(C, location, extend, deselect, toggle);
+ else if (obedit->type == OB_GROOM)
+ retval = ED_groom_select_pick(C, location, extend, deselect, toggle);
}
else if (obact && obact->mode & OB_MODE_PARTICLE_EDIT)
@@ -2579,6 +2677,43 @@ static void lattice_circle_select(ViewContext *vc, const bool select, const int
}
+static void groom_circle_select__doSelect(
+ void *userData,
+ GroomBundle *bundle,
+ GroomSection *section,
+ GroomSectionVertex *vertex,
+ const float screen_co[2])
+{
+ CircleSelectUserData *data = userData;
+
+ if (len_squared_v2v2(data->mval_fl, screen_co) <= data->radius_squared)
+ {
+ if (vertex)
+ {
+ vertex->flag = data->select ? (vertex->flag | GM_VERTEX_SELECT) : (vertex->flag & ~GM_VERTEX_SELECT);
+ }
+ else if (section)
+ {
+ section->flag = data->select ? (section->flag | GM_SECTION_SELECT) : (section->flag & ~GM_SECTION_SELECT);
+ }
+ else if (bundle)
+ {
+ bundle->flag = data->select ? (bundle->flag | GM_BUNDLE_SELECT) : (bundle->flag & ~GM_BUNDLE_SELECT);
+ }
+ }
+}
+
+static void groom_circle_select(ViewContext *vc, const bool select, const int mval[2], float rad)
+{
+ CircleSelectUserData data;
+
+ view3d_userdata_circleselect_init(&data, vc, select, mval, rad);
+
+ ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); /* for foreach's screen/vert projection */
+ groom_foreachScreenVert(vc, groom_circle_select__doSelect, &data, V3D_PROJ_TEST_CLIP_DEFAULT);
+}
+
+
/* NOTE: pose-bone case is copied from editbone case... */
static bool pchan_circle_doSelectJoint(void *userData, bPoseChannel *pchan, const float screen_co[2])
{
@@ -2785,6 +2920,9 @@ static void obedit_circle_select(
case OB_MBALL:
mball_circle_select(vc, select, mval, rad);
break;
+ case OB_GROOM:
+ groom_circle_select(vc, select, mval, rad);
+ break;
default:
return;
}
diff --git a/source/blender/editors/transform/transform.h b/source/blender/editors/transform/transform.h
index fe05207e645..40f6b64b055 100644
--- a/source/blender/editors/transform/transform.h
+++ b/source/blender/editors/transform/transform.h
@@ -648,6 +648,7 @@ void flushTransSeq(TransInfo *t);
void flushTransTracking(TransInfo *t);
void flushTransMasking(TransInfo *t);
void flushTransPaintCurve(TransInfo *t);
+void flushTransGroom(TransInfo *t);
void restoreBones(TransInfo *t);
/*********************** transform_manipulator.c ********** */
diff --git a/source/blender/editors/transform/transform_conversions.c b/source/blender/editors/transform/transform_conversions.c
index 2d7ff1eb523..3a1c60e4a65 100644
--- a/source/blender/editors/transform/transform_conversions.c
+++ b/source/blender/editors/transform/transform_conversions.c
@@ -36,6 +36,7 @@
#include "DNA_anim_types.h"
#include "DNA_brush_types.h"
#include "DNA_armature_types.h"
+#include "DNA_groom_types.h"
#include "DNA_lattice_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meta_types.h"
@@ -71,6 +72,7 @@
#include "BKE_fcurve.h"
#include "BKE_global.h"
#include "BKE_gpencil.h"
+#include "BKE_groom.h"
#include "BKE_layer.h"
#include "BKE_key.h"
#include "BKE_main.h"
@@ -1886,6 +1888,235 @@ static void createTransLatticeVerts(TransInfo *t)
}
}
+/* ********************* groom *************** */
+
+static int groom_trans_count_regions(EditGroom *edit, bool is_prop_edit)
+{
+ // TODO
+ UNUSED_VARS(edit, is_prop_edit);
+ return 0;
+}
+
+static void groom_transdata_init_regions(
+ EditGroom *edit,
+ bool is_prop_edit,
+ float obmat[4][4],
+ TransData *tdata)
+{
+ // TODO
+ UNUSED_VARS(edit, is_prop_edit, obmat, tdata);
+}
+
+static int groom_trans_count_curves(EditGroom *edit, bool is_prop_edit)
+{
+ int count = 0, countsel = 0;
+ for (GroomBundle *bundle = edit->bundles.first; bundle; bundle = bundle->next)
+ {
+ GroomSection *section = bundle->sections;
+ for (int i = 0; i < bundle->totsections; ++i, ++section)
+ {
+ ++count;
+ if (section->flag & GM_SECTION_SELECT)
+ {
+ ++countsel;
+ }
+ }
+ }
+
+ /* note: in prop mode we need at least 1 selected */
+ if (countsel > 0)
+ {
+ return is_prop_edit ? count : countsel;
+ }
+ return 0;
+}
+
+static void groom_transdata_init_curves(
+ EditGroom *edit,
+ bool is_prop_edit,
+ float obmat[4][4],
+ TransData *tdata)
+{
+ float mtx[3][3], smtx[3][3];
+ copy_m3_m4(mtx, obmat);
+ pseudoinverse_m3_m3(smtx, mtx, PSEUDOINVERSE_EPSILON);
+
+ TransData *td = tdata;
+ for (GroomBundle *bundle = edit->bundles.first; bundle; bundle = bundle->next)
+ {
+ GroomSection *section = bundle->sections;
+ for (int i = 0; i < bundle->totsections; ++i, ++section)
+ {
+ if (is_prop_edit || (section->flag & GM_SECTION_SELECT))
+ {
+ copy_v3_v3(td->iloc, section->center);
+ copy_v3_v3(td->center, section->center);
+ td->loc = section->center;
+
+ if (section->flag & GM_SECTION_SELECT)
+ {
+ td->flag = TD_SELECTED;
+ }
+ else
+ {
+ td->flag = 0;
+ }
+
+ copy_m3_m3(td->smtx, smtx);
+ copy_m3_m3(td->mtx, mtx);
+
+ td->ext = NULL;
+ td->val = NULL;
+
+ ++td;
+ }
+ }
+ }
+}
+
+static int groom_trans_count_verts(EditGroom *edit, bool is_prop_edit)
+{
+ int count = 0, countsel = 0;
+ for (GroomBundle *bundle = edit->bundles.first; bundle; bundle = bundle->next)
+ {
+ GroomSectionVertex *vertex = bundle->verts;
+ for (int i = 0; i < bundle->totverts; ++i, ++vertex)
+ {
+ ++count;
+ if (vertex->flag & GM_VERTEX_SELECT)
+ {
+ ++countsel;
+ }
+ }
+ }
+
+ /* note: in prop mode we need at least 1 selected */
+ if (countsel > 0)
+ {
+ return is_prop_edit ? count : countsel;
+ }
+ return 0;
+}
+
+static void groom_transdata_init_verts(
+ EditGroom *edit,
+ bool is_prop_edit,
+ float obmat[4][4],
+ TransData *tdata,
+ TransData2D *tdata2d)
+{
+ float obmat3[3][3];
+ copy_m3_m4(obmat3, obmat);
+
+ TransData *td = tdata;
+ TransData2D *td2d = tdata2d;
+ for (GroomBundle *bundle = edit->bundles.first; bundle; bundle = bundle->next)
+ {
+ GroomSection *section = bundle->sections;
+ GroomSectionVertex *vertex = bundle->verts;
+ for (int i = 0; i < bundle->totsections; ++i, ++section)
+ {
+ /* local coordinate frame for the section */
+ float mtx[3][3], smtx[3][3];
+ mul_m3_m3m3(mtx, obmat3, section->mat);
+ pseudoinverse_m3_m3(smtx, mtx, PSEUDOINVERSE_EPSILON);
+
+ for (int j = 0; j < bundle->numshapeverts; ++j, ++vertex)
+ {
+ if (is_prop_edit || (vertex->flag & GM_VERTEX_SELECT))
+ {
+ copy_v2_v2(td2d->loc, vertex->co);
+ td2d->loc2d = vertex->co;
+
+ td->loc = td2d->loc;
+ copy_v3_v3(td->iloc, td->loc);
+ /* section verts are centered around the curve */
+ zero_v3(td->center);
+
+ if (vertex->flag & GM_VERTEX_SELECT)
+ {
+ td->flag = TD_SELECTED;
+ }
+ else
+ {
+ td->flag = 0;
+ }
+
+ copy_m3_m3(td->smtx, smtx);
+ copy_m3_m3(td->mtx, mtx);
+
+ memset(td->axismtx, 0, sizeof(td->axismtx));
+ td->axismtx[2][2] = 1.0f;
+
+ td->ext = NULL;
+ td->val = NULL;
+
+ ++td;
+ ++td2d;
+ }
+ }
+ }
+ }
+}
+
+static void createTransGroomVerts(TransInfo *t)
+{
+ const ToolSettings *tsettings = t->scene->toolsettings;
+ const bool is_prop_edit = t->flag & T_PROP_EDIT;
+ EditGroom *edit = ((Groom *)t->obedit->data)->editgroom;
+
+ switch (tsettings->groom_edit_settings.mode)
+ {
+ case GM_EDIT_MODE_REGIONS:
+ t->total = groom_trans_count_regions(edit, is_prop_edit);
+ if (t->total > 0)
+ {
+ // TODO
+ groom_transdata_init_regions(edit, is_prop_edit, t->obedit->obmat, t->data);
+ }
+ break;
+ case GM_EDIT_MODE_CURVES:
+ t->total = groom_trans_count_curves(edit, is_prop_edit);
+ if (t->total > 0)
+ {
+ t->data = MEM_callocN(t->total * sizeof(TransData), "TransData(Groom EditMode)");
+
+ groom_transdata_init_curves(edit, is_prop_edit, t->obedit->obmat, t->data);
+ }
+ break;
+ case GM_EDIT_MODE_SECTIONS:
+ t->total = groom_trans_count_verts(edit, is_prop_edit);
+ if (t->total > 0)
+ {
+ t->data = MEM_callocN(t->total * sizeof(TransData), "TransData(Groom EditMode)");
+ t->data2d = MEM_callocN(t->total * sizeof(TransData2D), "TransData2D(Groom EditMode)");
+
+ groom_transdata_init_verts(edit, is_prop_edit, t->obedit->obmat, t->data, t->data2d);
+ }
+ break;
+ }
+}
+
+void flushTransGroom(TransInfo *t)
+{
+ switch (t->scene->toolsettings->groom_edit_settings.mode)
+ {
+ case GM_EDIT_MODE_REGIONS:
+ break;
+ case GM_EDIT_MODE_CURVES:
+ break;
+ case GM_EDIT_MODE_SECTIONS:
+ {
+ TransData2D *td2d = t->data2d;
+ for (int i = 0; i < t->total; ++i, ++td2d)
+ {
+ copy_v2_v2(td2d->loc2d, td2d->loc);
+ }
+ break;
+ }
+ }
+}
+
/* ******************* particle edit **************** */
static void createTransParticleVerts(bContext *C, TransInfo *t)
{
@@ -8252,6 +8483,9 @@ void createTransData(bContext *C, TransInfo *t)
else if (t->obedit->type == OB_MBALL) {
createTransMBallVerts(t);
}
+ else if (t->obedit->type == OB_GROOM) {
+ createTransGroomVerts(t);
+ }
else if (t->obedit->type == OB_ARMATURE) {
t->flag &= ~T_PROP_EDIT;
createTransArmatureVerts(t);
diff --git a/source/blender/editors/transform/transform_generics.c b/source/blender/editors/transform/transform_generics.c
index 6a813eb2a55..32cd02c59e6 100644
--- a/source/blender/editors/transform/transform_generics.c
+++ b/source/blender/editors/transform/transform_generics.c
@@ -755,6 +755,11 @@ static void recalcData_objects(TransInfo *t)
if (la->editlatt->latt->flag & LT_OUTSIDE) outside_lattice(la->editlatt->latt);
}
+ else if (t->obedit->type == OB_GROOM) {
+ flushTransGroom(t);
+
+ DEG_id_tag_update(t->obedit->data, 0); /* sets recalc flags */
+ }
else if (t->obedit->type == OB_MESH) {
BMEditMesh *em = BKE_editmesh_from_object(t->obedit);
/* mirror modifier clipping? */
diff --git a/source/blender/gpu/GPU_texture.h b/source/blender/gpu/GPU_texture.h
index 57185d2f39e..7c33153ee01 100644
--- a/source/blender/gpu/GPU_texture.h
+++ b/source/blender/gpu/GPU_texture.h
@@ -106,10 +106,10 @@ typedef enum GPUTextureFormat {
/* Texture only format */
GPU_RGB16F,
+ GPU_RGB32F,
#if 0
GPU_RGBA16_SNORM,
GPU_RGBA8_SNORM,
- GPU_RGB32F,
GPU_RGB32I,
GPU_RGB32UI,
GPU_RGB16_SNORM,
diff --git a/source/blender/makesdna/DNA_ID.h b/source/blender/makesdna/DNA_ID.h
index 778aaec7d19..72e4597a06e 100644
--- a/source/blender/makesdna/DNA_ID.h
+++ b/source/blender/makesdna/DNA_ID.h
@@ -347,6 +347,7 @@ typedef enum ID_Type {
ID_CF = MAKE_ID2('C', 'F'), /* CacheFile */
ID_WS = MAKE_ID2('W', 'S'), /* WorkSpace */
ID_LP = MAKE_ID2('L', 'P'), /* LightProbe */
+ ID_GM = MAKE_ID2('G', 'M'), /* Groom */
} ID_Type;
/* Only used as 'placeholder' in .blend files for directly linked datablocks. */
@@ -534,6 +535,7 @@ enum {
INDEX_ID_ME,
INDEX_ID_CU,
INDEX_ID_MB,
+ INDEX_ID_GM,
INDEX_ID_LT,
INDEX_ID_LA,
INDEX_ID_CA,
diff --git a/source/blender/makesdna/DNA_groom_types.h b/source/blender/makesdna/DNA_groom_types.h
new file mode 100644
index 00000000000..e1f6fbec7d0
--- /dev/null
+++ b/source/blender/makesdna/DNA_groom_types.h
@@ -0,0 +1,136 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) Blender Foundation
+ * All rights reserved.
+ *
+ * The Original Code is: all of this file.
+ *
+ * Contributor(s): Lukas Toenne
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file DNA_groom_types.h
+ * \ingroup DNA
+ */
+
+#ifndef __DNA_GROOM_TYPES_H__
+#define __DNA_GROOM_TYPES_H__
+
+#include "DNA_defs.h"
+#include "DNA_listBase.h"
+#include "DNA_ID.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Vertex in a closed curve for a bundle section */
+typedef struct GroomSectionVertex
+{
+ int flag;
+ float co[2]; /* Location in the section plane */
+} GroomSectionVertex;
+
+typedef enum GroomVertexFlag
+{
+ GM_VERTEX_SELECT = (1 << 0),
+} GroomVertexFlag;
+
+/* Cross-section of a bundle */
+typedef struct GroomSection {
+ int flag;
+ int pad;
+
+ float center[3]; /* Center point */
+
+ float mat[3][3]; /* Local coordinate frame */
+} GroomSection;
+
+typedef enum GroomSectionFlag
+{
+ GM_SECTION_SELECT = (1 << 0),
+} GroomSectionFlag;
+
+/* Single interpolated step along a groom curve */
+typedef struct GroomCurveCache
+{
+ float co[3]; /* Location vector */
+} GroomCurveCache;
+
+/* Bundle of hair strands following the same curve path */
+typedef struct GroomBundle {
+ struct GroomBundle *next, *prev; /* Pointers for ListBase element */
+
+ int flag;
+
+ int numshapeverts; /* Vertices per section loop */
+ int totsections; /* Number of sections along the curve */
+ int totverts; /* Number of vertices of all sections combined */
+ int curvesize; /* Number of verticess in a curve = (totsections - 1) * curve_res + 1 */
+ int totcurvecache; /* Number of cached curve steps = curve_size * (numshapeverts + 1) */
+
+ struct GroomSection *sections; /* List of sections [totsections] */
+ struct GroomSectionVertex *verts; /* List of vertices [totsections][numloopverts] */
+ struct GroomCurveCache *curvecache; /* Cached curve steps [numshapeverts + 1][curve_size], last is center curve */
+ struct MeshSample *scalp_region; /* Mesh samples bind to a scalp region [numloopverts + 1], last is center position */
+
+ /* Scalp Region */
+ /* XXX Face maps are used temporarily for creating regions,
+ * eventually should be replaced by a fully fledged 2D loop mesh */
+ char scalp_facemap_name[64]; /* Scalp face map to use as region, MAX_VGROUP_NAME */
+} GroomBundle;
+
+typedef enum GroomBundleFlag
+{
+ GM_BUNDLE_SELECT = (1 << 0),
+} GroomBundleFlag;
+
+/* Editable groom data */
+typedef struct EditGroom {
+ ListBase bundles; /* List of GroomBundle */
+} EditGroom;
+
+/* Groom curves for creating hair styles */
+typedef struct Groom {
+ ID id; /* Groom data is a datablock */
+ struct AnimData *adt; /* Animation data - for animating settings */
+
+ int curve_res; /* Curve resolution */
+ int pad;
+
+ ListBase bundles; /* List of GroomBundle */
+ int active_bundle; /* Index of active bundle in bundles list */
+ int pad2;
+
+ struct HairSystem *hair_system; /* Renderable hair geometry */
+ struct HairDrawSettings *hair_draw_settings; /* Draw settings for hair geometry */
+
+ struct Object *scalp_object; /* Surface for attaching hairs */
+
+ struct BoundBox *bb;
+
+ EditGroom *editgroom;
+ void *batch_cache;
+} Groom;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __DNA_GROOM_TYPES_H__ */
diff --git a/source/blender/makesdna/DNA_hair_types.h b/source/blender/makesdna/DNA_hair_types.h
new file mode 100644
index 00000000000..8a95077c7f6
--- /dev/null
+++ b/source/blender/makesdna/DNA_hair_types.h
@@ -0,0 +1,121 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Contributor(s): Blender Foundation
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ *
+ */
+
+/** \file DNA_hair_types.h
+ * \ingroup DNA
+ */
+
+#ifndef __DNA_HAIR_TYPES_H__
+#define __DNA_HAIR_TYPES_H__
+
+#include "DNA_defs.h"
+#include "DNA_listBase.h"
+#include "DNA_ID.h"
+#include "DNA_meshdata_types.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Root point (follicle) of a hair on a surface */
+typedef struct HairFollicle {
+ /* Sample on the scalp mesh for the root vertex */
+ MeshSample mesh_sample;
+ /* Parent curve indices for shape interpolation */
+ unsigned int parent_index[4];
+ /* Parent curve weights for shape interpolation */
+ float parent_weight[4];
+} HairFollicle;
+
+/* Collection of hair roots on a surface */
+typedef struct HairPattern {
+ struct HairFollicle *follicles;
+ int num_follicles;
+ int pad;
+} HairPattern;
+
+typedef struct HairGuideCurve {
+ /* Sample on the scalp mesh for the root vertex */
+ MeshSample mesh_sample;
+ /* Offset in the vertex array where the curve starts */
+ int vertstart;
+ /* Number of vertices in the curve */
+ int numverts;
+} HairGuideCurve;
+
+typedef struct HairGuideVertex {
+ int flag;
+ float co[3];
+} HairGuideVertex;
+
+typedef struct HairSystem {
+ int flag;
+ int pad;
+
+ /* Set of hair follicles on the scalp mesh */
+ struct HairPattern *pattern;
+
+ /* Curves for guiding hair fibers */
+ struct HairGuideCurve *curves;
+ /* Control vertices on guide curves */
+ struct HairGuideVertex *verts;
+ /* Number of guide curves */
+ int totcurves;
+ /* Number of guide curve vertices */
+ int totverts;
+
+ /* Material used for drawing and rendering hair fibers */
+ int material_index;
+ int pad2;
+
+ /* Data buffers for drawing */
+ void *draw_batch_cache;
+ /* Texture buffer for drawing */
+ void *draw_texture_cache;
+} HairSystem;
+
+typedef enum eHairSystemFlag
+{
+ /* Guide curve positions have changed, rebind hair follicles */
+ HAIR_SYSTEM_UPDATE_FOLLICLE_BINDING = (1 << 8),
+} eHairSystemFlag;
+
+typedef struct HairDrawSettings
+{
+ short follicle_mode;
+ short pad1;
+ int pad2;
+} HairDrawSettings;
+
+typedef enum eHairDrawFollicleMode
+{
+ HAIR_DRAW_FOLLICLE_NONE = 0,
+ HAIR_DRAW_FOLLICLE_POINTS = 1,
+ HAIR_DRAW_FOLLICLE_AXES = 2,
+} eHairDrawFollicleMode;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __DNA_HAIR_TYPES_H__ */
diff --git a/source/blender/makesdna/DNA_meshdata_types.h b/source/blender/makesdna/DNA_meshdata_types.h
index 6d38fe22ea1..1cbeee79326 100644
--- a/source/blender/makesdna/DNA_meshdata_types.h
+++ b/source/blender/makesdna/DNA_meshdata_types.h
@@ -380,6 +380,13 @@ enum {
FREESTYLE_FACE_MARK = 1,
};
+typedef struct MeshSample {
+ unsigned int orig_verts[3];
+ float orig_weights[3]; /* also used as volume sample location */
+ int orig_poly;
+ unsigned int orig_loops[3];
+} MeshSample;
+
/* mvert->flag */
enum {
/* SELECT = (1 << 0), */
diff --git a/source/blender/makesdna/DNA_modifier_types.h b/source/blender/makesdna/DNA_modifier_types.h
index 283e801ea7a..44627812a21 100644
--- a/source/blender/makesdna/DNA_modifier_types.h
+++ b/source/blender/makesdna/DNA_modifier_types.h
@@ -87,6 +87,7 @@ typedef enum ModifierType {
eModifierType_CorrectiveSmooth = 51,
eModifierType_MeshSequenceCache = 52,
eModifierType_SurfaceDeform = 53,
+ eModifierType_Fur = 54,
NUM_MODIFIER_TYPES
} ModifierType;
@@ -1622,4 +1623,22 @@ enum {
#define MOD_MESHSEQ_READ_ALL \
(MOD_MESHSEQ_READ_VERT | MOD_MESHSEQ_READ_POLY | MOD_MESHSEQ_READ_UV | MOD_MESHSEQ_READ_COLOR)
+/* Fur modifier */
+typedef struct FurModifierData {
+ ModifierData modifier;
+
+ int flag;
+ int pad;
+
+ struct HairSystem *hair_system;
+ struct HairDrawSettings *draw_settings;
+
+ /* Follicle distribution parameters */
+ int follicle_seed;
+ int follicle_count;
+
+ int guides_count;
+ int pad2;
+} FurModifierData;
+
#endif /* __DNA_MODIFIER_TYPES_H__ */
diff --git a/source/blender/makesdna/DNA_object_types.h b/source/blender/makesdna/DNA_object_types.h
index 2cbc266ea0c..17dc7ebd2ad 100644
--- a/source/blender/makesdna/DNA_object_types.h
+++ b/source/blender/makesdna/DNA_object_types.h
@@ -397,6 +397,7 @@ enum {
OB_SURF = 3,
OB_FONT = 4,
OB_MBALL = 5,
+ OB_GROOM = 6,
OB_LAMP = 10,
OB_CAMERA = 11,
@@ -417,16 +418,16 @@ enum {
#define OB_TYPE_SUPPORT_VGROUP(_type) \
(ELEM(_type, OB_MESH, OB_LATTICE))
#define OB_TYPE_SUPPORT_EDITMODE(_type) \
- (ELEM(_type, OB_MESH, OB_FONT, OB_CURVE, OB_SURF, OB_MBALL, OB_LATTICE, OB_ARMATURE))
+ (ELEM(_type, OB_MESH, OB_FONT, OB_CURVE, OB_SURF, OB_MBALL, OB_LATTICE, OB_ARMATURE, OB_GROOM))
#define OB_TYPE_SUPPORT_PARVERT(_type) \
(ELEM(_type, OB_MESH, OB_SURF, OB_CURVE, OB_LATTICE))
/* is this ID type used as object data */
#define OB_DATA_SUPPORT_ID(_id_type) \
- (ELEM(_id_type, ID_ME, ID_CU, ID_MB, ID_LA, ID_SPK, ID_LP, ID_CA, ID_LT, ID_AR))
+ (ELEM(_id_type, ID_ME, ID_CU, ID_MB, ID_LA, ID_SPK, ID_LP, ID_CA, ID_LT, ID_AR, ID_GM))
#define OB_DATA_SUPPORT_ID_CASE \
- ID_ME: case ID_CU: case ID_MB: case ID_LA: case ID_SPK: case ID_LP: case ID_CA: case ID_LT: case ID_AR
+ ID_ME: case ID_CU: case ID_MB: case ID_LA: case ID_SPK: case ID_LP: case ID_CA: case ID_LT: case ID_AR: case ID_GM
/* partype: first 4 bits: type */
enum {
diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h
index 696918d97a5..4b27190d79f 100644
--- a/source/blender/makesdna/DNA_scene_types.h
+++ b/source/blender/makesdna/DNA_scene_types.h
@@ -1102,6 +1102,22 @@ typedef struct ParticleEditSettings {
} ParticleEditSettings;
/* ------------------------------------------- */
+/* Groom Edit */
+
+/* GroomEditSettings.mode */
+typedef enum GroomEditMode {
+ GM_EDIT_MODE_REGIONS,
+ GM_EDIT_MODE_CURVES,
+ GM_EDIT_MODE_SECTIONS,
+} GroomEditMode;
+
+/* Groom Edit Mode Settings */
+typedef struct GroomEditSettings {
+ int mode;
+ int pad;
+} GroomEditSettings;
+
+/* ------------------------------------------- */
/* Sculpt */
/* Sculpt */
@@ -1491,7 +1507,10 @@ typedef struct ToolSettings {
/* Particle Editing */
struct ParticleEditSettings particle;
-
+
+ /* Groom Editing */
+ struct GroomEditSettings groom_edit_settings;
+
/* Transform Proportional Area of Effect */
float proportional_size;
diff --git a/source/blender/makesdna/intern/makesdna.c b/source/blender/makesdna/intern/makesdna.c
index f0f1c2210f0..e66ea248e7c 100644
--- a/source/blender/makesdna/intern/makesdna.c
+++ b/source/blender/makesdna/intern/makesdna.c
@@ -133,6 +133,8 @@ static const char *includefiles[] = {
"DNA_layer_types.h",
"DNA_workspace_types.h",
"DNA_lightprobe_types.h",
+ "DNA_hair_types.h",
+ "DNA_groom_types.h",
/* see comment above before editing! */
@@ -1360,5 +1362,7 @@ int main(int argc, char **argv)
#include "DNA_layer_types.h"
#include "DNA_workspace_types.h"
#include "DNA_lightprobe_types.h"
+#include "DNA_hair_types.h"
+#include "DNA_groom_types.h"
/* end of list */
diff --git a/source/blender/makesrna/RNA_access.h b/source/blender/makesrna/RNA_access.h
index 562855c01cc..d5752ff3e5e 100644
--- a/source/blender/makesrna/RNA_access.h
+++ b/source/blender/makesrna/RNA_access.h
@@ -267,6 +267,7 @@ extern StructRNA RNA_FreestyleLineSet;
extern StructRNA RNA_FreestyleModuleSettings;
extern StructRNA RNA_FreestyleSettings;
extern StructRNA RNA_Function;
+extern StructRNA RNA_FurModifier;
extern StructRNA RNA_GPencilFrame;
extern StructRNA RNA_GPencilLayer;
extern StructRNA RNA_GPencilPalette;
@@ -288,7 +289,11 @@ extern StructRNA RNA_GameTimerProperty;
extern StructRNA RNA_GaussianBlurSequence;
extern StructRNA RNA_GlowSequence;
extern StructRNA RNA_GreasePencil;
+extern StructRNA RNA_Groom;
+extern StructRNA RNA_GroomBundle;
extern StructRNA RNA_Group;
+extern StructRNA RNA_HairGroup;
+extern StructRNA RNA_HairPattern;
extern StructRNA RNA_Header;
extern StructRNA RNA_HemiLamp;
extern StructRNA RNA_Histogram;
diff --git a/source/blender/makesrna/intern/CMakeLists.txt b/source/blender/makesrna/intern/CMakeLists.txt
index 74c36c456b0..bac0ecd1e4d 100644
--- a/source/blender/makesrna/intern/CMakeLists.txt
+++ b/source/blender/makesrna/intern/CMakeLists.txt
@@ -51,7 +51,9 @@ set(DEFSRC
rna_fcurve.c
rna_fluidsim.c
rna_gpencil.c
+ rna_groom.c
rna_group.c
+ rna_hair.c
rna_image.c
rna_key.c
rna_lamp.c
@@ -62,6 +64,7 @@ set(DEFSRC
rna_mask.c
rna_material.c
rna_mesh.c
+ rna_mesh_sample.c
rna_meta.c
rna_modifier.c
rna_movieclip.c
diff --git a/source/blender/makesrna/intern/makesrna.c b/source/blender/makesrna/intern/makesrna.c
index 9745ca39872..ab1eaca52e4 100644
--- a/source/blender/makesrna/intern/makesrna.c
+++ b/source/blender/makesrna/intern/makesrna.c
@@ -3366,7 +3366,9 @@ static RNAProcessItem PROCESS_ITEMS[] = {
{"rna_fcurve.c", "rna_fcurve_api.c", RNA_def_fcurve},
{"rna_fluidsim.c", NULL, RNA_def_fluidsim},
{"rna_gpencil.c", NULL, RNA_def_gpencil},
+ {"rna_groom.c", NULL, RNA_def_groom},
{"rna_group.c", NULL, RNA_def_group},
+ {"rna_hair.c", NULL, RNA_def_hair},
{"rna_image.c", "rna_image_api.c", RNA_def_image},
{"rna_key.c", NULL, RNA_def_key},
{"rna_lamp.c", NULL, RNA_def_lamp},
@@ -3376,6 +3378,7 @@ static RNAProcessItem PROCESS_ITEMS[] = {
{"rna_main.c", "rna_main_api.c", RNA_def_main},
{"rna_material.c", "rna_material_api.c", RNA_def_material},
{"rna_mesh.c", "rna_mesh_api.c", RNA_def_mesh},
+ {"rna_mesh_sample.c", NULL, RNA_def_mesh_sample},
{"rna_meta.c", "rna_meta_api.c", RNA_def_meta},
{"rna_modifier.c", NULL, RNA_def_modifier},
{"rna_nla.c", NULL, RNA_def_nla},
diff --git a/source/blender/makesrna/intern/rna_ID.c b/source/blender/makesrna/intern/rna_ID.c
index 088b2b67af5..dd062ac5ace 100644
--- a/source/blender/makesrna/intern/rna_ID.c
+++ b/source/blender/makesrna/intern/rna_ID.c
@@ -198,6 +198,7 @@ StructRNA *ID_code_to_RNA_type(short idcode)
case ID_CF: return &RNA_CacheFile;
case ID_CU: return &RNA_Curve;
case ID_GD: return &RNA_GreasePencil;
+ case ID_GM: return &RNA_Groom;
case ID_GR: return &RNA_Group;
case ID_IM: return &RNA_Image;
case ID_KE: return &RNA_Key;
diff --git a/source/blender/makesrna/intern/rna_groom.c b/source/blender/makesrna/intern/rna_groom.c
new file mode 100644
index 00000000000..67c0b3a02dd
--- /dev/null
+++ b/source/blender/makesrna/intern/rna_groom.c
@@ -0,0 +1,231 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) Blender Foundation
+ * All rights reserved.
+ *
+ * The Original Code is: all of this file.
+ *
+ * Contributor(s): Lukas Toenne
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file blender/makesrna/intern/rna_groom.c
+ * \ingroup RNA
+ */
+
+#include <stdlib.h>
+
+#include "DNA_groom_types.h"
+#include "DNA_scene_types.h"
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_string_utils.h"
+#include "BLI_utildefines.h"
+
+#include "BLT_translation.h"
+
+#include "RNA_access.h"
+#include "RNA_define.h"
+
+#include "rna_internal.h"
+
+#include "WM_types.h"
+#include "DNA_object_types.h"
+
+
+#ifdef RNA_RUNTIME
+
+#include "BLI_listbase.h"
+#include "BLI_math.h"
+
+#include "WM_api.h"
+
+#include "BKE_groom.h"
+#include "BKE_object_facemap.h"
+
+#include "DEG_depsgraph.h"
+
+static void UNUSED_FUNCTION(rna_Groom_update)(Main *UNUSED(bmain), Scene *UNUSED(scene), PointerRNA *UNUSED(ptr))
+{
+ WM_main_add_notifier(NC_GROOM | NA_EDITED, NULL);
+}
+
+static void rna_Groom_update_data(Main *UNUSED(bmain), Scene *UNUSED(scene), PointerRNA *ptr)
+{
+ DEG_id_tag_update(ptr->id.data, 0);
+ WM_main_add_notifier(NC_GROOM | ND_DATA, ptr->id.data);
+}
+
+static int rna_GroomBundle_is_bound_get(PointerRNA *ptr)
+{
+ GroomBundle *bundle = (GroomBundle *)ptr->data;
+ return (bundle->scalp_region != NULL);
+}
+
+static void rna_GroomBundle_scalp_facemap_name_set(PointerRNA *ptr, const char *value)
+{
+ Groom *groom = (Groom *)ptr->id.data;
+ GroomBundle *bundle = (GroomBundle *)ptr->data;
+
+ if (groom->scalp_object)
+ {
+ bFaceMap *fm = BKE_object_facemap_find_name(groom->scalp_object, value);
+ if (fm) {
+ /* no need for BLI_strncpy_utf8, since this matches an existing facemap */
+ BLI_strncpy(bundle->scalp_facemap_name, value, sizeof(bundle->scalp_facemap_name));
+ return;
+ }
+ }
+
+ bundle->scalp_facemap_name[0] = '\0';
+}
+
+static PointerRNA rna_Groom_active_bundle_get(PointerRNA *ptr)
+{
+ Groom *groom = (Groom *)ptr->id.data;
+ PointerRNA r_ptr;
+ RNA_pointer_create(&groom->id, &RNA_GroomBundle, BLI_findlink(&groom->bundles, groom->active_bundle), &r_ptr);
+ return r_ptr;
+}
+
+static int rna_Groom_active_bundle_index_get(PointerRNA *ptr)
+{
+ Groom *groom = (Groom *)ptr->id.data;
+ return groom->active_bundle;
+}
+
+static void rna_Groom_active_bundle_index_set(PointerRNA *ptr, int value)
+{
+ Groom *groom = (Groom *)ptr->id.data;
+ groom->active_bundle = value;
+}
+
+static void rna_Groom_active_bundle_index_range(
+ PointerRNA *ptr,
+ int *min,
+ int *max,
+ int *UNUSED(softmin),
+ int *UNUSED(softmax))
+{
+ Groom *groom = (Groom *)ptr->id.data;
+ *min = 0;
+ *max = max_ii(0, BLI_listbase_count(&groom->bundles) - 1);
+}
+
+#else
+
+static void rna_def_groom_bundle(BlenderRNA *brna)
+{
+ StructRNA *srna;
+ PropertyRNA *prop;
+
+ srna = RNA_def_struct(brna, "GroomBundle", NULL);
+ RNA_def_struct_sdna(srna, "GroomBundle");
+ RNA_def_struct_ui_text(srna, "Groom Bundle", "Bundle of hair originating from a scalp region");
+
+ prop = RNA_def_property(srna, "is_bound", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_funcs(prop, "rna_GroomBundle_is_bound_get", NULL);
+ RNA_def_property_clear_flag(prop, PROP_EDITABLE);
+ RNA_def_property_ui_text(prop, "Bound", "Bundle was successfully bound to a scalp region");
+ RNA_def_property_update(prop, NC_GROOM | ND_DRAW, NULL);
+
+ prop = RNA_def_property(srna, "scalp_facemap", PROP_STRING, PROP_NONE);
+ RNA_def_property_string_sdna(prop, NULL, "scalp_facemap_name");
+ RNA_def_property_ui_text(prop, "Scalp Vertex Group", "Face map name of the scalp region");
+ RNA_def_property_string_funcs(prop, NULL, NULL, "rna_GroomBundle_scalp_facemap_name_set");
+ RNA_def_property_update(prop, 0, "rna_Groom_update_data");
+}
+
+/* groom.bundles */
+static void rna_def_groom_bundles(BlenderRNA *brna, PropertyRNA *cprop)
+{
+ StructRNA *srna;
+ PropertyRNA *prop;
+
+ RNA_def_property_srna(cprop, "GroomBundles");
+ srna = RNA_def_struct(brna, "GroomBundles", NULL);
+ RNA_def_struct_sdna(srna, "Groom");
+ RNA_def_struct_ui_text(srna, "Groom Bundles", "Collection of groom bundles");
+
+ prop = RNA_def_property(srna, "active", PROP_POINTER, PROP_NONE);
+ RNA_def_property_struct_type(prop, "GroomBundle");
+ RNA_def_property_pointer_funcs(prop, "rna_Groom_active_bundle_get", NULL, NULL, NULL);
+ RNA_def_property_ui_text(prop, "Active Groom Bundle", "Active groom bundle being displayed");
+ RNA_def_property_update(prop, NC_GROOM | ND_DRAW, NULL);
+
+ prop = RNA_def_property(srna, "active_index", PROP_INT, PROP_UNSIGNED);
+ RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);
+ RNA_def_property_int_funcs(prop, "rna_Groom_active_bundle_index_get",
+ "rna_Groom_active_bundle_index_set",
+ "rna_Groom_active_bundle_index_range");
+ RNA_def_property_ui_text(prop, "Active Groom Bundle Index", "Index of active groom bundle");
+ RNA_def_property_update(prop, NC_GROOM | ND_DRAW, NULL);
+}
+
+static void rna_def_groom(BlenderRNA *brna)
+{
+ StructRNA *srna;
+ PropertyRNA *prop;
+
+ srna = RNA_def_struct(brna, "Groom", "ID");
+ RNA_def_struct_sdna(srna, "Groom");
+ RNA_def_struct_ui_text(srna, "Groom", "Guide curve geometry for hair");
+ RNA_def_struct_ui_icon(srna, ICON_NONE);
+
+ /* Animation Data */
+ rna_def_animdata_common(srna);
+
+ prop = RNA_def_property(srna, "bundles", PROP_COLLECTION, PROP_NONE);
+ RNA_def_property_collection_sdna(prop, NULL, "bundles", NULL);
+ RNA_def_property_struct_type(prop, "GroomBundle");
+ RNA_def_property_ui_text(prop, "Bundles", "Bundles of hair");
+ rna_def_groom_bundles(brna, prop);
+
+ prop = RNA_def_property(srna, "curve_resolution", PROP_INT, PROP_NONE);
+ RNA_def_property_int_sdna(prop, NULL, "curve_res");
+ RNA_def_property_range(prop, 1, 1024);
+ RNA_def_property_ui_range(prop, 1, 64, 1, -1);
+ RNA_def_property_ui_text(prop, "Curve Resolution", "Curve subdivisions per segment");
+ RNA_def_property_update(prop, 0, "rna_Groom_update_data");
+
+ prop = RNA_def_property(srna, "hair_system", PROP_POINTER, PROP_NONE);
+ RNA_def_property_ui_text(prop, "Hair", "Hair data");
+ RNA_def_property_clear_flag(prop, PROP_EDITABLE);
+
+ prop = RNA_def_property(srna, "hair_draw_settings", PROP_POINTER, PROP_NONE);
+ RNA_def_property_ui_text(prop, "Hair Draw Settings", "Hair draw settings");
+ RNA_def_property_clear_flag(prop, PROP_EDITABLE);
+
+ prop = RNA_def_property(srna, "scalp_object", PROP_POINTER, PROP_NONE);
+ RNA_def_property_pointer_sdna(prop, NULL, "scalp_object");
+ RNA_def_property_ui_text(prop, "Scalp Object", "Surface for attaching hairs");
+ RNA_def_property_flag(prop, PROP_EDITABLE);
+ RNA_def_property_update(prop, 0, "rna_Groom_update_data");
+
+ UNUSED_VARS(prop);
+}
+
+void RNA_def_groom(BlenderRNA *brna)
+{
+ rna_def_groom(brna);
+ rna_def_groom_bundle(brna);
+}
+
+#endif
diff --git a/source/blender/makesrna/intern/rna_hair.c b/source/blender/makesrna/intern/rna_hair.c
new file mode 100644
index 00000000000..079ce5c6a07
--- /dev/null
+++ b/source/blender/makesrna/intern/rna_hair.c
@@ -0,0 +1,231 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Contributor(s): Blender Foundation.
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file blender/makesrna/intern/rna_hair.c
+ * \ingroup RNA
+ */
+
+#include <stdlib.h>
+
+#include "RNA_define.h"
+#include "RNA_enum_types.h"
+
+#include "rna_internal.h"
+
+#include "DNA_hair_types.h"
+
+#include "WM_types.h"
+
+#ifdef RNA_RUNTIME
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_listbase.h"
+
+#include "DNA_object_types.h"
+
+#include "BKE_context.h"
+#include "BKE_DerivedMesh.h"
+#include "BKE_hair.h"
+#include "BKE_main.h"
+#include "BKE_material.h"
+
+#include "DEG_depsgraph.h"
+
+#include "RNA_access.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+static void rna_HairSystem_update(Main *UNUSED(bmain), Scene *UNUSED(scene), PointerRNA *ptr)
+{
+ DEG_id_tag_update(ptr->id.data, OB_RECALC_DATA);
+}
+
+static void rna_HairSystem_generate_follicles(
+ HairSystem *hsys,
+ struct bContext *C,
+ Object *scalp,
+ int seed,
+ int count)
+{
+ if (!scalp)
+ {
+ return;
+ }
+
+ struct Scene *scene = CTX_data_scene(C);
+ EvaluationContext eval_ctx;
+ CTX_data_eval_ctx(C, &eval_ctx);
+
+ CustomDataMask datamask = CD_MASK_BAREMESH;
+ DerivedMesh *dm = mesh_get_derived_final(&eval_ctx, scene, scalp, datamask);
+
+ BKE_hair_generate_follicles(hsys, dm, (unsigned int)seed, count);
+}
+
+static const EnumPropertyItem *rna_HairSystem_material_slot_itemf(
+ bContext *C,
+ PointerRNA *UNUSED(ptr),
+ PropertyRNA *UNUSED(prop),
+ bool *r_free)
+{
+ Object *ob = CTX_data_pointer_get(C, "object").data;
+ Material *ma;
+ EnumPropertyItem *item = NULL;
+ EnumPropertyItem tmp = {0, "", 0, "", ""};
+ int totitem = 0;
+ int i;
+
+ if (ob && ob->totcol > 0) {
+ for (i = 1; i <= ob->totcol; i++) {
+ ma = give_current_material(ob, i);
+ tmp.value = i;
+ tmp.icon = ICON_MATERIAL_DATA;
+ if (ma) {
+ tmp.name = ma->id.name + 2;
+ tmp.identifier = tmp.name;
+ }
+ else {
+ tmp.name = "Default Material";
+ tmp.identifier = tmp.name;
+ }
+ RNA_enum_item_add(&item, &totitem, &tmp);
+ }
+ }
+ else {
+ tmp.value = 1;
+ tmp.icon = ICON_MATERIAL_DATA;
+ tmp.name = "Default Material";
+ tmp.identifier = tmp.name;
+ RNA_enum_item_add(&item, &totitem, &tmp);
+ }
+
+ RNA_enum_item_end(&item, &totitem);
+ *r_free = true;
+
+ return item;
+}
+
+#else
+
+static void rna_def_hair_follicle(BlenderRNA *brna)
+{
+ StructRNA *srna;
+ PropertyRNA *prop;
+
+ srna = RNA_def_struct(brna, "HairFollicle", NULL);
+ RNA_def_struct_ui_text(srna, "Hair Follicle", "Single follicle on a surface");
+ RNA_def_struct_sdna(srna, "HairFollicle");
+
+ prop = RNA_def_property(srna, "mesh_sample", PROP_POINTER, PROP_NONE);
+ RNA_def_property_struct_type(prop, "MeshSample");
+}
+
+static void rna_def_hair_pattern(BlenderRNA *brna)
+{
+ StructRNA *srna;
+ PropertyRNA *prop;
+
+ srna = RNA_def_struct(brna, "HairPattern", NULL);
+ RNA_def_struct_ui_text(srna, "Hair Pattern", "Set of hair follicles distributed on a surface");
+ RNA_def_struct_sdna(srna, "HairPattern");
+ RNA_def_struct_ui_icon(srna, ICON_STRANDS);
+
+ prop = RNA_def_property(srna, "follicles", PROP_COLLECTION, PROP_NONE);
+ RNA_def_property_collection_sdna(prop, NULL, "follicles", "num_follicles");
+ RNA_def_property_struct_type(prop, "HairFollicle");
+ RNA_def_property_ui_text(prop, "Follicles", "Hair fiber follicles");
+}
+
+static void rna_def_hair_system(BlenderRNA *brna)
+{
+ StructRNA *srna;
+ FunctionRNA *func;
+ PropertyRNA *prop, *parm;
+
+ static const EnumPropertyItem material_slot_items[] = {
+ {0, "DUMMY", 0, "Dummy", ""},
+ {0, NULL, 0, NULL, NULL}
+ };
+
+ srna = RNA_def_struct(brna, "HairSystem", NULL);
+ RNA_def_struct_ui_text(srna, "Hair System", "Hair rendering and deformation data");
+ RNA_def_struct_sdna(srna, "HairSystem");
+ RNA_def_struct_ui_icon(srna, ICON_STRANDS);
+
+ prop = RNA_def_property(srna, "pattern", PROP_POINTER, PROP_NONE);
+ RNA_def_property_struct_type(prop, "HairPattern");
+ RNA_def_property_ui_text(prop, "Pattern", "Hair pattern");
+
+ prop = RNA_def_property(srna, "material_index", PROP_INT, PROP_NONE);
+ RNA_def_property_int_sdna(prop, NULL, "material_index");
+ RNA_def_property_range(prop, 1, 32767);
+ RNA_def_property_ui_text(prop, "Material Index", "Index of material slot used for rendering hair fibers");
+ RNA_def_property_update(prop, 0, "rna_HairSystem_update");
+
+ prop = RNA_def_property(srna, "material_slot", PROP_ENUM, PROP_NONE);
+ RNA_def_property_enum_sdna(prop, NULL, "material_index");
+ RNA_def_property_enum_items(prop, material_slot_items);
+ RNA_def_property_enum_funcs(prop, NULL, NULL, "rna_HairSystem_material_slot_itemf");
+ RNA_def_property_ui_text(prop, "Material Slot", "Material slot used for rendering particles");
+ RNA_def_property_update(prop, 0, "rna_HairSystem_update");
+
+ func = RNA_def_function(srna, "generate_follicles", "rna_HairSystem_generate_follicles");
+ RNA_def_function_flag(func, FUNC_USE_CONTEXT);
+ parm = RNA_def_pointer(func, "scalp", "Object", "Scalp", "Scalp object on which to place hair follicles");
+ RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
+ parm = RNA_def_int(func, "seed", 0, 0, INT_MAX, "Seed", "Seed value for random numbers", 0, INT_MAX);
+ parm = RNA_def_int(func, "count", 0, 0, INT_MAX, "Count", "Maximum number of follicles to generate", 1, 1e5);
+ RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
+}
+
+static void rna_def_hair_draw_settings(BlenderRNA *brna)
+{
+ StructRNA *srna;
+ PropertyRNA *prop;
+
+ static const EnumPropertyItem follicle_mode_items[] = {
+ {HAIR_DRAW_FOLLICLE_NONE, "NONE", 0, "None", ""},
+ {HAIR_DRAW_FOLLICLE_POINTS, "POINTS", 0, "Points", "Draw a point for each follicle"},
+ {HAIR_DRAW_FOLLICLE_AXES, "AXES", 0, "Axes", "Draw direction of hair for each follicle"},
+ {0, NULL, 0, NULL, NULL}
+ };
+
+ srna = RNA_def_struct(brna, "HairDrawSettings", NULL);
+ RNA_def_struct_ui_text(srna, "Hair Draw Settings", "Settings for drawing hair systems");
+ RNA_def_struct_sdna(srna, "HairDrawSettings");
+
+ prop = RNA_def_property(srna, "follicle_mode", PROP_ENUM, PROP_NONE);
+ RNA_def_property_enum_items(prop, follicle_mode_items);
+ RNA_def_property_ui_text(prop, "Follicle Mode", "Draw follicles on the scalp surface");
+}
+
+void RNA_def_hair(BlenderRNA *brna)
+{
+ rna_def_hair_follicle(brna);
+ rna_def_hair_pattern(brna);
+ rna_def_hair_system(brna);
+ rna_def_hair_draw_settings(brna);
+}
+
+#endif
diff --git a/source/blender/makesrna/intern/rna_internal.h b/source/blender/makesrna/intern/rna_internal.h
index c1f82bab300..8bc82e90999 100644
--- a/source/blender/makesrna/intern/rna_internal.h
+++ b/source/blender/makesrna/intern/rna_internal.h
@@ -153,6 +153,7 @@ void RNA_def_fluidsim(struct BlenderRNA *brna);
void RNA_def_fcurve(struct BlenderRNA *brna);
void RNA_def_gameproperty(struct BlenderRNA *brna);
void RNA_def_gpencil(struct BlenderRNA *brna);
+void RNA_def_groom(struct BlenderRNA *brna);
void RNA_def_group(struct BlenderRNA *brna);
void RNA_def_image(struct BlenderRNA *brna);
void RNA_def_key(struct BlenderRNA *brna);
@@ -162,6 +163,7 @@ void RNA_def_linestyle(struct BlenderRNA *brna);
void RNA_def_main(struct BlenderRNA *brna);
void RNA_def_material(struct BlenderRNA *brna);
void RNA_def_mesh(struct BlenderRNA *brna);
+void RNA_def_mesh_sample(struct BlenderRNA *brna);
void RNA_def_meta(struct BlenderRNA *brna);
void RNA_def_modifier(struct BlenderRNA *brna);
void RNA_def_nla(struct BlenderRNA *brna);
@@ -200,6 +202,7 @@ void RNA_def_world(struct BlenderRNA *brna);
void RNA_def_movieclip(struct BlenderRNA *brna);
void RNA_def_tracking(struct BlenderRNA *brna);
void RNA_def_mask(struct BlenderRNA *brna);
+void RNA_def_hair(struct BlenderRNA *brna);
/* Common Define functions */
diff --git a/source/blender/makesrna/intern/rna_main_api.c b/source/blender/makesrna/intern/rna_main_api.c
index 077dac262b6..42c8eceeb16 100644
--- a/source/blender/makesrna/intern/rna_main_api.c
+++ b/source/blender/makesrna/intern/rna_main_api.c
@@ -234,6 +234,9 @@ static Object *rna_Main_objects_new(Main *bmain, ReportList *reports, const char
case ID_AR:
type = OB_ARMATURE;
break;
+ case ID_GM:
+ type = OB_GROOM;
+ break;
default:
{
const char *idname;
diff --git a/source/blender/makesrna/intern/rna_mesh_sample.c b/source/blender/makesrna/intern/rna_mesh_sample.c
new file mode 100644
index 00000000000..05e22fc48a2
--- /dev/null
+++ b/source/blender/makesrna/intern/rna_mesh_sample.c
@@ -0,0 +1,73 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file blender/makesrna/intern/rna_mesh_sample.c
+ * \ingroup RNA
+ */
+
+#include <stdlib.h>
+
+#include "MEM_guardedalloc.h"
+
+#include "DNA_meshdata_types.h"
+
+#include "BLI_utildefines.h"
+
+#include "BKE_mesh_sample.h"
+
+#include "RNA_access.h"
+#include "RNA_define.h"
+#include "RNA_types.h"
+
+#include "rna_internal.h"
+
+#include "WM_types.h"
+
+
+#ifdef RNA_RUNTIME
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+
+
+#else
+
+static void rna_def_mesh_sample(BlenderRNA *brna)
+{
+ StructRNA *srna;
+ PropertyRNA *prop;
+
+ srna = RNA_def_struct(brna, "MeshSample", NULL);
+ RNA_def_struct_sdna(srna, "MeshSample");
+ RNA_def_struct_ui_text(srna, "Mesh Sample", "Point on a mesh that follows deformation");
+
+ prop = RNA_def_property(srna, "vertex_indices", PROP_INT, PROP_UNSIGNED);
+ RNA_def_property_int_sdna(prop, NULL, "orig_verts");
+ RNA_def_property_clear_flag(prop, PROP_EDITABLE);
+ RNA_def_property_ui_text(prop, "Vertex Indices", "Index of the mesh vertices used for interpolation");
+}
+
+void RNA_def_mesh_sample(BlenderRNA *brna)
+{
+ rna_def_mesh_sample(brna);
+}
+
+#endif
diff --git a/source/blender/makesrna/intern/rna_modifier.c b/source/blender/makesrna/intern/rna_modifier.c
index 99417ee7b1b..579c865e679 100644
--- a/source/blender/makesrna/intern/rna_modifier.c
+++ b/source/blender/makesrna/intern/rna_modifier.c
@@ -114,6 +114,7 @@ const EnumPropertyItem rna_enum_object_modifier_type_items[] = {
{eModifierType_DynamicPaint, "DYNAMIC_PAINT", ICON_MOD_DYNAMICPAINT, "Dynamic Paint", ""},
{eModifierType_Explode, "EXPLODE", ICON_MOD_EXPLODE, "Explode", ""},
{eModifierType_Fluidsim, "FLUID_SIMULATION", ICON_MOD_FLUIDSIM, "Fluid Simulation", ""},
+ {eModifierType_Fur, "FUR", ICON_STRANDS, "Fur", ""},
{eModifierType_Ocean, "OCEAN", ICON_MOD_OCEAN, "Ocean", ""},
{eModifierType_ParticleInstance, "PARTICLE_INSTANCE", ICON_MOD_PARTICLES, "Particle Instance", ""},
{eModifierType_ParticleSystem, "PARTICLE_SYSTEM", ICON_MOD_PARTICLES, "Particle System", ""},
@@ -413,6 +414,8 @@ static StructRNA *rna_Modifier_refine(struct PointerRNA *ptr)
return &RNA_MeshSequenceCacheModifier;
case eModifierType_SurfaceDeform:
return &RNA_SurfaceDeformModifier;
+ case eModifierType_Fur:
+ return &RNA_FurModifier;
/* Default */
case eModifierType_None:
case eModifierType_ShapeKey:
@@ -4806,6 +4809,41 @@ static void rna_def_modifier_surfacedeform(BlenderRNA *brna)
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
}
+static void rna_def_modifier_fur(BlenderRNA *brna)
+{
+ StructRNA *srna;
+ PropertyRNA *prop;
+
+ srna = RNA_def_struct(brna, "FurModifier", "Modifier");
+ RNA_def_struct_ui_text(srna, "Fur Modifier", "");
+ RNA_def_struct_sdna(srna, "FurModifierData");
+ RNA_def_struct_ui_icon(srna, ICON_STRANDS);
+
+ prop = RNA_def_property(srna, "hair_system", PROP_POINTER, PROP_NONE);
+ RNA_def_property_ui_text(prop, "Hair", "Hair data");
+ RNA_def_property_clear_flag(prop, PROP_EDITABLE);
+
+ prop = RNA_def_property(srna, "follicle_seed", PROP_INT, PROP_NONE);
+ RNA_def_property_range(prop, 0, INT_MAX);
+ RNA_def_property_ui_text(prop, "Seed", "Follicle distribution random seed value");
+
+ prop = RNA_def_property(srna, "follicle_count", PROP_INT, PROP_NONE);
+ RNA_def_property_int_default(prop, 100000);
+ RNA_def_property_range(prop, 0, INT_MAX);
+ RNA_def_property_ui_range(prop, 1, 1e5, 1, 1);
+ RNA_def_property_ui_text(prop, "Follicle Count", "Maximum number of follicles");
+
+ prop = RNA_def_property(srna, "guides_count", PROP_INT, PROP_NONE);
+ RNA_def_property_int_default(prop, 1000);
+ RNA_def_property_range(prop, 0, INT_MAX);
+ RNA_def_property_ui_range(prop, 1, 1e3, 1, 1);
+ RNA_def_property_ui_text(prop, "Guides Count", "Maximum number of guide curves");
+
+ prop = RNA_def_property(srna, "draw_settings", PROP_POINTER, PROP_NONE);
+ RNA_def_property_ui_text(prop, "Draw Settings", "Hair draw settings");
+ RNA_def_property_clear_flag(prop, PROP_EDITABLE);
+}
+
void RNA_def_modifier(BlenderRNA *brna)
{
StructRNA *srna;
@@ -4924,6 +4962,7 @@ void RNA_def_modifier(BlenderRNA *brna)
rna_def_modifier_normaledit(brna);
rna_def_modifier_meshseqcache(brna);
rna_def_modifier_surfacedeform(brna);
+ rna_def_modifier_fur(brna);
}
#endif
diff --git a/source/blender/makesrna/intern/rna_object.c b/source/blender/makesrna/intern/rna_object.c
index 1932390d0fd..43001d6bd66 100644
--- a/source/blender/makesrna/intern/rna_object.c
+++ b/source/blender/makesrna/intern/rna_object.c
@@ -162,6 +162,7 @@ const EnumPropertyItem rna_enum_object_type_items[] = {
{OB_LAMP, "LAMP", 0, "Lamp", ""},
{OB_SPEAKER, "SPEAKER", 0, "Speaker", ""},
{OB_LIGHTPROBE, "LIGHT_PROBE", 0, "Probe", ""},
+ {OB_GROOM, "GROOM", 0, "Groom", ""},
{0, NULL, 0, NULL, NULL}
};
@@ -407,6 +408,7 @@ static StructRNA *rna_Object_data_typef(PointerRNA *ptr)
case OB_ARMATURE: return &RNA_Armature;
case OB_SPEAKER: return &RNA_Speaker;
case OB_LIGHTPROBE: return &RNA_LightProbe;
+ case OB_GROOM: return &RNA_Groom;
default: return &RNA_ID;
}
}
diff --git a/source/blender/makesrna/intern/rna_scene.c b/source/blender/makesrna/intern/rna_scene.c
index 3c600f6e367..3782e694d73 100644
--- a/source/blender/makesrna/intern/rna_scene.c
+++ b/source/blender/makesrna/intern/rna_scene.c
@@ -2441,6 +2441,10 @@ static void rna_def_tool_settings(BlenderRNA *brna)
RNA_def_property_pointer_sdna(prop, NULL, "particle");
RNA_def_property_ui_text(prop, "Particle Edit", "");
+ prop = RNA_def_property(srna, "groom_edit_settings", PROP_POINTER, PROP_NONE);
+ RNA_def_property_struct_type(prop, "GroomEditSettings");
+ RNA_def_property_ui_text(prop, "Groom Edit Settings", "");
+
prop = RNA_def_property(srna, "use_uv_sculpt", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "use_uv_sculpt", 1);
RNA_def_property_ui_text(prop, "UV Sculpt", "Enable brush for UV sculpting");
diff --git a/source/blender/makesrna/intern/rna_sculpt_paint.c b/source/blender/makesrna/intern/rna_sculpt_paint.c
index 49edc742a4b..13833c86d9a 100644
--- a/source/blender/makesrna/intern/rna_sculpt_paint.c
+++ b/source/blender/makesrna/intern/rna_sculpt_paint.c
@@ -242,6 +242,23 @@ static char *rna_ParticleEdit_path(PointerRNA *UNUSED(ptr))
return BLI_strdup("tool_settings.particle_edit");
}
+static char *rna_GroomEditSettings_path(PointerRNA *UNUSED(ptr))
+{
+ return BLI_strdup("tool_settings.groom_edit_settings");
+}
+
+static void rna_GroomEditSettings_update(bContext *C, PointerRNA *UNUSED(ptr))
+{
+ ViewLayer *view_layer = CTX_data_view_layer(C);
+ Object *ob = OBACT(view_layer);
+
+ if (ob)
+ {
+ DEG_id_tag_update(&ob->id, OB_RECALC_DATA);
+ WM_event_add_notifier(C, NC_OBJECT | ND_DRAW, ob);
+ }
+}
+
static int rna_Brush_mode_poll(PointerRNA *ptr, PointerRNA value)
{
Scene *scene = (Scene *)ptr->id.data;
@@ -1052,6 +1069,29 @@ static void rna_def_particle_edit(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Curve", "");
}
+static void rna_def_groom_edit_settings(BlenderRNA *brna)
+{
+ StructRNA *srna;
+ PropertyRNA *prop;
+
+ static const EnumPropertyItem mode_items[] = {
+ {GM_EDIT_MODE_REGIONS, "REGIONS", ICON_NONE, "Regions", "Region edit mode"},
+ {GM_EDIT_MODE_CURVES, "CURVES", ICON_NONE, "Curves", "Curve edit mode"},
+ {GM_EDIT_MODE_SECTIONS, "SECTIONS", ICON_NONE, "Sections", "Section edit mode"},
+ {0, NULL, 0, NULL, NULL}
+ };
+
+ srna = RNA_def_struct(brna, "GroomEditSettings", NULL);
+ RNA_def_struct_path_func(srna, "rna_GroomEditSettings_path");
+ RNA_def_struct_ui_text(srna, "Groom Edit", "Properties of groom editing mode");
+
+ prop = RNA_def_property(srna, "mode", PROP_ENUM, PROP_NONE);
+ RNA_def_property_enum_items(prop, mode_items);
+ RNA_def_property_ui_text(prop, "Mode", "Select mode");
+ RNA_def_property_flag(prop, PROP_CONTEXT_UPDATE);
+ RNA_def_property_update(prop, NC_OBJECT | ND_DRAW, "rna_GroomEditSettings_update");
+}
+
static void rna_def_gpencil_sculpt(BlenderRNA *brna)
{
static const EnumPropertyItem prop_direction_items[] = {
@@ -1165,6 +1205,7 @@ void RNA_def_sculpt_paint(BlenderRNA *brna)
rna_def_vertex_paint(brna);
rna_def_image_paint(brna);
rna_def_particle_edit(brna);
+ rna_def_groom_edit_settings(brna);
rna_def_gpencil_sculpt(brna);
RNA_define_animate_sdna(true);
}
diff --git a/source/blender/modifiers/CMakeLists.txt b/source/blender/modifiers/CMakeLists.txt
index 397a3263e22..cf1021ec945 100644
--- a/source/blender/modifiers/CMakeLists.txt
+++ b/source/blender/modifiers/CMakeLists.txt
@@ -63,6 +63,7 @@ set(SRC
intern/MOD_explode.c
intern/MOD_fluidsim.c
intern/MOD_fluidsim_util.c
+ intern/MOD_fur.c
intern/MOD_hook.c
intern/MOD_laplaciandeform.c
intern/MOD_laplaciansmooth.c
diff --git a/source/blender/modifiers/MOD_modifiertypes.h b/source/blender/modifiers/MOD_modifiertypes.h
index bf121af2bd1..ff486287d7e 100644
--- a/source/blender/modifiers/MOD_modifiertypes.h
+++ b/source/blender/modifiers/MOD_modifiertypes.h
@@ -86,6 +86,7 @@ extern ModifierTypeInfo modifierType_NormalEdit;
extern ModifierTypeInfo modifierType_CorrectiveSmooth;
extern ModifierTypeInfo modifierType_MeshSequenceCache;
extern ModifierTypeInfo modifierType_SurfaceDeform;
+extern ModifierTypeInfo modifierType_Fur;
/* MOD_util.c */
void modifier_type_init(ModifierTypeInfo *types[]);
diff --git a/source/blender/modifiers/intern/MOD_fur.c b/source/blender/modifiers/intern/MOD_fur.c
new file mode 100644
index 00000000000..3334a60d735
--- /dev/null
+++ b/source/blender/modifiers/intern/MOD_fur.c
@@ -0,0 +1,162 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2005 by the Blender Foundation.
+ * All rights reserved.
+ *
+ * Contributor(s): Daniel Dunbar
+ * Ton Roosendaal,
+ * Ben Batt,
+ * Brecht Van Lommel,
+ * Campbell Barton
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ *
+ */
+
+/** \file blender/modifiers/intern/MOD_fur.c
+ * \ingroup modifiers
+ */
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_utildefines.h"
+
+#include "DNA_object_types.h"
+#include "DNA_hair_types.h"
+
+#include "BKE_cdderivedmesh.h"
+#include "BKE_hair.h"
+#include "BKE_library.h"
+#include "BKE_library_query.h"
+#include "BKE_modifier.h"
+
+#include "DEG_depsgraph_build.h"
+
+#include "MOD_util.h"
+
+
+static void initData(ModifierData *md)
+{
+ FurModifierData *fmd = (FurModifierData *) md;
+
+ fmd->hair_system = BKE_hair_new();
+
+ fmd->flag |= 0;
+
+ fmd->follicle_count = 100000;
+ fmd->guides_count = 1000;
+
+ fmd->draw_settings = BKE_hair_draw_settings_new();
+}
+
+static void copyData(ModifierData *md, ModifierData *target)
+{
+ FurModifierData *fmd = (FurModifierData *) md;
+ FurModifierData *tfmd = (FurModifierData *) target;
+
+ if (tfmd->hair_system) {
+ BKE_hair_free(tfmd->hair_system);
+ }
+ if (tfmd->draw_settings)
+ {
+ BKE_hair_draw_settings_free(tfmd->draw_settings);
+ }
+
+ modifier_copyData_generic(md, target);
+
+ if (fmd->hair_system) {
+ tfmd->hair_system = BKE_hair_copy(fmd->hair_system);
+ }
+ if (fmd->draw_settings)
+ {
+ tfmd->draw_settings = BKE_hair_draw_settings_copy(fmd->draw_settings);
+ }
+}
+
+static void freeData(ModifierData *md)
+{
+ FurModifierData *fmd = (FurModifierData *) md;
+
+ if (fmd->hair_system) {
+ BKE_hair_free(fmd->hair_system);
+ }
+ if (fmd->draw_settings)
+ {
+ BKE_hair_draw_settings_free(fmd->draw_settings);
+ }
+}
+
+static DerivedMesh *applyModifier(ModifierData *md, const struct EvaluationContext *UNUSED(eval_ctx),
+ Object *UNUSED(ob), DerivedMesh *dm,
+ ModifierApplyFlag UNUSED(flag))
+{
+ FurModifierData *fmd = (FurModifierData *) md;
+
+ UNUSED_VARS(fmd);
+
+ return dm;
+}
+
+static void foreachObjectLink(
+ ModifierData *md,
+ Object *ob,
+ ObjectWalkFunc walk,
+ void *userData)
+{
+ FurModifierData *fmd = (FurModifierData *) md;
+ UNUSED_VARS(ob, walk, userData, fmd);
+}
+
+static void foreachIDLink(
+ ModifierData *md,
+ Object *ob,
+ IDWalkFunc walk,
+ void *userData)
+{
+ FurModifierData *fmd = (FurModifierData *) md;
+ UNUSED_VARS(fmd);
+
+ foreachObjectLink(md, ob, (ObjectWalkFunc)walk, userData);
+}
+
+ModifierTypeInfo modifierType_Fur = {
+ /* name */ "Fur",
+ /* structName */ "FurModifierData",
+ /* structSize */ sizeof(FurModifierData),
+ /* type */ eModifierTypeType_NonGeometrical,
+ /* flags */ eModifierTypeFlag_AcceptsMesh |
+ eModifierTypeFlag_SupportsEditmode,
+
+ /* copyData */ copyData,
+ /* deformVerts */ NULL,
+ /* deformMatrices */ NULL,
+ /* deformVertsEM */ NULL,
+ /* deformMatricesEM */ NULL,
+ /* applyModifier */ applyModifier,
+ /* applyModifierEM */ NULL,
+ /* initData */ initData,
+ /* requiredDataMask */ NULL,
+ /* freeData */ freeData,
+ /* isDisabled */ NULL,
+ /* updateDepsgraph */ NULL,
+ /* dependsOnTime */ NULL,
+ /* dependsOnNormals */ NULL,
+ /* foreachObjectLink */ foreachObjectLink,
+ /* foreachIDLink */ foreachIDLink,
+ /* foreachTexLink */ NULL,
+};
diff --git a/source/blender/modifiers/intern/MOD_util.c b/source/blender/modifiers/intern/MOD_util.c
index 5b19bcf4817..54ba6320a29 100644
--- a/source/blender/modifiers/intern/MOD_util.c
+++ b/source/blender/modifiers/intern/MOD_util.c
@@ -288,5 +288,6 @@ void modifier_type_init(ModifierTypeInfo *types[])
INIT_TYPE(CorrectiveSmooth);
INIT_TYPE(MeshSequenceCache);
INIT_TYPE(SurfaceDeform);
+ INIT_TYPE(Fur);
#undef INIT_TYPE
}
diff --git a/source/blender/windowmanager/WM_types.h b/source/blender/windowmanager/WM_types.h
index 4bd5bcfc056..383744b898a 100644
--- a/source/blender/windowmanager/WM_types.h
+++ b/source/blender/windowmanager/WM_types.h
@@ -256,6 +256,7 @@ typedef struct wmNotifier {
#define NC_GPENCIL (22<<24)
#define NC_LINESTYLE (23<<24)
#define NC_CAMERA (24<<24)
+#define NC_GROOM (25<<24)
/* data type, 256 entries is enough, it can overlap */
#define NOTE_DATA 0x00FF0000
@@ -391,6 +392,7 @@ typedef struct wmNotifier {
#define NS_EDITMODE_ARMATURE (8<<8)
#define NS_MODE_POSE (9<<8)
#define NS_MODE_PARTICLE (10<<8)
+#define NS_EDITMODE_GROOM (11<<8)
/* subtype 3d view editing */
#define NS_VIEW3D_GPU (16<<8)
diff --git a/source/blender/windowmanager/intern/wm_keymap.c b/source/blender/windowmanager/intern/wm_keymap.c
index 0db67e0b199..6f42de73e61 100644
--- a/source/blender/windowmanager/intern/wm_keymap.c
+++ b/source/blender/windowmanager/intern/wm_keymap.c
@@ -1796,6 +1796,9 @@ wmKeyMap *WM_keymap_guess_opname(const bContext *C, const char *opname)
else if (STRPREFIX(opname, "PARTICLE_OT")) {
km = WM_keymap_find_all(C, "Particle", 0, 0);
}
+ else if (STRPREFIX(opname, "GROOM_OT")) {
+ km = WM_keymap_find_all(C, "Groom", 0, 0);
+ }
else if (STRPREFIX(opname, "FONT_OT")) {
km = WM_keymap_find_all(C, "Font", 0, 0);
}