diff options
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 Binary files differnew file mode 100644 index 00000000000..074111a5a0b --- /dev/null +++ b/release/datafiles/brushicons/hairadd.png diff --git a/release/datafiles/brushicons/haircomb.png b/release/datafiles/brushicons/haircomb.png Binary files differnew file mode 100644 index 00000000000..074111a5a0b --- /dev/null +++ b/release/datafiles/brushicons/haircomb.png diff --git a/release/datafiles/brushicons/haircut.png b/release/datafiles/brushicons/haircut.png Binary files differnew file mode 100644 index 00000000000..074111a5a0b --- /dev/null +++ b/release/datafiles/brushicons/haircut.png diff --git a/release/datafiles/brushicons/hairlength.png b/release/datafiles/brushicons/hairlength.png Binary files differnew file mode 100644 index 00000000000..074111a5a0b --- /dev/null +++ b/release/datafiles/brushicons/hairlength.png diff --git a/release/datafiles/brushicons/hairpuff.png b/release/datafiles/brushicons/hairpuff.png Binary files differnew file mode 100644 index 00000000000..074111a5a0b --- /dev/null +++ b/release/datafiles/brushicons/hairpuff.png diff --git a/release/datafiles/brushicons/hairsmooth.png b/release/datafiles/brushicons/hairsmooth.png Binary files differnew file mode 100644 index 00000000000..074111a5a0b --- /dev/null +++ b/release/datafiles/brushicons/hairsmooth.png diff --git a/release/datafiles/brushicons/hairweight.png b/release/datafiles/brushicons/hairweight.png Binary files differnew file mode 100644 index 00000000000..074111a5a0b --- /dev/null +++ b/release/datafiles/brushicons/hairweight.png 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, ¶ms); + 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, ¶ms); + +#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 = ½ + } + } + + /* 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 = ½ + } + } + + /* 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 = ½ - } - } - - /* 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, ¶ms); + + 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, ¶ms); + + /* 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) |