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:
-rw-r--r--build_files/cmake/macros.cmake1
-rw-r--r--release/datafiles/brushicons/hairadd.pngbin0 -> 3341 bytes
-rw-r--r--release/datafiles/brushicons/haircomb.pngbin0 -> 3341 bytes
-rw-r--r--release/datafiles/brushicons/haircut.pngbin0 -> 3341 bytes
-rw-r--r--release/datafiles/brushicons/hairlength.pngbin0 -> 3341 bytes
-rw-r--r--release/datafiles/brushicons/hairpuff.pngbin0 -> 3341 bytes
-rw-r--r--release/datafiles/brushicons/hairsmooth.pngbin0 -> 3341 bytes
-rw-r--r--release/datafiles/brushicons/hairweight.pngbin0 -> 3341 bytes
m---------release/datafiles/locale0
m---------release/scripts/addons0
m---------release/scripts/addons_contrib0
-rw-r--r--release/scripts/startup/bl_operators/wm.py1
-rw-r--r--release/scripts/startup/bl_ui/__init__.py1
-rw-r--r--release/scripts/startup/bl_ui/properties_data_modifier.py18
-rw-r--r--release/scripts/startup/bl_ui/properties_hair.py40
-rw-r--r--release/scripts/startup/bl_ui/properties_paint_common.py4
-rw-r--r--release/scripts/startup/bl_ui/space_view3d.py36
-rw-r--r--release/scripts/startup/bl_ui/space_view3d_toolbar.py32
-rw-r--r--source/blender/blenkernel/BKE_context.h1
-rw-r--r--source/blender/blenkernel/BKE_customdata.h3
-rw-r--r--source/blender/blenkernel/BKE_editstrands.h106
-rw-r--r--source/blender/blenkernel/BKE_hair.h94
-rw-r--r--source/blender/blenkernel/BKE_mesh_sample.h77
-rw-r--r--source/blender/blenkernel/BKE_particle.h1
-rw-r--r--source/blender/blenkernel/CMakeLists.txt7
-rw-r--r--source/blender/blenkernel/intern/brush.c2
-rw-r--r--source/blender/blenkernel/intern/context.c2
-rw-r--r--source/blender/blenkernel/intern/customdata.c28
-rw-r--r--source/blender/blenkernel/intern/editstrands.c300
-rw-r--r--source/blender/blenkernel/intern/hair.c452
-rw-r--r--source/blender/blenkernel/intern/hair_draw.c606
-rw-r--r--source/blender/blenkernel/intern/mesh_sample.c855
-rw-r--r--source/blender/blenkernel/intern/object.c1
-rw-r--r--source/blender/blenkernel/intern/particle.c13
-rw-r--r--source/blender/blenkernel/intern/scene.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.c42
-rw-r--r--source/blender/blenloader/intern/writefile.c27
-rw-r--r--source/blender/bmesh/CMakeLists.txt4
-rw-r--r--source/blender/bmesh/bmesh.h2
-rw-r--r--source/blender/bmesh/intern/bmesh_interp.c28
-rw-r--r--source/blender/bmesh/intern/bmesh_interp.h6
-rw-r--r--source/blender/bmesh/intern/bmesh_iterators_inline.h3
-rw-r--r--source/blender/bmesh/intern/bmesh_mesh_conv.c20
-rw-r--r--source/blender/bmesh/intern/bmesh_mesh_conv.h4
-rw-r--r--source/blender/bmesh/intern/bmesh_operators_private.h1
-rw-r--r--source/blender/bmesh/intern/bmesh_strands.c158
-rw-r--r--source/blender/bmesh/intern/bmesh_strands.h216
-rw-r--r--source/blender/bmesh/intern/bmesh_strands_conv.c720
-rw-r--r--source/blender/bmesh/intern/bmesh_strands_conv.h62
-rw-r--r--source/blender/draw/CMakeLists.txt6
-rw-r--r--source/blender/draw/engines/eevee/eevee_materials.c413
-rw-r--r--source/blender/draw/engines/eevee/eevee_private.h27
-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.c35
-rw-r--r--source/blender/draw/intern/draw_cache.h14
-rw-r--r--source/blender/draw/intern/draw_cache_impl.h20
-rw-r--r--source/blender/draw/intern/draw_cache_impl_hair.c310
-rw-r--r--source/blender/draw/intern/draw_cache_impl_strands.c341
-rw-r--r--source/blender/draw/intern/draw_common.h17
-rw-r--r--source/blender/draw/intern/draw_hair.c61
-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_strands_mode.c308
-rw-r--r--source/blender/draw/modes/object_mode.c14
-rw-r--r--source/blender/draw/modes/shaders/edit_strands_vert.glsl28
-rw-r--r--source/blender/editors/CMakeLists.txt1
-rw-r--r--source/blender/editors/armature/armature_utils.c2
-rw-r--r--source/blender/editors/curve/editcurve.c2
-rw-r--r--source/blender/editors/curve/editfont_undo.c2
-rw-r--r--source/blender/editors/datafiles/CMakeLists.txt7
-rw-r--r--source/blender/editors/hair/CMakeLists.txt57
-rw-r--r--source/blender/editors/hair/SConscript54
-rw-r--r--source/blender/editors/hair/hair_cursor.c100
-rw-r--r--source/blender/editors/hair/hair_edit.c465
-rw-r--r--source/blender/editors/hair/hair_intern.h115
-rw-r--r--source/blender/editors/hair/hair_mirror.c217
-rw-r--r--source/blender/editors/hair/hair_object_mesh.c86
-rw-r--r--source/blender/editors/hair/hair_object_particles.c101
-rw-r--r--source/blender/editors/hair/hair_ops.c143
-rw-r--r--source/blender/editors/hair/hair_select.c506
-rw-r--r--source/blender/editors/hair/hair_stroke.c500
-rw-r--r--source/blender/editors/hair/hair_undo.c195
-rw-r--r--source/blender/editors/include/ED_datafiles.h21
-rw-r--r--source/blender/editors/include/ED_physics.h27
-rw-r--r--source/blender/editors/include/ED_util.h3
-rw-r--r--source/blender/editors/include/UI_icons.h7
-rw-r--r--source/blender/editors/interface/interface_icons.c13
-rw-r--r--source/blender/editors/mesh/editmesh_undo.c2
-rw-r--r--source/blender/editors/metaball/mball_edit.c2
-rw-r--r--source/blender/editors/object/CMakeLists.txt1
-rw-r--r--source/blender/editors/object/object_edit.c6
-rw-r--r--source/blender/editors/object/object_hair.c117
-rw-r--r--source/blender/editors/object/object_intern.h3
-rw-r--r--source/blender/editors/object/object_lattice.c2
-rw-r--r--source/blender/editors/object/object_ops.c2
-rw-r--r--source/blender/editors/screen/screen_context.c8
-rw-r--r--source/blender/editors/sculpt_paint/paint_ops.c46
-rw-r--r--source/blender/editors/space_api/spacetypes.c2
-rw-r--r--source/blender/editors/space_outliner/outliner_draw.c3
-rw-r--r--source/blender/editors/space_view3d/CMakeLists.txt1
-rw-r--r--source/blender/editors/space_view3d/drawobject.c30
-rw-r--r--source/blender/editors/space_view3d/drawstrands.c382
-rw-r--r--source/blender/editors/space_view3d/space_view3d.c3
-rw-r--r--source/blender/editors/space_view3d/view3d_header.c2
-rw-r--r--source/blender/editors/space_view3d/view3d_intern.h5
-rw-r--r--source/blender/editors/space_view3d/view3d_select.c21
-rw-r--r--source/blender/editors/transform/transform.h1
-rw-r--r--source/blender/editors/transform/transform_conversions.c246
-rw-r--r--source/blender/editors/transform/transform_generics.c7
-rw-r--r--source/blender/editors/transform/transform_orientations.c2
-rw-r--r--source/blender/editors/util/ed_util.c15
-rw-r--r--source/blender/editors/util/editmode_undo.c21
-rw-r--r--source/blender/editors/util/undo.c12
-rw-r--r--source/blender/gpu/GPU_texture.h2
-rw-r--r--source/blender/makesdna/DNA_brush_types.h13
-rw-r--r--source/blender/makesdna/DNA_customdata_types.h9
-rw-r--r--source/blender/makesdna/DNA_hair_types.h85
-rw-r--r--source/blender/makesdna/DNA_mesh_types.h1
-rw-r--r--source/blender/makesdna/DNA_meshdata_types.h7
-rw-r--r--source/blender/makesdna/DNA_modifier_types.h13
-rw-r--r--source/blender/makesdna/DNA_object_types.h2
-rw-r--r--source/blender/makesdna/DNA_particle_types.h1
-rw-r--r--source/blender/makesdna/DNA_scene_types.h41
-rw-r--r--source/blender/makesdna/intern/makesdna.c2
-rw-r--r--source/blender/makesrna/RNA_access.h3
-rw-r--r--source/blender/makesrna/RNA_enum_types.h1
-rw-r--r--source/blender/makesrna/intern/CMakeLists.txt2
-rw-r--r--source/blender/makesrna/intern/makesrna.c2
-rw-r--r--source/blender/makesrna/intern/rna_brush.c28
-rw-r--r--source/blender/makesrna/intern/rna_context.c1
-rw-r--r--source/blender/makesrna/intern/rna_hair.c189
-rw-r--r--source/blender/makesrna/intern/rna_internal.h2
-rw-r--r--source/blender/makesrna/intern/rna_mesh_sample.c73
-rw-r--r--source/blender/makesrna/intern/rna_modifier.c19
-rw-r--r--source/blender/makesrna/intern/rna_object.c1
-rw-r--r--source/blender/makesrna/intern/rna_scene.c4
-rw-r--r--source/blender/makesrna/intern/rna_sculpt_paint.c93
-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_hair.c134
-rw-r--r--source/blender/modifiers/intern/MOD_util.c1
-rw-r--r--source/blender/physics/BPH_strands.h44
-rw-r--r--source/blender/physics/CMakeLists.txt3
-rw-r--r--source/blender/physics/intern/eigen_utils.h32
-rw-r--r--source/blender/physics/intern/implicit.h5
-rw-r--r--source/blender/physics/intern/implicit_blender.c2
-rw-r--r--source/blender/physics/intern/implicit_eigen.cpp2
-rw-r--r--source/blender/physics/intern/strands.cpp434
-rw-r--r--source/blender/windowmanager/WM_types.h1
153 files changed, 10627 insertions, 215 deletions
diff --git a/build_files/cmake/macros.cmake b/build_files/cmake/macros.cmake
index 9811116b0a7..6c377b069fa 100644
--- a/build_files/cmake/macros.cmake
+++ b/build_files/cmake/macros.cmake
@@ -591,6 +591,7 @@ function(SETUP_BLENDER_SORTED_LIBS)
bf_editor_object
bf_editor_armature
bf_editor_physics
+ bf_editor_hair
bf_editor_render
bf_editor_scene
bf_editor_screen
diff --git a/release/datafiles/brushicons/hairadd.png b/release/datafiles/brushicons/hairadd.png
new file mode 100644
index 00000000000..074111a5a0b
--- /dev/null
+++ b/release/datafiles/brushicons/hairadd.png
Binary files differ
diff --git a/release/datafiles/brushicons/haircomb.png b/release/datafiles/brushicons/haircomb.png
new file mode 100644
index 00000000000..074111a5a0b
--- /dev/null
+++ b/release/datafiles/brushicons/haircomb.png
Binary files differ
diff --git a/release/datafiles/brushicons/haircut.png b/release/datafiles/brushicons/haircut.png
new file mode 100644
index 00000000000..074111a5a0b
--- /dev/null
+++ b/release/datafiles/brushicons/haircut.png
Binary files differ
diff --git a/release/datafiles/brushicons/hairlength.png b/release/datafiles/brushicons/hairlength.png
new file mode 100644
index 00000000000..074111a5a0b
--- /dev/null
+++ b/release/datafiles/brushicons/hairlength.png
Binary files differ
diff --git a/release/datafiles/brushicons/hairpuff.png b/release/datafiles/brushicons/hairpuff.png
new file mode 100644
index 00000000000..074111a5a0b
--- /dev/null
+++ b/release/datafiles/brushicons/hairpuff.png
Binary files differ
diff --git a/release/datafiles/brushicons/hairsmooth.png b/release/datafiles/brushicons/hairsmooth.png
new file mode 100644
index 00000000000..074111a5a0b
--- /dev/null
+++ b/release/datafiles/brushicons/hairsmooth.png
Binary files differ
diff --git a/release/datafiles/brushicons/hairweight.png b/release/datafiles/brushicons/hairweight.png
new file mode 100644
index 00000000000..074111a5a0b
--- /dev/null
+++ b/release/datafiles/brushicons/hairweight.png
Binary files differ
diff --git a/release/datafiles/locale b/release/datafiles/locale
-Subproject c93ed11a47b3016cf59711ec16de2e2e94c30e9
+Subproject 9628dc1922be2fb6281bc66f5f7512c2a57c294
diff --git a/release/scripts/addons b/release/scripts/addons
-Subproject 371960484a38fc64e0a2635170a41a0d8ab2f6b
+Subproject 407d0ea752b3af73d3f13ba072671bd09eefecb
diff --git a/release/scripts/addons_contrib b/release/scripts/addons_contrib
-Subproject a8515cfdfe9a98127b592f36fcbe51b7e23b969
+Subproject 9f29e18707917ec5be262431d2e09dbb85332f4
diff --git a/release/scripts/startup/bl_operators/wm.py b/release/scripts/startup/bl_operators/wm.py
index 20586b727d5..6e3235ee186 100644
--- a/release/scripts/startup/bl_operators/wm.py
+++ b/release/scripts/startup/bl_operators/wm.py
@@ -163,6 +163,7 @@ class BRUSH_OT_active_index_set(Operator):
"vertex_paint": "use_paint_vertex",
"weight_paint": "use_paint_weight",
"image_paint": "use_paint_image",
+ "hair_edit": "use_hair_edit",
}
def execute(self, context):
diff --git a/release/scripts/startup/bl_ui/__init__.py b/release/scripts/startup/bl_ui/__init__.py
index cc3d1ffc229..93103249522 100644
--- a/release/scripts/startup/bl_ui/__init__.py
+++ b/release/scripts/startup/bl_ui/__init__.py
@@ -43,6 +43,7 @@ _modules = [
"properties_data_lightprobe",
"properties_data_speaker",
"properties_game",
+ "properties_hair",
"properties_mask_common",
"properties_material",
"properties_object",
diff --git a/release/scripts/startup/bl_ui/properties_data_modifier.py b/release/scripts/startup/bl_ui/properties_data_modifier.py
index 05321ee4486..b162d23e679 100644
--- a/release/scripts/startup/bl_ui/properties_data_modifier.py
+++ b/release/scripts/startup/bl_ui/properties_data_modifier.py
@@ -1528,6 +1528,24 @@ class DATA_PT_modifiers(ModifierButtonsPanel, Panel):
if md.rest_source == 'BIND':
layout.operator("object.correctivesmooth_bind", text="Unbind" if is_bind else "Bind")
+ def HAIR(self, layout, ob, md):
+ hair = md.hair
+
+ col = layout.column()
+ col.label(text="Follicles:")
+ col.label(text="Count: %d" % len(hair.follicles))
+ col.operator("object.hair_follicles_generate", text="Generate")
+
+ col = layout.column()
+ col.template_list("HAIR_UL_groups", "", hair, "groups", hair, "active_group_index")
+
+ layout.separator()
+
+ group = hair.active_group
+ if group:
+ col = layout.column()
+ col.prop(group, "type")
+
classes = (
DATA_PT_modifiers,
diff --git a/release/scripts/startup/bl_ui/properties_hair.py b/release/scripts/startup/bl_ui/properties_hair.py
new file mode 100644
index 00000000000..2d234bf3f94
--- /dev/null
+++ b/release/scripts/startup/bl_ui/properties_hair.py
@@ -0,0 +1,40 @@
+# ##### 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 #####
+
+# <pep8 compliant>
+import bpy
+from bpy.types import UIList
+
+class HAIR_UL_groups(UIList):
+ def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
+ group = item
+ if self.layout_type in {'DEFAULT', 'COMPACT'}:
+ layout.prop(group, "name", text="", emboss=False, icon_value=icon)
+ elif self.layout_type == 'GRID':
+ layout.alignment = 'CENTER'
+ layout.label(text="", icon_value=icon)
+
+
+classes = (
+ HAIR_UL_groups,
+)
+
+if __name__ == "__main__": # only for live edit.
+ from bpy.utils import register_class
+ for cls in classes:
+ register_class(cls)
diff --git a/release/scripts/startup/bl_ui/properties_paint_common.py b/release/scripts/startup/bl_ui/properties_paint_common.py
index 91f4239c067..f0fa3751de8 100644
--- a/release/scripts/startup/bl_ui/properties_paint_common.py
+++ b/release/scripts/startup/bl_ui/properties_paint_common.py
@@ -38,10 +38,10 @@ class UnifiedPaintPanel:
elif context.image_paint_object:
if (toolsettings.image_paint and toolsettings.image_paint.detect_data()):
return toolsettings.image_paint
-
- return None
elif context.particle_edit_object:
return toolsettings.particle_edit
+ elif context.hair_edit_object:
+ return toolsettings.hair_edit
return None
diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py
index 761e6294ffa..d49f443ff4f 100644
--- a/release/scripts/startup/bl_ui/space_view3d.py
+++ b/release/scripts/startup/bl_ui/space_view3d.py
@@ -52,6 +52,12 @@ class VIEW3D_HT_header(Header):
# Particle edit
if mode == 'PARTICLE_EDIT':
row.prop(toolsettings.particle_edit, "select_mode", text="", expand=True)
+ elif mode == 'HAIR_EDIT':
+ row.prop(toolsettings.hair_edit, "select_mode", text="", expand=True)
+ row.prop(toolsettings.hair_edit, "hair_draw_mode", text="", expand=True)
+ if toolsettings.hair_edit.hair_draw_mode == 'FIBERS':
+ row.prop(toolsettings.hair_edit, "hair_draw_size", text="Size")
+ row.prop(toolsettings.hair_edit, "hair_draw_subdivision", text="Subdivide")
# Occlude geometry
if ((view.viewport_shade not in {'BOUNDBOX', 'WIREFRAME'} and (mode == 'PARTICLE_EDIT' or (mode == 'EDIT' and obj.type == 'MESH'))) or
@@ -172,7 +178,7 @@ class VIEW3D_MT_editor_menus(Menu):
layout.menu("VIEW3D_MT_select_paint_mask")
elif mesh.use_paint_mask_vertex and mode_string == 'PAINT_WEIGHT':
layout.menu("VIEW3D_MT_select_paint_mask_vertex")
- elif mode_string != 'SCULPT':
+ elif mode_string not in {'SCULPT'}:
layout.menu("VIEW3D_MT_select_%s" % mode_string.lower())
if gp_edit:
@@ -197,7 +203,7 @@ class VIEW3D_MT_editor_menus(Menu):
elif obj:
if mode_string != 'PAINT_TEXTURE':
layout.menu("VIEW3D_MT_%s" % mode_string.lower())
- if mode_string in {'SCULPT', 'PAINT_VERTEX', 'PAINT_WEIGHT', 'PAINT_TEXTURE'}:
+ if mode_string in {'SCULPT', 'PAINT_VERTEX', 'PAINT_WEIGHT', 'PAINT_TEXTURE', 'HAIR'}:
layout.menu("VIEW3D_MT_brush")
if mode_string == 'SCULPT':
layout.menu("VIEW3D_MT_hide_mask")
@@ -1072,6 +1078,13 @@ class VIEW3D_MT_select_paint_mask_vertex(Menu):
layout.operator("paint.vert_select_ungrouped", text="Ungrouped Verts")
+class VIEW3D_MT_select_hair(Menu):
+ bl_label = "Select"
+
+ def draw(self, context):
+ layout = self.layout
+
+
class VIEW3D_MT_angle_control(Menu):
bl_label = "Angle Control"
@@ -1712,7 +1725,7 @@ class VIEW3D_MT_brush(Menu):
return
# brush paint modes
- layout.menu("VIEW3D_MT_brush_paint_modes")
+ layout.menu("VIEW3D_MT_brush_object_modes")
# brush tool
if context.sculpt_object:
@@ -1722,6 +1735,8 @@ class VIEW3D_MT_brush(Menu):
layout.prop_menu_enum(brush, "image_tool")
elif context.vertex_paint_object or context.weight_paint_object:
layout.prop_menu_enum(brush, "vertex_tool")
+ elif context.hair_edit_object:
+ layout.prop_menu_enum(brush, "hair_tool")
# TODO: still missing a lot of brush options here
@@ -1745,7 +1760,7 @@ class VIEW3D_MT_brush(Menu):
layout.operator("sculpt.set_persistent_base")
-class VIEW3D_MT_brush_paint_modes(Menu):
+class VIEW3D_MT_brush_object_modes(Menu):
bl_label = "Enabled Modes"
def draw(self, context):
@@ -1758,6 +1773,7 @@ class VIEW3D_MT_brush_paint_modes(Menu):
layout.prop(brush, "use_paint_vertex", text="Vertex Paint")
layout.prop(brush, "use_paint_weight", text="Weight Paint")
layout.prop(brush, "use_paint_image", text="Texture Paint")
+ layout.prop(brush, "use_hair_edit", text="Hair Edit")
# ********** Vertex paint menu **********
@@ -2022,6 +2038,14 @@ class VIEW3D_MT_particle_specials(Menu):
class VIEW3D_MT_particle_showhide(ShowHideMenu, Menu):
_operator_name = "particle"
+# ********** Hair menu **********
+
+class VIEW3D_MT_hair(Menu):
+ bl_label = "Hair"
+
+ def draw(self, context):
+ layout = self.layout
+
# ********** Pose Menu **********
@@ -3774,6 +3798,7 @@ classes = (
VIEW3D_MT_select_pose,
VIEW3D_MT_select_pose_more_less,
VIEW3D_MT_select_particle,
+ VIEW3D_MT_select_hair,
VIEW3D_MT_edit_mesh,
VIEW3D_MT_edit_mesh_select_similar,
VIEW3D_MT_edit_mesh_select_by_trait,
@@ -3814,7 +3839,7 @@ classes = (
VIEW3D_MT_make_links,
VIEW3D_MT_object_game,
VIEW3D_MT_brush,
- VIEW3D_MT_brush_paint_modes,
+ VIEW3D_MT_brush_object_modes,
VIEW3D_MT_paint_vertex,
VIEW3D_MT_hook,
VIEW3D_MT_vertex_group,
@@ -3824,6 +3849,7 @@ classes = (
VIEW3D_MT_particle,
VIEW3D_MT_particle_specials,
VIEW3D_MT_particle_showhide,
+ VIEW3D_MT_hair,
VIEW3D_MT_pose,
VIEW3D_MT_pose_transform,
VIEW3D_MT_pose_slide,
diff --git a/release/scripts/startup/bl_ui/space_view3d_toolbar.py b/release/scripts/startup/bl_ui/space_view3d_toolbar.py
index 580fc03f01f..d76d4824ad6 100644
--- a/release/scripts/startup/bl_ui/space_view3d_toolbar.py
+++ b/release/scripts/startup/bl_ui/space_view3d_toolbar.py
@@ -974,6 +974,21 @@ class VIEW3D_PT_tools_brush(Panel, View3DPaintPanel):
layout.row().prop(brush, "puff_mode", expand=True)
layout.prop(brush, "use_puff_volume")
+ # Hair Mode #
+
+ elif context.hair_edit_object and brush:
+ col = layout.column()
+
+ row = col.row(align=True)
+ self.prop_unified_size(row, context, brush, "size", slider=True, text="Radius")
+ self.prop_unified_size(row, context, brush, "use_pressure_size")
+
+ row = col.row(align=True)
+ self.prop_unified_strength(row, context, brush, "strength", text="Strength")
+ self.prop_unified_strength(row, context, brush, "use_pressure_strength")
+
+ col.prop(brush, "hair_tool", text="Tool")
+
# Sculpt Mode #
elif context.sculpt_object and brush:
@@ -1958,6 +1973,23 @@ class VIEW3D_PT_tools_particlemode(View3DPanel, Panel):
sub.prop(pe, "fade_frames", slider=True)
+class VIEW3D_PT_tools_hairmode(View3DPanel, Panel):
+ """Tools for hair mode"""
+ bl_context = "hairmode"
+ bl_label = "Options"
+ bl_category = "Tools"
+
+ def draw(self, context):
+ layout = self.layout
+
+ settings = context.tool_settings.hair_edit
+ ob = context.active_object
+
+ if ob.data:
+ col = layout.column(align=True)
+ col.prop(ob.data, "use_mirror_x")
+
+
# Grease Pencil drawing tools
class VIEW3D_PT_tools_grease_pencil_draw(GreasePencilDrawingToolsPanel, Panel):
bl_space_type = 'VIEW_3D'
diff --git a/source/blender/blenkernel/BKE_context.h b/source/blender/blenkernel/BKE_context.h
index 350d7a40875..9d47ac470ea 100644
--- a/source/blender/blenkernel/BKE_context.h
+++ b/source/blender/blenkernel/BKE_context.h
@@ -116,6 +116,7 @@ enum {
CTX_MODE_PAINT_VERTEX,
CTX_MODE_PAINT_TEXTURE,
CTX_MODE_PARTICLE,
+ CTX_MODE_HAIR,
CTX_MODE_OBJECT
};
diff --git a/source/blender/blenkernel/BKE_customdata.h b/source/blender/blenkernel/BKE_customdata.h
index 51cd4da183a..cc5927e072e 100644
--- a/source/blender/blenkernel/BKE_customdata.h
+++ b/source/blender/blenkernel/BKE_customdata.h
@@ -57,6 +57,8 @@ extern const CustomDataMask CD_MASK_EDITMESH;
extern const CustomDataMask CD_MASK_DERIVEDMESH;
extern const CustomDataMask CD_MASK_BMESH;
extern const CustomDataMask CD_MASK_FACECORNERS;
+extern const CustomDataMask CD_MASK_STRANDS;
+extern const CustomDataMask CD_MASK_STRANDS_BMESH;
extern const CustomDataMask CD_MASK_EVERYTHING;
/* for ORIGINDEX layer type, indicates no original index for this element */
@@ -269,6 +271,7 @@ void *CustomData_get(const struct CustomData *data, int index, int type);
void *CustomData_get_n(const struct CustomData *data, int type, int index, int n);
void *CustomData_bmesh_get(const struct CustomData *data, void *block, int type);
void *CustomData_bmesh_get_n(const struct CustomData *data, void *block, int type, int n);
+void *CustomData_bmesh_get_named(const struct CustomData *data, void *block, int type, const char *name);
/* gets the layer at physical index n, with no type checking.
*/
diff --git a/source/blender/blenkernel/BKE_editstrands.h b/source/blender/blenkernel/BKE_editstrands.h
new file mode 100644
index 00000000000..be04edb9fc6
--- /dev/null
+++ b/source/blender/blenkernel/BKE_editstrands.h
@@ -0,0 +1,106 @@
+/*
+ * ***** 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_EDITSTRANDS_H__
+#define __BKE_EDITSTRANDS_H__
+
+/** \file blender/blenkernel/BKE_editstrands.h
+ * \ingroup bke
+ */
+
+#include "BLI_utildefines.h"
+
+#include "DNA_customdata_types.h"
+
+#include "BKE_customdata.h"
+#include "BKE_editmesh.h"
+
+#include "bmesh.h"
+
+struct BMesh;
+struct DerivedMesh;
+struct Mesh;
+struct Object;
+
+typedef struct BMEditStrands {
+ BMEditMesh base;
+
+ /* Scalp mesh for fixing root vertices */
+ struct DerivedMesh *root_dm;
+
+ int flag;
+
+ unsigned int vertex_glbuf; // legacy gpu code
+ unsigned int elem_glbuf; // legacy gpu code
+ unsigned int dot_glbuf; // legacy gpu code
+ void *batch_cache;
+} BMEditStrands;
+
+/* BMEditStrands->flag */
+typedef enum BMEditStrandsFlag {
+ BM_STRANDS_DIRTY_SEGLEN = (1 << 0),
+ BM_STRANDS_DIRTY_ROOTS = (1 << 1),
+} BMEditStrandsFlag;
+
+struct BMEditStrands *BKE_editstrands_create(struct BMesh *bm, struct DerivedMesh *root_dm);
+struct BMEditStrands *BKE_editstrands_copy(struct BMEditStrands *es);
+struct BMEditStrands *BKE_editstrands_from_object_particles(struct Object *ob, struct ParticleSystem **r_psys);
+struct BMEditStrands *BKE_editstrands_from_object(struct Object *ob);
+void BKE_editstrands_update_linked_customdata(struct BMEditStrands *es);
+void BKE_editstrands_free(struct BMEditStrands *es);
+
+/* === Constraints === */
+
+/* Stores vertex locations for temporary reference:
+ * Vertex locations get modified by tools, but then need to be corrected
+ * by calculating a smooth solution based on the difference to original pre-tool locations.
+ */
+typedef float (*BMEditStrandsLocations)[3];
+BMEditStrandsLocations BKE_editstrands_get_locations(struct BMEditStrands *edit);
+void BKE_editstrands_free_locations(BMEditStrandsLocations locs);
+
+void BKE_editstrands_solve_constraints(struct Object *ob, struct BMEditStrands *es, BMEditStrandsLocations orig);
+void BKE_editstrands_ensure(struct BMEditStrands *es);
+
+/* === Particle Conversion === */
+
+struct BMesh *BKE_editstrands_particles_to_bmesh(struct Object *ob, struct ParticleSystem *psys);
+void BKE_editstrands_particles_from_bmesh(struct Object *ob, struct ParticleSystem *psys);
+
+/* === Mesh Conversion === */
+struct BMesh *BKE_editstrands_mesh_to_bmesh(struct Object *ob, struct Mesh *me);
+void BKE_editstrands_mesh_from_bmesh(struct Object *ob);
+
+/* === Draw Cache === */
+enum {
+ BKE_STRANDS_BATCH_DIRTY_ALL = 0,
+ BKE_STRANDS_BATCH_DIRTY_SELECT = 1,
+};
+void BKE_editstrands_batch_cache_dirty(struct BMEditStrands *es, int mode);
+void BKE_editstrands_batch_cache_free(struct BMEditStrands *es);
+
+#endif
diff --git a/source/blender/blenkernel/BKE_hair.h b/source/blender/blenkernel/BKE_hair.h
new file mode 100644
index 00000000000..94ec9ff80cc
--- /dev/null
+++ b/source/blender/blenkernel/BKE_hair.h
@@ -0,0 +1,94 @@
+/*
+ * ***** 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"
+
+struct HairFollicle;
+struct HairPattern;
+struct HairGroup;
+struct DerivedMesh;
+
+static const unsigned int STRAND_INDEX_NONE = 0xFFFFFFFF;
+
+struct HairPattern* BKE_hair_new(void);
+struct HairPattern* BKE_hair_copy(struct HairPattern *hair);
+void BKE_hair_free(struct HairPattern *hair);
+
+void BKE_hair_set_num_follicles(struct HairPattern *hair, int count);
+void BKE_hair_follicles_generate(struct HairPattern *hair, struct DerivedMesh *scalp, int count, unsigned int seed);
+
+struct HairGroup* BKE_hair_group_new(struct HairPattern *hair, int type);
+void BKE_hair_group_remove(struct HairPattern *hair, struct HairGroup *group);
+struct HairGroup* BKE_hair_group_copy(struct HairPattern *hair, struct HairGroup *group);
+void BKE_hair_group_moveto(struct HairPattern *hair, struct HairGroup *group, int position);
+
+void BKE_hair_group_name_set(struct HairPattern *hair, struct HairGroup *group, const char *name);
+
+void BKE_hair_update_groups(struct HairPattern *hair);
+
+/* === Draw Buffer Texture === */
+
+typedef struct HairDrawDataInterface {
+ const struct HairGroup *group;
+ struct DerivedMesh *scalp;
+
+ int (*get_num_strands)(const struct HairDrawDataInterface* hairdata);
+ int (*get_num_verts)(const struct HairDrawDataInterface* hairdata);
+
+ void (*get_strand_lengths)(const struct HairDrawDataInterface* hairdata, int *r_lengths);
+ void (*get_strand_roots)(const struct HairDrawDataInterface* hairdata, struct MeshSample *r_roots);
+ void (*get_strand_vertices)(const struct HairDrawDataInterface* hairdata, float (*r_positions)[3]);
+} HairDrawDataInterface;
+
+int* BKE_hair_strands_get_fiber_lengths(const struct HairDrawDataInterface *hairdata, int subdiv);
+void BKE_hair_strands_get_texture_buffer_size(const struct HairDrawDataInterface *hairdata, int subdiv,
+ int *r_size, int *r_strand_map_start,
+ int *r_strand_vertex_start, int *r_fiber_start);
+void BKE_hair_strands_get_texture_buffer(const struct HairDrawDataInterface *hairdata, int subdiv, void *texbuffer);
+
+/* === Draw Cache === */
+
+enum {
+ BKE_HAIR_BATCH_DIRTY_ALL = 0,
+};
+void BKE_hair_batch_cache_dirty(struct HairGroup *group, int mode);
+void BKE_hair_batch_cache_all_dirty(struct HairPattern *hair, int mode);
+void BKE_hair_batch_cache_free(struct HairGroup *group);
+
+int* BKE_hair_group_get_fiber_lengths(struct HairGroup *group, struct DerivedMesh *scalp, int subdiv);
+void BKE_hair_group_get_texture_buffer_size(struct HairGroup *group, struct DerivedMesh *scalp, int subdiv,
+ int *r_size, int *r_strand_map_start, int *r_strand_vertex_start, int *r_fiber_start);
+void BKE_hair_group_get_texture_buffer(struct HairGroup *group, struct DerivedMesh *scalp, int subdiv, void *texbuffer);
+
+#endif
diff --git a/source/blender/blenkernel/BKE_mesh_sample.h b/source/blender/blenkernel/BKE_mesh_sample.h
new file mode 100644
index 00000000000..377f0c2c182
--- /dev/null
+++ b/source/blender/blenkernel/BKE_mesh_sample.h
@@ -0,0 +1,77 @@
+/*
+ * ***** 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 bool (*MeshSampleRayFp)(void *userdata, float ray_start[3], float ray_end[3]);
+
+/* ==== Evaluate ==== */
+
+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);
+
+
+/* ==== Sampling ==== */
+
+struct MeshSampleGenerator *BKE_mesh_sample_gen_surface_vertices(struct DerivedMesh *dm);
+
+/* face_weights is optional */
+struct MeshSampleGenerator *BKE_mesh_sample_gen_surface_random(struct DerivedMesh *dm, unsigned int seed);
+struct MeshSampleGenerator *BKE_mesh_sample_gen_surface_random_ex(struct DerivedMesh *dm, unsigned int seed,
+ MeshSampleVertexWeightFp vertex_weight_cb, void *userdata, bool use_facearea);
+
+struct MeshSampleGenerator *BKE_mesh_sample_gen_surface_raycast(struct DerivedMesh *dm, MeshSampleRayFp ray_cb, void *userdata);
+
+struct MeshSampleGenerator *BKE_mesh_sample_gen_volume_random_bbray(struct DerivedMesh *dm, unsigned int seed, float density);
+
+void BKE_mesh_sample_free_generator(struct MeshSampleGenerator *gen);
+
+bool BKE_mesh_sample_generate(struct MeshSampleGenerator *gen, struct MeshSample *sample);
+
+/* ==== 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 a563fcbc99e..ae103e613a4 100644
--- a/source/blender/blenkernel/BKE_particle.h
+++ b/source/blender/blenkernel/BKE_particle.h
@@ -420,6 +420,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 db4c44a586e..55fbb9d0c38 100644
--- a/source/blender/blenkernel/CMakeLists.txt
+++ b/source/blender/blenkernel/CMakeLists.txt
@@ -103,6 +103,7 @@ set(SRC
intern/editmesh.c
intern/editmesh_bvh.c
intern/editmesh_tangent.c
+ intern/editstrands.c
intern/effect.c
intern/fcurve.c
intern/fluidsim.c
@@ -111,6 +112,8 @@ set(SRC
intern/freestyle.c
intern/gpencil.c
intern/group.c
+ intern/hair.c
+ intern/hair_draw.c
intern/icons.c
intern/idcode.c
intern/idprop.c
@@ -135,6 +138,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
@@ -233,6 +237,7 @@ set(SRC
BKE_deform.h
BKE_displist.h
BKE_dynamicpaint.h
+ BKE_editstrands.h
BKE_editmesh.h
BKE_editmesh_bvh.h
BKE_editmesh_tangent.h
@@ -244,6 +249,7 @@ set(SRC
BKE_global.h
BKE_gpencil.h
BKE_group.h
+ BKE_hair.h
BKE_icons.h
BKE_idcode.h
BKE_idprop.h
@@ -265,6 +271,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/brush.c b/source/blender/blenkernel/intern/brush.c
index 03b0710c8fc..60ea78f3f2b 100644
--- a/source/blender/blenkernel/intern/brush.c
+++ b/source/blender/blenkernel/intern/brush.c
@@ -70,7 +70,7 @@ static void brush_defaults(Brush *brush)
brush->blend = 0;
brush->flag = 0;
- brush->ob_mode = OB_MODE_ALL_PAINT;
+ brush->ob_mode = OB_MODE_ALL_BRUSH;
/* BRUSH SCULPT TOOL SETTINGS */
brush->weight = 1.0f; /* weight of brush 0 - 1.0 */
diff --git a/source/blender/blenkernel/intern/context.c b/source/blender/blenkernel/intern/context.c
index 4f1dd18b39c..d301ff56876 100644
--- a/source/blender/blenkernel/intern/context.c
+++ b/source/blender/blenkernel/intern/context.c
@@ -1002,6 +1002,7 @@ int CTX_data_mode_enum_ex(const Object *obedit, const Object *ob)
else if (ob->mode & OB_MODE_VERTEX_PAINT) return CTX_MODE_PAINT_VERTEX;
else if (ob->mode & OB_MODE_TEXTURE_PAINT) return CTX_MODE_PAINT_TEXTURE;
else if (ob->mode & OB_MODE_PARTICLE_EDIT) return CTX_MODE_PARTICLE;
+ else if (ob->mode & OB_MODE_HAIR_EDIT) return CTX_MODE_HAIR;
}
}
@@ -1031,6 +1032,7 @@ static const char *data_mode_strings[] = {
"vertexpaint",
"imagepaint",
"particlemode",
+ "hairmode",
"objectmode",
NULL
};
diff --git a/source/blender/blenkernel/intern/customdata.c b/source/blender/blenkernel/intern/customdata.c
index 3ccf619fd11..1833a3f2b5a 100644
--- a/source/blender/blenkernel/intern/customdata.c
+++ b/source/blender/blenkernel/intern/customdata.c
@@ -52,6 +52,7 @@
#include "BKE_customdata.h"
#include "BKE_customdata_file.h"
+#include "BKE_editstrands.h"
#include "BKE_global.h"
#include "BKE_main.h"
#include "BKE_mesh_mapping.h"
@@ -1295,6 +1296,8 @@ static const LayerTypeInfo LAYERTYPEINFO[CD_NUMTYPES] = {
{sizeof(short[4][3]), "", 0, NULL, NULL, NULL, NULL, layerSwap_flnor, NULL},
/* 41: CD_CUSTOMLOOPNORMAL */
{sizeof(short[2]), "vec2s", 1, NULL, NULL, NULL, NULL, NULL, NULL},
+ /* 42: CD_MESH_SAMPLE */
+ {sizeof(MeshSample), "MeshSample", 1, NULL, NULL, NULL, NULL, NULL, NULL},
};
@@ -1310,7 +1313,7 @@ static const char *LAYERTYPENAMES[CD_NUMTYPES] = {
/* 30-34 */ "CDSubSurfCrease", "CDOrigSpaceLoop", "CDPreviewLoopCol", "CDBMElemPyPtr", "CDPaintMask",
/* 35-36 */ "CDGridPaintMask", "CDMVertSkin",
/* 37-38 */ "CDFreestyleEdge", "CDFreestyleFace",
- /* 39-41 */ "CDMLoopTangent", "CDTessLoopNormal", "CDCustomLoopNormal",
+ /* 39-42 */ "CDMLoopTangent", "CDTessLoopNormal", "CDCustomLoopNormal", "CDMeshSample",
};
@@ -1355,6 +1358,17 @@ const CustomDataMask CD_MASK_FACECORNERS =
CD_MASK_ORIGSPACE | CD_MASK_ORIGSPACE_MLOOP |
CD_MASK_TESSLOOPNORMAL | CD_MASK_NORMAL |
CD_MASK_TANGENT | CD_MASK_MLOOPTANGENT;
+const CustomDataMask CD_MASK_STRANDS =
+ CD_MASK_MVERT | CD_MASK_MEDGE |
+ CD_MASK_MDEFORMVERT | CD_MASK_MCOL |
+ CD_MASK_PROP_FLT | CD_MASK_PROP_INT | CD_MASK_PROP_STR | CD_MASK_MDISPS |
+ CD_MASK_MVERT_SKIN | CD_MASK_FREESTYLE_EDGE |
+ CD_MASK_MSURFACE_SAMPLE;
+const CustomDataMask CD_MASK_STRANDS_BMESH =
+ CD_MASK_MDEFORMVERT | CD_MASK_PROP_FLT | CD_MASK_PROP_INT |
+ CD_MASK_PROP_STR | CD_MASK_SHAPEKEY | CD_MASK_SHAPE_KEYINDEX | CD_MASK_MDISPS |
+ CD_MASK_MVERT_SKIN | CD_MASK_FREESTYLE_EDGE |
+ CD_MASK_MSURFACE_SAMPLE;
const CustomDataMask CD_MASK_EVERYTHING =
CD_MASK_MVERT | CD_MASK_MDEFORMVERT | CD_MASK_MEDGE | CD_MASK_MFACE |
CD_MASK_MTFACE | CD_MASK_MCOL | CD_MASK_ORIGINDEX | CD_MASK_NORMAL /* | CD_MASK_POLYINDEX */ | CD_MASK_PROP_FLT |
@@ -2900,6 +2914,18 @@ void *CustomData_bmesh_get_layer_n(const CustomData *data, void *block, int n)
return POINTER_OFFSET(block, data->layers[n].offset);
}
+/*Bmesh Custom Data Functions. Should replace editmesh ones with these as well, due to more effecient memory alloc*/
+void *CustomData_bmesh_get_named(const CustomData *data, void *block, int type, const char *name)
+{
+ int layer_index;
+
+ /* get the layer index of the named layer of type */
+ layer_index = CustomData_get_named_layer_index(data, type, name);
+ if (layer_index == -1) return NULL;
+
+ return (char *)block + data->layers[layer_index].offset;
+}
+
bool CustomData_layer_has_math(const struct CustomData *data, int layer_n)
{
const LayerTypeInfo *typeInfo = layerType_getInfo(data->layers[layer_n].type);
diff --git a/source/blender/blenkernel/intern/editstrands.c b/source/blender/blenkernel/intern/editstrands.c
new file mode 100644
index 00000000000..74d300751d3
--- /dev/null
+++ b/source/blender/blenkernel/intern/editstrands.c
@@ -0,0 +1,300 @@
+/*
+ * ***** 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/editstrands.c
+ * \ingroup bke
+ */
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_listbase.h"
+#include "BLI_math.h"
+#include "BLI_mempool.h"
+
+#include "DNA_customdata_types.h"
+#include "DNA_key_types.h"
+#include "DNA_mesh_types.h"
+#include "DNA_modifier_types.h"
+#include "DNA_object_types.h"
+#include "DNA_particle_types.h"
+
+#include "BKE_bvhutils.h"
+#include "BKE_customdata.h"
+#include "BKE_cdderivedmesh.h"
+#include "BKE_DerivedMesh.h"
+#include "BKE_editstrands.h"
+#include "BKE_effect.h"
+#include "BKE_hair.h"
+#include "BKE_mesh_sample.h"
+#include "BKE_object.h"
+#include "BKE_particle.h"
+
+#include "BPH_strands.h"
+
+#include "intern/bmesh_mesh_conv.h"
+#include "intern/bmesh_strands_conv.h"
+
+BMEditStrands *BKE_editstrands_create(BMesh *bm, DerivedMesh *root_dm)
+{
+ BMEditStrands *es = MEM_callocN(sizeof(BMEditStrands), __func__);
+
+ es->base.bm = bm;
+ es->root_dm = CDDM_copy(root_dm);
+
+ BKE_editstrands_batch_cache_dirty(es, BKE_STRANDS_BATCH_DIRTY_ALL);
+
+ return es;
+}
+
+BMEditStrands *BKE_editstrands_copy(BMEditStrands *es)
+{
+ BMEditStrands *es_copy = MEM_callocN(sizeof(BMEditStrands), __func__);
+ *es_copy = *es;
+
+ es_copy->base.bm = BM_mesh_copy(es->base.bm);
+ es_copy->root_dm = CDDM_copy(es->root_dm);
+
+ BKE_editstrands_batch_cache_dirty(es_copy, BKE_STRANDS_BATCH_DIRTY_ALL);
+
+ return es_copy;
+}
+
+/**
+ * \brief Return the BMEditStrands for a given object's particle systems
+ */
+BMEditStrands *BKE_editstrands_from_object_particles(Object *ob, ParticleSystem **r_psys)
+{
+ ParticleSystem *psys = psys_get_current(ob);
+ if (psys && psys->hairedit) {
+ if (r_psys) {
+ *r_psys = psys;
+ }
+ return psys->hairedit;
+ }
+
+ if (r_psys) {
+ *r_psys = NULL;
+ }
+ return NULL;
+}
+
+/**
+ * \brief Return the BMEditStrands for a given object
+ */
+BMEditStrands *BKE_editstrands_from_object(Object *ob)
+{
+ if (ob && ob->type == OB_MESH) {
+ Mesh *me = ob->data;
+ if (me->edit_strands)
+ return me->edit_strands;
+ }
+
+ return BKE_editstrands_from_object_particles(ob, NULL);
+}
+
+void BKE_editstrands_update_linked_customdata(BMEditStrands *UNUSED(es))
+{
+}
+
+/*does not free the BMEditStrands struct itself*/
+void BKE_editstrands_free(BMEditStrands *es)
+{
+ BKE_editstrands_batch_cache_free(es);
+
+ if (es->base.bm)
+ BM_mesh_free(es->base.bm);
+ if (es->root_dm)
+ es->root_dm->release(es->root_dm);
+}
+
+/* === Constraints === */
+
+BMEditStrandsLocations BKE_editstrands_get_locations(BMEditStrands *edit)
+{
+ BMesh *bm = edit->base.bm;
+ BMEditStrandsLocations locs = MEM_mallocN(3*sizeof(float) * bm->totvert, "editstrands locations");
+
+ BMVert *v;
+ BMIter iter;
+ int i;
+
+ BM_ITER_MESH_INDEX(v, &iter, bm, BM_VERTS_OF_MESH, i) {
+ copy_v3_v3(locs[i], v->co);
+ }
+
+ return locs;
+}
+
+void BKE_editstrands_free_locations(BMEditStrandsLocations locs)
+{
+ MEM_freeN(locs);
+}
+
+void BKE_editstrands_solve_constraints(Object *ob, BMEditStrands *es, BMEditStrandsLocations orig)
+{
+ BKE_editstrands_ensure(es);
+
+ BPH_strands_solve_constraints(ob, es, orig);
+
+ BKE_editstrands_batch_cache_dirty(es, BKE_STRANDS_BATCH_DIRTY_ALL);
+}
+
+static void editstrands_calc_segment_lengths(BMesh *bm)
+{
+ BMVert *root, *v, *vprev;
+ BMIter iter, iter_strand;
+ int k;
+
+ BM_ITER_STRANDS(root, &iter, bm, BM_STRANDS_OF_MESH) {
+ BM_ITER_STRANDS_ELEM_INDEX(v, &iter_strand, root, BM_VERTS_OF_STRAND, k) {
+ if (k > 0) {
+ float length = len_v3v3(v->co, vprev->co);
+ BM_elem_float_data_named_set(&bm->vdata, v, CD_PROP_FLT, CD_HAIR_SEGMENT_LENGTH, length);
+ }
+ vprev = v;
+ }
+ }
+}
+
+void BKE_editstrands_ensure(BMEditStrands *es)
+{
+ BM_strands_cd_flag_ensure(es->base.bm, 0);
+
+ if (es->flag & BM_STRANDS_DIRTY_SEGLEN) {
+ editstrands_calc_segment_lengths(es->base.bm);
+
+ es->flag &= ~BM_STRANDS_DIRTY_SEGLEN;
+ }
+}
+
+
+/* === Particle Conversion === */
+
+BMesh *BKE_editstrands_particles_to_bmesh(Object *ob, ParticleSystem *psys)
+{
+ ParticleSystemModifierData *psmd = psys_get_modifier(ob, psys);
+
+ const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_PSYS(psys);
+ BMesh *bm;
+
+ bm = BM_mesh_create(&allocsize,
+ &((struct BMeshCreateParams){.use_toolflags = false,}));
+
+ if (psmd && psmd->dm_final) {
+ DM_ensure_tessface(psmd->dm_final);
+
+ BM_strands_bm_from_psys(bm, ob, psys, psmd->dm_final, true, /*psys->shapenr*/ -1);
+
+ editstrands_calc_segment_lengths(bm);
+ }
+
+ return bm;
+}
+
+void BKE_editstrands_particles_from_bmesh(Object *ob, ParticleSystem *psys)
+{
+ ParticleSystemModifierData *psmd = psys_get_modifier(ob, psys);
+ BMesh *bm = psys->hairedit ? psys->hairedit->base.bm : NULL;
+
+ if (bm) {
+ if (psmd && psmd->dm_final) {
+ BVHTreeFromMesh bvhtree = {NULL};
+
+ DM_ensure_tessface(psmd->dm_final);
+
+ bvhtree_from_mesh_faces(&bvhtree, psmd->dm_final, 0.0, 2, 6);
+
+ BM_strands_bm_to_psys(bm, ob, psys, psmd->dm_final, &bvhtree);
+
+ free_bvhtree_from_mesh(&bvhtree);
+ }
+ }
+}
+
+
+/* === Mesh Conversion === */
+
+BMesh *BKE_editstrands_mesh_to_bmesh(Object *ob, Mesh *me)
+{
+ const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(me);
+ BMesh *bm;
+ struct BMeshFromMeshParams params = {0};
+
+ bm = BM_mesh_create(&allocsize,
+ &((struct BMeshCreateParams){.use_toolflags = false,}));
+
+ params.use_shapekey = true;
+ params.active_shapekey = ob->shapenr;
+ BM_mesh_bm_from_me(bm, me, &params);
+ BM_strands_cd_flag_ensure(bm, 0);
+
+ editstrands_calc_segment_lengths(bm);
+
+ return bm;
+}
+
+void BKE_editstrands_mesh_from_bmesh(Object *ob)
+{
+ Mesh *me = ob->data;
+ BMesh *bm = me->edit_strands->base.bm;
+ struct BMeshToMeshParams params = {0};
+
+ /* Workaround for T42360, 'ob->shapenr' should be 1 in this case.
+ * however this isn't synchronized between objects at the moment. */
+ if (UNLIKELY((ob->shapenr == 0) && (me->key && !BLI_listbase_is_empty(&me->key->block)))) {
+ bm->shapenr = 1;
+ }
+
+ BM_mesh_bm_to_me(bm, me, &params);
+
+#ifdef USE_TESSFACE_DEFAULT
+ BKE_mesh_tessface_calc(me);
+#endif
+
+ /* free derived mesh. usually this would happen through depsgraph but there
+ * are exceptions like file save that will not cause this, and we want to
+ * avoid ending up with an invalid derived mesh then */
+ BKE_object_free_derived_caches(ob);
+}
+
+/* === Draw Cache === */
+void (*BKE_editstrands_batch_cache_dirty_cb)(BMEditStrands *es, int mode) = NULL;
+void (*BKE_editstrands_batch_cache_free_cb)(BMEditStrands *es) = NULL;
+
+void BKE_editstrands_batch_cache_dirty(BMEditStrands *es, int mode)
+{
+ if (es->batch_cache) {
+ BKE_editstrands_batch_cache_dirty_cb(es, mode);
+ }
+}
+
+void BKE_editstrands_batch_cache_free(BMEditStrands *es)
+{
+ if (es->batch_cache) {
+ BKE_editstrands_batch_cache_free_cb(es);
+ }
+}
diff --git a/source/blender/blenkernel/intern/hair.c b/source/blender/blenkernel/intern/hair.c
new file mode 100644
index 00000000000..f9e66a88b03
--- /dev/null
+++ b/source/blender/blenkernel/intern/hair.c
@@ -0,0 +1,452 @@
+/*
+ * ***** 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_math.h"
+#include "BLI_kdtree.h"
+#include "BLI_listbase.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 "BKE_DerivedMesh.h"
+#include "BKE_mesh_sample.h"
+#include "BKE_hair.h"
+
+#include "BLT_translation.h"
+
+HairPattern* BKE_hair_new(void)
+{
+ HairPattern *hair = MEM_callocN(sizeof(HairPattern), "hair");
+
+ /* add a default hair group */
+ BKE_hair_group_new(hair, HAIR_GROUP_TYPE_NORMALS);
+
+ return hair;
+}
+
+HairPattern* BKE_hair_copy(HairPattern *hair)
+{
+ HairPattern *nhair = MEM_dupallocN(hair);
+
+ nhair->follicles = MEM_dupallocN(hair->follicles);
+
+ BLI_duplicatelist(&nhair->groups, &hair->groups);
+
+ return nhair;
+}
+
+static void hair_group_free(HairGroup *group)
+{
+ BKE_hair_batch_cache_free(group);
+
+ if (group->strands_parent_index) {
+ MEM_freeN(group->strands_parent_index);
+ }
+ if (group->strands_parent_weight) {
+ MEM_freeN(group->strands_parent_weight);
+ }
+}
+
+void BKE_hair_free(struct HairPattern *hair)
+{
+ if (hair->follicles) {
+ MEM_freeN(hair->follicles);
+ }
+
+ for (HairGroup *group = hair->groups.first; group; group = group->next) {
+ hair_group_free(group);
+ }
+ BLI_freelistN(&hair->groups);
+
+ MEM_freeN(hair);
+}
+
+void BKE_hair_set_num_follicles(HairPattern *hair, int count)
+{
+ if (hair->num_follicles != count) {
+ if (count > 0) {
+ if (hair->follicles) {
+ hair->follicles = MEM_reallocN_id(hair->follicles, sizeof(HairFollicle) * count, "hair follicles");
+ }
+ else {
+ hair->follicles = MEM_callocN(sizeof(HairFollicle) * count, "hair follicles");
+ }
+ }
+ else {
+ if (hair->follicles) {
+ MEM_freeN(hair->follicles);
+ hair->follicles = NULL;
+ }
+ }
+ hair->num_follicles = count;
+ }
+}
+
+void BKE_hair_follicles_generate(HairPattern *hair, DerivedMesh *scalp, int count, unsigned int seed)
+{
+ BKE_hair_set_num_follicles(hair, count);
+ if (count == 0) {
+ return;
+ }
+
+ MeshSampleGenerator *gen = BKE_mesh_sample_gen_surface_random(scalp, seed);
+ unsigned int i;
+
+ HairFollicle *foll = hair->follicles;
+ for (i = 0; i < count; ++i, ++foll) {
+ bool ok = BKE_mesh_sample_generate(gen, &foll->mesh_sample);
+ if (!ok) {
+ /* clear remaining samples */
+ memset(foll, 0, sizeof(HairFollicle) * (count - i));
+ break;
+ }
+ }
+
+ BKE_mesh_sample_free_generator(gen);
+
+ BKE_hair_batch_cache_all_dirty(hair, BKE_HAIR_BATCH_DIRTY_ALL);
+
+ BKE_hair_update_groups(hair);
+}
+
+HairGroup* BKE_hair_group_new(HairPattern *hair, int type)
+{
+ HairGroup *group = MEM_callocN(sizeof(HairGroup), "hair group");
+
+ group->type = type;
+ BKE_hair_group_name_set(hair, group, DATA_("Group"));
+
+ switch (type) {
+ case HAIR_GROUP_TYPE_NORMALS:
+ group->normals_max_length = 0.1f;
+ break;
+ case HAIR_GROUP_TYPE_STRANDS:
+ break;
+ }
+
+ BLI_addtail(&hair->groups, group);
+
+ return group;
+}
+
+void BKE_hair_group_remove(HairPattern *hair, HairGroup *group)
+{
+ if (!group) {
+ return;
+ }
+ BLI_assert(BLI_findindex(&hair->groups, group) >= 0);
+
+ BLI_remlink(&hair->groups, group);
+
+ hair_group_free(group);
+ MEM_freeN(group);
+}
+
+HairGroup* BKE_hair_group_copy(HairPattern *hair, HairGroup *group)
+{
+ if (!group) {
+ return NULL;
+ }
+
+ HairGroup *ngroup = MEM_dupallocN(group);
+
+ BLI_insertlinkafter(&hair->groups, group, ngroup);
+ return ngroup;
+}
+
+void BKE_hair_group_moveto(HairPattern *hair, HairGroup *group, int position)
+{
+ if (!group) {
+ return;
+ }
+ BLI_assert(BLI_findindex(&hair->groups, group) >= 0);
+
+ BLI_remlink(&hair->groups, group);
+ BLI_insertlinkbefore(&hair->groups, BLI_findlink(&hair->groups, position), group);
+}
+
+void BKE_hair_group_name_set(HairPattern *hair, HairGroup *group, const char *name)
+{
+ BLI_strncpy_utf8(group->name, name, sizeof(group->name));
+ BLI_uniquename(&hair->groups, group, DATA_("Group"), '.', offsetof(HairGroup, name), sizeof(group->name));
+}
+
+#define HAIR_FOLLICLE_GROUP_NONE INT_MAX
+
+static void hair_claim_group_follicle(HairGroup *group, int group_index, int *follicle_group, int i)
+{
+ if (follicle_group[i] == HAIR_FOLLICLE_GROUP_NONE) {
+ follicle_group[i] = group_index;
+ ++group->num_follicles;
+ }
+}
+
+static void hair_group_follicles_normals(HairPattern *hair, HairGroup *group, int group_index, int *follicle_group)
+{
+ const int num_follicles = hair->num_follicles;
+ for (int i = 0; i < num_follicles; ++i) {
+ // claim all
+ hair_claim_group_follicle(group, group_index, follicle_group, i);
+ }
+}
+
+static void hair_group_follicles_strands(HairPattern *hair, HairGroup *group, int group_index, int *follicle_group)
+{
+ // TODO
+ UNUSED_VARS(hair, group, group_index, follicle_group);
+}
+
+typedef struct HairFollicleSortContext {
+ const HairFollicle *start;
+ const int *follicle_group;
+} HairFollicleSortContext;
+
+static int cmpHairFollicleByGroup(const void *a, const void *b, void *ctx_)
+{
+ const HairFollicleSortContext *ctx = (const HairFollicleSortContext *)ctx_;
+ const size_t ia = (const HairFollicle *)a - ctx->start;
+ const size_t ib = (const HairFollicle *)b - ctx->start;
+ return ctx->follicle_group[ib] - ctx->follicle_group[ia];
+}
+
+void BKE_hair_update_groups(HairPattern *hair)
+{
+ const int num_follicles = hair->num_follicles;
+ int *follicle_group = MEM_mallocN(sizeof(int) * num_follicles, "hair follicle group index");
+ for (int i = 0; i < num_follicles; ++i) {
+ follicle_group[i] = HAIR_FOLLICLE_GROUP_NONE;
+ }
+
+ int group_index = 0;
+ for (HairGroup *group = hair->groups.first; group; group = group->next, ++group_index) {
+ // Note: follicles array is sorted below
+ if (group->prev) {
+ group->follicles = group->prev->follicles + group->prev->num_follicles;
+ }
+ else {
+ group->follicles = hair->follicles;
+ }
+
+ group->num_follicles = 0;
+ switch (group->type) {
+ case HAIR_GROUP_TYPE_NORMALS:
+ hair_group_follicles_normals(hair, group, group_index, follicle_group);
+ break;
+ case HAIR_GROUP_TYPE_STRANDS:
+ hair_group_follicles_strands(hair, group, group_index, follicle_group);
+ break;
+ }
+ }
+
+ {
+ HairFollicleSortContext ctx;
+ ctx.start = hair->follicles;
+ ctx.follicle_group = follicle_group;
+ BLI_qsort_r(hair->follicles, num_follicles, sizeof(HairFollicle), cmpHairFollicleByGroup, &ctx);
+ }
+
+ MEM_freeN(follicle_group);
+
+ BKE_hair_batch_cache_all_dirty(hair, BKE_HAIR_BATCH_DIRTY_ALL);
+}
+
+/* ================================= */
+
+typedef struct HairGroupDrawDataInterface {
+ HairDrawDataInterface base;
+ int numstrands;
+ int numverts_orig;
+} HairGroupDrawDataInterface;
+
+static int get_num_strands(const HairDrawDataInterface *hairdata_)
+{
+ const HairGroupDrawDataInterface *hairdata = (HairGroupDrawDataInterface *)hairdata_;
+ return hairdata->numstrands;
+}
+
+static int get_num_verts(const HairDrawDataInterface *hairdata_)
+{
+ const HairGroupDrawDataInterface *hairdata = (HairGroupDrawDataInterface *)hairdata_;
+ return hairdata->numverts_orig;
+}
+
+static void get_strand_lengths_normals(const HairDrawDataInterface* hairdata_, int *r_lengths)
+{
+ const HairGroupDrawDataInterface *hairdata = (HairGroupDrawDataInterface *)hairdata_;
+ for (int i = 0; i < hairdata->numstrands; ++i)
+ {
+ r_lengths[i] = 2;
+ }
+}
+
+static void get_strand_roots_normals(const HairDrawDataInterface* hairdata_, struct MeshSample *r_roots)
+{
+ const HairGroupDrawDataInterface *hairdata = (HairGroupDrawDataInterface *)hairdata_;
+ DerivedMesh *scalp = hairdata->base.scalp;
+
+ MeshSampleGenerator *gen = BKE_mesh_sample_gen_surface_vertices(scalp);
+
+ int i = 0;
+ for (; i < hairdata->numstrands; ++i)
+ {
+ if (!BKE_mesh_sample_generate(gen, &r_roots[i])) {
+ break;
+ }
+ }
+ // clear remaining samples, if any
+ for (; i < hairdata->numstrands; ++i)
+ {
+ BKE_mesh_sample_clear(&r_roots[i]);
+ }
+
+ BKE_mesh_sample_free_generator(gen);
+}
+
+static void get_strand_vertices_normals(const HairDrawDataInterface* hairdata_, float (*r_verts)[3])
+{
+ const HairGroupDrawDataInterface *hairdata = (HairGroupDrawDataInterface *)hairdata_;
+ DerivedMesh *scalp = hairdata->base.scalp;
+
+ MeshSampleGenerator *gen = BKE_mesh_sample_gen_surface_vertices(scalp);
+
+ int i = 0;
+ for (; i < hairdata->numstrands; ++i)
+ {
+ MeshSample sample;
+ if (!BKE_mesh_sample_generate(gen, &sample)) {
+ break;
+ }
+
+ float co[3], nor[3], tang[3];
+ BKE_mesh_sample_eval(scalp, &sample, co, nor, tang);
+
+ copy_v3_v3(r_verts[i << 1], co);
+ madd_v3_v3v3fl(r_verts[(i << 1) + 1], co, nor, hairdata->base.group->normals_max_length);
+ }
+ // clear remaining data, if any
+ for (; i < hairdata->numstrands; ++i)
+ {
+ zero_v3(r_verts[i << 1]);
+ zero_v3(r_verts[(i << 1) + 1]);
+ }
+
+ BKE_mesh_sample_free_generator(gen);
+}
+
+static void get_strand_lengths_strands(const HairDrawDataInterface* hairdata_, int *r_lengths)
+{
+ const HairGroupDrawDataInterface *hairdata = (HairGroupDrawDataInterface *)hairdata_;
+ for (int i = 0; i < hairdata->numstrands; ++i)
+ {
+ // TODO
+ r_lengths[i] = 0;
+ }
+}
+
+static void get_strand_roots_strands(const HairDrawDataInterface* hairdata_, struct MeshSample *r_roots)
+{
+ const HairGroupDrawDataInterface *hairdata = (HairGroupDrawDataInterface *)hairdata_;
+ for (int i = 0; i < hairdata->numstrands; ++i)
+ {
+ // TODO
+ memset(&r_roots[i], 0, sizeof(MeshSample));
+ }
+}
+
+static void get_strand_vertices_strands(const HairDrawDataInterface* hairdata_, float (*r_verts)[3])
+{
+ const HairGroupDrawDataInterface *hairdata = (HairGroupDrawDataInterface *)hairdata_;
+ for (int i = 0; i < hairdata->numverts_orig; ++i)
+ {
+ // TODO
+ zero_v3(r_verts[i]);
+ }
+}
+
+static HairGroupDrawDataInterface hair_group_get_interface(HairGroup *group, DerivedMesh *scalp)
+{
+ HairGroupDrawDataInterface hairdata;
+ hairdata.base.group = group;
+ hairdata.base.scalp = scalp;
+ hairdata.base.get_num_strands = get_num_strands;
+ hairdata.base.get_num_verts = get_num_verts;
+
+ switch (group->type) {
+ case HAIR_GROUP_TYPE_NORMALS: {
+ hairdata.numstrands = scalp->getNumVerts(scalp);
+ hairdata.numverts_orig = 2 * hairdata.numstrands;
+ hairdata.base.get_strand_lengths = get_strand_lengths_normals;
+ hairdata.base.get_strand_roots = get_strand_roots_normals;
+ hairdata.base.get_strand_vertices = get_strand_vertices_normals;
+ break;
+ }
+ case HAIR_GROUP_TYPE_STRANDS: {
+ // TODO
+ hairdata.numstrands = 0;
+ hairdata.numverts_orig = 0;
+ hairdata.base.get_strand_lengths = get_strand_lengths_strands;
+ hairdata.base.get_strand_roots = get_strand_roots_strands;
+ hairdata.base.get_strand_vertices = get_strand_vertices_strands;
+ break;
+ }
+ }
+
+ return hairdata;
+}
+
+int* BKE_hair_group_get_fiber_lengths(HairGroup *group, DerivedMesh *scalp, int subdiv)
+{
+ HairGroupDrawDataInterface hairdata = hair_group_get_interface(group, scalp);
+ return BKE_hair_strands_get_fiber_lengths(&hairdata.base, subdiv);
+}
+
+void BKE_hair_group_get_texture_buffer_size(HairGroup *group, DerivedMesh *scalp, int subdiv,
+ int *r_size, int *r_strand_map_start,
+ int *r_strand_vertex_start, int *r_fiber_start)
+{
+ HairGroupDrawDataInterface hairdata = hair_group_get_interface(group, scalp);
+ BKE_hair_strands_get_texture_buffer_size(&hairdata.base, subdiv,
+ r_size, r_strand_map_start, r_strand_vertex_start, r_fiber_start);
+}
+
+void BKE_hair_group_get_texture_buffer(HairGroup *group, DerivedMesh *scalp, int subdiv, void *buffer)
+{
+ HairGroupDrawDataInterface hairdata = hair_group_get_interface(group, scalp);
+ BKE_hair_strands_get_texture_buffer(&hairdata.base, subdiv, buffer);
+}
diff --git a/source/blender/blenkernel/intern/hair_draw.c b/source/blender/blenkernel/intern/hair_draw.c
new file mode 100644
index 00000000000..a7f6cda8ad8
--- /dev/null
+++ b/source/blender/blenkernel/intern/hair_draw.c
@@ -0,0 +1,606 @@
+/*
+ * ***** 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"
+
+#if 0
+bool BKE_hair_fiber_get_location(const HairFiber *fiber, DerivedMesh *root_dm, float loc[3])
+{
+ float nor[3], tang[3];
+ if (BKE_mesh_sample_eval(root_dm, &fiber->root, loc, nor, tang)) {
+ return true;
+ }
+ else {
+ zero_v3(loc);
+ return false;
+ }
+}
+
+bool BKE_hair_fiber_get_vectors(const HairFiber *fiber, DerivedMesh *root_dm,
+ float loc[3], float nor[3], float tang[3])
+{
+ if (BKE_mesh_sample_eval(root_dm, &fiber->root, loc, nor, tang)) {
+ return true;
+ }
+ else {
+ zero_v3(loc);
+ zero_v3(nor);
+ zero_v3(tang);
+ return false;
+ }
+}
+
+bool BKE_hair_fiber_get_matrix(const HairFiber *fiber, DerivedMesh *root_dm, float mat[4][4])
+{
+ if (BKE_mesh_sample_eval(root_dm, &fiber->root, mat[3], mat[2], mat[0])) {
+ cross_v3_v3v3(mat[1], mat[2], mat[0]);
+ mat[0][3] = 0.0f;
+ mat[1][3] = 0.0f;
+ mat[2][3] = 0.0f;
+ mat[3][3] = 1.0f;
+ return true;
+ }
+ else {
+ unit_m4(mat);
+ return false;
+ }
+}
+
+BLI_INLINE void verify_fiber_weights(HairFiber *fiber)
+{
+ const float *w = fiber->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 sort_fiber_weights(HairFiber *fiber)
+{
+ unsigned int *idx = fiber->parent_index;
+ float *w = fiber->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 strand_find_closest(HairFiber *fiber, 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) {
+ fiber->parent_index[k] = nearest[k].index;
+ sloc[k] = strandloc[nearest[k].index];
+ }
+ for (; k < 4; ++k) {
+ fiber->parent_index[k] = STRAND_INDEX_NONE;
+ fiber->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(fiber->parent_weight, w);
+ /* float precisions issues can cause slightly negative weights */
+ CLAMP3(fiber->parent_weight, 0.0f, 1.0f);
+ }
+ else if (found == 2) {
+ fiber->parent_weight[1] = line_point_factor_v3(loc, sloc[0], sloc[1]);
+ fiber->parent_weight[0] = 1.0f - fiber->parent_weight[1];
+ /* float precisions issues can cause slightly negative weights */
+ CLAMP2(fiber->parent_weight, 0.0f, 1.0f);
+ }
+ else if (found == 1) {
+ fiber->parent_weight[0] = 1.0f;
+ }
+
+ sort_fiber_weights(fiber);
+}
+
+static void strand_calc_root_distance(HairFiber *fiber, const float loc[3], const float nor[3], const float tang[3],
+ const float (*strandloc)[3])
+{
+ if (fiber->parent_index[0] == STRAND_INDEX_NONE)
+ return;
+
+ float cotang[3];
+ cross_v3_v3v3(cotang, nor, tang);
+
+ const float *sloc0 = strandloc[fiber->parent_index[0]];
+ float dist[3];
+ sub_v3_v3v3(dist, loc, sloc0);
+ fiber->root_distance[0] = dot_v3v3(dist, tang);
+ fiber->root_distance[1] = dot_v3v3(dist, cotang);
+}
+
+static void strands_calc_weights(const HairDrawDataInterface *hairdata, struct DerivedMesh *scalp, HairFiber *fibers, int num_fibers)
+{
+ const int num_strands = hairdata->get_num_strands(hairdata);
+ if (num_strands == 0)
+ return;
+
+ float (*strandloc)[3] = MEM_mallocN(sizeof(float) * 3 * num_strands, "strand locations");
+ {
+ MeshSample *roots = MEM_mallocN(sizeof(MeshSample) * num_strands, "strand roots");
+ hairdata->get_strand_roots(hairdata, roots);
+ for (int i = 0; i < num_strands; ++i) {
+ float nor[3], tang[3];
+ if (!BKE_mesh_sample_eval(scalp, &roots[i], strandloc[i], nor, tang)) {
+ zero_v3(strandloc[i]);
+ }
+ }
+ MEM_freeN(roots);
+ }
+
+ 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);
+
+ HairFiber *fiber = fibers;
+ for (int i = 0; i < num_fibers; ++i, ++fiber) {
+ float loc[3], nor[3], tang[3];
+ if (BKE_mesh_sample_eval(scalp, &fiber->root, loc, nor, tang)) {
+
+ strand_find_closest(fiber, loc, tree, strandloc);
+ verify_fiber_weights(fiber);
+
+ strand_calc_root_distance(fiber, loc, nor, tang, strandloc);
+ }
+ }
+
+ BLI_kdtree_free(tree);
+ MEM_freeN(strandloc);
+}
+
+HairFiber* BKE_hair_fibers_create(const HairDrawDataInterface *hairdata,
+ struct DerivedMesh *scalp, unsigned int amount,
+ unsigned int seed)
+{
+ MeshSampleGenerator *gen = BKE_mesh_sample_gen_surface_random(scalp, seed);
+ unsigned int i;
+
+ HairFiber *fibers = MEM_mallocN(sizeof(HairFiber) * amount, "HairFiber");
+ HairFiber *fiber;
+
+ for (i = 0, fiber = fibers; i < amount; ++i, ++fiber) {
+ if (BKE_mesh_sample_generate(gen, &fiber->root)) {
+ int k;
+ /* influencing control strands are determined later */
+ for (k = 0; k < 4; ++k) {
+ fiber->parent_index[k] = STRAND_INDEX_NONE;
+ fiber->parent_weight[k] = 0.0f;
+ }
+ }
+ else {
+ /* clear remaining samples */
+ memset(fiber, 0, sizeof(HairFiber) * (amount - i));
+ break;
+ }
+ }
+
+ BKE_mesh_sample_free_generator(gen);
+
+ strands_calc_weights(hairdata, scalp, fibers, amount);
+
+ return fibers;
+}
+#endif
+
+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;
+}
+
+static void hair_get_strand_subdiv_lengths(int *lengths, const int *orig_lengths, int num_strands, int subdiv)
+{
+ for (int i = 0; i < num_strands; ++i) {
+ lengths[i] = hair_get_strand_subdiv_length(orig_lengths[i], subdiv);
+ }
+}
+
+int* BKE_hair_strands_get_fiber_lengths(const HairDrawDataInterface *hairdata, int subdiv)
+{
+ if (!hairdata->group) {
+ return NULL;
+ }
+
+ const int totfibers = hairdata->group->num_follicles;
+ int *fiber_length = MEM_mallocN(sizeof(int) * totfibers, "fiber length");
+
+ switch (hairdata->group->type) {
+ case HAIR_GROUP_TYPE_NORMALS: {
+ const int length = hair_get_strand_subdiv_length(2, subdiv);
+ for (int i = 0; i < totfibers; ++i) {
+ fiber_length[i] = length;
+ }
+ break;
+ }
+ case HAIR_GROUP_TYPE_STRANDS: {
+ const int num_strands = hairdata->get_num_strands(hairdata);
+ int *lengths = MEM_mallocN(sizeof(int) * num_strands, "strand length");
+ hairdata->get_strand_lengths(hairdata, lengths);
+ hair_get_strand_subdiv_lengths(lengths, lengths, num_strands, subdiv);
+
+ for (int i = 0; i < totfibers; ++i) {
+ // Calculate the length of the fiber from the weighted average of its control strands
+ float fiblen = 0.0f;
+ const int *parent_index = hairdata->group->strands_parent_index[i];
+ const float *parent_weight = hairdata->group->strands_parent_weight[i];
+
+ for (int k = 0; k < 4; ++k) {
+ int si = parent_index[k];
+ float sw = parent_weight[k];
+ if (si == 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);
+ break;
+ }
+ }
+
+ 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_get_texture_buffer_size(int numstrands, int numverts_orig, int subdiv, int numfibers,
+ int *r_size, int *r_strand_map_start,
+ int *r_strand_vertex_start, int *r_fiber_start)
+{
+ 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);
+}
+
+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(float (*verts)[3], const float (*verts_orig)[3], int numverts_orig, int subdiv)
+{
+ {
+ /* Move vertex positions from the dense array to their initial configuration for subdivision. */
+ const int step = (1 << subdiv);
+ const float (*src)[3] = verts_orig;
+ float (*dst)[3] = verts;
+ for (int i = 0; i < numverts_orig; ++i) {
+ copy_v3_v3(*dst, *src);
+
+ ++src;
+ dst += step;
+ }
+ }
+
+ /* Subdivide */
+ for (int d = 0; d < subdiv; ++d) {
+ const int num_edges = (numverts_orig - 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 = ((numverts_orig - 1) << subdiv) + 1;
+ return num_verts;
+}
+
+static void hair_get_strand_buffer(DerivedMesh *scalp, int numstrands, int numverts_orig, int subdiv,
+ const int *lengths_orig, const float (*vertco_orig)[3], const MeshSample *roots,
+ HairStrandMapTextureBuffer *strand_map_buffer,
+ HairStrandVertexTextureBuffer *strand_vertex_buffer)
+{
+ const int numverts = hair_get_strand_subdiv_numverts(numstrands, numverts_orig, subdiv);
+
+ const int *lengths;
+ const float (*vertco)[3];
+ int *lengths_subdiv = NULL;
+ float (*vertco_subdiv)[3] = NULL;
+ if (subdiv > 0) {
+ lengths = lengths_subdiv = MEM_mallocN(sizeof(int) * numstrands, "strand lengths subdivided");
+ hair_get_strand_subdiv_lengths(lengths_subdiv, lengths_orig, numstrands, subdiv);
+
+ vertco = vertco_subdiv = MEM_mallocN(sizeof(float[3]) * numverts, "strand vertex positions subdivided");
+ }
+ else {
+ lengths = lengths_orig;
+ vertco = vertco_orig;
+ }
+
+ HairStrandMapTextureBuffer *smap = strand_map_buffer;
+ HairStrandVertexTextureBuffer *svert = strand_vertex_buffer;
+ int vertex_orig_start = 0;
+ int vertex_start = 0;
+ for (int i = 0; i < numstrands; ++i) {
+ const int len_orig = lengths_orig[i];
+ const int len = lengths[i];
+ smap->vertex_start = vertex_start;
+ smap->vertex_count = len;
+
+ if (subdiv > 0) {
+ hair_strand_subdivide(vertco_subdiv + vertex_start, vertco_orig + vertex_orig_start, len_orig, subdiv);
+ }
+
+ {
+ float pos[3];
+ float matrix[3][3];
+ BKE_mesh_sample_eval(scalp, &roots[i], 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_orig_start += len_orig;
+ vertex_start += len;
+ ++smap;
+ svert += len;
+ }
+
+ if (subdiv > 0) {
+ MEM_freeN(lengths_subdiv);
+ MEM_freeN(vertco_subdiv);
+ }
+}
+
+static void hair_get_fiber_buffer(const HairGroup *group, DerivedMesh *scalp,
+ HairFiberTextureBuffer *fiber_buf)
+{
+ const int totfibers = group->num_follicles;
+ HairFiberTextureBuffer *fb = fiber_buf;
+ float nor[3], tang[3];
+ switch (group->type) {
+ case HAIR_GROUP_TYPE_NORMALS: {
+ HairFollicle *foll = group->follicles;
+ for (int i = 0; i < totfibers; ++i, ++fb, ++foll) {
+ for (int k = 0; k < 3; ++k) {
+ fb->parent_index[k] = foll->mesh_sample.orig_verts[k];
+ fb->parent_weight[k] = foll->mesh_sample.orig_weights[k];
+ }
+ fb->parent_index[3] = STRAND_INDEX_NONE;
+ fb->parent_weight[3] = 0.0f;
+
+ BKE_mesh_sample_eval(scalp, &group->follicles[i].mesh_sample, fb->root_position, nor, tang);
+ }
+ break;
+ }
+ case HAIR_GROUP_TYPE_STRANDS: {
+ BLI_assert(group->strands_parent_index != NULL);
+ BLI_assert(group->strands_parent_weight != NULL);
+ HairFollicle *foll = group->follicles;
+ for (int i = 0; i < totfibers; ++i, ++fb, ++foll) {
+ memcpy(fb->parent_index, group->strands_parent_index[i], sizeof(fb->parent_index));
+ memcpy(fb->parent_weight, group->strands_parent_weight[i], sizeof(fb->parent_weight));
+
+ BKE_mesh_sample_eval(scalp, &foll->mesh_sample, fb->root_position, nor, tang);
+ }
+ break;
+ }
+ }
+}
+
+void BKE_hair_strands_get_texture_buffer_size(const HairDrawDataInterface *hairdata, int subdiv,
+ int *r_size, int *r_strand_map_start,
+ int *r_strand_vertex_start, int *r_fiber_start)
+{
+ const int totstrands = hairdata->get_num_strands(hairdata);
+ const int totverts = hairdata->get_num_verts(hairdata);
+ hair_get_texture_buffer_size(totstrands, totverts, subdiv, hairdata->group->num_follicles,
+ r_size, r_strand_map_start, r_strand_vertex_start, r_fiber_start);
+}
+
+void BKE_hair_strands_get_texture_buffer(const HairDrawDataInterface *hairdata, int subdiv, void *buffer)
+{
+ const int totstrands = hairdata->get_num_strands(hairdata);
+ const int totverts_orig = hairdata->get_num_verts(hairdata);
+ int size, strand_map_start, strand_vertex_start, fiber_start;
+ hair_get_texture_buffer_size(totstrands, totverts_orig, subdiv, hairdata->group->num_follicles,
+ &size, &strand_map_start, &strand_vertex_start, &fiber_start);
+
+ int *lengths_orig = MEM_mallocN(sizeof(int) * totstrands, "strand lengths");
+ float (*vertco_orig)[3] = MEM_mallocN(sizeof(float[3]) * totverts_orig, "strand vertex positions");
+ MeshSample *roots = MEM_mallocN(sizeof(MeshSample) * totstrands, "strand roots");
+ hairdata->get_strand_lengths(hairdata, lengths_orig);
+ hairdata->get_strand_vertices(hairdata, vertco_orig);
+ hairdata->get_strand_roots(hairdata, roots);
+
+ hair_get_strand_buffer(hairdata->scalp, totstrands, totverts_orig, subdiv,
+ lengths_orig, vertco_orig, roots,
+ (HairStrandMapTextureBuffer*)((char*)buffer + strand_map_start),
+ (HairStrandVertexTextureBuffer*)((char*)buffer + strand_vertex_start));
+ hair_get_fiber_buffer(hairdata->group, hairdata->scalp, (HairFiberTextureBuffer*)((char*)buffer + fiber_start));
+
+ MEM_freeN(lengths_orig);
+ MEM_freeN(vertco_orig);
+ MEM_freeN(roots);
+}
+
+void (*BKE_hair_batch_cache_dirty_cb)(HairGroup *group, int mode) = NULL;
+void (*BKE_hair_batch_cache_free_cb)(HairGroup *group) = NULL;
+
+void BKE_hair_batch_cache_dirty(HairGroup *group, int mode)
+{
+ if (group->draw_batch_cache) {
+ BKE_hair_batch_cache_dirty_cb(group, mode);
+ }
+}
+
+void BKE_hair_batch_cache_all_dirty(struct HairPattern *hair, int mode)
+{
+ for (HairGroup *group = hair->groups.first; group; group = group->next) {
+ BKE_hair_batch_cache_dirty(group, mode);
+ }
+}
+
+void BKE_hair_batch_cache_free(HairGroup *group)
+{
+ if (group->draw_batch_cache || group->draw_texture_cache) {
+ BKE_hair_batch_cache_free_cb(group);
+ }
+}
diff --git a/source/blender/blenkernel/intern/mesh_sample.c b/source/blender/blenkernel/intern/mesh_sample.c
new file mode 100644
index 00000000000..8e823350fce
--- /dev/null
+++ b/source/blender/blenkernel/intern/mesh_sample.c
@@ -0,0 +1,855 @@
+/*
+ * ***** 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 "MEM_guardedalloc.h"
+
+#include "DNA_key_types.h"
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+
+#include "BLI_utildefines.h"
+#include "BLI_math.h"
+#include "BLI_rand.h"
+
+#include "BKE_bvhutils.h"
+#include "BKE_mesh_sample.h"
+#include "BKE_customdata.h"
+#include "BKE_DerivedMesh.h"
+
+#include "BLI_strict_flags.h"
+
+/* ==== Evaluate ==== */
+
+bool BKE_mesh_sample_is_volume_sample(const MeshSample *sample)
+{
+ return sample->orig_verts[0] == 0 && sample->orig_verts[1] == 0;
+}
+
+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));
+}
+
+
+/* ==== Sampling Utilities ==== */
+
+BLI_INLINE void 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]];
+}
+
+/* ==== Sampling ==== */
+
+typedef void (*GeneratorFreeFp)(struct MeshSampleGenerator *gen);
+typedef bool (*GeneratorMakeSampleFp)(struct MeshSampleGenerator *gen, struct MeshSample *sample);
+
+typedef struct MeshSampleGenerator
+{
+ GeneratorFreeFp free;
+ GeneratorMakeSampleFp make_sample;
+} MeshSampleGenerator;
+
+static void sample_generator_init(MeshSampleGenerator *gen, GeneratorFreeFp free, GeneratorMakeSampleFp make_sample)
+{
+ gen->free = free;
+ gen->make_sample = make_sample;
+}
+
+/* ------------------------------------------------------------------------- */
+
+typedef struct MSurfaceSampleGenerator_Vertices {
+ MeshSampleGenerator base;
+
+ DerivedMesh *dm;
+ int (*vert_loop_map)[3];
+ int cur_vert;
+} MSurfaceSampleGenerator_Vertices;
+
+static void generator_vertices_free(MSurfaceSampleGenerator_Vertices *gen)
+{
+ if (gen->vert_loop_map) {
+ MEM_freeN(gen->vert_loop_map);
+ }
+ MEM_freeN(gen);
+}
+
+static bool generator_vertices_make_sample(MSurfaceSampleGenerator_Vertices *gen, MeshSample *sample)
+{
+ DerivedMesh *dm = gen->dm;
+ const int num_verts = dm->getNumVerts(dm);
+ const MLoop *mloops = dm->getLoopArray(dm);
+
+ while (gen->cur_vert < num_verts) {
+ int cur_vert = gen->cur_vert++;
+
+ const int *loops = gen->vert_loop_map[cur_vert];
+ if (loops[0] >= 0) {
+ 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;
+ }
+ }
+ return false;
+}
+
+MeshSampleGenerator *BKE_mesh_sample_gen_surface_vertices(DerivedMesh *dm)
+{
+ MSurfaceSampleGenerator_Vertices *gen;
+
+ DM_ensure_normals(dm);
+
+ gen = MEM_callocN(sizeof(MSurfaceSampleGenerator_Vertices), "MSurfaceSampleGenerator_Vertices");
+ sample_generator_init(&gen->base, (GeneratorFreeFp)generator_vertices_free, (GeneratorMakeSampleFp)generator_vertices_make_sample);
+
+ gen->dm = dm;
+ gen->cur_vert = 0;
+
+ {
+ const int num_verts = dm->getNumVerts(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;
+ }
+
+ return &gen->base;
+}
+
+/* ------------------------------------------------------------------------- */
+
+//#define USE_DEBUG_COUNT
+
+typedef struct MSurfaceSampleGenerator_Random {
+ MeshSampleGenerator base;
+
+ DerivedMesh *dm;
+ RNG *rng;
+ float *tri_weights;
+ float *vertex_weights;
+
+#ifdef USE_DEBUG_COUNT
+ int *debug_count;
+#endif
+} MSurfaceSampleGenerator_Random;
+
+static void generator_random_free(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);
+ if (gen->rng)
+ BLI_rng_free(gen->rng);
+ MEM_freeN(gen);
+}
+
+/* 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(MSurfaceSampleGenerator_Random *gen, MeshSample *sample)
+{
+ DerivedMesh *dm = gen->dm;
+ RNG *rng = gen->rng;
+ 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;
+}
+
+BLI_INLINE float triangle_weight(DerivedMesh *dm, const MLoopTri *tri, MeshSampleVertexWeightFp vertex_weight_cb, void *userdata)
+{
+ 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 (vertex_weight_cb) {
+ float w1 = vertex_weight_cb(dm, v1, index1, userdata);
+ float w2 = vertex_weight_cb(dm, v2, index2, userdata);
+ float w3 = vertex_weight_cb(dm, v3, index3, userdata);
+
+ weight *= (w1 + w2 + w3) / 3.0f;
+ }
+
+ return weight;
+}
+
+MeshSampleGenerator *BKE_mesh_sample_gen_surface_random_ex(DerivedMesh *dm, unsigned int seed,
+ MeshSampleVertexWeightFp vertex_weight_cb, void *userdata, bool use_facearea)
+{
+ MSurfaceSampleGenerator_Random *gen;
+
+ DM_ensure_normals(dm);
+ DM_ensure_looptri_data(dm);
+
+ gen = MEM_callocN(sizeof(MSurfaceSampleGenerator_Random), "MSurfaceSampleGenerator_Random");
+ sample_generator_init(&gen->base, (GeneratorFreeFp)generator_random_free, (GeneratorMakeSampleFp)generator_random_make_sample);
+
+ gen->dm = dm;
+ gen->rng = BLI_rng_new(seed);
+
+ if (use_facearea) {
+ int numtris = dm->getNumLoopTri(dm);
+ int numweights = numtris;
+ const MLoopTri *mtris = dm->getLoopTriArray(dm);
+ const MLoopTri *mt;
+ int i;
+ float totweight;
+
+ gen->tri_weights = MEM_mallocN(sizeof(float) * (size_t)numweights, "mesh sample triangle weights");
+
+ /* accumulate weights */
+ totweight = 0.0f;
+ for (i = 0, mt = mtris; i < numtris; ++i, ++mt) {
+ float weight = triangle_weight(dm, mt, vertex_weight_cb, userdata);
+ gen->tri_weights[i] = totweight;
+ totweight += weight;
+ }
+
+ /* normalize */
+ if (totweight > 0.0f) {
+ float norm = 1.0f / totweight;
+ for (i = 0, mt = mtris; i < numtris; ++i, ++mt) {
+ gen->tri_weights[i] *= norm;
+ }
+ }
+ else {
+ /* invalid weights, remove to avoid invalid binary search */
+ MEM_freeN(gen->tri_weights);
+ gen->tri_weights = NULL;
+ }
+
+#ifdef USE_DEBUG_COUNT
+ gen->debug_count = MEM_callocN(sizeof(int) * (size_t)numweights, "surface sample debug counts");
+#endif
+ }
+
+ return &gen->base;
+}
+
+MeshSampleGenerator *BKE_mesh_sample_gen_surface_random(DerivedMesh *dm, unsigned int seed)
+{
+ return BKE_mesh_sample_gen_surface_random_ex(dm, seed, NULL, NULL, true);
+}
+
+/* ------------------------------------------------------------------------- */
+
+typedef struct MSurfaceSampleGenerator_RayCast {
+ MeshSampleGenerator base;
+
+ DerivedMesh *dm;
+ BVHTreeFromMesh bvhdata;
+
+ MeshSampleRayFp ray_cb;
+ void *userdata;
+} MSurfaceSampleGenerator_RayCast;
+
+static void generator_raycast_free(MSurfaceSampleGenerator_RayCast *gen)
+{
+ free_bvhtree_from_mesh(&gen->bvhdata);
+ MEM_freeN(gen);
+}
+
+static bool generator_raycast_make_sample(MSurfaceSampleGenerator_RayCast *gen, MeshSample *sample)
+{
+ float ray_start[3], ray_end[3], ray_dir[3], dist;
+ BVHTreeRayHit hit;
+
+ if (!gen->ray_cb(gen->userdata, 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, &gen->bvhdata) >= 0) {
+
+ mesh_sample_weights_from_loc(sample, gen->dm, hit.index, hit.co);
+
+ return true;
+ }
+ else
+ return false;
+}
+
+MeshSampleGenerator *BKE_mesh_sample_gen_surface_raycast(DerivedMesh *dm, MeshSampleRayFp ray_cb, void *userdata)
+{
+ MSurfaceSampleGenerator_RayCast *gen;
+ BVHTreeFromMesh bvhdata;
+
+ DM_ensure_tessface(dm);
+
+ if (dm->getNumTessFaces(dm) == 0)
+ return NULL;
+
+ memset(&bvhdata, 0, sizeof(BVHTreeFromMesh));
+ bvhtree_from_mesh_faces(&bvhdata, dm, 0.0f, 4, 6);
+ if (!bvhdata.tree)
+ return NULL;
+
+ gen = MEM_callocN(sizeof(MSurfaceSampleGenerator_RayCast), "MSurfaceSampleGenerator_RayCast");
+ sample_generator_init(&gen->base, (GeneratorFreeFp)generator_raycast_free, (GeneratorMakeSampleFp)generator_raycast_make_sample);
+
+ gen->dm = dm;
+ memcpy(&gen->bvhdata, &bvhdata, sizeof(gen->bvhdata));
+ gen->ray_cb = ray_cb;
+ gen->userdata = userdata;
+
+ return &gen->base;
+}
+
+/* ------------------------------------------------------------------------- */
+
+typedef struct MVolumeSampleGenerator_Random {
+ MeshSampleGenerator base;
+
+ DerivedMesh *dm;
+ BVHTreeFromMesh bvhdata;
+ RNG *rng;
+ float min[3], max[3], extent[3], volume;
+ float density;
+ int max_samples_per_ray;
+
+ /* current ray intersections */
+ BVHTreeRayHit *ray_hits;
+ int tothits, allochits;
+
+ /* current segment index and sample number */
+ int cur_seg, cur_tot, cur_sample;
+} MVolumeSampleGenerator_Random;
+
+static void generator_volume_random_free(MVolumeSampleGenerator_Random *gen)
+{
+ if (gen->rng)
+ BLI_rng_free(gen->rng);
+ free_bvhtree_from_mesh(&gen->bvhdata);
+
+ if (gen->ray_hits)
+ MEM_freeN(gen->ray_hits);
+
+ MEM_freeN(gen);
+}
+
+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 *gen, int tothits)
+{
+ if (tothits > gen->allochits) {
+ gen->allochits = (int)hibit((unsigned int)tothits) << 1;
+ gen->ray_hits = MEM_reallocN(gen->ray_hits, (size_t)gen->allochits * sizeof(BVHTreeRayHit));
+ }
+}
+
+static void generator_volume_ray_cb(void *userdata, int index, const BVHTreeRay *ray, BVHTreeRayHit *hit)
+{
+ MVolumeSampleGenerator_Random *gen = userdata;
+
+ gen->bvhdata.raycast_callback(&gen->bvhdata, index, ray, hit);
+
+ if (hit->index >= 0) {
+ ++gen->tothits;
+ generator_volume_hits_reserve(gen, gen->tothits);
+
+ memcpy(&gen->ray_hits[gen->tothits-1], hit, sizeof(BVHTreeRayHit));
+ }
+}
+
+static void generator_volume_random_cast_ray(MVolumeSampleGenerator_Random *gen)
+{
+ /* bounding box margin to get clean ray intersections */
+ static const float margin = 0.01f;
+
+ RNG *rng = gen->rng;
+ float ray_start[3], ray_end[3], ray_dir[3];
+ int axis;
+
+ 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;
+
+ 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);
+
+ madd_v3_v3v3v3(ray_start, gen->min, ray_start, gen->extent);
+ madd_v3_v3v3v3(ray_end, gen->min, ray_end, gen->extent);
+
+ sub_v3_v3v3(ray_dir, ray_end, ray_start);
+
+ gen->tothits = 0;
+ BLI_bvhtree_ray_cast_all(gen->bvhdata.tree, ray_start, ray_dir, 0.0f, BVH_RAYCAST_DIST_MAX,
+ generator_volume_ray_cb, gen);
+
+ gen->cur_seg = 0;
+ gen->cur_tot = 0;
+ gen->cur_sample = 0;
+}
+
+static void generator_volume_init_segment(MVolumeSampleGenerator_Random *gen)
+{
+ BVHTreeRayHit *a, *b;
+ float length;
+
+ BLI_assert(gen->cur_seg + 1 < gen->tothits);
+ a = &gen->ray_hits[gen->cur_seg];
+ b = &gen->ray_hits[gen->cur_seg + 1];
+
+ length = len_v3v3(a->co, b->co);
+ gen->cur_tot = min_ii(gen->max_samples_per_ray, (int)ceilf(length * gen->density));
+ gen->cur_sample = 0;
+}
+
+static bool generator_volume_random_make_sample(MVolumeSampleGenerator_Random *gen, MeshSample *sample)
+{
+ RNG *rng = gen->rng;
+ BVHTreeRayHit *a, *b;
+
+ if (gen->cur_seg + 1 >= gen->tothits) {
+ generator_volume_random_cast_ray(gen);
+ if (gen->tothits < 2)
+ return false;
+ }
+
+ if (gen->cur_sample >= gen->cur_tot) {
+ gen->cur_seg += 2;
+
+ if (gen->cur_seg + 1 >= gen->tothits) {
+ generator_volume_random_cast_ray(gen);
+ if (gen->tothits < 2)
+ return false;
+ }
+
+ generator_volume_init_segment(gen);
+ }
+ a = &gen->ray_hits[gen->cur_seg];
+ b = &gen->ray_hits[gen->cur_seg + 1];
+
+ if (gen->cur_sample < gen->cur_tot) {
+ float t;
+
+ sample->orig_verts[0] = 0;
+ sample->orig_verts[1] = 0;
+ sample->orig_verts[2] = 0;
+
+ t = BLI_rng_get_float(rng);
+ interp_v3_v3v3(sample->orig_weights, a->co, b->co, t);
+
+ gen->cur_sample += 1;
+
+ return true;
+ }
+
+ return false;
+}
+
+MeshSampleGenerator *BKE_mesh_sample_gen_volume_random_bbray(DerivedMesh *dm, unsigned int seed, float density)
+{
+ MVolumeSampleGenerator_Random *gen;
+ BVHTreeFromMesh bvhdata;
+
+ gen = MEM_callocN(sizeof(MVolumeSampleGenerator_Random), "MVolumeSampleGenerator_Random");
+ sample_generator_init(&gen->base, (GeneratorFreeFp)generator_volume_random_free,
+ (GeneratorMakeSampleFp)generator_volume_random_make_sample);
+
+ DM_ensure_tessface(dm);
+
+ if (dm->getNumTessFaces(dm) == 0)
+ return NULL;
+
+ memset(&bvhdata, 0, sizeof(BVHTreeFromMesh));
+ bvhtree_from_mesh_faces(&bvhdata, dm, 0.0f, 4, 6);
+ if (!bvhdata.tree)
+ return NULL;
+
+ gen->dm = dm;
+ memcpy(&gen->bvhdata, &bvhdata, sizeof(gen->bvhdata));
+ gen->rng = BLI_rng_new(seed);
+
+ 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->density = density;
+ gen->max_samples_per_ray = max_ii(1, (int)powf(gen->volume, 1.0f/3.0f)) >> 1;
+
+ generator_volume_hits_reserve(gen, 64);
+
+ return &gen->base;
+}
+
+/* ------------------------------------------------------------------------- */
+
+void BKE_mesh_sample_free_generator(MeshSampleGenerator *gen)
+{
+ gen->free(gen);
+}
+
+bool BKE_mesh_sample_generate(MeshSampleGenerator *gen, struct MeshSample *sample)
+{
+ return gen->make_sample(gen, sample);
+}
+
+/* ==== 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 9f48d8f6b11..07de120ff48 100644
--- a/source/blender/blenkernel/intern/object.c
+++ b/source/blender/blenkernel/intern/object.c
@@ -977,6 +977,7 @@ ParticleSystem *BKE_object_copy_particlesystem(ParticleSystem *psys, const int f
psysn->pathcache = NULL;
psysn->childcache = NULL;
psysn->edit = NULL;
+ psysn->hairedit = NULL;
psysn->pdd = NULL;
psysn->effectors = NULL;
psysn->tree = NULL;
diff --git a/source/blender/blenkernel/intern/particle.c b/source/blender/blenkernel/intern/particle.c
index 7ba7c13fe40..79c4ad6e618 100644
--- a/source/blender/blenkernel/intern/particle.c
+++ b/source/blender/blenkernel/intern/particle.c
@@ -65,6 +65,7 @@
#include "BKE_boids.h"
#include "BKE_cloth.h"
#include "BKE_colortools.h"
+#include "BKE_editstrands.h"
#include "BKE_effect.h"
#include "BKE_global.h"
#include "BKE_group.h"
@@ -568,6 +569,11 @@ void psys_free(Object *ob, ParticleSystem *psys)
if (psys->edit && psys->free_edit)
psys->free_edit(psys->edit);
+ if (psys->hairedit) {
+ BKE_editstrands_free(psys->hairedit);
+ MEM_freeN(psys->hairedit);
+ psys->hairedit = NULL;
+ }
if (psys->child) {
MEM_freeN(psys->child);
@@ -1606,6 +1612,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],
@@ -3207,7 +3218,7 @@ void object_remove_particle_system(Scene *UNUSED(scene), Object *ob)
if (ob->particlesystem.first)
((ParticleSystem *) ob->particlesystem.first)->flag |= PSYS_CURRENT;
else
- ob->mode &= ~OB_MODE_PARTICLE_EDIT;
+ ob->mode &= ~(OB_MODE_PARTICLE_EDIT | OB_MODE_HAIR_EDIT);
DEG_relations_tag_update(G.main);
DEG_id_tag_update(&ob->id, OB_RECALC_DATA);
diff --git a/source/blender/blenkernel/intern/scene.c b/source/blender/blenkernel/intern/scene.c
index 5d974192241..b380a98932d 100644
--- a/source/blender/blenkernel/intern/scene.c
+++ b/source/blender/blenkernel/intern/scene.c
@@ -913,6 +913,11 @@ void BKE_scene_init(Scene *sce)
sce->toolsettings->imapaint.normal_angle = 80;
sce->toolsettings->imapaint.seam_bleed = 2;
+ sce->toolsettings->hair_edit.select_mode = HAIR_SELECT_VERTEX;
+ sce->toolsettings->hair_edit.hair_draw_mode = HAIR_DRAW_FIBERS;
+ sce->toolsettings->hair_edit.hair_draw_size = 2.5f;
+ sce->toolsettings->hair_edit.hair_draw_subdiv = 2;
+
sce->physics_settings.gravity[0] = 0.0f;
sce->physics_settings.gravity[1] = 0.0f;
sce->physics_settings.gravity[2] = -9.81f;
diff --git a/source/blender/blenlib/BLI_math_geom.h b/source/blender/blenlib/BLI_math_geom.h
index 0fef849c8fa..e1bccc2d0a2 100644
--- a/source/blender/blenlib/BLI_math_geom.h
+++ b/source/blender/blenlib/BLI_math_geom.h
@@ -318,6 +318,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 53fcf9c745c..86045b40811 100644
--- a/source/blender/blenlib/intern/math_geom.c
+++ b/source/blender/blenlib/intern/math_geom.c
@@ -2830,6 +2830,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 573ecce3224..336b7b58afe 100644
--- a/source/blender/blenloader/intern/readfile.c
+++ b/source/blender/blenloader/intern/readfile.c
@@ -73,6 +73,7 @@
#include "DNA_genfile.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"
@@ -4423,6 +4424,7 @@ static void direct_link_particlesystems(FileData *fd, ListBase *particles)
psys->edit = NULL;
psys->free_edit = NULL;
+ psys->hairedit = NULL;
psys->pathcache = NULL;
psys->childcache = NULL;
BLI_listbase_clear(&psys->pathcachebufs);
@@ -4665,6 +4667,7 @@ static void direct_link_mesh(FileData *fd, Mesh *mesh)
mesh->bb = NULL;
mesh->edit_btmesh = NULL;
+ mesh->edit_strands = NULL;
mesh->batch_cache = NULL;
/* happens with old files */
@@ -5117,6 +5120,24 @@ static void direct_link_pose(FileData *fd, bPose *pose)
}
}
+static void direct_link_hair(FileData *fd, HairPattern *hair)
+{
+ if (!hair) {
+ return;
+ }
+
+ hair->follicles = newdataadr(fd, hair->follicles);
+
+ link_list(fd, &hair->groups);
+ for (HairGroup *group = hair->groups.first; group; group = group->next) {
+ group->strands_parent_index = newdataadr(fd, group->strands_parent_index);
+ group->strands_parent_weight = newdataadr(fd, group->strands_parent_weight);
+
+ group->draw_batch_cache = NULL;
+ group->draw_texture_cache = NULL;
+ }
+}
+
static void direct_link_modifiers(FileData *fd, ListBase *lb)
{
ModifierData *md;
@@ -5438,6 +5459,14 @@ static void direct_link_modifiers(FileData *fd, ListBase *lb)
}
}
}
+ else if (md->type == eModifierType_Hair) {
+ HairModifierData *hmd = (HairModifierData *)md;
+
+ hmd->hair = newdataadr(fd, hmd->hair);
+ direct_link_hair(fd, hmd->hair);
+
+ hmd->edit = NULL;
+ }
}
}
@@ -5467,7 +5496,7 @@ static void direct_link_object(FileData *fd, Object *ob)
* See [#34776, #42780] for more information.
*/
if (fd->memfile || (ob->id.tag & (LIB_TAG_EXTERN | LIB_TAG_INDIRECT))) {
- ob->mode &= ~(OB_MODE_EDIT | OB_MODE_PARTICLE_EDIT);
+ ob->mode &= ~(OB_MODE_EDIT | OB_MODE_PARTICLE_EDIT | OB_MODE_HAIR_EDIT);
if (!fd->memfile) {
ob->mode &= ~OB_MODE_POSE;
}
@@ -5818,6 +5847,14 @@ static void lib_link_scene(FileData *fd, Main *main)
sce->toolsettings->particle.shape_object = newlibadr(fd, sce->id.lib, sce->toolsettings->particle.shape_object);
+ {
+ HairEditSettings *hair_edit = &sce->toolsettings->hair_edit;
+ if (hair_edit->brush)
+ hair_edit->brush = newlibadr(fd, sce->id.lib, hair_edit->brush);
+ if (hair_edit->shape_object)
+ hair_edit->shape_object = newlibadr(fd, sce->id.lib, hair_edit->shape_object);
+ }
+
for (BaseLegacy *base_legacy_next, *base_legacy = sce->base.first; base_legacy; base_legacy = base_legacy_next) {
base_legacy_next = base_legacy->next;
@@ -6130,7 +6167,8 @@ static void direct_link_scene(FileData *fd, Scene *sce, Main *bmain)
sce->toolsettings->particle.scene_layer = NULL;
sce->toolsettings->particle.object = NULL;
sce->toolsettings->gp_sculpt.paintcursor = NULL;
-
+ sce->toolsettings->hair_edit.paint_cursor = NULL;
+
/* in rare cases this is needed, see [#33806] */
if (sce->toolsettings->vpaint) {
sce->toolsettings->vpaint->vpaint_prev = NULL;
diff --git a/source/blender/blenloader/intern/writefile.c b/source/blender/blenloader/intern/writefile.c
index b4f5df8891e..7939d9107bc 100644
--- a/source/blender/blenloader/intern/writefile.c
+++ b/source/blender/blenloader/intern/writefile.c
@@ -121,6 +121,7 @@
#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"
@@ -1706,6 +1707,23 @@ static void write_fmaps(WriteData *wd, ListBase *fbase)
}
}
+static void write_hair(WriteData *wd, HairPattern *hair)
+{
+ writestruct(wd, DATA, HairPattern, hair->num_follicles, hair->follicles);
+
+ writelist(wd, DATA, HairGroup, &hair->groups);
+ for (HairGroup *group = hair->groups.first; group; group = group->next) {
+ const int (*parent_index)[4] = group->strands_parent_index;
+ const float (*parent_weight)[4] = group->strands_parent_weight;
+ if (parent_index) {
+ writedata(wd, DATA, sizeof(*parent_index) * group->num_follicles, parent_index);
+ }
+ if (parent_weight) {
+ writedata(wd, DATA, sizeof(*parent_weight) * group->num_follicles, parent_weight);
+ }
+ }
+}
+
static void write_modifiers(WriteData *wd, ListBase *modbase)
{
ModifierData *md;
@@ -1877,6 +1895,14 @@ static void write_modifiers(WriteData *wd, ListBase *modbase)
}
}
}
+ else if (md->type == eModifierType_Hair) {
+ HairModifierData *hmd = (HairModifierData *)md;
+
+ if (hmd->hair) {
+ writestruct(wd, DATA, HairPattern, 1, hmd->hair);
+ write_hair(wd, hmd->hair);
+ }
+ }
}
}
@@ -2259,6 +2285,7 @@ static void write_mesh(WriteData *wd, Mesh *mesh)
CustomData_reset(&mesh->pdata);
CustomData_reset(&mesh->ldata);
mesh->edit_btmesh = NULL;
+ mesh->edit_strands = NULL;
/* now fill in polys to mfaces */
/* XXX This breaks writing design, by using temp allocated memory, which will likely generate
diff --git a/source/blender/bmesh/CMakeLists.txt b/source/blender/bmesh/CMakeLists.txt
index ea24da86626..bbe5044f1bf 100644
--- a/source/blender/bmesh/CMakeLists.txt
+++ b/source/blender/bmesh/CMakeLists.txt
@@ -119,6 +119,10 @@ set(SRC
intern/bmesh_queries.c
intern/bmesh_queries.h
intern/bmesh_queries_inline.h
+ intern/bmesh_strands.c
+ intern/bmesh_strands.h
+ intern/bmesh_strands_conv.c
+ intern/bmesh_strands_conv.h
intern/bmesh_structure.c
intern/bmesh_structure.h
intern/bmesh_structure_inline.h
diff --git a/source/blender/bmesh/bmesh.h b/source/blender/bmesh/bmesh.h
index b84a3d5e559..ab955b7826a 100644
--- a/source/blender/bmesh/bmesh.h
+++ b/source/blender/bmesh/bmesh.h
@@ -244,6 +244,8 @@ extern "C" {
#include "intern/bmesh_mesh_validate.h"
#include "intern/bmesh_mods.h"
#include "intern/bmesh_operators.h"
+#include "intern/bmesh_strands.h"
+#include "intern/bmesh_strands_conv.h"
#include "intern/bmesh_polygon.h"
#include "intern/bmesh_polygon_edgenet.h"
#include "intern/bmesh_queries.h"
diff --git a/source/blender/bmesh/intern/bmesh_interp.c b/source/blender/bmesh/intern/bmesh_interp.c
index 20ee31251e8..5c076f258de 100644
--- a/source/blender/bmesh/intern/bmesh_interp.c
+++ b/source/blender/bmesh/intern/bmesh_interp.c
@@ -900,6 +900,34 @@ void BM_elem_float_data_set(CustomData *cd, void *element, int type, const float
if (f) *f = val;
}
+float BM_elem_float_data_named_get(CustomData *cd, void *element, int type, const char *name)
+{
+ const float *f = CustomData_bmesh_get_named(cd, ((BMHeader *)element)->data, type, name);
+ return f ? *f : 0.0f;
+}
+
+void BM_elem_float_data_named_set(CustomData *cd, void *element, int type, const char *name, const float val)
+{
+ float *f = CustomData_bmesh_get_named(cd, ((BMHeader *)element)->data, type, name);
+ if (f) *f = val;
+}
+
+void BM_elem_meshsample_data_named_get(CustomData *cd, void *element, int type, const char *name, MeshSample *val)
+{
+ const MeshSample *s = CustomData_bmesh_get_named(cd, ((BMHeader *)element)->data, type, name);
+ if (s)
+ memcpy(val, s, sizeof(MeshSample));
+ else
+ memset(val, 0, sizeof(MeshSample));
+}
+
+void BM_elem_meshsample_data_named_set(CustomData *cd, void *element, int type, const char *name, const MeshSample *val)
+{
+ MeshSample *s = CustomData_bmesh_get_named(cd, ((BMHeader *)element)->data, type, name);
+ if (s)
+ memcpy(s, val, sizeof(MeshSample));
+}
+
/** \name Loop interpolation functions: BM_vert_loop_groups_data_layer_***
*
* Handling loop custom-data such as UV's, while keeping contiguous fans is rather tedious.
diff --git a/source/blender/bmesh/intern/bmesh_interp.h b/source/blender/bmesh/intern/bmesh_interp.h
index dabdd23cf6f..970d1cf68b4 100644
--- a/source/blender/bmesh/intern/bmesh_interp.h
+++ b/source/blender/bmesh/intern/bmesh_interp.h
@@ -29,6 +29,8 @@
struct LinkNode;
struct MemArena;
+struct MeshSample;
+
void BM_loop_interp_multires_ex(
BMesh *bm, BMLoop *l_dst, const BMFace *f_src,
@@ -54,6 +56,10 @@ void BM_data_layer_copy(BMesh *bm, CustomData *data, int type, int src_n, int d
float BM_elem_float_data_get(CustomData *cd, void *element, int type);
void BM_elem_float_data_set(CustomData *cd, void *element, int type, const float val);
+float BM_elem_float_data_named_get(CustomData *cd, void *element, int type, const char *name);
+void BM_elem_float_data_named_set(CustomData *cd, void *element, int type, const char *name, const float val);
+void BM_elem_meshsample_data_named_get(CustomData *cd, void *element, int type, const char *name, struct MeshSample *val);
+void BM_elem_meshsample_data_named_set(CustomData *cd, void *element, int type, const char *name, const struct MeshSample *val);
void BM_face_interp_from_face_ex(
BMesh *bm, BMFace *f_dst, const BMFace *f_src, const bool do_vertex,
diff --git a/source/blender/bmesh/intern/bmesh_iterators_inline.h b/source/blender/bmesh/intern/bmesh_iterators_inline.h
index e68440021e6..43b054d7845 100644
--- a/source/blender/bmesh/intern/bmesh_iterators_inline.h
+++ b/source/blender/bmesh/intern/bmesh_iterators_inline.h
@@ -29,6 +29,8 @@
#ifndef __BMESH_ITERATORS_INLINE_H__
#define __BMESH_ITERATORS_INLINE_H__
+#include "BLI_mempool.h"
+
/* inline here optimizes out the switch statement when called with
* constant values (which is very common), nicer for loop-in-loop situations */
@@ -43,7 +45,6 @@ BLI_INLINE void *BM_iter_step(BMIter *iter)
return iter->step(iter);
}
-
/**
* \brief Iterator Init
*
diff --git a/source/blender/bmesh/intern/bmesh_mesh_conv.c b/source/blender/bmesh/intern/bmesh_mesh_conv.c
index 49f055e1827..e73d2f70dbe 100644
--- a/source/blender/bmesh/intern/bmesh_mesh_conv.c
+++ b/source/blender/bmesh/intern/bmesh_mesh_conv.c
@@ -98,6 +98,9 @@
#include "bmesh.h"
#include "intern/bmesh_private.h" /* for element checking */
+/* XXX stupid hack: linker otherwise strips bmesh_strands_conv.c because it is not used inside bmesh */
+void *__dummy_hack__ = &BM_strands_count_psys_keys;
+
void BM_mesh_cd_flag_ensure(BMesh *bm, Mesh *mesh, const char cd_flag)
{
const char cd_flag_all = BM_mesh_cd_flag_from_bmesh(bm) | cd_flag;
@@ -200,6 +203,7 @@ void BM_mesh_bm_from_me(
BMFace *f;
float (*keyco)[3] = NULL;
int totloops, i, j;
+ CustomDataMask mask = CD_MASK_BMESH | params->cd_mask_extra;
/* free custom data */
/* this isnt needed in most cases but do just incase */
@@ -210,10 +214,10 @@ void BM_mesh_bm_from_me(
if (!me || !me->totvert) {
if (me) { /*no verts? still copy customdata layout*/
- CustomData_copy(&me->vdata, &bm->vdata, CD_MASK_BMESH, CD_ASSIGN, 0);
- CustomData_copy(&me->edata, &bm->edata, CD_MASK_BMESH, CD_ASSIGN, 0);
- CustomData_copy(&me->ldata, &bm->ldata, CD_MASK_BMESH, CD_ASSIGN, 0);
- CustomData_copy(&me->pdata, &bm->pdata, CD_MASK_BMESH, CD_ASSIGN, 0);
+ CustomData_copy(&me->vdata, &bm->vdata, mask, CD_ASSIGN, 0);
+ CustomData_copy(&me->edata, &bm->edata, mask, CD_ASSIGN, 0);
+ CustomData_copy(&me->ldata, &bm->ldata, mask, CD_ASSIGN, 0);
+ CustomData_copy(&me->pdata, &bm->pdata, mask, CD_ASSIGN, 0);
CustomData_bmesh_init_pool(&bm->vdata, me->totvert, BM_VERT);
CustomData_bmesh_init_pool(&bm->edata, me->totedge, BM_EDGE);
@@ -225,10 +229,10 @@ void BM_mesh_bm_from_me(
vtable = MEM_mallocN(sizeof(void **) * me->totvert, "mesh to bmesh vtable");
- CustomData_copy(&me->vdata, &bm->vdata, CD_MASK_BMESH, CD_CALLOC, 0);
- CustomData_copy(&me->edata, &bm->edata, CD_MASK_BMESH, CD_CALLOC, 0);
- CustomData_copy(&me->ldata, &bm->ldata, CD_MASK_BMESH, CD_CALLOC, 0);
- CustomData_copy(&me->pdata, &bm->pdata, CD_MASK_BMESH, CD_CALLOC, 0);
+ CustomData_copy(&me->vdata, &bm->vdata, mask, CD_CALLOC, 0);
+ CustomData_copy(&me->edata, &bm->edata, mask, CD_CALLOC, 0);
+ CustomData_copy(&me->ldata, &bm->ldata, mask, CD_CALLOC, 0);
+ CustomData_copy(&me->pdata, &bm->pdata, mask, CD_CALLOC, 0);
if ((params->active_shapekey != 0) && (me->key != NULL)) {
actkey = BLI_findlink(&me->key->block, params->active_shapekey - 1);
diff --git a/source/blender/bmesh/intern/bmesh_mesh_conv.h b/source/blender/bmesh/intern/bmesh_mesh_conv.h
index 1974d364171..e69f3d6c489 100644
--- a/source/blender/bmesh/intern/bmesh_mesh_conv.h
+++ b/source/blender/bmesh/intern/bmesh_mesh_conv.h
@@ -32,7 +32,10 @@
* \ingroup bmesh
*/
+#include "BLI_sys_types.h"
+
struct Mesh;
+typedef uint64_t CustomDataMask;
void BM_mesh_cd_validate(BMesh *bm);
void BM_mesh_cd_flag_ensure(BMesh *bm, struct Mesh *mesh, const char cd_flag);
@@ -48,6 +51,7 @@ struct BMeshFromMeshParams {
uint use_shapekey : 1;
/* define the active shape key (index + 1) */
int active_shapekey;
+ int64_t cd_mask_extra;
};
void BM_mesh_bm_from_me(
BMesh *bm, struct Mesh *me,
diff --git a/source/blender/bmesh/intern/bmesh_operators_private.h b/source/blender/bmesh/intern/bmesh_operators_private.h
index 5548ee7c361..c0896691fd3 100644
--- a/source/blender/bmesh/intern/bmesh_operators_private.h
+++ b/source/blender/bmesh/intern/bmesh_operators_private.h
@@ -54,6 +54,7 @@ void bmo_create_icosphere_exec(BMesh *bm, BMOperator *op);
void bmo_create_monkey_exec(BMesh *bm, BMOperator *op);
void bmo_create_uvsphere_exec(BMesh *bm, BMOperator *op);
void bmo_create_vert_exec(BMesh *bm, BMOperator *op);
+//void bmo_create_strand_exec(BMesh *bm, BMOperator *op);
void bmo_delete_exec(BMesh *bm, BMOperator *op);
void bmo_dissolve_edges_exec(BMesh *bm, BMOperator *op);
void bmo_dissolve_faces_exec(BMesh *bm, BMOperator *op);
diff --git a/source/blender/bmesh/intern/bmesh_strands.c b/source/blender/bmesh/intern/bmesh_strands.c
new file mode 100644
index 00000000000..87477716562
--- /dev/null
+++ b/source/blender/bmesh/intern/bmesh_strands.c
@@ -0,0 +1,158 @@
+/*
+ * ***** 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 *****
+ */
+
+/** \file blender/bmesh/intern/bmesh_strands.c
+ * \ingroup bmesh
+ */
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_utildefines.h"
+#include "BLI_math.h"
+#include "BLI_mempool.h"
+
+#include "bmesh.h"
+#include "bmesh_private.h"
+
+/*
+ * STRANDS OF MESH CALLBACKS
+ */
+
+void bmstranditer__strands_of_mesh_begin(struct BMIter__elem_of_mesh *iter)
+{
+ BLI_mempool_iternew(iter->pooliter.pool, &iter->pooliter);
+}
+
+void *bmstranditer__strands_of_mesh_step(struct BMIter__elem_of_mesh *iter)
+{
+ BMVert *v;
+
+ do {
+ v = BLI_mempool_iterstep(&iter->pooliter);
+ } while (v && !BM_strands_vert_is_root(v));
+
+ return v;
+}
+
+/*
+ * VERTS OF STRAND CALLBACKS
+ */
+
+/* BMIter__vert_of_strand is not included in the union in BMIter, just make sure it is big enough */
+BLI_STATIC_ASSERT(sizeof(BMIter__vert_of_strand) <= sizeof(BMIter), "BMIter must be at least as large as BMIter__vert_of_strand")
+
+void bmstranditer__verts_of_strand_begin(struct BMIter__vert_of_strand *iter)
+{
+ iter->e_next = iter->v_next->e;
+}
+
+void *bmstranditer__verts_of_strand_step(struct BMIter__vert_of_strand *iter)
+{
+ BMVert *v_curr = iter->v_next;
+
+ if (iter->e_next) {
+ BMEdge *e_first = iter->e_next;
+
+ /* select the other vertex of the current edge */
+ iter->v_next = (iter->v_next == iter->e_next->v1 ? iter->e_next->v2 : iter->e_next->v1);
+
+ /* select the next edge of the current vertex */
+ iter->e_next = bmesh_disk_edge_next(iter->e_next, iter->v_next);
+ if (iter->e_next == e_first) {
+ /* only one edge means the last segment, terminate */
+ iter->e_next = NULL;
+ }
+ }
+ else
+ iter->v_next = NULL; /* last vertex, terminate */
+
+ return v_curr;
+}
+
+/* ------------------------------------------------------------------------- */
+
+int BM_strands_count(BMesh *bm)
+{
+ BMVert *v;
+ BMIter iter;
+
+ int count = 0;
+ BM_ITER_STRANDS(v, &iter, bm, BM_STRANDS_OF_MESH) {
+ ++count;
+ }
+
+ return count;
+}
+
+int BM_strands_keys_count(BMVert *root)
+{
+ BMVert *v;
+ BMIter iter;
+
+ int count = 0;
+ BM_ITER_STRANDS_ELEM(v, &iter, root, BM_VERTS_OF_STRAND) {
+ ++count;
+ }
+
+ return count;
+}
+
+int BM_strands_keys_count_max(BMesh *bm)
+{
+ BMVert *v;
+ BMIter iter;
+ int maxkeys = 0;
+ BM_ITER_STRANDS(v, &iter, bm, BM_STRANDS_OF_MESH) {
+ int n = BM_strands_keys_count(v);
+ if (n > maxkeys)
+ maxkeys = n;
+ }
+ return maxkeys;
+}
+
+/* ------------------------------------------------------------------------- */
+
+/* Create a new strand */
+BMVert *BM_strands_create(BMesh *bm, int len, bool set_defaults)
+{
+ float co[3] = {0.0f, 0.0f, 0.0f};
+
+ BMVert *root, *v, *vprev;
+ int k;
+
+ for (k = 0; k < len; ++k) {
+ vprev = v;
+ v = BM_vert_create(bm, co, NULL, set_defaults ? BM_CREATE_NOP : BM_CREATE_SKIP_CD);
+
+ zero_v3(v->no);
+
+ /* root */
+ if (k == 0) {
+ root = v;
+ }
+ else {
+ /*BMEdge *e =*/ BM_edge_create(bm, vprev, v, NULL, set_defaults ? BM_CREATE_NOP : BM_CREATE_SKIP_CD);
+ }
+ }
+
+ return root;
+}
diff --git a/source/blender/bmesh/intern/bmesh_strands.h b/source/blender/bmesh/intern/bmesh_strands.h
new file mode 100644
index 00000000000..1f060fa2dae
--- /dev/null
+++ b/source/blender/bmesh/intern/bmesh_strands.h
@@ -0,0 +1,216 @@
+/*
+ * ***** 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 *****
+ */
+
+#ifndef __BMESH_STRANDS_H__
+#define __BMESH_STRANDS_H__
+
+/** \file blender/bmesh/intern/bmesh_strands.h
+ * \ingroup bmesh
+ */
+
+#include "BLI_utildefines.h"
+
+#include "bmesh.h"
+#include "bmesh_queries.h"
+#include "bmesh_structure.h"
+
+/* True if v is the root of a strand */
+BLI_INLINE bool BM_strands_vert_is_root(BMVert *v)
+{
+ BMEdge *e_first = v->e;
+ BMEdge *e_next;
+
+ if (!e_first)
+ return true; /* single vertex is both root and tip */
+ e_next = bmesh_disk_edge_next(e_first, v);
+
+ /* with a single edge, the vertex is either first or last of the curve;
+ * first vertex is defined as the root
+ */
+ if (e_next == e_first) {
+ if (e_first->v1 == v)
+ return true;
+ }
+ return false;
+}
+
+/* True if v is the tip of a strand */
+BLI_INLINE bool BM_strands_vert_is_tip(BMVert *v)
+{
+ BMEdge *e_first = v->e;
+ BMEdge *e_next;
+
+ if (!e_first)
+ return true; /* single vertex is both root and tip */
+ e_next = bmesh_disk_edge_next(e_first, v);
+
+ /* with a single edge, the vertex is either first or last of the curve;
+ * last vertex is defined as the tip
+ */
+ if (e_next == e_first) {
+ if (e_first->v2 == v)
+ return true;
+ }
+ return false;
+}
+
+/* Next vertex on a strand */
+BLI_INLINE BMVert *BM_strands_vert_next(BMVert *v)
+{
+ BMEdge *e_first = v->e;
+ BMEdge *e_next;
+
+ /* one of the edges leads to the previous vertex */
+ if (e_first) {
+ if (e_first->v1 == v)
+ return e_first->v2;
+
+ e_next = bmesh_disk_edge_next(e_first, v);
+ if (e_next->v1 == v)
+ return e_next->v2;
+ }
+ return NULL;
+}
+
+/* Previous vertex on a strand */
+BLI_INLINE BMVert *BM_strands_vert_prev(BMVert *v)
+{
+ BMEdge *e_first = v->e;
+ BMEdge *e_next;
+
+ /* one of the edges leads to the previous vertex */
+ if (e_first) {
+ if (e_first->v2 == v)
+ return e_first->v1;
+
+ e_next = bmesh_disk_edge_next(e_first, v);
+ if (e_next->v2 == v)
+ return e_next->v1;
+ }
+ return NULL;
+}
+
+int BM_strands_count(BMesh *bm);
+int BM_strands_keys_count(BMVert *root);
+int BM_strands_keys_count_max(BMesh *bm);
+
+/* Create a new strand */
+struct BMVert *BM_strands_create(struct BMesh *bm, int len, bool set_defaults);
+
+/* ==== Iterators ==== */
+
+typedef enum BMStrandsIterType {
+ BM_STRANDS_OF_MESH,
+ BM_VERTS_OF_STRAND,
+} BMStrandsIterType;
+
+#define BM_ITER_STRANDS(ele, iter, bm, itype) \
+ for (BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BM_strand_iter_new(iter, bm, itype, NULL); \
+ ele; \
+ BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BM_iter_step(iter))
+
+#define BM_ITER_STRANDS_INDEX(ele, iter, bm, itype, indexvar) \
+ for (BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BM_strand_iter_new(iter, bm, itype, NULL), indexvar = 0; \
+ ele; \
+ BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BM_iter_step(iter), (indexvar)++)
+
+#define BM_ITER_STRANDS_ELEM(ele, iter, data, itype) \
+ for (BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BM_strand_iter_new(iter, NULL, itype, data); \
+ ele; \
+ BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BM_iter_step(iter))
+
+#define BM_ITER_STRANDS_ELEM_INDEX(ele, iter, data, itype, indexvar) \
+ for (BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BM_strand_iter_new(iter, NULL, itype, data), indexvar = 0; \
+ ele; \
+ BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BM_iter_step(iter), (indexvar)++)
+
+typedef struct BMIter__vert_of_strand {
+ BMVert *v_next;
+ BMEdge *e_next;
+} BMIter__vert_of_strand;
+
+void bmstranditer__strands_of_mesh_begin(struct BMIter__elem_of_mesh *iter);
+void *bmstranditer__strands_of_mesh_step(struct BMIter__elem_of_mesh *iter);
+
+void bmstranditer__verts_of_strand_begin(struct BMIter__vert_of_strand *iter);
+void *bmstranditer__verts_of_strand_step(struct BMIter__vert_of_strand *iter);
+
+BLI_INLINE bool BM_strand_iter_init(BMIter *iter, BMesh *bm, const char itype, void *data)
+{
+ /* int argtype; */
+ iter->itype = itype;
+
+ /* inlining optimizes out this switch when called with the defined type */
+ switch ((BMStrandsIterType)itype) {
+ case BM_STRANDS_OF_MESH:
+ BLI_assert(bm != NULL);
+ BLI_assert(data == NULL);
+ iter->begin = (BMIter__begin_cb)bmstranditer__strands_of_mesh_begin;
+ iter->step = (BMIter__step_cb)bmstranditer__strands_of_mesh_step;
+ iter->data.elem_of_mesh.pooliter.pool = bm->vpool;
+ break;
+ case BM_VERTS_OF_STRAND: {
+ BMVert *root;
+
+ BLI_assert(data != NULL);
+ BLI_assert(((BMElem *)data)->head.htype == BM_VERT);
+ root = (BMVert *)data;
+ BLI_assert(BM_strands_vert_is_root(root));
+ iter->begin = (BMIter__begin_cb)bmstranditer__verts_of_strand_begin;
+ iter->step = (BMIter__step_cb)bmstranditer__verts_of_strand_step;
+ ((BMIter__vert_of_strand *)(&iter->data))->v_next = root;
+ break;
+ }
+ default:
+ /* fallback to regular bmesh iterator */
+ return BM_iter_init(iter, bm, itype, data);
+ break;
+ }
+
+ iter->begin(iter);
+
+ return true;
+}
+
+/**
+ * \brief Iterator New
+ *
+ * Takes a bmesh iterator structure and fills
+ * it with the appropriate function pointers based
+ * upon its type and then calls BMeshIter_step()
+ * to return the first element of the iterator.
+ *
+ */
+BLI_INLINE void *BM_strand_iter_new(BMIter *iter, BMesh *bm, const char itype, void *data)
+{
+ if (LIKELY(BM_strand_iter_init(iter, bm, itype, data))) {
+ return BM_iter_step(iter);
+ }
+ else {
+ return NULL;
+ }
+}
+
+#define BM_strand_iter_new(iter, bm, itype, data) \
+ (BM_ITER_CHECK_TYPE_DATA(data), BM_strand_iter_new(iter, bm, itype, data))
+
+#endif /* __BMESH_STRANDS_H__ */
diff --git a/source/blender/bmesh/intern/bmesh_strands_conv.c b/source/blender/bmesh/intern/bmesh_strands_conv.c
new file mode 100644
index 00000000000..6cd39798126
--- /dev/null
+++ b/source/blender/bmesh/intern/bmesh_strands_conv.c
@@ -0,0 +1,720 @@
+/*
+ * ***** 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 *****
+ */
+
+/** \file blender/bmesh/intern/bmesh_strands_conv.c
+ * \ingroup bmesh
+ *
+ * BM mesh conversion functions.
+ */
+
+#include "DNA_meshdata_types.h"
+#include "DNA_object_types.h"
+#include "DNA_particle_types.h"
+#include "DNA_key_types.h"
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_listbase.h"
+#include "BLI_math.h"
+
+#include "BKE_customdata.h"
+#include "BKE_key.h"
+#include "BKE_main.h"
+#include "BKE_mesh_sample.h"
+#include "BKE_particle.h"
+
+#include "bmesh.h"
+#include "intern/bmesh_private.h" /* for element checking */
+
+const char *CD_HAIR_SEGMENT_LENGTH = "HAIR_SEGMENT_LENGTH";
+const char *CD_HAIR_MASS = "HAIR_MASS";
+const char *CD_HAIR_WEIGHT = "HAIR_WEIGHT";
+const char *CD_HAIR_ROOT_LOCATION = "HAIR_ROOT_LOCATION";
+
+/* ------------------------------------------------------------------------- */
+
+/**
+ * Currently this is only used for Python scripts
+ * which may fail to keep matching UV/TexFace layers.
+ *
+ * \note This should only perform any changes in exceptional cases,
+ * if we need this to be faster we could inline #BM_data_layer_add and only
+ * call #update_data_blocks once at the end.
+ */
+void BM_strands_cd_validate(BMesh *UNUSED(bm))
+{
+}
+
+void BM_strands_cd_flag_ensure(BMesh *bm, const char cd_flag)
+{
+ const char cd_flag_all = BM_strands_cd_flag_from_bmesh(bm) | cd_flag;
+ BM_strands_cd_flag_apply(bm, cd_flag_all);
+}
+
+void BM_strands_cd_flag_apply(BMesh *bm, const char UNUSED(cd_flag))
+{
+ /* CustomData_bmesh_init_pool() must run first */
+ BLI_assert(bm->vdata.totlayer == 0 || bm->vdata.pool != NULL);
+ BLI_assert(bm->edata.totlayer == 0 || bm->edata.pool != NULL);
+
+ if (CustomData_get_named_layer_index(&bm->vdata, CD_PROP_FLT, CD_HAIR_MASS) < 0) {
+ BM_data_layer_add_named(bm, &bm->vdata, CD_PROP_FLT, CD_HAIR_MASS);
+ }
+ if (CustomData_get_named_layer_index(&bm->vdata, CD_PROP_FLT, CD_HAIR_WEIGHT) < 0) {
+ BM_data_layer_add_named(bm, &bm->vdata, CD_PROP_FLT, CD_HAIR_WEIGHT);
+ }
+ if (CustomData_get_named_layer_index(&bm->vdata, CD_MSURFACE_SAMPLE, CD_HAIR_ROOT_LOCATION) < 0) {
+ BM_data_layer_add_named(bm, &bm->vdata, CD_MSURFACE_SAMPLE, CD_HAIR_ROOT_LOCATION);
+ }
+ if (CustomData_get_named_layer_index(&bm->vdata, CD_PROP_FLT, CD_HAIR_SEGMENT_LENGTH) < 0) {
+ BM_data_layer_add_named(bm, &bm->vdata, CD_PROP_FLT, CD_HAIR_SEGMENT_LENGTH);
+ }
+}
+
+char BM_strands_cd_flag_from_bmesh(BMesh *UNUSED(bm))
+{
+ char cd_flag = 0;
+ return cd_flag;
+}
+
+/* particles */
+
+int BM_strands_count_psys_keys(ParticleSystem *psys)
+{
+ ParticleData *pa;
+ int p;
+ int totkeys = 0;
+
+ for (p = 0, pa = psys->particles; p < psys->totpart; ++p, ++pa)
+ totkeys += pa->totkey;
+
+ return totkeys;
+}
+
+#if 0
+static KeyBlock *bm_set_shapekey_from_psys(BMesh *bm, ParticleSystem *psys, int totvert, int act_key_nr)
+{
+ KeyBlock *actkey, *block;
+ int i, j;
+
+ if (!psys->key) {
+ return NULL;
+ }
+
+ if (act_key_nr != 0)
+ actkey = BLI_findlink(&psys->key->block, act_key_nr - 1);
+ else
+ actkey = NULL;
+
+ CustomData_add_layer(&bm->vdata, CD_SHAPE_KEYINDEX, CD_ASSIGN, NULL, 0);
+
+ /* check if we need to generate unique ids for the shapekeys.
+ * this also exists in the file reading code, but is here for
+ * a sanity check */
+ if (!psys->key->uidgen) {
+ fprintf(stderr,
+ "%s had to generate shape key uid's in a situation we shouldn't need to! "
+ "(bmesh internal error)\n",
+ __func__);
+
+ psys->key->uidgen = 1;
+ for (block = psys->key->block.first; block; block = block->next) {
+ block->uid = psys->key->uidgen++;
+ }
+ }
+
+ if (actkey && actkey->totelem == totvert) {
+ bm->shapenr = act_key_nr;
+ }
+
+ for (i = 0, block = psys->key->block.first; block; block = block->next, i++) {
+ CustomData_add_layer_named(&bm->vdata, CD_SHAPEKEY,
+ CD_ASSIGN, NULL, 0, block->name);
+
+ j = CustomData_get_layer_index_n(&bm->vdata, CD_SHAPEKEY, i);
+ bm->vdata.layers[j].uid = block->uid;
+ }
+
+ return actkey;
+}
+#endif
+
+/* create vertex and edge data for BMesh based on particle hair keys */
+static void bm_make_particles(BMesh *bm, Object *ob, ParticleSystem *psys, struct DerivedMesh *emitter_dm, float (*keyco)[3], int UNUSED(cd_shape_keyindex_offset))
+{
+// KeyBlock *block;
+ ParticleData *pa;
+ HairKey *hkey;
+ int p, k;
+
+ int vindex, eindex;
+ BMVert *v = NULL, *v_prev;
+ BMEdge *e;
+
+ float hairmat[4][4];
+
+ /* XXX currently all particles and keys have the same mass, this may change */
+ float mass = psys->part->mass;
+
+ vindex = 0;
+ eindex = 0;
+ for (p = 0, pa = psys->particles; p < psys->totpart; ++p, ++pa) {
+
+ /* hair keys are in a local "hair space", but edit data should be in object space */
+ psys_mat_hair_to_object(ob, emitter_dm, psys->part->from, pa, hairmat);
+
+ for (k = 0, hkey = pa->hair; k < pa->totkey; ++k, ++hkey) {
+ float co[3];
+
+ copy_v3_v3(co, keyco ? keyco[vindex] : hkey->co);
+ mul_m4_v3(hairmat, co);
+
+ v_prev = v;
+ v = BM_vert_create(bm, co, NULL, BM_CREATE_SKIP_CD);
+ BM_elem_index_set(v, vindex); /* set_ok */
+
+ /* transfer flag */
+// v->head.hflag = BM_vert_flag_from_mflag(mvert->flag & ~SELECT);
+
+ /* this is necessary for selection counts to work properly */
+// if (hkey->editflag & SELECT) {
+// BM_vert_select_set(bm, v, true);
+// }
+
+// normal_short_to_float_v3(v->no, mvert->no);
+
+ /* Copy Custom Data */
+// CustomData_to_bmesh_block(&me->vdata, &bm->vdata, vindex, &v->head.data, true);
+ CustomData_bmesh_set_default(&bm->vdata, &v->head.data);
+
+ BM_elem_float_data_named_set(&bm->vdata, v, CD_PROP_FLT, CD_HAIR_MASS, mass);
+ BM_elem_float_data_named_set(&bm->vdata, v, CD_PROP_FLT, CD_HAIR_WEIGHT, hkey->weight);
+
+ /* root */
+ if (k == 0) {
+ MeshSample root_loc;
+ if (BKE_mesh_sample_from_particle(&root_loc, psys, emitter_dm, pa)) {
+ BM_elem_meshsample_data_named_set(&bm->vdata, v, CD_MSURFACE_SAMPLE, CD_HAIR_ROOT_LOCATION, &root_loc);
+ }
+ }
+
+#if 0
+ /* set shapekey data */
+ if (psys->key) {
+ /* set shape key original index */
+ if (cd_shape_keyindex_offset != -1) BM_ELEM_CD_SET_INT(v, cd_shape_keyindex_offset, vindex);
+
+ for (block = psys->key->block.first, j = 0; block; block = block->next, j++) {
+ float *co = CustomData_bmesh_get_n(&bm->vdata, v->head.data, CD_SHAPEKEY, j);
+
+ if (co) {
+ copy_v3_v3(co, ((float *)block->data) + 3 * vindex);
+ }
+ }
+ }
+#endif
+
+ vindex += 1;
+
+ if (k > 0) {
+ e = BM_edge_create(bm, v_prev, v, NULL, BM_CREATE_SKIP_CD);
+ BM_elem_index_set(e, eindex); /* set_ok; one less edge than vertices for each particle */
+
+ /* transfer flags */
+// e->head.hflag = BM_edge_flag_from_mflag(medge->flag & ~SELECT);
+
+ /* this is necessary for selection counts to work properly */
+// if (medge->flag & SELECT) {
+// BM_edge_select_set(bm, e, true);
+// }
+
+ /* Copy Custom Data */
+// CustomData_to_bmesh_block(&me->edata, &bm->edata, eindex, &e->head.data, true);
+ CustomData_bmesh_set_default(&bm->edata, &e->head.data);
+
+ eindex += 1;
+ }
+
+ } /* hair keys */
+
+ } /* particles */
+
+ bm->elem_index_dirty &= ~(BM_VERT | BM_EDGE); /* added in order, clear dirty flag */
+}
+
+/**
+ * \brief ParticleSystem -> BMesh
+ */
+void BM_strands_bm_from_psys(BMesh *bm, Object *ob, ParticleSystem *psys, struct DerivedMesh *emitter_dm,
+ const bool set_key, int UNUSED(act_key_nr))
+{
+ /*KeyBlock *actkey;*/
+ float (*keyco)[3] = NULL;
+ int totvert, totedge;
+
+ int cd_shape_keyindex_offset;
+
+ /* free custom data */
+ /* this isnt needed in most cases but do just incase */
+ CustomData_free(&bm->vdata, bm->totvert);
+ CustomData_free(&bm->edata, bm->totedge);
+ CustomData_free(&bm->ldata, bm->totloop);
+ CustomData_free(&bm->pdata, bm->totface);
+
+ totvert = BM_strands_count_psys_keys(psys);
+ totedge = totvert - psys->totpart;
+
+ if (!psys || !totvert || !totedge) {
+ if (psys) { /*no verts? still copy customdata layout*/
+// CustomData_copy(&me->vdata, &bm->vdata, CD_MASK_BMESH, CD_ASSIGN, 0);
+// CustomData_copy(&me->edata, &bm->edata, CD_MASK_BMESH, CD_ASSIGN, 0);
+
+ CustomData_bmesh_init_pool(&bm->vdata, totvert, BM_VERT);
+ CustomData_bmesh_init_pool(&bm->edata, totedge, BM_EDGE);
+ CustomData_bmesh_init_pool(&bm->ldata, 0, BM_LOOP);
+ CustomData_bmesh_init_pool(&bm->pdata, 0, BM_FACE);
+ }
+ return; /* sanity check */
+ }
+
+#if 0
+ actkey = bm_set_shapekey_from_psys(bm, psys, totvert, act_key_nr);
+ if (actkey)
+ keyco = actkey->data;
+#endif
+
+ CustomData_bmesh_init_pool(&bm->vdata, totvert, BM_VERT);
+ CustomData_bmesh_init_pool(&bm->edata, totedge, BM_EDGE);
+
+ BM_strands_cd_flag_apply(bm, /*psys->cd_flag*/0);
+
+ cd_shape_keyindex_offset = /*psys->key ? CustomData_get_offset(&bm->vdata, CD_SHAPE_KEYINDEX) :*/ -1;
+
+ bm_make_particles(bm, ob, psys, emitter_dm, set_key ? keyco : NULL, cd_shape_keyindex_offset);
+
+
+#if 0 /* TODO */
+ if (me->mselect && me->totselect != 0) {
+
+ BMVert **vert_array = MEM_mallocN(sizeof(BMVert *) * bm->totvert, "VSelConv");
+ BMEdge **edge_array = MEM_mallocN(sizeof(BMEdge *) * bm->totedge, "ESelConv");
+ BMFace **face_array = MEM_mallocN(sizeof(BMFace *) * bm->totface, "FSelConv");
+ MSelect *msel;
+
+#pragma omp parallel sections if (bm->totvert + bm->totedge + bm->totface >= BM_OMP_LIMIT)
+ {
+#pragma omp section
+ { BM_iter_as_array(bm, BM_VERTS_OF_MESH, NULL, (void **)vert_array, bm->totvert); }
+#pragma omp section
+ { BM_iter_as_array(bm, BM_EDGES_OF_MESH, NULL, (void **)edge_array, bm->totedge); }
+#pragma omp section
+ { BM_iter_as_array(bm, BM_FACES_OF_MESH, NULL, (void **)face_array, bm->totface); }
+ }
+
+ for (i = 0, msel = me->mselect; i < me->totselect; i++, msel++) {
+ switch (msel->type) {
+ case ME_VSEL:
+ BM_select_history_store(bm, (BMElem *)vert_array[msel->index]);
+ break;
+ case ME_ESEL:
+ BM_select_history_store(bm, (BMElem *)edge_array[msel->index]);
+ break;
+ case ME_FSEL:
+ BM_select_history_store(bm, (BMElem *)face_array[msel->index]);
+ break;
+ }
+ }
+
+ MEM_freeN(vert_array);
+ MEM_freeN(edge_array);
+ MEM_freeN(face_array);
+ }
+ else {
+ me->totselect = 0;
+ if (me->mselect) {
+ MEM_freeN(me->mselect);
+ me->mselect = NULL;
+ }
+ }
+#endif
+}
+
+#if 0
+/**
+ * \brief BMesh -> Mesh
+ */
+static BMVert **bm_to_mesh_vertex_map(BMesh *bm, int ototvert)
+{
+ const int cd_shape_keyindex_offset = CustomData_get_offset(&bm->vdata, CD_SHAPE_KEYINDEX);
+ BMVert **vertMap = NULL;
+ BMVert *eve;
+ int i = 0;
+ BMIter iter;
+
+ /* caller needs to ensure this */
+ BLI_assert(ototvert > 0);
+
+ vertMap = MEM_callocN(sizeof(*vertMap) * ototvert, "vertMap");
+ if (cd_shape_keyindex_offset != -1) {
+ BM_ITER_MESH_INDEX (eve, &iter, bm, BM_VERTS_OF_MESH, i) {
+ const int keyi = BM_ELEM_CD_GET_INT(eve, cd_shape_keyindex_offset);
+ if ((keyi != ORIGINDEX_NONE) && (keyi < ototvert)) {
+ vertMap[keyi] = eve;
+ }
+ }
+ }
+ else {
+ BM_ITER_MESH_INDEX (eve, &iter, bm, BM_VERTS_OF_MESH, i) {
+ if (i < ototvert) {
+ vertMap[i] = eve;
+ }
+ else {
+ break;
+ }
+ }
+ }
+
+ return vertMap;
+}
+
+/**
+ * returns customdata shapekey index from a keyblock or -1
+ * \note could split this out into a more generic function */
+static int bm_to_mesh_shape_layer_index_from_kb(BMesh *bm, KeyBlock *currkey)
+{
+ int i;
+ int j = 0;
+
+ for (i = 0; i < bm->vdata.totlayer; i++) {
+ if (bm->vdata.layers[i].type == CD_SHAPEKEY) {
+ if (currkey->uid == bm->vdata.layers[i].uid) {
+ return j;
+ }
+ j++;
+ }
+ }
+ return -1;
+}
+
+BLI_INLINE void bmesh_quick_edgedraw_flag(MEdge *med, BMEdge *e)
+{
+ /* this is a cheap way to set the edge draw, its not precise and will
+ * pick the first 2 faces an edge uses.
+ * The dot comparison is a little arbitrary, but set so that a 5 subd
+ * IcoSphere won't vanish but subd 6 will (as with pre-bmesh blender) */
+
+
+ if ( /* (med->flag & ME_EDGEDRAW) && */ /* assume to be true */
+ (e->l && (e->l != e->l->radial_next)) &&
+ (dot_v3v3(e->l->f->no, e->l->radial_next->f->no) > 0.9995f))
+ {
+ med->flag &= ~ME_EDGEDRAW;
+ }
+ else {
+ med->flag |= ME_EDGEDRAW;
+ }
+}
+#endif
+
+static void make_particle_hair(BMesh *bm, BMVert *root, Object *ob, ParticleSystem *psys, struct DerivedMesh *emitter_dm, struct BVHTreeFromMesh *emitter_bvhtree, struct ParticleData *pa)
+{
+ int totkey = BM_strands_keys_count(root);
+ HairKey *hair;
+
+ BMVert *v;
+ BMIter iter;
+ HairKey *hkey;
+ int k;
+
+ float inv_hairmat[4][4];
+
+ pa->alive = PARS_ALIVE;
+ pa->flag = 0;
+
+ pa->time = 0.0f;
+ pa->lifetime = 100.0f;
+ pa->dietime = 100.0f;
+
+ pa->size = psys->part->size;
+
+ // TODO define other particle stuff ...
+
+ hair = MEM_callocN(totkey * sizeof(HairKey), "hair keys");
+
+ hkey = hair;
+ k = 0;
+ BM_ITER_STRANDS_ELEM(v, &iter, root, BM_VERTS_OF_STRAND) {
+ /* root */
+ if (k == 0) {
+ MeshSample root_loc;
+ BM_elem_meshsample_data_named_get(&bm->vdata, v, CD_MSURFACE_SAMPLE, CD_HAIR_ROOT_LOCATION, &root_loc);
+ if (!BKE_mesh_sample_to_particle(&root_loc, psys, emitter_dm, emitter_bvhtree, pa)) {
+ pa->num = 0;
+ pa->num_dmcache = DMCACHE_NOTFOUND;
+ zero_v4(pa->fuv);
+ pa->foffset = 0.0f;
+ }
+
+ /* edit data is in object space, hair keys must be converted back into "hair space" */
+ psys_mat_hair_to_object(ob, emitter_dm, psys->part->from, pa, inv_hairmat);
+ invert_m4(inv_hairmat);
+ }
+
+ mul_v3_m4v3(hkey->co, inv_hairmat, v->co);
+ mul_v3_m4v3(hkey->world_co, ob->obmat, v->co);
+
+ hkey->time = totkey > 0 ? (float)k / (float)(totkey - 1) : 0.0f;
+ if (k == 0) {
+ /* weight 1.0 is used for pinning hair roots in particles */
+ hkey->weight = 1.0f;
+ }
+ else {
+ hkey->weight = BM_elem_float_data_named_get(&bm->vdata, v, CD_PROP_FLT, CD_HAIR_WEIGHT);
+ }
+
+ ++hkey;
+ ++k;
+
+ BM_CHECK_ELEMENT(v);
+ }
+
+ if (pa->hair)
+ MEM_freeN(pa->hair);
+
+ pa->hair = hair;
+ pa->totkey = totkey;
+}
+
+void BM_strands_bm_to_psys(BMesh *bm, Object *ob, ParticleSystem *psys, struct DerivedMesh *emitter_dm, struct BVHTreeFromMesh *emitter_bvhtree)
+{
+ ParticleData *particles, *oldparticles;
+ int ototpart, ntotpart;
+
+ BMVert *root;
+ BMIter iter;
+ ParticleData *pa;
+ int p;
+
+ ototpart = psys->totpart;
+
+ ntotpart = BM_strands_count(bm);
+
+ /* new particles block */
+ if (bm->totvert == 0) particles = NULL;
+ else particles = MEM_callocN(ntotpart * sizeof(ParticleData), "particles");
+
+ /* lets save the old particles just in case we are actually working on
+ * a key ... we now do processing of the keys at the end */
+ oldparticles = psys->particles;
+
+ psys->totpart = ntotpart;
+
+// psys->cd_flag = BM_strands_cd_flag_from_bmesh(bm);
+
+ pa = particles;
+ p = 0;
+ BM_ITER_STRANDS(root, &iter, bm, BM_STRANDS_OF_MESH) {
+
+ make_particle_hair(bm, root, ob, psys, emitter_dm, emitter_bvhtree, pa);
+
+ ++pa;
+ ++p;
+ }
+ bm->elem_index_dirty &= ~BM_VERT;
+
+
+#if 0 // TODO
+ {
+ BMEditSelection *selected;
+ me->totselect = BLI_listbase_count(&(bm->selected));
+
+ if (me->mselect) MEM_freeN(me->mselect);
+
+ me->mselect = MEM_callocN(sizeof(MSelect) * me->totselect, "Mesh selection history");
+
+
+ for (i = 0, selected = bm->selected.first; selected; i++, selected = selected->next) {
+ if (selected->htype == BM_VERT) {
+ me->mselect[i].type = ME_VSEL;
+
+ }
+ else if (selected->htype == BM_EDGE) {
+ me->mselect[i].type = ME_ESEL;
+
+ }
+ else if (selected->htype == BM_FACE) {
+ me->mselect[i].type = ME_FSEL;
+ }
+
+ me->mselect[i].index = BM_elem_index_get(selected->ele);
+ }
+ }
+#endif
+
+#if 0 // TODO
+ /* see comment below, this logic is in twice */
+
+ if (me->key) {
+ const int cd_shape_keyindex_offset = CustomData_get_offset(&bm->vdata, CD_SHAPE_KEYINDEX);
+
+ KeyBlock *currkey;
+ KeyBlock *actkey = BLI_findlink(&me->key->block, bm->shapenr - 1);
+
+ float (*ofs)[3] = NULL;
+
+ /* go through and find any shapekey customdata layers
+ * that might not have corresponding KeyBlocks, and add them if
+ * necessary */
+ j = 0;
+ for (i = 0; i < bm->vdata.totlayer; i++) {
+ if (bm->vdata.layers[i].type != CD_SHAPEKEY)
+ continue;
+
+ for (currkey = me->key->block.first; currkey; currkey = currkey->next) {
+ if (currkey->uid == bm->vdata.layers[i].uid)
+ break;
+ }
+
+ if (!currkey) {
+ currkey = BKE_keyblock_add(me->key, bm->vdata.layers[i].name);
+ currkey->uid = bm->vdata.layers[i].uid;
+ }
+
+ j++;
+ }
+
+
+ /* editing the base key should update others */
+ if ((me->key->type == KEY_RELATIVE) && /* only need offsets for relative shape keys */
+ (actkey != NULL) && /* unlikely, but the active key may not be valid if the
+ * bmesh and the mesh are out of sync */
+ (oldverts != NULL)) /* not used here, but 'oldverts' is used later for applying 'ofs' */
+ {
+ const bool act_is_basis = BKE_keyblock_is_basis(me->key, bm->shapenr - 1);
+
+ /* active key is a base */
+ if (act_is_basis && (cd_shape_keyindex_offset != -1)) {
+ float (*fp)[3] = actkey->data;
+
+ ofs = MEM_callocN(sizeof(float) * 3 * bm->totvert, "currkey->data");
+ mvert = me->mvert;
+ BM_ITER_MESH_INDEX (eve, &iter, bm, BM_VERTS_OF_MESH, i) {
+ const int keyi = BM_ELEM_CD_GET_INT(eve, cd_shape_keyindex_offset);
+
+ if (keyi != ORIGINDEX_NONE) {
+ sub_v3_v3v3(ofs[i], mvert->co, fp[keyi]);
+ }
+ else {
+ /* if there are new vertices in the mesh, we can't propagate the offset
+ * because it will only work for the existing vertices and not the new
+ * ones, creating a mess when doing e.g. subdivide + translate */
+ MEM_freeN(ofs);
+ ofs = NULL;
+ break;
+ }
+
+ mvert++;
+ }
+ }
+ }
+
+ for (currkey = me->key->block.first; currkey; currkey = currkey->next) {
+ const bool apply_offset = (ofs && (currkey != actkey) && (bm->shapenr - 1 == currkey->relative));
+ int cd_shape_offset;
+ int keyi;
+ float (*ofs_pt)[3] = ofs;
+ float *newkey, (*oldkey)[3], *fp;
+
+ j = bm_to_mesh_shape_layer_index_from_kb(bm, currkey);
+ cd_shape_offset = CustomData_get_n_offset(&bm->vdata, CD_SHAPEKEY, j);
+
+
+ fp = newkey = MEM_callocN(me->key->elemsize * bm->totvert, "currkey->data");
+ oldkey = currkey->data;
+
+ mvert = me->mvert;
+ BM_ITER_MESH (eve, &iter, bm, BM_VERTS_OF_MESH) {
+
+ if (currkey == actkey) {
+ copy_v3_v3(fp, eve->co);
+
+ if (actkey != me->key->refkey) { /* important see bug [#30771] */
+ if (cd_shape_keyindex_offset != -1) {
+ if (oldverts) {
+ keyi = BM_ELEM_CD_GET_INT(eve, cd_shape_keyindex_offset);
+ if (keyi != ORIGINDEX_NONE && keyi < currkey->totelem) { /* valid old vertex */
+ copy_v3_v3(mvert->co, oldverts[keyi].co);
+ }
+ }
+ }
+ }
+ }
+ else if (j != -1) {
+ /* in most cases this runs */
+ copy_v3_v3(fp, BM_ELEM_CD_GET_VOID_P(eve, cd_shape_offset));
+ }
+ else if ((oldkey != NULL) &&
+ (cd_shape_keyindex_offset != -1) &&
+ ((keyi = BM_ELEM_CD_GET_INT(eve, cd_shape_keyindex_offset)) != ORIGINDEX_NONE) &&
+ (keyi < currkey->totelem))
+ {
+ /* old method of reconstructing keys via vertice's original key indices,
+ * currently used if the new method above fails (which is theoretically
+ * possible in certain cases of undo) */
+ copy_v3_v3(fp, oldkey[keyi]);
+ }
+ else {
+ /* fail! fill in with dummy value */
+ copy_v3_v3(fp, mvert->co);
+ }
+
+ /* propagate edited basis offsets to other shapes */
+ if (apply_offset) {
+ add_v3_v3(fp, *ofs_pt++);
+ }
+
+ fp += 3;
+ mvert++;
+ }
+
+ currkey->totelem = bm->totvert;
+ if (currkey->data) {
+ MEM_freeN(currkey->data);
+ }
+ currkey->data = newkey;
+ }
+
+ if (ofs) MEM_freeN(ofs);
+ }
+#else
+ psys->particles = particles;
+#endif
+
+ if (oldparticles) {
+ ParticleData *opa;
+ int op;
+ for (op = 0, opa = oldparticles; op < ototpart; ++op, ++opa)
+ if (opa->hair)
+ MEM_freeN(opa->hair);
+ MEM_freeN(oldparticles);
+ }
+}
diff --git a/source/blender/bmesh/intern/bmesh_strands_conv.h b/source/blender/bmesh/intern/bmesh_strands_conv.h
new file mode 100644
index 00000000000..334fabb53a8
--- /dev/null
+++ b/source/blender/bmesh/intern/bmesh_strands_conv.h
@@ -0,0 +1,62 @@
+/*
+ * ***** 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) 2014 Blender Foundation.
+ * All rights reserved.
+ *
+ * The Original Code is: all of this file.
+ *
+ * Contributor(s): Lukas Toenne.
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+#ifndef __BMESH_STRANDS_CONV_H__
+#define __BMESH_STRANDS_CONV_H__
+
+/** \file blender/bmesh/intern/bmesh_strands_conv.h
+ * \ingroup bmesh
+ */
+
+struct BMesh;
+struct Mesh;
+struct Object;
+struct ParticleSystem;
+struct DerivedMesh;
+struct BVHTreeFromMesh;
+
+extern const char *CD_HAIR_SEGMENT_LENGTH;
+extern const char *CD_HAIR_MASS;
+extern const char *CD_HAIR_WEIGHT;
+extern const char *CD_HAIR_ROOT_LOCATION;
+
+void BM_strands_cd_validate(struct BMesh *bm);
+void BM_strands_cd_flag_ensure(struct BMesh *bm, const char cd_flag);
+void BM_strands_cd_flag_apply(struct BMesh *bm, const char cd_flag);
+char BM_strands_cd_flag_from_bmesh(struct BMesh *bm);
+
+/* particles */
+
+int BM_strands_count_psys_keys(struct ParticleSystem *psys);
+void BM_strands_bm_from_psys(struct BMesh *bm, struct Object *ob, struct ParticleSystem *psys, struct DerivedMesh *emitter_dm,
+ const bool set_key, int act_key_nr);
+void BM_strands_bm_to_psys(struct BMesh *bm, struct Object *ob, struct ParticleSystem *psys, struct DerivedMesh *emitter_dm, struct BVHTreeFromMesh *emitter_bvhtree);
+
+#define BMALLOC_TEMPLATE_FROM_PSYS(psys) { (CHECK_TYPE_INLINE(psys, ParticleSystem *), \
+ BM_strands_count_psys_keys(psys)), (BM_strands_count_psys_keys(psys) - (psys)->totpart), 0, 0 }
+
+#endif /* __BMESH_STRANDS_CONV_H__ */
diff --git a/source/blender/draw/CMakeLists.txt b/source/blender/draw/CMakeLists.txt
index 63f466d3792..c5ae2dadd6c 100644
--- a/source/blender/draw/CMakeLists.txt
+++ b/source/blender/draw/CMakeLists.txt
@@ -57,10 +57,13 @@ set(SRC
intern/draw_cache.c
intern/draw_cache_impl_curve.c
intern/draw_cache_impl_displist.c
+ intern/draw_cache_impl_hair.c
intern/draw_cache_impl_lattice.c
intern/draw_cache_impl_mesh.c
intern/draw_cache_impl_particles.c
+ intern/draw_cache_impl_strands.c
intern/draw_common.c
+ intern/draw_hair.c
intern/draw_manager.c
intern/draw_manager_text.c
intern/draw_manager_profiling.c
@@ -70,6 +73,7 @@ set(SRC
modes/edit_lattice_mode.c
modes/edit_mesh_mode.c
modes/edit_metaball_mode.c
+ modes/edit_strands_mode.c
modes/edit_surface_mode.c
modes/edit_text_mode.c
modes/object_mode.c
@@ -122,6 +126,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/ambient_occlusion_lib.glsl SRC)
data_to_c_simple(engines/eevee/shaders/effect_minmaxz_frag.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)
@@ -185,6 +190,7 @@ 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)
data_to_c_simple(modes/shaders/edit_normals_geom.glsl SRC)
+data_to_c_simple(modes/shaders/edit_strands_vert.glsl SRC)
data_to_c_simple(modes/shaders/object_empty_image_frag.glsl SRC)
data_to_c_simple(modes/shaders/object_empty_image_vert.glsl SRC)
data_to_c_simple(modes/shaders/object_outline_resolve_frag.glsl SRC)
diff --git a/source/blender/draw/engines/eevee/eevee_materials.c b/source/blender/draw/engines/eevee/eevee_materials.c
index ef330c424ab..070b6be94a4 100644
--- a/source/blender/draw/engines/eevee/eevee_materials.c
+++ b/source/blender/draw/engines/eevee/eevee_materials.c
@@ -25,19 +25,25 @@
#include "DRW_render.h"
-#include "DNA_world_types.h"
+#include "DNA_hair_types.h"
#include "DNA_modifier_types.h"
#include "DNA_view3d_types.h"
+#include "DNA_world_types.h"
#include "BLI_dynstr.h"
#include "BLI_ghash.h"
#include "BLI_alloca.h"
+#include "BKE_DerivedMesh.h"
+#include "BKE_editstrands.h"
#include "BKE_particle.h"
#include "BKE_paint.h"
#include "BKE_pbvh.h"
+#include "DEG_depsgraph.h"
+
#include "GPU_material.h"
+#include "GPU_texture.h"
#include "eevee_engine.h"
#include "eevee_lut.h"
@@ -70,6 +76,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;
@@ -106,6 +114,7 @@ extern char datatoc_lightprobe_geom_glsl[];
extern char datatoc_lightprobe_vert_glsl[];
extern char datatoc_background_vert_glsl[];
extern char datatoc_volumetric_frag_glsl[];
+extern char datatoc_hair_lib_glsl[];
extern Material defmaterial;
extern GlobalsUboStorage ts;
@@ -282,6 +291,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");
}
@@ -397,60 +409,87 @@ static void add_standard_uniforms(
static void create_default_shader(int options)
{
- DynStr *ds_frag = BLI_dynstr_new();
- BLI_dynstr_append(ds_frag, e_data.frag_shader_lib);
- BLI_dynstr_append(ds_frag, datatoc_default_frag_glsl);
- char *frag_str = BLI_dynstr_get_cstring(ds_frag);
- BLI_dynstr_free(ds_frag);
+ 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 *frag_str = NULL;
+ {
+ DynStr *ds_frag = BLI_dynstr_new();
+ BLI_dynstr_append(ds_frag, e_data.frag_shader_lib);
+ BLI_dynstr_append(ds_frag, datatoc_default_frag_glsl);
+ frag_str = BLI_dynstr_get_cstring(ds_frag);
+ BLI_dynstr_free(ds_frag);
+ }
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);
}
void EEVEE_materials_init(EEVEE_StorageList *stl)
{
if (!e_data.frag_shader_lib) {
- char *frag_str = NULL;
-
/* Shaders */
- DynStr *ds_frag = BLI_dynstr_new();
- BLI_dynstr_append(ds_frag, datatoc_bsdf_common_lib_glsl);
+ {
+ DynStr *ds_frag = BLI_dynstr_new();
+ BLI_dynstr_append(ds_frag, datatoc_bsdf_common_lib_glsl);
BLI_dynstr_append(ds_frag, datatoc_bsdf_sampling_lib_glsl);
- BLI_dynstr_append(ds_frag, datatoc_ambient_occlusion_lib_glsl);
+ BLI_dynstr_append(ds_frag, datatoc_ambient_occlusion_lib_glsl);
BLI_dynstr_append(ds_frag, datatoc_raytrace_lib_glsl);
BLI_dynstr_append(ds_frag, datatoc_ssr_lib_glsl);
- BLI_dynstr_append(ds_frag, datatoc_octahedron_lib_glsl);
- BLI_dynstr_append(ds_frag, datatoc_irradiance_lib_glsl);
- BLI_dynstr_append(ds_frag, datatoc_lightprobe_lib_glsl);
- BLI_dynstr_append(ds_frag, datatoc_ltc_lib_glsl);
- BLI_dynstr_append(ds_frag, datatoc_bsdf_direct_lib_glsl);
- BLI_dynstr_append(ds_frag, datatoc_lamps_lib_glsl);
- BLI_dynstr_append(ds_frag, datatoc_lit_surface_frag_glsl);
- e_data.frag_shader_lib = BLI_dynstr_get_cstring(ds_frag);
- BLI_dynstr_free(ds_frag);
+ BLI_dynstr_append(ds_frag, datatoc_octahedron_lib_glsl);
+ BLI_dynstr_append(ds_frag, datatoc_irradiance_lib_glsl);
+ BLI_dynstr_append(ds_frag, datatoc_lightprobe_lib_glsl);
+ BLI_dynstr_append(ds_frag, datatoc_ltc_lib_glsl);
+ BLI_dynstr_append(ds_frag, datatoc_bsdf_direct_lib_glsl);
+ BLI_dynstr_append(ds_frag, datatoc_lamps_lib_glsl);
+ BLI_dynstr_append(ds_frag, datatoc_lit_surface_frag_glsl);
+ e_data.frag_shader_lib = BLI_dynstr_get_cstring(ds_frag);
+ BLI_dynstr_free(ds_frag);
+ }
+
+ {
+ DynStr *ds_frag = BLI_dynstr_new();
+ BLI_dynstr_append(ds_frag, datatoc_bsdf_common_lib_glsl);
+ BLI_dynstr_append(ds_frag, datatoc_ambient_occlusion_lib_glsl);
+ BLI_dynstr_append(ds_frag, datatoc_octahedron_lib_glsl);
+ BLI_dynstr_append(ds_frag, datatoc_irradiance_lib_glsl);
+ BLI_dynstr_append(ds_frag, datatoc_lightprobe_lib_glsl);
+ BLI_dynstr_append(ds_frag, datatoc_ltc_lib_glsl);
+ BLI_dynstr_append(ds_frag, datatoc_bsdf_direct_lib_glsl);
+ BLI_dynstr_append(ds_frag, datatoc_lamps_lib_glsl);
+ BLI_dynstr_append(ds_frag, datatoc_volumetric_frag_glsl);
+ e_data.volume_shader_lib = BLI_dynstr_get_cstring(ds_frag);
+ BLI_dynstr_free(ds_frag);
+ }
- ds_frag = BLI_dynstr_new();
- BLI_dynstr_append(ds_frag, datatoc_bsdf_common_lib_glsl);
- BLI_dynstr_append(ds_frag, datatoc_ambient_occlusion_lib_glsl);
- BLI_dynstr_append(ds_frag, datatoc_octahedron_lib_glsl);
- BLI_dynstr_append(ds_frag, datatoc_irradiance_lib_glsl);
- BLI_dynstr_append(ds_frag, datatoc_lightprobe_lib_glsl);
- BLI_dynstr_append(ds_frag, datatoc_ltc_lib_glsl);
- BLI_dynstr_append(ds_frag, datatoc_bsdf_direct_lib_glsl);
- BLI_dynstr_append(ds_frag, datatoc_lamps_lib_glsl);
- BLI_dynstr_append(ds_frag, datatoc_volumetric_frag_glsl);
- e_data.volume_shader_lib = BLI_dynstr_get_cstring(ds_frag);
- BLI_dynstr_free(ds_frag);
+ char *hair_fiber_vert_str = NULL;
+ {
+ DynStr *ds_vert = BLI_dynstr_new();
+ BLI_dynstr_append(ds_vert, datatoc_hair_lib_glsl);
+ BLI_dynstr_append(ds_vert, datatoc_prepass_vert_glsl);
+ hair_fiber_vert_str = BLI_dynstr_get_cstring(ds_vert);
+ BLI_dynstr_free(ds_vert);
+ }
- ds_frag = BLI_dynstr_new();
- BLI_dynstr_append(ds_frag, e_data.frag_shader_lib);
- BLI_dynstr_append(ds_frag, datatoc_default_frag_glsl);
- frag_str = BLI_dynstr_get_cstring(ds_frag);
- BLI_dynstr_free(ds_frag);
+ char *frag_str = NULL;
+ {
+ DynStr *ds_frag = BLI_dynstr_new();
+ BLI_dynstr_append(ds_frag, e_data.frag_shader_lib);
+ BLI_dynstr_append(ds_frag, datatoc_default_frag_glsl);
+ frag_str = BLI_dynstr_get_cstring(ds_frag);
+ BLI_dynstr_free(ds_frag);
+ }
e_data.default_background = DRW_shader_create(
datatoc_background_vert_glsl, NULL, datatoc_default_world_frag_glsl,
@@ -464,7 +503,19 @@ void EEVEE_materials_init(EEVEE_StorageList *stl)
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);
/* Textures */
const int layers = 3 + 16;
@@ -680,23 +731,35 @@ struct GPUMaterial *EEVEE_material_mesh_depth_get(
}
struct GPUMaterial *EEVEE_material_hair_get(
- struct Scene *scene, Material *ma)
+ struct Scene *scene, Material *ma, 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;
+ if (use_fibers) {
+ options |= VAR_MAT_HAIR_FIBERS;
+ }
GPUMaterial *mat = GPU_material_from_nodetree_find(&ma->gpumaterial, 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 = GPU_material_from_nodetree(
scene, ma->nodetree, &ma->gpumaterial, 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;
@@ -707,13 +770,14 @@ struct GPUMaterial *EEVEE_material_hair_get(
**/
static struct DRWShadingGroup *EEVEE_default_shading_group_create(
EEVEE_SceneLayerData *sldata, EEVEE_Data *vedata, DRWPass *pass,
- bool is_hair, bool is_flat_normal, bool use_blend, bool use_ssr)
+ bool is_hair, bool is_hair_fibers, bool is_flat_normal, bool use_blend, bool use_ssr)
{
static int ssr_id;
ssr_id = (use_ssr) ? 0 : -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;
if (use_blend) options |= VAR_MAT_BLEND;
@@ -732,13 +796,14 @@ static struct DRWShadingGroup *EEVEE_default_shading_group_create(
**/
static struct DRWShadingGroup *EEVEE_default_shading_group_get(
EEVEE_SceneLayerData *sldata, EEVEE_Data *vedata,
- bool is_hair, bool is_flat_normal, bool use_ssr)
+ bool is_hair, bool is_hair_fibers, bool is_flat_normal, bool use_ssr)
{
static int ssr_id;
ssr_id = (use_ssr) ? 0 : -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;
if (e_data.default_lit[options] == NULL) {
@@ -746,7 +811,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]);
@@ -822,18 +888,22 @@ void EEVEE_materials_cache_init(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);
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);
}
{
@@ -970,7 +1040,7 @@ static void material_opaque(
/* Fallback to default shader */
if (*shgrp == NULL) {
- *shgrp = EEVEE_default_shading_group_get(sldata, vedata, false, use_flat_nor, stl->effects->use_ssr);
+ *shgrp = EEVEE_default_shading_group_get(sldata, vedata, false, false, use_flat_nor, stl->effects->use_ssr);
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);
@@ -1035,7 +1105,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);
+ false, false, use_flat_nor, true, false);
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);
@@ -1082,6 +1152,179 @@ static void material_transparent(
}
}
+static void material_particle_hair(EEVEE_SceneLayerData *sldata, EEVEE_Data *vedata,
+ 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;
+ if ((ob->mode & OB_MODE_HAIR_EDIT) == 0) {
+ 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);
+
+ shgrp = DRW_shgroup_material_create(gpumat, psl->material_pass);
+ if (shgrp) {
+ add_standard_uniforms(shgrp, sldata, vedata, NULL, NULL, 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) {
+ shgrp = EEVEE_default_shading_group_get(sldata, vedata, true, false, false, stl->effects->use_ssr);
+ 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_SceneLayerData *sldata, EEVEE_Data *vedata, Object *ob, HairGroup *group)
+{
+ 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;
+ const HairEditSettings *tsettings = &scene->toolsettings->hair_edit;
+
+ float mat[4][4];
+ copy_m4_m4(mat, ob->obmat);
+
+ const DRWHairFiberTextureBuffer *fiber_buffer = NULL;
+ struct Gwn_Batch *hair_geom;
+ {
+ DerivedMesh *scalp = NULL;
+ if (ob->derivedFinal) {
+ scalp = ob->derivedFinal;
+ }
+ else {
+ EvaluationContext eval_ctx = {0};
+ DEG_evaluation_context_init(&eval_ctx, DAG_EVAL_VIEWPORT);
+ scalp = mesh_get_derived_final(&eval_ctx, scene, ob, CD_MASK_BAREMESH);
+ }
+ hair_geom = DRW_cache_hair_get_fibers(group, tsettings->hair_draw_subdiv, scalp, &fiber_buffer);
+ }
+
+ if (!group->draw_texture_cache) {
+ group->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 **)(&group->draw_texture_cache);
+
+ // TODO
+ Material *ma = NULL;/*give_current_material(ob, omat);*/
+ 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, true);
+
+ shgrp = DRW_shgroup_material_create(gpumat, psl->material_pass);
+ if (shgrp) {
+ add_standard_uniforms(shgrp, sldata, vedata, NULL, NULL, 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) {
+ shgrp = EEVEE_default_shading_group_get(sldata, vedata, true, true, false, stl->effects->use_ssr);
+ 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_SceneLayerData *sldata, Object *ob)
{
EEVEE_PassList *psl = ((EEVEE_Data *)vedata)->psl;
@@ -1211,77 +1454,15 @@ void EEVEE_materials_cache_populate(EEVEE_Data *vedata, EEVEE_SceneLayerData *sl
if (ob->type == OB_MESH) {
if (ob != draw_ctx->scene->obedit) {
- 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);
-
- shgrp = DRW_shgroup_material_create(gpumat, psl->material_pass);
- if (shgrp) {
- add_standard_uniforms(shgrp, sldata, vedata, NULL, NULL, 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) {
- shgrp = EEVEE_default_shading_group_get(sldata, vedata, true, false, stl->effects->use_ssr);
- 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(sldata, vedata, ob, psys, md);
+ }
+ else if (md->type == eModifierType_Hair) {
+ HairModifierData *hmd = (HairModifierData *)md;
+ for (HairGroup *group = hmd->hair->groups.first; group; group = group->next) {
+ material_hair(sldata, vedata, ob, group);
}
}
}
@@ -1306,6 +1487,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_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 1f1a4ef0fcf..4a798c7435c 100644
--- a/source/blender/draw/engines/eevee/eevee_private.h
+++ b/source/blender/draw/engines/eevee/eevee_private.h
@@ -60,21 +60,22 @@ 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_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_HAIR_FIBERS = (1 << 5),
/* Max number of variation */
/* IMPORTANT : Leave it last and set
* it's value accordingly. */
- VAR_MAT_MAX = (1 << 5),
+ VAR_MAT_MAX = (1 << 6),
/* These are options that are not counted in VAR_MAT_MAX
* because they are not cumulative with the others above. */
- VAR_MAT_CLIP = (1 << 8),
- VAR_MAT_HASH = (1 << 9),
- VAR_MAT_MULT = (1 << 10),
- VAR_MAT_SHADOW = (1 << 11),
+ VAR_MAT_CLIP = (1 << 9),
+ VAR_MAT_HASH = (1 << 10),
+ VAR_MAT_MULT = (1 << 11),
+ VAR_MAT_SHADOW = (1 << 12),
VAR_MAT_REFRACT = (1 << 12),
};
@@ -476,6 +477,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;
@@ -519,7 +524,7 @@ struct GPUMaterial *EEVEE_material_world_volume_get(
struct GPUMaterial *EEVEE_material_mesh_get(
struct Scene *scene, Material *ma, bool use_blend, bool use_multiply, bool use_refract);
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);
+struct GPUMaterial *EEVEE_material_hair_get(struct Scene *scene, Material *ma, bool use_fibers);
void EEVEE_materials_free(void);
void EEVEE_draw_default_passes(EEVEE_PassList *psl);
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 998ccfa453a..fd7f3870d27 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;
@@ -25,7 +30,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);
@@ -37,4 +52,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 6f26a815736..1b53e503003 100644
--- a/source/blender/draw/engines/eevee/shaders/prepass_vert.glsl
+++ b/source/blender/draw/engines/eevee/shaders/prepass_vert.glsl
@@ -1,15 +1,31 @@
uniform mat4 ModelViewProjectionMatrix;
uniform mat4 ModelMatrix;
+uniform mat4 ModelViewMatrix;
#ifdef CLIP_PLANES
uniform vec4 ClipPlanes[1];
#endif
+#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(worldPosition, ClipPlanes[0]);
diff --git a/source/blender/draw/intern/draw_cache.c b/source/blender/draw/intern/draw_cache.c
index bf65d8f879d..e94268e7b86 100644
--- a/source/blender/draw/intern/draw_cache.c
+++ b/source/blender/draw/intern/draw_cache.c
@@ -2543,3 +2543,38 @@ Gwn_Batch *DRW_cache_particles_get_prim(int type)
return NULL;
}
+
+/* -------------------------------------------------------------------- */
+
+/** \name Strands
+ * \{ */
+
+Gwn_Batch *DRW_cache_editstrands_get_tips(struct BMEditStrands *es)
+{
+ return DRW_editstrands_batch_cache_get_tips(es);
+}
+
+Gwn_Batch *DRW_cache_editstrands_get_roots(struct BMEditStrands *es)
+{
+ return DRW_editstrands_batch_cache_get_roots(es);
+}
+
+Gwn_Batch *DRW_cache_editstrands_get_points(struct BMEditStrands *es)
+{
+ return DRW_editstrands_batch_cache_get_points(es);
+}
+
+Gwn_Batch *DRW_cache_editstrands_get_wires(struct BMEditStrands *es)
+{
+ return DRW_editstrands_batch_cache_get_wires(es);
+}
+
+/* -------------------------------------------------------------------- */
+
+/** \name Hair */
+
+Gwn_Batch *DRW_cache_hair_get_fibers(struct HairGroup *group, int subdiv, struct DerivedMesh *scalp,
+ const struct DRWHairFiberTextureBuffer **r_buffer)
+{
+ return DRW_hair_batch_cache_get_fibers(group, subdiv, scalp, r_buffer);
+}
diff --git a/source/blender/draw/intern/draw_cache.h b/source/blender/draw/intern/draw_cache.h
index ac7062b3cc8..5e4dc14fc2e 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 BMEditStrands;
+struct HairGroup;
+struct DRWHairFiberTextureBuffer;
+struct DerivedMesh;
void DRW_shape_cache_free(void);
@@ -156,4 +160,14 @@ struct Gwn_Batch *DRW_cache_particles_get_hair(struct ParticleSystem *psys, stru
struct Gwn_Batch *DRW_cache_particles_get_dots(struct ParticleSystem *psys);
struct Gwn_Batch *DRW_cache_particles_get_prim(int type);
+/* Strands */
+struct Gwn_Batch *DRW_cache_editstrands_get_tips(struct BMEditStrands *es);
+struct Gwn_Batch *DRW_cache_editstrands_get_roots(struct BMEditStrands *es);
+struct Gwn_Batch *DRW_cache_editstrands_get_points(struct BMEditStrands *es);
+struct Gwn_Batch *DRW_cache_editstrands_get_wires(struct BMEditStrands *es);
+
+/* Hair */
+struct Gwn_Batch *DRW_cache_hair_get_fibers(struct HairGroup *group, int subdiv, struct DerivedMesh *scalp,
+ const struct DRWHairFiberTextureBuffer **r_buffer);
+
#endif /* __DRAW_CACHE_H__ */
diff --git a/source/blender/draw/intern/draw_cache_impl.h b/source/blender/draw/intern/draw_cache_impl.h
index 82972b9f75b..57ccdd15054 100644
--- a/source/blender/draw/intern/draw_cache_impl.h
+++ b/source/blender/draw/intern/draw_cache_impl.h
@@ -32,6 +32,10 @@ struct ListBase;
struct CurveCache;
struct ParticleSystem;
struct ModifierData;
+struct BMEditStrands;
+struct HairGroup;
+struct DRWHairFiberTextureBuffer;
+struct DerivedMesh;
struct Curve;
struct Lattice;
@@ -50,6 +54,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_hair_batch_cache_dirty(struct HairGroup *group, int mode);
+void DRW_hair_batch_cache_free(struct HairGroup *group);
+
+void DRW_editstrands_batch_cache_dirty(struct BMEditStrands *es, int mode);
+void DRW_editstrands_batch_cache_free(struct BMEditStrands *es);
+
/* 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(
@@ -107,4 +117,14 @@ 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 ParticleSystem *psys);
+/* Strands */
+struct Gwn_Batch *DRW_editstrands_batch_cache_get_wires(struct BMEditStrands *es);
+struct Gwn_Batch *DRW_editstrands_batch_cache_get_tips(struct BMEditStrands *es);
+struct Gwn_Batch *DRW_editstrands_batch_cache_get_roots(struct BMEditStrands *es);
+struct Gwn_Batch *DRW_editstrands_batch_cache_get_points(struct BMEditStrands *es);
+
+/* Hair */
+struct Gwn_Batch *DRW_hair_batch_cache_get_fibers(struct HairGroup *group, int subdiv, struct DerivedMesh *scalp,
+ const struct DRWHairFiberTextureBuffer **r_buffer);
+
#endif /* __DRAW_CACHE_IMPL_H__ */
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..fbcce57e3b8
--- /dev/null
+++ b/source/blender/draw/intern/draw_cache_impl_hair.c
@@ -0,0 +1,310 @@
+/*
+ * ***** 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 "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 *verts;
+ Gwn_IndexBuf *segments;
+
+ Gwn_Batch *fibers;
+
+ DRWHairFiberTextureBuffer texbuffer;
+
+ /* settings to determine if cache is invalid */
+ bool is_dirty;
+} HairBatchCache;
+
+/* Gwn_Batch cache management. */
+
+static void hair_batch_cache_clear(HairGroup *group);
+
+static bool hair_batch_cache_valid(HairGroup *group)
+{
+ HairBatchCache *cache = group->draw_batch_cache;
+
+ if (cache == NULL) {
+ return false;
+ }
+
+ if (cache->is_dirty) {
+ return false;
+ }
+
+ return true;
+}
+
+static void hair_batch_cache_init(HairGroup *group)
+{
+ HairBatchCache *cache = group->draw_batch_cache;
+
+ if (!cache) {
+ cache = group->draw_batch_cache = MEM_callocN(sizeof(*cache), __func__);
+ }
+ else {
+ memset(cache, 0, sizeof(*cache));
+ }
+
+ cache->is_dirty = false;
+}
+
+static HairBatchCache *hair_batch_cache_get(HairGroup *group)
+{
+ if (!hair_batch_cache_valid(group)) {
+ hair_batch_cache_clear(group);
+ hair_batch_cache_init(group);
+ }
+ return group->draw_batch_cache;
+}
+
+void DRW_hair_batch_cache_dirty(HairGroup *group, int mode)
+{
+ HairBatchCache *cache = group->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(HairGroup *group)
+{
+ HairBatchCache *cache = group->draw_batch_cache;
+
+ if (group->draw_texture_cache) {
+ GPU_texture_free(group->draw_texture_cache);
+ group->draw_texture_cache = NULL;
+ }
+
+ if (cache) {
+ GWN_BATCH_DISCARD_SAFE(cache->fibers);
+ GWN_VERTBUF_DISCARD_SAFE(cache->verts);
+ GWN_INDEXBUF_DISCARD_SAFE(cache->segments);
+
+ {
+ 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(HairGroup *group)
+{
+ hair_batch_cache_clear(group);
+ MEM_SAFE_FREE(group->draw_batch_cache);
+}
+
+static void hair_batch_cache_ensure_fibers(HairGroup *group, struct DerivedMesh *scalp, int subdiv, HairBatchCache *cache)
+{
+ TIMEIT_START(hair_batch_cache_ensure_fibers);
+
+ GWN_VERTBUF_DISCARD_SAFE(cache->verts);
+ GWN_INDEXBUF_DISCARD_SAFE(cache->segments);
+
+ const int totfibers = group->num_follicles;
+ int *fiber_lengths = BKE_hair_group_get_fiber_lengths(group, scalp, 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->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->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->verts, fiber_index_id, vi, &i);
+ GWN_vertbuf_attr_set(cache->verts, curve_param_id, vi, &a);
+ GWN_vertbuf_attr_set(cache->verts, fiber_index_id, vi+1, &i);
+ GWN_vertbuf_attr_set(cache->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);
+
+ MEM_freeN(fiber_lengths);
+
+ TIMEIT_BENCH(cache->segments = GWN_indexbuf_build(&elb), indexbuf_build);
+
+ TIMEIT_END(hair_batch_cache_ensure_fibers);
+}
+
+static void hair_batch_cache_ensure_texbuffer(HairGroup *group, 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_group_get_texture_buffer_size(group, scalp, 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_group_get_texture_buffer(group, 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(HairGroup *group, int subdiv, struct DerivedMesh *scalp,
+ const DRWHairFiberTextureBuffer **r_buffer)
+{
+ HairBatchCache *cache = hair_batch_cache_get(group);
+
+ TIMEIT_START(DRW_hair_batch_cache_get_fibers);
+
+ if (cache->fibers == NULL) {
+ TIMEIT_BENCH(hair_batch_cache_ensure_fibers(group, scalp, subdiv, cache),
+ hair_batch_cache_ensure_fibers);
+
+ TIMEIT_BENCH(cache->fibers = GWN_batch_create(GWN_PRIM_TRIS, cache->verts, cache->segments),
+ GWN_batch_create);
+
+ TIMEIT_BENCH(hair_batch_cache_ensure_texbuffer(group, scalp, subdiv, cache),
+ hair_batch_cache_ensure_texbuffer);
+ }
+
+ if (r_buffer) {
+ *r_buffer = &cache->texbuffer;
+ }
+
+ TIMEIT_END(DRW_hair_batch_cache_get_fibers);
+
+ return cache->fibers;
+}
diff --git a/source/blender/draw/intern/draw_cache_impl_strands.c b/source/blender/draw/intern/draw_cache_impl_strands.c
new file mode 100644
index 00000000000..94803a9512c
--- /dev/null
+++ b/source/blender/draw/intern/draw_cache_impl_strands.c
@@ -0,0 +1,341 @@
+/*
+ * ***** 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_editstrands.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
+
+/* ---------------------------------------------------------------------- */
+/* Strands Gwn_Batch Cache */
+
+typedef enum VertexDrawFlags
+{
+ STRANDS_VERTEX_SELECT = (1 << 0),
+} VertexDrawFlags;
+
+typedef struct StrandsBatchCache {
+ Gwn_VertBuf *pos;
+ Gwn_IndexBuf *segments;
+ Gwn_IndexBuf *tips_idx;
+ Gwn_IndexBuf *roots_idx;
+
+ Gwn_Batch *wires;
+ Gwn_Batch *tips;
+ Gwn_Batch *roots;
+ Gwn_Batch *points;
+
+ int segment_count;
+ int point_count;
+
+ /* settings to determine if cache is invalid */
+ bool is_dirty;
+} StrandsBatchCache;
+
+/* Gwn_Batch cache management. */
+
+static void editstrands_batch_cache_clear(BMEditStrands *es);
+
+static bool editstrands_batch_cache_valid(BMEditStrands *es)
+{
+ StrandsBatchCache *cache = es->batch_cache;
+
+ if (cache == NULL) {
+ return false;
+ }
+
+ if (cache->is_dirty) {
+ return false;
+ }
+
+ return true;
+}
+
+static void editstrands_batch_cache_init(BMEditStrands *es)
+{
+ StrandsBatchCache *cache = es->batch_cache;
+
+ if (!cache) {
+ cache = es->batch_cache = MEM_callocN(sizeof(*cache), __func__);
+ }
+ else {
+ memset(cache, 0, sizeof(*cache));
+ }
+
+ cache->is_dirty = false;
+}
+
+static StrandsBatchCache *editstrands_batch_cache_get(BMEditStrands *es)
+{
+ if (!editstrands_batch_cache_valid(es)) {
+ editstrands_batch_cache_clear(es);
+ editstrands_batch_cache_init(es);
+ }
+ return es->batch_cache;
+}
+
+void DRW_editstrands_batch_cache_dirty(BMEditStrands *es, int mode)
+{
+ StrandsBatchCache *cache = es->batch_cache;
+ if (cache == NULL) {
+ return;
+ }
+ switch (mode) {
+ case BKE_STRANDS_BATCH_DIRTY_ALL:
+ case BKE_STRANDS_BATCH_DIRTY_SELECT:
+ cache->is_dirty = true;
+ break;
+ default:
+ BLI_assert(0);
+ }
+}
+
+static void editstrands_batch_cache_clear(BMEditStrands *es)
+{
+ StrandsBatchCache *cache = es->batch_cache;
+
+ if (cache) {
+ GWN_BATCH_DISCARD_SAFE(cache->wires);
+ GWN_BATCH_DISCARD_SAFE(cache->points);
+ GWN_BATCH_DISCARD_SAFE(cache->tips);
+ GWN_BATCH_DISCARD_SAFE(cache->roots);
+ GWN_VERTBUF_DISCARD_SAFE(cache->pos);
+ GWN_INDEXBUF_DISCARD_SAFE(cache->segments);
+ }
+}
+
+void DRW_editstrands_batch_cache_free(BMEditStrands *es)
+{
+ editstrands_batch_cache_clear(es);
+ MEM_SAFE_FREE(es->batch_cache);
+}
+
+static void editstrands_batch_cache_ensure_pos(BMEditStrands *es, StrandsBatchCache *cache)
+{
+ if (cache->pos) {
+ return;
+ }
+
+ GWN_VERTBUF_DISCARD_SAFE(cache->pos);
+
+ static Gwn_VertFormat format = { 0 };
+ static unsigned pos_id, flag_id;
+
+ /* initialize vertex format */
+ if (format.attrib_ct == 0) {
+ pos_id = GWN_vertformat_attr_add(&format, "pos", GWN_COMP_F32, 3, GWN_FETCH_FLOAT);
+ flag_id = GWN_vertformat_attr_add(&format, "flag", GWN_COMP_U8, 1, GWN_FETCH_INT);
+ }
+
+ BMesh *bm = es->base.bm;
+ BMVert *vert;
+ BMIter iter;
+ int curr_point;
+
+ cache->pos = GWN_vertbuf_create_with_format(&format);
+ GWN_vertbuf_data_alloc(cache->pos, bm->totvert);
+
+ BM_ITER_MESH_INDEX(vert, &iter, bm, BM_VERTS_OF_MESH, curr_point) {
+ GWN_vertbuf_attr_set(cache->pos, pos_id, curr_point, vert->co);
+
+ uint8_t flag = 0;
+ if (BM_elem_flag_test(vert, BM_ELEM_SELECT))
+ flag |= STRANDS_VERTEX_SELECT;
+ GWN_vertbuf_attr_set(cache->pos, flag_id, curr_point, &flag);
+ }
+}
+
+static void editstrands_batch_cache_ensure_segments(BMEditStrands *es, StrandsBatchCache *cache)
+{
+ if (cache->segments) {
+ return;
+ }
+
+ GWN_INDEXBUF_DISCARD_SAFE(cache->segments);
+
+ BMesh *bm = es->base.bm;
+ BMEdge *edge;
+ BMIter iter;
+
+ Gwn_IndexBufBuilder elb;
+ GWN_indexbuf_init(&elb, GWN_PRIM_LINES, bm->totedge, bm->totvert);
+
+ BM_mesh_elem_index_ensure(bm, BM_VERT);
+
+ BM_ITER_MESH(edge, &iter, bm, BM_EDGES_OF_MESH) {
+ GWN_indexbuf_add_line_verts(&elb, BM_elem_index_get(edge->v1), BM_elem_index_get(edge->v2));
+ }
+
+ cache->segments = GWN_indexbuf_build(&elb);
+}
+
+static void editstrands_batch_cache_ensure_tips_idx(BMEditStrands *es, StrandsBatchCache *cache)
+{
+ if (cache->tips_idx) {
+ return;
+ }
+
+ GWN_INDEXBUF_DISCARD_SAFE(cache->tips_idx);
+
+ BMesh *bm = es->base.bm;
+ int totstrands = BM_strands_count(bm);
+ BMVert *root, *vert;
+ BMIter iter, iter_strand;
+
+ Gwn_IndexBufBuilder elb;
+ GWN_indexbuf_init(&elb, GWN_PRIM_POINTS, totstrands, bm->totvert);
+
+ BM_mesh_elem_index_ensure(bm, BM_VERT);
+
+ BM_ITER_STRANDS(root, &iter, bm, BM_STRANDS_OF_MESH) {
+ BM_ITER_STRANDS_ELEM(vert, &iter_strand, root, BM_VERTS_OF_STRAND)
+ {
+ if (BM_strands_vert_is_tip(vert)) {
+ GWN_indexbuf_add_point_vert(&elb, BM_elem_index_get(vert));
+ break;
+ }
+ }
+ }
+
+ cache->tips_idx = GWN_indexbuf_build(&elb);
+}
+
+static void editstrands_batch_cache_ensure_roots_idx(BMEditStrands *es, StrandsBatchCache *cache)
+{
+ if (cache->roots_idx) {
+ return;
+ }
+
+ GWN_INDEXBUF_DISCARD_SAFE(cache->roots_idx);
+
+ BMesh *bm = es->base.bm;
+ int totstrands = BM_strands_count(bm);
+ BMVert *root, *vert;
+ BMIter iter, iter_strand;
+
+ Gwn_IndexBufBuilder elb;
+ GWN_indexbuf_init(&elb, GWN_PRIM_POINTS, totstrands, bm->totvert);
+
+ BM_mesh_elem_index_ensure(bm, BM_VERT);
+
+ BM_ITER_STRANDS(root, &iter, bm, BM_STRANDS_OF_MESH) {
+ BM_ITER_STRANDS_ELEM(vert, &iter_strand, root, BM_VERTS_OF_STRAND)
+ {
+ if (BM_strands_vert_is_root(vert)) {
+ GWN_indexbuf_add_point_vert(&elb, BM_elem_index_get(vert));
+ break;
+ }
+ }
+ }
+
+ cache->roots_idx = GWN_indexbuf_build(&elb);
+}
+
+Gwn_Batch *DRW_editstrands_batch_cache_get_wires(BMEditStrands *es)
+{
+ StrandsBatchCache *cache = editstrands_batch_cache_get(es);
+
+ if (cache->wires == NULL) {
+ editstrands_batch_cache_ensure_pos(es, cache);
+ editstrands_batch_cache_ensure_segments(es, cache);
+ cache->wires = GWN_batch_create(GWN_PRIM_LINES, cache->pos, cache->segments);
+ }
+
+ return cache->wires;
+}
+
+Gwn_Batch *DRW_editstrands_batch_cache_get_tips(BMEditStrands *es)
+{
+ StrandsBatchCache *cache = editstrands_batch_cache_get(es);
+
+ if (cache->tips == NULL) {
+ editstrands_batch_cache_ensure_pos(es, cache);
+ editstrands_batch_cache_ensure_tips_idx(es, cache);
+ cache->tips = GWN_batch_create(GWN_PRIM_POINTS, cache->pos, cache->tips_idx);
+ }
+
+ return cache->tips;
+}
+
+Gwn_Batch *DRW_editstrands_batch_cache_get_roots(BMEditStrands *es)
+{
+ StrandsBatchCache *cache = editstrands_batch_cache_get(es);
+
+ if (cache->roots == NULL) {
+ editstrands_batch_cache_ensure_pos(es, cache);
+ editstrands_batch_cache_ensure_roots_idx(es, cache);
+ cache->roots = GWN_batch_create(GWN_PRIM_POINTS, cache->pos, cache->roots_idx);
+ }
+
+ return cache->roots;
+}
+
+Gwn_Batch *DRW_editstrands_batch_cache_get_points(BMEditStrands *es)
+{
+ StrandsBatchCache *cache = editstrands_batch_cache_get(es);
+
+ if (cache->points == NULL) {
+ editstrands_batch_cache_ensure_pos(es, cache);
+ cache->points = GWN_batch_create(GWN_PRIM_POINTS, cache->pos, NULL);
+ }
+
+ return cache->points;
+}
diff --git a/source/blender/draw/intern/draw_common.h b/source/blender/draw/intern/draw_common.h
index a6c9b24af7d..c9f3629dc18 100644
--- a/source/blender/draw/intern/draw_common.h
+++ b/source/blender/draw/intern/draw_common.h
@@ -29,7 +29,9 @@
struct DRWPass;
struct DRWShadingGroup;
struct Gwn_Batch;
+struct GPUTexture;
struct Object;
+struct Scene;
struct SceneLayer;
/* Used as ubo but colors can be directly referenced as well */
@@ -128,4 +130,19 @@ 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);
+
#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..dfa65f86644
--- /dev/null
+++ b/source/blender/draw/intern/draw_hair.c
@@ -0,0 +1,61 @@
+/*
+ * 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 "DNA_scene_types.h"
+
+#include "DRW_render.h"
+
+#include "BLI_utildefines.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 *scene,
+ GPUTexture **fibertex, const DRWHairFiberTextureBuffer *texbuffer)
+{
+ const HairEditSettings *tsettings = &scene->toolsettings->hair_edit;
+
+ 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_buffer(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);
+}
diff --git a/source/blender/draw/intern/draw_manager.c b/source/blender/draw/intern/draw_manager.c
index 889fb3ce810..02df1f55ae7 100644
--- a/source/blender/draw/intern/draw_manager.c
+++ b/source/blender/draw/intern/draw_manager.c
@@ -2917,6 +2917,9 @@ static void DRW_engines_enable_from_mode(int mode)
case CTX_MODE_PARTICLE:
use_drw_engine(&draw_engine_particle_type);
break;
+ case CTX_MODE_HAIR:
+ use_drw_engine(&draw_engine_edit_strands_type);
+ break;
case CTX_MODE_OBJECT:
break;
default:
@@ -3579,6 +3582,7 @@ void DRW_engines_register(void)
DRW_engine_register(&draw_engine_edit_lattice_type);
DRW_engine_register(&draw_engine_edit_mesh_type);
DRW_engine_register(&draw_engine_edit_metaball_type);
+ DRW_engine_register(&draw_engine_edit_strands_type);
DRW_engine_register(&draw_engine_edit_surface_type);
DRW_engine_register(&draw_engine_edit_text_type);
DRW_engine_register(&draw_engine_paint_texture_type);
@@ -3602,6 +3606,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: editstrands.c */
+ extern void *BKE_editstrands_batch_cache_dirty_cb;
+ extern void *BKE_editstrands_batch_cache_free_cb;
+ /* BKE: hair.c */
+ extern void *BKE_hair_batch_cache_dirty_cb;
+ extern void *BKE_hair_batch_cache_free_cb;
BKE_curve_batch_cache_dirty_cb = DRW_curve_batch_cache_dirty;
BKE_curve_batch_cache_free_cb = DRW_curve_batch_cache_free;
@@ -3614,6 +3624,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_editstrands_batch_cache_dirty_cb = DRW_editstrands_batch_cache_dirty;
+ BKE_editstrands_batch_cache_free_cb = DRW_editstrands_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..4a0ff1e6b15 100644
--- a/source/blender/draw/modes/draw_mode_engines.h
+++ b/source/blender/draw/modes/draw_mode_engines.h
@@ -32,6 +32,7 @@ extern DrawEngineType draw_engine_edit_curve_type;
extern DrawEngineType draw_engine_edit_lattice_type;
extern DrawEngineType draw_engine_edit_mesh_type;
extern DrawEngineType draw_engine_edit_metaball_type;
+extern DrawEngineType draw_engine_edit_strands_type;
extern DrawEngineType draw_engine_edit_surface_type;
extern DrawEngineType draw_engine_edit_text_type;
extern DrawEngineType draw_engine_paint_texture_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_strands_mode.c b/source/blender/draw/modes/edit_strands_mode.c
new file mode 100644
index 00000000000..38b601efc9b
--- /dev/null
+++ b/source/blender/draw/modes/edit_strands_mode.c
@@ -0,0 +1,308 @@
+/*
+ * 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 blender/draw/modes/particle_mode.c
+ * \ingroup draw
+ */
+
+#include "DRW_engine.h"
+#include "DRW_render.h"
+
+#include "BLI_ghash.h"
+
+#include "DNA_view3d_types.h"
+
+#include "BKE_editstrands.h"
+
+#include "GPU_shader.h"
+#include "GPU_texture.h"
+
+#include "draw_common.h"
+
+#include "draw_mode_engines.h"
+
+extern GlobalsUboStorage ts;
+
+extern char datatoc_edit_strands_vert_glsl[];
+extern char datatoc_gpu_shader_point_varying_color_frag_glsl[];
+extern char datatoc_gpu_shader_3D_smooth_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_STRANDS_engine_init() to
+ * initialize most of them and EDIT_STRANDS_cache_init()
+ * for EDIT_STRANDS_PassList */
+
+typedef struct EDIT_STRANDS_PassList {
+ /* Declare all passes here and init them in
+ * EDIT_STRANDS_cache_init().
+ * Only contains (DRWPass *) */
+ struct DRWPass *wires;
+ struct DRWPass *tips;
+ struct DRWPass *roots;
+ struct DRWPass *points;
+} EDIT_STRANDS_PassList;
+
+typedef struct EDIT_STRANDS_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_STRANDS_PrivateData *g_data;
+} EDIT_STRANDS_StorageList;
+
+typedef struct EDIT_STRANDS_Data {
+ /* Struct returned by DRW_viewport_engine_data_get.
+ * If you don't use one of these, just make it a (void *) */
+ // void *fbl;
+ void *engine_type; /* Required */
+ DRWViewportEmptyList *fbl;
+ DRWViewportEmptyList *txl;
+ EDIT_STRANDS_PassList *psl;
+ EDIT_STRANDS_StorageList *stl;
+} EDIT_STRANDS_Data;
+
+/* *********** STATIC *********** */
+
+static struct {
+ /* Custom shaders :
+ * Add sources to source/blender/draw/modes/shaders
+ * init in EDIT_STRANDS_engine_init();
+ * free in EDIT_STRANDS_engine_free(); */
+ struct GPUShader *edit_point_shader;
+ struct GPUShader *edit_wire_shader;
+} e_data = {NULL}; /* Engine data */
+
+typedef struct EDIT_STRANDS_PrivateData {
+ /* resulting curve as 'wire' for fast editmode drawing */
+ DRWShadingGroup *wires_shgrp;
+ DRWShadingGroup *tips_shgrp;
+ DRWShadingGroup *roots_shgrp;
+ DRWShadingGroup *points_shgrp;
+} EDIT_STRANDS_PrivateData; /* Transient data */
+
+/* *********** FUNCTIONS *********** */
+
+/* Init Textures, Framebuffers, Storage and Shaders.
+ * It is called for every frames.
+ * (Optional) */
+static void EDIT_STRANDS_engine_init(void *vedata)
+{
+ EDIT_STRANDS_StorageList *stl = ((EDIT_STRANDS_Data *)vedata)->stl;
+
+ UNUSED_VARS(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.edit_point_shader) {
+ e_data.edit_point_shader = DRW_shader_create(
+ datatoc_edit_strands_vert_glsl,
+ NULL,
+ datatoc_gpu_shader_point_varying_color_frag_glsl,
+ NULL);
+ }
+
+ if (!e_data.edit_wire_shader) {
+ e_data.edit_wire_shader = DRW_shader_create(
+ datatoc_edit_strands_vert_glsl,
+ NULL,
+ datatoc_gpu_shader_3D_smooth_color_frag_glsl,
+ NULL);
+ }
+}
+
+/* Cleanup when destroying the engine.
+ * This is not per viewport ! only when quitting blender.
+ * Mostly used for freeing shaders */
+static void EDIT_STRANDS_engine_free(void)
+{
+ DRW_SHADER_FREE_SAFE(e_data.edit_point_shader);
+ DRW_SHADER_FREE_SAFE(e_data.edit_wire_shader);
+}
+
+/* Here init all passes and shading groups
+ * Assume that all Passes are NULL */
+static void EDIT_STRANDS_cache_init(void *vedata)
+{
+ EDIT_STRANDS_PassList *psl = ((EDIT_STRANDS_Data *)vedata)->psl;
+ EDIT_STRANDS_StorageList *stl = ((EDIT_STRANDS_Data *)vedata)->stl;
+
+ if (!stl->g_data) {
+ /* Alloc transient pointers */
+ stl->g_data = MEM_mallocN(sizeof(*stl->g_data), __func__);
+ }
+
+ {
+ /* Strand wires */
+ DRWState state = DRW_STATE_WRITE_COLOR | DRW_STATE_WRITE_DEPTH | DRW_STATE_DEPTH_LESS | DRW_STATE_BLEND;
+ psl->wires = DRW_pass_create("Strand Wire Verts Pass", state);
+
+ stl->g_data->wires_shgrp = DRW_shgroup_create(e_data.edit_wire_shader, psl->wires);
+ DRW_shgroup_uniform_vec4(stl->g_data->wires_shgrp, "color", ts.colorWireEdit, 1);
+ DRW_shgroup_uniform_vec4(stl->g_data->wires_shgrp, "colorSelect", ts.colorEdgeSelect, 1);
+ }
+
+ {
+ /* Tip vertices */
+ DRWState state = DRW_STATE_WRITE_COLOR | DRW_STATE_DEPTH_LESS | DRW_STATE_BLEND;
+ psl->tips = DRW_pass_create("Strand Tip Verts Pass", state);
+
+ stl->g_data->tips_shgrp = DRW_shgroup_create(e_data.edit_point_shader, psl->tips);
+ DRW_shgroup_uniform_vec4(stl->g_data->tips_shgrp, "color", ts.colorVertex, 1);
+ DRW_shgroup_uniform_vec4(stl->g_data->tips_shgrp, "colorSelect", ts.colorVertexSelect, 1);
+ DRW_shgroup_uniform_float(stl->g_data->tips_shgrp, "sizeVertex", &ts.sizeVertex, 1);
+ }
+
+ {
+ /* Root vertices */
+ DRWState state = DRW_STATE_WRITE_COLOR | DRW_STATE_DEPTH_LESS | DRW_STATE_BLEND;
+ psl->roots = DRW_pass_create("Strand Root Verts Pass", state);
+
+ stl->g_data->roots_shgrp = DRW_shgroup_create(e_data.edit_point_shader, psl->roots);
+ DRW_shgroup_uniform_vec4(stl->g_data->roots_shgrp, "color", ts.colorVertex, 1);
+ DRW_shgroup_uniform_vec4(stl->g_data->roots_shgrp, "colorSelect", ts.colorVertexSelect, 1);
+ DRW_shgroup_uniform_float(stl->g_data->roots_shgrp, "sizeVertex", &ts.sizeVertex, 1);
+ }
+
+ {
+ /* Interior vertices */
+ DRWState state = DRW_STATE_WRITE_COLOR | DRW_STATE_DEPTH_LESS | DRW_STATE_BLEND;
+ psl->points = DRW_pass_create("Strand Interior Verts Pass", state);
+
+ stl->g_data->points_shgrp = DRW_shgroup_create(e_data.edit_point_shader, psl->points);
+ DRW_shgroup_uniform_vec4(stl->g_data->points_shgrp, "color", ts.colorVertex, 1);
+ DRW_shgroup_uniform_vec4(stl->g_data->points_shgrp, "colorSelect", ts.colorVertexSelect, 1);
+ DRW_shgroup_uniform_float(stl->g_data->points_shgrp, "sizeVertex", &ts.sizeVertex, 1);
+ }
+}
+
+static void edit_strands_add_ob_to_pass(
+ Scene *scene, Object *ob, BMEditStrands *edit,
+ DRWShadingGroup *tips_shgrp,
+ DRWShadingGroup *roots_shgrp,
+ DRWShadingGroup *points_shgrp,
+ DRWShadingGroup *wires_shgrp)
+{
+ HairEditSettings *tsettings = &scene->toolsettings->hair_edit;
+
+ {
+ struct Gwn_Batch *geom = DRW_cache_editstrands_get_wires(edit);
+ DRW_shgroup_call_add(wires_shgrp, geom, ob->obmat);
+ }
+
+ switch (tsettings->select_mode) {
+ case HAIR_SELECT_TIP: {
+ struct Gwn_Batch *geom = DRW_cache_editstrands_get_tips(edit);
+ DRW_shgroup_call_add(tips_shgrp, geom, ob->obmat);
+ break;
+ }
+
+ case HAIR_SELECT_STRAND: {
+#if 0
+ struct Gwn_Batch *geom = DRW_cache_editstrands_get_roots(edit);
+ DRW_shgroup_call_add(roots_shgrp, geom, ob->obmat);
+#else
+ UNUSED_VARS(roots_shgrp);
+#endif
+ break;
+ }
+
+ case HAIR_SELECT_VERTEX: {
+ struct Gwn_Batch *geom = DRW_cache_editstrands_get_points(edit);
+ DRW_shgroup_call_add(points_shgrp, geom, ob->obmat);
+ break;
+ }
+ }
+}
+
+/* Add geometry to shadingGroups. Execute for each objects */
+static void EDIT_STRANDS_cache_populate(void *vedata, Object *ob)
+{
+ EDIT_STRANDS_StorageList *stl = ((EDIT_STRANDS_Data *)vedata)->stl;
+
+ BMEditStrands *edit = BKE_editstrands_from_object(ob);
+ const DRWContextState *draw_ctx = DRW_context_state_get();
+ Scene *scene = draw_ctx->scene;
+
+ // Don't draw strands while editing the object itself
+ if (ob == scene->obedit)
+ return;
+
+ if (edit) {
+ edit_strands_add_ob_to_pass(scene, ob, edit,
+ stl->g_data->tips_shgrp, stl->g_data->roots_shgrp,
+ stl->g_data->points_shgrp, stl->g_data->wires_shgrp);
+ }
+}
+
+/* Optional: Post-cache_populate callback */
+static void EDIT_STRANDS_cache_finish(void *vedata)
+{
+ EDIT_STRANDS_PassList *psl = ((EDIT_STRANDS_Data *)vedata)->psl;
+ EDIT_STRANDS_StorageList *stl = ((EDIT_STRANDS_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_STRANDS_draw_scene(void *vedata)
+{
+ EDIT_STRANDS_PassList *psl = ((EDIT_STRANDS_Data *)vedata)->psl;
+
+ DRW_draw_pass(psl->wires);
+ DRW_draw_pass(psl->points);
+ DRW_draw_pass(psl->roots);
+ DRW_draw_pass(psl->tips);
+
+ /* If you changed framebuffer, double check you rebind
+ * the default one with its textures attached before finishing */
+}
+
+static const DrawEngineDataSize STRANDS_data_size = DRW_VIEWPORT_DATA_SIZE(EDIT_STRANDS_Data);
+
+DrawEngineType draw_engine_edit_strands_type = {
+ NULL, NULL,
+ N_("EditStrandsMode"),
+ &STRANDS_data_size,
+ EDIT_STRANDS_engine_init,
+ EDIT_STRANDS_engine_free,
+ &EDIT_STRANDS_cache_init,
+ &EDIT_STRANDS_cache_populate,
+ &EDIT_STRANDS_cache_finish,
+ NULL, /* draw_background but not needed by mode engines */
+ &EDIT_STRANDS_draw_scene
+};
diff --git a/source/blender/draw/modes/object_mode.c b/source/blender/draw/modes/object_mode.c
index 3f492b2f054..cb782bacde0 100644
--- a/source/blender/draw/modes/object_mode.c
+++ b/source/blender/draw/modes/object_mode.c
@@ -1674,6 +1674,8 @@ static void OBJECT_cache_populate(void *vedata, Object *ob)
SceneLayer *sl = draw_ctx->scene_layer;
View3D *v3d = draw_ctx->v3d;
int theme_id = TH_UNDEFINED;
+ const bool is_edited = (ob == scene->obedit) ||
+ ((ob == draw_ctx->obact) && (ob->mode & OB_MODE_ALL_BRUSH));
//CollectionEngineSettings *ces_mode_ob = BKE_layer_collection_engine_evaluated_get(ob, COLLECTION_MODE_OBJECT, "");
@@ -1681,8 +1683,7 @@ static void OBJECT_cache_populate(void *vedata, Object *ob)
bool do_outlines = ((ob->base_flag & BASE_SELECTED) != 0);
if (do_outlines) {
- Object *obedit = scene->obedit;
- if (ob != obedit && !((ob == draw_ctx->obact) && (ob->mode & OB_MODE_ALL_PAINT))) {
+ if (!is_edited) {
struct Gwn_Batch *geom = DRW_cache_object_surface_get(ob);
if (geom) {
theme_id = DRW_object_wire_theme_get(ob, sl, NULL);
@@ -1699,8 +1700,7 @@ static void OBJECT_cache_populate(void *vedata, Object *ob)
{
Mesh *me = ob->data;
if (me->totpoly == 0) {
- Object *obedit = scene->obedit;
- if (ob != obedit) {
+ if (!is_edited) {
struct Gwn_Batch *geom = DRW_cache_mesh_edges_get(ob);
if (geom) {
if (theme_id == TH_UNDEFINED) {
@@ -1720,8 +1720,7 @@ static void OBJECT_cache_populate(void *vedata, Object *ob)
break;
case OB_LATTICE:
{
- Object *obedit = scene->obedit;
- if (ob != obedit) {
+ if (!is_edited) {
struct Gwn_Batch *geom = DRW_cache_lattice_wire_get(ob, false);
if (theme_id == TH_UNDEFINED) {
theme_id = DRW_object_wire_theme_get(ob, sl, NULL);
@@ -1735,8 +1734,7 @@ static void OBJECT_cache_populate(void *vedata, Object *ob)
case OB_CURVE:
{
- Object *obedit = scene->obedit;
- if (ob != obedit) {
+ if (!is_edited) {
struct Gwn_Batch *geom = DRW_cache_curve_edge_wire_get(ob);
if (theme_id == TH_UNDEFINED) {
theme_id = DRW_object_wire_theme_get(ob, sl, NULL);
diff --git a/source/blender/draw/modes/shaders/edit_strands_vert.glsl b/source/blender/draw/modes/shaders/edit_strands_vert.glsl
new file mode 100644
index 00000000000..5e54bb5112a
--- /dev/null
+++ b/source/blender/draw/modes/shaders/edit_strands_vert.glsl
@@ -0,0 +1,28 @@
+
+/* Draw Curve Vertices */
+
+uniform mat4 ModelViewProjectionMatrix;
+uniform vec2 viewportSize;
+uniform vec4 color;
+uniform vec4 colorSelect;
+uniform float sizeVertex;
+
+in vec3 pos;
+in int flag;
+
+out vec4 finalColor;
+
+#define VERTEX_SELECTED (1 << 0)
+
+void main()
+{
+ gl_Position = ModelViewProjectionMatrix * vec4(pos, 1.0);
+ gl_PointSize = sizeVertex;
+
+ if ((flag & VERTEX_SELECTED) != 0) {
+ finalColor = colorSelect;
+ }
+ else {
+ finalColor = color;
+ }
+}
diff --git a/source/blender/editors/CMakeLists.txt b/source/blender/editors/CMakeLists.txt
index 757fca0a1b2..b419ca7eede 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(hair)
add_subdirectory(interface)
add_subdirectory(io)
add_subdirectory(manipulator_library)
diff --git a/source/blender/editors/armature/armature_utils.c b/source/blender/editors/armature/armature_utils.c
index a2663cdd935..d94c580dcfb 100644
--- a/source/blender/editors/armature/armature_utils.c
+++ b/source/blender/editors/armature/armature_utils.c
@@ -833,7 +833,7 @@ static void *get_armature_edit(bContext *C)
void undo_push_armature(bContext *C, const char *name)
{
// XXX solve getdata()
- undo_editmode_push(C, name, get_armature_edit, free_undoBones, undoBones_to_editBones, editBones_to_undoBones, NULL);
+ undo_editmode_push(C, name, CTX_data_edit_object, get_armature_edit, free_undoBones, undoBones_to_editBones, editBones_to_undoBones, NULL);
}
/* *************************************************************** */
diff --git a/source/blender/editors/curve/editcurve.c b/source/blender/editors/curve/editcurve.c
index 6327dbb8fae..5064205fe41 100644
--- a/source/blender/editors/curve/editcurve.c
+++ b/source/blender/editors/curve/editcurve.c
@@ -6227,7 +6227,7 @@ static void *get_data(bContext *C)
/* and this is all the undo system needs to know */
void undo_push_curve(bContext *C, const char *name)
{
- undo_editmode_push(C, name, get_data, free_undoCurve, undoCurve_to_editCurve, editCurve_to_undoCurve, NULL);
+ undo_editmode_push(C, name, CTX_data_edit_object, get_data, free_undoCurve, undoCurve_to_editCurve, editCurve_to_undoCurve, NULL);
}
void ED_curve_beztcpy(EditNurb *editnurb, BezTriple *dst, BezTriple *src, int count)
diff --git a/source/blender/editors/curve/editfont_undo.c b/source/blender/editors/curve/editfont_undo.c
index a61f863b61e..75f3df73118 100644
--- a/source/blender/editors/curve/editfont_undo.c
+++ b/source/blender/editors/curve/editfont_undo.c
@@ -307,5 +307,5 @@ static void *get_undoFont(bContext *C)
/* and this is all the undo system needs to know */
void undo_push_font(bContext *C, const char *name)
{
- undo_editmode_push(C, name, get_undoFont, free_undoFont, undoFont_to_editFont, editFont_to_undoFont, NULL);
+ undo_editmode_push(C, name, CTX_data_edit_object, get_undoFont, free_undoFont, undoFont_to_editFont, editFont_to_undoFont, NULL);
}
diff --git a/source/blender/editors/datafiles/CMakeLists.txt b/source/blender/editors/datafiles/CMakeLists.txt
index 2a84ca7f297..2ac800ee6e0 100644
--- a/source/blender/editors/datafiles/CMakeLists.txt
+++ b/source/blender/editors/datafiles/CMakeLists.txt
@@ -78,6 +78,13 @@ if(WITH_BLENDER)
data_to_c_simple(../../../../release/datafiles/brushicons/fill.png SRC)
data_to_c_simple(../../../../release/datafiles/brushicons/flatten.png SRC)
data_to_c_simple(../../../../release/datafiles/brushicons/grab.png SRC)
+ data_to_c_simple(../../../../release/datafiles/brushicons/hairadd.png SRC)
+ data_to_c_simple(../../../../release/datafiles/brushicons/haircomb.png SRC)
+ data_to_c_simple(../../../../release/datafiles/brushicons/haircut.png SRC)
+ data_to_c_simple(../../../../release/datafiles/brushicons/hairlength.png SRC)
+ data_to_c_simple(../../../../release/datafiles/brushicons/hairpuff.png SRC)
+ data_to_c_simple(../../../../release/datafiles/brushicons/hairsmooth.png SRC)
+ data_to_c_simple(../../../../release/datafiles/brushicons/hairweight.png SRC)
data_to_c_simple(../../../../release/datafiles/brushicons/inflate.png SRC)
data_to_c_simple(../../../../release/datafiles/brushicons/layer.png SRC)
data_to_c_simple(../../../../release/datafiles/brushicons/lighten.png SRC)
diff --git a/source/blender/editors/hair/CMakeLists.txt b/source/blender/editors/hair/CMakeLists.txt
new file mode 100644
index 00000000000..97c18009560
--- /dev/null
+++ b/source/blender/editors/hair/CMakeLists.txt
@@ -0,0 +1,57 @@
+# ***** 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): Jacques Beaurain.
+#
+# ***** END GPL LICENSE BLOCK *****
+
+set(INC
+ ../include
+ ../sculpt_paint
+ ../../blenfont
+ ../../blenkernel
+ ../../blenlib
+ ../../bmesh
+ ../../depsgraph
+ ../../gpu
+ ../../makesdna
+ ../../makesrna
+ ../../windowmanager
+ ../../../../intern/guardedalloc
+ ../../../../intern/glew-mx
+)
+
+set(INC_SYS
+ ${GLEW_INCLUDE_PATH}
+)
+
+set(SRC
+ hair_cursor.c
+ hair_edit.c
+ hair_mirror.c
+ hair_object_mesh.c
+ hair_object_particles.c
+ hair_ops.c
+ hair_select.c
+ hair_stroke.c
+ hair_undo.c
+
+ hair_intern.h
+)
+
+add_definitions(${GL_DEFINITIONS})
+
+blender_add_lib(bf_editor_hair "${SRC}" "${INC}" "${INC_SYS}")
diff --git a/source/blender/editors/hair/SConscript b/source/blender/editors/hair/SConscript
new file mode 100644
index 00000000000..2e715a05e7c
--- /dev/null
+++ b/source/blender/editors/hair/SConscript
@@ -0,0 +1,54 @@
+#!/usr/bin/env python
+#
+# ***** 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) 2006, Blender Foundation
+# All rights reserved.
+#
+# The Original Code is: all of this file.
+#
+# Contributor(s): Nathan Letwory.
+#
+# ***** END GPL LICENSE BLOCK *****
+
+Import ('env')
+
+sources = env.Glob('*.c')
+
+incs = [
+ '#/intern/guardedalloc',
+ env['BF_GLEW_INC'],
+ '#/intern/glew-mx',
+ '../include',
+ '../sculpt_paint',
+ '../../blenfont',
+ '../../blenkernel',
+ '../../blenlib',
+ '../../bmesh',
+ '../../gpu',
+ '../../makesdna',
+ '../../makesrna',
+ '../../windowmanager',
+ ]
+incs = ' '.join(incs)
+
+defs = ['BF_GL_DEFINITIONS']
+
+if env['OURPLATFORM'] in ('win32-vc', 'win32-mingw', 'linuxcross', 'win64-vc', 'win64-mingw'):
+ incs += ' ' + env['BF_PTHREADS_INC']
+
+env.BlenderLib ( 'bf_editors_hair', sources, Split(incs), defs, libtype=['core'], priority=[45] )
diff --git a/source/blender/editors/hair/hair_cursor.c b/source/blender/editors/hair/hair_cursor.c
new file mode 100644
index 00000000000..06c39727670
--- /dev/null
+++ b/source/blender/editors/hair/hair_cursor.c
@@ -0,0 +1,100 @@
+/*
+ * ***** 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/hair/hair_cursor.c
+ * \ingroup edhair
+ */
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_utildefines.h"
+#include "BLI_math.h"
+
+#include "DNA_brush_types.h"
+#include "DNA_scene_types.h"
+#include "DNA_screen_types.h"
+#include "DNA_view3d_types.h"
+
+#include "BKE_brush.h"
+#include "BKE_context.h"
+
+#include "GPU_immediate.h"
+#include "GPU_immediate_util.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "BIF_gl.h"
+#include "BIF_glutil.h"
+
+#include "ED_view3d.h"
+
+#include "hair_intern.h"
+
+static void hair_draw_cursor(bContext *C, int x, int y, void *UNUSED(customdata))
+{
+ Scene *scene = CTX_data_scene(C);
+ UnifiedPaintSettings *ups = &scene->toolsettings->unified_paint_settings;
+ HairEditSettings *settings = &scene->toolsettings->hair_edit;
+ Brush *brush = settings->brush;
+ if (!brush)
+ return;
+
+ float final_radius = BKE_brush_size_get(scene, brush);
+ float col[4];
+
+ /* set various defaults */
+ copy_v3_v3(col, brush->add_col);
+ col[3] = 0.5f;
+
+ Gwn_VertFormat *format = immVertexFormat();
+ uint pos = GWN_vertformat_attr_add(format, "pos", GWN_COMP_F32, 2, GWN_FETCH_FLOAT);
+
+ immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR);
+
+ immUniformColor4fv(col);
+
+ /* draw an inner brush */
+ if (ups->stroke_active && BKE_brush_use_size_pressure(scene, brush)) {
+ /* inner at full alpha */
+ imm_draw_circle_wire(pos, x, y, final_radius * ups->size_pressure_value, 40);
+ /* outer at half alpha */
+ immUniformColor3fvAlpha(col, col[3]*0.5f);
+ }
+ imm_draw_circle_wire(pos, x, y, final_radius, 40);
+
+ immUnbindProgram();
+}
+
+void hair_edit_cursor_start(bContext *C, int (*poll)(bContext *C))
+{
+ Scene *scene = CTX_data_scene(C);
+ HairEditSettings *settings = &scene->toolsettings->hair_edit;
+
+ if (!settings->paint_cursor)
+ settings->paint_cursor = WM_paint_cursor_activate(CTX_wm_manager(C), poll, hair_draw_cursor, NULL);
+}
diff --git a/source/blender/editors/hair/hair_edit.c b/source/blender/editors/hair/hair_edit.c
new file mode 100644
index 00000000000..ccf66e34f90
--- /dev/null
+++ b/source/blender/editors/hair/hair_edit.c
@@ -0,0 +1,465 @@
+/*
+ * ***** 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/hair/hair_edit.c
+ * \ingroup edhair
+ */
+
+#include <stdlib.h>
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_utildefines.h"
+#include "BLI_math.h"
+
+#include "DNA_brush_types.h"
+#include "DNA_mesh_types.h"
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+#include "DNA_screen_types.h"
+#include "DNA_view3d_types.h"
+
+#include "BKE_brush.h"
+#include "BKE_cdderivedmesh.h"
+#include "BKE_context.h"
+#include "BKE_DerivedMesh.h"
+#include "BKE_editstrands.h"
+#include "BKE_paint.h"
+
+#include "DEG_depsgraph.h"
+
+#include "bmesh.h"
+
+#include "RNA_access.h"
+#include "RNA_define.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "ED_object.h"
+#include "ED_view3d.h"
+
+#include "hair_intern.h"
+#include "paint_intern.h"
+
+#define USE_PARTICLES 1
+
+int hair_edit_poll(bContext *C)
+{
+ Object *obact;
+
+ obact = CTX_data_active_object(C);
+ if ((obact && obact->mode & OB_MODE_HAIR_EDIT) && CTX_wm_region_view3d(C)) {
+ return true;
+ }
+
+ return false;
+}
+
+bool hair_use_mirror_x(Object *ob)
+{
+ if (ob->type == OB_MESH)
+ return ((Mesh *)ob->data)->editflag & ME_EDIT_MIRROR_X;
+ else
+ return false;
+}
+
+bool hair_use_mirror_topology(Object *ob)
+{
+ if (ob->type == OB_MESH)
+ return ((Mesh *)ob->data)->editflag & ME_EDIT_MIRROR_TOPO;
+ else
+ return false;
+}
+
+
+/* ==== BMesh utilities ==== */
+
+void hair_bm_min_max(BMEditStrands *edit, float min[3], float max[3])
+{
+ BMesh *bm = edit->base.bm;
+ BMVert *v;
+ BMIter iter;
+
+ if (bm->totvert > 0) {
+ INIT_MINMAX(min, max);
+
+ BM_ITER_MESH(v, &iter, bm, BM_VERTS_OF_MESH) {
+ minmax_v3v3_v3(min, max, v->co);
+ }
+ }
+ else {
+ zero_v3(min);
+ zero_v3(max);
+ }
+}
+
+
+/* ==== edit mode toggle ==== */
+
+int hair_edit_toggle_poll(bContext *C)
+{
+ Object *ob = CTX_data_active_object(C);
+
+ if (ob == NULL)
+ return false;
+ if (!ob->data || ((ID *)ob->data)->lib)
+ return false;
+ if (CTX_data_edit_object(C))
+ return false;
+
+#if USE_PARTICLES
+ return ED_hair_object_has_hair_particle_data(ob);
+#else
+ return ob->type == OB_MESH;
+#endif
+}
+
+static void toggle_hair_cursor(bContext *C, bool enable)
+{
+ wmWindowManager *wm = CTX_wm_manager(C);
+ Scene *scene = CTX_data_scene(C);
+ HairEditSettings *settings = &scene->toolsettings->hair_edit;
+
+ if (enable) {
+ hair_edit_cursor_start(C, hair_edit_toggle_poll);
+ }
+ else {
+ if (settings->paint_cursor) {
+ WM_paint_cursor_end(wm, settings->paint_cursor);
+ settings->paint_cursor = NULL;
+ }
+ }
+}
+
+static int hair_edit_toggle_exec(bContext *C, wmOperator *op)
+{
+ Scene *scene = CTX_data_scene(C);
+ Object *ob = CTX_data_active_object(C);
+ const int mode_flag = OB_MODE_HAIR_EDIT;
+ const bool is_mode_set = (ob->mode & mode_flag) != 0;
+ EvaluationContext eval_ctx;
+ CTX_data_eval_ctx(C, &eval_ctx);
+
+ if (!is_mode_set) {
+ if (!ED_object_mode_compat_set(C, ob, mode_flag, op->reports)) {
+ return OPERATOR_CANCELLED;
+ }
+ }
+
+ if (!is_mode_set) {
+#if USE_PARTICLES
+ ED_hair_object_init_particle_edit(&eval_ctx, scene, ob);
+#else
+ ED_hair_object_init_mesh_edit(&eval_ctx, scene, ob);
+#endif
+ ob->mode |= mode_flag;
+
+ toggle_hair_cursor(C, true);
+ WM_event_add_notifier(C, NC_SCENE|ND_MODE|NS_MODE_HAIR, NULL);
+ }
+ else {
+#if USE_PARTICLES
+ ED_hair_object_apply_particle_edit(ob);
+#else
+ ED_hair_object_apply_mesh_edit(ob);
+#endif
+ ob->mode &= ~mode_flag;
+
+ toggle_hair_cursor(C, false);
+ WM_event_add_notifier(C, NC_SCENE|ND_MODE|NS_MODE_HAIR, NULL);
+ }
+
+ DEG_id_tag_update(&ob->id, OB_RECALC_DATA);
+
+ return OPERATOR_FINISHED;
+}
+
+void HAIR_OT_hair_edit_toggle(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Hair Edit Toggle";
+ ot->idname = "HAIR_OT_hair_edit_toggle";
+ ot->description = "Toggle hair edit mode";
+
+ /* api callbacks */
+ ot->exec = hair_edit_toggle_exec;
+ ot->poll = hair_edit_toggle_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+}
+
+
+/* ==== brush stroke ==== */
+
+void hair_init_viewcontext(bContext *C, ViewContext *vc)
+{
+ View3D *v3d;
+ bool has_zbuf;
+
+ view3d_set_viewcontext(C, vc);
+
+ v3d = vc->v3d;
+ has_zbuf = (v3d->drawtype > OB_WIRE) && (v3d->flag & V3D_ZBUF_SELECT);
+
+ if (has_zbuf) {
+ if (v3d->flag & V3D_INVALID_BACKBUF) {
+ /* needed or else the draw matrix can be incorrect */
+ view3d_operator_needs_opengl(C);
+
+ ED_view3d_backbuf_validate(C, vc);
+ /* we may need to force an update here by setting the rv3d as dirty
+ * for now it seems ok, but take care!:
+ * rv3d->depths->dirty = 1; */
+ ED_view3d_depth_update(vc->ar);
+ }
+ }
+}
+
+typedef struct HairStroke {
+ Scene *scene;
+ Object *ob;
+ BMEditStrands *edit;
+
+ bool first;
+ float lastmouse[2];
+ float zfac;
+
+ float smoothdir[2];
+} HairStroke;
+
+static int hair_stroke_init(bContext *C, wmOperator *op)
+{
+ Scene *scene = CTX_data_scene(C);
+ Object *ob = CTX_data_active_object(C);
+ BMEditStrands *edit = BKE_editstrands_from_object(ob);
+ ARegion *ar = CTX_wm_region(C);
+
+ HairStroke *stroke;
+ float min[3], max[3], center[3];
+
+ /* set the 'distance factor' for grabbing (used in comb etc) */
+ hair_bm_min_max(edit, min, max);
+ mid_v3_v3v3(center, min, max);
+
+ stroke = MEM_callocN(sizeof(HairStroke), "HairStroke");
+ stroke->first = true;
+ op->customdata = stroke;
+
+ stroke->scene = scene;
+ stroke->ob = ob;
+ stroke->edit = edit;
+
+ stroke->zfac = ED_view3d_calc_zfac(ar->regiondata, center, NULL);
+
+ return 1;
+}
+
+static bool hair_stroke_apply(bContext *C, wmOperator *op, PointerRNA *itemptr)
+{
+ HairStroke *stroke = op->customdata;
+ Scene *scene = stroke->scene;
+ Object *ob = stroke->ob;
+ BMEditStrands *edit = stroke->edit;
+ HairEditSettings *settings = &scene->toolsettings->hair_edit;
+ ARegion *ar = CTX_wm_region(C);
+ const float smoothfac = 0.9f; /* XXX should this be configurable? */
+
+ float mouse[2], mdelta[2], zvec[3], delta_max;
+ int totsteps, step;
+ HairToolData tool_data;
+ bool updated = false;
+
+ RNA_float_get_array(itemptr, "mouse", mouse);
+
+ if (stroke->first) {
+ copy_v2_v2(stroke->lastmouse, mouse);
+ zero_v2(stroke->smoothdir);
+ stroke->first = false;
+ }
+
+ if (!settings->brush)
+ return false;
+
+ sub_v2_v2v2(mdelta, mouse, stroke->lastmouse);
+ delta_max = max_ff(fabsf(mdelta[0]), fabsf(mdelta[1]));
+
+ totsteps = delta_max / (0.2f * BKE_brush_size_get(scene, settings->brush)) + 1;
+ mul_v2_fl(mdelta, 1.0f / (float)totsteps);
+
+ /* low-pass filter to smooth out jittery pixel increments in the direction */
+ interp_v2_v2v2(stroke->smoothdir, mdelta, stroke->smoothdir, smoothfac);
+
+ hair_init_viewcontext(C, &tool_data.vc);
+ tool_data.scene = scene;
+ tool_data.ob = ob;
+ tool_data.edit = edit;
+ tool_data.settings = settings;
+
+ invert_m4_m4(tool_data.imat, ob->obmat);
+ copy_v2_v2(tool_data.mval, mouse);
+ tool_data.mdepth = stroke->zfac;
+
+ zvec[0] = 0.0f; zvec[1] = 0.0f; zvec[2] = stroke->zfac;
+ ED_view3d_win_to_3d(tool_data.vc.v3d, ar, zvec, mouse, tool_data.loc);
+ ED_view3d_win_to_delta(ar, stroke->smoothdir, tool_data.delta, stroke->zfac);
+ /* tools work in object space */
+ mul_m4_v3(tool_data.imat, tool_data.loc);
+ mul_mat3_m4_v3(tool_data.imat, tool_data.delta);
+
+ for (step = 0; step < totsteps; ++step) {
+ bool step_updated = hair_brush_step(&tool_data);
+
+ if (step_updated)
+ BKE_editstrands_solve_constraints(ob, edit, NULL);
+
+ updated |= step_updated;
+ }
+
+ copy_v2_v2(stroke->lastmouse, mouse);
+
+ return updated;
+}
+
+static void hair_stroke_exit(wmOperator *op)
+{
+ HairStroke *stroke = op->customdata;
+ MEM_freeN(stroke);
+}
+
+static int hair_stroke_exec(bContext *C, wmOperator *op)
+{
+ HairStroke *stroke = op->customdata;
+ Object *ob = stroke->ob;
+
+ bool updated = false;
+
+ if (!hair_stroke_init(C, op))
+ return OPERATOR_CANCELLED;
+
+ RNA_BEGIN (op->ptr, itemptr, "stroke")
+ {
+ updated |= hair_stroke_apply(C, op, &itemptr);
+ }
+ RNA_END;
+
+ if (updated) {
+ DEG_id_tag_update(&ob->id, OB_RECALC_DATA);
+ WM_event_add_notifier(C, NC_OBJECT|ND_MODIFIER, ob);
+ }
+
+ hair_stroke_exit(op);
+
+ return OPERATOR_FINISHED;
+}
+
+static void hair_stroke_apply_event(bContext *C, wmOperator *op, const wmEvent *event)
+{
+ HairStroke *stroke = op->customdata;
+ Object *ob = stroke->ob;
+
+ PointerRNA itemptr;
+ float mouse[2];
+ bool updated = false;
+
+ mouse[0] = event->mval[0];
+ mouse[1] = event->mval[1];
+
+ /* fill in stroke */
+ RNA_collection_add(op->ptr, "stroke", &itemptr);
+
+ RNA_float_set_array(&itemptr, "mouse", mouse);
+
+ /* apply */
+ updated |= hair_stroke_apply(C, op, &itemptr);
+
+ if (updated) {
+ DEG_id_tag_update(&ob->id, OB_RECALC_DATA);
+ WM_event_add_notifier(C, NC_OBJECT|ND_MODIFIER, ob);
+ }
+ else {
+ /* even if nothing was changed, still trigger redraw
+ * for brush drawing during the modal operator
+ */
+ WM_event_add_notifier(C, NC_OBJECT|ND_DRAW, ob);
+ }
+}
+
+static int hair_stroke_invoke(bContext *C, wmOperator *op, const wmEvent *event)
+{
+ if (!hair_stroke_init(C, op))
+ return OPERATOR_CANCELLED;
+
+ hair_stroke_apply_event(C, op, event);
+
+ WM_event_add_modal_handler(C, op);
+
+ return OPERATOR_RUNNING_MODAL;
+}
+
+static int hair_stroke_modal(bContext *C, wmOperator *op, const wmEvent *event)
+{
+ switch (event->type) {
+ case LEFTMOUSE:
+ case MIDDLEMOUSE:
+ case RIGHTMOUSE: // XXX hardcoded
+ hair_stroke_exit(op);
+ return OPERATOR_FINISHED;
+ case MOUSEMOVE:
+ hair_stroke_apply_event(C, op, event);
+ break;
+ }
+
+ return OPERATOR_RUNNING_MODAL;
+}
+
+static void hair_stroke_cancel(bContext *UNUSED(C), wmOperator *op)
+{
+ hair_stroke_exit(op);
+}
+
+void HAIR_OT_stroke(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Hair Stroke";
+ ot->idname = "HAIR_OT_stroke";
+ ot->description = "Use a stroke tool on hair strands";
+
+ /* api callbacks */
+ ot->exec = hair_stroke_exec;
+ ot->invoke = hair_stroke_invoke;
+ ot->modal = hair_stroke_modal;
+ ot->cancel = hair_stroke_cancel;
+ ot->poll = hair_edit_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER|OPTYPE_UNDO|OPTYPE_BLOCKING;
+
+ /* properties */
+ RNA_def_collection_runtime(ot->srna, "stroke", &RNA_OperatorStrokeElement, "Stroke", "");
+}
diff --git a/source/blender/editors/hair/hair_intern.h b/source/blender/editors/hair/hair_intern.h
new file mode 100644
index 00000000000..2a8590d5679
--- /dev/null
+++ b/source/blender/editors/hair/hair_intern.h
@@ -0,0 +1,115 @@
+/*
+ * ***** 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/hair/hair_intern.h
+ * \ingroup edhair
+ */
+
+#ifndef __HAIR_INTERN_H__
+#define __HAIR_INTERN_H__
+
+#include "BIF_glutil.h"
+
+#include "ED_view3d.h"
+
+struct ARegion;
+struct bContext;
+struct wmOperatorType;
+struct rcti;
+struct EvaluationContext;
+
+struct Object;
+
+/* hair_edit.c */
+bool hair_use_mirror_x(struct Object *ob);
+bool hair_use_mirror_topology(struct Object *ob);
+
+int hair_edit_toggle_poll(struct bContext *C);
+int hair_edit_poll(struct bContext *C);
+
+void HAIR_OT_hair_edit_toggle(struct wmOperatorType *ot);
+
+/* hair_select.c */
+void HAIR_OT_select_all(struct wmOperatorType *ot);
+void HAIR_OT_select_linked(struct wmOperatorType *ot);
+
+/* hair_stroke.c */
+void HAIR_OT_stroke(struct wmOperatorType *ot);
+
+/* hair_object_mesh.c */
+bool ED_hair_object_init_mesh_edit(struct EvaluationContext *eval_ctx, struct Scene *scene, struct Object *ob);
+bool ED_hair_object_apply_mesh_edit(struct Object *ob);
+
+/* hair_object_particles.c */
+bool ED_hair_object_has_hair_particle_data(struct Object *ob);
+bool ED_hair_object_init_particle_edit(struct EvaluationContext *eval_ctx, struct Scene *scene, struct Object *ob);
+bool ED_hair_object_apply_particle_edit(struct Object *ob);
+
+
+/* ==== Hair Brush ==== */
+
+void hair_init_viewcontext(struct bContext *C, struct ViewContext *vc);
+
+bool hair_test_depth(struct ViewContext *vc, const float co[3], const int screen_co[2]);
+bool hair_test_vertex_inside_circle(struct ViewContext *vc, const float mval[2], float radsq,
+ struct BMVert *v, float *r_dist);
+bool hair_test_edge_inside_circle(struct ViewContext *vc, const float mval[2], float radsq,
+ struct BMVert *v1, struct BMVert *v2, float *r_dist, float *r_lambda);
+bool hair_test_vertex_inside_rect(struct ViewContext *vc, struct rcti *rect, struct BMVert *v);
+bool hair_test_vertex_inside_lasso(struct ViewContext *vc, const int mcoords[][2], short moves, struct BMVert *v);
+
+typedef struct HairToolData {
+ /* context */
+ struct Scene *scene;
+ struct Object *ob;
+ struct BMEditStrands *edit;
+ struct HairEditSettings *settings;
+ ViewContext vc;
+
+ /* view space */
+ float mval[2]; /* mouse coordinates */
+ float mdepth; /* mouse z depth */
+
+ /* object space */
+ float imat[4][4]; /* obmat inverse */
+ float loc[3]; /* start location */
+ float delta[3]; /* stroke step */
+} HairToolData;
+
+bool hair_brush_step(struct HairToolData *data);
+
+/* ==== Cursor ==== */
+
+void hair_edit_cursor_start(struct bContext *C, int (*poll)(struct bContext *C));
+
+/* ==== BMesh utilities ==== */
+
+struct BMEditStrands;
+
+void hair_bm_min_max(struct BMEditStrands *edit, float min[3], float max[3]);
+
+#endif
diff --git a/source/blender/editors/hair/hair_mirror.c b/source/blender/editors/hair/hair_mirror.c
new file mode 100644
index 00000000000..ee377a3634b
--- /dev/null
+++ b/source/blender/editors/hair/hair_mirror.c
@@ -0,0 +1,217 @@
+/*
+ * ***** 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/hair/hair_mirror.c
+ * \ingroup edhair
+ */
+
+#include <stdlib.h>
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_utildefines.h"
+#include "BLI_math.h"
+
+#include "BKE_editmesh_bvh.h"
+#include "BKE_editstrands.h"
+
+#include "ED_physics.h"
+#include "ED_util.h"
+
+#include "bmesh.h"
+
+#include "hair_intern.h"
+
+/* TODO use_topology is not yet implemented for strands.
+ * Native strand topology is not very useful for this.
+ * Instead, the topology of the scalp mesh should be used for finding mirrored strand roots,
+ * then the arc- or parametric length of a vertex from the root to find mirrored verts.
+ */
+
+#define BM_SEARCH_MAXDIST_MIRR 0.00002f
+#define BM_CD_LAYER_ID "__mirror_index"
+/**
+ * \param edit Edit strands.
+ * \param use_self Allow a vertex to point to its self (middle verts).
+ * \param use_select Restrict to selected verts.
+ * \param use_topology Use topology mirror.
+ * \param maxdist Distance for close point test.
+ * \param r_index Optional array to write into, as an alternative to a customdata layer (length of total verts).
+ */
+void ED_strands_mirror_cache_begin_ex(BMEditStrands *edit, const int axis, const bool use_self, const bool use_select,
+ /* extra args */
+ const bool UNUSED(use_topology), float maxdist, int *r_index)
+{
+ BMesh *bm = edit->base.bm;
+ BMIter iter;
+ BMVert *v;
+ int cd_vmirr_offset;
+ int i;
+
+ /* one or the other is used depending if topo is enabled */
+ struct BMBVHTree *tree = NULL;
+
+ BM_mesh_elem_table_ensure(bm, BM_VERT);
+
+ if (r_index == NULL) {
+ const char *layer_id = BM_CD_LAYER_ID;
+ int mirror_cdlayer = CustomData_get_named_layer_index(&bm->vdata, CD_PROP_INT, layer_id);
+ if (mirror_cdlayer == -1) {
+ BM_data_layer_add_named(bm, &bm->vdata, CD_PROP_INT, layer_id);
+ mirror_cdlayer = CustomData_get_named_layer_index(&bm->vdata, CD_PROP_INT, layer_id);
+ }
+
+ cd_vmirr_offset = CustomData_get_n_offset(&bm->vdata, CD_PROP_INT,
+ mirror_cdlayer - CustomData_get_layer_index(&bm->vdata, CD_PROP_INT));
+
+ bm->vdata.layers[mirror_cdlayer].flag |= CD_FLAG_TEMPORARY;
+
+ edit->base.mirror_cdlayer = mirror_cdlayer;
+ }
+
+ BM_mesh_elem_index_ensure(bm, BM_VERT);
+
+ tree = BKE_bmbvh_new(edit->base.bm, NULL, 0, 0, NULL, false);
+
+#define VERT_INTPTR(_v, _i) r_index ? &r_index[_i] : BM_ELEM_CD_GET_VOID_P(_v, cd_vmirr_offset);
+
+ BM_ITER_MESH_INDEX (v, &iter, bm, BM_VERTS_OF_MESH, i) {
+ BLI_assert(BM_elem_index_get(v) == i);
+
+ /* temporary for testing, check for selection */
+ if (use_select && !BM_elem_flag_test(v, BM_ELEM_SELECT)) {
+ /* do nothing */
+ }
+ else {
+ BMVert *v_mirr;
+ float co[3];
+ int *idx = VERT_INTPTR(v, i);
+
+ copy_v3_v3(co, v->co);
+ co[axis] *= -1.0f;
+ v_mirr = BKE_bmbvh_find_vert_closest(tree, co, maxdist);
+
+ if (v_mirr && (use_self || (v_mirr != v))) {
+ const int i_mirr = BM_elem_index_get(v_mirr);
+ *idx = i_mirr;
+ idx = VERT_INTPTR(v_mirr, i_mirr);
+ *idx = i;
+ }
+ else {
+ *idx = -1;
+ }
+ }
+
+ }
+
+#undef VERT_INTPTR
+
+ BKE_bmbvh_free(tree);
+}
+
+void ED_strands_mirror_cache_begin(BMEditStrands *edit, const int axis,
+ const bool use_self, const bool use_select,
+ const bool use_topology)
+{
+ ED_strands_mirror_cache_begin_ex(edit, axis,
+ use_self, use_select,
+ /* extra args */
+ use_topology, BM_SEARCH_MAXDIST_MIRR, NULL);
+}
+
+BMVert *ED_strands_mirror_get(BMEditStrands *edit, BMVert *v)
+{
+ BMesh *bm = edit->base.bm;
+ int mirror_cdlayer = edit->base.mirror_cdlayer;
+ const int *mirr = CustomData_bmesh_get_layer_n(&bm->vdata, v->head.data, mirror_cdlayer);
+
+ BLI_assert(mirror_cdlayer != -1); /* invalid use */
+
+ if (mirr && *mirr >= 0 && *mirr < bm->totvert) {
+ if (!bm->vtable) {
+ printf("err: should only be called between "
+ "ED_strands_mirror_cache_begin and ED_strands_mirror_cache_end\n");
+ return NULL;
+ }
+
+ return bm->vtable[*mirr];
+ }
+
+ return NULL;
+}
+
+BMEdge *ED_strands_mirror_get_edge(BMEditStrands *edit, BMEdge *e)
+{
+ BMVert *v1_mirr = ED_strands_mirror_get(edit, e->v1);
+ if (v1_mirr) {
+ BMVert *v2_mirr = ED_strands_mirror_get(edit, e->v2);
+ if (v2_mirr) {
+ return BM_edge_exists(v1_mirr, v2_mirr);
+ }
+ }
+
+ return NULL;
+}
+
+void ED_strands_mirror_cache_clear(BMEditStrands *edit, BMVert *v)
+{
+ BMesh *bm = edit->base.bm;
+ int mirror_cdlayer = edit->base.mirror_cdlayer;
+ int *mirr = CustomData_bmesh_get_layer_n(&bm->vdata, v->head.data, mirror_cdlayer);
+
+ BLI_assert(mirror_cdlayer != -1); /* invalid use */
+
+ if (mirr) {
+ *mirr = -1;
+ }
+}
+
+void ED_strands_mirror_cache_end(BMEditStrands *edit)
+{
+ edit->base.mirror_cdlayer = -1;
+}
+
+void ED_strands_mirror_apply(BMEditStrands *edit, const int sel_from, const int sel_to)
+{
+ BMesh *bm = edit->base.bm;
+ BMIter iter;
+ BMVert *v;
+
+ BLI_assert((bm->vtable != NULL) && ((bm->elem_table_dirty & BM_VERT) == 0));
+
+ BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
+ if (BM_elem_flag_test(v, BM_ELEM_SELECT) == sel_from) {
+ BMVert *mirr = ED_strands_mirror_get(edit, v);
+ if (mirr) {
+ if (BM_elem_flag_test(mirr, BM_ELEM_SELECT) == sel_to) {
+ copy_v3_v3(mirr->co, v->co);
+ mirr->co[0] *= -1.0f;
+ }
+ }
+ }
+ }
+}
diff --git a/source/blender/editors/hair/hair_object_mesh.c b/source/blender/editors/hair/hair_object_mesh.c
new file mode 100644
index 00000000000..887eed12042
--- /dev/null
+++ b/source/blender/editors/hair/hair_object_mesh.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/hair/hair_object_mesh.c
+ * \ingroup edhair
+ */
+
+#include <stdlib.h>
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_utildefines.h"
+
+#include "DNA_mesh_types.h"
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+
+#include "BKE_cdderivedmesh.h"
+#include "BKE_DerivedMesh.h"
+#include "BKE_editstrands.h"
+
+#include "bmesh.h"
+
+#include "hair_intern.h"
+
+bool ED_hair_object_init_mesh_edit(struct EvaluationContext *UNUSED(eval_ctx), Scene *UNUSED(scene), Object *ob)
+{
+ if (ob->type == OB_MESH) {
+ Mesh *me = ob->data;
+
+ if (!me->edit_strands) {
+ BMesh *bm = BKE_editstrands_mesh_to_bmesh(ob, me);
+ DerivedMesh *root_dm = CDDM_new(0, 0, 0, 0, 0);
+
+ me->edit_strands = BKE_editstrands_create(bm, root_dm);
+
+ root_dm->release(root_dm);
+ }
+ return true;
+ }
+
+ return false;
+}
+
+bool ED_hair_object_apply_mesh_edit(Object *ob)
+{
+ if (ob->type == OB_MESH) {
+ Mesh *me = ob->data;
+
+ if (me->edit_strands) {
+ BKE_editstrands_mesh_from_bmesh(ob);
+
+ BKE_editstrands_free(me->edit_strands);
+ MEM_freeN(me->edit_strands);
+ me->edit_strands = NULL;
+ }
+
+ return true;
+ }
+
+ return false;
+}
diff --git a/source/blender/editors/hair/hair_object_particles.c b/source/blender/editors/hair/hair_object_particles.c
new file mode 100644
index 00000000000..3aa446db983
--- /dev/null
+++ b/source/blender/editors/hair/hair_object_particles.c
@@ -0,0 +1,101 @@
+/*
+ * ***** 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/hair/hair_object_particles.c
+ * \ingroup edhair
+ */
+
+#include <stdlib.h>
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_utildefines.h"
+
+#include "DNA_mesh_types.h"
+#include "DNA_object_types.h"
+#include "DNA_particle_types.h"
+#include "DNA_scene_types.h"
+
+#include "BKE_cdderivedmesh.h"
+#include "BKE_DerivedMesh.h"
+#include "BKE_editstrands.h"
+#include "BKE_particle.h"
+
+#include "bmesh.h"
+
+#include "hair_intern.h"
+
+bool ED_hair_object_has_hair_particle_data(Object *ob)
+{
+ ParticleSystem *psys = psys_get_current(ob);
+ if (psys && psys->part->type == PART_HAIR)
+ return true;
+
+ return false;
+}
+
+bool ED_hair_object_init_particle_edit(struct EvaluationContext *eval_ctx, Scene *scene, Object *ob)
+{
+ ParticleSystem *psys = psys_get_current(ob);
+ BMesh *bm;
+ DerivedMesh *dm;
+
+ if (psys && psys->part->type == PART_HAIR) {
+ if (!psys->hairedit) {
+ bm = BKE_editstrands_particles_to_bmesh(ob, psys);
+
+ if (ob->type == OB_MESH || ob->derivedFinal)
+ dm = ob->derivedFinal ? ob->derivedFinal : mesh_get_derived_final(eval_ctx, scene, ob, CD_MASK_BAREMESH);
+ else
+ dm = NULL;
+
+ psys->hairedit = BKE_editstrands_create(bm, dm);
+ }
+ return true;
+ }
+
+ return false;
+}
+
+bool ED_hair_object_apply_particle_edit(Object *ob)
+{
+ ParticleSystem *psys = psys_get_current(ob);
+ if (psys->part->type == PART_HAIR) {
+ if (psys->hairedit) {
+ BKE_editstrands_particles_from_bmesh(ob, psys);
+ psys->flag |= PSYS_EDITED;
+
+ BKE_editstrands_free(psys->hairedit);
+ MEM_freeN(psys->hairedit);
+ psys->hairedit = NULL;
+ }
+
+ return true;
+ }
+
+ return false;
+}
diff --git a/source/blender/editors/hair/hair_ops.c b/source/blender/editors/hair/hair_ops.c
new file mode 100644
index 00000000000..bb3b027cf20
--- /dev/null
+++ b/source/blender/editors/hair/hair_ops.c
@@ -0,0 +1,143 @@
+/*
+ * ***** 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/hair/hair_ops.c
+ * \ingroup edhair
+ */
+
+#include "BLI_utildefines.h"
+
+#include "DNA_object_types.h"
+
+#include "BKE_context.h"
+
+#include "RNA_access.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "ED_physics.h"
+
+#include "hair_intern.h"
+#include "paint_intern.h"
+
+void ED_operatortypes_hair(void)
+{
+ WM_operatortype_append(HAIR_OT_hair_edit_toggle);
+
+ WM_operatortype_append(HAIR_OT_select_all);
+ WM_operatortype_append(HAIR_OT_select_linked);
+
+ WM_operatortype_append(HAIR_OT_stroke);
+}
+
+static int hair_poll(bContext *C)
+{
+ if (hair_edit_toggle_poll(C))
+ if (CTX_data_active_object(C)->mode & OB_MODE_HAIR_EDIT)
+ return true;
+
+ return false;
+}
+
+static void ed_keymap_hair_brush_switch(wmKeyMap *keymap, const char *mode)
+{
+ wmKeyMapItem *kmi;
+ int i;
+ /* index 0-9 (zero key is tenth), shift key for index 10-19 */
+ for (i = 0; i < 20; i++) {
+ kmi = WM_keymap_add_item(keymap, "BRUSH_OT_active_index_set",
+ ZEROKEY + ((i + 1) % 10), KM_PRESS, i < 10 ? 0 : KM_SHIFT, 0);
+ RNA_string_set(kmi->ptr, "mode", mode);
+ RNA_int_set(kmi->ptr, "index", i);
+ }
+}
+
+static void ed_keymap_hair_brush_size(wmKeyMap *keymap, const char *UNUSED(path))
+{
+ wmKeyMapItem *kmi;
+
+ kmi = WM_keymap_add_item(keymap, "BRUSH_OT_scale_size", LEFTBRACKETKEY, KM_PRESS, 0, 0);
+ RNA_float_set(kmi->ptr, "scalar", 0.9);
+
+ kmi = WM_keymap_add_item(keymap, "BRUSH_OT_scale_size", RIGHTBRACKETKEY, KM_PRESS, 0, 0);
+ RNA_float_set(kmi->ptr, "scalar", 10.0 / 9.0); // 1.1111....
+}
+
+static void ed_keymap_hair_brush_radial_control(wmKeyMap *keymap, const char *settings, RCFlags flags)
+{
+ wmKeyMapItem *kmi;
+ /* only size needs to follow zoom, strength shows fixed size circle */
+ int flags_nozoom = flags & (~RC_ZOOM);
+ int flags_noradial_secondary = flags & (~(RC_SECONDARY_ROTATION | RC_ZOOM));
+
+ kmi = WM_keymap_add_item(keymap, "WM_OT_radial_control", FKEY, KM_PRESS, 0, 0);
+ set_brush_rc_props(kmi->ptr, settings, "size", "use_unified_size", flags);
+
+ kmi = WM_keymap_add_item(keymap, "WM_OT_radial_control", FKEY, KM_PRESS, KM_SHIFT, 0);
+ set_brush_rc_props(kmi->ptr, settings, "strength", "use_unified_strength", flags_nozoom);
+
+ if (flags & RC_WEIGHT) {
+ kmi = WM_keymap_add_item(keymap, "WM_OT_radial_control", WKEY, KM_PRESS, 0, 0);
+ set_brush_rc_props(kmi->ptr, settings, "weight", "use_unified_weight", flags_nozoom);
+ }
+
+ if (flags & RC_ROTATION) {
+ kmi = WM_keymap_add_item(keymap, "WM_OT_radial_control", FKEY, KM_PRESS, KM_CTRL, 0);
+ set_brush_rc_props(kmi->ptr, settings, "texture_slot.angle", NULL, flags_noradial_secondary);
+ }
+
+ if (flags & RC_SECONDARY_ROTATION) {
+ kmi = WM_keymap_add_item(keymap, "WM_OT_radial_control", FKEY, KM_PRESS, KM_CTRL | KM_ALT, 0);
+ set_brush_rc_props(kmi->ptr, settings, "mask_texture_slot.angle", NULL, flags_nozoom);
+ }
+}
+
+void ED_keymap_hair(wmKeyConfig *keyconf)
+{
+ wmKeyMap *keymap;
+ wmKeyMapItem *kmi;
+
+ keymap = WM_keymap_find(keyconf, "Hair", 0, 0);
+ keymap->poll = hair_poll;
+
+ kmi = WM_keymap_add_item(keymap, "HAIR_OT_select_all", AKEY, KM_PRESS, 0, 0);
+ RNA_enum_set(kmi->ptr, "action", SEL_TOGGLE);
+ kmi = WM_keymap_add_item(keymap, "HAIR_OT_select_all", IKEY, KM_PRESS, KM_CTRL, 0);
+ RNA_enum_set(kmi->ptr, "action", SEL_INVERT);
+
+ kmi = WM_keymap_add_item(keymap, "HAIR_OT_select_linked", LKEY, KM_PRESS, 0, 0);
+ RNA_boolean_set(kmi->ptr, "deselect", false);
+ kmi = WM_keymap_add_item(keymap, "HAIR_OT_select_linked", LKEY, KM_PRESS, KM_SHIFT, 0);
+ RNA_boolean_set(kmi->ptr, "deselect", true);
+
+ kmi = WM_keymap_add_item(keymap, "HAIR_OT_stroke", LEFTMOUSE, KM_PRESS, 0, 0);
+
+ ed_keymap_hair_brush_switch(keymap, "hair_edit");
+ ed_keymap_hair_brush_size(keymap, "tool_settings.hair_edit.brush.size");
+ ed_keymap_hair_brush_radial_control(keymap, "hair_edit", 0);
+}
diff --git a/source/blender/editors/hair/hair_select.c b/source/blender/editors/hair/hair_select.c
new file mode 100644
index 00000000000..90ccfcabd0d
--- /dev/null
+++ b/source/blender/editors/hair/hair_select.c
@@ -0,0 +1,506 @@
+/*
+ * ***** 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/hair/hair_select.c
+ * \ingroup edhair
+ */
+
+#include <stdlib.h>
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_utildefines.h"
+#include "BLI_math.h"
+#include "BLI_rect.h"
+
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+#include "DNA_screen_types.h"
+#include "DNA_view3d_types.h"
+
+#include "BKE_context.h"
+#include "BKE_editstrands.h"
+
+#include "DEG_depsgraph.h"
+
+#include "bmesh.h"
+
+#include "RNA_access.h"
+#include "RNA_define.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "ED_object.h"
+#include "ED_physics.h"
+#include "ED_view3d.h"
+
+#include "hair_intern.h"
+
+BLI_INLINE bool apply_select_action_flag(BMVert *v, int action)
+{
+ bool cursel = BM_elem_flag_test_bool(v, BM_ELEM_SELECT);
+ bool newsel;
+
+ switch (action) {
+ case SEL_SELECT:
+ newsel = true;
+ break;
+ case SEL_DESELECT:
+ newsel = false;
+ break;
+ case SEL_INVERT:
+ newsel = !cursel;
+ break;
+ case SEL_TOGGLE:
+ /* toggle case should be converted to SELECT or DESELECT based on global state */
+ BLI_assert(false);
+ break;
+ }
+
+ if (newsel != cursel) {
+ BM_elem_flag_set(v, BM_ELEM_SELECT, newsel);
+ return true;
+ }
+ else
+ return false;
+}
+
+/* poll function */
+typedef bool (*PollVertexCb)(void *userdata, struct BMVert *v);
+/* distance metric function */
+typedef bool (*DistanceVertexCb)(void *userdata, struct BMVert *v, float *dist);
+typedef void (*ActionVertexCb)(void *userdata, struct BMVert *v, int action);
+
+static int hair_select_verts_filter(bContext *C, Object *ob, BMEditStrands *edit,
+ HairEditSelectMode select_mode, int action,
+ PollVertexCb cb, void *userdata)
+{
+ BMesh *bm = edit->base.bm;
+
+ BMVert *v;
+ BMIter iter;
+ int tot = 0;
+
+ bm->selectmode = BM_VERT;
+
+ switch (select_mode) {
+ case HAIR_SELECT_STRAND:
+ break;
+ case HAIR_SELECT_VERTEX:
+ BM_ITER_MESH(v, &iter, bm, BM_VERTS_OF_MESH) {
+ if (!cb(userdata, v))
+ continue;
+
+ if (apply_select_action_flag(v, action))
+ ++tot;
+ }
+ break;
+ case HAIR_SELECT_TIP:
+ BM_ITER_MESH(v, &iter, bm, BM_VERTS_OF_MESH) {
+ if (!BM_strands_vert_is_tip(v))
+ continue;
+ if (!cb(userdata, v))
+ continue;
+
+ if (apply_select_action_flag(v, action))
+ ++tot;
+ }
+ break;
+ }
+
+ BM_mesh_select_mode_flush(bm);
+
+ if (tot > 0) {
+ BKE_editstrands_batch_cache_dirty(edit, BKE_STRANDS_BATCH_DIRTY_SELECT);
+ WM_event_add_notifier(C, NC_GEOM | ND_SELECT, ob->data);
+ }
+
+ return tot;
+}
+
+static bool hair_select_verts_closest(bContext *C, Object *ob, BMEditStrands *edit, HairEditSelectMode select_mode, int action, DistanceVertexCb cb, ActionVertexCb action_cb, void *userdata)
+{
+ BMesh *bm = edit->base.bm;
+
+ BMVert *v;
+ BMIter iter;
+
+ float dist;
+ BMVert *closest_v = NULL;
+ float closest_dist = FLT_MAX;
+
+ bm->selectmode = BM_VERT;
+
+ switch (select_mode) {
+ case HAIR_SELECT_STRAND:
+ break;
+ case HAIR_SELECT_VERTEX:
+ BM_ITER_MESH(v, &iter, bm, BM_VERTS_OF_MESH) {
+ if (!cb(userdata, v, &dist))
+ continue;
+
+ if (dist < closest_dist) {
+ closest_v = v;
+ closest_dist = dist;
+ }
+ }
+ break;
+ case HAIR_SELECT_TIP:
+ BM_ITER_MESH(v, &iter, bm, BM_VERTS_OF_MESH) {
+ if (!BM_strands_vert_is_tip(v))
+ continue;
+ if (!cb(userdata, v, &dist))
+ continue;
+
+ if (dist < closest_dist) {
+ closest_v = v;
+ closest_dist = dist;
+ }
+ }
+ break;
+ }
+
+ if (closest_v) {
+ action_cb(userdata, closest_v, action);
+
+ BM_mesh_select_mode_flush(bm);
+
+ BKE_editstrands_batch_cache_dirty(edit, BKE_STRANDS_BATCH_DIRTY_SELECT);
+ WM_event_add_notifier(C, NC_GEOM | ND_SELECT, ob->data);
+
+ return true;
+ }
+ else
+ return false;
+}
+
+static void hair_deselect_all(BMEditStrands *edit)
+{
+ BMesh *bm = edit->base.bm;
+
+ BMVert *v;
+ BMIter iter;
+
+ BM_ITER_MESH(v, &iter, bm, BM_VERTS_OF_MESH) {
+ BM_elem_flag_set(v, BM_ELEM_SELECT, false);
+ }
+}
+
+/* ------------------------------------------------------------------------- */
+
+/************************ select/deselect all operator ************************/
+
+static bool poll_vertex_all(void *UNUSED(userdata), struct BMVert *UNUSED(v))
+{
+ return true;
+}
+
+static int select_all_exec(bContext *C, wmOperator *op)
+{
+ Scene *scene = CTX_data_scene(C);
+ Object *ob = CTX_data_active_object(C);
+ BMEditStrands *edit = BKE_editstrands_from_object(ob);
+ HairEditSettings *settings = &scene->toolsettings->hair_edit;
+ int action = RNA_enum_get(op->ptr, "action");
+
+ if (!edit)
+ return 0;
+
+ /* toggle action depends on current global selection state */
+ if (action == SEL_TOGGLE) {
+ if (edit->base.bm->totvertsel == 0)
+ action = SEL_SELECT;
+ else
+ action = SEL_DESELECT;
+ }
+
+ hair_select_verts_filter(C, ob, edit, settings->select_mode, action, poll_vertex_all, NULL);
+
+ return OPERATOR_FINISHED;
+}
+
+void HAIR_OT_select_all(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Select/Deselect All";
+ ot->idname = "HAIR_OT_select_all";
+ ot->description = "Select/Deselect all hair vertices";
+
+ /* api callbacks */
+ ot->exec = select_all_exec;
+ ot->poll = hair_edit_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+ WM_operator_properties_select_all(ot);
+}
+
+/************************ mouse select operator ************************/
+
+typedef struct DistanceVertexCirleData {
+ ViewContext vc;
+ float mval[2];
+ float radsq;
+} DistanceVertexCirleData;
+
+static bool distance_vertex_circle(void *userdata, struct BMVert *v, float *dist)
+{
+ DistanceVertexCirleData *data = userdata;
+
+ return hair_test_vertex_inside_circle(&data->vc, data->mval, data->radsq, v, dist);
+}
+
+static void closest_vertex_select(void *UNUSED(userdata), struct BMVert *v, int action)
+{
+ apply_select_action_flag(v, action);
+}
+
+int ED_hair_mouse_select(bContext *C, const int mval[2], bool extend, bool deselect, bool toggle)
+{
+ Scene *scene = CTX_data_scene(C);
+ Object *ob = CTX_data_active_object(C);
+ BMEditStrands *edit = BKE_editstrands_from_object(ob);
+ HairEditSettings *settings = &scene->toolsettings->hair_edit;
+ float select_radius = ED_view3d_select_dist_px();
+
+ DistanceVertexCirleData data;
+ int action;
+
+ if (!extend && !deselect && !toggle) {
+ hair_deselect_all(edit);
+ }
+
+ hair_init_viewcontext(C, &data.vc);
+ data.mval[0] = mval[0];
+ data.mval[1] = mval[1];
+ data.radsq = select_radius * select_radius;
+
+ if (extend)
+ action = SEL_SELECT;
+ else if (deselect)
+ action = SEL_DESELECT;
+ else
+ action = SEL_INVERT;
+
+ hair_select_verts_closest(C, ob, edit, settings->select_mode, action, distance_vertex_circle, closest_vertex_select, &data);
+
+ return OPERATOR_FINISHED;
+}
+
+/************************ select linked operator ************************/
+
+static void linked_vertices_select(void *UNUSED(userdata), struct BMVert *v, int action)
+{
+ BMVert *lv;
+
+ apply_select_action_flag(v, action);
+
+ for (lv = BM_strands_vert_prev(v); lv; lv = BM_strands_vert_prev(lv))
+ apply_select_action_flag(lv, action);
+ for (lv = BM_strands_vert_next(v); lv; lv = BM_strands_vert_next(lv))
+ apply_select_action_flag(lv, action);
+}
+
+static int select_linked_exec(bContext *C, wmOperator *op)
+{
+ Scene *scene = CTX_data_scene(C);
+ Object *ob = CTX_data_active_object(C);
+ BMEditStrands *edit = BKE_editstrands_from_object(ob);
+ HairEditSettings *settings = &scene->toolsettings->hair_edit;
+ float select_radius = ED_view3d_select_dist_px();
+
+ DistanceVertexCirleData data;
+ int location[2];
+ int action;
+
+ RNA_int_get_array(op->ptr, "location", location);
+
+ hair_init_viewcontext(C, &data.vc);
+ data.mval[0] = location[0];
+ data.mval[1] = location[1];
+ data.radsq = select_radius * select_radius;
+
+ action = RNA_boolean_get(op->ptr, "deselect") ? SEL_DESELECT : SEL_SELECT;
+
+ hair_select_verts_closest(C, ob, edit, settings->select_mode, action, distance_vertex_circle, linked_vertices_select, &data);
+
+ return OPERATOR_FINISHED;
+}
+
+static int select_linked_invoke(bContext *C, wmOperator *op, const wmEvent *event)
+{
+ RNA_int_set_array(op->ptr, "location", event->mval);
+ return select_linked_exec(C, op);
+}
+
+void HAIR_OT_select_linked(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Select Linked";
+ ot->idname = "HAIR_OT_select_linked";
+ ot->description = "Select connected vertices";
+
+ /* api callbacks */
+ ot->exec = select_linked_exec;
+ ot->invoke = select_linked_invoke;
+ ot->poll = hair_edit_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+ /* properties */
+ RNA_def_boolean(ot->srna, "deselect", 0, "Deselect", "Deselect linked keys rather than selecting them");
+ RNA_def_int_vector(ot->srna, "location", 2, NULL, 0, INT_MAX, "Location", "", 0, 16384);
+}
+
+/************************ border select operator ************************/
+
+typedef struct PollVertexRectData {
+ ViewContext vc;
+ rcti rect;
+} PollVertexRectData;
+
+static bool poll_vertex_inside_rect(void *userdata, struct BMVert *v)
+{
+ PollVertexRectData *data = userdata;
+
+ return hair_test_vertex_inside_rect(&data->vc, &data->rect, v);
+}
+
+int ED_hair_border_select(bContext *C, rcti *rect, bool select, bool extend)
+{
+ Scene *scene = CTX_data_scene(C);
+ Object *ob = CTX_data_active_object(C);
+ BMEditStrands *edit = BKE_editstrands_from_object(ob);
+ HairEditSettings *settings = &scene->toolsettings->hair_edit;
+
+ PollVertexRectData data;
+ int action;
+
+ if (!extend && select)
+ hair_deselect_all(edit);
+
+ hair_init_viewcontext(C, &data.vc);
+ data.rect = *rect;
+
+ if (extend)
+ action = SEL_SELECT;
+ else if (select)
+ action = SEL_INVERT;
+ else
+ action = SEL_DESELECT;
+
+ hair_select_verts_filter(C, ob, edit, settings->select_mode, action, poll_vertex_inside_rect, &data);
+
+ return OPERATOR_FINISHED;
+}
+
+/************************ circle select operator ************************/
+
+typedef struct PollVertexCirleData {
+ ViewContext vc;
+ float mval[2];
+ float radsq;
+} PollVertexCirleData;
+
+static bool poll_vertex_inside_circle(void *userdata, struct BMVert *v)
+{
+ PollVertexCirleData *data = userdata;
+ float dist;
+
+ return hair_test_vertex_inside_circle(&data->vc, data->mval, data->radsq, v, &dist);
+}
+
+int ED_hair_circle_select(bContext *C, bool select, const int mval[2], float radius)
+{
+ Scene *scene = CTX_data_scene(C);
+ Object *ob = CTX_data_active_object(C);
+ BMEditStrands *edit = BKE_editstrands_from_object(ob);
+ HairEditSettings *settings = &scene->toolsettings->hair_edit;
+ int action = select ? SEL_SELECT : SEL_DESELECT;
+
+ PollVertexCirleData data;
+ int tot;
+
+ if (!edit)
+ return 0;
+
+ hair_init_viewcontext(C, &data.vc);
+ data.mval[0] = mval[0];
+ data.mval[1] = mval[1];
+ data.radsq = radius * radius;
+
+ tot = hair_select_verts_filter(C, ob, edit, settings->select_mode, action, poll_vertex_inside_circle, &data);
+
+ return tot;
+}
+
+/************************ lasso select operator ************************/
+
+typedef struct PollVertexLassoData {
+ ViewContext vc;
+ const int (*mcoords)[2];
+ short moves;
+} PollVertexLassoData;
+
+static bool poll_vertex_inside_lasso(void *userdata, struct BMVert *v)
+{
+ PollVertexLassoData *data = userdata;
+
+ return hair_test_vertex_inside_lasso(&data->vc, data->mcoords, data->moves, v);
+}
+
+int ED_hair_lasso_select(bContext *C, const int mcoords[][2], const short moves, bool extend, bool select)
+{
+ Scene *scene = CTX_data_scene(C);
+ Object *ob = CTX_data_active_object(C);
+ BMEditStrands *edit = BKE_editstrands_from_object(ob);
+ HairEditSettings *settings = &scene->toolsettings->hair_edit;
+
+ PollVertexLassoData data;
+ int action;
+
+ if (!extend && select)
+ hair_deselect_all(edit);
+
+ hair_init_viewcontext(C, &data.vc);
+ data.mcoords = mcoords;
+ data.moves = moves;
+
+ if (extend)
+ action = SEL_SELECT;
+ else if (select)
+ action = SEL_INVERT;
+ else
+ action = SEL_DESELECT;
+
+ hair_select_verts_filter(C, ob, edit, settings->select_mode, action, poll_vertex_inside_lasso, &data);
+
+ return OPERATOR_FINISHED;
+}
diff --git a/source/blender/editors/hair/hair_stroke.c b/source/blender/editors/hair/hair_stroke.c
new file mode 100644
index 00000000000..c18ef56bf6f
--- /dev/null
+++ b/source/blender/editors/hair/hair_stroke.c
@@ -0,0 +1,500 @@
+/*
+ * ***** 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/hair/hair_edit.c
+ * \ingroup edhair
+ */
+
+#include <stdlib.h>
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_utildefines.h"
+#include "BLI_lasso.h"
+#include "BLI_math.h"
+#include "BLI_rect.h"
+
+#include "DNA_brush_types.h"
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+#include "DNA_view3d_types.h"
+
+#include "BKE_brush.h"
+#include "BKE_DerivedMesh.h"
+#include "BKE_editstrands.h"
+#include "BKE_effect.h"
+#include "BKE_mesh_sample.h"
+
+#include "bmesh.h"
+
+#include "BIF_gl.h"
+#include "BIF_glutil.h"
+
+#include "ED_physics.h"
+#include "ED_view3d.h"
+
+#include "hair_intern.h"
+
+bool hair_test_depth(ViewContext *vc, const float co[3], const int screen_co[2])
+{
+ View3D *v3d = vc->v3d;
+ ViewDepths *vd = vc->rv3d->depths;
+ const bool has_zbuf = (v3d->drawtype > OB_WIRE) && (v3d->flag & V3D_ZBUF_SELECT);
+
+ float uco[3];
+ float depth;
+
+ /* nothing to do */
+ if (!has_zbuf)
+ return true;
+
+ ED_view3d_project(vc->ar, co, uco);
+
+ /* check if screen_co is within bounds because brush_cut uses out of screen coords */
+ if (screen_co[0] >= 0 && screen_co[0] < vd->w && screen_co[1] >= 0 && screen_co[1] < vd->h) {
+ BLI_assert(vd && vd->depths);
+ /* we know its not clipped */
+ depth = vd->depths[screen_co[1] * vd->w + screen_co[0]];
+
+ return ((float)uco[2] - 0.00001f <= depth);
+ }
+
+ return false;
+}
+
+bool hair_test_vertex_inside_circle(ViewContext *vc, const float mval[2], float radsq, BMVert *v, float *r_dist)
+{
+ float (*obmat)[4] = vc->obact->obmat;
+ float co_world[3];
+ float dx, dy, distsq;
+ int screen_co[2];
+
+ mul_v3_m4v3(co_world, obmat, v->co);
+
+ /* TODO, should this check V3D_PROJ_TEST_CLIP_BB too? */
+ if (ED_view3d_project_int_global(vc->ar, co_world, screen_co, V3D_PROJ_TEST_CLIP_WIN) != V3D_PROJ_RET_OK)
+ return false;
+
+ dx = mval[0] - (float)screen_co[0];
+ dy = mval[1] - (float)screen_co[1];
+ distsq = dx * dx + dy * dy;
+
+ if (distsq > radsq)
+ return false;
+
+ if (hair_test_depth(vc, v->co, screen_co)) {
+ *r_dist = sqrtf(distsq);
+ return true;
+ }
+ else
+ return false;
+}
+
+bool hair_test_edge_inside_circle(ViewContext *vc, const float mval[2], float radsq, BMVert *v1, BMVert *v2, float *r_dist, float *r_lambda)
+{
+ float (*obmat)[4] = vc->obact->obmat;
+ float world_co1[3], world_co2[3];
+ float dx, dy, distsq;
+ int screen_co1[2], screen_co2[2], screen_cp[2];
+ float lambda, world_cp[3], screen_cpf[2], screen_co1f[2], screen_co2f[2];
+
+ mul_v3_m4v3(world_co1, obmat, v1->co);
+ mul_v3_m4v3(world_co2, obmat, v2->co);
+
+ /* TODO, should this check V3D_PROJ_TEST_CLIP_BB too? */
+ if (ED_view3d_project_int_global(vc->ar, world_co1, screen_co1, V3D_PROJ_TEST_CLIP_WIN) != V3D_PROJ_RET_OK)
+ return false;
+ if (ED_view3d_project_int_global(vc->ar, world_co2, screen_co2, V3D_PROJ_TEST_CLIP_WIN) != V3D_PROJ_RET_OK)
+ return false;
+
+ screen_co1f[0] = screen_co1[0];
+ screen_co1f[1] = screen_co1[1];
+ screen_co2f[0] = screen_co2[0];
+ screen_co2f[1] = screen_co2[1];
+ lambda = closest_to_line_v2(screen_cpf, mval, screen_co1f, screen_co2f);
+ if (lambda < 0.0f || lambda > 1.0f) {
+ CLAMP(lambda, 0.0f, 1.0f);
+ interp_v2_v2v2(screen_cpf, screen_co1f, screen_co2f, lambda);
+ }
+
+ dx = mval[0] - screen_cpf[0];
+ dy = mval[1] - screen_cpf[1];
+ distsq = dx * dx + dy * dy;
+
+ if (distsq > radsq)
+ return false;
+
+ interp_v3_v3v3(world_cp, world_co1, world_co2, lambda);
+
+ screen_cp[0] = screen_cpf[0];
+ screen_cp[1] = screen_cpf[1];
+ if (hair_test_depth(vc, world_cp, screen_cp)) {
+ *r_dist = sqrtf(distsq);
+ *r_lambda = lambda;
+ return true;
+ }
+ else
+ return false;
+}
+
+bool hair_test_vertex_inside_rect(ViewContext *vc, rcti *rect, BMVert *v)
+{
+ float (*obmat)[4] = vc->obact->obmat;
+ float co_world[3];
+ int screen_co[2];
+
+ mul_v3_m4v3(co_world, obmat, v->co);
+
+ /* TODO, should this check V3D_PROJ_TEST_CLIP_BB too? */
+ if (ED_view3d_project_int_global(vc->ar, co_world, screen_co, V3D_PROJ_TEST_CLIP_WIN) != V3D_PROJ_RET_OK)
+ return false;
+
+ if (!BLI_rcti_isect_pt_v(rect, screen_co))
+ return false;
+
+ if (hair_test_depth(vc, v->co, screen_co))
+ return true;
+ else
+ return false;
+}
+
+bool hair_test_vertex_inside_lasso(ViewContext *vc, const int mcoords[][2], short moves, BMVert *v)
+{
+ float (*obmat)[4] = vc->obact->obmat;
+ float co_world[3];
+ int screen_co[2];
+
+ mul_v3_m4v3(co_world, obmat, v->co);
+
+ /* TODO, should this check V3D_PROJ_TEST_CLIP_BB too? */
+ if (ED_view3d_project_int_global(vc->ar, co_world, screen_co, V3D_PROJ_TEST_CLIP_WIN) != V3D_PROJ_RET_OK)
+ return false;
+
+ if (!BLI_lasso_is_point_inside(mcoords, moves, screen_co[0], screen_co[1], IS_CLIPPED))
+ return false;
+
+ if (hair_test_depth(vc, v->co, screen_co))
+ return true;
+ else
+ return false;
+}
+
+/* ------------------------------------------------------------------------- */
+
+typedef void (*VertexToolCb)(HairToolData *data, void *userdata, BMVert *v, float factor);
+
+/* apply tool directly to each vertex inside the filter area */
+static int UNUSED_FUNCTION(hair_tool_apply_vertex)(HairToolData *data, VertexToolCb cb, void *userdata)
+{
+ BMesh *bm = data->edit->base.bm;
+ Scene *scene = data->scene;
+ Brush *brush = data->settings->brush;
+ const float rad = BKE_brush_size_get(scene, brush);
+ const float radsq = rad*rad;
+ const float threshold = 0.0f; /* XXX could be useful, is it needed? */
+ const bool use_mirror = hair_use_mirror_x(data->ob);
+
+ BMVert *v, *v_mirr;
+ BMIter iter;
+ int tot = 0;
+ float dist, factor;
+
+ if (use_mirror)
+ ED_strands_mirror_cache_begin(data->edit, 0, false, false, hair_use_mirror_topology(data->ob));
+
+ BM_ITER_MESH(v, &iter, bm, BM_VERTS_OF_MESH) {
+ if (!hair_test_vertex_inside_circle(&data->vc, data->mval, radsq, v, &dist))
+ continue;
+
+ factor = 1.0f - dist / rad;
+ if (factor > threshold) {
+ cb(data, userdata, v, factor);
+ ++tot;
+
+ if (use_mirror) {
+ v_mirr = ED_strands_mirror_get(data->edit, v);
+ if (v_mirr)
+ cb(data, userdata, v_mirr, factor);
+ }
+ }
+ }
+
+ /* apply mirror */
+ if (use_mirror)
+ ED_strands_mirror_cache_end(data->edit);
+
+ return tot;
+}
+
+/* ------------------------------------------------------------------------- */
+
+typedef void (*EdgeToolCb)(HairToolData *data, void *userdata, BMVert *v1, BMVert *v2, float factor, float edge_param);
+
+static int hair_tool_apply_strand_edges(HairToolData *data, EdgeToolCb cb, void *userdata, BMVert *root)
+{
+ Scene *scene = data->scene;
+ Brush *brush = data->settings->brush;
+ const float rad = BKE_brush_size_get(scene, brush);
+ const float radsq = rad*rad;
+ const float threshold = 0.0f; /* XXX could be useful, is it needed? */
+ const bool use_mirror = hair_use_mirror_x(data->ob);
+
+ BMVert *v, *v_mirr, *vprev, *vprev_mirr;
+ BMIter iter;
+ int k;
+ int tot = 0;
+
+ BM_ITER_STRANDS_ELEM_INDEX(v, &iter, root, BM_VERTS_OF_STRAND, k) {
+ if (use_mirror)
+ v_mirr = ED_strands_mirror_get(data->edit, v);
+
+ if (k > 0) {
+ float dist, lambda;
+
+ if (hair_test_edge_inside_circle(&data->vc, data->mval, radsq, vprev, v, &dist, &lambda)) {
+ float factor = 1.0f - dist / rad;
+ if (factor > threshold) {
+ cb(data, userdata, vprev, v, factor, lambda);
+ ++tot;
+
+ if (use_mirror) {
+ if (vprev_mirr && v_mirr)
+ cb(data, userdata, vprev_mirr, v_mirr, factor, lambda);
+ }
+ }
+ }
+ }
+
+ vprev = v;
+ vprev_mirr = v_mirr;
+ }
+
+ return tot;
+}
+
+/* apply tool to vertices of edges inside the filter area,
+ * using the closest edge point for weighting
+ */
+static int hair_tool_apply_edge(HairToolData *data, EdgeToolCb cb, void *userdata)
+{
+ BMesh *bm = data->edit->base.bm;
+ BMVert *root;
+ BMIter iter;
+ int tot = 0;
+
+ if (hair_use_mirror_x(data->ob))
+ ED_strands_mirror_cache_begin(data->edit, 0, false, false, hair_use_mirror_topology(data->ob));
+
+ BM_ITER_STRANDS(root, &iter, bm, BM_STRANDS_OF_MESH) {
+ tot += hair_tool_apply_strand_edges(data, cb, userdata, root);
+ }
+
+ /* apply mirror */
+ if (hair_use_mirror_x(data->ob))
+ ED_strands_mirror_cache_end(data->edit);
+
+ return tot;
+}
+
+/* ------------------------------------------------------------------------- */
+
+typedef struct CombData {
+ float power;
+} CombData;
+
+static void UNUSED_FUNCTION(hair_vertex_comb)(HairToolData *data, void *userdata, BMVert *v, float factor)
+{
+ CombData *combdata = userdata;
+
+ float combfactor = powf(factor, combdata->power);
+
+ madd_v3_v3fl(v->co, data->delta, combfactor);
+}
+
+/* Edge-based combing tool:
+ * Unlike the vertex tool (which simply displaces vertices), the edge tool
+ * adjusts edge orientations to follow the stroke direction.
+ */
+static void hair_edge_comb(HairToolData *data, void *userdata, BMVert *v1, BMVert *v2, float factor, float UNUSED(edge_param))
+{
+ CombData *combdata = userdata;
+ float strokedir[3], edge[3], edgedir[3], strokelen, edgelen;
+ float edge_proj[3];
+
+ float combfactor = powf(factor, combdata->power);
+ float effect;
+
+ strokelen = normalize_v3_v3(strokedir, data->delta);
+
+ sub_v3_v3v3(edge, v2->co, v1->co);
+ edgelen = normalize_v3_v3(edgedir, edge);
+ if (edgelen == 0.0f)
+ return;
+
+ /* This factor prevents sudden changes in direction with small stroke lengths.
+ * The arctan maps the 0..inf range of the length ratio to 0..1 smoothly.
+ */
+ effect = atan(strokelen / edgelen * 4.0f / (0.5f*M_PI));
+
+ mul_v3_v3fl(edge_proj, strokedir, edgelen);
+
+ interp_v3_v3v3(edge, edge, edge_proj, combfactor * effect);
+
+ add_v3_v3v3(v2->co, v1->co, edge);
+}
+
+
+BLI_INLINE void construct_m4_loc_nor_tan(float mat[4][4], const float loc[3], const float nor[3], const float tang[3])
+{
+ float cotang[3];
+
+ cross_v3_v3v3(cotang, nor, tang);
+
+ copy_v3_v3(mat[0], tang);
+ copy_v3_v3(mat[1], cotang);
+ copy_v3_v3(mat[2], nor);
+ copy_v3_v3(mat[3], loc);
+ mat[0][3] = 0.0f;
+ mat[1][3] = 0.0f;
+ mat[2][3] = 0.0f;
+ mat[3][3] = 1.0f;
+}
+
+static void grow_hair(BMEditStrands *edit, MeshSample *sample)
+{
+ BMesh *bm = edit->base.bm;
+ DerivedMesh *dm = edit->root_dm;
+ const float len = 1.5f;
+
+ float root_mat[4][4];
+ BMVert *root, *v;
+ BMIter iter;
+ int i;
+
+ {
+ float co[3], nor[3], tang[3];
+ BKE_mesh_sample_eval(dm, sample, co, nor, tang);
+ construct_m4_loc_nor_tan(root_mat, co, nor, tang);
+ }
+
+ root = BM_strands_create(bm, 5, true);
+
+ BM_elem_meshsample_data_named_set(&bm->vdata, root, CD_MSURFACE_SAMPLE, CD_HAIR_ROOT_LOCATION, sample);
+
+ BM_ITER_STRANDS_ELEM_INDEX(v, &iter, root, BM_VERTS_OF_STRAND, i) {
+ float co[3];
+
+ co[0] = co[1] = 0.0f;
+ co[2] = len * (float)i / (float)(len - 1);
+
+ mul_m4_v3(root_mat, co);
+
+ copy_v3_v3(v->co, co);
+ }
+
+ BM_mesh_elem_index_ensure(bm, BM_ALL);
+}
+
+static bool hair_add_ray_cb(void *vdata, float ray_start[3], float ray_end[3])
+{
+ HairToolData *data = vdata;
+ ViewContext *vc = &data->vc;
+
+ ED_view3d_win_to_segment(vc->ar, vc->v3d, data->mval, ray_start, ray_end, true);
+
+ mul_m4_v3(data->imat, ray_start);
+ mul_m4_v3(data->imat, ray_end);
+
+ return true;
+}
+
+static bool hair_get_surface_sample(HairToolData *data, MeshSample *sample)
+{
+ DerivedMesh *dm = data->edit->root_dm;
+
+ MeshSampleGenerator *gen;
+ bool ok;
+
+ gen = BKE_mesh_sample_gen_surface_raycast(dm, hair_add_ray_cb, data);
+ ok = BKE_mesh_sample_generate(gen, sample);
+ BKE_mesh_sample_free_generator(gen);
+
+ return ok;
+}
+
+static bool hair_add(HairToolData *data)
+{
+ MeshSample sample;
+
+ if (!hair_get_surface_sample(data, &sample))
+ return false;
+
+ grow_hair(data->edit, &sample);
+
+ return true;
+}
+
+
+bool hair_brush_step(HairToolData *data)
+{
+ Brush *brush = data->settings->brush;
+ BrushHairTool hair_tool = brush->hair_tool;
+ BMEditStrands *edit = data->edit;
+ int tot = 0;
+
+ switch (hair_tool) {
+ case HAIR_TOOL_COMB: {
+ CombData combdata;
+ combdata.power = (brush->alpha - 0.5f) * 2.0f;
+ if (combdata.power < 0.0f)
+ combdata.power = 1.0f - 9.0f * combdata.power;
+ else
+ combdata.power = 1.0f - combdata.power;
+
+ tot = hair_tool_apply_edge(data, hair_edge_comb, &combdata);
+ break;
+ }
+ case HAIR_TOOL_CUT:
+ break;
+ case HAIR_TOOL_LENGTH:
+ break;
+ case HAIR_TOOL_PUFF:
+ break;
+ case HAIR_TOOL_ADD:
+ if (hair_add(data))
+ edit->flag |= BM_STRANDS_DIRTY_SEGLEN;
+ break;
+ case HAIR_TOOL_SMOOTH:
+ break;
+ case HAIR_TOOL_WEIGHT:
+ break;
+ }
+
+ return tot > 0;
+}
diff --git a/source/blender/editors/hair/hair_undo.c b/source/blender/editors/hair/hair_undo.c
new file mode 100644
index 00000000000..cb5ae43f5dd
--- /dev/null
+++ b/source/blender/editors/hair/hair_undo.c
@@ -0,0 +1,195 @@
+/*
+ * ***** 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/hair/hair_undo.c
+ * \ingroup edhair
+ */
+
+#include <stdlib.h>
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_utildefines.h"
+
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+#include "DNA_view3d_types.h"
+
+#include "BKE_context.h"
+#include "BKE_DerivedMesh.h"
+#include "BKE_editstrands.h"
+#include "BKE_key.h"
+#include "BKE_mesh.h"
+#include "BKE_mesh_sample.h"
+
+#include "ED_physics.h"
+#include "ED_util.h"
+
+#include "bmesh.h"
+
+#include "hair_intern.h"
+
+static void *strands_get_edit(bContext *C)
+{
+ Object *obact = CTX_data_active_object(C);
+ const int mode_flag = OB_MODE_HAIR_EDIT;
+ const bool is_mode_set = ((obact->mode & mode_flag) != 0);
+
+ if (obact && is_mode_set) {
+ BMEditStrands *edit = BKE_editstrands_from_object(obact);
+ return edit;
+ }
+ return NULL;
+}
+
+typedef struct UndoStrands {
+ Mesh me; /* Mesh supports all the customdata we need, easiest way to implement undo storage */
+ int selectmode;
+
+ /** \note
+ * this isn't a prefect solution, if you edit keys and change shapes this works well (fixing [#32442]),
+ * but editing shape keys, going into object mode, removing or changing their order,
+ * then go back into editmode and undo will give issues - where the old index will be out of sync
+ * with the new object index.
+ *
+ * There are a few ways this could be made to work but for now its a known limitation with mixing
+ * object and editmode operations - Campbell */
+ int shapenr;
+} UndoStrands;
+
+/* undo simply makes copies of a bmesh */
+static void *strands_edit_to_undo(void *editv, void *UNUSED(obdata))
+{
+ BMEditStrands *edit = editv;
+ BMesh *bm = edit->base.bm;
+// Mesh *obme = obdata;
+ struct BMeshToMeshParams params = {0};
+
+ UndoStrands *undo = MEM_callocN(sizeof(UndoStrands), "undo Strands");
+
+ /* make sure shape keys work */
+// um->me.key = obme->key ? BKE_key_copy_nolib(obme->key) : NULL;
+
+ /* BM_mesh_validate(em->bm); */ /* for troubleshooting */
+
+ params.cd_mask_extra = CD_MASK_STRANDS;
+ BM_mesh_bm_to_me(bm, &undo->me, &params);
+
+ undo->selectmode = bm->selectmode;
+ undo->shapenr = bm->shapenr;
+
+ return undo;
+}
+
+static void strands_undo_to_edit(void *undov, void *editv, void *UNUSED(obdata))
+{
+ UndoStrands *undo = undov;
+ BMEditStrands *edit = editv, *edit_tmp;
+ Object *ob = edit->base.ob;
+ DerivedMesh *dm = edit->root_dm;
+ BMesh *bm;
+// Key *key = ((Mesh *) obdata)->key;
+ struct BMeshFromMeshParams params = {0};
+
+ const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(&undo->me);
+
+ edit->base.bm->shapenr = undo->shapenr;
+
+ bm = BM_mesh_create(&allocsize,
+ &((struct BMeshCreateParams){.use_toolflags = false,}));
+ params.cd_mask_extra = CD_MASK_STRANDS_BMESH;
+ params.active_shapekey = undo->shapenr;
+ BM_mesh_bm_from_me(bm, &undo->me, &params);
+
+ /* note: have to create the new edit before freeing the old one,
+ * because it owns the root_dm and we have to copy it before
+ * it gets released when freeing the old edit.
+ */
+ edit_tmp = BKE_editstrands_create(bm, dm);
+ BKE_editstrands_free(edit);
+ *edit = *edit_tmp;
+
+ bm->selectmode = undo->selectmode;
+ edit->base.ob = ob;
+
+#if 0
+ /* T35170: Restore the active key on the RealMesh. Otherwise 'fake' offset propagation happens
+ * if the active is a basis for any other. */
+ if (key && (key->type == KEY_RELATIVE)) {
+ /* Since we can't add, remove or reorder keyblocks in editmode, it's safe to assume
+ * shapenr from restored bmesh and keyblock indices are in sync. */
+ const int kb_act_idx = ob->shapenr - 1;
+
+ /* If it is, let's patch the current mesh key block to its restored value.
+ * Else, the offsets won't be computed and it won't matter. */
+ if (BKE_keyblock_is_basis(key, kb_act_idx)) {
+ KeyBlock *kb_act = BLI_findlink(&key->block, kb_act_idx);
+
+ if (kb_act->totelem != undo->me.totvert) {
+ /* The current mesh has some extra/missing verts compared to the undo, adjust. */
+ MEM_SAFE_FREE(kb_act->data);
+ kb_act->data = MEM_mallocN((size_t)(key->elemsize * bm->totvert), __func__);
+ kb_act->totelem = undo->me.totvert;
+ }
+
+ BKE_keyblock_update_from_mesh(&undo->me, kb_act);
+ }
+ }
+#endif
+
+ ob->shapenr = undo->shapenr;
+
+ MEM_freeN(edit_tmp);
+}
+
+static void strands_free_undo(void *undov)
+{
+ UndoStrands *undo = undov;
+
+ if (undo->me.key) {
+ BKE_key_free(undo->me.key);
+ MEM_freeN(undo->me.key);
+ }
+
+ BKE_mesh_free(&undo->me);
+ MEM_freeN(undo);
+}
+
+/* and this is all the undo system needs to know */
+void undo_push_strands(bContext *C, const char *name)
+{
+ /* edit->ob gets out of date and crashes on mesh undo,
+ * this is an easy way to ensure its OK
+ * though we could investigate the matter further. */
+ Object *obact = CTX_data_active_object(C);
+ BMEditStrands *edit = BKE_editstrands_from_object(obact);
+ edit->base.ob = obact;
+
+ undo_editmode_push(C, name, CTX_data_active_object, strands_get_edit, strands_free_undo, strands_undo_to_edit, strands_edit_to_undo, NULL);
+}
diff --git a/source/blender/editors/include/ED_datafiles.h b/source/blender/editors/include/ED_datafiles.h
index 661ab58b98c..5e2c6bb8c44 100644
--- a/source/blender/editors/include/ED_datafiles.h
+++ b/source/blender/editors/include/ED_datafiles.h
@@ -165,6 +165,27 @@ extern char datatoc_twist_png[];
extern int datatoc_vertexdraw_png_size;
extern char datatoc_vertexdraw_png[];
+extern int datatoc_haircomb_png_size;
+extern char datatoc_haircomb_png[];
+
+extern int datatoc_haircut_png_size;
+extern char datatoc_haircut_png[];
+
+extern int datatoc_hairlength_png_size;
+extern char datatoc_hairlength_png[];
+
+extern int datatoc_hairpuff_png_size;
+extern char datatoc_hairpuff_png[];
+
+extern int datatoc_hairadd_png_size;
+extern char datatoc_hairadd_png[];
+
+extern int datatoc_hairsmooth_png_size;
+extern char datatoc_hairsmooth_png[];
+
+extern int datatoc_hairweight_png_size;
+extern char datatoc_hairweight_png[];
+
/* Matcap files */
extern int datatoc_mc01_jpg_size;
diff --git a/source/blender/editors/include/ED_physics.h b/source/blender/editors/include/ED_physics.h
index fed842c969e..f805aff386d 100644
--- a/source/blender/editors/include/ED_physics.h
+++ b/source/blender/editors/include/ED_physics.h
@@ -35,9 +35,13 @@
struct bContext;
struct ReportList;
struct wmKeyConfig;
+struct ViewContext;
+struct rcti;
+struct Main;
struct Scene;
struct Object;
+struct BMEditStrands;
/* particle_edit.c */
int PE_poll(struct bContext *C);
@@ -56,5 +60,28 @@ void ED_rigidbody_constraint_remove(struct Main *bmain, struct Scene *scene, str
void ED_operatortypes_physics(void);
void ED_keymap_physics(struct wmKeyConfig *keyconf);
+/* hair edit */
+void undo_push_strands(struct bContext *C, const char *name);
+
+void ED_strands_mirror_cache_begin_ex(struct BMEditStrands *edit, const int axis,
+ const bool use_self, const bool use_select,
+ const bool use_topology, float maxdist, int *r_index);
+void ED_strands_mirror_cache_begin(struct BMEditStrands *edit, const int axis,
+ const bool use_self, const bool use_select,
+ const bool use_topology);
+void ED_strands_mirror_apply(struct BMEditStrands *edit, const int sel_from, const int sel_to);
+struct BMVert *ED_strands_mirror_get(struct BMEditStrands *edit, struct BMVert *v);
+struct BMEdge *ED_strands_mirror_get_edge(struct BMEditStrands *edit, struct BMEdge *e);
+void ED_strands_mirror_cache_clear(struct BMEditStrands *edit, struct BMVert *v);
+void ED_strands_mirror_cache_end(struct BMEditStrands *edit);
+
+int ED_hair_mouse_select(struct bContext *C, const int mval[2], bool extend, bool deselect, bool toggle);
+int ED_hair_border_select(struct bContext *C, struct rcti *rect, bool select, bool extend);
+int ED_hair_circle_select(struct bContext *C, bool select, const int mval[2], float radius);
+int ED_hair_lasso_select(struct bContext *C, const int mcoords[][2], short moves, bool extend, bool select);
+
+void ED_operatortypes_hair(void);
+void ED_keymap_hair(struct wmKeyConfig *keyconf);
+
#endif /* __ED_PHYSICS_H__ */
diff --git a/source/blender/editors/include/ED_util.h b/source/blender/editors/include/ED_util.h
index 60c4b3593aa..70075767c9a 100644
--- a/source/blender/editors/include/ED_util.h
+++ b/source/blender/editors/include/ED_util.h
@@ -32,6 +32,8 @@
#define __ED_UTIL_H__
struct bContext;
+struct PackedFile;
+struct ScrArea;
struct SpaceLink;
struct wmOperator;
struct wmOperatorType;
@@ -72,6 +74,7 @@ bool ED_undo_is_valid(const struct bContext *C, const char *undoname);
/* undo_editmode.c */
void undo_editmode_push(struct bContext *C, const char *name,
+ struct Object *(*get_object)(const struct bContext * C),
void * (*getdata)(struct bContext *C),
void (*freedata)(void *),
void (*to_editmode)(void *, void *, void *),
diff --git a/source/blender/editors/include/UI_icons.h b/source/blender/editors/include/UI_icons.h
index 0c83038b7a3..9d889f71051 100644
--- a/source/blender/editors/include/UI_icons.h
+++ b/source/blender/editors/include/UI_icons.h
@@ -977,6 +977,13 @@ DEF_ICON(BRUSH_TEXMASK)
DEF_ICON(BRUSH_THUMB)
DEF_ICON(BRUSH_ROTATE)
DEF_ICON(BRUSH_VERTEXDRAW)
+DEF_ICON(BRUSH_HAIR_COMB)
+DEF_ICON(BRUSH_HAIR_CUT)
+DEF_ICON(BRUSH_HAIR_LENGTH)
+DEF_ICON(BRUSH_HAIR_PUFF)
+DEF_ICON(BRUSH_HAIR_ADD)
+DEF_ICON(BRUSH_HAIR_SMOOTH)
+DEF_ICON(BRUSH_HAIR_WEIGHT)
/* Matcaps */
DEF_ICON(MATCAP_01)
diff --git a/source/blender/editors/interface/interface_icons.c b/source/blender/editors/interface/interface_icons.c
index ac07756b372..42b9e65d5ca 100644
--- a/source/blender/editors/interface/interface_icons.c
+++ b/source/blender/editors/interface/interface_icons.c
@@ -415,6 +415,13 @@ static void init_brush_icons(void)
INIT_BRUSH_ICON(ICON_BRUSH_THUMB, thumb);
INIT_BRUSH_ICON(ICON_BRUSH_ROTATE, twist);
INIT_BRUSH_ICON(ICON_BRUSH_VERTEXDRAW, vertexdraw);
+ INIT_BRUSH_ICON(ICON_BRUSH_HAIR_COMB, haircomb);
+ INIT_BRUSH_ICON(ICON_BRUSH_HAIR_CUT, haircut);
+ INIT_BRUSH_ICON(ICON_BRUSH_HAIR_LENGTH, hairlength);
+ INIT_BRUSH_ICON(ICON_BRUSH_HAIR_PUFF, hairpuff);
+ INIT_BRUSH_ICON(ICON_BRUSH_HAIR_ADD, hairadd);
+ INIT_BRUSH_ICON(ICON_BRUSH_HAIR_SMOOTH, hairsmooth);
+ INIT_BRUSH_ICON(ICON_BRUSH_HAIR_WEIGHT, hairweight);
#undef INIT_BRUSH_ICON
}
@@ -1209,6 +1216,8 @@ static int ui_id_brush_get_icon(const bContext *C, ID *id)
mode = OB_MODE_VERTEX_PAINT;
else if (ob->mode & OB_MODE_TEXTURE_PAINT)
mode = OB_MODE_TEXTURE_PAINT;
+ else if (ob->mode & OB_MODE_HAIR_EDIT)
+ mode = OB_MODE_HAIR_EDIT;
}
else if ((sima = CTX_wm_space_image(C)) &&
(sima->mode == SI_MODE_PAINT))
@@ -1229,6 +1238,10 @@ static int ui_id_brush_get_icon(const bContext *C, ID *id)
items = rna_enum_brush_image_tool_items;
tool = br->imagepaint_tool;
}
+ else if (mode == OB_MODE_HAIR_EDIT) {
+ items = brush_hair_tool_items;
+ tool = br->hair_tool;
+ }
if (!items || !RNA_enum_icon_from_value(items, tool, &id->icon_id))
id->icon_id = 0;
diff --git a/source/blender/editors/mesh/editmesh_undo.c b/source/blender/editors/mesh/editmesh_undo.c
index 534ca22178e..a35f70caef3 100644
--- a/source/blender/editors/mesh/editmesh_undo.c
+++ b/source/blender/editors/mesh/editmesh_undo.c
@@ -665,5 +665,5 @@ void undo_push_mesh(bContext *C, const char *name)
BMEditMesh *em = BKE_editmesh_from_object(obedit);
em->ob = obedit;
- undo_editmode_push(C, name, getEditMesh, free_undo, undoMesh_to_editbtMesh, editbtMesh_to_undoMesh, NULL);
+ undo_editmode_push(C, name, CTX_data_edit_object, getEditMesh, free_undo, undoMesh_to_editbtMesh, editbtMesh_to_undoMesh, NULL);
}
diff --git a/source/blender/editors/metaball/mball_edit.c b/source/blender/editors/metaball/mball_edit.c
index 47d354ac72a..d987808412a 100644
--- a/source/blender/editors/metaball/mball_edit.c
+++ b/source/blender/editors/metaball/mball_edit.c
@@ -752,5 +752,5 @@ static void *get_data(bContext *C)
/* this is undo system for MetaBalls */
void undo_push_mball(bContext *C, const char *name)
{
- undo_editmode_push(C, name, get_data, free_undoMball, undoMball_to_editMball, editMball_to_undoMball, NULL);
+ undo_editmode_push(C, name, CTX_data_edit_object, get_data, free_undoMball, undoMball_to_editMball, editMball_to_undoMball, NULL);
}
diff --git a/source/blender/editors/object/CMakeLists.txt b/source/blender/editors/object/CMakeLists.txt
index 8050508983b..69c897359e6 100644
--- a/source/blender/editors/object/CMakeLists.txt
+++ b/source/blender/editors/object/CMakeLists.txt
@@ -49,6 +49,7 @@ set(SRC
object_edit.c
object_facemap_ops.c
object_group.c
+ object_hair.c
object_hook.c
object_lattice.c
object_lod.c
diff --git a/source/blender/editors/object/object_edit.c b/source/blender/editors/object/object_edit.c
index 953dedab02d..850b772baed 100644
--- a/source/blender/editors/object/object_edit.c
+++ b/source/blender/editors/object/object_edit.c
@@ -1411,7 +1411,7 @@ static EnumPropertyItem *object_mode_set_itemsf(bContext *C, PointerRNA *UNUSED(
(input->value == OB_MODE_POSE && (ob->type == OB_ARMATURE)) ||
(input->value == OB_MODE_PARTICLE_EDIT && use_mode_particle_edit) ||
(ELEM(input->value, OB_MODE_SCULPT, OB_MODE_VERTEX_PAINT,
- OB_MODE_WEIGHT_PAINT, OB_MODE_TEXTURE_PAINT) && (ob->type == OB_MESH)) ||
+ OB_MODE_WEIGHT_PAINT, OB_MODE_TEXTURE_PAINT, OB_MODE_HAIR_EDIT) && (ob->type == OB_MESH)) ||
(input->value == OB_MODE_OBJECT))
{
RNA_enum_item_add(&item, &totitem, input);
@@ -1453,6 +1453,8 @@ static const char *object_mode_op_string(int mode)
return "PAINT_OT_texture_paint_toggle";
if (mode == OB_MODE_PARTICLE_EDIT)
return "PARTICLE_OT_particle_edit_toggle";
+ if (mode == OB_MODE_HAIR_EDIT)
+ return "HAIR_OT_hair_edit_toggle";
if (mode == OB_MODE_POSE)
return "OBJECT_OT_posemode_toggle";
if (mode == OB_MODE_GPENCIL)
@@ -1474,7 +1476,7 @@ static bool object_mode_compat_test(Object *ob, ObjectMode mode)
switch (ob->type) {
case OB_MESH:
if (mode & (OB_MODE_EDIT | OB_MODE_SCULPT | OB_MODE_VERTEX_PAINT | OB_MODE_WEIGHT_PAINT |
- OB_MODE_TEXTURE_PAINT | OB_MODE_PARTICLE_EDIT))
+ OB_MODE_TEXTURE_PAINT | OB_MODE_PARTICLE_EDIT | OB_MODE_HAIR_EDIT))
{
return true;
}
diff --git a/source/blender/editors/object/object_hair.c b/source/blender/editors/object/object_hair.c
new file mode 100644
index 00000000000..b5bcdbb5a3c
--- /dev/null
+++ b/source/blender/editors/object/object_hair.c
@@ -0,0 +1,117 @@
+/*
+ * ***** 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/object/object_hair.c
+ * \ingroup edobj
+ */
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_utildefines.h"
+
+#include "DNA_hair_types.h"
+#include "DNA_modifier_types.h"
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+
+#include "RNA_access.h"
+#include "RNA_define.h"
+
+#include "BKE_context.h"
+#include "BKE_DerivedMesh.h"
+#include "BKE_hair.h"
+#include "DEG_depsgraph.h"
+
+#include "ED_object.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "object_intern.h"
+
+/************************ Hair Follicle Generation Operator *********************/
+
+static int hair_follicles_generate_poll(bContext *C)
+{
+ return edit_modifier_poll_generic(C, &RNA_HairModifier, 0);
+}
+
+static int hair_follicles_generate_exec(bContext *C, wmOperator *op)
+{
+ Scene *scene = CTX_data_scene(C);
+ Object *ob = ED_object_active_context(C);
+ HairModifierData *hmd = (HairModifierData *)edit_modifier_property_get(op, ob, eModifierType_Hair);
+ EvaluationContext eval_ctx;
+ CTX_data_eval_ctx(C, &eval_ctx);
+
+ if (!hmd)
+ return OPERATOR_CANCELLED;
+
+ CustomDataMask mask = CD_MASK_BAREMESH;
+ DerivedMesh *scalp = mesh_get_derived_final(&eval_ctx, scene, ob, mask);
+ if (!scalp)
+ return OPERATOR_CANCELLED;
+
+ int count = RNA_int_get(op->ptr, "count");
+ unsigned int seed = RNA_int_get(op->ptr, "seed");
+
+ BKE_hair_follicles_generate(hmd->hair, scalp, count, seed);
+
+ DEG_id_tag_update(&ob->id, OB_RECALC_DATA);
+ WM_event_add_notifier(C, NC_OBJECT | ND_MODIFIER, ob);
+ return OPERATOR_FINISHED;
+}
+
+static int hair_follicles_generate_invoke(bContext *C, wmOperator *op, const wmEvent *event)
+{
+ if (edit_modifier_invoke_properties(C, op)) {
+ return WM_operator_props_popup_confirm(C, op, event);
+ }
+ else {
+ return OPERATOR_CANCELLED;
+ }
+}
+
+void OBJECT_OT_hair_follicles_generate(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Generate Hair Follicles";
+ ot->description = "Generate hair follicle data";
+ ot->idname = "OBJECT_OT_hair_follicles_generate";
+
+ /* api callbacks */
+ ot->poll = hair_follicles_generate_poll;
+ ot->invoke = hair_follicles_generate_invoke;
+ ot->exec = hair_follicles_generate_exec;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
+
+ edit_modifier_properties(ot);
+ RNA_def_int(ot->srna, "count", 1000, 0, INT_MAX, "Count", "Number of hair follicles to generate", 1, 1000000);
+ RNA_def_int(ot->srna, "seed", 0, 0, INT_MAX, "Seed", "Seed value for randomization", 0, INT_MAX);
+}
diff --git a/source/blender/editors/object/object_intern.h b/source/blender/editors/object/object_intern.h
index 3e655fa04a4..fe68adf1362 100644
--- a/source/blender/editors/object/object_intern.h
+++ b/source/blender/editors/object/object_intern.h
@@ -286,5 +286,8 @@ void TRANSFORM_OT_vertex_random(struct wmOperatorType *ot);
void OBJECT_OT_data_transfer(struct wmOperatorType *ot);
void OBJECT_OT_datalayout_transfer(struct wmOperatorType *ot);
+/* object_hair.c */
+void OBJECT_OT_hair_follicles_generate(struct wmOperatorType *ot);
+
#endif /* __OBJECT_INTERN_H__ */
diff --git a/source/blender/editors/object/object_lattice.c b/source/blender/editors/object/object_lattice.c
index 4eafe715a6f..edfc5b431b1 100644
--- a/source/blender/editors/object/object_lattice.c
+++ b/source/blender/editors/object/object_lattice.c
@@ -982,6 +982,6 @@ static void *get_editlatt(bContext *C)
/* and this is all the undo system needs to know */
void undo_push_lattice(bContext *C, const char *name)
{
- undo_editmode_push(C, name, get_editlatt, free_undoLatt, undoLatt_to_editLatt, editLatt_to_undoLatt, validate_undoLatt);
+ undo_editmode_push(C, name, CTX_data_edit_object, get_editlatt, free_undoLatt, undoLatt_to_editLatt, editLatt_to_undoLatt, validate_undoLatt);
}
diff --git a/source/blender/editors/object/object_ops.c b/source/blender/editors/object/object_ops.c
index 07922f2d3b2..66e54c1436b 100644
--- a/source/blender/editors/object/object_ops.c
+++ b/source/blender/editors/object/object_ops.c
@@ -260,6 +260,8 @@ 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_hair_follicles_generate);
}
void ED_operatormacros_object(void)
diff --git a/source/blender/editors/screen/screen_context.c b/source/blender/editors/screen/screen_context.c
index aec32ff2dff..d631401ab4d 100644
--- a/source/blender/editors/screen/screen_context.c
+++ b/source/blender/editors/screen/screen_context.c
@@ -72,7 +72,7 @@ const char *screen_context_dir[] = {
"visible_pose_bones", "selected_pose_bones", "active_bone", "active_pose_bone",
"active_base", "active_object", "object", "edit_object",
"sculpt_object", "vertex_paint_object", "weight_paint_object",
- "image_paint_object", "particle_edit_object",
+ "image_paint_object", "particle_edit_object", "hair_edit_object",
"sequences", "selected_sequences", "selected_editable_sequences", /* sequencer */
"gpencil_data", "gpencil_data_owner", /* grease pencil data */
"visible_gpencil_layers", "editable_gpencil_layers", "editable_gpencil_strokes",
@@ -399,6 +399,12 @@ int ed_screen_context(const bContext *C, const char *member, bContextDataResult
return 1;
}
+ else if (CTX_data_equals(member, "hair_edit_object")) {
+ if (obact && (obact->mode & OB_MODE_HAIR_EDIT))
+ CTX_data_id_pointer_set(result, &obact->id);
+
+ return 1;
+ }
else if (CTX_data_equals(member, "sequences")) {
Editing *ed = BKE_sequencer_editing_get(scene, false);
if (ed) {
diff --git a/source/blender/editors/sculpt_paint/paint_ops.c b/source/blender/editors/sculpt_paint/paint_ops.c
index 123a70d5044..9d571fc12fe 100644
--- a/source/blender/editors/sculpt_paint/paint_ops.c
+++ b/source/blender/editors/sculpt_paint/paint_ops.c
@@ -44,6 +44,7 @@
#include "ED_screen.h"
#include "ED_image.h"
#include "UI_resources.h"
+#include "UI_interface.h"
#include "WM_api.h"
#include "WM_types.h"
@@ -60,20 +61,40 @@
#include <stddef.h>
/* Brush operators */
+
static int brush_add_exec(bContext *C, wmOperator *UNUSED(op))
{
/*int type = RNA_enum_get(op->ptr, "type");*/
- Paint *paint = BKE_paint_get_active_from_context(C);
- Brush *br = BKE_paint_brush(paint);
Main *bmain = CTX_data_main(C);
PaintMode mode = BKE_paintmode_get_active_from_context(C);
-
+ Scene *scene = CTX_data_scene(C);
+ Object *ob = CTX_data_active_object(C);
+ Paint *paint = NULL;
+ HairEditSettings *hair_edit = NULL;
+ Brush *br = NULL;
+
+ /* get active brush context */
+ if (ob->mode == OB_MODE_HAIR_EDIT) {
+ hair_edit = &scene->toolsettings->hair_edit;
+ br = hair_edit->brush;
+ }
+ else {
+ paint = BKE_paint_get_active_from_context(C);
+ br = BKE_paint_brush(paint);
+ }
+
if (br)
br = BKE_brush_copy(bmain, br);
else
br = BKE_brush_add(bmain, "Brush", BKE_paint_object_mode_from_paint_mode(mode));
- BKE_paint_brush_set(paint, br);
+ /* set new brush pointer in the context */
+ if (ob->mode == OB_MODE_HAIR_EDIT) {
+ hair_edit->brush = br;
+ }
+ else {
+ BKE_paint_brush_set(paint, br);
+ }
return OPERATOR_FINISHED;
}
@@ -96,11 +117,20 @@ static void BRUSH_OT_add(wmOperatorType *ot)
static int brush_scale_size_exec(bContext *C, wmOperator *op)
{
Scene *scene = CTX_data_scene(C);
- Paint *paint = BKE_paint_get_active_from_context(C);
- Brush *brush = BKE_paint_brush(paint);
- // Object *ob = CTX_data_active_object(C);
+ Object *ob = CTX_data_active_object(C);
+ Brush *brush;
float scalar = RNA_float_get(op->ptr, "scalar");
+ /* get active brush context */
+ if (ob->mode == OB_MODE_HAIR_EDIT) {
+ HairEditSettings *hair_edit = &scene->toolsettings->hair_edit;
+ brush = hair_edit->brush;
+ }
+ else {
+ Paint *paint = BKE_paint_get_active_from_context(C);
+ brush = BKE_paint_brush(paint);
+ }
+
if (brush) {
// pixel radius
{
@@ -699,7 +729,7 @@ static int brush_select_exec(bContext *C, wmOperator *op)
Object *ob = CTX_data_active_object(C);
if (ob) {
/* select current paint mode */
- paint_mode = ob->mode & OB_MODE_ALL_PAINT;
+ paint_mode = ob->mode & OB_MODE_ALL_BRUSH;
}
else {
return OPERATOR_CANCELLED;
diff --git a/source/blender/editors/space_api/spacetypes.c b/source/blender/editors/space_api/spacetypes.c
index e64e9841139..43543428a21 100644
--- a/source/blender/editors/space_api/spacetypes.c
+++ b/source/blender/editors/space_api/spacetypes.c
@@ -107,6 +107,7 @@ void ED_spacetypes_init(void)
ED_operatortypes_anim();
ED_operatortypes_animchannels();
ED_operatortypes_gpencil();
+ ED_operatortypes_hair();
ED_operatortypes_object();
ED_operatortypes_mesh();
ED_operatortypes_sculpt();
@@ -193,6 +194,7 @@ void ED_spacetypes_keymap(wmKeyConfig *keyconf)
ED_keymap_anim(keyconf);
ED_keymap_animchannels(keyconf);
ED_keymap_gpencil(keyconf);
+ ED_keymap_hair(keyconf);
ED_keymap_object(keyconf); /* defines lattice also */
ED_keymap_mesh(keyconf);
ED_keymap_uvedit(keyconf);
diff --git a/source/blender/editors/space_outliner/outliner_draw.c b/source/blender/editors/space_outliner/outliner_draw.c
index e3baf44bf9c..6b8b3466fe8 100644
--- a/source/blender/editors/space_outliner/outliner_draw.c
+++ b/source/blender/editors/space_outliner/outliner_draw.c
@@ -1017,6 +1017,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_Hair:
+ ICON_DRAW(ICON_STRANDS);
+ break;
/* Default */
case eModifierType_None:
case eModifierType_ShapeKey:
diff --git a/source/blender/editors/space_view3d/CMakeLists.txt b/source/blender/editors/space_view3d/CMakeLists.txt
index db79d578b5d..6bf0d142aa4 100644
--- a/source/blender/editors/space_view3d/CMakeLists.txt
+++ b/source/blender/editors/space_view3d/CMakeLists.txt
@@ -48,6 +48,7 @@ set(SRC
drawmesh.c
drawobject.c
drawsimdebug.c
+ drawstrands.c
drawvolume.c
space_view3d.c
view3d_buttons.c
diff --git a/source/blender/editors/space_view3d/drawobject.c b/source/blender/editors/space_view3d/drawobject.c
index ce206e9de0c..4543172feb4 100644
--- a/source/blender/editors/space_view3d/drawobject.c
+++ b/source/blender/editors/space_view3d/drawobject.c
@@ -59,6 +59,7 @@
#include "BKE_DerivedMesh.h"
#include "BKE_deform.h"
#include "BKE_displist.h"
+#include "BKE_editstrands.h"
#include "BKE_font.h"
#include "BKE_global.h"
#include "BKE_image.h"
@@ -4228,7 +4229,7 @@ static void draw_em_fancy_new(Scene *UNUSED(scene), ARegion *UNUSED(ar), View3D
void draw_mesh_object_outline(View3D *v3d, Object *ob, DerivedMesh *dm, const unsigned char ob_wire_col[4]) /* LEGACY */
{
if ((v3d->transp == false) && /* not when we draw the transparent pass */
- (ob->mode & OB_MODE_ALL_PAINT) == false) /* not when painting (its distracting) - campbell */
+ (ob->mode & OB_MODE_ALL_BRUSH) == false) /* not when painting (its distracting) - campbell */
{
glLineWidth(UI_GetThemeValuef(TH_OUTLINE_WIDTH) * 2.0f);
glDepthMask(GL_FALSE);
@@ -8749,7 +8750,7 @@ void draw_object(
if (!skip_object) {
/* draw outline for selected objects, mesh does itself */
if ((v3d->flag & V3D_SELECT_OUTLINE) && !render_override && ob->type != OB_MESH) {
- if (dt > OB_WIRE && (ob->mode & OB_MODE_EDIT) == 0 && (dflag & DRAW_SCENESET) == 0) {
+ if (dt > OB_WIRE && (ob->mode & (OB_MODE_EDIT | OB_MODE_HAIR_EDIT)) == 0 && (dflag & DRAW_SCENESET) == 0) {
if (!(ob->dtx & OB_DRAWWIRE) && (base->flag & BASE_SELECTED) && !(dflag & (DRAW_PICKING | DRAW_CONSTCOLOR))) {
draw_object_selected_outline(eval_ctx, scene, sl, v3d, ar, base, ob_wire_col);
}
@@ -8940,14 +8941,16 @@ afterdraw:
view3d_cached_text_draw_begin();
for (psys = ob->particlesystem.first; psys; psys = psys->next) {
- /* run this so that possible child particles get cached */
- if (ob->mode & OB_MODE_PARTICLE_EDIT && is_obact) {
- PTCacheEdit *edit = PE_create_current(eval_ctx, scene, ob);
- if (edit && edit->psys == psys)
- draw_update_ptcache_edit(eval_ctx, scene, sl, ob, edit);
+ if (!(ob->mode & OB_MODE_HAIR_EDIT)) {
+ /* run this so that possible child particles get cached */
+ if (ob->mode & OB_MODE_PARTICLE_EDIT && is_obact) {
+ PTCacheEdit *edit = PE_create_current(eval_ctx, scene, ob);
+ if (edit && edit->psys == psys)
+ draw_update_ptcache_edit(eval_ctx, scene, sl, ob, edit);
+ }
+
+ draw_new_particle_system(eval_ctx, scene, v3d, rv3d, base, psys, dt, dflag);
}
-
- draw_new_particle_system(eval_ctx, scene, v3d, rv3d, base, psys, dt, dflag);
}
invert_m4_m4(ob->imat, ob->obmat);
view3d_cached_text_draw_end(v3d, ar, 0);
@@ -8971,6 +8974,13 @@ afterdraw:
gpuMultMatrix(ob->obmat);
}
}
+
+ if (ob->mode & OB_MODE_HAIR_EDIT && is_obact) {
+ BMEditStrands *edit = BKE_editstrands_from_object(ob);
+ if (edit) {
+ draw_strands_edit_hair(scene, v3d, ar, edit);
+ }
+ }
}
/* draw code for smoke, only draw domains */
@@ -9159,7 +9169,7 @@ afterdraw:
}
/* object centers, need to be drawn in viewmat space for speed, but OK for picking select */
- if (!is_obact || !(ob->mode & OB_MODE_ALL_PAINT)) {
+ if (!is_obact || !(ob->mode & OB_MODE_ALL_BRUSH)) {
int do_draw_center = -1; /* defines below are zero or positive... */
if (render_override) {
diff --git a/source/blender/editors/space_view3d/drawstrands.c b/source/blender/editors/space_view3d/drawstrands.c
new file mode 100644
index 00000000000..dd37e4144f5
--- /dev/null
+++ b/source/blender/editors/space_view3d/drawstrands.c
@@ -0,0 +1,382 @@
+/*
+ * ***** 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 *****
+ */
+
+/** \file blender/editors/space_view3d/drawstrands.c
+ * \ingroup spview3d
+ */
+
+#include "BLI_utildefines.h"
+#include "BLI_math.h"
+
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+#include "DNA_view3d_types.h"
+
+#include "BKE_editstrands.h"
+#include "BKE_main.h"
+
+#include "bmesh.h"
+
+#include "ED_screen.h"
+#include "ED_types.h"
+
+#include "UI_resources.h"
+#include "UI_interface_icons.h"
+
+#include "BIF_gl.h"
+#include "BIF_glutil.h"
+
+#include "GPU_draw.h"
+#include "GPU_extensions.h"
+#include "GPU_select.h"
+
+#include "view3d_intern.h"
+
+typedef enum StrandsShadeMode {
+ STRANDS_SHADE_FLAT,
+ STRANDS_SHADE_HAIR,
+} StrandsShadeMode;
+
+typedef struct StrandsDrawInfo {
+ bool has_zbuf;
+ bool use_zbuf_select;
+
+ StrandsShadeMode shade_mode;
+ int select_mode;
+
+ float col_base[4];
+ float col_select[4];
+} StrandsDrawInfo;
+
+BLI_INLINE bool strands_use_normals(const StrandsDrawInfo *info)
+{
+ return ELEM(info->shade_mode, STRANDS_SHADE_HAIR);
+}
+
+static void init_draw_info(StrandsDrawInfo *info, View3D *v3d,
+ StrandsShadeMode shade_mode, int select_mode)
+{
+ info->has_zbuf = v3d->zbuf;
+ info->use_zbuf_select = (v3d->flag & V3D_ZBUF_SELECT);
+
+ info->shade_mode = shade_mode;
+ info->select_mode = select_mode;
+
+ /* get selection theme colors */
+ UI_GetThemeColor4fv(TH_VERTEX, info->col_base);
+ UI_GetThemeColor4fv(TH_VERTEX_SELECT, info->col_select);
+}
+
+static void set_opengl_state_strands(const StrandsDrawInfo *info)
+{
+ if (!info->use_zbuf_select)
+ glDisable(GL_DEPTH_TEST);
+ glEnable(GL_BLEND);
+
+ if (ELEM(info->shade_mode, STRANDS_SHADE_HAIR)) {
+ glEnable(GL_LIGHTING);
+ glColorMaterial(GL_FRONT_AND_BACK, GL_DIFFUSE);
+ glEnable(GL_COLOR_MATERIAL);
+ glShadeModel(GL_SMOOTH);
+ }
+ else {
+ glDisable(GL_LIGHTING);
+ }
+
+ glEnableClientState(GL_VERTEX_ARRAY);
+ if (strands_use_normals(info))
+ glEnableClientState(GL_NORMAL_ARRAY);
+}
+
+static void set_opengl_state_dots(const StrandsDrawInfo *info)
+{
+ if (!info->use_zbuf_select)
+ glDisable(GL_DEPTH_TEST);
+ glEnable(GL_BLEND);
+
+ glDisable(GL_LIGHTING);
+
+ glEnableClientState(GL_VERTEX_ARRAY);
+ glPointSize(3.0);
+}
+
+static void restore_opengl_state(const StrandsDrawInfo *info)
+{
+ glDisableClientState(GL_NORMAL_ARRAY);
+ glDisableClientState(GL_VERTEX_ARRAY);
+
+ glDisable(GL_BLEND);
+ glDisable(GL_LIGHTING);
+ glDisable(GL_COLOR_MATERIAL);
+ glShadeModel(GL_FLAT);
+ if (info->has_zbuf)
+ glEnable(GL_DEPTH_TEST);
+ glLineWidth(1.0f);
+ glPointSize(1.0);
+}
+
+/* ------------------------------------------------------------------------- */
+/* strands */
+
+static void setup_gpu_buffers_strands(BMEditStrands *edit, const StrandsDrawInfo *info)
+{
+ const size_t size_v3 = sizeof(float) * 3;
+ const size_t size_vertex = (strands_use_normals(info) ? 2*size_v3 : size_v3);
+ BMesh *bm = edit->base.bm;
+
+// int totstrands = BM_strands_count(edit->bm);
+ int totvert = bm->totvert;
+ int totedge = bm->totedge;
+
+ if (!edit->vertex_glbuf)
+ glGenBuffers(1, &edit->vertex_glbuf);
+ if (!edit->elem_glbuf)
+ glGenBuffers(1, &edit->elem_glbuf);
+
+ glBindBuffer(GL_ARRAY_BUFFER, edit->vertex_glbuf);
+ glBufferData(GL_ARRAY_BUFFER, size_vertex * totvert, NULL, GL_DYNAMIC_DRAW);
+
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, edit->elem_glbuf);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned int) * totedge * 2, NULL, GL_DYNAMIC_DRAW);
+
+ glVertexPointer(3, GL_FLOAT, size_vertex, NULL);
+ if (strands_use_normals(info))
+ glNormalPointer(GL_FLOAT, size_vertex, (GLubyte *)NULL + size_v3);
+}
+
+static void unbind_gpu_buffers_strands(void)
+{
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+}
+
+static int write_gpu_buffers_strands(BMEditStrands *edit, const StrandsDrawInfo *info)
+{
+ const size_t size_v3 = sizeof(float) * 3;
+ const size_t size_vertex = (strands_use_normals(info) ? 2*size_v3 : size_v3);
+ BMesh *bm = edit->base.bm;
+
+ GLubyte *vertex_data;
+ unsigned int *elem_data;
+ BMVert *root, *v, *vprev;
+ BMIter iter, iter_strand;
+ int index, indexprev, index_edge;
+ int k;
+
+ vertex_data = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
+ elem_data = glMapBuffer(GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY);
+ if (!vertex_data || !elem_data)
+ return 0;
+
+ BM_mesh_elem_index_ensure(bm, BM_VERT);
+
+ index_edge = 0;
+ BM_ITER_STRANDS(root, &iter, bm, BM_STRANDS_OF_MESH) {
+ BM_ITER_STRANDS_ELEM_INDEX(v, &iter_strand, root, BM_VERTS_OF_STRAND, k) {
+ size_t offset_co;
+
+ index = BM_elem_index_get(v);
+
+ offset_co = index * size_vertex;
+ copy_v3_v3((float *)(vertex_data + offset_co), v->co);
+
+ if (k > 0) {
+ if (strands_use_normals(info)) {
+ size_t offset_nor = offset_co + size_v3;
+ float nor[3];
+ sub_v3_v3v3(nor, v->co, vprev->co);
+ normalize_v3(nor);
+ copy_v3_v3((float *)(vertex_data + offset_nor), nor);
+
+ if (k == 1) {
+ /* define root normal: same as first segment */
+ size_t offset_root_nor = indexprev * size_vertex + size_v3;
+ copy_v3_v3((float *)(vertex_data + offset_root_nor), nor);
+ }
+ }
+
+ {
+ elem_data[index_edge + 0] = indexprev;
+ elem_data[index_edge + 1] = index;
+ index_edge += 2;
+ }
+ }
+
+ vprev = v;
+ indexprev = index;
+ }
+ }
+
+ glUnmapBuffer(GL_ARRAY_BUFFER);
+ glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER);
+
+ return index_edge;
+}
+
+/* ------------------------------------------------------------------------- */
+/* dots */
+
+static void setup_gpu_buffers_dots(BMEditStrands *edit, const StrandsDrawInfo *info, bool selected)
+{
+ const size_t size_v3 = sizeof(float) * 3;
+ const size_t size_vertex = size_v3;
+ BMesh *bm = edit->base.bm;
+
+ BMVert *v;
+ BMIter iter;
+ int totvert;
+
+ switch (info->select_mode) {
+ case HAIR_SELECT_STRAND:
+ totvert = 0;
+ break;
+ case HAIR_SELECT_VERTEX:
+ totvert = selected ? bm->totvertsel : bm->totvert - bm->totvertsel;
+ break;
+ case HAIR_SELECT_TIP:
+ totvert = 0;
+ BM_ITER_MESH(v, &iter, bm, BM_VERTS_OF_MESH) {
+ if (BM_elem_flag_test_bool(v, BM_ELEM_SELECT) != selected)
+ continue;
+ if (!BM_strands_vert_is_tip(v))
+ continue;
+
+ ++totvert;
+ }
+ break;
+ }
+
+ if (totvert == 0)
+ return;
+
+ if (!edit->dot_glbuf)
+ glGenBuffers(1, &edit->dot_glbuf);
+
+ glBindBuffer(GL_ARRAY_BUFFER, edit->dot_glbuf);
+ glBufferData(GL_ARRAY_BUFFER, size_vertex * totvert, NULL, GL_DYNAMIC_DRAW);
+
+ glVertexPointer(3, GL_FLOAT, size_vertex, NULL);
+}
+
+static void unbind_gpu_buffers_dots(void)
+{
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+}
+
+static int write_gpu_buffers_dots(BMEditStrands *edit, const StrandsDrawInfo *info, bool selected)
+{
+ const size_t size_v3 = sizeof(float) * 3;
+ const size_t size_vertex = size_v3;
+ BMesh *bm = edit->base.bm;
+
+ GLubyte *vertex_data;
+ BMVert *v;
+ BMIter iter;
+ int index_dot;
+
+ if (info->select_mode == HAIR_SELECT_STRAND)
+ return 0;
+
+ vertex_data = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
+ if (!vertex_data)
+ return 0;
+
+ BM_mesh_elem_index_ensure(bm, BM_VERT);
+
+ index_dot = 0;
+ switch (info->select_mode) {
+ case HAIR_SELECT_STRAND:
+ /* already exited, but keep the case for the compiler */
+ break;
+ case HAIR_SELECT_VERTEX:
+ BM_ITER_MESH(v, &iter, bm, BM_VERTS_OF_MESH) {
+ size_t offset_co;
+
+ if (BM_elem_flag_test_bool(v, BM_ELEM_SELECT) != selected)
+ continue;
+
+ offset_co = index_dot * size_vertex;
+ copy_v3_v3((float *)(vertex_data + offset_co), v->co);
+ ++index_dot;
+ }
+ break;
+ case HAIR_SELECT_TIP:
+ BM_ITER_MESH(v, &iter, bm, BM_VERTS_OF_MESH) {
+ size_t offset_co;
+
+ if (BM_elem_flag_test_bool(v, BM_ELEM_SELECT) != selected)
+ continue;
+ if (!BM_strands_vert_is_tip(v))
+ continue;
+
+ offset_co = index_dot * size_vertex;
+ copy_v3_v3((float *)(vertex_data + offset_co), v->co);
+ ++index_dot;
+ }
+ break;
+ }
+
+ glUnmapBuffer(GL_ARRAY_BUFFER);
+
+ return index_dot;
+}
+
+/* ------------------------------------------------------------------------- */
+
+static void draw_dots(BMEditStrands *edit, const StrandsDrawInfo *info, bool selected)
+{
+ int totelem;
+
+ if (selected)
+ glColor3fv(info->col_select);
+ else
+ glColor3fv(info->col_base);
+
+ setup_gpu_buffers_dots(edit, info, selected);
+ totelem = write_gpu_buffers_dots(edit, info, selected);
+ if (totelem > 0)
+ glDrawArrays(GL_POINTS, 0, totelem);
+}
+
+void draw_strands_edit_hair(Scene *scene, View3D *v3d, ARegion *UNUSED(ar), BMEditStrands *edit)
+{
+ HairEditSettings *settings = &scene->toolsettings->hair_edit;
+
+ StrandsDrawInfo info;
+ int totelem;
+
+ init_draw_info(&info, v3d, STRANDS_SHADE_HAIR, settings->select_mode);
+
+ set_opengl_state_strands(&info);
+ setup_gpu_buffers_strands(edit, &info);
+ totelem = write_gpu_buffers_strands(edit, &info);
+ if (totelem > 0)
+ glDrawElements(GL_LINES, totelem, GL_UNSIGNED_INT, NULL);
+ unbind_gpu_buffers_strands();
+
+ set_opengl_state_dots(&info);
+ draw_dots(edit, &info, false);
+ draw_dots(edit, &info, true);
+ unbind_gpu_buffers_dots();
+
+ restore_opengl_state(&info);
+}
diff --git a/source/blender/editors/space_view3d/space_view3d.c b/source/blender/editors/space_view3d/space_view3d.c
index a6636f9ffb3..2dd0e101ea9 100644
--- a/source/blender/editors/space_view3d/space_view3d.c
+++ b/source/blender/editors/space_view3d/space_view3d.c
@@ -554,6 +554,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, "Hair", 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_header.c b/source/blender/editors/space_view3d/view3d_header.c
index b6deabdb447..82ab57e44aa 100644
--- a/source/blender/editors/space_view3d/view3d_header.c
+++ b/source/blender/editors/space_view3d/view3d_header.c
@@ -309,7 +309,7 @@ void uiTemplateHeader3D(uiLayout *layout, struct bContext *C)
if (obedit == NULL && is_paint) {
/* Manipulators aren't used in paint modes */
- if (!ELEM(ob->mode, OB_MODE_SCULPT, OB_MODE_PARTICLE_EDIT)) {
+ if (!ELEM(ob->mode, OB_MODE_SCULPT, OB_MODE_PARTICLE_EDIT, OB_MODE_HAIR_EDIT)) {
/* masks aren't used for sculpt and particle painting */
PointerRNA meshptr;
diff --git a/source/blender/editors/space_view3d/view3d_intern.h b/source/blender/editors/space_view3d/view3d_intern.h
index cdba5ce8b81..e39bbec70e3 100644
--- a/source/blender/editors/space_view3d/view3d_intern.h
+++ b/source/blender/editors/space_view3d/view3d_intern.h
@@ -48,6 +48,8 @@ struct bMotionPath;
struct bPoseChannel;
struct Mesh;
struct SceneLayer;
+struct BMEditStrands;
+
struct wmOperatorType;
struct wmWindowManager;
struct wmKeyConfig;
@@ -212,6 +214,9 @@ void draw_mesh_paint(View3D *v3d, RegionView3D *rv3d,
/* drawsimdebug.c */
void draw_sim_debug_data(Scene *scene, View3D *v3d, ARegion *ar);
+/* drawstrands.c */
+void draw_strands_edit_hair(Scene *scene, View3D *v3d, ARegion *ar, struct BMEditStrands *edit);
+
/* view3d_draw.c */
void view3d_main_region_draw(const struct bContext *C, struct ARegion *ar);
void view3d_draw_region_info(const struct bContext *C, struct ARegion *ar, const int offset);
diff --git a/source/blender/editors/space_view3d/view3d_select.c b/source/blender/editors/space_view3d/view3d_select.c
index c02925078a6..2fa32c44fc7 100644
--- a/source/blender/editors/space_view3d/view3d_select.c
+++ b/source/blender/editors/space_view3d/view3d_select.c
@@ -84,9 +84,11 @@
#include "ED_armature.h"
#include "ED_curve.h"
+#include "ED_physics.h"
#include "ED_particle.h"
#include "ED_mesh.h"
#include "ED_object.h"
+#include "ED_physics.h"
#include "ED_screen.h"
#include "ED_sculpt.h"
#include "ED_mball.h"
@@ -821,6 +823,9 @@ static void view3d_lasso_select(bContext *C, ViewContext *vc,
else if (ob && (ob->mode & OB_MODE_PARTICLE_EDIT)) {
PE_lasso_select(C, mcords, moves, extend, select);
}
+ else if (ob && (ob->mode & OB_MODE_HAIR_EDIT)) {
+ ED_hair_lasso_select(C, mcords, moves, extend, select);
+ }
else {
do_lasso_select_objects(vc, mcords, moves, extend, select);
WM_event_add_notifier(C, NC_SCENE | ND_OB_SELECT, vc->scene);
@@ -2217,6 +2222,9 @@ static int view3d_borderselect_exec(bContext *C, wmOperator *op)
else if (vc.obact && vc.obact->mode & OB_MODE_PARTICLE_EDIT) {
ret = PE_border_select(C, &rect, select, extend);
}
+ else if (vc.obact && vc.obact->mode & OB_MODE_HAIR_EDIT) {
+ ret = ED_hair_border_select(C, &rect, select, extend);
+ }
else { /* object mode with none active */
ret = do_object_pose_box_select(C, &vc, &rect, select, extend);
}
@@ -2349,6 +2357,8 @@ static int view3d_select_exec(bContext *C, wmOperator *op)
}
else if (obact && obact->mode & OB_MODE_PARTICLE_EDIT)
return PE_mouse_particles(C, location, extend, deselect, toggle);
+ else if (obact && obact->mode & OB_MODE_HAIR_EDIT)
+ return ED_hair_mouse_select(C, location, extend, deselect, toggle);
else if (obact && BKE_paint_select_face_test(obact))
retval = paintface_mouse_select(C, obact, location, extend, deselect, toggle);
else if (BKE_paint_select_vert_test(obact))
@@ -2865,7 +2875,7 @@ static int view3d_circle_select_exec(bContext *C, wmOperator *op)
RNA_int_get(op->ptr, "y")};
if (CTX_data_edit_object(C) || BKE_paint_select_elem_test(obact) ||
- (obact && (obact->mode & (OB_MODE_PARTICLE_EDIT | OB_MODE_POSE))) )
+ (obact && (obact->mode & (OB_MODE_PARTICLE_EDIT | OB_MODE_POSE | OB_MODE_HAIR_EDIT))) )
{
EvaluationContext eval_ctx;
ViewContext vc;
@@ -2887,10 +2897,15 @@ static int view3d_circle_select_exec(bContext *C, wmOperator *op)
paint_vertsel_circle_select(&eval_ctx, &vc, select, mval, (float)radius);
WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obact->data);
}
- else if (obact->mode & OB_MODE_POSE)
+ else if (obact->mode & OB_MODE_POSE) {
pose_circle_select(&vc, select, mval, (float)radius);
- else
+ }
+ else if (obact->mode & OB_MODE_HAIR_EDIT) {
+ ED_hair_circle_select(C, select, mval, (float)radius);
+ }
+ else {
return PE_circle_select(C, select, mval, (float)radius);
+ }
}
else if (obact && obact->mode & OB_MODE_SCULPT) {
return OPERATOR_CANCELLED;
diff --git a/source/blender/editors/transform/transform.h b/source/blender/editors/transform/transform.h
index d17a0672ddf..052f05fa1e1 100644
--- a/source/blender/editors/transform/transform.h
+++ b/source/blender/editors/transform/transform.h
@@ -627,6 +627,7 @@ void flushTransGraphData(TransInfo *t);
void remake_graph_transdata(TransInfo *t, struct ListBase *anim_data);
void flushTransUVs(TransInfo *t);
void flushTransParticles(TransInfo *t);
+void flushTransStrands(TransInfo *t);
bool clipUVTransform(TransInfo *t, float vec[2], const bool resize);
void clipUVData(TransInfo *t);
void flushTransNodes(TransInfo *t);
diff --git a/source/blender/editors/transform/transform_conversions.c b/source/blender/editors/transform/transform_conversions.c
index 7f33856358b..3b85c254698 100644
--- a/source/blender/editors/transform/transform_conversions.c
+++ b/source/blender/editors/transform/transform_conversions.c
@@ -68,6 +68,7 @@
#include "BKE_context.h"
#include "BKE_crazyspace.h"
#include "BKE_curve.h"
+#include "BKE_editstrands.h"
#include "BKE_fcurve.h"
#include "BKE_global.h"
#include "BKE_gpencil.h"
@@ -2056,6 +2057,226 @@ void flushTransParticles(TransInfo *t)
PE_update_object(&eval_ctx, scene, sl, OBACT_NEW(sl), 1);
}
+
+/* ******************* hair edit **************** */
+
+static void editmesh_set_connectivity_distance(BMesh *bm, float mtx[3][3], float *dists, int *index);
+static struct TransIslandData *bmesh_islands_info_calc(
+ BMesh *bm, short selectmode, int *r_island_tot, int **r_island_vert_map,
+ bool calc_single_islands);
+
+static void StrandVertsToTransData(TransInfo *t, TransData *td,
+ BMEditStrands *UNUSED(edit), BMVert *eve, const float nor[3], const float tang[3],
+ struct TransIslandData *v_island)
+{
+ BLI_assert(BM_elem_flag_test(eve, BM_ELEM_HIDDEN) == 0);
+
+ td->flag = 0;
+ td->loc = eve->co;
+ copy_v3_v3(td->iloc, td->loc);
+
+ if (v_island) {
+ copy_v3_v3(td->center, v_island->co);
+ copy_m3_m3(td->axismtx, v_island->axismtx);
+ }
+ else if (t->around == V3D_AROUND_LOCAL_ORIGINS) {
+ copy_v3_v3(td->center, td->loc);
+ createSpaceNormalTangent(td->axismtx, nor, tang);
+ }
+ else {
+ copy_v3_v3(td->center, td->loc);
+
+ /* Setting normals */
+ copy_v3_v3(td->axismtx[2], nor);
+ td->axismtx[0][0] =
+ td->axismtx[0][1] =
+ td->axismtx[0][2] =
+ td->axismtx[1][0] =
+ td->axismtx[1][1] =
+ td->axismtx[1][2] = 0.0f;
+ }
+
+ td->ext = NULL;
+ td->val = NULL;
+ td->extra = NULL;
+}
+
+static void createTransStrandVerts(TransInfo *t)
+{
+ SceneLayer *sl = t->scene_layer;
+ Object *ob = OBACT_NEW(sl);
+ BMEditStrands *edit = BKE_editstrands_from_object(ob);
+ BMesh *bm = edit->base.bm;
+ TransData *tob = NULL;
+ BMVert *eve;
+ BMIter iter;
+ float mtx[3][3], smtx[3][3];
+ float *dists = NULL;
+ int a;
+ int prop_mode = (t->flag & T_PROP_EDIT) ? (t->flag & T_PROP_EDIT_ALL) : 0;
+ int mirror = 0;
+
+ struct TransIslandData *island_info = NULL;
+ int island_info_tot;
+ int *island_vert_map = NULL;
+
+ /* Even for translation this is needed because of island-orientation, see: T51651. */
+ const bool is_island_center = (t->around == V3D_AROUND_LOCAL_ORIGINS);
+ /* Original index of our connected vertex when connected distances are calculated.
+ * Optional, allocate if needed. */
+ int *dists_index = NULL;
+
+ if (t->flag & T_MIRROR) {
+#if 0 // TODO mirror relies on EDBM functions which don't have an equivalent for editstrands yet
+ EDBM_verts_mirror_cache_begin(em, 0, false, (t->flag & T_PROP_EDIT) == 0, false);
+ mirror = 1;
+#endif
+ }
+
+ /* quick check if we can transform */
+ /* note: in prop mode we need at least 1 selected */
+ if (bm->totvertsel == 0) {
+ goto cleanup;
+ }
+
+ if (prop_mode) {
+ unsigned int count = 0;
+ BM_ITER_MESH (eve, &iter, bm, BM_VERTS_OF_MESH) {
+ if (!BM_elem_flag_test(eve, BM_ELEM_HIDDEN)) {
+ count++;
+ }
+ }
+
+ t->total = count;
+
+ /* allocating scratch arrays */
+ if (prop_mode & T_PROP_CONNECTED) {
+ dists = MEM_mallocN(bm->totvert * sizeof(float), "scratch nears");
+ if (is_island_center) {
+ dists_index = MEM_mallocN(bm->totvert * sizeof(int), __func__);
+ }
+ }
+ }
+ else {
+ t->total = bm->totvertsel;
+ }
+
+ tob = t->data = MEM_callocN(t->total * sizeof(TransData), "TransObData(Strands EditMode)");
+
+ copy_m3_m4(mtx, ob->obmat);
+ /* we use a pseudoinverse so that when one of the axes is scaled to 0,
+ * matrix inversion still works and we can still moving along the other */
+ pseudoinverse_m3_m3(smtx, mtx, PSEUDOINVERSE_EPSILON);
+
+ if (prop_mode & T_PROP_CONNECTED) {
+ editmesh_set_connectivity_distance(bm, mtx, dists, dists_index);
+ }
+
+ if (is_island_center) {
+ /* In this specific case, near-by vertices will need to know the island of the nearest connected vertex. */
+ const bool calc_single_islands = (prop_mode & T_PROP_CONNECTED);
+
+ island_info = bmesh_islands_info_calc(bm, SCE_SELECT_VERTEX, &island_info_tot, &island_vert_map, calc_single_islands);
+ }
+
+ /* find out which half we do */
+ if (mirror) {
+#if 0 // TODO
+ BM_ITER_MESH (eve, &iter, bm, BM_VERTS_OF_MESH) {
+ if (BM_elem_flag_test(eve, BM_ELEM_SELECT) && eve->co[0] != 0.0f) {
+ if (eve->co[0] < 0.0f) {
+ t->mirror = -1;
+ mirror = -1;
+ }
+ break;
+ }
+ }
+#endif
+ }
+
+ t->custom.type.data = BKE_editstrands_get_locations(edit);
+ t->custom.type.use_free = true;
+
+ BM_ITER_MESH_INDEX (eve, &iter, bm, BM_VERTS_OF_MESH, a) {
+ if (!BM_elem_flag_test(eve, BM_ELEM_HIDDEN)) {
+ if (prop_mode || BM_elem_flag_test(eve, BM_ELEM_SELECT)) {
+ struct TransIslandData *v_island = (island_info && island_vert_map[a] != -1) ?
+ &island_info[island_vert_map[a]] : NULL;
+ /* XXX TODO calculate normal and tangent along the strand */
+ float nor[3], tang[3];
+ nor[0] = 0.0f; nor[1] = 0.0f; nor[2] = 1.0f;
+ tang[0] = 0.0f; tang[1] = 1.0f; tang[2] = 0.0f;
+
+ StrandVertsToTransData(t, tob, edit, eve, nor, tang, v_island);
+
+ /* selected */
+ if (BM_elem_flag_test(eve, BM_ELEM_SELECT))
+ tob->flag |= TD_SELECTED;
+
+ if (prop_mode) {
+ if (prop_mode & T_PROP_CONNECTED) {
+ tob->dist = dists[a];
+ }
+ else {
+ tob->flag |= TD_NOTCONNECTED;
+ tob->dist = FLT_MAX;
+ }
+ }
+
+ copy_m3_m3(tob->smtx, smtx);
+ copy_m3_m3(tob->mtx, mtx);
+
+ /* Mirror? */
+ if ((mirror > 0 && tob->iloc[0] > 0.0f) || (mirror < 0 && tob->iloc[0] < 0.0f)) {
+#if 0 // TODO
+ BMVert *vmir = EDBM_verts_mirror_get(em, eve);
+ if (vmir && vmir != eve) {
+ tob->extra = vmir;
+ }
+#endif
+ }
+ tob++;
+ }
+ }
+ }
+
+ if (island_info) {
+ MEM_freeN(island_info);
+ MEM_freeN(island_vert_map);
+ }
+
+ if (mirror != 0) {
+#if 0 // TODO
+ tob = t->data;
+ for (a = 0; a < t->total; a++, tob++) {
+ if (ABS(tob->loc[0]) <= 0.00001f) {
+ tob->flag |= TD_MIRROR_EDGE;
+ }
+ }
+#endif
+ }
+
+cleanup:
+ if (dists)
+ MEM_freeN(dists);
+
+ if (t->flag & T_MIRROR) {
+#if 0 // TODO
+ EDBM_verts_mirror_cache_end(em);
+#endif
+ }
+}
+
+void flushTransStrands(TransInfo *t)
+{
+ SceneLayer *sl = t->scene_layer;
+ Object *ob = OBACT_NEW(sl);
+ BMEditStrands *edit = BKE_editstrands_from_object(ob);
+ BMEditStrandsLocations origlocs = t->custom.type.data;
+
+ BKE_editstrands_solve_constraints(ob, edit, origlocs);
+}
+
/* ********************* mesh ****************** */
static bool bmesh_test_dist_add(
@@ -2234,11 +2455,10 @@ static void editmesh_set_connectivity_distance(BMesh *bm, float mtx[3][3], float
}
}
-static struct TransIslandData *editmesh_islands_info_calc(
- BMEditMesh *em, int *r_island_tot, int **r_island_vert_map,
+static struct TransIslandData *bmesh_islands_info_calc(
+ BMesh *bm, short selectmode, int *r_island_tot, int **r_island_vert_map,
bool calc_single_islands)
{
- BMesh *bm = em->bm;
struct TransIslandData *trans_islands;
char htype;
char itype;
@@ -2252,7 +2472,7 @@ static struct TransIslandData *editmesh_islands_info_calc(
int *vert_map;
- if (em->selectmode & (SCE_SELECT_VERTEX | SCE_SELECT_EDGE)) {
+ if (selectmode & (SCE_SELECT_VERTEX | SCE_SELECT_EDGE)) {
groups_array = MEM_mallocN(sizeof(*groups_array) * bm->totedgesel, __func__);
group_tot = BM_mesh_calc_edge_groups(bm, groups_array, &group_index,
NULL, NULL,
@@ -2559,10 +2779,9 @@ static void createTransEditVerts(TransInfo *t)
/* In this specific case, near-by vertices will need to know the island of the nearest connected vertex. */
const bool calc_single_islands = (
(prop_mode & T_PROP_CONNECTED) &&
- (t->around == V3D_AROUND_LOCAL_ORIGINS) &&
(em->selectmode & SCE_SELECT_VERTEX));
- island_info = editmesh_islands_info_calc(em, &island_info_tot, &island_vert_map, calc_single_islands);
+ island_info = bmesh_islands_info_calc(em->bm, em->selectmode, &island_info_tot, &island_vert_map, calc_single_islands);
}
/* detect CrazySpace [tm] */
@@ -6495,6 +6714,11 @@ void special_aftertrans_update(bContext *C, TransInfo *t)
{
/* do nothing */
}
+ else if ((ob->mode & OB_MODE_HAIR_EDIT) &&
+ BKE_editstrands_from_object(ob))
+ {
+ /* do nothing */
+ }
else { /* Objects */
int i;
@@ -8257,6 +8481,16 @@ void createTransData(bContext *C, TransInfo *t)
sort_trans_data_dist(t);
}
}
+ else if (ob && (ob->mode & OB_MODE_HAIR_EDIT) && BKE_editstrands_from_object(ob)) {
+ createTransStrandVerts(t);
+ t->flag |= T_POINTS;
+
+ if (t->data && t->flag & T_PROP_EDIT) {
+ sort_trans_data(t); // makes selected become first in array
+ set_prop_dist(t, 1);
+ sort_trans_data_dist(t);
+ }
+ }
else if (ob && (ob->mode & OB_MODE_ALL_PAINT)) {
if ((t->options & CTX_PAINT_CURVE) && !ELEM(t->mode, TFM_SHEAR, TFM_SHRINKFATTEN)) {
t->flag |= T_POINTS | T_2D_EDIT;
diff --git a/source/blender/editors/transform/transform_generics.c b/source/blender/editors/transform/transform_generics.c
index a0eee4296c1..90e12f03979 100644
--- a/source/blender/editors/transform/transform_generics.c
+++ b/source/blender/editors/transform/transform_generics.c
@@ -71,6 +71,7 @@
#include "BKE_action.h"
#include "BKE_armature.h"
#include "BKE_curve.h"
+#include "BKE_editstrands.h"
#include "BKE_fcurve.h"
#include "BKE_lattice.h"
#include "BKE_library.h"
@@ -909,6 +910,12 @@ static void recalcData_objects(TransInfo *t)
}
flushTransParticles(t);
}
+ else if (base && (base->object->mode & OB_MODE_HAIR_EDIT) && BKE_editstrands_from_object(base->object)) {
+ if (t->state != TRANS_CANCEL) {
+ applyProject(t);
+ }
+ flushTransStrands(t);
+ }
else {
int i;
diff --git a/source/blender/editors/transform/transform_orientations.c b/source/blender/editors/transform/transform_orientations.c
index e91db762eb1..4f436a24ef5 100644
--- a/source/blender/editors/transform/transform_orientations.c
+++ b/source/blender/editors/transform/transform_orientations.c
@@ -1039,7 +1039,7 @@ int getTransformOrientation_ex(const bContext *C, float normal[3], float plane[3
result = ORIENTATION_EDGE;
}
}
- else if (ob && (ob->mode & (OB_MODE_ALL_PAINT | OB_MODE_PARTICLE_EDIT))) {
+ else if (ob && (ob->mode & (OB_MODE_ALL_BRUSH | OB_MODE_PARTICLE_EDIT))) {
/* pass */
}
else {
diff --git a/source/blender/editors/util/ed_util.c b/source/blender/editors/util/ed_util.c
index 8178079eb19..13822c658bf 100644
--- a/source/blender/editors/util/ed_util.c
+++ b/source/blender/editors/util/ed_util.c
@@ -52,6 +52,7 @@
#include "BLT_translation.h"
#include "BKE_context.h"
+#include "BKE_editstrands.h"
#include "BKE_global.h"
#include "BKE_main.h"
#include "BKE_multires.h"
@@ -129,6 +130,7 @@ void ED_editors_exit(bContext *C)
{
Main *bmain = CTX_data_main(C);
Scene *sce;
+ Object *ob;
if (!bmain)
return;
@@ -139,7 +141,7 @@ void ED_editors_exit(bContext *C)
for (sce = bmain->scene.first; sce; sce = sce->id.next) {
if (sce->obedit) {
- Object *ob = sce->obedit;
+ ob = sce->obedit;
if (ob) {
if (ob->type == OB_MESH) {
@@ -156,6 +158,17 @@ void ED_editors_exit(bContext *C)
}
}
}
+
+ for (ob = bmain->object.first; ob; ob = ob->id.next) {
+ if (ob->type == OB_MESH) {
+ Mesh *me = ob->data;
+ if (me->edit_strands) {
+ BKE_editstrands_free(me->edit_strands);
+ MEM_freeN(me->edit_strands);
+ me->edit_strands = NULL;
+ }
+ }
+ }
/* global in meshtools... */
ED_mesh_mirror_spatial_table(NULL, NULL, NULL, NULL, 'e');
diff --git a/source/blender/editors/util/editmode_undo.c b/source/blender/editors/util/editmode_undo.c
index d9f777771a8..7f3ac1cdcda 100644
--- a/source/blender/editors/util/editmode_undo.c
+++ b/source/blender/editors/util/editmode_undo.c
@@ -85,6 +85,7 @@ typedef struct UndoElem {
void *undodata;
uintptr_t undosize;
char name[BKE_UNDO_STR_MAX];
+ Object *(*get_object)(const bContext *C);
void * (*getdata)(bContext * C);
void (*freedata)(void *);
void (*to_editmode)(void *, void *, void *);
@@ -107,14 +108,15 @@ static void undo_restore(UndoElem *undo, void *editdata, void *obdata)
/* name can be a dynamic string */
void undo_editmode_push(bContext *C, const char *name,
- void * (*getdata)(bContext * C),
+ Object *(*get_object)(const bContext *C),
+ void * (*getdata)(bContext *C),
void (*freedata)(void *),
void (*to_editmode)(void *, void *, void *),
void *(*from_editmode)(void *, void *),
int (*validate_undo)(void *, void *))
{
UndoElem *uel;
- Object *obedit = CTX_data_edit_object(C);
+ Object *obedit = get_object(C);
void *editdata;
int nr;
uintptr_t memused, totmem, maxmem;
@@ -134,6 +136,7 @@ void undo_editmode_push(bContext *C, const char *name,
BLI_strncpy(uel->name, name, sizeof(uel->name));
BLI_addtail(&undobase, uel);
+ uel->get_object = get_object;
uel->getdata = getdata;
uel->freedata = freedata;
uel->to_editmode = to_editmode;
@@ -194,19 +197,19 @@ void undo_editmode_push(bContext *C, const char *name,
static void undo_clean_stack(bContext *C)
{
UndoElem *uel, *next;
- Object *obedit = CTX_data_edit_object(C);
/* global undo changes pointers, so we also allow identical names */
/* side effect: when deleting/renaming object and start editing new one with same name */
uel = undobase.first;
while (uel) {
+ Object *obedit = uel->get_object(C);
void *editdata = uel->getdata(C);
bool is_valid = false;
next = uel->next;
/* for when objects are converted, renamed, or global undo changes pointers... */
- if (uel->type == obedit->type) {
+ if (obedit && uel->type == obedit->type) {
if (STREQ(uel->id.name, obedit->id.name)) {
if (uel->validate_undo == NULL)
is_valid = true;
@@ -233,12 +236,13 @@ static void undo_clean_stack(bContext *C)
/* 1 = an undo, -1 is a redo. we have to make sure 'curundo' remains at current situation */
void undo_editmode_step(bContext *C, int step)
{
- Object *obedit = CTX_data_edit_object(C);
+ Object *obedit;
/* prevent undo to happen on wrong object, stack can be a mix */
undo_clean_stack(C);
if (step == 0) {
+ obedit = curundo->get_object(C);
undo_restore(curundo, curundo->getdata(C), obedit->data);
}
else if (step == 1) {
@@ -249,6 +253,7 @@ void undo_editmode_step(bContext *C, int step)
else {
if (G.debug & G_DEBUG) printf("undo %s\n", curundo->name);
curundo = curundo->prev;
+ obedit = curundo->get_object(C);
undo_restore(curundo, curundo->getdata(C), obedit->data);
}
}
@@ -259,15 +264,19 @@ void undo_editmode_step(bContext *C, int step)
error("No more steps to redo");
}
else {
+ obedit = curundo->get_object(C);
undo_restore(curundo->next, curundo->getdata(C), obedit->data);
curundo = curundo->next;
if (G.debug & G_DEBUG) printf("redo %s\n", curundo->name);
}
}
+ obedit = curundo->get_object(C);
+
/* special case for editmesh, mode must be copied back to the scene */
if (obedit->type == OB_MESH) {
- EDBM_selectmode_to_scene(C);
+ if (obedit == CTX_data_edit_object(C))
+ EDBM_selectmode_to_scene(C);
}
DEG_id_tag_update(&obedit->id, OB_RECALC_DATA);
diff --git a/source/blender/editors/util/undo.c b/source/blender/editors/util/undo.c
index ff328a28ee9..bd180789cbe 100644
--- a/source/blender/editors/util/undo.c
+++ b/source/blender/editors/util/undo.c
@@ -55,6 +55,7 @@
#include "ED_mball.h"
#include "ED_mesh.h"
#include "ED_object.h"
+#include "ED_physics.h"
#include "ED_render.h"
#include "ED_screen.h"
#include "ED_paint.h"
@@ -103,6 +104,9 @@ void ED_undo_push(bContext *C, const char *str)
PE_undo_push(CTX_data_scene(C), CTX_data_scene_layer(C), str);
}
+ else if (obact && obact->mode & OB_MODE_HAIR_EDIT) {
+ undo_push_strands(C, str);
+ }
else if (obact && obact->mode & OB_MODE_SCULPT) {
/* do nothing for now */
}
@@ -185,6 +189,14 @@ static int ed_undo_step(bContext *C, int step, const char *undoname)
else
PE_redo(scene, sl);
}
+ else if (obact && obact->mode & OB_MODE_HAIR_EDIT) {
+ if (undoname)
+ undo_editmode_name(C, undoname);
+ else
+ undo_editmode_step(C, step);
+
+ WM_event_add_notifier(C, NC_GEOM | ND_DATA, NULL);
+ }
else if (U.uiflag & USER_GLOBALUNDO) {
// note python defines not valid here anymore.
//#ifdef WITH_PYTHON
diff --git a/source/blender/gpu/GPU_texture.h b/source/blender/gpu/GPU_texture.h
index 83872ccdf80..742daee22ee 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_brush_types.h b/source/blender/makesdna/DNA_brush_types.h
index 4e4c769f396..298a794500c 100644
--- a/source/blender/makesdna/DNA_brush_types.h
+++ b/source/blender/makesdna/DNA_brush_types.h
@@ -102,6 +102,9 @@ typedef struct Brush {
char vertexpaint_tool; /* active vertex/weight paint blend mode (poorly named) */
char imagepaint_tool; /* active image paint tool */
char mask_tool; /* enum BrushMaskTool, only used if sculpt_tool is SCULPT_TOOL_MASK */
+ char hair_tool; /* active hair tool */
+ char pad2[3];
+ int pad3;
float autosmooth_factor;
@@ -299,6 +302,16 @@ typedef enum BrushImagePaintTool {
PAINT_TOOL_MASK = 5
} BrushImagePaintTool;
+typedef enum BrushHairTool {
+ HAIR_TOOL_COMB = 1,
+ HAIR_TOOL_CUT = 2,
+ HAIR_TOOL_LENGTH = 3,
+ HAIR_TOOL_PUFF = 4,
+ HAIR_TOOL_ADD = 5,
+ HAIR_TOOL_SMOOTH = 6,
+ HAIR_TOOL_WEIGHT = 7,
+} BrushHairTool;
+
/* direction that the brush displaces along */
enum {
SCULPT_DISP_DIR_AREA = 0,
diff --git a/source/blender/makesdna/DNA_customdata_types.h b/source/blender/makesdna/DNA_customdata_types.h
index 0e0b1d669d9..5b1eeb798d7 100644
--- a/source/blender/makesdna/DNA_customdata_types.h
+++ b/source/blender/makesdna/DNA_customdata_types.h
@@ -63,10 +63,9 @@ typedef struct CustomDataExternal {
* layers, each with a data type (e.g. MTFace, MDeformVert, etc.). */
typedef struct CustomData {
CustomDataLayer *layers; /* CustomDataLayers, ordered by type */
- int typemap[42]; /* runtime only! - maps types to indices of first layer of that type,
+ int typemap[43]; /* runtime only! - maps types to indices of first layer of that type,
* MUST be >= CD_NUMTYPES, but we cant use a define here.
* Correct size is ensured in CustomData_update_typemap assert() */
- int pad_i1;
int totlayer, maxlayer; /* number of layers, size of layers array */
int totsize; /* in editmode, total size of all data layers */
struct BLI_mempool *pool; /* (BMesh Only): Memory pool for allocation of blocks */
@@ -130,7 +129,9 @@ typedef enum CustomDataType {
CD_TESSLOOPNORMAL = 40,
CD_CUSTOMLOOPNORMAL = 41,
- CD_NUMTYPES = 42
+ CD_MSURFACE_SAMPLE = 42,
+
+ CD_NUMTYPES = 43
} CustomDataType;
/* Bits for CustomDataMask */
@@ -179,6 +180,8 @@ typedef enum CustomDataType {
#define CD_MASK_TESSLOOPNORMAL (1LL << CD_TESSLOOPNORMAL)
#define CD_MASK_CUSTOMLOOPNORMAL (1LL << CD_CUSTOMLOOPNORMAL)
+#define CD_MASK_MSURFACE_SAMPLE (1LL << CD_MSURFACE_SAMPLE)
+
/* CustomData.flag */
enum {
/* Indicates layer should not be copied by CustomData_from_template or CustomData_copy_data */
diff --git a/source/blender/makesdna/DNA_hair_types.h b/source/blender/makesdna/DNA_hair_types.h
new file mode 100644
index 00000000000..3095ecc91fb
--- /dev/null
+++ b/source/blender/makesdna/DNA_hair_types.h
@@ -0,0 +1,85 @@
+/*
+ * ***** 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;
+} HairFollicle;
+
+/* Collection of hair roots on a surface */
+typedef struct HairPattern {
+ struct HairFollicle *follicles;
+ int num_follicles;
+
+ int active_group;
+ ListBase groups;
+} HairPattern;
+
+typedef struct HairGroup {
+ struct HairGroup *next, *prev;
+
+ char name[64]; /* MAX_NAME */
+ int type;
+ int pad;
+
+ struct HairFollicle *follicles;
+ int num_follicles;
+
+ /* NORMALS */
+ float normals_max_length;
+
+ /* STRANDS */
+ int (*strands_parent_index)[4];
+ float (*strands_parent_weight)[4];
+
+ void *draw_batch_cache;
+ void *draw_texture_cache;
+} HairGroup;
+
+typedef enum HairGroup_Type {
+ HAIR_GROUP_TYPE_NORMALS = 1,
+ HAIR_GROUP_TYPE_STRANDS = 2,
+} HairGroup_Type;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __DNA_HAIR_TYPES_H__ */
diff --git a/source/blender/makesdna/DNA_mesh_types.h b/source/blender/makesdna/DNA_mesh_types.h
index 505b1f7157b..9794f37e997 100644
--- a/source/blender/makesdna/DNA_mesh_types.h
+++ b/source/blender/makesdna/DNA_mesh_types.h
@@ -87,6 +87,7 @@ typedef struct Mesh {
/* When the object is available, the preferred access method is: BKE_editmesh_from_object(ob) */
struct BMEditMesh *edit_btmesh; /* not saved in file! */
+ struct BMEditStrands *edit_strands; /* not saved in file! */
struct CustomData vdata, edata, fdata;
diff --git a/source/blender/makesdna/DNA_meshdata_types.h b/source/blender/makesdna/DNA_meshdata_types.h
index d0a3c7bb95a..d8077c7bc36 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 c185f84997e..5071859f82e 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_Hair = 54,
NUM_MODIFIER_TYPES
} ModifierType;
@@ -1615,4 +1616,16 @@ enum {
#define MOD_MESHSEQ_READ_ALL \
(MOD_MESHSEQ_READ_VERT | MOD_MESHSEQ_READ_POLY | MOD_MESHSEQ_READ_UV | MOD_MESHSEQ_READ_COLOR)
+/* Hair modifier */
+typedef struct HairModifierData {
+ ModifierData modifier;
+
+ int flag;
+ int pad;
+
+ struct HairPattern *hair;
+
+ struct BMEditStrands *edit; /* edit data (runtime) */
+} HairModifierData;
+
#endif /* __DNA_MODIFIER_TYPES_H__ */
diff --git a/source/blender/makesdna/DNA_object_types.h b/source/blender/makesdna/DNA_object_types.h
index c25afb0ec6a..d8a11b83fea 100644
--- a/source/blender/makesdna/DNA_object_types.h
+++ b/source/blender/makesdna/DNA_object_types.h
@@ -718,10 +718,12 @@ typedef enum ObjectMode {
OB_MODE_PARTICLE_EDIT = 1 << 5,
OB_MODE_POSE = 1 << 6,
OB_MODE_GPENCIL = 1 << 7, /* NOTE: Just a dummy to make the UI nicer */
+ OB_MODE_HAIR_EDIT = 1 << 8,
} ObjectMode;
/* any mode where the brush system is used */
#define OB_MODE_ALL_PAINT (OB_MODE_SCULPT | OB_MODE_VERTEX_PAINT | OB_MODE_WEIGHT_PAINT | OB_MODE_TEXTURE_PAINT)
+#define OB_MODE_ALL_BRUSH (OB_MODE_ALL_PAINT | OB_MODE_HAIR_EDIT)
#define MAX_DUPLI_RECUR 8
diff --git a/source/blender/makesdna/DNA_particle_types.h b/source/blender/makesdna/DNA_particle_types.h
index ea7905eb2ad..54fe5d7da56 100644
--- a/source/blender/makesdna/DNA_particle_types.h
+++ b/source/blender/makesdna/DNA_particle_types.h
@@ -278,6 +278,7 @@ typedef struct ParticleSystem {
struct PTCacheEdit *edit; /* particle editmode (runtime) */
void (*free_edit)(struct PTCacheEdit *edit); /* free callback */
+ struct BMEditStrands *hairedit; /* hair edit data (runtime) */
struct ParticleCacheKey **pathcache; /* path cache (runtime) */
struct ParticleCacheKey **childcache; /* child cache (runtime) */
diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h
index 44eb0881089..93413819025 100644
--- a/source/blender/makesdna/DNA_scene_types.h
+++ b/source/blender/makesdna/DNA_scene_types.h
@@ -1118,6 +1118,42 @@ typedef struct ParticleEditSettings {
} ParticleEditSettings;
/* ------------------------------------------- */
+/* Hair Edit */
+
+/* HairEditSettings->select_mode */
+typedef enum HairEditSelectMode {
+ HAIR_SELECT_STRAND = 0,
+ HAIR_SELECT_VERTEX = 1,
+ HAIR_SELECT_TIP = 2,
+} HairEditSelectMode;
+
+/* HairEditSettings->hair_draw_mode */
+typedef enum HairEditDrawMode {
+ HAIR_DRAW_NONE = 0,
+ HAIR_DRAW_FIBERS = 1,
+} HairEditDrawMode;
+
+/* HairEditSettings->flag */
+typedef enum HairEditFlag {
+ HAIR_EDIT_SHOW_BRUSH = (1 << 0),
+ HAIR_EDIT_SHOW_DEBUG = (1 << 16),
+} HairEditFlag;
+
+typedef struct HairEditSettings {
+ int flag;
+ short select_mode;
+ short hair_draw_mode;
+ float hair_draw_size;
+ int hair_draw_subdiv;
+
+ struct Brush *brush;
+ struct Object *shape_object;
+
+ /* WM Paint cursor */
+ void *paint_cursor;
+} HairEditSettings;
+
+/* ------------------------------------------- */
/* Sculpt */
/* Sculpt */
@@ -1514,7 +1550,10 @@ typedef struct ToolSettings {
/* Particle Editing */
struct ParticleEditSettings particle;
-
+
+ /* Hair Editing */
+ struct HairEditSettings hair_edit;
+
/* 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 182a026df94..1edd5f8010e 100644
--- a/source/blender/makesdna/intern/makesdna.c
+++ b/source/blender/makesdna/intern/makesdna.c
@@ -133,6 +133,7 @@ static const char *includefiles[] = {
"DNA_layer_types.h",
"DNA_workspace_types.h",
"DNA_lightprobe_types.h",
+ "DNA_hair_types.h",
/* see comment above before editing! */
@@ -1360,5 +1361,6 @@ 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"
/* end of list */
diff --git a/source/blender/makesrna/RNA_access.h b/source/blender/makesrna/RNA_access.h
index 526aa3ddf55..e6f2d19b9cd 100644
--- a/source/blender/makesrna/RNA_access.h
+++ b/source/blender/makesrna/RNA_access.h
@@ -285,6 +285,9 @@ extern StructRNA RNA_GaussianBlurSequence;
extern StructRNA RNA_GlowSequence;
extern StructRNA RNA_GreasePencil;
extern StructRNA RNA_Group;
+extern StructRNA RNA_HairGroup;
+extern StructRNA RNA_HairModifier;
+extern StructRNA RNA_HairPattern;
extern StructRNA RNA_Header;
extern StructRNA RNA_HemiLamp;
extern StructRNA RNA_Histogram;
diff --git a/source/blender/makesrna/RNA_enum_types.h b/source/blender/makesrna/RNA_enum_types.h
index 301c6fae3ca..c7342719f86 100644
--- a/source/blender/makesrna/RNA_enum_types.h
+++ b/source/blender/makesrna/RNA_enum_types.h
@@ -108,6 +108,7 @@ extern EnumPropertyItem rna_enum_motionpath_bake_location_items[];
extern EnumPropertyItem rna_enum_event_value_items[];
extern EnumPropertyItem rna_enum_event_type_items[];
extern EnumPropertyItem rna_enum_operator_return_items[];
+extern EnumPropertyItem brush_hair_tool_items[];
extern EnumPropertyItem rna_enum_brush_sculpt_tool_items[];
extern EnumPropertyItem rna_enum_brush_vertex_tool_items[];
diff --git a/source/blender/makesrna/intern/CMakeLists.txt b/source/blender/makesrna/intern/CMakeLists.txt
index fa01df2cbda..0327bf6dd58 100644
--- a/source/blender/makesrna/intern/CMakeLists.txt
+++ b/source/blender/makesrna/intern/CMakeLists.txt
@@ -52,6 +52,7 @@ set(DEFSRC
rna_fluidsim.c
rna_gpencil.c
rna_group.c
+ rna_hair.c
rna_image.c
rna_key.c
rna_lamp.c
@@ -61,6 +62,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 f232a1aef48..bb3212d0df9 100644
--- a/source/blender/makesrna/intern/makesrna.c
+++ b/source/blender/makesrna/intern/makesrna.c
@@ -3332,6 +3332,7 @@ static RNAProcessItem PROCESS_ITEMS[] = {
{"rna_fluidsim.c", NULL, RNA_def_fluidsim},
{"rna_gpencil.c", NULL, RNA_def_gpencil},
{"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},
@@ -3340,6 +3341,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_brush.c b/source/blender/makesrna/intern/rna_brush.c
index 1948f425083..6f453c21370 100644
--- a/source/blender/makesrna/intern/rna_brush.c
+++ b/source/blender/makesrna/intern/rna_brush.c
@@ -107,6 +107,17 @@ EnumPropertyItem rna_enum_brush_image_tool_items[] = {
{0, NULL, 0, NULL, NULL}
};
+EnumPropertyItem brush_hair_tool_items[] = {
+ {HAIR_TOOL_COMB, "COMB", ICON_BRUSH_HAIR_COMB, "Comb", "Align hairs to the stroke direction"},
+ {HAIR_TOOL_CUT, "CUT", ICON_BRUSH_HAIR_CUT, "Cut", "Shorten and/or remove hairs"},
+ {HAIR_TOOL_LENGTH, "LENGTH", ICON_BRUSH_HAIR_LENGTH, "Length", "Increase hair length"},
+ {HAIR_TOOL_PUFF, "PUFF", ICON_BRUSH_HAIR_PUFF, "Puff", "Increase spacing between hairs"},
+ {HAIR_TOOL_ADD, "ADD", ICON_BRUSH_HAIR_ADD, "Add", "Add more hairs on the object"},
+ {HAIR_TOOL_SMOOTH, "SMOOTH", ICON_BRUSH_HAIR_SMOOTH, "Smooth", "Align hairs in the same direction"},
+ {HAIR_TOOL_WEIGHT, "WEIGHT", ICON_BRUSH_HAIR_WEIGHT, "Weight", "Set hair vertex weights"},
+ {0, NULL, 0, NULL, NULL}
+};
+
#ifdef RNA_RUNTIME
#include "MEM_guardedalloc.h"
@@ -407,6 +418,13 @@ static void rna_Brush_imagepaint_tool_update(Main *bmain, Scene *scene, PointerR
rna_Brush_update(bmain, scene, ptr);
}
+static void rna_Brush_hair_tool_update(Main *bmain, Scene *scene, PointerRNA *ptr)
+{
+ Brush *br = (Brush *)ptr->data;
+ rna_Brush_reset_icon(br, "hair");
+ rna_Brush_update(bmain, scene, ptr);
+}
+
static void rna_Brush_stroke_update(Main *bmain, Scene *scene, PointerRNA *ptr)
{
WM_main_add_notifier(NC_SCENE | ND_TOOLSETTINGS, scene);
@@ -900,6 +918,12 @@ static void rna_def_brush(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Image Paint Tool", "");
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_IMAGE, "rna_Brush_imagepaint_tool_update");
+ prop = RNA_def_property(srna, "hair_tool", PROP_ENUM, PROP_NONE);
+ RNA_def_property_enum_sdna(prop, NULL, "hair_tool");
+ RNA_def_property_enum_items(prop, brush_hair_tool_items);
+ RNA_def_property_ui_text(prop, "Hair Tool", "");
+ RNA_def_property_update(prop, 0, "rna_Brush_hair_tool_update");
+
prop = RNA_def_property(srna, "direction", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_bitflag_sdna(prop, NULL, "flag");
RNA_def_property_enum_items(prop, prop_direction_items);
@@ -1345,6 +1369,10 @@ static void rna_def_brush(BlenderRNA *brna)
RNA_def_property_boolean_sdna(prop, NULL, "ob_mode", OB_MODE_TEXTURE_PAINT);
RNA_def_property_ui_text(prop, "Use Texture", "Use this brush in texture paint mode");
+ prop = RNA_def_property(srna, "use_hair_edit", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "ob_mode", OB_MODE_HAIR_EDIT);
+ RNA_def_property_ui_text(prop, "Use Hair", "Use this brush in hair edit mode");
+
/* texture */
prop = RNA_def_property(srna, "texture_slot", PROP_POINTER, PROP_NONE);
RNA_def_property_struct_type(prop, "BrushTextureSlot");
diff --git a/source/blender/makesrna/intern/rna_context.c b/source/blender/makesrna/intern/rna_context.c
index 5701150b1bf..5b7a023aab1 100644
--- a/source/blender/makesrna/intern/rna_context.c
+++ b/source/blender/makesrna/intern/rna_context.c
@@ -188,6 +188,7 @@ void RNA_def_context(BlenderRNA *brna)
{CTX_MODE_PAINT_VERTEX, "PAINT_VERTEX", 0, "Vertex Paint", ""},
{CTX_MODE_PAINT_TEXTURE, "PAINT_TEXTURE", 0, "Texture Paint", ""},
{CTX_MODE_PARTICLE, "PARTICLE", 0, "Particle", ""},
+ {CTX_MODE_HAIR, "HAIR", 0, "Hair", ""},
{CTX_MODE_OBJECT, "OBJECT", 0, "Object", ""},
{0, NULL, 0, NULL, NULL}
};
diff --git a/source/blender/makesrna/intern/rna_hair.c b/source/blender/makesrna/intern/rna_hair.c
new file mode 100644
index 00000000000..2938edfe0b8
--- /dev/null
+++ b/source/blender/makesrna/intern/rna_hair.c
@@ -0,0 +1,189 @@
+/*
+ * ***** 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_modifier_types.h"
+#include "DNA_object_types.h"
+
+#include "BKE_hair.h"
+#include "BKE_main.h"
+#include "DEG_depsgraph.h"
+
+#include "RNA_access.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+static void rna_HairGroup_update(Main *UNUSED(bmain), Scene *UNUSED(scene), PointerRNA *ptr)
+{
+ HairGroup *group = ptr->data;
+ UNUSED_VARS(group);
+}
+
+static HairPattern* find_hair_group_pattern(ID *id, HairGroup *group)
+{
+ Object *ob = (Object *)id;
+ for (ModifierData *md = ob->modifiers.first; md; md = md->next) {
+ if (md->type == eModifierType_Hair) {
+ HairModifierData *hmd = (HairModifierData *)md;
+ for (HairGroup *g = hmd->hair->groups.first; g; g = g->next) {
+ if (g == group) {
+ return hmd->hair;
+ }
+ }
+ }
+ }
+ return NULL;
+}
+
+static void rna_HairGroup_name_set(PointerRNA *ptr, const char *value)
+{
+ HairGroup *group = ptr->data;
+ HairPattern *hair = find_hair_group_pattern(ptr->id.data, group);
+ BLI_assert(hair != NULL);
+
+ BKE_hair_group_name_set(hair, group, value);
+}
+
+static void rna_HairPattern_update(Main *UNUSED(bmain), Scene *UNUSED(scene), PointerRNA *ptr)
+{
+ HairPattern *hair = ptr->data;
+ UNUSED_VARS(hair);
+}
+
+PointerRNA rna_HairPattern_active_group_get(PointerRNA *ptr)
+{
+ HairPattern *hair = ptr->data;
+ PointerRNA result;
+ RNA_pointer_create(ptr->id.data, &RNA_HairGroup, BLI_findlink(&hair->groups, hair->active_group), &result);
+ return result;
+}
+
+#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_group(BlenderRNA *brna)
+{
+ StructRNA *srna;
+ PropertyRNA *prop;
+
+ static EnumPropertyItem type_items[] = {
+ {HAIR_GROUP_TYPE_NORMALS, "NORMALS", 0, "Normals", "Hair grows straight along surface normals"},
+ {HAIR_GROUP_TYPE_STRANDS, "STRANDS", 0, "Strands", "Hair is interpolated between control strands"},
+ {0, NULL, 0, NULL, NULL}
+ };
+
+ srna = RNA_def_struct(brna, "HairGroup", NULL);
+ RNA_def_struct_ui_text(srna, "Hair Group", "Group of hairs that are generated in the same way");
+ RNA_def_struct_sdna(srna, "HairGroup");
+
+ prop = RNA_def_property(srna, "type", PROP_ENUM, PROP_NONE);
+ RNA_def_property_enum_items(prop, type_items);
+ RNA_def_property_enum_default(prop, HAIR_GROUP_TYPE_NORMALS);
+ RNA_def_property_ui_text(prop, "Type", "Generator type");
+ RNA_def_property_update(prop, NC_OBJECT | ND_DRAW, "rna_HairGroup_update");
+
+ prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE);
+ RNA_def_property_string_funcs(prop, NULL, NULL, "rna_HairGroup_name_set");
+ RNA_def_property_ui_text(prop, "Name", "");
+ RNA_def_struct_name_property(srna, prop);
+ RNA_def_property_update(prop, NC_OBJECT | ND_DRAW | NA_RENAME, NULL);
+
+ prop = RNA_def_property(srna, "normals_max_length", PROP_FLOAT, PROP_NONE);
+ RNA_def_property_range(prop, 0.0f, FLT_MAX);
+ RNA_def_property_ui_range(prop, 0.0, 10.0, 0.1, 4);
+ RNA_def_property_ui_text(prop, "Maximum Length", "Maximum length of hair fibers in this group");
+ RNA_def_property_update(prop, NC_OBJECT | ND_DRAW, "rna_HairGroup_update");
+}
+
+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");
+
+ prop = RNA_def_property(srna, "groups", PROP_COLLECTION, PROP_NONE);
+ RNA_def_property_collection_sdna(prop, NULL, "groups", NULL);
+ RNA_def_property_struct_type(prop, "HairGroup");
+ RNA_def_property_ui_text(prop, "Groups", "Hair group using a the same generator method");
+
+ prop = RNA_def_property(srna, "active_group", PROP_POINTER, PROP_NONE);
+ RNA_def_property_pointer_funcs(prop, "rna_HairPattern_active_group_get", NULL, NULL, NULL);
+ RNA_def_property_struct_type(prop, "HairGroup");
+ RNA_def_property_ui_text(prop, "Active Group", "");
+ RNA_def_property_update(prop, NC_OBJECT | ND_DRAW, "rna_HairPattern_update");
+
+ prop = RNA_def_property(srna, "active_group_index", PROP_INT, PROP_NONE);
+ RNA_def_property_int_sdna(prop, NULL, "active_group");
+ RNA_def_property_ui_text(prop, "Active Group Index", "");
+ RNA_def_property_update(prop, NC_OBJECT | ND_DRAW, "rna_HairPattern_update");
+}
+
+void RNA_def_hair(BlenderRNA *brna)
+{
+ rna_def_hair_follicle(brna);
+ rna_def_hair_group(brna);
+ rna_def_hair_pattern(brna);
+}
+
+#endif
diff --git a/source/blender/makesrna/intern/rna_internal.h b/source/blender/makesrna/intern/rna_internal.h
index 00b1b53ad71..98a5febd746 100644
--- a/source/blender/makesrna/intern/rna_internal.h
+++ b/source/blender/makesrna/intern/rna_internal.h
@@ -157,6 +157,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);
@@ -194,6 +195,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_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 03340c6c356..f10145e66a2 100644
--- a/source/blender/makesrna/intern/rna_modifier.c
+++ b/source/blender/makesrna/intern/rna_modifier.c
@@ -114,6 +114,7 @@ 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_Hair, "HAIR", ICON_STRANDS, "Hair", ""},
{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_Hair:
+ return &RNA_HairModifier;
/* Default */
case eModifierType_None:
case eModifierType_ShapeKey:
@@ -4749,6 +4752,21 @@ static void rna_def_modifier_surfacedeform(BlenderRNA *brna)
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
}
+static void rna_def_modifier_hair(BlenderRNA *brna)
+{
+ StructRNA *srna;
+ PropertyRNA *prop;
+
+ srna = RNA_def_struct(brna, "HairModifier", "Modifier");
+ RNA_def_struct_ui_text(srna, "Hair Modifier", "");
+ RNA_def_struct_sdna(srna, "HairModifierData");
+ RNA_def_struct_ui_icon(srna, ICON_STRANDS);
+
+ prop = RNA_def_property(srna, "hair", PROP_POINTER, PROP_NONE);
+ RNA_def_property_ui_text(prop, "Hair", "Hair data");
+ RNA_def_property_clear_flag(prop, PROP_EDITABLE);
+}
+
void RNA_def_modifier(BlenderRNA *brna)
{
StructRNA *srna;
@@ -4867,6 +4885,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_hair(brna);
}
#endif
diff --git a/source/blender/makesrna/intern/rna_object.c b/source/blender/makesrna/intern/rna_object.c
index 50160b5b189..3fc877ca357 100644
--- a/source/blender/makesrna/intern/rna_object.c
+++ b/source/blender/makesrna/intern/rna_object.c
@@ -71,6 +71,7 @@ EnumPropertyItem rna_enum_object_mode_items[] = {
{OB_MODE_TEXTURE_PAINT, "TEXTURE_PAINT", ICON_TPAINT_HLT, "Texture Paint", ""},
{OB_MODE_PARTICLE_EDIT, "PARTICLE_EDIT", ICON_PARTICLEMODE, "Particle Edit", ""},
{OB_MODE_GPENCIL, "GPENCIL_EDIT", ICON_GREASEPENCIL, "Edit Strokes", "Edit Grease Pencil Strokes"},
+ {OB_MODE_HAIR_EDIT, "HAIR_EDIT", ICON_PARTICLEMODE, "Hair Edit", ""},
{0, NULL, 0, NULL, NULL}
};
diff --git a/source/blender/makesrna/intern/rna_scene.c b/source/blender/makesrna/intern/rna_scene.c
index 9fb60a13dda..40d5b3a7378 100644
--- a/source/blender/makesrna/intern/rna_scene.c
+++ b/source/blender/makesrna/intern/rna_scene.c
@@ -3570,6 +3570,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, "hair_edit", PROP_POINTER, PROP_NONE);
+ RNA_def_property_pointer_sdna(prop, NULL, "hair_edit");
+ RNA_def_property_ui_text(prop, "Hair Edit", "");
+
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 73fe801106a..eb352057928 100644
--- a/source/blender/makesrna/intern/rna_sculpt_paint.c
+++ b/source/blender/makesrna/intern/rna_sculpt_paint.c
@@ -103,6 +103,8 @@ EnumPropertyItem rna_enum_symmetrize_direction_items[] = {
#include "BKE_context.h"
#include "BKE_DerivedMesh.h"
+#include "BKE_editstrands.h"
+#include "BKE_effect.h"
#include "BKE_pointcache.h"
#include "BKE_particle.h"
#include "BKE_pbvh.h"
@@ -258,6 +260,8 @@ static int rna_Brush_mode_poll(PointerRNA *ptr, PointerRNA value)
mode = OB_MODE_VERTEX_PAINT;
else if (ptr->data == ts->wpaint)
mode = OB_MODE_WEIGHT_PAINT;
+ else if (ptr->data == &ts->hair_edit)
+ mode = OB_MODE_HAIR_EDIT;
return brush->ob_mode & mode;
}
@@ -429,6 +433,28 @@ static char *rna_GPencilSculptBrush_path(PointerRNA *UNUSED(ptr))
return BLI_strdup("tool_settings.gpencil_sculpt.brush");
}
+/* ==== Hair Edit ==== */
+
+static char *rna_HairEdit_path(PointerRNA *UNUSED(ptr))
+{
+ return BLI_strdup("tool_settings.hair_edit");
+}
+
+static void rna_HairEdit_update(Main *UNUSED(bmain), Scene *scene, PointerRNA *UNUSED(ptr))
+{
+ Object *ob = OBACT;
+
+ if (ob)
+ DEG_id_tag_update(&ob->id, OB_RECALC_DATA);
+}
+
+static void rna_HairEdit_brush_update(Main *UNUSED(bmain), Scene *UNUSED(scene), PointerRNA *ptr)
+{
+ HairEditSettings *settings = ptr->data;
+ Brush *brush = settings->brush;
+ BKE_paint_invalidate_overlay_all();
+ WM_main_add_notifier(NC_BRUSH | NA_EDITED, brush);
+}
#else
@@ -1130,6 +1156,72 @@ static void rna_def_gpencil_sculpt(BlenderRNA *brna)
RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL);
}
+static void rna_def_hair_edit(BlenderRNA *brna)
+{
+ StructRNA *srna;
+ PropertyRNA *prop;
+
+ static EnumPropertyItem select_mode_items[] = {
+ {HAIR_SELECT_STRAND, "STRAND", ICON_PARTICLE_PATH, "Strand", "Strand edit mode"},
+ {HAIR_SELECT_VERTEX, "VERTEX", ICON_PARTICLE_POINT, "Vertex", "Vertex select mode"},
+ {HAIR_SELECT_TIP, "TIP", ICON_PARTICLE_TIP, "Tip", "Tip select mode"},
+ {0, NULL, 0, NULL, NULL}
+ };
+
+ static EnumPropertyItem hair_draw_mode_items[] = {
+ {HAIR_DRAW_NONE, "NONE", ICON_NONE, "None", "Don't show hair while editing"},
+ {HAIR_DRAW_FIBERS, "FIBERS", ICON_STRANDS, "Fibers", "Draw hair fibers"},
+ {0, NULL, 0, NULL, NULL}
+ };
+
+ srna = RNA_def_struct(brna, "HairEdit", NULL);
+ RNA_def_struct_sdna(srna, "HairEditSettings");
+ RNA_def_struct_path_func(srna, "rna_HairEdit_path");
+ RNA_def_struct_ui_text(srna, "Hair Edit", "Settings for hair editing mode");
+
+ prop = RNA_def_property(srna, "brush", PROP_POINTER, PROP_NONE);
+ RNA_def_property_flag(prop, PROP_EDITABLE);
+ RNA_def_property_pointer_funcs(prop, NULL, NULL, NULL, "rna_Brush_mode_poll");
+ RNA_def_property_ui_text(prop, "Brush", "Active Brush");
+ RNA_def_property_update(prop, 0, "rna_HairEdit_brush_update");
+
+ prop = RNA_def_property(srna, "show_brush", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "flag", HAIR_EDIT_SHOW_BRUSH);
+ RNA_def_property_ui_text(prop, "Show Brush", "");
+ RNA_def_property_update(prop, NC_SCENE | ND_TOOLSETTINGS, NULL);
+
+ prop = RNA_def_property(srna, "select_mode", PROP_ENUM, PROP_NONE);
+ RNA_def_property_enum_sdna(prop, NULL, "select_mode");
+ RNA_def_property_enum_items(prop, select_mode_items);
+ RNA_def_property_ui_text(prop, "Selection Mode", "Hair selection mode");
+ RNA_def_property_update(prop, NC_OBJECT | ND_DRAW, "rna_HairEdit_update");
+
+ prop = RNA_def_property(srna, "hair_draw_mode", PROP_ENUM, PROP_NONE);
+ RNA_def_property_enum_sdna(prop, NULL, "hair_draw_mode");
+ RNA_def_property_enum_items(prop, hair_draw_mode_items);
+ RNA_def_property_ui_text(prop, "Hair Draw Mode", "Draw mode for edited hair results");
+ RNA_def_property_update(prop, NC_OBJECT | ND_DRAW, "rna_HairEdit_update");
+
+ prop = RNA_def_property(srna, "hair_draw_size", PROP_FLOAT, PROP_NONE);
+ RNA_def_property_float_sdna(prop, NULL, "hair_draw_size");
+ RNA_def_property_range(prop, 0.0f, FLT_MAX);
+ RNA_def_property_ui_range(prop, 1.0f, 10.0f, 1.0f, 1);
+ RNA_def_property_ui_text(prop, "Hair Draw Size", "Width of hair fibers in pixels");
+ RNA_def_property_update(prop, NC_OBJECT | ND_DRAW, "rna_HairEdit_update");
+
+ prop = RNA_def_property(srna, "hair_draw_subdivision", PROP_INT, PROP_NONE);
+ RNA_def_property_int_sdna(prop, NULL, "hair_draw_subdiv");
+ RNA_def_property_range(prop, 0, INT_MAX);
+ RNA_def_property_ui_range(prop, 0, 5, 1, -1);
+ RNA_def_property_ui_text(prop, "Hair Draw Subdivision", "Subdivide hair fibers");
+ RNA_def_property_update(prop, NC_OBJECT | ND_DRAW, "rna_HairEdit_update");
+
+ prop = RNA_def_property(srna, "shape_object", PROP_POINTER, PROP_NONE);
+ RNA_def_property_flag(prop, PROP_EDITABLE);
+ RNA_def_property_ui_text(prop, "Shape Object", "Outer shape to use for tools");
+ RNA_def_property_update(prop, NC_OBJECT | ND_DRAW, "rna_HairEdit_update");
+}
+
void RNA_def_sculpt_paint(BlenderRNA *brna)
{
/* *** Non-Animated *** */
@@ -1142,6 +1234,7 @@ void RNA_def_sculpt_paint(BlenderRNA *brna)
rna_def_image_paint(brna);
rna_def_particle_edit(brna);
rna_def_gpencil_sculpt(brna);
+ rna_def_hair_edit(brna);
RNA_define_animate_sdna(true);
}
diff --git a/source/blender/modifiers/CMakeLists.txt b/source/blender/modifiers/CMakeLists.txt
index d6a7b94505f..0632578cfc4 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_hair.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..cf3794dfaa5 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_Hair;
/* MOD_util.c */
void modifier_type_init(ModifierTypeInfo *types[]);
diff --git a/source/blender/modifiers/intern/MOD_hair.c b/source/blender/modifiers/intern/MOD_hair.c
new file mode 100644
index 00000000000..d17a629e055
--- /dev/null
+++ b/source/blender/modifiers/intern/MOD_hair.c
@@ -0,0 +1,134 @@
+/*
+ * ***** 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_displace.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_editstrands.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)
+{
+ HairModifierData *hmd = (HairModifierData *) md;
+
+ hmd->hair = BKE_hair_new();
+
+ hmd->flag |= 0;
+
+ hmd->edit = NULL;
+}
+
+static void copyData(ModifierData *md, ModifierData *target)
+{
+ HairModifierData *hmd = (HairModifierData *) md;
+ HairModifierData *thmd = (HairModifierData *) target;
+
+ if (thmd->hair) {
+ BKE_hair_free(thmd->hair);
+ }
+
+ modifier_copyData_generic(md, target);
+
+ if (hmd->hair) {
+ thmd->hair = BKE_hair_copy(hmd->hair);
+ }
+
+ thmd->edit = NULL;
+}
+
+static void freeData(ModifierData *md)
+{
+ HairModifierData *hmd = (HairModifierData *) md;
+
+ if (hmd->hair) {
+ BKE_hair_free(hmd->hair);
+ }
+
+ if (hmd->edit) {
+ BKE_editstrands_free(hmd->edit);
+ MEM_freeN(hmd->edit);
+ }
+}
+
+static DerivedMesh *applyModifier(ModifierData *md, const struct EvaluationContext *UNUSED(eval_ctx),
+ Object *UNUSED(ob), DerivedMesh *dm,
+ ModifierApplyFlag UNUSED(flag))
+{
+ HairModifierData *hmd = (HairModifierData *) md;
+
+ UNUSED_VARS(hmd);
+
+ return dm;
+}
+
+ModifierTypeInfo modifierType_Hair = {
+ /* name */ "Hair",
+ /* structName */ "HairModifierData",
+ /* structSize */ sizeof(HairModifierData),
+ /* 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,
+ /* updateDepgraph */ NULL,
+ /* updateDepsgraph */ NULL,
+ /* dependsOnTime */ NULL,
+ /* dependsOnNormals */ NULL,
+ /* foreachObjectLink */ NULL,
+ /* foreachIDLink */ NULL,
+ /* foreachTexLink */ NULL,
+};
diff --git a/source/blender/modifiers/intern/MOD_util.c b/source/blender/modifiers/intern/MOD_util.c
index ded1f0b77e6..2edc00a1331 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(Hair);
#undef INIT_TYPE
}
diff --git a/source/blender/physics/BPH_strands.h b/source/blender/physics/BPH_strands.h
new file mode 100644
index 00000000000..068c47f1c4c
--- /dev/null
+++ b/source/blender/physics/BPH_strands.h
@@ -0,0 +1,44 @@
+/*
+ * ***** 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 __BPH_STRANDS_H__
+#define __BPH_STRANDS_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct Object;
+struct BMEditStrands;
+
+void BPH_strands_solve_constraints(struct Object *ob, struct BMEditStrands *es, float (*orig)[3]);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/source/blender/physics/CMakeLists.txt b/source/blender/physics/CMakeLists.txt
index 0a4ff3fe0f0..81c4bcdac31 100644
--- a/source/blender/physics/CMakeLists.txt
+++ b/source/blender/physics/CMakeLists.txt
@@ -28,6 +28,7 @@ set(INC
intern
../blenlib
../blenkernel
+ ../bmesh
../imbuf
../makesdna
../../../intern/guardedalloc
@@ -45,8 +46,10 @@ set(SRC
intern/implicit_blender.c
intern/implicit_eigen.cpp
intern/eigen_utils.h
+ intern/strands.cpp
BPH_mass_spring.h
+ BPH_strands.h
)
blender_add_lib(bf_physics "${SRC}" "${INC}" "${INC_SYS}")
diff --git a/source/blender/physics/intern/eigen_utils.h b/source/blender/physics/intern/eigen_utils.h
index e4a4f306aeb..ebfed7cb690 100644
--- a/source/blender/physics/intern/eigen_utils.h
+++ b/source/blender/physics/intern/eigen_utils.h
@@ -39,6 +39,7 @@
#endif
#include <Eigen/Sparse>
+#include <Eigen/SVD>
#include <Eigen/src/Core/util/DisableStupidWarnings.h>
#ifdef __GNUC__
@@ -113,6 +114,7 @@ public:
};
typedef Eigen::VectorXf lVector;
+typedef Eigen::VectorXf VectorX;
/* Extension of dense Eigen vectors,
* providing 3-float block access for blenlib math functions
@@ -146,6 +148,7 @@ public:
typedef Eigen::Triplet<Scalar> Triplet;
typedef std::vector<Triplet> TripletList;
+typedef Eigen::MatrixXf MatrixX;
typedef Eigen::SparseMatrix<Scalar> lMatrix;
/* Constructor type that provides more convenient handling of Eigen triplets
@@ -201,6 +204,33 @@ typedef Eigen::ConjugateGradient<lMatrix, Eigen::Lower, Eigen::DiagonalPrecondit
using Eigen::ComputationInfo;
+template <typename MatrixType>
+BLI_INLINE MatrixType pseudo_inverse(const MatrixType& mat, double epsilon = std::numeric_limits<double>::epsilon())
+{
+ typedef Eigen::JacobiSVD<MatrixType> SVD;
+// typedef typename SVD::SingularValuesType SingularValues;
+
+ SVD svd(mat, Eigen::ComputeThinU | Eigen::ComputeThinV);
+
+ double tolerance = epsilon * std::max(mat.cols(), mat.rows()) * svd.singularValues().array().abs()(0);
+ return svd.matrixV() * (svd.singularValues().array().abs() > tolerance).select(svd.singularValues().array().inverse(), 0).matrix().asDiagonal() * svd.matrixU().adjoint();
+
+// const double pinvtoler=1.e-6; // choose your tolerance wisely!
+// SingularValues singularValues_inv = svd.singularValues();
+// for (long i = 0; i < mat.cols(); ++i) {
+// if (svd.singularValues(i) > pinvtoler )
+// singularValues_inv(i) = 1.0 / svd.singularValues(i);
+// else singularValues_inv(i) = 0;
+// }
+
+// return (svd.matrixV() * singularValues_inv.asDiagonal() * svd.matrixU().transpose());
+}
+
+BLI_INLINE void print_matrix_elem(float v)
+{
+ printf("%-8.3f", v);
+}
+
BLI_INLINE void print_lvector(const lVector3f &v)
{
for (int i = 0; i < v.rows(); ++i) {
@@ -221,7 +251,7 @@ BLI_INLINE void print_lmatrix(const lMatrix &m)
if (i > 0 && i % 3 == 0)
printf(" ");
- implicit_print_matrix_elem(m.coeff(j, i));
+ print_matrix_elem(m.coeff(j, i));
}
printf("\n");
}
diff --git a/source/blender/physics/intern/implicit.h b/source/blender/physics/intern/implicit.h
index 2f62ab98e12..d632ce267a1 100644
--- a/source/blender/physics/intern/implicit.h
+++ b/source/blender/physics/intern/implicit.h
@@ -67,11 +67,6 @@ typedef struct ImplicitSolverResult {
float error;
} ImplicitSolverResult;
-BLI_INLINE void implicit_print_matrix_elem(float v)
-{
- printf("%-8.3f", v);
-}
-
void BPH_mass_spring_set_vertex_mass(struct Implicit_Data *data, int index, float mass);
void BPH_mass_spring_set_rest_transform(struct Implicit_Data *data, int index, float rot[3][3]);
diff --git a/source/blender/physics/intern/implicit_blender.c b/source/blender/physics/intern/implicit_blender.c
index 16cd335dc0c..bfc78f140b0 100644
--- a/source/blender/physics/intern/implicit_blender.c
+++ b/source/blender/physics/intern/implicit_blender.c
@@ -333,7 +333,7 @@ static void print_bfmatrix(fmatrix3x3 *m)
if (i > 0 && i % 3 == 0)
printf(" ");
- implicit_print_matrix_elem(t[i + j * size]);
+ print_matrix_elem(t[i + j * size]);
}
printf("\n");
}
diff --git a/source/blender/physics/intern/implicit_eigen.cpp b/source/blender/physics/intern/implicit_eigen.cpp
index ff4c705ed61..db27d8ee223 100644
--- a/source/blender/physics/intern/implicit_eigen.cpp
+++ b/source/blender/physics/intern/implicit_eigen.cpp
@@ -268,7 +268,7 @@ static void print_lmatrix(const lMatrix &m)
if (i > 0 && i % 3 == 0)
printf(" ");
- implicit_print_matrix_elem(m.coeff(j, i));
+ print_matrix_elem(m.coeff(j, i));
}
printf("\n");
}
diff --git a/source/blender/physics/intern/strands.cpp b/source/blender/physics/intern/strands.cpp
new file mode 100644
index 00000000000..6d7c8e58f51
--- /dev/null
+++ b/source/blender/physics/intern/strands.cpp
@@ -0,0 +1,434 @@
+/*
+ * ***** 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/physics/intern/strands.c
+ * \ingroup bke
+ */
+
+extern "C" {
+#include "MEM_guardedalloc.h"
+
+#include "BLI_math.h"
+
+#include "DNA_customdata_types.h"
+#include "DNA_object_types.h"
+
+#include "BKE_bvhutils.h"
+#include "BKE_customdata.h"
+#include "BKE_cdderivedmesh.h"
+#include "BKE_DerivedMesh.h"
+#include "BKE_editstrands.h"
+#include "BKE_effect.h"
+#include "BKE_mesh_sample.h"
+
+#include "bmesh.h"
+}
+
+#include "BPH_strands.h"
+
+#include "eigen_utils.h"
+
+/* === constraints === */
+
+static bool strand_get_root_vectors(BMEditStrands *edit, BMVert *root, float loc[3], float nor[3], float tang[3])
+{
+ BMesh *bm = edit->base.bm;
+ DerivedMesh *root_dm = edit->root_dm;
+ MeshSample root_sample;
+
+ BM_elem_meshsample_data_named_get(&bm->vdata, root, CD_MSURFACE_SAMPLE, CD_HAIR_ROOT_LOCATION, &root_sample);
+ return BKE_mesh_sample_eval(root_dm, &root_sample, loc, nor, tang);
+}
+
+static int strand_count_vertices(BMVert *root)
+{
+ BMVert *v;
+ BMIter iter;
+
+ int len = 0;
+ BM_ITER_STRANDS_ELEM(v, &iter, root, BM_VERTS_OF_STRAND) {
+ ++len;
+ }
+ return len;
+}
+
+static int UNUSED_FUNCTION(strands_get_max_length)(BMEditStrands *edit)
+{
+ BMesh *bm = edit->base.bm;
+ BMVert *root;
+ BMIter iter;
+ int maxlen = 0;
+
+ BM_ITER_STRANDS(root, &iter, bm, BM_STRANDS_OF_MESH) {
+ int len = strand_count_vertices(root);
+ if (len > maxlen)
+ maxlen = len;
+ }
+ return maxlen;
+}
+
+static void strands_apply_root_locations(BMEditStrands *edit)
+{
+ BMesh *bm = edit->base.bm;
+ BMVert *root;
+ BMIter iter;
+
+ if (!edit->root_dm)
+ return;
+
+ BM_ITER_STRANDS(root, &iter, bm, BM_STRANDS_OF_MESH) {
+ float loc[3], nor[3], tang[3];
+
+ if (strand_get_root_vectors(edit, root, loc, nor, tang))
+ copy_v3_v3(root->co, loc);
+ }
+}
+
+static void strands_adjust_segment_lengths(BMesh *bm)
+{
+ BMVert *root, *v, *vprev;
+ BMIter iter, iter_strand;
+ int k;
+
+ BM_ITER_STRANDS(root, &iter, bm, BM_STRANDS_OF_MESH) {
+ BM_ITER_STRANDS_ELEM_INDEX(v, &iter_strand, root, BM_VERTS_OF_STRAND, k) {
+ if (k > 0) {
+ float base_length = BM_elem_float_data_named_get(&bm->vdata, v, CD_PROP_FLT, CD_HAIR_SEGMENT_LENGTH);
+ float dist[3];
+ float length;
+
+ sub_v3_v3v3(dist, v->co, vprev->co);
+ length = len_v3(dist);
+ if (length > 0.0f)
+ madd_v3_v3v3fl(v->co, vprev->co, dist, base_length / length);
+ }
+ vprev = v;
+ }
+ }
+}
+
+/* try to find a nice solution to keep distances between neighboring keys */
+/* XXX Stub implementation ported from particles:
+ * Successively relax each segment starting from the root,
+ * repeat this for every vertex (O(n^2) !!)
+ * This should be replaced by a more advanced method using a least-squares
+ * error metric with length and root location constraints (IK solver)
+ */
+static void strands_solve_edge_relaxation(BMEditStrands *edit)
+{
+ BMesh *bm = edit->base.bm;
+ const int Nmax = BM_strands_keys_count_max(bm);
+ /* cache for vertex positions and segment lengths, for easier indexing */
+ float **co = (float **)MEM_mallocN(sizeof(float*) * Nmax, "strand positions");
+ float *target_length = (float *)MEM_mallocN(sizeof(float) * Nmax, "strand segment lengths");
+
+ BMVert *root;
+ BMIter iter;
+ BM_ITER_STRANDS(root, &iter, bm, BM_STRANDS_OF_MESH) {
+ const int S = 1; /* TODO particles use PE_LOCK_FIRST option */
+ const int N = BM_strands_keys_count(root);
+ const float divN = 1.0f / (float)N;
+
+ /* setup positions cache */
+ {
+ BMVert *v;
+ BMIter viter;
+ int k;
+ BM_ITER_STRANDS_ELEM_INDEX(v, &viter, root, BM_VERTS_OF_STRAND, k) {
+ co[k] = v->co;
+ target_length[k] = BM_elem_float_data_named_get(&bm->vdata, v, CD_PROP_FLT, CD_HAIR_SEGMENT_LENGTH);
+ }
+ }
+
+ for (int iter = 1; iter < N; iter++) {
+ float correct_first[3] = {0.0f, 0.0f, 0.0f};
+ float correct_second[3] = {0.0f, 0.0f, 0.0f};
+
+ for (int k = S; k < N; k++) {
+ if (k > 0) {
+ /* calculate correction for the first vertex */
+ float dir[3];
+ sub_v3_v3v3(dir, co[k-1], co[k]);
+ float length = normalize_v3(dir);
+
+ mul_v3_v3fl(correct_first, dir, divN * (length - target_length[k]));
+ }
+
+ if (k < N-1) {
+ /* calculate correction for the second vertex */
+ float dir[3];
+ sub_v3_v3v3(dir, co[k+1], co[k]);
+ float length_next = normalize_v3(dir);
+
+ mul_v3_v3fl(correct_second, dir, divN * (length_next - target_length[k+1]));
+ }
+
+ /* apply both corrections (try to satisfy both sides equally) */
+ add_v3_v3(co[k], correct_first);
+ add_v3_v3(co[k], correct_second);
+ }
+ }
+ }
+
+ if (co)
+ MEM_freeN(co);
+ if (target_length)
+ MEM_freeN(target_length);
+
+ strands_adjust_segment_lengths(bm);
+}
+
+typedef struct IKTarget {
+ BMVert *vertex;
+ float weight;
+} IKTarget;
+
+static int strand_find_ik_targets(BMVert *root, IKTarget *targets)
+{
+ BMVert *v;
+ BMIter iter;
+ int k, index;
+
+ index = 0;
+ BM_ITER_STRANDS_ELEM_INDEX(v, &iter, root, BM_VERTS_OF_STRAND, k) {
+ /* XXX TODO allow multiple targets and do weight calculation here */
+ if (BM_strands_vert_is_tip(v)) {
+ IKTarget *target = &targets[index];
+ target->vertex = v;
+ target->weight = 1.0f;
+ ++index;
+ }
+ }
+
+ return index;
+}
+
+static void calc_jacobian_entry(Object *ob, BMEditStrands *UNUSED(edit), IKTarget *target, int index_target, int index_angle,
+ const float point[3], const float axis1[3], const float axis2[3], MatrixX &J)
+{
+ float (*obmat)[4] = ob->obmat;
+
+ float dist[3], jac1[3], jac2[3];
+
+ sub_v3_v3v3(dist, target->vertex->co, point);
+
+ cross_v3_v3v3(jac1, axis1, dist);
+ cross_v3_v3v3(jac2, axis2, dist);
+
+ for (int i = 0; i < 3; ++i) {
+ J.coeffRef(index_target + i, index_angle + 0) = jac1[i];
+ J.coeffRef(index_target + i, index_angle + 1) = jac2[i];
+ }
+
+#if 1
+ {
+ float wco[3], wdir[3];
+
+ mul_v3_m4v3(wco, obmat, point);
+
+ mul_v3_m4v3(wdir, obmat, jac1);
+ BKE_sim_debug_data_add_vector(wco, wdir, 1,1,0, "strands", index_angle, 1);
+ mul_v3_m4v3(wdir, obmat, jac2);
+ BKE_sim_debug_data_add_vector(wco, wdir, 0,1,1, "strands", index_angle + 1, 2);
+ }
+#endif
+}
+
+static MatrixX strand_calc_target_jacobian(Object *ob, BMEditStrands *edit, BMVert *root, int numjoints, IKTarget *targets, int numtargets)
+{
+ BMVert *v, *vprev;
+ BMIter iter_strand;
+ int k;
+
+ float loc[3], axis[3], dir[3];
+
+ MatrixX J(3 * numtargets, 2 * numjoints);
+ if (!strand_get_root_vectors(edit, root, loc, dir, axis)) {
+ return J;
+ }
+
+ BM_ITER_STRANDS_ELEM_INDEX(v, &iter_strand, root, BM_VERTS_OF_STRAND, k) {
+ float dirprev[3];
+
+ if (k > 0) {
+ float rot[3][3];
+
+ copy_v3_v3(dirprev, dir);
+ sub_v3_v3v3(dir, v->co, vprev->co);
+ normalize_v3(dir);
+
+ rotation_between_vecs_to_mat3(rot, dirprev, dir);
+ mul_m3_v3(rot, axis);
+ }
+
+ calc_jacobian_entry(ob, edit, &targets[0], 0, 2*k, v->co, axis, dir, J);
+
+#if 0
+ {
+ float (*obmat)[4] = ob->obmat;
+ float wco[3], wdir[3];
+
+ mul_v3_m4v3(wco, obmat, v->co);
+
+ mul_v3_m4v3(wdir, obmat, axis);
+ BKE_sim_debug_data_add_vector(edit->debug_data, wco, wdir, 1,0,0, "strands", BM_elem_index_get(v), 1);
+ mul_v3_m4v3(wdir, obmat, dir);
+ BKE_sim_debug_data_add_vector(edit->debug_data, wco, wdir, 0,1,0, "strands", BM_elem_index_get(v), 2);
+ cross_v3_v3v3(wdir, axis, dir);
+ mul_m4_v3(obmat, wdir);
+ BKE_sim_debug_data_add_vector(edit->debug_data, wco, wdir, 0,0,1, "strands", BM_elem_index_get(v), 3);
+ }
+#endif
+
+ vprev = v;
+ }
+
+ return J;
+}
+
+static VectorX strand_angles_to_loc(Object *UNUSED(ob), BMEditStrands *edit, BMVert *root, int numjoints, const VectorX &angles)
+{
+ BMesh *bm = edit->base.bm;
+ BMVert *v, *vprev;
+ BMIter iter_strand;
+ int k;
+
+ float loc[3], axis[3], dir[3];
+ float mat_theta[3][3], mat_phi[3][3];
+
+ if (!strand_get_root_vectors(edit, root, loc, dir, axis))
+ return VectorX();
+
+ VectorX result(3*numjoints);
+
+ BM_ITER_STRANDS_ELEM_INDEX(v, &iter_strand, root, BM_VERTS_OF_STRAND, k) {
+ float dirprev[3];
+
+ if (k > 0) {
+ const float base_length = BM_elem_float_data_named_get(&bm->vdata, v, CD_PROP_FLT, CD_HAIR_SEGMENT_LENGTH);
+ float rot[3][3];
+
+ copy_v3_v3(dirprev, dir);
+ sub_v3_v3v3(dir, v->co, vprev->co);
+ normalize_v3(dir);
+
+ rotation_between_vecs_to_mat3(rot, dirprev, dir);
+ mul_m3_v3(rot, axis);
+
+ /* apply rotations from previous joint on the vertex */
+ float vec[3];
+ mul_v3_v3fl(vec, dir, base_length);
+
+ mul_m3_v3(mat_theta, vec);
+ mul_m3_v3(mat_phi, vec);
+ add_v3_v3v3(&result.coeffRef(3*k), &result.coeff(3*(k-1)), vec);
+ }
+ else {
+ copy_v3_v3(&result.coeffRef(3*k), v->co);
+ }
+
+ float theta = angles[2*k + 0];
+ float phi = angles[2*k + 1];
+ axis_angle_normalized_to_mat3(mat_theta, axis, theta);
+ axis_angle_normalized_to_mat3(mat_phi, dir, phi);
+
+ vprev = v;
+ }
+
+ return result;
+}
+
+static void UNUSED_FUNCTION(strand_apply_ik_result)(Object *UNUSED(ob), BMEditStrands *UNUSED(edit), BMVert *root, const VectorX &solution)
+{
+ BMVert *v;
+ BMIter iter_strand;
+ int k;
+
+ BM_ITER_STRANDS_ELEM_INDEX(v, &iter_strand, root, BM_VERTS_OF_STRAND, k) {
+ copy_v3_v3(v->co, &solution.coeff(3*k));
+ }
+}
+
+static void strands_solve_inverse_kinematics(Object *ob, BMEditStrands *edit, float (*orig)[3])
+{
+ BMesh *bm = edit->base.bm;
+
+ BMVert *root;
+ BMIter iter;
+
+ BM_ITER_STRANDS(root, &iter, bm, BM_STRANDS_OF_MESH) {
+ int numjoints = strand_count_vertices(root);
+ if (numjoints <= 0)
+ continue;
+
+ IKTarget targets[1]; /* XXX placeholder, later should be allocated to max. strand length */
+ int numtargets = strand_find_ik_targets(root, targets);
+
+ MatrixX J = strand_calc_target_jacobian(ob, edit, root, numjoints, targets, numtargets);
+ MatrixX Jinv = pseudo_inverse(J, 1.e-6);
+
+ VectorX x(3 * numtargets);
+ for (int i = 0; i < numtargets; ++i) {
+ sub_v3_v3v3(&x.coeffRef(3*i), targets[i].vertex->co, orig[i]);
+ /* TODO calculate deviation of vertices from their origin (whatever that is) */
+// x[3*i + 0] = 0.0f;
+// x[3*i + 1] = 0.0f;
+// x[3*i + 2] = 0.0f;
+ }
+ VectorX angles = Jinv * x;
+ VectorX solution = strand_angles_to_loc(ob, edit, root, numjoints, angles);
+
+// strand_apply_ik_result(ob, edit, root, solution);
+
+#if 1
+ {
+ BMVert *v;
+ BMIter iter_strand;
+ int k;
+ float wco[3];
+
+ BM_ITER_STRANDS_ELEM_INDEX(v, &iter_strand, root, BM_VERTS_OF_STRAND, k) {
+ mul_v3_m4v3(wco, ob->obmat, &solution.coeff(3*k));
+ BKE_sim_debug_data_add_circle(wco, 0.05f, 1,0,1, "strands", k, BM_elem_index_get(root), 2344);
+ }
+ }
+#endif
+ }
+}
+
+void BPH_strands_solve_constraints(Object *ob, BMEditStrands *edit, float (*orig)[3])
+{
+ strands_apply_root_locations(edit);
+
+ if (true) {
+ strands_solve_edge_relaxation(edit);
+ }
+ else {
+ if (orig)
+ strands_solve_inverse_kinematics(ob, edit, orig);
+ }
+}
diff --git a/source/blender/windowmanager/WM_types.h b/source/blender/windowmanager/WM_types.h
index 7fe9f3a8ab4..bb7384a64c1 100644
--- a/source/blender/windowmanager/WM_types.h
+++ b/source/blender/windowmanager/WM_types.h
@@ -384,6 +384,7 @@ typedef struct wmNotifier {
#define NS_EDITMODE_ARMATURE (8<<8)
#define NS_MODE_POSE (9<<8)
#define NS_MODE_PARTICLE (10<<8)
+#define NS_MODE_HAIR (11<<8)
/* subtype 3d view editing */
#define NS_VIEW3D_GPU (16<<8)