diff options
62 files changed, 8056 insertions, 28 deletions
diff --git a/release/scripts/startup/bl_ui/__init__.py b/release/scripts/startup/bl_ui/__init__.py index b3131e64df7..86c376bcb21 100644 --- a/release/scripts/startup/bl_ui/__init__.py +++ b/release/scripts/startup/bl_ui/__init__.py @@ -67,6 +67,7 @@ _modules = [ "properties_scene", "properties_texture", "properties_world", + "properties_collection", # Generic Space Modules # @@ -104,6 +105,8 @@ import bpy if bpy.app.build_options.freestyle: _modules.append("properties_freestyle") +_modules.append("properties_lineart") + __import__(name=__name__, fromlist=_modules) _namespace = globals() _modules_loaded = [_namespace[name] for name in _modules] diff --git a/release/scripts/startup/bl_ui/properties_collection.py b/release/scripts/startup/bl_ui/properties_collection.py new file mode 100644 index 00000000000..fa4c3cdb779 --- /dev/null +++ b/release/scripts/startup/bl_ui/properties_collection.py @@ -0,0 +1,98 @@ +# ##### 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> +from bpy.types import Panel + + +class CollectionButtonsPanel: + bl_space_type = 'PROPERTIES' + bl_region_type = 'WINDOW' + bl_context = "collection" + + +def lineart_make_line_type_entry(col, line_type, text_disp, expand, search_from): + col.prop(line_type, "use", text=text_disp) + if line_type.use and expand: + col.prop_search(line_type, "layer", search_from, + "layers", icon='GREASEPENCIL') + col.prop_search(line_type, "material", search_from, + "materials", icon='SHADING_TEXTURE') + + +class COLLECTION_PT_collection_flags(CollectionButtonsPanel, Panel): + bl_label = "Restrictions" + + @classmethod + def poll(cls, context): + vl = context.view_layer + vlc = vl.active_layer_collection + return (vlc.name != 'Master Collection') + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + + collection = context.collection + vl = context.view_layer + vlc = vl.active_layer_collection + + col = layout.column(align=True) + col.prop(collection, "hide_select", text="Selectable", toggle=False, invert_checkbox=True) + col.prop(collection, "hide_render", toggle=False) + + col = layout.column(align=True) + col.prop(vlc, "holdout", toggle=False) + col.prop(vlc, "indirect_only", toggle=False) + + +class COLLECTION_PT_lineart_collection(CollectionButtonsPanel, Panel): + bl_label = "Line Art" + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + collection = context.collection + + row = layout.row() + row.prop(collection, "lineart_usage") + +class COLLECTION_PT_instancing(CollectionButtonsPanel, Panel): + bl_label = "Instancing" + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + collection = context.collection + + row = layout.row() + row.prop(collection, "instance_offset") + +classes = ( + COLLECTION_PT_collection_flags, + COLLECTION_PT_lineart_collection, + COLLECTION_PT_instancing, +) + +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_lineart.py b/release/scripts/startup/bl_ui/properties_lineart.py new file mode 100644 index 00000000000..7c4d6c7be0b --- /dev/null +++ b/release/scripts/startup/bl_ui/properties_lineart.py @@ -0,0 +1,59 @@ +# ##### 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> +from bpy.types import Panel + + +class LineartButtonsPanel: + bl_space_type = 'PROPERTIES' + bl_region_type = 'WINDOW' + bl_context = "object" + + +class OBJECT_PT_lineart(LineartButtonsPanel, Panel): + bl_label = "Line Art" + bl_options = {'DEFAULT_CLOSED'} + + @classmethod + def poll(cls, context): + ob = context.object + return (ob.type in {'MESH', 'FONT', 'CURVE', 'SURFACE'}) + + def draw(self, context): + layout = self.layout + lineart = context.object.lineart + + layout.use_property_split = True + + layout.prop(lineart, 'usage') + layout.use_property_split = True + + row = layout.row(heading="Override Crease") + row.prop(lineart, "use_crease_override", text="") + row.prop(lineart, "crease_threshold", slider=True, text="") + + +classes = ( + OBJECT_PT_lineart, +) + +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_material.py b/release/scripts/startup/bl_ui/properties_material.py index 47ab98386f4..ebd91143239 100644 --- a/release/scripts/startup/bl_ui/properties_material.py +++ b/release/scripts/startup/bl_ui/properties_material.py @@ -274,6 +274,38 @@ class MATERIAL_PT_viewport(MaterialButtonsPanel, Panel): col.prop(mat, "roughness") +class MATERIAL_PT_lineart(MaterialButtonsPanel, Panel): + bl_label = "Line Art" + bl_options = {'DEFAULT_CLOSED'} + + @classmethod + def poll(cls, context): + mat = context.material + return mat and not mat.grease_pencil + + def draw(self, context): + layout = self.layout + + mat = context.material + lineart = mat.lineart + + layout.prop(lineart, "use_transparency") + + if lineart.use_transparency: + + layout.label(text="Transparency Masks:") + + row = layout.row(align=True) + row.prop(lineart, "transparency_mask_0", text="0", toggle=True) + row.prop(lineart, "transparency_mask_1", text="1", toggle=True) + row.prop(lineart, "transparency_mask_2", text="2", toggle=True) + row.prop(lineart, "transparency_mask_3", text="3", toggle=True) + row.prop(lineart, "transparency_mask_4", text="4", toggle=True) + row.prop(lineart, "transparency_mask_5", text="5", toggle=True) + row.prop(lineart, "transparency_mask_6", text="6", toggle=True) + row.prop(lineart, "transparency_mask_7", text="7", toggle=True) + + classes = ( MATERIAL_MT_context_menu, MATERIAL_UL_matslots, @@ -282,6 +314,7 @@ classes = ( EEVEE_MATERIAL_PT_surface, EEVEE_MATERIAL_PT_volume, EEVEE_MATERIAL_PT_settings, + MATERIAL_PT_lineart, MATERIAL_PT_viewport, EEVEE_MATERIAL_PT_viewport_settings, MATERIAL_PT_custom_props, diff --git a/release/scripts/startup/bl_ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py index 2742b75a68b..6980181a725 100644 --- a/release/scripts/startup/bl_ui/space_view3d.py +++ b/release/scripts/startup/bl_ui/space_view3d.py @@ -3829,11 +3829,10 @@ class VIEW3D_MT_edit_mesh_context_menu(Menu): col.operator("mesh.mark_sharp") col.operator("mesh.mark_sharp", text="Clear Sharp").clear = True - if render.use_freestyle: - col.separator() + col.separator() - col.operator("mesh.mark_freestyle_edge").clear = False - col.operator("mesh.mark_freestyle_edge", text="Clear Freestyle Edge").clear = True + col.operator("mesh.mark_freestyle_edge").clear = False + col.operator("mesh.mark_freestyle_edge", text="Clear Freestyle Edge").clear = True col.separator() @@ -4028,11 +4027,10 @@ class VIEW3D_MT_edit_mesh_edges_data(Menu): props.use_verts = True props.clear = True - if render.use_freestyle: - layout.separator() + layout.separator() - layout.operator("mesh.mark_freestyle_edge").clear = False - layout.operator("mesh.mark_freestyle_edge", text="Clear Freestyle Edge").clear = True + layout.operator("mesh.mark_freestyle_edge").clear = False + layout.operator("mesh.mark_freestyle_edge", text="Clear Freestyle Edge").clear = True class VIEW3D_MT_edit_mesh_edges(Menu): diff --git a/source/blender/CMakeLists.txt b/source/blender/CMakeLists.txt index f875a990d0a..8d18cf0ae9a 100644 --- a/source/blender/CMakeLists.txt +++ b/source/blender/CMakeLists.txt @@ -56,6 +56,7 @@ set(SRC_DNA_INC ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_layer_types.h ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_light_types.h ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_lightprobe_types.h + ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_lineart_types.h ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_linestyle_types.h ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_listBase.h ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_mask_types.h diff --git a/source/blender/blenkernel/BKE_collection.h b/source/blender/blenkernel/BKE_collection.h index d15aebfe03d..d0fca5e3796 100644 --- a/source/blender/blenkernel/BKE_collection.h +++ b/source/blender/blenkernel/BKE_collection.h @@ -82,6 +82,8 @@ struct Collection *BKE_collection_master_add(void); bool BKE_collection_has_object(struct Collection *collection, const struct Object *ob); bool BKE_collection_has_object_recursive(struct Collection *collection, struct Object *ob); +bool BKE_collection_has_object_recursive_instanced(struct Collection *collection, + struct Object *ob); struct Collection *BKE_collection_object_find(struct Main *bmain, struct Scene *scene, struct Collection *collection, @@ -123,6 +125,7 @@ bool BKE_collection_object_cyclic_check(struct Main *bmain, /* Object list cache. */ struct ListBase BKE_collection_object_cache_get(struct Collection *collection); +ListBase BKE_collection_object_cache_instanced_get(struct Collection *collection); void BKE_collection_object_cache_free(struct Collection *collection); struct Base *BKE_collection_or_layer_objects(const struct ViewLayer *view_layer, diff --git a/source/blender/blenkernel/BKE_global.h b/source/blender/blenkernel/BKE_global.h index b3d0e0c9f98..9e237679795 100644 --- a/source/blender/blenkernel/BKE_global.h +++ b/source/blender/blenkernel/BKE_global.h @@ -77,6 +77,7 @@ typedef struct Global { * * 1112: Disable new Cloth internal springs handling (09/2014). * * 1234: Disable new dyntopo code fixing skinny faces generation (04/2015). * * 3001: Enable additional Fluid modifier (Mantaflow) options (02/2020). + * * 4000: Line Art state output and debugging logs (03/2021). * * 16384 and above: Reserved for python (add-ons) usage. */ short debug_value; diff --git a/source/blender/blenkernel/BKE_gpencil.h b/source/blender/blenkernel/BKE_gpencil.h index 5cfdcf241d1..a0a3f30d6d8 100644 --- a/source/blender/blenkernel/BKE_gpencil.h +++ b/source/blender/blenkernel/BKE_gpencil.h @@ -212,6 +212,10 @@ void BKE_gpencil_layer_mask_sort(struct bGPdata *gpd, struct bGPDlayer *gpl); void BKE_gpencil_layer_mask_sort_all(struct bGPdata *gpd); void BKE_gpencil_layer_frames_sort(struct bGPDlayer *gpl, bool *r_has_duplicate_frames); +struct bGPDlayer *BKE_gpencil_layer_get_by_name(struct bGPdata *gpd, + char *name, + int first_if_not_found); + /* Brush */ struct Material *BKE_gpencil_brush_material_get(struct Brush *brush); void BKE_gpencil_brush_material_set(struct Brush *brush, struct Material *material); @@ -231,6 +235,7 @@ struct Material *BKE_gpencil_object_material_new(struct Main *bmain, int *r_index); int BKE_gpencil_object_material_index_get(struct Object *ob, struct Material *ma); +int BKE_gpencil_object_material_get_index_name(struct Object *ob, char *name); struct Material *BKE_gpencil_object_material_from_brush_get(struct Object *ob, struct Brush *brush); diff --git a/source/blender/blenkernel/BKE_gpencil_modifier.h b/source/blender/blenkernel/BKE_gpencil_modifier.h index c066c161f46..c6406c8478c 100644 --- a/source/blender/blenkernel/BKE_gpencil_modifier.h +++ b/source/blender/blenkernel/BKE_gpencil_modifier.h @@ -206,7 +206,8 @@ typedef struct GpencilModifierTypeInfo { * This function is optional. */ void (*updateDepsgraph)(struct GpencilModifierData *md, - const struct ModifierUpdateDepsgraphContext *ctx); + const struct ModifierUpdateDepsgraphContext *ctx, + const int mode); /** * Should return true if the modifier needs to be recalculated on time diff --git a/source/blender/blenkernel/intern/collection.c b/source/blender/blenkernel/intern/collection.c index 28b91dcc8ce..4e4134c7c8f 100644 --- a/source/blender/blenkernel/intern/collection.c +++ b/source/blender/blenkernel/intern/collection.c @@ -122,7 +122,9 @@ static void collection_copy_data(Main *bmain, ID *id_dst, const ID *id_src, cons } collection_dst->flag &= ~COLLECTION_HAS_OBJECT_CACHE; + collection_dst->flag &= ~COLLECTION_HAS_OBJECT_CACHE_INSTANCED; BLI_listbase_clear(&collection_dst->object_cache); + BLI_listbase_clear(&collection_dst->object_cache_instanced); BLI_listbase_clear(&collection_dst->gobject); BLI_listbase_clear(&collection_dst->children); @@ -214,8 +216,10 @@ static void collection_blend_write(BlendWriter *writer, ID *id, const void *id_a if (collection->id.us > 0 || BLO_write_is_undo(writer)) { /* Clean up, important in undo case to reduce false detection of changed data-blocks. */ collection->flag &= ~COLLECTION_HAS_OBJECT_CACHE; + collection->flag &= ~COLLECTION_HAS_OBJECT_CACHE_INSTANCED; collection->tag = 0; BLI_listbase_clear(&collection->object_cache); + BLI_listbase_clear(&collection->object_cache_instanced); BLI_listbase_clear(&collection->parents); /* write LibData */ @@ -246,8 +250,10 @@ void BKE_collection_blend_read_data(BlendDataReader *reader, Collection *collect BKE_previewimg_blend_read(reader, collection->preview); collection->flag &= ~COLLECTION_HAS_OBJECT_CACHE; + collection->flag &= ~COLLECTION_HAS_OBJECT_CACHE_INSTANCED; collection->tag = 0; BLI_listbase_clear(&collection->object_cache); + BLI_listbase_clear(&collection->object_cache_instanced); BLI_listbase_clear(&collection->parents); #ifdef USE_COLLECTION_COMPAT_28 @@ -773,7 +779,10 @@ const char *BKE_collection_ui_name_get(struct Collection *collection) /** \name Object List Cache * \{ */ -static void collection_object_cache_fill(ListBase *lb, Collection *collection, int parent_restrict) +static void collection_object_cache_fill(ListBase *lb, + Collection *collection, + int parent_restrict, + bool with_instances) { int child_restrict = collection->flag | parent_restrict; @@ -784,6 +793,10 @@ static void collection_object_cache_fill(ListBase *lb, Collection *collection, i base = MEM_callocN(sizeof(Base), "Object Base"); base->object = cob->ob; BLI_addtail(lb, base); + if (with_instances && cob->ob->instance_collection) { + collection_object_cache_fill( + lb, cob->ob->instance_collection, child_restrict, with_instances); + } } /* Only collection flags are checked here currently, object restrict flag is checked @@ -798,7 +811,7 @@ static void collection_object_cache_fill(ListBase *lb, Collection *collection, i } LISTBASE_FOREACH (CollectionChild *, child, &collection->children) { - collection_object_cache_fill(lb, child->collection, child_restrict); + collection_object_cache_fill(lb, child->collection, child_restrict, with_instances); } } @@ -809,7 +822,7 @@ ListBase BKE_collection_object_cache_get(Collection *collection) BLI_mutex_lock(&cache_lock); if (!(collection->flag & COLLECTION_HAS_OBJECT_CACHE)) { - collection_object_cache_fill(&collection->object_cache, collection, 0); + collection_object_cache_fill(&collection->object_cache, collection, 0, false); collection->flag |= COLLECTION_HAS_OBJECT_CACHE; } BLI_mutex_unlock(&cache_lock); @@ -818,11 +831,29 @@ ListBase BKE_collection_object_cache_get(Collection *collection) return collection->object_cache; } +ListBase BKE_collection_object_cache_instanced_get(Collection *collection) +{ + if (!(collection->flag & COLLECTION_HAS_OBJECT_CACHE_INSTANCED)) { + static ThreadMutex cache_lock = BLI_MUTEX_INITIALIZER; + + BLI_mutex_lock(&cache_lock); + if (!(collection->flag & COLLECTION_HAS_OBJECT_CACHE_INSTANCED)) { + collection_object_cache_fill(&collection->object_cache_instanced, collection, 0, true); + collection->flag |= COLLECTION_HAS_OBJECT_CACHE_INSTANCED; + } + BLI_mutex_unlock(&cache_lock); + } + + return collection->object_cache_instanced; +} + static void collection_object_cache_free(Collection *collection) { /* Clear own cache an for all parents, since those are affected by changes as well. */ collection->flag &= ~COLLECTION_HAS_OBJECT_CACHE; + collection->flag &= ~COLLECTION_HAS_OBJECT_CACHE_INSTANCED; BLI_freelistN(&collection->object_cache); + BLI_freelistN(&collection->object_cache_instanced); LISTBASE_FOREACH (CollectionParent *, parent, &collection->parents) { collection_object_cache_free(parent->collection); @@ -928,6 +959,16 @@ bool BKE_collection_has_object_recursive(Collection *collection, Object *ob) return (BLI_findptr(&objects, ob, offsetof(Base, object))); } +bool BKE_collection_has_object_recursive_instanced(Collection *collection, Object *ob) +{ + if (ELEM(NULL, collection, ob)) { + return false; + } + + const ListBase objects = BKE_collection_object_cache_instanced_get(collection); + return (BLI_findptr(&objects, ob, offsetof(Base, object))); +} + static Collection *collection_next_find(Main *bmain, Scene *scene, Collection *collection) { if (scene && collection == scene->master_collection) { diff --git a/source/blender/blenkernel/intern/gpencil.c b/source/blender/blenkernel/intern/gpencil.c index 00dcaad83db..3b46672f9cd 100644 --- a/source/blender/blenkernel/intern/gpencil.c +++ b/source/blender/blenkernel/intern/gpencil.c @@ -1663,6 +1663,31 @@ bGPDlayer *BKE_gpencil_layer_active_get(bGPdata *gpd) return NULL; } +bGPDlayer *BKE_gpencil_layer_get_by_name(bGPdata *gpd, char *name, int first_if_not_found) +{ + bGPDlayer *gpl; + int i = 0; + + /* error checking */ + if (ELEM(NULL, gpd, gpd->layers.first)) { + return NULL; + } + + /* loop over layers until found (assume only one active) */ + for (gpl = gpd->layers.first; gpl; gpl = gpl->next) { + if (STREQ(name, gpl->info)) { + return gpl; + } + i++; + } + + /* no such layer */ + if (first_if_not_found) { + return gpd->layers.first; + } + return NULL; +} + /** * Set active grease pencil layer. * \param gpd: Grease pencil data-block @@ -2421,6 +2446,21 @@ int BKE_gpencil_object_material_index_get(Object *ob, Material *ma) return -1; } +int BKE_gpencil_object_material_get_index_name(Object *ob, char *name) +{ + short *totcol = BKE_object_material_len_p(ob); + Material *read_ma = NULL; + for (short i = 0; i < *totcol; i++) { + read_ma = BKE_object_material_get(ob, i + 1); + /* Material names are like "MAMaterial.001" */ + if (STREQ(name, &read_ma->id.name[2])) { + return i; + } + } + + return -1; +} + /** * Create a default palette. * \param bmain: Main pointer diff --git a/source/blender/blenkernel/intern/object_update.c b/source/blender/blenkernel/intern/object_update.c index 69442b7646c..1e6a099040f 100644 --- a/source/blender/blenkernel/intern/object_update.c +++ b/source/blender/blenkernel/intern/object_update.c @@ -176,12 +176,14 @@ void BKE_object_handle_data_update(Depsgraph *depsgraph, Scene *scene, Object *o CustomData_MeshMasks cddata_masks = scene->customdata_mask; CustomData_MeshMasks_update(&cddata_masks, &CD_MASK_BAREMESH); - if (DEG_get_mode(depsgraph) == DAG_EVAL_RENDER) { - /* Make sure Freestyle edge/face marks appear in DM for render (see T40315). */ + /* Make sure Freestyle edge/face marks appear in DM for render (see T40315). Due to Line Art + * impementation, edge marks should also be shown in viewport. */ #ifdef WITH_FREESTYLE - cddata_masks.emask |= CD_MASK_FREESTYLE_EDGE; - cddata_masks.pmask |= CD_MASK_FREESTYLE_FACE; + cddata_masks.emask |= CD_MASK_FREESTYLE_EDGE; + cddata_masks.pmask |= CD_MASK_FREESTYLE_FACE; + cddata_masks.vmask |= CD_MASK_MDEFORMVERT; #endif + if (DEG_get_mode(depsgraph) == DAG_EVAL_RENDER) { /* Always compute UVs, vertex colors as orcos for render. */ cddata_masks.lmask |= CD_MASK_MLOOPUV | CD_MASK_MLOOPCOL; cddata_masks.vmask |= CD_MASK_ORCO | CD_MASK_PROP_COLOR; diff --git a/source/blender/blenlib/BLI_math_base.h b/source/blender/blenlib/BLI_math_base.h index c862290b262..028ca31a059 100644 --- a/source/blender/blenlib/BLI_math_base.h +++ b/source/blender/blenlib/BLI_math_base.h @@ -117,6 +117,9 @@ MINLINE float sasqrt(float fac); MINLINE float interpf(float a, float b, float t); MINLINE double interpd(double a, double b, double t); +MINLINE float ratiof(float min, float max, float pos); +MINLINE double ratiod(double min, double max, double pos); + /* NOTE: Compilers will upcast all types smaller than int to int when performing arithmetic * operation. */ MINLINE int square_s(short a); diff --git a/source/blender/blenlib/intern/math_base_inline.c b/source/blender/blenlib/intern/math_base_inline.c index 39945960e68..6481fac5a14 100644 --- a/source/blender/blenlib/intern/math_base_inline.c +++ b/source/blender/blenlib/intern/math_base_inline.c @@ -180,6 +180,18 @@ MINLINE double interpd(double target, double origin, double fac) return (fac * target) + (1.0f - fac) * origin; } +MINLINE float ratiof(float min, float max, float pos) +{ + float range = max - min; + return range == 0 ? 0 : ((pos - min) / range); +} + +MINLINE double ratiod(double min, double max, double pos) +{ + double range = max - min; + return range == 0 ? 0 : ((pos - min) / range); +} + /* used for zoom values*/ MINLINE float power_of_2(float val) { diff --git a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc index 96b4da34347..3cc2ec02165 100644 --- a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc +++ b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc @@ -2085,7 +2085,7 @@ void DepsgraphRelationBuilder::build_object_data_geometry(Object *object) if (mti->updateDepsgraph) { DepsNodeHandle handle = create_node_handle(obdata_ubereval_key); ctx.node = reinterpret_cast<::DepsNodeHandle *>(&handle); - mti->updateDepsgraph(md, &ctx); + mti->updateDepsgraph(md, &ctx, graph_->mode); } if (BKE_object_modifier_gpencil_use_time(object, md)) { TimeSourceKey time_src_key; diff --git a/source/blender/editors/gpencil/CMakeLists.txt b/source/blender/editors/gpencil/CMakeLists.txt index 1e91edbb3c0..47ae90acb74 100644 --- a/source/blender/editors/gpencil/CMakeLists.txt +++ b/source/blender/editors/gpencil/CMakeLists.txt @@ -36,6 +36,7 @@ set(SRC annotate_paint.c drawgpencil.c editaction_gpencil.c + gpencil_add_lineart.c gpencil_add_monkey.c gpencil_add_stroke.c gpencil_armature.c diff --git a/source/blender/editors/gpencil/gpencil_add_lineart.c b/source/blender/editors/gpencil/gpencil_add_lineart.c new file mode 100644 index 00000000000..71253635ea8 --- /dev/null +++ b/source/blender/editors/gpencil/gpencil_add_lineart.c @@ -0,0 +1,120 @@ +/* + * 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 Blender Foundation + * This is a new part of Blender + */ + +/** \file + * \ingroup edgpencil + */ + +#include "BLI_math.h" +#include "BLI_utildefines.h" + +#include "DNA_gpencil_types.h" +#include "DNA_material_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" + +#include "BKE_brush.h" +#include "BKE_context.h" +#include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" +#include "BKE_lib_id.h" +#include "BKE_main.h" +#include "BKE_material.h" + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_query.h" + +#include "ED_gpencil.h" + +/* Definition of the most important info from a color */ +typedef struct ColorTemplate { + const char *name; + float line[4]; + float fill[4]; +} ColorTemplate; + +/* Add color an ensure duplications (matched by name) */ +static int gpencil_lineart_material(Main *bmain, + Object *ob, + const ColorTemplate *pct, + const bool fill) +{ + short *totcol = BKE_object_material_len_p(ob); + Material *ma = NULL; + for (short i = 0; i < *totcol; i++) { + ma = BKE_gpencil_material(ob, i + 1); + if (STREQ(ma->id.name, pct->name)) { + return i; + } + } + + int idx; + + /* create a new one */ + ma = BKE_gpencil_object_material_new(bmain, ob, pct->name, &idx); + + copy_v4_v4(ma->gp_style->stroke_rgba, pct->line); + srgb_to_linearrgb_v4(ma->gp_style->stroke_rgba, ma->gp_style->stroke_rgba); + + copy_v4_v4(ma->gp_style->fill_rgba, pct->fill); + srgb_to_linearrgb_v4(ma->gp_style->fill_rgba, ma->gp_style->fill_rgba); + + if (fill) { + ma->gp_style->flag |= GP_MATERIAL_FILL_SHOW; + } + + return idx; +} + +/* ***************************************************************** */ +/* Color Data */ + +static const ColorTemplate gp_stroke_material_black = { + "Black", + {0.0f, 0.0f, 0.0f, 1.0f}, + {0.0f, 0.0f, 0.0f, 0.0f}, +}; + +/* ***************************************************************** */ +/* LineArt API */ + +/* Add a Simple LineArt setup. */ +void ED_gpencil_create_lineart(bContext *C, Object *ob) +{ + Main *bmain = CTX_data_main(C); + Scene *scene = CTX_data_scene(C); + bGPdata *gpd = (bGPdata *)ob->data; + + /* create colors */ + int color_black = gpencil_lineart_material(bmain, ob, &gp_stroke_material_black, false); + + /* set first color as active and in brushes */ + ob->actcol = color_black + 1; + + /* layers */ + bGPDlayer *lines = BKE_gpencil_layer_addnew(gpd, "Lines", true); + + /* frames */ + BKE_gpencil_frame_addnew(lines, CFRA); + + /* update depsgraph */ + /* To trigger modifier update, this is still needed although we don't have any strokes. */ + DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY); + gpd->flag |= GP_DATA_CACHE_IS_DIRTY; +} diff --git a/source/blender/editors/include/ED_gpencil.h b/source/blender/editors/include/ED_gpencil.h index 2fbf280475b..f3b5abb1072 100644 --- a/source/blender/editors/include/ED_gpencil.h +++ b/source/blender/editors/include/ED_gpencil.h @@ -250,6 +250,7 @@ void ED_gpencil_brush_draw_eraser(struct Brush *brush, int x, int y); void ED_gpencil_create_monkey(struct bContext *C, struct Object *ob, float mat[4][4]); void ED_gpencil_create_stroke(struct bContext *C, struct Object *ob, float mat[4][4]); +void ED_gpencil_create_lineart(struct bContext *C, struct Object *ob); /* ------------ Object Utilities ------------ */ struct Object *ED_gpencil_add_object(struct bContext *C, diff --git a/source/blender/editors/mesh/editmesh_tools.c b/source/blender/editors/mesh/editmesh_tools.c index 68ac2842bab..8bb557509e0 100644 --- a/source/blender/editors/mesh/editmesh_tools.c +++ b/source/blender/editors/mesh/editmesh_tools.c @@ -7746,7 +7746,7 @@ void MESH_OT_symmetry_snap(struct wmOperatorType *ot) /** \} */ -#ifdef WITH_FREESTYLE +#if defined(WITH_FREESTYLE) /* -------------------------------------------------------------------- */ /** \name Mark Edge (Freestyle) Operator diff --git a/source/blender/editors/mesh/mesh_intern.h b/source/blender/editors/mesh/mesh_intern.h index 21feddfb886..763bdf04d83 100644 --- a/source/blender/editors/mesh/mesh_intern.h +++ b/source/blender/editors/mesh/mesh_intern.h @@ -267,7 +267,7 @@ void MESH_OT_paint_mask_slice(struct wmOperatorType *ot); struct wmKeyMap *point_normals_modal_keymap(wmKeyConfig *keyconf); -#ifdef WITH_FREESTYLE +#if defined(WITH_FREESTYLE) void MESH_OT_mark_freestyle_edge(struct wmOperatorType *ot); void MESH_OT_mark_freestyle_face(struct wmOperatorType *ot); #endif diff --git a/source/blender/editors/mesh/mesh_ops.c b/source/blender/editors/mesh/mesh_ops.c index 27d73497b49..34e9a3f45a5 100644 --- a/source/blender/editors/mesh/mesh_ops.c +++ b/source/blender/editors/mesh/mesh_ops.c @@ -130,7 +130,7 @@ void ED_operatortypes_mesh(void) WM_operatortype_append(MESH_OT_loop_multi_select); WM_operatortype_append(MESH_OT_mark_seam); WM_operatortype_append(MESH_OT_mark_sharp); -#ifdef WITH_FREESTYLE +#if defined(WITH_FREESTYLE) WM_operatortype_append(MESH_OT_mark_freestyle_edge); #endif WM_operatortype_append(MESH_OT_vertices_smooth); diff --git a/source/blender/editors/object/object_add.c b/source/blender/editors/object/object_add.c index dc941f12eba..e4527740164 100644 --- a/source/blender/editors/object/object_add.c +++ b/source/blender/editors/object/object_add.c @@ -31,6 +31,7 @@ #include "DNA_camera_types.h" #include "DNA_collection_types.h" #include "DNA_curve_types.h" +#include "DNA_gpencil_modifier_types.h" #include "DNA_gpencil_types.h" #include "DNA_key_types.h" #include "DNA_light_types.h" @@ -68,6 +69,7 @@ #include "BKE_geometry_set.h" #include "BKE_gpencil_curve.h" #include "BKE_gpencil_geom.h" +#include "BKE_gpencil_modifier.h" #include "BKE_hair.h" #include "BKE_key.h" #include "BKE_lattice.h" @@ -1305,7 +1307,7 @@ static bool object_gpencil_add_poll(bContext *C) static int object_gpencil_add_exec(bContext *C, wmOperator *op) { - Object *ob = CTX_data_active_object(C); + Object *ob = CTX_data_active_object(C), *ob_orig = ob; bGPdata *gpd = (ob && (ob->type == OB_GPENCIL)) ? ob->data : NULL; const int type = RNA_enum_get(op->ptr, "type"); @@ -1333,6 +1335,11 @@ static int object_gpencil_add_exec(bContext *C, wmOperator *op) ob_name = "Stroke"; break; } + case GP_LRT_OBJECT: + case GP_LRT_COLLECTION: { + ob_name = "Line Art"; + break; + } default: { break; } @@ -1373,6 +1380,46 @@ static int object_gpencil_add_exec(bContext *C, wmOperator *op) ED_gpencil_create_monkey(C, ob, mat); break; } + case GP_LRT_SCENE: + case GP_LRT_COLLECTION: + case GP_LRT_OBJECT: { + float radius = RNA_float_get(op->ptr, "radius"); + float mat[4][4]; + + ED_object_new_primitive_matrix(C, ob, loc, rot, mat); + mul_v3_fl(mat[0], radius); + mul_v3_fl(mat[1], radius); + mul_v3_fl(mat[2], radius); + + ED_gpencil_create_lineart(C, ob); + + gpd = ob->data; + + /* Add Line Art modifier */ + LineartGpencilModifierData *md = (LineartGpencilModifierData *)BKE_gpencil_modifier_new( + eGpencilModifierType_Lineart); + BLI_addtail(&ob->greasepencil_modifiers, md); + BKE_gpencil_modifier_unique_name(&ob->greasepencil_modifiers, (GpencilModifierData *)md); + + if (type == GP_LRT_COLLECTION) { + md->source_type = LRT_SOURCE_COLLECTION; + md->source_collection = CTX_data_collection(C); + } + else if (type == GP_LRT_OBJECT) { + md->source_type = LRT_SOURCE_OBJECT; + md->source_object = ob_orig; + } + else { + /* Whole scene. */ + md->source_type = LRT_SOURCE_SCENE; + } + /* Only created one layer and one material. */ + strcpy(md->target_layer, ((bGPDlayer *)gpd->layers.first)->info); + md->target_material = BKE_gpencil_material(ob, 1); + + /* Stroke object is drawn in front of meshes by default. */ + ob->dtx |= OB_DRAW_IN_FRONT; + } case GP_EMPTY: /* do nothing */ break; @@ -1393,6 +1440,41 @@ static int object_gpencil_add_exec(bContext *C, wmOperator *op) return OPERATOR_FINISHED; } +static const EnumPropertyItem *object_gpencil_add_options(bContext *C, + PointerRNA *UNUSED(ptr), + PropertyRNA *UNUSED(prop), + bool *r_free) +{ + EnumPropertyItem *item = NULL; + const EnumPropertyItem *item_ref = rna_enum_object_gpencil_type_items; + int totitem = 0; + int i = 0; + int orig_count = RNA_enum_items_count(item_ref); + + /* Default types. */ + for (i = 0; i < orig_count; i++) { + if (item_ref[i].value == GP_LRT_OBJECT || item_ref[i].value == GP_LRT_COLLECTION || + item_ref[i].value == GP_LRT_SCENE) { + if (item_ref[i].value == GP_LRT_SCENE) { + /* separator before line art types */ + RNA_enum_item_add_separator(&item, &totitem); + } + else if (item_ref[i].value == GP_LRT_OBJECT) { + Object *ob = CTX_data_active_object(C); + if (!ob || ob->type != OB_MESH) { + continue; + } + } + } + RNA_enum_item_add(&item, &totitem, &item_ref[i]); + } + + RNA_enum_item_end(&item, &totitem); + *r_free = true; + + return item; +} + void OBJECT_OT_gpencil_add(wmOperatorType *ot) { /* identifiers */ @@ -1413,6 +1495,7 @@ void OBJECT_OT_gpencil_add(wmOperatorType *ot) ED_object_add_generic_props(ot, false); ot->prop = RNA_def_enum(ot->srna, "type", rna_enum_object_gpencil_type_items, 0, "Type", ""); + RNA_def_enum_funcs(ot->prop, object_gpencil_add_options); } /** \} */ diff --git a/source/blender/editors/object/object_ops.c b/source/blender/editors/object/object_ops.c index 57676cf5c76..71bea6bf9e3 100644 --- a/source/blender/editors/object/object_ops.c +++ b/source/blender/editors/object/object_ops.c @@ -37,6 +37,8 @@ #include "object_intern.h" +#include "MOD_gpencil_lineart.h" + /* ************************** registration **********************************/ void ED_operatortypes_object(void) @@ -154,6 +156,9 @@ void ED_operatortypes_object(void) WM_operatortype_append(OBJECT_OT_gpencil_modifier_copy); WM_operatortype_append(OBJECT_OT_gpencil_modifier_copy_to_selected); + /* grease pencil line art */ + WM_operatortypes_lineart(); + /* Shader FX. */ WM_operatortype_append(OBJECT_OT_shaderfx_add); WM_operatortype_append(OBJECT_OT_shaderfx_remove); diff --git a/source/blender/editors/space_buttons/buttons_context.c b/source/blender/editors/space_buttons/buttons_context.c index 3efaff72637..13762b348c2 100644 --- a/source/blender/editors/space_buttons/buttons_context.c +++ b/source/blender/editors/space_buttons/buttons_context.c @@ -33,6 +33,7 @@ #include "DNA_armature_types.h" #include "DNA_brush_types.h" +#include "DNA_collection_types.h" #include "DNA_linestyle_types.h" #include "DNA_material_types.h" #include "DNA_node_types.h" @@ -153,6 +154,29 @@ static bool buttons_context_path_world(ButsContextPath *path) return false; } +static bool buttons_context_path_collection(ButsContextPath *path, wmWindow *window) +{ + PointerRNA *ptr = &path->ptr[path->len - 1]; + + /* if we already have a (pinned) collection, we're done */ + if (RNA_struct_is_a(ptr->type, &RNA_Collection)) { + return true; + } + /* if we have a view layer, use the view layer's active collection */ + if (buttons_context_path_view_layer(path, window)) { + ViewLayer *view_layer = path->ptr[path->len - 1].data; + Collection *c = view_layer->active_collection->collection; + if (c) { + RNA_id_pointer_create(&c->id, &path->ptr[path->len]); + path->len++; + return true; + } + } + + /* no path to a collection possible */ + return false; +} + static bool buttons_context_path_linestyle(ButsContextPath *path, wmWindow *window) { PointerRNA *ptr = &path->ptr[path->len - 1]; @@ -575,6 +599,9 @@ static bool buttons_context_path( case BCONTEXT_WORLD: found = buttons_context_path_world(path); break; + case BCONTEXT_COLLECTION: /* This is for Line Art collection flags */ + found = buttons_context_path_collection(path, window); + break; case BCONTEXT_TOOL: found = true; break; @@ -858,6 +885,10 @@ int /*eContextResult*/ buttons_context(const bContext *C, set_pointer_type(path, result, &RNA_World); return CTX_RESULT_OK; } + if (CTX_data_equals(member, "collection")) { + set_pointer_type(path, result, &RNA_Collection); + return 1; + } if (CTX_data_equals(member, "object")) { set_pointer_type(path, result, &RNA_Object); return CTX_RESULT_OK; diff --git a/source/blender/editors/space_buttons/space_buttons.c b/source/blender/editors/space_buttons/space_buttons.c index 07bc1d42c3f..43d45db38e2 100644 --- a/source/blender/editors/space_buttons/space_buttons.c +++ b/source/blender/editors/space_buttons/space_buttons.c @@ -206,6 +206,14 @@ int ED_buttons_tabs_list(SpaceProperties *sbuts, short *context_tabs_array) context_tabs_array[length] = -1; length++; } + if (sbuts->pathflag & (1 << BCONTEXT_VIEW_LAYER)) { + context_tabs_array[length] = BCONTEXT_COLLECTION; + length++; + } + if (length != 0) { + context_tabs_array[length] = -1; + length++; + } if (sbuts->pathflag & (1 << BCONTEXT_OBJECT)) { context_tabs_array[length] = BCONTEXT_OBJECT; length++; @@ -271,6 +279,8 @@ static const char *buttons_main_region_context_string(const short mainb) return "view_layer"; case BCONTEXT_WORLD: return "world"; + case BCONTEXT_COLLECTION: + return "collection"; case BCONTEXT_OBJECT: return "object"; case BCONTEXT_DATA: diff --git a/source/blender/gpencil_modifiers/CMakeLists.txt b/source/blender/gpencil_modifiers/CMakeLists.txt index 9f1484b47c0..e3bb0d773a2 100644 --- a/source/blender/gpencil_modifiers/CMakeLists.txt +++ b/source/blender/gpencil_modifiers/CMakeLists.txt @@ -53,6 +53,7 @@ set(SRC intern/MOD_gpencilbuild.c intern/MOD_gpencilcolor.c intern/MOD_gpencilhook.c + intern/MOD_gpencillineart.c intern/MOD_gpencillattice.c intern/MOD_gpencilmirror.c intern/MOD_gpencilmultiply.c @@ -70,6 +71,16 @@ set(SRC MOD_gpencil_modifiertypes.h intern/MOD_gpencil_ui_common.h intern/MOD_gpencil_util.h + + # Lineart code + intern/lineart/lineart_ops.c + intern/lineart/lineart_cpu.c + intern/lineart/lineart_chain.c + intern/lineart/lineart_util.c + + intern/lineart/lineart_intern.h + intern/lineart/MOD_lineart.h + ) set(LIB @@ -79,6 +90,7 @@ if(WITH_INTERNATIONAL) add_definitions(-DWITH_INTERNATIONAL) endif() +add_definitions(${GL_DEFINITIONS}) blender_add_lib(bf_gpencil_modifiers "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") diff --git a/source/blender/gpencil_modifiers/MOD_gpencil_lineart.h b/source/blender/gpencil_modifiers/MOD_gpencil_lineart.h new file mode 100644 index 00000000000..685f0cb36cb --- /dev/null +++ b/source/blender/gpencil_modifiers/MOD_gpencil_lineart.h @@ -0,0 +1,31 @@ +/* + * 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. + */ + +/** \file + * \ingroup modifiers + */ + +#pragma once + +#include "DNA_windowmanager_types.h" + +/* Operator types should be in exposed header. */ +void OBJECT_OT_lineart_bake_strokes(struct wmOperatorType *ot); +void OBJECT_OT_lineart_bake_strokes_all(struct wmOperatorType *ot); +void OBJECT_OT_lineart_clear(struct wmOperatorType *ot); +void OBJECT_OT_lineart_clear_all(struct wmOperatorType *ot); + +void WM_operatortypes_lineart(void); diff --git a/source/blender/gpencil_modifiers/MOD_gpencil_modifiertypes.h b/source/blender/gpencil_modifiers/MOD_gpencil_modifiertypes.h index 3f167ac6785..e6ce7983a0f 100644 --- a/source/blender/gpencil_modifiers/MOD_gpencil_modifiertypes.h +++ b/source/blender/gpencil_modifiers/MOD_gpencil_modifiertypes.h @@ -43,6 +43,7 @@ extern GpencilModifierTypeInfo modifierType_Gpencil_Armature; extern GpencilModifierTypeInfo modifierType_Gpencil_Time; extern GpencilModifierTypeInfo modifierType_Gpencil_Multiply; extern GpencilModifierTypeInfo modifierType_Gpencil_Texture; +extern GpencilModifierTypeInfo modifierType_Gpencil_Lineart; /* MOD_gpencil_util.c */ void gpencil_modifier_type_init(GpencilModifierTypeInfo *types[]); diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c b/source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c index f5bfe66562b..beb32c7a38e 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c @@ -74,6 +74,7 @@ void gpencil_modifier_type_init(GpencilModifierTypeInfo *types[]) INIT_GP_TYPE(Time); INIT_GP_TYPE(Multiply); INIT_GP_TYPE(Texture); + INIT_GP_TYPE(Lineart); #undef INIT_GP_TYPE } diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilarmature.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilarmature.c index cc8eae64300..f021f71dbdc 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilarmature.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilarmature.c @@ -179,7 +179,9 @@ static bool isDisabled(GpencilModifierData *md, int UNUSED(userRenderParams)) return !mmd->object || mmd->object->type != OB_ARMATURE; } -static void updateDepsgraph(GpencilModifierData *md, const ModifierUpdateDepsgraphContext *ctx) +static void updateDepsgraph(GpencilModifierData *md, + const ModifierUpdateDepsgraphContext *ctx, + const int UNUSED(mode)) { ArmatureGpencilModifierData *lmd = (ArmatureGpencilModifierData *)md; if (lmd->object != NULL) { diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilarray.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilarray.c index b8fa88327fc..7aa1320c4e3 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilarray.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilarray.c @@ -318,7 +318,9 @@ static void generateStrokes(GpencilModifierData *md, Depsgraph *depsgraph, Objec generate_geometry(md, depsgraph, scene, ob); } -static void updateDepsgraph(GpencilModifierData *md, const ModifierUpdateDepsgraphContext *ctx) +static void updateDepsgraph(GpencilModifierData *md, + const ModifierUpdateDepsgraphContext *ctx, + const int UNUSED(mode)) { ArrayGpencilModifierData *lmd = (ArrayGpencilModifierData *)md; if (lmd->object != NULL) { diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilhook.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilhook.c index 09cce3f1ab4..e0300eb498f 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilhook.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilhook.c @@ -330,7 +330,9 @@ static bool isDisabled(GpencilModifierData *md, int UNUSED(userRenderParams)) return !mmd->object; } -static void updateDepsgraph(GpencilModifierData *md, const ModifierUpdateDepsgraphContext *ctx) +static void updateDepsgraph(GpencilModifierData *md, + const ModifierUpdateDepsgraphContext *ctx, + const int UNUSED(mode)) { HookGpencilModifierData *lmd = (HookGpencilModifierData *)md; if (lmd->object != NULL) { diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencillattice.c b/source/blender/gpencil_modifiers/intern/MOD_gpencillattice.c index ff493258b9b..7d78892aa6b 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencillattice.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencillattice.c @@ -195,7 +195,9 @@ static bool isDisabled(GpencilModifierData *md, int UNUSED(userRenderParams)) return !mmd->object || mmd->object->type != OB_LATTICE; } -static void updateDepsgraph(GpencilModifierData *md, const ModifierUpdateDepsgraphContext *ctx) +static void updateDepsgraph(GpencilModifierData *md, + const ModifierUpdateDepsgraphContext *ctx, + const int UNUSED(mode)) { LatticeGpencilModifierData *lmd = (LatticeGpencilModifierData *)md; if (lmd->object != NULL) { diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c b/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c new file mode 100644 index 00000000000..7fed771dabe --- /dev/null +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c @@ -0,0 +1,477 @@ +/* + * 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, Blender Foundation + * This is a new part of Blender + */ + +/** \file + * \ingroup modifiers + */ + +#include <stdio.h> + +#include "BLI_utildefines.h" + +#include "BLI_blenlib.h" +#include "BLI_math_vector.h" + +#include "BLT_translation.h" + +#include "DNA_collection_types.h" +#include "DNA_defaults.h" +#include "DNA_gpencil_modifier_types.h" +#include "DNA_gpencil_types.h" +#include "DNA_lineart_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" +#include "DNA_screen_types.h" + +#include "lineart/MOD_lineart.h" + +#include "BKE_collection.h" +#include "BKE_context.h" +#include "BKE_global.h" +#include "BKE_gpencil.h" +#include "BKE_gpencil_modifier.h" +#include "BKE_lib_query.h" +#include "BKE_main.h" +#include "BKE_material.h" +#include "BKE_screen.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "BKE_modifier.h" +#include "RNA_access.h" + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_query.h" + +#include "MOD_gpencil_modifiertypes.h" +#include "MOD_gpencil_ui_common.h" +#include "MOD_gpencil_util.h" + +#include "WM_api.h" +#include "WM_types.h" + +static void initData(GpencilModifierData *md) +{ + LineartGpencilModifierData *gpmd = (LineartGpencilModifierData *)md; + + BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(gpmd, modifier)); + + MEMCPY_STRUCT_AFTER(gpmd, DNA_struct_default_get(LineartGpencilModifierData), modifier); +} + +static void copyData(const GpencilModifierData *md, GpencilModifierData *target) +{ + BKE_gpencil_modifier_copydata_generic(md, target); +} + +static void generate_strokes_actual( + GpencilModifierData *md, Depsgraph *depsgraph, Object *ob, bGPDlayer *gpl, bGPDframe *gpf) +{ + LineartGpencilModifierData *lmd = (LineartGpencilModifierData *)md; + + if (G.debug_value == 4000) { + printf("LRT: Generating from modifier.\n"); + } + + MOD_lineart_gpencil_generate( + lmd->render_buffer, + depsgraph, + ob, + gpl, + gpf, + lmd->source_type, + lmd->source_type == LRT_SOURCE_OBJECT ? (void *)lmd->source_object : + (void *)lmd->source_collection, + lmd->level_start, + lmd->use_multiple_levels ? lmd->level_end : lmd->level_start, + lmd->target_material ? BKE_gpencil_object_material_index_get(ob, lmd->target_material) : 0, + lmd->line_types, + lmd->transparency_flags, + lmd->transparency_mask, + lmd->thickness, + lmd->opacity, + lmd->pre_sample_length, + lmd->source_vertex_group, + lmd->vgname, + lmd->flags); +} + +static bool isModifierDisabled(GpencilModifierData *md) +{ + LineartGpencilModifierData *lmd = (LineartGpencilModifierData *)md; + + if ((lmd->target_layer[0] == '\0') || (lmd->target_material == NULL)) { + return true; + } + + if (lmd->source_type == LRT_SOURCE_OBJECT && !lmd->source_object) { + return true; + } + + if (lmd->source_type == LRT_SOURCE_COLLECTION && !lmd->source_collection) { + return true; + } + + /* Preventing calculation in depsgraph when baking frames. */ + if (lmd->flags & LRT_GPENCIL_IS_BAKED) { + return true; + } + + return false; +} +static void generateStrokes(GpencilModifierData *md, Depsgraph *depsgraph, Object *ob) +{ + LineartGpencilModifierData *lmd = (LineartGpencilModifierData *)md; + bGPdata *gpd = ob->data; + + /* Guard early, don't trigger calculation when no gpencil frame is present. Probably should + * disable in the isModifierDisabled() function but we need addtional arg for depsgraph and + * gpd. */ + bGPDlayer *gpl = BKE_gpencil_layer_get_by_name(gpd, lmd->target_layer, 1); + if (gpl == NULL) { + return; + } + /* Need to call this or we don't get active frame (user may haven't selected any one). */ + BKE_gpencil_frame_active_set(depsgraph, gpd); + bGPDframe *gpf = gpl->actframe; + if (gpf == NULL) { + BKE_gpencil_frame_addnew(gpl, DEG_get_evaluated_scene(depsgraph)->r.cfra); + return; + } + + /* Check all parameters required are filled. */ + if (isModifierDisabled(md)) { + return; + } + + MOD_lineart_compute_feature_lines(depsgraph, lmd); + + generate_strokes_actual(md, depsgraph, ob, gpl, gpf); + + MOD_lineart_destroy_render_data(lmd); + + WM_main_add_notifier(NA_EDITED | NC_GPENCIL, NULL); +} + +static void bakeModifier(Main *UNUSED(bmain), + Depsgraph *depsgraph, + GpencilModifierData *md, + Object *ob) +{ + bGPdata *gpd = ob->data; + LineartGpencilModifierData *lmd = (LineartGpencilModifierData *)md; + + bGPDlayer *gpl = BKE_gpencil_layer_get_by_name(gpd, lmd->target_layer, 1); + if (gpl == NULL) { + return; + } + bGPDframe *gpf = gpl->actframe; + if (gpf == NULL) { + return; + } + + MOD_lineart_compute_feature_lines(depsgraph, lmd); + + generate_strokes_actual(md, depsgraph, ob, gpl, gpf); + + MOD_lineart_destroy_render_data(lmd); +} + +static bool isDisabled(GpencilModifierData *md, int UNUSED(userRenderParams)) +{ + return isModifierDisabled(md); +} + +static void updateDepsgraph(GpencilModifierData *md, + const ModifierUpdateDepsgraphContext *ctx, + const int mode) +{ + DEG_add_object_relation(ctx->node, ctx->object, DEG_OB_COMP_TRANSFORM, "Line Art Modifier"); + + LineartGpencilModifierData *lmd = (LineartGpencilModifierData *)md; + if (lmd->source_type == LRT_SOURCE_OBJECT && lmd->source_object) { + DEG_add_object_relation( + ctx->node, lmd->source_object, DEG_OB_COMP_GEOMETRY, "Line Art Modifier"); + DEG_add_object_relation( + ctx->node, lmd->source_object, DEG_OB_COMP_TRANSFORM, "Line Art Modifier"); + } + else { + FOREACH_COLLECTION_VISIBLE_OBJECT_RECURSIVE_BEGIN (ctx->scene->master_collection, ob, mode) { + if (ob->type == OB_MESH || ob->type == OB_MBALL || ob->type == OB_CURVE || + ob->type == OB_SURF || ob->type == OB_FONT) { + if (!(ob->lineart.usage & COLLECTION_LRT_EXCLUDE)) { + DEG_add_object_relation(ctx->node, ob, DEG_OB_COMP_GEOMETRY, "Line Art Modifier"); + DEG_add_object_relation(ctx->node, ob, DEG_OB_COMP_TRANSFORM, "Line Art Modifier"); + } + } + } + FOREACH_COLLECTION_VISIBLE_OBJECT_RECURSIVE_END; + } + DEG_add_object_relation( + ctx->node, ctx->scene->camera, DEG_OB_COMP_TRANSFORM, "Line Art Modifier"); +} + +static void foreachIDLink(GpencilModifierData *md, Object *ob, IDWalkFunc walk, void *userData) +{ + LineartGpencilModifierData *lmd = (LineartGpencilModifierData *)md; + + walk(userData, ob, (ID **)&lmd->target_material, IDWALK_CB_USER); + walk(userData, ob, (ID **)&lmd->source_collection, IDWALK_CB_NOP); + + walk(userData, ob, (ID **)&lmd->source_object, IDWALK_CB_NOP); +} + +static void panel_draw(const bContext *UNUSED(C), Panel *panel) +{ + uiLayout *layout = panel->layout; + + PointerRNA ob_ptr; + PointerRNA *ptr = gpencil_modifier_panel_get_property_pointers(panel, &ob_ptr); + + PointerRNA obj_data_ptr = RNA_pointer_get(&ob_ptr, "data"); + + const int source_type = RNA_enum_get(ptr, "source_type"); + const bool is_baked = RNA_boolean_get(ptr, "is_baked"); + + uiLayoutSetPropSep(layout, true); + uiLayoutSetEnabled(layout, !is_baked); + + uiItemR(layout, ptr, "source_type", 0, NULL, ICON_NONE); + + if (source_type == LRT_SOURCE_OBJECT) { + uiItemR(layout, ptr, "source_object", 0, NULL, ICON_OBJECT_DATA); + } + else if (source_type == LRT_SOURCE_COLLECTION) { + uiItemR(layout, ptr, "source_collection", 0, NULL, ICON_OUTLINER_COLLECTION); + } + else { + /* Source is Scene. */ + } + + uiLayout *col = uiLayoutColumnWithHeading(layout, true, IFACE_("Edge Types")); + + uiItemR(col, ptr, "use_contour", 0, "Contour", ICON_NONE); + uiItemR(col, ptr, "use_material", 0, "Material", ICON_NONE); + uiItemR(col, ptr, "use_edge_mark", 0, "Edge Marks", ICON_NONE); + uiItemR(col, ptr, "use_intersection", 0, "Intersection", ICON_NONE); + + uiLayout *row = uiLayoutRow(col, true); + /* Don't automatically add the "animate property" button. */ + uiLayoutSetPropDecorate(row, false); + + const bool use_crease = RNA_boolean_get(ptr, "use_crease"); + uiLayout *sub = uiLayoutRow(row, true); + uiItemR(sub, ptr, "use_crease", 0, "Crease", ICON_NONE); + sub = uiLayoutRow(sub, true); + uiLayoutSetEnabled(sub, use_crease); + uiItemR(sub, ptr, "crease_threshold", UI_ITEM_R_SLIDER, "", ICON_NONE); + uiItemDecoratorR(row, ptr, "crease_threshold", 0); + + uiItemPointerR(layout, ptr, "target_layer", &obj_data_ptr, "layers", NULL, ICON_GREASEPENCIL); + uiItemPointerR( + layout, ptr, "target_material", &obj_data_ptr, "materials", NULL, ICON_SHADING_TEXTURE); + + uiItemR(layout, ptr, "remove_doubles", 0, NULL, ICON_NONE); + uiItemR(layout, ptr, "allow_overlapping_edges", 0, "Overlapping Edges As Contour", ICON_NONE); + + gpencil_modifier_panel_end(layout, ptr); +} + +static void style_panel_draw(const bContext *UNUSED(C), Panel *panel) +{ + PointerRNA *ptr = gpencil_modifier_panel_get_property_pointers(panel, NULL); + + uiLayout *layout = panel->layout; + + const bool is_baked = RNA_boolean_get(ptr, "is_baked"); + + uiLayoutSetPropSep(layout, true); + uiLayoutSetEnabled(layout, !is_baked); + + uiItemR(layout, ptr, "thickness", UI_ITEM_R_SLIDER, NULL, ICON_NONE); + + uiItemR(layout, ptr, "opacity", UI_ITEM_R_SLIDER, NULL, ICON_NONE); +} + +static void occlusion_panel_draw(const bContext *UNUSED(C), Panel *panel) +{ + PointerRNA *ptr = gpencil_modifier_panel_get_property_pointers(panel, NULL); + + uiLayout *layout = panel->layout; + + const bool is_baked = RNA_boolean_get(ptr, "is_baked"); + + uiLayoutSetPropSep(layout, true); + uiLayoutSetEnabled(layout, !is_baked); + + const bool use_multiple_levels = RNA_boolean_get(ptr, "use_multiple_levels"); + const bool use_transparency = RNA_boolean_get(ptr, "use_transparency"); + + uiItemR(layout, ptr, "use_multiple_levels", 0, "Multiple Levels", ICON_NONE); + + if (use_multiple_levels) { + uiLayout *col = uiLayoutColumn(layout, true); + uiItemR(col, ptr, "level_start", 0, NULL, ICON_NONE); + uiItemR(col, ptr, "level_end", 0, NULL, ICON_NONE); + } + else { + uiItemR(layout, ptr, "level_start", 0, "Level", ICON_NONE); + } + + uiItemR(layout, ptr, "use_transparency", 0, "Transparency", ICON_NONE); + + uiLayout *col = uiLayoutColumn(layout, true); + + if (use_transparency) { + uiItemR(col, ptr, "transparency_match", 0, "Match", ICON_NONE); + } + + if (use_transparency) { + uiLayout *row = uiLayoutRow(col, true); + uiItemR(row, ptr, "transparency_mask_0", UI_ITEM_R_TOGGLE, "0", ICON_NONE); + uiItemR(row, ptr, "transparency_mask_1", UI_ITEM_R_TOGGLE, "1", ICON_NONE); + uiItemR(row, ptr, "transparency_mask_2", UI_ITEM_R_TOGGLE, "2", ICON_NONE); + uiItemR(row, ptr, "transparency_mask_3", UI_ITEM_R_TOGGLE, "3", ICON_NONE); + uiItemR(row, ptr, "transparency_mask_4", UI_ITEM_R_TOGGLE, "4", ICON_NONE); + uiItemR(row, ptr, "transparency_mask_5", UI_ITEM_R_TOGGLE, "5", ICON_NONE); + uiItemR(row, ptr, "transparency_mask_6", UI_ITEM_R_TOGGLE, "6", ICON_NONE); + uiItemR(row, ptr, "transparency_mask_7", UI_ITEM_R_TOGGLE, "7", ICON_NONE); + } +} + +static void chaining_panel_draw(const bContext *UNUSED(C), Panel *panel) +{ + PointerRNA *ptr = gpencil_modifier_panel_get_property_pointers(panel, NULL); + + uiLayout *layout = panel->layout; + + const bool is_baked = RNA_boolean_get(ptr, "is_baked"); + + uiLayoutSetPropSep(layout, true); + uiLayoutSetEnabled(layout, !is_baked); + + uiLayout *col = uiLayoutColumnWithHeading(layout, true, IFACE_("Chain")); + uiItemR(col, ptr, "fuzzy_intersections", 0, NULL, ICON_NONE); + uiItemR(col, ptr, "fuzzy_everything", 0, NULL, ICON_NONE); + + col = uiLayoutColumn(layout, true); + uiItemR(col, ptr, "chaining_geometry_threshold", 0, NULL, ICON_NONE); + uiItemR(col, ptr, "chaining_image_threshold", 0, NULL, ICON_NONE); + + uiItemR(layout, ptr, "pre_sample_length", UI_ITEM_R_SLIDER, NULL, ICON_NONE); + + uiItemR(layout, ptr, "angle_splitting_threshold", UI_ITEM_R_SLIDER, NULL, ICON_NONE); +} + +static void vgroup_panel_draw(const bContext *UNUSED(C), Panel *panel) +{ + PointerRNA ob_ptr; + PointerRNA *ptr = gpencil_modifier_panel_get_property_pointers(panel, &ob_ptr); + + uiLayout *layout = panel->layout; + + bool is_baked = RNA_boolean_get(ptr, "is_baked"); + + uiLayoutSetPropSep(layout, true); + uiLayoutSetEnabled(layout, !is_baked); + + uiLayout *col = uiLayoutColumn(layout, true); + + uiLayout *row = uiLayoutRow(col, true); + uiItemR(row, ptr, "source_vertex_group", 0, "Filter Source", ICON_GROUP_VERTEX); + uiItemR(row, ptr, "invert_source_vertex_group", UI_ITEM_R_TOGGLE, "", ICON_ARROW_LEFTRIGHT); + + uiItemR(col, ptr, "match_output_vertex_group", 0, NULL, ICON_NONE); + + bool match_output = RNA_boolean_get(ptr, "match_output_vertex_group"); + if (!match_output) { + uiItemPointerR(col, ptr, "vertex_group", &ob_ptr, "vertex_groups", "Target", ICON_NONE); + } + + uiItemR(layout, ptr, "soft_selection", 0, NULL, ICON_NONE); +} + +static void baking_panel_draw(const bContext *UNUSED(C), Panel *panel) +{ + uiLayout *layout = panel->layout; + PointerRNA ob_ptr; + PointerRNA *ptr = gpencil_modifier_panel_get_property_pointers(panel, &ob_ptr); + + bool is_baked = RNA_boolean_get(ptr, "is_baked"); + + uiLayoutSetPropSep(layout, true); + + if (is_baked) { + uiLayout *col = uiLayoutColumn(layout, false); + uiLayoutSetPropSep(col, false); + uiItemL(col, "Modifier has baked data.", ICON_NONE); + uiItemR(col, ptr, "is_baked", UI_ITEM_R_TOGGLE, "Continue Without Clearing", ICON_NONE); + } + + uiLayout *col = uiLayoutColumn(layout, false); + uiLayoutSetEnabled(col, !is_baked); + uiItemO(col, NULL, ICON_NONE, "OBJECT_OT_lineart_bake_strokes"); + uiItemO(col, NULL, ICON_NONE, "OBJECT_OT_lineart_bake_strokes_all"); + + col = uiLayoutColumn(layout, false); + uiItemO(col, NULL, ICON_NONE, "OBJECT_OT_lineart_clear"); + uiItemO(col, NULL, ICON_NONE, "OBJECT_OT_lineart_clear_all"); +} + +static void panelRegister(ARegionType *region_type) +{ + PanelType *panel_type = gpencil_modifier_panel_register( + region_type, eGpencilModifierType_Lineart, panel_draw); + + gpencil_modifier_subpanel_register( + region_type, "style", "Style", NULL, style_panel_draw, panel_type); + gpencil_modifier_subpanel_register( + region_type, "occlusion", "Occlusion", NULL, occlusion_panel_draw, panel_type); + gpencil_modifier_subpanel_register( + region_type, "chaining", "Chaining", NULL, chaining_panel_draw, panel_type); + gpencil_modifier_subpanel_register( + region_type, "vgroup", "Vertex Weight Transfer", NULL, vgroup_panel_draw, panel_type); + gpencil_modifier_subpanel_register( + region_type, "baking", "Baking", NULL, baking_panel_draw, panel_type); +} + +GpencilModifierTypeInfo modifierType_Gpencil_Lineart = { + /* name. */ "Line Art", + /* structName. */ "LineartGpencilModifierData", + /* structSize. */ sizeof(LineartGpencilModifierData), + /* type. */ eGpencilModifierTypeType_Gpencil, + /* flags. */ eGpencilModifierTypeFlag_SupportsEditmode, + + /* copyData. */ copyData, + + /* deformStroke. */ NULL, + /* generateStrokes. */ generateStrokes, + /* bakeModifier. */ bakeModifier, + /* remapTime. */ NULL, + + /* initData. */ initData, + /* freeData. */ NULL, + /* isDisabled. */ isDisabled, + /* updateDepsgraph. */ updateDepsgraph, + /* dependsOnTime. */ NULL, + /* foreachIDLink. */ foreachIDLink, + /* foreachTexLink. */ NULL, + /* panelRegister. */ panelRegister, +}; diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilmirror.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilmirror.c index 111c60436bf..eec4eab3441 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencilmirror.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilmirror.c @@ -207,7 +207,9 @@ static bool isDisabled(GpencilModifierData *UNUSED(md), int UNUSED(userRenderPar return false; } -static void updateDepsgraph(GpencilModifierData *md, const ModifierUpdateDepsgraphContext *ctx) +static void updateDepsgraph(GpencilModifierData *md, + const ModifierUpdateDepsgraphContext *ctx, + const int UNUSED(mode)) { MirrorGpencilModifierData *lmd = (MirrorGpencilModifierData *)md; if (lmd->object != NULL) { diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpenciltint.c b/source/blender/gpencil_modifiers/intern/MOD_gpenciltint.c index 311d08238b9..fcc0a1f35eb 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpenciltint.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpenciltint.c @@ -311,7 +311,9 @@ static bool isDisabled(GpencilModifierData *md, int UNUSED(userRenderParams)) return !mmd->object; } -static void updateDepsgraph(GpencilModifierData *md, const ModifierUpdateDepsgraphContext *ctx) +static void updateDepsgraph(GpencilModifierData *md, + const ModifierUpdateDepsgraphContext *ctx, + const int UNUSED(mode)) { TintGpencilModifierData *lmd = (TintGpencilModifierData *)md; if (lmd->object != NULL) { diff --git a/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h b/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h new file mode 100644 index 00000000000..7519f3ef40f --- /dev/null +++ b/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h @@ -0,0 +1,552 @@ +/* + * 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) 2008 Blender Foundation. + * All rights reserved. + */ + +/** \file + * \ingroup editors + */ + +#pragma once + +#include "BLI_linklist.h" +#include "BLI_listbase.h" +#include "BLI_math.h" +#include "BLI_threads.h" + +#include "DNA_lineart_types.h" + +#include <math.h> +#include <string.h> + +typedef struct LineartStaticMemPoolNode { + Link item; + size_t size; + size_t used_byte; + /* User memory starts here */ +} LineartStaticMemPoolNode; + +typedef struct LineartStaticMemPool { + ListBase pools; + SpinLock lock_mem; +} LineartStaticMemPool; + +typedef struct LineartRenderTriangleAdjacent { + struct LineartRenderLine *rl[3]; +} LineartRenderTriangleAdjacent; + +typedef struct LineartRenderTriangle { + struct LineartRenderVert *v[3]; + + /* first culled in line list to use adjacent triangle info, then go through triangle list. */ + double gn[3]; + + /* Material flag is removed to save space. */ + unsigned char transparency_mask; + unsigned char flags; /* eLineartTriangleFlags */ + + /* Only use single link list, because we don't need to go back in order. + * This variable is also reused to store the pointer to adjacent lines of this triangle before + * intersection statge */ + struct LinkNode *intersecting_verts; +} LineartRenderTriangle; + +typedef struct LineartRenderTriangleThread { + struct LineartRenderTriangle base; + /* This variable is used to store per-thread triangle-line testing pair, + * also re-used to store triangle-triangle pair for intersection testing stage. + * Do not directly use LineartRenderTriangleThread. + * The size of LineartRenderTriangle is dynamically allocated to contain set thread number of + * "testing" field. Worker threads will test lines against the "base" triangle. + * At least one thread is present, thus we always have at least testing[0]. */ + struct LineartRenderLine *testing[1]; +} LineartRenderTriangleThread; + +typedef enum eLineArtElementNodeFlag { + LRT_ELEMENT_IS_ADDITIONAL = (1 << 0), + LRT_ELEMENT_BORDER_ONLY = (1 << 1), + LRT_ELEMENT_NO_INTERSECTION = (1 << 2), +} eLineArtElementNodeFlag; + +typedef struct LineartRenderElementLinkNode { + struct LineartRenderElementLinkNode *next, *prev; + void *pointer; + int element_count; + void *object_ref; + eLineArtElementNodeFlag flags; + + /* Per object value, always set, if not enabled by ObjectLineArt, then it's set to global. */ + float crease_threshold; +} LineartRenderElementLinkNode; + +typedef struct LineartRenderLineSegment { + struct LineartRenderLineSegment *next, *prev; + /** at==0: left at==1: right (this is in 2D projected space) */ + double at; + /** Occlusion level after "at" point */ + unsigned char occlusion; + + /** For determining lines beind a glass window material. + * the size of this variable should also be dynamically decided, 1 byte to 8 byte, + * allows 8 to 64 materials for "transparent mask". 1 byte (8 materials) should be + * enought for most cases. + */ + unsigned char transparency_mask; +} LineartRenderLineSegment; + +typedef struct LineartRenderVert { + double gloc[3]; + double fbcoord[4]; + + /* Scene global index. */ + int index; + + /** Intersection data flag is here, when LRT_VERT_HAS_INTERSECTION_DATA is set, + * size of the struct is extended to include intersection data. + * See eLineArtVertFlags. + */ + char flag; + +} LineartRenderVert; + +typedef struct LineartRenderVertIntersection { + struct LineartRenderVert base; + /* Use vert index because we only use this to check vertex equal. This way we save 8 Bytes. */ + int isec1, isec2; + struct LineartRenderTriangle *intersecting_with; +} LineartRenderVertIntersection; + +typedef enum eLineArtVertFlags { + LRT_VERT_HAS_INTERSECTION_DATA = (1 << 0), + LRT_VERT_EDGE_USED = (1 << 1), +} eLineArtVertFlags; + +typedef struct LineartRenderLine { + /* We only need link node kind of list here. */ + struct LineartRenderLine *next; + struct LineartRenderVert *l, *r; + /* Local vertex index for two ends, not puting in RenderVert because all verts are loaded, so as + * long as fewer than half of the mesh edges are becoming a feature line, we save more memory. */ + int l_obindex, r_obindex; + struct LineartRenderTriangle *tl, *tr; + ListBase segments; + char min_occ; + + /** Also for line type determination on chainning */ + unsigned char flags; + + /** Still need this entry because culled lines will not add to object reln node, + * TODO: If really need more savings, we can allocate this in a "extended" way too, but we need + * another bit in flags to be able to show the difference. + */ + struct Object *object_ref; +} LineartRenderLine; + +typedef struct LineartRenderLineChain { + struct LineartRenderLineChain *next, *prev; + ListBase chain; + + /** Calculated before draw cmd. */ + float length; + + /** Used when re-connecting and gp stroke generation */ + char picked; + char level; + + /** Chain now only contains one type of segments */ + int type; + unsigned char transparency_mask; + + struct Object *object_ref; +} LineartRenderLineChain; + +typedef struct LineartRenderLineChainItem { + struct LineartRenderLineChainItem *next, *prev; + /** Need z value for fading */ + float pos[3]; + /** For restoring position to 3d space */ + float gpos[3]; + float normal[3]; + char line_type; + char occlusion; + unsigned char transparency_mask; + size_t index; +} LineartRenderLineChainItem; + +typedef struct LineartChainRegisterEntry { + struct LineartChainRegisterEntry *next, *prev; + LineartRenderLineChain *rlc; + LineartRenderLineChainItem *rlci; + char picked; + + /** left/right mark. + * Because we revert list in chaining so we need the flag. */ + char is_left; +} LineartChainRegisterEntry; + +typedef struct LineartRenderBuffer { + struct LineartRenderBuffer *prev, *next; + + int thread_count; + + int w, h; + int tile_size_w, tile_size_h; + int tile_count_x, tile_count_y; + double width_per_tile, height_per_tile; + double view_projection[4][4]; + + struct LineartBoundingArea *initial_bounding_areas; + unsigned int bounding_area_count; + + ListBase vertex_buffer_pointers; + ListBase line_buffer_pointers; + ListBase triangle_buffer_pointers; + + /* This one's memory is not from main pool and is free()ed after culling stage. */ + ListBase triangle_adjacent_pointers; + + ListBase intersecting_vertex_buffer; + /** Use the one comes with Line Art. */ + LineartStaticMemPool render_data_pool; + ListBase wasted_cuts; + SpinLock lock_cuts; + + /* Render status */ + double view_vector[3]; + + int triangle_size; + + unsigned int contour_count; + unsigned int contour_processed; + LineartRenderLine *contour_managed; + /* Now changed to linknodes. */ + LineartRenderLine *contours; + + unsigned int intersection_count; + unsigned int intersection_processed; + LineartRenderLine *intersection_managed; + LineartRenderLine *intersection_lines; + + unsigned int crease_count; + unsigned int crease_processed; + LineartRenderLine *crease_managed; + LineartRenderLine *crease_lines; + + unsigned int material_line_count; + unsigned int material_processed; + LineartRenderLine *material_managed; + LineartRenderLine *material_lines; + + unsigned int edge_mark_count; + unsigned int edge_mark_processed; + LineartRenderLine *edge_mark_managed; + LineartRenderLine *edge_marks; + + ListBase chains; + + /** For managing calculation tasks for multiple threads. */ + SpinLock lock_task; + + /* settings */ + + int max_occlusion_level; + double crease_angle; + double crease_cos; + + int draw_material_preview; + double material_transparency; + + bool use_contour; + bool use_crease; + bool use_material; + bool use_edge_marks; + bool use_intersections; + bool fuzzy_intersections; + bool fuzzy_everything; + bool allow_boundaries; + bool allow_overlapping_edges; + bool remove_doubles; + + /** Keep an copy of these data so when line art is running it's self-contained. */ + bool cam_is_persp; + float cam_obmat[4][4]; + double camera_pos[3]; + double near_clip, far_clip; + float shift_x, shift_y; + float crease_threshold; + float chaining_image_threshold; + float chaining_geometry_threshold; + float angle_splitting_threshold; +} LineartRenderBuffer; + +#define DBL_TRIANGLE_LIM 1e-8 +#define DBL_EDGE_LIM 1e-9 + +#define LRT_MEMORY_POOL_64MB (1 << 26) + +typedef enum eLineartTriangleFlags { + LRT_CULL_DONT_CARE = 0, + LRT_CULL_USED = (1 << 0), + LRT_CULL_DISCARD = (1 << 1), + LRT_CULL_GENERATED = (1 << 2), + LRT_TRIANGLE_INTERSECTION_ONLY = (1 << 3), + LRT_TRIANGLE_NO_INTERSECTION = (1 << 4), +} eLineartTriangleFlags; + +/** Controls how many lines a worker thread is processing at one request. + * There's no significant performance impact on choosing different values. + * Don't make it too small so that the worker thread won't request too many times. */ +#define LRT_THREAD_LINE_COUNT 1000 + +typedef struct LineartRenderTaskInfo { + struct LineartRenderBuffer *rb; + + int thread_id; + + LineartRenderLine *contour; + LineartRenderLine *contour_end; + + LineartRenderLine *intersection; + LineartRenderLine *intersection_end; + + LineartRenderLine *crease; + LineartRenderLine *crease_end; + + LineartRenderLine *material; + LineartRenderLine *material_end; + + LineartRenderLine *edge_mark; + LineartRenderLine *edge_mark_end; + +} LineartRenderTaskInfo; + +/** Bounding area diagram: + * + * +----+ <----U (Upper edge Y value) + * | | + * +----+ <----B (Bottom edge Y value) + * ^ ^ + * L R (Left/Right edge X value) + * + * Example structure when subdividing 1 bounding areas: + * 1 area can be divided into 4 smaller children to + * accomodate image areas with denser triangle distribution. + * +--+--+-----+ + * +--+--+ | + * +--+--+-----+ + * | | | + * +-----+-----+ + * lp/rp/up/bp is the list for + * storing pointers to adjacent bounding areas. + */ +typedef struct LineartBoundingArea { + double l, r, u, b; + double cx, cy; + + /** 1,2,3,4 quadrant */ + struct LineartBoundingArea *child; + + ListBase lp; + ListBase rp; + ListBase up; + ListBase bp; + + short triangle_count; + + ListBase linked_triangles; + ListBase linked_lines; + + /** Reserved for image space reduction && multithread chainning */ + ListBase linked_chains; +} LineartBoundingArea; + +#define LRT_TILE(tile, r, c, CCount) tile[r * CCount + c] + +#define LRT_CLAMP(a, Min, Max) a = a < Min ? Min : (a > Max ? Max : a) + +#define LRT_MAX3_INDEX(a, b, c) (a > b ? (a > c ? 0 : (b > c ? 1 : 2)) : (b > c ? 1 : 2)) + +#define LRT_MIN3_INDEX(a, b, c) (a < b ? (a < c ? 0 : (b < c ? 1 : 2)) : (b < c ? 1 : 2)) + +#define LRT_MAX3_INDEX_ABC(x, y, z) (x > y ? (x > z ? a : (y > z ? b : c)) : (y > z ? b : c)) + +#define LRT_MIN3_INDEX_ABC(x, y, z) (x < y ? (x < z ? a : (y < z ? b : c)) : (y < z ? b : c)) + +#define LRT_ABC(index) (index == 0 ? a : (index == 1 ? b : c)) + +#define LRT_DOUBLE_CLOSE_ENOUGH(a, b) (((a) + DBL_EDGE_LIM) >= (b) && ((a)-DBL_EDGE_LIM) <= (b)) + +BLI_INLINE int lineart_LineIntersectTest2d( + const double *a1, const double *a2, const double *b1, const double *b2, double *aRatio) +{ +#define USE_VECTOR_LINE_INTERSECTION +#ifdef USE_VECTOR_LINE_INTERSECTION + + /* from isect_line_line_v2_point() */ + + double s10[2], s32[2]; + double div; + + sub_v2_v2v2_db(s10, a2, a1); + sub_v2_v2v2_db(s32, b2, b1); + + div = cross_v2v2_db(s10, s32); + if (div != 0.0f) { + const double u = cross_v2v2_db(a2, a1); + const double v = cross_v2v2_db(b2, b1); + + const double rx = ((s32[0] * u) - (s10[0] * v)) / div; + const double ry = ((s32[1] * u) - (s10[1] * v)) / div; + double rr; + + if (fabs(a2[0] - a1[0]) > fabs(a2[1] - a1[1])) { + *aRatio = ratiod(a1[0], a2[0], rx); + if (fabs(b2[0] - b1[0]) > fabs(b2[1] - b1[1])) { + rr = ratiod(b1[0], b2[0], rx); + } + else { + rr = ratiod(b1[1], b2[1], ry); + } + if ((*aRatio) > 0 && (*aRatio) < 1 && rr > 0 && rr < 1) { + return 1; + } + return 0; + } + + *aRatio = ratiod(a1[1], a2[1], ry); + if (fabs(b2[0] - b1[0]) > fabs(b2[1] - b1[1])) { + rr = ratiod(b1[0], b2[0], rx); + } + else { + rr = ratiod(b1[1], b2[1], ry); + } + if ((*aRatio) > 0 && (*aRatio) < 1 && rr > 0 && rr < 1) { + return 1; + } + return 0; + } + return 0; + +#else + double k1, k2; + double x; + double y; + double ratio; + double x_diff = (a2[0] - a1[0]); + double x_diff2 = (b2[0] - b1[0]); + + if (LRT_DOUBLE_CLOSE_ENOUGH(x_diff, 0)) { + if (LRT_DOUBLE_CLOSE_ENOUGH(x_diff2, 0)) { + *aRatio = 0; + return 0; + } + double r2 = ratiod(b1[0], b2[0], a1[0]); + x = interpd(b2[0], b1[0], r2); + y = interpd(b2[1], b1[1], r2); + *aRatio = ratio = ratiod(a1[1], a2[1], y); + } + else { + if (LRT_DOUBLE_CLOSE_ENOUGH(x_diff2, 0)) { + ratio = ratiod(a1[0], a2[0], b1[0]); + x = interpd(a2[0], a1[0], ratio); + *aRatio = ratio; + } + else { + k1 = (a2[1] - a1[1]) / x_diff; + k2 = (b2[1] - b1[1]) / x_diff2; + + if ((k1 == k2)) + return 0; + + x = (a1[1] - b1[1] - k1 * a1[0] + k2 * b1[0]) / (k2 - k1); + + ratio = (x - a1[0]) / x_diff; + + *aRatio = ratio; + } + } + + if (LRT_DOUBLE_CLOSE_ENOUGH(b1[0], b2[0])) { + y = interpd(a2[1], a1[1], ratio); + if (y > MAX2(b1[1], b2[1]) || y < MIN2(b1[1], b2[1])) + return 0; + } + else if (ratio <= 0 || ratio > 1 || (b1[0] > b2[0] && x > b1[0]) || + (b1[0] < b2[0] && x < b1[0]) || (b2[0] > b1[0] && x > b2[0]) || + (b2[0] < b1[0] && x < b2[0])) + return 0; + + return 1; +#endif +} + +struct Depsgraph; +struct Scene; +struct LineartRenderBuffer; +struct LineartGpencilModifierData; + +void MOD_lineart_destroy_render_data(struct LineartGpencilModifierData *lmd); + +void MOD_lineart_chain_feature_lines(LineartRenderBuffer *rb); +void MOD_lineart_chain_split_for_fixed_occlusion(LineartRenderBuffer *rb); +void MOD_lineart_chain_connect(LineartRenderBuffer *rb, const bool do_geometry_space); +void MOD_lineart_chain_discard_short(LineartRenderBuffer *rb, const float threshold); +void MOD_lineart_chain_split_angle(LineartRenderBuffer *rb, float angle_threshold_rad); + +int MOD_lineart_chain_count(const LineartRenderLineChain *rlc); +void MOD_lineart_chain_clear_picked_flag(struct LineartRenderBuffer *rb); + +int MOD_lineart_compute_feature_lines(struct Depsgraph *depsgraph, + struct LineartGpencilModifierData *lmd); + +struct Scene; + +LineartBoundingArea *MOD_lineart_get_parent_bounding_area(LineartRenderBuffer *rb, + double x, + double y); + +LineartBoundingArea *MOD_lineart_get_bounding_area(LineartRenderBuffer *rb, double x, double y); + +struct bGPDlayer; +struct bGPDframe; +struct GpencilModifierData; + +void MOD_lineart_gpencil_generate(LineartRenderBuffer *rb, + struct Depsgraph *depsgraph, + struct Object *ob, + struct bGPDlayer *gpl, + struct bGPDframe *gpf, + char source_type, + void *source_reference, + int level_start, + int level_end, + int mat_nr, + short line_types, + unsigned char transparency_flags, + unsigned char transparency_mask, + short thickness, + float opacity, + float pre_sample_length, + const char *source_vgname, + const char *vgname, + int modifier_flags); + +float MOD_lineart_chain_compute_length(LineartRenderLineChain *rlc); + +struct wmOperatorType; + +void ED_operatortypes_lineart(void); diff --git a/source/blender/gpencil_modifiers/intern/lineart/lineart_chain.c b/source/blender/gpencil_modifiers/intern/lineart/lineart_chain.c new file mode 100644 index 00000000000..36115fa1027 --- /dev/null +++ b/source/blender/gpencil_modifiers/intern/lineart/lineart_chain.c @@ -0,0 +1,980 @@ +/* + * 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) 2019 Blender Foundation. + * All rights reserved. + */ + +/** \file + * \ingroup editors + */ + +#include "BLI_linklist.h" +#include "BLI_listbase.h" +#include "BLI_math.h" + +#include "BKE_customdata.h" +#include "BKE_object.h" + +#include "DEG_depsgraph_query.h" + +#include "DNA_camera_types.h" +#include "DNA_lineart_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_scene_types.h" + +#include "MOD_lineart.h" + +#include "bmesh.h" + +#include "lineart_intern.h" + +#include <math.h> + +#define LRT_OTHER_RV(rl, rv) ((rv) == (rl)->l ? (rl)->r : ((rv) == (rl)->r ? (rl)->l : NULL)) + +/* Get a connected line, only for lines who has the exact given vert, or (in the case of + * intersection lines) who has a vert that has the exact same position. */ +static LineartRenderLine *lineart_line_get_connected(LineartBoundingArea *ba, + LineartRenderVert *rv, + LineartRenderVert **new_rv, + int match_flag) +{ + LISTBASE_FOREACH (LinkData *, lip, &ba->linked_lines) { + LineartRenderLine *nrl = lip->data; + + if ((!(nrl->flags & LRT_EDGE_FLAG_ALL_TYPE)) || (nrl->flags & LRT_EDGE_FLAG_CHAIN_PICKED)) { + continue; + } + + if (match_flag && ((nrl->flags & LRT_EDGE_FLAG_ALL_TYPE) & match_flag) == 0) { + continue; + } + + *new_rv = LRT_OTHER_RV(nrl, rv); + if (*new_rv) { + return nrl; + } + + if (nrl->flags & LRT_EDGE_FLAG_INTERSECTION) { + if (rv->fbcoord[0] == nrl->l->fbcoord[0] && rv->fbcoord[1] == nrl->l->fbcoord[1]) { + *new_rv = LRT_OTHER_RV(nrl, nrl->l); + return nrl; + } + if (rv->fbcoord[0] == nrl->r->fbcoord[0] && rv->fbcoord[1] == nrl->r->fbcoord[1]) { + *new_rv = LRT_OTHER_RV(nrl, nrl->r); + return nrl; + } + } + } + + return NULL; +} + +static LineartRenderLineChain *lineart_chain_create(LineartRenderBuffer *rb) +{ + LineartRenderLineChain *rlc; + rlc = lineart_mem_aquire(&rb->render_data_pool, sizeof(LineartRenderLineChain)); + + BLI_addtail(&rb->chains, rlc); + + return rlc; +} + +static bool lineart_point_overlapping(LineartRenderLineChainItem *rlci, + float x, + float y, + double threshold) +{ + if (!rlci) { + return false; + } + if (((rlci->pos[0] + threshold) >= x) && ((rlci->pos[0] - threshold) <= x) && + ((rlci->pos[1] + threshold) >= y) && ((rlci->pos[1] - threshold) <= y)) { + return true; + } + return false; +} + +static LineartRenderLineChainItem *lineart_chain_append_point(LineartRenderBuffer *rb, + LineartRenderLineChain *rlc, + float *fbcoord, + float *gpos, + float *normal, + char type, + int level, + unsigned char transparency_mask, + size_t index) +{ + LineartRenderLineChainItem *rlci; + + if (lineart_point_overlapping(rlc->chain.last, fbcoord[0], fbcoord[1], 1e-5)) { + /* Because the new chain point is overlapping, just replace the type and occlusion level of the + * current point. This makes it so that the line to the point after this one has the correct + * type and level. */ + LineartRenderLineChainItem *old_rlci = rlc->chain.last; + old_rlci->line_type = type; + old_rlci->occlusion = level; + return old_rlci; + } + + rlci = lineart_mem_aquire(&rb->render_data_pool, sizeof(LineartRenderLineChainItem)); + + copy_v2_v2(rlci->pos, fbcoord); + copy_v3_v3(rlci->gpos, gpos); + rlci->index = index; + copy_v3_v3(rlci->normal, normal); + rlci->line_type = type & LRT_EDGE_FLAG_ALL_TYPE; + rlci->occlusion = level; + rlci->transparency_mask = transparency_mask; + BLI_addtail(&rlc->chain, rlci); + + return rlci; +} + +static LineartRenderLineChainItem *lineart_chain_prepend_point(LineartRenderBuffer *rb, + LineartRenderLineChain *rlc, + float *fbcoord, + float *gpos, + float *normal, + char type, + int level, + unsigned char transparency_mask, + size_t index) +{ + LineartRenderLineChainItem *rlci; + + if (lineart_point_overlapping(rlc->chain.first, fbcoord[0], fbcoord[1], 1e-5)) { + return rlc->chain.first; + } + + rlci = lineart_mem_aquire(&rb->render_data_pool, sizeof(LineartRenderLineChainItem)); + + copy_v2_v2(rlci->pos, fbcoord); + copy_v3_v3(rlci->gpos, gpos); + rlci->index = index; + copy_v3_v3(rlci->normal, normal); + rlci->line_type = type & LRT_EDGE_FLAG_ALL_TYPE; + rlci->occlusion = level; + rlci->transparency_mask = transparency_mask; + BLI_addhead(&rlc->chain, rlci); + + return rlci; +} + +void MOD_lineart_chain_feature_lines(LineartRenderBuffer *rb) +{ + LineartRenderLineChain *rlc; + LineartRenderLineChainItem *rlci; + LineartBoundingArea *ba; + LineartRenderLineSegment *rls; + int last_occlusion; + unsigned char last_transparency; + /* Used when converting from double. */ + float use_fbcoord[2]; + float use_gpos[3]; + +#define VERT_COORD_TO_FLOAT(a) \ + copy_v2fl_v2db(use_fbcoord, (a)->fbcoord); \ + copy_v3fl_v3db(use_gpos, (a)->gloc); + +#define POS_TO_FLOAT(lpos, gpos) \ + copy_v2fl_v2db(use_fbcoord, lpos); \ + copy_v3fl_v3db(use_gpos, gpos); + + LRT_ITER_ALL_LINES_BEGIN + { + if ((!(rl->flags & LRT_EDGE_FLAG_ALL_TYPE)) || (rl->flags & LRT_EDGE_FLAG_CHAIN_PICKED)) { + LRT_ITER_ALL_LINES_NEXT + continue; + } + + rl->flags |= LRT_EDGE_FLAG_CHAIN_PICKED; + + rlc = lineart_chain_create(rb); + + /* One chain can only have one object_ref, + * so we assign it based on the first segment we found. */ + rlc->object_ref = rl->object_ref; + + LineartRenderLine *new_rl = rl; + LineartRenderVert *new_rv; + float N[3] = {0}; + + if (rl->tl) { + N[0] += rl->tl->gn[0]; + N[1] += rl->tl->gn[1]; + N[2] += rl->tl->gn[2]; + } + if (rl->tr) { + N[0] += rl->tr->gn[0]; + N[1] += rl->tr->gn[1]; + N[2] += rl->tr->gn[2]; + } + if (rl->tl || rl->tr) { + normalize_v3(N); + } + + /* Step 1: grow left. */ + ba = MOD_lineart_get_bounding_area(rb, rl->l->fbcoord[0], rl->l->fbcoord[1]); + new_rv = rl->l; + rls = rl->segments.first; + VERT_COORD_TO_FLOAT(new_rv); + lineart_chain_prepend_point(rb, + rlc, + use_fbcoord, + use_gpos, + N, + rl->flags, + rls->occlusion, + rls->transparency_mask, + rl->l_obindex); + while (ba && (new_rl = lineart_line_get_connected(ba, new_rv, &new_rv, rl->flags))) { + new_rl->flags |= LRT_EDGE_FLAG_CHAIN_PICKED; + + if (new_rl->tl || new_rl->tr) { + zero_v3(N); + if (new_rl->tl) { + N[0] += new_rl->tl->gn[0]; + N[1] += new_rl->tl->gn[1]; + N[2] += new_rl->tl->gn[2]; + } + if (new_rl->tr) { + N[0] += new_rl->tr->gn[0]; + N[1] += new_rl->tr->gn[1]; + N[2] += new_rl->tr->gn[2]; + } + normalize_v3(N); + } + + if (new_rv == new_rl->l) { + for (rls = new_rl->segments.last; rls; rls = rls->prev) { + double gpos[3], lpos[3]; + double *lfb = new_rl->l->fbcoord, *rfb = new_rl->r->fbcoord; + double global_at = lfb[3] * rls->at / (rls->at * lfb[3] + (1 - rls->at) * rfb[3]); + interp_v3_v3v3_db(lpos, new_rl->l->fbcoord, new_rl->r->fbcoord, rls->at); + interp_v3_v3v3_db(gpos, new_rl->l->gloc, new_rl->r->gloc, global_at); + POS_TO_FLOAT(lpos, gpos) + lineart_chain_prepend_point(rb, + rlc, + use_fbcoord, + use_gpos, + N, + new_rl->flags, + rls->occlusion, + rls->transparency_mask, + new_rl->l_obindex); + last_occlusion = rls->occlusion; + last_transparency = rls->transparency_mask; + } + } + else if (new_rv == new_rl->r) { + rls = new_rl->segments.first; + last_occlusion = rls->occlusion; + last_transparency = rls->transparency_mask; + rls = rls->next; + for (; rls; rls = rls->next) { + double gpos[3], lpos[3]; + double *lfb = new_rl->l->fbcoord, *rfb = new_rl->r->fbcoord; + double global_at = lfb[3] * rls->at / (rls->at * lfb[3] + (1 - rls->at) * rfb[3]); + interp_v3_v3v3_db(lpos, new_rl->l->fbcoord, new_rl->r->fbcoord, rls->at); + interp_v3_v3v3_db(gpos, new_rl->l->gloc, new_rl->r->gloc, global_at); + POS_TO_FLOAT(lpos, gpos) + lineart_chain_prepend_point(rb, + rlc, + use_fbcoord, + use_gpos, + N, + new_rl->flags, + last_occlusion, + last_transparency, + new_rl->r_obindex); + last_occlusion = rls->occlusion; + last_transparency = rls->transparency_mask; + } + VERT_COORD_TO_FLOAT(new_rl->r); + lineart_chain_prepend_point(rb, + rlc, + use_fbcoord, + use_gpos, + N, + new_rl->flags, + last_occlusion, + last_transparency, + new_rl->r_obindex); + } + ba = MOD_lineart_get_bounding_area(rb, new_rv->fbcoord[0], new_rv->fbcoord[1]); + } + + /* Restore normal value. */ + if (rl->tl || rl->tr) { + zero_v3(N); + if (rl->tl) { + N[0] += rl->tl->gn[0]; + N[1] += rl->tl->gn[1]; + N[2] += rl->tl->gn[2]; + } + if (rl->tr) { + N[0] += rl->tr->gn[0]; + N[1] += rl->tr->gn[1]; + N[2] += rl->tr->gn[2]; + } + normalize_v3(N); + } + /* Step 2: Adding all cuts from the given line, so we can continue connecting the right side + * of the line. */ + rls = rl->segments.first; + last_occlusion = ((LineartRenderLineSegment *)rls)->occlusion; + last_transparency = ((LineartRenderLineSegment *)rls)->transparency_mask; + for (rls = rls->next; rls; rls = rls->next) { + double gpos[3], lpos[3]; + double *lfb = rl->l->fbcoord, *rfb = rl->r->fbcoord; + double global_at = lfb[3] * rls->at / (rls->at * lfb[3] + (1 - rls->at) * rfb[3]); + interp_v3_v3v3_db(lpos, rl->l->fbcoord, rl->r->fbcoord, rls->at); + interp_v3_v3v3_db(gpos, rl->l->gloc, rl->r->gloc, global_at); + POS_TO_FLOAT(lpos, gpos) + lineart_chain_append_point(rb, + rlc, + use_fbcoord, + use_gpos, + N, + rl->flags, + rls->occlusion, + rls->transparency_mask, + rl->l_obindex); + last_occlusion = rls->occlusion; + } + VERT_COORD_TO_FLOAT(rl->r) + lineart_chain_append_point(rb, + rlc, + use_fbcoord, + use_gpos, + N, + rl->flags, + last_occlusion, + last_transparency, + rl->r_obindex); + + /* Step 3: grow right. */ + ba = MOD_lineart_get_bounding_area(rb, rl->r->fbcoord[0], rl->r->fbcoord[1]); + new_rv = rl->r; + while (ba && (new_rl = lineart_line_get_connected(ba, new_rv, &new_rv, rl->flags))) { + new_rl->flags |= LRT_EDGE_FLAG_CHAIN_PICKED; + + if (new_rl->tl || new_rl->tr) { + zero_v3(N); + if (new_rl->tl) { + N[0] += new_rl->tl->gn[0]; + N[1] += new_rl->tl->gn[1]; + N[2] += new_rl->tl->gn[2]; + } + if (new_rl->tr) { + N[0] += new_rl->tr->gn[0]; + N[1] += new_rl->tr->gn[1]; + N[2] += new_rl->tr->gn[2]; + } + normalize_v3(N); + } + + /* Fix leading vertex type. */ + rlci = rlc->chain.last; + rlci->line_type = new_rl->flags & LRT_EDGE_FLAG_ALL_TYPE; + + if (new_rv == new_rl->l) { + rls = new_rl->segments.last; + last_occlusion = rls->occlusion; + last_transparency = rls->transparency_mask; + /* Fix leading vertex occlusion. */ + rlci->occlusion = last_occlusion; + for (rls = new_rl->segments.last; rls; rls = rls->prev) { + double gpos[3], lpos[3]; + double *lfb = new_rl->l->fbcoord, *rfb = new_rl->r->fbcoord; + double global_at = lfb[3] * rls->at / (rls->at * lfb[3] + (1 - rls->at) * rfb[3]); + interp_v3_v3v3_db(lpos, new_rl->l->fbcoord, new_rl->r->fbcoord, rls->at); + interp_v3_v3v3_db(gpos, new_rl->l->gloc, new_rl->r->gloc, global_at); + last_occlusion = rls->prev ? rls->prev->occlusion : last_occlusion; + POS_TO_FLOAT(lpos, gpos) + lineart_chain_append_point(rb, + rlc, + use_fbcoord, + use_gpos, + N, + new_rl->flags, + last_occlusion, + last_transparency, + new_rl->l_obindex); + } + } + else if (new_rv == new_rl->r) { + rls = new_rl->segments.first; + last_occlusion = rls->occlusion; + last_transparency = rls->transparency_mask; + rlci->occlusion = last_occlusion; + rls = rls->next; + for (; rls; rls = rls->next) { + double gpos[3], lpos[3]; + double *lfb = new_rl->l->fbcoord, *rfb = new_rl->r->fbcoord; + double global_at = lfb[3] * rls->at / (rls->at * lfb[3] + (1 - rls->at) * rfb[3]); + interp_v3_v3v3_db(lpos, new_rl->l->fbcoord, new_rl->r->fbcoord, rls->at); + interp_v3_v3v3_db(gpos, new_rl->l->gloc, new_rl->r->gloc, global_at); + POS_TO_FLOAT(lpos, gpos) + lineart_chain_append_point(rb, + rlc, + use_fbcoord, + use_gpos, + N, + new_rl->flags, + rls->occlusion, + rls->transparency_mask, + new_rl->r_obindex); + last_occlusion = rls->occlusion; + last_transparency = rls->transparency_mask; + } + VERT_COORD_TO_FLOAT(new_rl->r) + lineart_chain_append_point(rb, + rlc, + use_fbcoord, + use_gpos, + N, + new_rl->flags, + last_occlusion, + last_transparency, + new_rl->r_obindex); + } + ba = MOD_lineart_get_bounding_area(rb, new_rv->fbcoord[0], new_rv->fbcoord[1]); + } + if (rb->fuzzy_everything) { + rlc->type = LRT_EDGE_FLAG_CONTOUR; + } + else { + rlc->type = (rl->flags & LRT_EDGE_FLAG_ALL_TYPE); + } + } + LRT_ITER_ALL_LINES_END +} + +static LineartBoundingArea *lineart_bounding_area_get_rlci_recursive( + LineartRenderBuffer *rb, LineartBoundingArea *root, LineartRenderLineChainItem *rlci) +{ + if (root->child == NULL) { + return root; + } + + LineartBoundingArea *ch = root->child; +#define IN_BOUND(ba, rlci) \ + ba.l <= rlci->pos[0] && ba.r >= rlci->pos[0] && ba.b <= rlci->pos[1] && ba.u >= rlci->pos[1] + + if (IN_BOUND(ch[0], rlci)) { + return lineart_bounding_area_get_rlci_recursive(rb, &ch[0], rlci); + } + if (IN_BOUND(ch[1], rlci)) { + return lineart_bounding_area_get_rlci_recursive(rb, &ch[1], rlci); + } + if (IN_BOUND(ch[2], rlci)) { + return lineart_bounding_area_get_rlci_recursive(rb, &ch[2], rlci); + } + if (IN_BOUND(ch[3], rlci)) { + return lineart_bounding_area_get_rlci_recursive(rb, &ch[3], rlci); + } +#undef IN_BOUND + return NULL; +} + +static LineartBoundingArea *lineart_bounding_area_get_end_point(LineartRenderBuffer *rb, + LineartRenderLineChainItem *rlci) +{ + if (!rlci) { + return NULL; + } + LineartBoundingArea *root = MOD_lineart_get_parent_bounding_area(rb, rlci->pos[0], rlci->pos[1]); + if (root == NULL) { + return NULL; + } + return lineart_bounding_area_get_rlci_recursive(rb, root, rlci); +} + +/* Here we will try to connect geometry space chains together in image space. However we can't + * chain two chains together if their end and start points lie on the border between two bounding + * areas, this happens either when 1) the geometry is way too dense, or 2) the chaining threshold + * is too big that it covers multiple small bounding areas. */ +static void lineart_bounding_area_link_point_recursive(LineartRenderBuffer *rb, + LineartBoundingArea *root, + LineartRenderLineChain *rlc, + LineartRenderLineChainItem *rlci) +{ + if (root->child == NULL) { + LineartChainRegisterEntry *cre = lineart_list_append_pointer_pool_sized( + &root->linked_chains, &rb->render_data_pool, rlc, sizeof(LineartChainRegisterEntry)); + + cre->rlci = rlci; + + if (rlci == rlc->chain.first) { + cre->is_left = 1; + } + } + else { + LineartBoundingArea *ch = root->child; + +#define IN_BOUND(ba, rlci) \ + ba.l <= rlci->pos[0] && ba.r >= rlci->pos[0] && ba.b <= rlci->pos[1] && ba.u >= rlci->pos[1] + + if (IN_BOUND(ch[0], rlci)) { + lineart_bounding_area_link_point_recursive(rb, &ch[0], rlc, rlci); + } + else if (IN_BOUND(ch[1], rlci)) { + lineart_bounding_area_link_point_recursive(rb, &ch[1], rlc, rlci); + } + else if (IN_BOUND(ch[2], rlci)) { + lineart_bounding_area_link_point_recursive(rb, &ch[2], rlc, rlci); + } + else if (IN_BOUND(ch[3], rlci)) { + lineart_bounding_area_link_point_recursive(rb, &ch[3], rlc, rlci); + } + +#undef IN_BOUND + } +} + +static void lineart_bounding_area_link_chain(LineartRenderBuffer *rb, LineartRenderLineChain *rlc) +{ + LineartRenderLineChainItem *pl = rlc->chain.first; + LineartRenderLineChainItem *pr = rlc->chain.last; + LineartBoundingArea *ba1 = MOD_lineart_get_parent_bounding_area(rb, pl->pos[0], pl->pos[1]); + LineartBoundingArea *ba2 = MOD_lineart_get_parent_bounding_area(rb, pr->pos[0], pr->pos[1]); + + if (ba1) { + lineart_bounding_area_link_point_recursive(rb, ba1, rlc, pl); + } + if (ba2) { + lineart_bounding_area_link_point_recursive(rb, ba2, rlc, pr); + } +} + +void MOD_lineart_chain_split_for_fixed_occlusion(LineartRenderBuffer *rb) +{ + LineartRenderLineChain *rlc, *new_rlc; + LineartRenderLineChainItem *rlci, *next_rlci; + ListBase swap = {0}; + + swap.first = rb->chains.first; + swap.last = rb->chains.last; + + rb->chains.last = rb->chains.first = NULL; + + while ((rlc = BLI_pophead(&swap)) != NULL) { + rlc->next = rlc->prev = NULL; + BLI_addtail(&rb->chains, rlc); + LineartRenderLineChainItem *first_rlci = (LineartRenderLineChainItem *)rlc->chain.first; + int fixed_occ = first_rlci->occlusion; + unsigned char fixed_mask = first_rlci->transparency_mask; + rlc->level = fixed_occ; + rlc->transparency_mask = fixed_mask; + for (rlci = first_rlci->next; rlci; rlci = next_rlci) { + next_rlci = rlci->next; + if (rlci->occlusion != fixed_occ || rlci->transparency_mask != fixed_mask) { + if (next_rlci) { + if (lineart_point_overlapping(next_rlci, rlci->pos[0], rlci->pos[1], 1e-5)) { + continue; + } + } + else { + /* Set the same occlusion level for the end vertex, so when further connection is needed + * the backwards occlusion info is also correct. */ + rlci->occlusion = fixed_occ; + /* No need to split at the last point anyway. */ + break; + } + new_rlc = lineart_chain_create(rb); + new_rlc->chain.first = rlci; + new_rlc->chain.last = rlc->chain.last; + rlc->chain.last = rlci->prev; + ((LineartRenderLineChainItem *)rlc->chain.last)->next = 0; + rlci->prev = 0; + + /* End the previous one. */ + lineart_chain_append_point(rb, + rlc, + rlci->pos, + rlci->gpos, + rlci->normal, + rlci->line_type, + fixed_occ, + fixed_mask, + rlci->index); + new_rlc->object_ref = rlc->object_ref; + new_rlc->type = rlc->type; + rlc = new_rlc; + fixed_occ = rlci->occlusion; + fixed_mask = rlci->transparency_mask; + rlc->level = fixed_occ; + rlc->transparency_mask = fixed_mask; + } + } + } + LISTBASE_FOREACH (LineartRenderLineChain *, irlc, &rb->chains) { + lineart_bounding_area_link_chain(rb, irlc); + } +} + +/* Note: segment type (crease/material/contour...) is ambiguous after this. */ +static void lineart_chain_connect(LineartRenderBuffer *UNUSED(rb), + LineartRenderLineChain *onto, + LineartRenderLineChain *sub, + int reverse_1, + int reverse_2) +{ + LineartRenderLineChainItem *rlci; + if (onto->type == LRT_EDGE_FLAG_INTERSECTION) { + if (sub->object_ref) { + onto->object_ref = sub->object_ref; + onto->type = LRT_EDGE_FLAG_CONTOUR; + } + } + else if (sub->type == LRT_EDGE_FLAG_INTERSECTION) { + if (onto->type != LRT_EDGE_FLAG_INTERSECTION) { + onto->type = LRT_EDGE_FLAG_CONTOUR; + } + } + if (!reverse_1) { /* L--R L-R. */ + if (reverse_2) { /* L--R R-L. */ + BLI_listbase_reverse(&sub->chain); + } + rlci = sub->chain.first; + if (lineart_point_overlapping(onto->chain.last, rlci->pos[0], rlci->pos[1], 1e-5)) { + BLI_pophead(&sub->chain); + if (sub->chain.first == NULL) { + return; + } + } + ((LineartRenderLineChainItem *)onto->chain.last)->next = sub->chain.first; + ((LineartRenderLineChainItem *)sub->chain.first)->prev = onto->chain.last; + onto->chain.last = sub->chain.last; + } + else { /* L-R L--R. */ + if (!reverse_2) { /* R-L L--R. */ + BLI_listbase_reverse(&sub->chain); + } + rlci = onto->chain.first; + if (lineart_point_overlapping(sub->chain.last, rlci->pos[0], rlci->pos[1], 1e-5)) { + BLI_pophead(&onto->chain); + if (onto->chain.first == NULL) { + return; + } + } + ((LineartRenderLineChainItem *)sub->chain.last)->next = onto->chain.first; + ((LineartRenderLineChainItem *)onto->chain.first)->prev = sub->chain.last; + onto->chain.first = sub->chain.first; + } +} + +static LineartChainRegisterEntry *lineart_chain_get_closest_cre(LineartRenderBuffer *rb, + LineartBoundingArea *ba, + LineartRenderLineChain *rlc, + LineartRenderLineChainItem *rlci, + int occlusion, + unsigned char transparency_mask, + float dist, + int do_geometry_space, + float *result_new_len, + LineartBoundingArea *caller_ba) +{ + + LineartChainRegisterEntry *closest_cre = NULL; + + /* Keep using for loop because cre could be removed from the iteration before getting to the + * next one. */ + LISTBASE_FOREACH_MUTABLE (LineartChainRegisterEntry *, cre, &ba->linked_chains) { + if (cre->rlc->object_ref != rlc->object_ref) { + if (!rb->fuzzy_everything) { + if (rb->fuzzy_intersections) { + /* If none of those are intersection lines... */ + if ((!(cre->rlc->type & LRT_EDGE_FLAG_INTERSECTION)) && + (!(rlc->type & LRT_EDGE_FLAG_INTERSECTION))) { + continue; /* We don't want to chain along different objects at the moment. */ + } + } + else { + continue; + } + } + } + if (cre->rlc->picked || cre->picked) { + continue; + } + if (cre->rlc == rlc || (!cre->rlc->chain.first) || (cre->rlc->level != occlusion) || + (cre->rlc->transparency_mask != transparency_mask)) { + continue; + } + if (!rb->fuzzy_everything) { + if (cre->rlc->type != rlc->type) { + if (rb->fuzzy_intersections) { + if (!(cre->rlc->type == LRT_EDGE_FLAG_INTERSECTION || + rlc->type == LRT_EDGE_FLAG_INTERSECTION)) { + continue; /* Fuzzy intersetions but no intersection line found. */ + } + } + else { /* Line type different but no fuzzy. */ + continue; + } + } + } + + float new_len = do_geometry_space ? len_v3v3(cre->rlci->gpos, rlci->gpos) : + len_v2v2(cre->rlci->pos, rlci->pos); + if (new_len < dist) { + closest_cre = cre; + dist = new_len; + if (result_new_len) { + (*result_new_len) = new_len; + } + } + } + + /* We want a closer point anyway. So using modified dist is fine. */ + float adjacent_new_len = dist; + LineartChainRegisterEntry *adjacent_closest; + +#define LRT_TEST_ADJACENT_AREAS(dist_to, list) \ + if (dist_to < dist && dist_to > 0) { \ + LISTBASE_FOREACH (LinkData *, ld, list) { \ + LineartBoundingArea *sba = (LineartBoundingArea *)ld->data; \ + adjacent_closest = lineart_chain_get_closest_cre(rb, \ + sba, \ + rlc, \ + rlci, \ + occlusion, \ + transparency_mask, \ + dist, \ + do_geometry_space, \ + &adjacent_new_len, \ + ba); \ + if (adjacent_new_len < dist) { \ + dist = adjacent_new_len; \ + closest_cre = adjacent_closest; \ + } \ + } \ + } + if (!do_geometry_space && !caller_ba) { + LRT_TEST_ADJACENT_AREAS(rlci->pos[0] - ba->l, &ba->lp); + LRT_TEST_ADJACENT_AREAS(ba->r - rlci->pos[0], &ba->rp); + LRT_TEST_ADJACENT_AREAS(ba->u - rlci->pos[1], &ba->up); + LRT_TEST_ADJACENT_AREAS(rlci->pos[1] - ba->b, &ba->bp); + } + if (result_new_len) { + (*result_new_len) = dist; + } + return closest_cre; +} + +/* This function only connects two different chains. It will not do any clean up or smart chaining. + * So no: removing overlapping chains, removal of short isolated segments, and no loop reduction is + * implemented yet. */ +void MOD_lineart_chain_connect(LineartRenderBuffer *rb, const bool do_geometry_space) +{ + LineartRenderLineChain *rlc; + LineartRenderLineChainItem *rlci_l, *rlci_r; + LineartBoundingArea *ba_l, *ba_r; + LineartChainRegisterEntry *closest_cre_l, *closest_cre_r, *closest_cre; + float dist = do_geometry_space ? rb->chaining_geometry_threshold : rb->chaining_image_threshold; + float dist_l, dist_r; + int occlusion, reverse_main; + unsigned char transparency_mask; + ListBase swap = {0}; + + if ((!do_geometry_space && rb->chaining_image_threshold < 0.0001) || + (do_geometry_space && rb->chaining_geometry_threshold < 0.0001)) { + return; + } + + swap.first = rb->chains.first; + swap.last = rb->chains.last; + + rb->chains.last = rb->chains.first = NULL; + + while ((rlc = BLI_pophead(&swap)) != NULL) { + rlc->next = rlc->prev = NULL; + if (rlc->picked) { + continue; + } + BLI_addtail(&rb->chains, rlc); + + occlusion = rlc->level; + transparency_mask = rlc->transparency_mask; + + rlci_l = rlc->chain.first; + rlci_r = rlc->chain.last; + while ((ba_l = lineart_bounding_area_get_end_point(rb, rlci_l)) && + (ba_r = lineart_bounding_area_get_end_point(rb, rlci_r))) { + closest_cre_l = lineart_chain_get_closest_cre(rb, + ba_l, + rlc, + rlci_l, + occlusion, + transparency_mask, + dist, + do_geometry_space, + &dist_l, + NULL); + closest_cre_r = lineart_chain_get_closest_cre(rb, + ba_r, + rlc, + rlci_r, + occlusion, + transparency_mask, + dist, + do_geometry_space, + &dist_r, + NULL); + if (closest_cre_l && closest_cre_r) { + if (dist_l < dist_r) { + closest_cre = closest_cre_l; + reverse_main = 1; + } + else { + closest_cre = closest_cre_r; + reverse_main = 0; + } + } + else if (closest_cre_l) { + closest_cre = closest_cre_l; + reverse_main = 1; + } + else if (closest_cre_r) { + closest_cre = closest_cre_r; + BLI_remlink(&ba_r->linked_chains, closest_cre_r); + reverse_main = 0; + } + else { + break; + } + closest_cre->picked = 1; + closest_cre->rlc->picked = 1; + if (closest_cre->is_left) { + lineart_chain_connect(rb, rlc, closest_cre->rlc, reverse_main, 0); + } + else { + lineart_chain_connect(rb, rlc, closest_cre->rlc, reverse_main, 1); + } + BLI_remlink(&swap, closest_cre->rlc); + rlci_l = rlc->chain.first; + rlci_r = rlc->chain.last; + } + rlc->picked = 1; + } +} + +/* Length is in image space. */ +float MOD_lineart_chain_compute_length(LineartRenderLineChain *rlc) +{ + LineartRenderLineChainItem *rlci; + float offset_accum = 0; + float dist; + float last_point[2]; + + rlci = rlc->chain.first; + copy_v2_v2(last_point, rlci->pos); + for (rlci = rlc->chain.first; rlci; rlci = rlci->next) { + dist = len_v2v2(rlci->pos, last_point); + offset_accum += dist; + copy_v2_v2(last_point, rlci->pos); + } + return offset_accum; +} + +void MOD_lineart_chain_discard_short(LineartRenderBuffer *rb, const float threshold) +{ + LineartRenderLineChain *rlc, *next_rlc; + for (rlc = rb->chains.first; rlc; rlc = next_rlc) { + next_rlc = rlc->next; + if (MOD_lineart_chain_compute_length(rlc) < threshold) { + BLI_remlink(&rb->chains, rlc); + } + } +} + +int MOD_lineart_chain_count(const LineartRenderLineChain *rlc) +{ + int count = 0; + LISTBASE_FOREACH (LineartRenderLineChainItem *, rlci, &rlc->chain) { + count++; + } + return count; +} + +void MOD_lineart_chain_clear_picked_flag(LineartRenderBuffer *rb) +{ + if (rb == NULL) { + return; + } + LISTBASE_FOREACH (LineartRenderLineChain *, rlc, &rb->chains) { + rlc->picked = 0; + } +} + +/* This should always be the last stage!, see the end of + * MOD_lineart_chain_split_for_fixed_occlusion().*/ +void MOD_lineart_chain_split_angle(LineartRenderBuffer *rb, float angle_threshold_rad) +{ + LineartRenderLineChain *rlc, *new_rlc; + LineartRenderLineChainItem *rlci, *next_rlci, *prev_rlci; + ListBase swap = {0}; + + swap.first = rb->chains.first; + swap.last = rb->chains.last; + + rb->chains.last = rb->chains.first = NULL; + + while ((rlc = BLI_pophead(&swap)) != NULL) { + rlc->next = rlc->prev = NULL; + BLI_addtail(&rb->chains, rlc); + LineartRenderLineChainItem *first_rlci = (LineartRenderLineChainItem *)rlc->chain.first; + for (rlci = first_rlci->next; rlci; rlci = next_rlci) { + next_rlci = rlci->next; + prev_rlci = rlci->prev; + float angle = M_PI; + if (next_rlci && prev_rlci) { + angle = angle_v2v2v2(prev_rlci->pos, rlci->pos, next_rlci->pos); + } + else { + break; /* No need to split at the last point anyway.*/ + } + if (angle < angle_threshold_rad) { + new_rlc = lineart_chain_create(rb); + new_rlc->chain.first = rlci; + new_rlc->chain.last = rlc->chain.last; + rlc->chain.last = rlci->prev; + ((LineartRenderLineChainItem *)rlc->chain.last)->next = 0; + rlci->prev = 0; + + /* End the previous one. */ + lineart_chain_append_point(rb, + rlc, + rlci->pos, + rlci->gpos, + rlci->normal, + rlci->line_type, + rlc->level, + rlci->transparency_mask, + rlci->index); + new_rlc->object_ref = rlc->object_ref; + new_rlc->type = rlc->type; + new_rlc->level = rlc->level; + new_rlc->transparency_mask = rlc->transparency_mask; + rlc = new_rlc; + } + } + } +} diff --git a/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c b/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c new file mode 100644 index 00000000000..195f0088114 --- /dev/null +++ b/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c @@ -0,0 +1,3931 @@ +/* + * 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) 2019 Blender Foundation. + * All rights reserved. + */ + +/* \file + * \ingroup editors + */ + +#include "MOD_lineart.h" + +#include "BLI_alloca.h" +#include "BLI_linklist.h" +#include "BLI_listbase.h" +#include "BLI_math_matrix.h" +#include "BLI_task.h" +#include "BLI_utildefines.h" + +#include "BKE_callbacks.h" +#include "BKE_camera.h" +#include "BKE_collection.h" +#include "BKE_context.h" +#include "BKE_curve.h" +#include "BKE_customdata.h" +#include "BKE_deform.h" +#include "BKE_editmesh.h" +#include "BKE_global.h" +#include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" +#include "BKE_gpencil_modifier.h" +#include "BKE_material.h" +#include "BKE_mesh.h" +#include "BKE_object.h" +#include "BKE_scene.h" +#include "BKE_screen.h" +#include "BKE_text.h" +#include "DEG_depsgraph_query.h" +#include "DNA_camera_types.h" +#include "DNA_collection_types.h" +#include "DNA_gpencil_types.h" +#include "DNA_lineart_types.h" +#include "DNA_material_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_modifier_types.h" +#include "DNA_scene_types.h" +#include "DNA_text_types.h" +#include "MEM_guardedalloc.h" + +#include "RNA_access.h" +#include "RNA_define.h" + +#include "BLI_math.h" +#include "BLI_string_utils.h" + +#include "bmesh.h" +#include "bmesh_class.h" +#include "bmesh_tools.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "MOD_gpencil_modifiertypes.h" + +#include "lineart_intern.h" + +static LineartBoundingArea *lineart_line_first_bounding_area(LineartRenderBuffer *rb, + LineartRenderLine *rl); + +static void lineart_bounding_area_link_line(LineartRenderBuffer *rb, + LineartBoundingArea *root_ba, + LineartRenderLine *rl); + +static LineartBoundingArea *lineart_bounding_area_next(LineartBoundingArea *This, + LineartRenderLine *rl, + double x, + double y, + double k, + int positive_x, + int positive_y, + double *next_x, + double *next_y); + +static bool lineart_get_line_bounding_areas(LineartRenderBuffer *rb, + LineartRenderLine *rl, + int *rowbegin, + int *rowend, + int *colbegin, + int *colend); + +static void lineart_bounding_area_link_triangle(LineartRenderBuffer *rb, + LineartBoundingArea *root_ba, + LineartRenderTriangle *rt, + double *LRUB, + int recursive, + int recursive_level, + bool do_intersection); + +static bool lineart_triangle_line_image_space_occlusion(SpinLock *spl, + const LineartRenderTriangle *rt, + const LineartRenderLine *rl, + const double *override_camera_loc, + const bool override_cam_is_persp, + const bool allow_overlapping_edges, + const double vp[4][4], + const double *camera_dir, + const float cam_shift_x, + const float cam_shift_y, + double *from, + double *to); + +static void lineart_add_line_to_list(LineartRenderBuffer *rb, LineartRenderLine *rl); + +static void lineart_line_discard_segment(LineartRenderBuffer *rb, LineartRenderLineSegment *rls) +{ + BLI_spin_lock(&rb->lock_cuts); + + memset(rls, 0, sizeof(LineartRenderLineSegment)); + + /* Storing the node for potentially reuse the memory for new segment data. Line Art data is not + * freed after all calulations are done. */ + BLI_addtail(&rb->wasted_cuts, rls); + + BLI_spin_unlock(&rb->lock_cuts); +} + +static LineartRenderLineSegment *lineart_line_give_segment(LineartRenderBuffer *rb) +{ + BLI_spin_lock(&rb->lock_cuts); + + /* See if there is any already allocated memory we can reuse. */ + if (rb->wasted_cuts.first) { + LineartRenderLineSegment *rls = (LineartRenderLineSegment *)BLI_pophead(&rb->wasted_cuts); + BLI_spin_unlock(&rb->lock_cuts); + memset(rls, 0, sizeof(LineartRenderLineSegment)); + return rls; + } + BLI_spin_unlock(&rb->lock_cuts); + + /* Otherwise allocate some new memory. */ + return (LineartRenderLineSegment *)lineart_mem_aquire_thread(&rb->render_data_pool, + sizeof(LineartRenderLineSegment)); +} + +/* Cuts the line in image space and mark occlusion level for each segment. */ +static void lineart_line_cut(LineartRenderBuffer *rb, + LineartRenderLine *rl, + double start, + double end, + unsigned char transparency_mask) +{ + LineartRenderLineSegment *rls, *irls, *next_rls, *prev_rls; + LineartRenderLineSegment *start_segment = 0, *end_segment = 0; + LineartRenderLineSegment *ns = 0, *ns2 = 0; + int untouched = 0; + + /* If for some reason the occlusion function may give a result that has zero length, or reversed + * in direction, or NAN, so we take care of them here. */ + if (LRT_DOUBLE_CLOSE_ENOUGH(start, end)) { + return; + } + if (LRT_DOUBLE_CLOSE_ENOUGH(start, 1) || LRT_DOUBLE_CLOSE_ENOUGH(end, 0)) { + return; + } + if (UNLIKELY(start != start)) { + start = 0; + } + if (UNLIKELY(end != end)) { + end = 0; + } + + if (start > end) { + double t = start; + start = end; + end = t; + } + + /* Begin looking for starting position of the segment. */ + /* Not using a list iteration macro because of it more clear when using for loops to iterate + * through the segments. */ + for (rls = rl->segments.first; rls; rls = rls->next) { + if (LRT_DOUBLE_CLOSE_ENOUGH(rls->at, start)) { + start_segment = rls; + ns = start_segment; + break; + } + if (rls->next == NULL) { + break; + } + irls = rls->next; + if (irls->at > start + 1e-09 && start > rls->at) { + start_segment = irls; + ns = lineart_mem_aquire_thread(&rb->render_data_pool, sizeof(LineartRenderLineSegment)); + break; + } + } + if (!start_segment && LRT_DOUBLE_CLOSE_ENOUGH(1, end)) { + untouched = 1; + } + for (rls = start_segment; rls; rls = rls->next) { + /* We tried to cut at existing cutting point (e.g. where the line's occluded by a triangle + * strip). */ + if (LRT_DOUBLE_CLOSE_ENOUGH(rls->at, end)) { + end_segment = rls; + ns2 = end_segment; + break; + } + /* This check is to prevent rls->at == 1.0 (where we don't need to cut because we are at the + * end point). */ + if (!rls->next && LRT_DOUBLE_CLOSE_ENOUGH(1, end)) { + end_segment = rls; + ns2 = end_segment; + untouched = 1; + break; + } + /* When an actual cut is needed in the line. */ + if (rls->at > end) { + end_segment = rls; + ns2 = lineart_line_give_segment(rb); + break; + } + } + + /* When an actual cut is needed in the line. */ + if (ns == NULL) { + ns = lineart_line_give_segment(rb); + } + if (ns2 == NULL) { + if (untouched) { + ns2 = ns; + end_segment = ns2; + } + else { + ns2 = lineart_line_give_segment(rb); + } + } + + /* Insert cutting points. */ + if (start_segment) { + if (start_segment != ns) { + ns->occlusion = start_segment->prev ? (irls = start_segment->prev)->occlusion : 0; + BLI_insertlinkbefore(&rl->segments, (void *)start_segment, (void *)ns); + } + } + else { + ns->occlusion = (irls = rl->segments.last)->occlusion; + BLI_addtail(&rl->segments, ns); + } + if (end_segment) { + if (end_segment != ns2) { + ns2->occlusion = end_segment->prev ? (irls = end_segment->prev)->occlusion : 0; + BLI_insertlinkbefore(&rl->segments, (void *)end_segment, (void *)ns2); + } + } + else { + ns2->occlusion = (irls = rl->segments.last)->occlusion; + BLI_addtail(&rl->segments, ns2); + } + + ns->at = start; + if (!untouched) { + ns2->at = end; + } + else { + ns2 = ns2->next; + } + + /* Register 1 level of occlusion. */ + for (rls = ns; rls && rls != ns2; rls = rls->next) { + rls->occlusion++; + rls->transparency_mask |= transparency_mask; + } + + /* Reduce adjacent cutting points of the same level, which saves memory. */ + char min_occ = 127; + prev_rls = NULL; + for (rls = rl->segments.first; rls; rls = next_rls) { + next_rls = rls->next; + + if (prev_rls && prev_rls->occlusion == rls->occlusion && + prev_rls->transparency_mask == rls->transparency_mask) { + BLI_remlink(&rl->segments, rls); + /* This put the node back to the render buffer, if more cut happens, these unused nodes get + * picked first. */ + lineart_line_discard_segment(rb, rls); + continue; + } + + min_occ = MIN2(min_occ, rls->occlusion); + + prev_rls = rls; + } + rl->min_occ = min_occ; +} + +/* To see if given line is connected to an adjacent intersection line. */ +BLI_INLINE bool lineart_occlusion_is_adjacent_intersection(LineartRenderLine *rl, + LineartRenderTriangle *rt) +{ + LineartRenderVertIntersection *l = (void *)rl->l; + LineartRenderVertIntersection *r = (void *)rl->r; + return ((l->base.flag && l->intersecting_with == (void *)rt) || + (r->base.flag && r->intersecting_with == (void *)rt)); +} + +static void lineart_occlusion_single_line(LineartRenderBuffer *rb, + LineartRenderLine *rl, + int thread_id) +{ + double x = rl->l->fbcoord[0], y = rl->l->fbcoord[1]; + LineartBoundingArea *ba = lineart_line_first_bounding_area(rb, rl); + LineartBoundingArea *nba = ba; + LineartRenderTriangleThread *rt; + + /* These values are used for marching along the line. */ + double l, r; + double k = (rl->r->fbcoord[1] - rl->l->fbcoord[1]) / + (rl->r->fbcoord[0] - rl->l->fbcoord[0] + 1e-30); + int positive_x = (rl->r->fbcoord[0] - rl->l->fbcoord[0]) > 0 ? + 1 : + (rl->r->fbcoord[0] == rl->l->fbcoord[0] ? 0 : -1); + int positive_y = (rl->r->fbcoord[1] - rl->l->fbcoord[1]) > 0 ? + 1 : + (rl->r->fbcoord[1] == rl->l->fbcoord[1] ? 0 : -1); + + while (nba) { + + LISTBASE_FOREACH (LinkData *, lip, &nba->linked_triangles) { + rt = lip->data; + /* If we are already testing the line in this thread, then don't do it. */ + if (rt->testing[thread_id] == rl || (rt->base.flags & LRT_TRIANGLE_INTERSECTION_ONLY) || + lineart_occlusion_is_adjacent_intersection(rl, (LineartRenderTriangle *)rt)) { + continue; + } + rt->testing[thread_id] = rl; + if (lineart_triangle_line_image_space_occlusion(&rb->lock_task, + (void *)rt, + rl, + rb->camera_pos, + rb->cam_is_persp, + rb->allow_overlapping_edges, + rb->view_projection, + rb->view_vector, + rb->shift_x, + rb->shift_y, + &l, + &r)) { + lineart_line_cut(rb, rl, l, r, rt->base.transparency_mask); + if (rl->min_occ > rb->max_occlusion_level) { + /* No need to caluclate any longer on this line because no level more than set value is + * going to show up in the rendered result. */ + return; + } + } + } + /* Marching along rl->l to rl->r, searching each possible bounding areas it may touch. */ + nba = lineart_bounding_area_next(nba, rl, x, y, k, positive_x, positive_y, &x, &y); + } +} + +static int lineart_occlusion_make_task_info(LineartRenderBuffer *rb, LineartRenderTaskInfo *rti) +{ + LineartRenderLine *data; + int i; + int res = 0; + + BLI_spin_lock(&rb->lock_task); + +#define LRT_ASSIGN_OCCLUSION_TASK(name) \ + if (rb->name##_managed) { \ + data = rb->name##_managed; \ + rti->name = (void *)data; \ + for (i = 0; i < LRT_THREAD_LINE_COUNT && data; i++) { \ + data = data->next; \ + } \ + rti->name##_end = data; \ + rb->name##_managed = data; \ + res = 1; \ + } \ + else { \ + rti->name = NULL; \ + } + + LRT_ASSIGN_OCCLUSION_TASK(contour); + LRT_ASSIGN_OCCLUSION_TASK(intersection); + LRT_ASSIGN_OCCLUSION_TASK(crease); + LRT_ASSIGN_OCCLUSION_TASK(material); + LRT_ASSIGN_OCCLUSION_TASK(edge_mark); + +#undef LRT_ASSIGN_OCCLUSION_TASK + + BLI_spin_unlock(&rb->lock_task); + + return res; +} + +static void lineart_occlusion_worker(TaskPool *__restrict UNUSED(pool), LineartRenderTaskInfo *rti) +{ + LineartRenderBuffer *rb = rti->rb; + LineartRenderLine *lip; + + while (lineart_occlusion_make_task_info(rb, rti)) { + + for (lip = (void *)rti->contour; lip && lip != rti->contour_end; lip = lip->next) { + lineart_occlusion_single_line(rb, lip, rti->thread_id); + } + + for (lip = (void *)rti->crease; lip && lip != rti->crease_end; lip = lip->next) { + lineart_occlusion_single_line(rb, lip, rti->thread_id); + } + + for (lip = (void *)rti->intersection; lip && lip != rti->intersection_end; lip = lip->next) { + lineart_occlusion_single_line(rb, lip, rti->thread_id); + } + + for (lip = (void *)rti->material; lip && lip != rti->material_end; lip = lip->next) { + lineart_occlusion_single_line(rb, lip, rti->thread_id); + } + + for (lip = (void *)rti->edge_mark; lip && lip != rti->edge_mark_end; lip = lip->next) { + lineart_occlusion_single_line(rb, lip, rti->thread_id); + } + } +} + +/* All internal functions starting with lineart_main_ is called inside + * MOD_lineart_compute_feature_lines function. + * This function handles all occlusion calculation. */ +static void lineart_main_occlusion_begin(LineartRenderBuffer *rb) +{ + int thread_count = rb->thread_count; + LineartRenderTaskInfo *rti = MEM_callocN(sizeof(LineartRenderTaskInfo) * thread_count, + "Task Pool"); + int i; + + rb->contour_managed = rb->contours; + rb->crease_managed = rb->crease_lines; + rb->intersection_managed = rb->intersection_lines; + rb->material_managed = rb->material_lines; + rb->edge_mark_managed = rb->edge_marks; + + TaskPool *tp = BLI_task_pool_create(NULL, TASK_PRIORITY_HIGH); + + for (i = 0; i < thread_count; i++) { + rti[i].thread_id = i; + rti[i].rb = rb; + BLI_task_pool_push(tp, (TaskRunFunction)lineart_occlusion_worker, &rti[i], 0, NULL); + } + BLI_task_pool_work_and_wait(tp); + BLI_task_pool_free(tp); + + MEM_freeN(rti); +} + +/* Test if v lies with in the triangle formed by v0, v1, and v2. Returns false when v is exactly on + * the edge. + * For v to be inside the triangle, it needs to be at the same side of v0->v1, v1->v2, and + * v2->v0, where the "side" is determined by checking the sign of cross(v1-v0, v1-v) and so on. + */ +static bool lineart_point_inside_triangle(const double v[2], + const double v0[2], + const double v1[2], + const double v2[2]) +{ + double cl, c; + + cl = (v0[0] - v[0]) * (v1[1] - v[1]) - (v0[1] - v[1]) * (v1[0] - v[0]); + c = cl; + + cl = (v1[0] - v[0]) * (v2[1] - v[1]) - (v1[1] - v[1]) * (v2[0] - v[0]); + if (c * cl <= 0) { + return false; + } + + c = cl; + + cl = (v2[0] - v[0]) * (v0[1] - v[1]) - (v2[1] - v[1]) * (v0[0] - v[0]); + if (c * cl <= 0) { + return false; + } + + c = cl; + + cl = (v0[0] - v[0]) * (v1[1] - v[1]) - (v0[1] - v[1]) * (v1[0] - v[0]); + if (c * cl <= 0) { + return false; + } + + return true; +} + +static int lineart_point_on_line_segment(double v[2], double v0[2], double v1[2]) +{ + /* c1!=c2 by default. */ + double c1 = 1, c2 = 0; + double l0[2], l1[2]; + + sub_v2_v2v2_db(l0, v, v0); + sub_v2_v2v2_db(l1, v, v1); + + if (v1[0] == v0[0] && v1[1] == v0[1]) { + return 0; + } + + if (v1[0] - v0[0]) { + c1 = ratiod(v0[0], v1[0], v[0]); + } + else if (v[0] == v1[0]) { + c2 = ratiod(v0[1], v1[1], v[1]); + return (c2 >= 0 && c2 <= 1); + } + + if (v1[1] - v0[1]) { + c2 = ratiod(v0[1], v1[1], v[1]); + } + else if (v[1] == v1[1]) { + c1 = ratiod(v0[0], v1[0], v[0]); + return (c1 >= 0 && c1 <= 1); + } + + if (LRT_DOUBLE_CLOSE_ENOUGH(c1, c2) && c1 >= 0 && c1 <= 1) { + return 1; + } + + return 0; +} + +/* Same algorithm as lineart_point_inside_triangle(), but returns differently: + * 0-outside 1-on the edge 2-inside. */ +static int lineart_point_triangle_relation(double v[2], double v0[2], double v1[2], double v2[2]) +{ + double cl, c; + double r; + if (lineart_point_on_line_segment(v, v0, v1) || lineart_point_on_line_segment(v, v1, v2) || + lineart_point_on_line_segment(v, v2, v0)) { + return 1; + } + + cl = (v0[0] - v[0]) * (v1[1] - v[1]) - (v0[1] - v[1]) * (v1[0] - v[0]); + c = cl; + + cl = (v1[0] - v[0]) * (v2[1] - v[1]) - (v1[1] - v[1]) * (v2[0] - v[0]); + if ((r = c * cl) < 0) { + return 0; + } + + c = cl; + + cl = (v2[0] - v[0]) * (v0[1] - v[1]) - (v2[1] - v[1]) * (v0[0] - v[0]); + if ((r = c * cl) < 0) { + return 0; + } + + c = cl; + + cl = (v0[0] - v[0]) * (v1[1] - v[1]) - (v0[1] - v[1]) * (v1[0] - v[0]); + if ((r = c * cl) < 0) { + return 0; + } + + if (r == 0) { + return 1; + } + + return 2; +} + +/* Similar with lineart_point_inside_triangle, but in 3d. + * Returns false when not co-plannar. */ +static bool lineart_point_inside_triangle3d(double v[3], double v0[3], double v1[3], double v2[3]) +{ + double l[3], r[3]; + double N1[3], N2[3]; + double d; + + sub_v3_v3v3_db(l, v1, v0); + sub_v3_v3v3_db(r, v, v1); + cross_v3_v3v3_db(N1, l, r); + + sub_v3_v3v3_db(l, v2, v1); + sub_v3_v3v3_db(r, v, v2); + cross_v3_v3v3_db(N2, l, r); + + if ((d = dot_v3v3_db(N1, N2)) < 0) { + return false; + } + + sub_v3_v3v3_db(l, v0, v2); + sub_v3_v3v3_db(r, v, v0); + cross_v3_v3v3_db(N1, l, r); + + if ((d = dot_v3v3_db(N1, N2)) < 0) { + return false; + } + + sub_v3_v3v3_db(l, v1, v0); + sub_v3_v3v3_db(r, v, v1); + cross_v3_v3v3_db(N2, l, r); + + if ((d = dot_v3v3_db(N1, N2)) < 0) { + return false; + } + + return true; +} + +/* The following lineart_memory_get_XXX_space functions are for allocating new memory for some + * modified geometries in the culling stage. */ +static LineartRenderElementLinkNode *lineart_memory_get_triangle_space(LineartRenderBuffer *rb) +{ + LineartRenderElementLinkNode *reln; + + /* We don't need to allocate a whole bunch of triangles because the amount of clipped triangles + * are relatively small. */ + LineartRenderTriangle *render_triangles = lineart_mem_aquire(&rb->render_data_pool, + 64 * rb->triangle_size); + + reln = lineart_list_append_pointer_pool_sized(&rb->triangle_buffer_pointers, + &rb->render_data_pool, + render_triangles, + sizeof(LineartRenderElementLinkNode)); + reln->element_count = 64; + reln->flags |= LRT_ELEMENT_IS_ADDITIONAL; + + return reln; +} + +static LineartRenderElementLinkNode *lineart_memory_get_vert_space(LineartRenderBuffer *rb) +{ + LineartRenderElementLinkNode *reln; + + LineartRenderVert *render_vertices = lineart_mem_aquire(&rb->render_data_pool, + sizeof(LineartRenderVert) * 64); + + reln = lineart_list_append_pointer_pool_sized(&rb->vertex_buffer_pointers, + &rb->render_data_pool, + render_vertices, + sizeof(LineartRenderElementLinkNode)); + reln->element_count = 64; + reln->flags |= LRT_ELEMENT_IS_ADDITIONAL; + + return reln; +} + +static LineartRenderElementLinkNode *lineart_memory_get_line_space(LineartRenderBuffer *rb) +{ + LineartRenderElementLinkNode *reln; + + LineartRenderLine *render_lines = lineart_mem_aquire(&rb->render_data_pool, + sizeof(LineartRenderLine) * 64); + + reln = lineart_list_append_pointer_pool_sized(&rb->line_buffer_pointers, + &rb->render_data_pool, + render_lines, + sizeof(LineartRenderElementLinkNode)); + reln->element_count = 64; + reln->crease_threshold = rb->crease_threshold; + reln->flags |= LRT_ELEMENT_IS_ADDITIONAL; + + return reln; +} + +static void lineart_triangle_post(LineartRenderTriangle *rt, LineartRenderTriangle *orig) +{ + /* Just re-assign normal and set cull flag. */ + copy_v3_v3_db(rt->gn, orig->gn); + rt->flags = LRT_CULL_GENERATED; +} + +static void lineart_triangle_set_cull_flag(LineartRenderTriangle *rt, unsigned char flag) +{ + unsigned char intersection_only = (rt->flags & LRT_TRIANGLE_INTERSECTION_ONLY); + rt->flags = flag; + rt->flags |= intersection_only; +} + +static bool lineart_line_match(LineartRenderTriangle *rt, LineartRenderLine *rl, int v1, int v2) +{ + return ((rt->v[v1] == rl->l && rt->v[v2] == rl->r) || + (rt->v[v2] == rl->l && rt->v[v1] == rl->r)); +} + +/* Does near-plane cut on 1 triangle only. When cutting with far-plane, the camera vectors gets + * reversed by the caller so don't need to implement one in a different direction. */ +static void lineart_triangle_cull_single(LineartRenderBuffer *rb, + LineartRenderTriangle *rt, + int in0, + int in1, + int in2, + double *cam_pos, + double *view_dir, + bool allow_boundaries, + double (*vp)[4], + Object *ob, + int *r_v_count, + int *r_l_count, + int *r_t_count, + LineartRenderElementLinkNode *veln, + LineartRenderElementLinkNode *leln, + LineartRenderElementLinkNode *teln) +{ + double vv1[3], vv2[3], dot1, dot2; + double a; + int v_count = *r_v_count; + int l_count = *r_l_count; + int t_count = *r_t_count; + int l_obi, r_obi; + char new_flag = 0; + + LineartRenderLine *new_rl, *rl, *old_rl; + LineartRenderLineSegment *rls; + LineartRenderTriangleAdjacent *rta; + + if (rt->flags & (LRT_CULL_USED | LRT_CULL_GENERATED | LRT_CULL_DISCARD)) { + return; + } + + /* See definition of rt->intersecting_verts and the usage in + * lineart_geometry_object_load() for details. */ + rta = (void *)rt->intersecting_verts; + + LineartRenderVert *rv = &((LineartRenderVert *)veln->pointer)[v_count]; + LineartRenderTriangle *rt1 = (void *)(((unsigned char *)teln->pointer) + + rb->triangle_size * t_count); + LineartRenderTriangle *rt2 = (void *)(((unsigned char *)teln->pointer) + + rb->triangle_size * (t_count + 1)); + + new_rl = &((LineartRenderLine *)leln->pointer)[l_count]; + /* Init rl to the last rl entry. */ + rl = new_rl; + +#define INCREASE_RL \ + l_count++; \ + l_obi = rl->l_obindex; \ + r_obi = rl->r_obindex; \ + new_rl = &((LineartRenderLine *)leln->pointer)[l_count]; \ + rl = new_rl; \ + rl->l_obindex = l_obi; \ + rl->r_obindex = r_obi; \ + rls = lineart_mem_aquire(&rb->render_data_pool, sizeof(LineartRenderLineSegment)); \ + BLI_addtail(&rl->segments, rls); + +#define SELECT_RL(rl_num, llink, rlink, newrt) \ + if (rta->rl[rl_num]) { \ + old_rl = rta->rl[rl_num]; \ + new_flag = old_rl->flags; \ + old_rl->flags = LRT_EDGE_FLAG_CHAIN_PICKED; \ + INCREASE_RL \ + rl->l = (llink); \ + rl->r = (rlink); \ + rl->flags = new_flag; \ + rl->object_ref = ob; \ + rl->tl = ((old_rl->tl == rt) ? (newrt) : (old_rl->tl)); \ + rl->tr = ((old_rl->tr == rt) ? (newrt) : (old_rl->tr)); \ + lineart_add_line_to_list(rb, rl); \ + } + +#define RELINK_RL(rl_num, newrt) \ + if (rta->rl[rl_num]) { \ + old_rl = rta->rl[rl_num]; \ + old_rl->tl = ((old_rl->tl == rt) ? (newrt) : (old_rl->tl)); \ + old_rl->tr = ((old_rl->tr == rt) ? (newrt) : (old_rl->tr)); \ + } + +#define REMOVE_TRIANGLE_RL \ + if (rta->rl[0]) { \ + rta->rl[0]->flags = LRT_EDGE_FLAG_CHAIN_PICKED; \ + } \ + if (rta->rl[1]) { \ + rta->rl[1]->flags = LRT_EDGE_FLAG_CHAIN_PICKED; \ + } \ + if (rta->rl[2]) { \ + rta->rl[2]->flags = LRT_EDGE_FLAG_CHAIN_PICKED; \ + } + + switch (in0 + in1 + in2) { + case 0: /* Triangle is visible. Ignore this triangle. */ + return; + case 3: + /* Triangle completely behind near plane, throw it away + * also remove render lines form being computed. */ + lineart_triangle_set_cull_flag(rt, LRT_CULL_DISCARD); + REMOVE_TRIANGLE_RL + return; + case 2: + /* Two points behind near plane, cut those and + * generate 2 new points, 3 lines and 1 triangle. */ + lineart_triangle_set_cull_flag(rt, LRT_CULL_USED); + + /* (!in0) means "when point 0 is visible". + * conditons for point 1, 2 are the same idea. + * 1-----|-------0 + * | | --- + * | |--- + * | ---| + * 2-- | + * (near)---------->(far) + * Will become: + * |N******0 + * |* *** + * |N** + * | + * | + * (near)---------->(far) + */ + if (!in0) { + + /* Cut point for line 2---|-----0. */ + sub_v3_v3v3_db(vv1, rt->v[0]->gloc, cam_pos); + sub_v3_v3v3_db(vv2, cam_pos, rt->v[2]->gloc); + dot1 = dot_v3v3_db(vv1, view_dir); + dot2 = dot_v3v3_db(vv2, view_dir); + a = dot1 / (dot1 + dot2); + /* Assign it to a new point. */ + interp_v3_v3v3_db(rv[0].gloc, rt->v[0]->gloc, rt->v[2]->gloc, a); + mul_v4_m4v3_db(rv[0].fbcoord, vp, rv[0].gloc); + rv[0].index = rt->v[2]->index; + + /* Cut point for line 1---|-----0. */ + sub_v3_v3v3_db(vv1, rt->v[0]->gloc, cam_pos); + sub_v3_v3v3_db(vv2, cam_pos, rt->v[1]->gloc); + dot1 = dot_v3v3_db(vv1, view_dir); + dot2 = dot_v3v3_db(vv2, view_dir); + a = dot1 / (dot1 + dot2); + /* Assign it to another new point. */ + interp_v3_v3v3_db(rv[1].gloc, rt->v[0]->gloc, rt->v[1]->gloc, a); + mul_v4_m4v3_db(rv[1].fbcoord, vp, rv[1].gloc); + rv[1].index = rt->v[1]->index; + + /* New line connecting two new points. */ + INCREASE_RL + if (allow_boundaries) { + rl->flags = LRT_EDGE_FLAG_CONTOUR; + lineart_prepend_line_direct(&rb->contours, rl); + } + /* Note: inverting rl->l/r (left/right point) doesn't matter as long as + * rt->rl and rt->v has the same sequence. and the winding direction + * can be either CW or CCW but needs to be consistent throughout the calculation. + */ + rl->l = &rv[1]; + rl->r = &rv[0]; + /* Only one adjacent triangle, because the other side is the near plane. */ + /* Use tl or tr doesn't matter. */ + rl->tl = rt1; + rl->object_ref = ob; + + /* New line connecting original point 0 and a new point, only when it's a selected line. */ + SELECT_RL(2, rt->v[0], &rv[0], rt1) + /* New line connecting original point 0 and another new point. */ + SELECT_RL(0, rt->v[0], &rv[1], rt1) + + /* Re-assign triangle point array to two new points. */ + rt1->v[0] = rt->v[0]; + rt1->v[1] = &rv[1]; + rt1->v[2] = &rv[0]; + + lineart_triangle_post(rt1, rt); + + v_count += 2; + t_count += 1; + } + else if (!in2) { + sub_v3_v3v3_db(vv1, rt->v[2]->gloc, cam_pos); + sub_v3_v3v3_db(vv2, cam_pos, rt->v[0]->gloc); + dot1 = dot_v3v3_db(vv1, view_dir); + dot2 = dot_v3v3_db(vv2, view_dir); + a = dot1 / (dot1 + dot2); + interp_v3_v3v3_db(rv[0].gloc, rt->v[2]->gloc, rt->v[0]->gloc, a); + mul_v4_m4v3_db(rv[0].fbcoord, vp, rv[0].gloc); + rv[0].index = rt->v[0]->index; + + sub_v3_v3v3_db(vv1, rt->v[2]->gloc, cam_pos); + sub_v3_v3v3_db(vv2, cam_pos, rt->v[1]->gloc); + dot1 = dot_v3v3_db(vv1, view_dir); + dot2 = dot_v3v3_db(vv2, view_dir); + a = dot1 / (dot1 + dot2); + interp_v3_v3v3_db(rv[1].gloc, rt->v[2]->gloc, rt->v[1]->gloc, a); + mul_v4_m4v3_db(rv[1].fbcoord, vp, rv[1].gloc); + rv[1].index = rt->v[1]->index; + + INCREASE_RL + if (allow_boundaries) { + rl->flags = LRT_EDGE_FLAG_CONTOUR; + lineart_prepend_line_direct(&rb->contours, rl); + } + rl->l = &rv[0]; + rl->r = &rv[1]; + rl->tl = rt1; + rl->object_ref = ob; + + SELECT_RL(2, rt->v[2], &rv[0], rt1) + SELECT_RL(1, rt->v[2], &rv[1], rt1) + + rt1->v[0] = &rv[0]; + rt1->v[1] = &rv[1]; + rt1->v[2] = rt->v[2]; + + lineart_triangle_post(rt1, rt); + + v_count += 2; + t_count += 1; + } + else if (!in1) { + sub_v3_v3v3_db(vv1, rt->v[1]->gloc, cam_pos); + sub_v3_v3v3_db(vv2, cam_pos, rt->v[2]->gloc); + dot1 = dot_v3v3_db(vv1, view_dir); + dot2 = dot_v3v3_db(vv2, view_dir); + a = dot1 / (dot1 + dot2); + interp_v3_v3v3_db(rv[0].gloc, rt->v[1]->gloc, rt->v[2]->gloc, a); + mul_v4_m4v3_db(rv[0].fbcoord, vp, rv[0].gloc); + rv[0].index = rt->v[2]->index; + + sub_v3_v3v3_db(vv1, rt->v[1]->gloc, cam_pos); + sub_v3_v3v3_db(vv2, cam_pos, rt->v[0]->gloc); + dot1 = dot_v3v3_db(vv1, view_dir); + dot2 = dot_v3v3_db(vv2, view_dir); + a = dot1 / (dot1 + dot2); + interp_v3_v3v3_db(rv[1].gloc, rt->v[1]->gloc, rt->v[0]->gloc, a); + mul_v4_m4v3_db(rv[1].fbcoord, vp, rv[1].gloc); + rv[1].index = rt->v[0]->index; + + INCREASE_RL + if (allow_boundaries) { + rl->flags = LRT_EDGE_FLAG_CONTOUR; + lineart_prepend_line_direct(&rb->contours, rl); + } + rl->l = &rv[1]; + rl->r = &rv[0]; + rl->tl = rt1; + rl->object_ref = ob; + + SELECT_RL(1, rt->v[1], &rv[0], rt1) + SELECT_RL(0, rt->v[1], &rv[1], rt1) + + rt1->v[0] = &rv[0]; + rt1->v[1] = rt->v[1]; + rt1->v[2] = &rv[1]; + + lineart_triangle_post(rt1, rt); + + v_count += 2; + t_count += 1; + } + break; + case 1: + /* One point behind near plane, cut those and + * generate 2 new points, 4 lines and 2 triangles. */ + lineart_triangle_set_cull_flag(rt, LRT_CULL_USED); + + /* (in0) means "when point 0 is invisible". + * conditons for point 1, 2 are the same idea. + * 0------|----------1 + * -- | | + * ---| | + * |-- | + * | --- | + * | --- | + * | --2 + * (near)---------->(far) + * Will become: + * |N*********1 + * |* *** | + * |* *** | + * |N** | + * | *** | + * | *** | + * | **2 + * (near)---------->(far) + */ + if (in0) { + /* Cut point for line 0---|------1. */ + sub_v3_v3v3_db(vv1, rt->v[1]->gloc, cam_pos); + sub_v3_v3v3_db(vv2, cam_pos, rt->v[0]->gloc); + dot1 = dot_v3v3_db(vv1, view_dir); + dot2 = dot_v3v3_db(vv2, view_dir); + a = dot2 / (dot1 + dot2); + /* Assign to a new point. */ + interp_v3_v3v3_db(rv[0].gloc, rt->v[0]->gloc, rt->v[1]->gloc, a); + mul_v4_m4v3_db(rv[0].fbcoord, vp, rv[0].gloc); + rv[0].index = rt->v[0]->index; + + /* Cut point for line 0---|------2. */ + sub_v3_v3v3_db(vv1, rt->v[2]->gloc, cam_pos); + sub_v3_v3v3_db(vv2, cam_pos, rt->v[0]->gloc); + dot1 = dot_v3v3_db(vv1, view_dir); + dot2 = dot_v3v3_db(vv2, view_dir); + a = dot2 / (dot1 + dot2); + /* Assign to aother new point. */ + interp_v3_v3v3_db(rv[1].gloc, rt->v[0]->gloc, rt->v[2]->gloc, a); + mul_v4_m4v3_db(rv[1].fbcoord, vp, rv[1].gloc); + rv[1].index = rt->v[0]->index; + + /* New line connects two new points. */ + INCREASE_RL + if (allow_boundaries) { + rl->flags = LRT_EDGE_FLAG_CONTOUR; + lineart_prepend_line_direct(&rb->contours, rl); + } + rl->l = &rv[1]; + rl->r = &rv[0]; + rl->tl = rt1; + rl->object_ref = ob; + + /* New line connects new point 0 and old point 1, + * this is a border line. + */ + + SELECT_RL(0, rt->v[1], &rv[0], rt1) + SELECT_RL(2, rt->v[2], &rv[1], rt2) + RELINK_RL(1, rt2) + + /* We now have one triangle closed. */ + rt1->v[0] = rt->v[1]; + rt1->v[1] = &rv[1]; + rt1->v[2] = &rv[0]; + /* Close the second triangle. */ + rt2->v[0] = &rv[1]; + rt2->v[1] = rt->v[1]; + rt2->v[2] = rt->v[2]; + + lineart_triangle_post(rt1, rt); + lineart_triangle_post(rt2, rt); + + v_count += 2; + t_count += 2; + } + else if (in1) { + + sub_v3_v3v3_db(vv1, rt->v[1]->gloc, cam_pos); + sub_v3_v3v3_db(vv2, cam_pos, rt->v[2]->gloc); + dot1 = dot_v3v3_db(vv1, view_dir); + dot2 = dot_v3v3_db(vv2, view_dir); + a = dot1 / (dot1 + dot2); + interp_v3_v3v3_db(rv[0].gloc, rt->v[1]->gloc, rt->v[2]->gloc, a); + mul_v4_m4v3_db(rv[0].fbcoord, vp, rv[0].gloc); + rv[0].index = rt->v[1]->index; + + sub_v3_v3v3_db(vv1, rt->v[1]->gloc, cam_pos); + sub_v3_v3v3_db(vv2, cam_pos, rt->v[0]->gloc); + dot1 = dot_v3v3_db(vv1, view_dir); + dot2 = dot_v3v3_db(vv2, view_dir); + a = dot1 / (dot1 + dot2); + interp_v3_v3v3_db(rv[1].gloc, rt->v[1]->gloc, rt->v[0]->gloc, a); + mul_v4_m4v3_db(rv[1].fbcoord, vp, rv[1].gloc); + rv[1].index = rt->v[1]->index; + + INCREASE_RL + if (allow_boundaries) { + rl->flags = LRT_EDGE_FLAG_CONTOUR; + lineart_prepend_line_direct(&rb->contours, rl); + } + rl->l = &rv[1]; + rl->r = &rv[0]; + rl->tl = rt1; + rl->object_ref = ob; + + SELECT_RL(1, rt->v[2], &rv[0], rt1) + SELECT_RL(0, rt->v[0], &rv[1], rt2) + RELINK_RL(2, rt2) + + rt1->v[0] = rt->v[2]; + rt1->v[1] = &rv[1]; + rt1->v[2] = &rv[0]; + + rt2->v[0] = &rv[1]; + rt2->v[1] = rt->v[2]; + rt2->v[2] = rt->v[0]; + + lineart_triangle_post(rt1, rt); + lineart_triangle_post(rt2, rt); + + v_count += 2; + t_count += 2; + } + else if (in2) { + + sub_v3_v3v3_db(vv1, rt->v[2]->gloc, cam_pos); + sub_v3_v3v3_db(vv2, cam_pos, rt->v[0]->gloc); + dot1 = dot_v3v3_db(vv1, view_dir); + dot2 = dot_v3v3_db(vv2, view_dir); + a = dot1 / (dot1 + dot2); + interp_v3_v3v3_db(rv[0].gloc, rt->v[2]->gloc, rt->v[0]->gloc, a); + mul_v4_m4v3_db(rv[0].fbcoord, vp, rv[0].gloc); + rv[0].index = rt->v[2]->index; + + sub_v3_v3v3_db(vv1, rt->v[2]->gloc, cam_pos); + sub_v3_v3v3_db(vv2, cam_pos, rt->v[1]->gloc); + dot1 = dot_v3v3_db(vv1, view_dir); + dot2 = dot_v3v3_db(vv2, view_dir); + a = dot1 / (dot1 + dot2); + interp_v3_v3v3_db(rv[1].gloc, rt->v[2]->gloc, rt->v[1]->gloc, a); + mul_v4_m4v3_db(rv[1].fbcoord, vp, rv[1].gloc); + rv[1].index = rt->v[2]->index; + + INCREASE_RL + if (allow_boundaries) { + rl->flags = LRT_EDGE_FLAG_CONTOUR; + lineart_prepend_line_direct(&rb->contours, rl); + } + rl->l = &rv[1]; + rl->r = &rv[0]; + rl->tl = rt1; + rl->object_ref = ob; + + SELECT_RL(2, rt->v[0], &rv[0], rt1) + SELECT_RL(1, rt->v[1], &rv[1], rt2) + RELINK_RL(0, rt2) + + rt1->v[0] = rt->v[0]; + rt1->v[1] = &rv[1]; + rt1->v[2] = &rv[0]; + + rt2->v[0] = &rv[1]; + rt2->v[1] = rt->v[0]; + rt2->v[2] = rt->v[1]; + + lineart_triangle_post(rt1, rt); + lineart_triangle_post(rt2, rt); + + v_count += 2; + t_count += 2; + } + break; + } + *r_v_count = v_count; + *r_l_count = l_count; + *r_t_count = t_count; + +#undef INCREASE_RL +#undef SELECT_RL +#undef RELINK_RL +#undef REMOVE_TRIANGLE_RL +} + +/* This function cuts triangles with near- or far-plane. Setting clip_far = true for cutting with + * far-plane. For triangles that's crossing the plane, it will generate new 1 or 2 triangles with + * new topology that represents the trimmed triangle. (which then became a triangle or a square + * formed by two triangles) + */ +static void lineart_main_cull_triangles(LineartRenderBuffer *rb, bool clip_far) +{ + LineartRenderTriangle *rt; + LineartRenderElementLinkNode *veln, *teln, *leln; + double(*vp)[4] = rb->view_projection; + int i; + int v_count = 0, t_count = 0, l_count = 0; + Object *ob; + bool allow_boundaries = rb->allow_boundaries; + double cam_pos[3]; + double clip_start = rb->near_clip, clip_end = rb->far_clip; + double view_dir[3], clip_advance[3]; + + copy_v3_v3_db(view_dir, rb->view_vector); + copy_v3_v3_db(clip_advance, rb->view_vector); + copy_v3_v3_db(cam_pos, rb->camera_pos); + + if (clip_far) { + /* Move starting point to end plane. */ + mul_v3db_db(clip_advance, -clip_end); + add_v3_v3_db(cam_pos, clip_advance); + + /* "reverse looking". */ + mul_v3db_db(view_dir, -1.0f); + } + else { + /* Clip Near. */ + mul_v3db_db(clip_advance, -clip_start); + add_v3_v3_db(cam_pos, clip_advance); + } + + veln = lineart_memory_get_vert_space(rb); + teln = lineart_memory_get_triangle_space(rb); + leln = lineart_memory_get_line_space(rb); + + /* Additional memory space for storing generated points and triangles. */ +#define LRT_CULL_ENSURE_MEMORY \ + if (v_count > 60) { \ + veln->element_count = v_count; \ + veln = lineart_memory_get_vert_space(rb); \ + v_count = 0; \ + } \ + if (t_count > 60) { \ + teln->element_count = t_count; \ + teln = lineart_memory_get_triangle_space(rb); \ + t_count = 0; \ + } \ + if (l_count > 60) { \ + leln->element_count = l_count; \ + leln = lineart_memory_get_line_space(rb); \ + l_count = 0; \ + } + +#define LRT_CULL_DECIDE_INSIDE \ + /* These three represents points that are in the clipping range or not*/ \ + in0 = 0, in1 = 0, in2 = 0; \ + if (clip_far) { \ + /* Point outside far plane. */ \ + if (rt->v[0]->fbcoord[use_w] > clip_end) { \ + in0 = 1; \ + } \ + if (rt->v[1]->fbcoord[use_w] > clip_end) { \ + in1 = 1; \ + } \ + if (rt->v[2]->fbcoord[use_w] > clip_end) { \ + in2 = 1; \ + } \ + } \ + else { \ + /* Point inside near plane. */ \ + if (rt->v[0]->fbcoord[use_w] < clip_start) { \ + in0 = 1; \ + } \ + if (rt->v[1]->fbcoord[use_w] < clip_start) { \ + in1 = 1; \ + } \ + if (rt->v[2]->fbcoord[use_w] < clip_start) { \ + in2 = 1; \ + } \ + } + + int use_w = 3; + int in0 = 0, in1 = 0, in2 = 0; + + if (!rb->cam_is_persp) { + clip_start = -1; + clip_end = 1; + use_w = 2; + } + + /* Then go through all the other triangles. */ + LISTBASE_FOREACH (LineartRenderElementLinkNode *, reln, &rb->triangle_buffer_pointers) { + if (reln->flags & LRT_ELEMENT_IS_ADDITIONAL) { + continue; + } + ob = reln->object_ref; + for (i = 0; i < reln->element_count; i++) { + /* Select the triangle in the array. */ + rt = (void *)(((unsigned char *)reln->pointer) + rb->triangle_size * i); + + LRT_CULL_DECIDE_INSIDE + LRT_CULL_ENSURE_MEMORY + lineart_triangle_cull_single(rb, + rt, + in0, + in1, + in2, + cam_pos, + view_dir, + allow_boundaries, + vp, + ob, + &v_count, + &l_count, + &t_count, + veln, + leln, + teln); + } + teln->element_count = t_count; + veln->element_count = v_count; + } + +#undef LRT_CULL_ENSURE_MEMORY +#undef LRT_CULL_DECIDE_INSIDE +} + +/* Adjacent data is only used during the initial stages of computing. So we can free it using this + * function when it is not needed anymore. */ +static void lineart_main_free_adjacent_data(LineartRenderBuffer *rb) +{ + LinkData *ld; + while ((ld = BLI_pophead(&rb->triangle_adjacent_pointers)) != NULL) { + MEM_freeN(ld->data); + } + LISTBASE_FOREACH (LineartRenderElementLinkNode *, reln, &rb->triangle_buffer_pointers) { + LineartRenderTriangle *rt = reln->pointer; + int i; + for (i = 0; i < reln->element_count; i++) { + /* See definition of rt->intersecting_verts and the usage in + * lineart_geometry_object_load() for detailes. */ + rt->intersecting_verts = NULL; + rt = (LineartRenderTriangle *)(((unsigned char *)rt) + rb->triangle_size); + } + } +} + +static void lineart_main_perspective_division(LineartRenderBuffer *rb) +{ + LineartRenderVert *rv; + int i; + + if (!rb->cam_is_persp) { + return; + } + + LISTBASE_FOREACH (LineartRenderElementLinkNode *, reln, &rb->vertex_buffer_pointers) { + rv = reln->pointer; + for (i = 0; i < reln->element_count; i++) { + /* Do not divide Z, we use Z to back transform cut points in later chaining process. */ + rv[i].fbcoord[0] /= rv[i].fbcoord[3]; + rv[i].fbcoord[1] /= rv[i].fbcoord[3]; + /* Re-map z into (0-1) range, because we no longer need NDC (Normalized Device Coordinates) + * at the moment. + * The algorithm currently doesn't need Z for operation, we use W instead. If Z is needed in + * the future, the line below correctly transforms it to view space coordinates. */ + /* rv[i].fbcoord[2] = -2 * rv[i].fbcoord[2] / (far - near) - (far + near) / (far - near);. */ + rv[i].fbcoord[0] -= rb->shift_x * 2; + rv[i].fbcoord[1] -= rb->shift_y * 2; + } + } +} + +/* Transform a single vert to it's viewing position. */ +static void lineart_vert_transform( + BMVert *v, int index, LineartRenderVert *RvBuf, double (*mv_mat)[4], double (*mvp_mat)[4]) +{ + double co[4]; + LineartRenderVert *rv = &RvBuf[index]; + copy_v3db_v3fl(co, v->co); + mul_v3_m4v3_db(rv->gloc, mv_mat, co); + mul_v4_m4v3_db(rv->fbcoord, mvp_mat, co); +} + +/* Because we have a variable size for LineartRenderTriangle, we need an access helper. See + * LineartRenderTriangleThread for more info. */ +static LineartRenderTriangle *lineart_triangle_from_index(LineartRenderBuffer *rb, + LineartRenderTriangle *rt_array, + int index) +{ + char *b = (char *)rt_array; + b += (index * rb->triangle_size); + return (LineartRenderTriangle *)b; +} + +static char lineart_identify_feature_line(LineartRenderBuffer *rb, + BMEdge *e, + LineartRenderTriangle *rt_array, + LineartRenderVert *rv_array, + float crease_threshold, + bool no_crease, + bool count_freestyle, + BMesh *bm_if_freestyle) +{ + BMLoop *ll, *lr = NULL; + ll = e->l; + if (ll) { + lr = e->l->radial_next; + } + + if (ll == lr || !lr) { + return LRT_EDGE_FLAG_CONTOUR; + } + + LineartRenderTriangle *rt1, *rt2; + LineartRenderVert *l; + + /* The mesh should already be triangulated now, so we can assume each face is a triangle. */ + rt1 = lineart_triangle_from_index(rb, rt_array, BM_elem_index_get(ll->f)); + rt2 = lineart_triangle_from_index(rb, rt_array, BM_elem_index_get(lr->f)); + + l = &rv_array[BM_elem_index_get(e->v1)]; + + double vv[3]; + double *view_vector = vv; + double dot_1 = 0, dot_2 = 0; + double result; + FreestyleEdge *fe; + + if (rb->cam_is_persp) { + sub_v3_v3v3_db(view_vector, l->gloc, rb->camera_pos); + } + else { + view_vector = rb->view_vector; + } + + dot_1 = dot_v3v3_db(view_vector, rt1->gn); + dot_2 = dot_v3v3_db(view_vector, rt2->gn); + + if ((result = dot_1 * dot_2) < 0 && (dot_1 + dot_2)) { + return LRT_EDGE_FLAG_CONTOUR; + } + + if (rb->use_crease && (dot_v3v3_db(rt1->gn, rt2->gn) < crease_threshold)) { + if (!no_crease) { + return LRT_EDGE_FLAG_CREASE; + } + } + else if (rb->use_material && (ll->f->mat_nr != lr->f->mat_nr)) { + return LRT_EDGE_FLAG_MATERIAL; + } + else if (count_freestyle && rb->use_edge_marks) { + fe = CustomData_bmesh_get(&bm_if_freestyle->edata, e->head.data, CD_FREESTYLE_EDGE); + if (fe->flag & FREESTYLE_EDGE_MARK) { + return LRT_EDGE_FLAG_EDGE_MARK; + } + } + return 0; +} + +static void lineart_add_line_to_list(LineartRenderBuffer *rb, LineartRenderLine *rl) +{ + switch (rl->flags) { + case LRT_EDGE_FLAG_CONTOUR: + lineart_prepend_line_direct(&rb->contours, rl); + break; + case LRT_EDGE_FLAG_CREASE: + lineart_prepend_line_direct(&rb->crease_lines, rl); + break; + case LRT_EDGE_FLAG_MATERIAL: + lineart_prepend_line_direct(&rb->material_lines, rl); + break; + case LRT_EDGE_FLAG_EDGE_MARK: + lineart_prepend_line_direct(&rb->edge_marks, rl); + break; + case LRT_EDGE_FLAG_INTERSECTION: + lineart_prepend_line_direct(&rb->intersection_lines, rl); + break; + } +} + +static void lineart_triangle_adjacent_assign(LineartRenderTriangle *rt, + LineartRenderTriangleAdjacent *rta, + LineartRenderLine *rl) +{ + if (lineart_line_match(rt, rl, 0, 1)) { + rta->rl[0] = rl; + } + else if (lineart_line_match(rt, rl, 1, 2)) { + rta->rl[1] = rl; + } + else if (lineart_line_match(rt, rl, 2, 0)) { + rta->rl[2] = rl; + } +} + +static void lineart_geometry_object_load(Depsgraph *dg, + Object *ob, + double (*mv_mat)[4], + double (*mvp_mat)[4], + LineartRenderBuffer *rb, + int override_usage, + int *global_vindex) +{ + BMesh *bm; + BMVert *v; + BMFace *f; + BMEdge *e; + BMLoop *loop; + LineartRenderLine *rl; + LineartRenderTriangle *rt; + LineartRenderTriangleAdjacent *orta; + double new_mvp[4][4], new_mv[4][4], normal[4][4]; + float imat[4][4]; + LineartRenderElementLinkNode *reln; + LineartRenderVert *orv; + LineartRenderLine *orl; + LineartRenderTriangle *ort; + Object *orig_ob; + int CanFindFreestyle = 0; + int i, global_i = (*global_vindex); + Mesh *use_mesh; + float use_crease = 0; + + int usage = override_usage ? override_usage : ob->lineart.usage; + +#define LRT_MESH_FINISH \ + BM_mesh_free(bm); \ + if (ob->type != OB_MESH) { \ + BKE_mesh_free(use_mesh); \ + MEM_freeN(use_mesh); \ + } + + if (usage == OBJECT_LRT_EXCLUDE) { + return; + } + + if (ob->type == OB_MESH || ob->type == OB_MBALL || ob->type == OB_CURVE || ob->type == OB_SURF || + ob->type == OB_FONT) { + + if (ob->type == OB_MESH) { + use_mesh = DEG_get_evaluated_object(dg, ob)->data; + } + else { + use_mesh = BKE_mesh_new_from_object(NULL, ob, false); + } + + /* In case we can not get any mesh geometry data from the object */ + if (!use_mesh) { + return; + } + + /* First we need to prepare the matrix used for transforming this specific object. */ + mul_m4db_m4db_m4fl_uniq(new_mvp, mvp_mat, ob->obmat); + mul_m4db_m4db_m4fl_uniq(new_mv, mv_mat, ob->obmat); + + invert_m4_m4(imat, ob->obmat); + transpose_m4(imat); + copy_m4d_m4(normal, imat); + + if (use_mesh->edit_mesh) { + /* Do not use edit_mesh directly because we will modify it, so create a copy. */ + bm = BM_mesh_copy(use_mesh->edit_mesh->bm); + } + else { + const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(((Mesh *)(use_mesh))); + bm = BM_mesh_create(&allocsize, + &((struct BMeshCreateParams){ + .use_toolflags = true, + })); + BM_mesh_bm_from_me(bm, + use_mesh, + &((struct BMeshFromMeshParams){ + .calc_face_normal = true, + })); + } + + if (rb->remove_doubles) { + BMEditMesh *em = BKE_editmesh_create(bm, false); + BMOperator findop, weldop; + + /* See bmesh_opdefines.c and bmesh_operators.c for op names and argument formatting. */ + BMO_op_initf(bm, &findop, BMO_FLAG_DEFAULTS, "find_doubles verts=%av dist=%f", 0.0001); + + BMO_op_exec(bm, &findop); + + /* Weld the vertices. */ + BMO_op_init(bm, &weldop, BMO_FLAG_DEFAULTS, "weld_verts"); + BMO_slot_copy(&findop, slots_out, "targetmap.out", &weldop, slots_in, "targetmap"); + BMO_op_exec(bm, &weldop); + + BMO_op_finish(bm, &findop); + BMO_op_finish(bm, &weldop); + + MEM_freeN(em); + } + + BM_mesh_elem_hflag_disable_all(bm, BM_FACE | BM_EDGE, BM_ELEM_TAG, false); + BM_mesh_triangulate( + bm, MOD_TRIANGULATE_QUAD_FIXED, MOD_TRIANGULATE_NGON_BEAUTY, 4, false, NULL, NULL, NULL); + BM_mesh_normals_update(bm); + BM_mesh_elem_table_ensure(bm, BM_VERT | BM_EDGE | BM_FACE); + BM_mesh_elem_index_ensure(bm, BM_VERT | BM_EDGE | BM_FACE); + + if (CustomData_has_layer(&bm->edata, CD_FREESTYLE_EDGE)) { + CanFindFreestyle = 1; + } + + /* Only allocate memory for verts and tris as we don't know how many lines we will generate + * yet. */ + orv = lineart_mem_aquire(&rb->render_data_pool, sizeof(LineartRenderVert) * bm->totvert); + ort = lineart_mem_aquire(&rb->render_data_pool, bm->totface * rb->triangle_size); + + orig_ob = ob->id.orig_id ? (Object *)ob->id.orig_id : ob; + + reln = lineart_list_append_pointer_pool_sized(&rb->vertex_buffer_pointers, + &rb->render_data_pool, + orv, + sizeof(LineartRenderElementLinkNode)); + reln->element_count = bm->totvert; + reln->object_ref = orig_ob; + + if (ob->lineart.flags & OBJECT_LRT_OWN_CREASE) { + use_crease = cosf(M_PI - ob->lineart.crease_threshold); + } + else { + use_crease = rb->crease_threshold; + } + + /* FIXME Yiming: Hack for getting clean 3D text, the seam that extruded text object creates + * erroneous detection on creases. Future configuration should allow options. */ + if (ob->type == OB_FONT) { + reln->flags |= LRT_ELEMENT_BORDER_ONLY; + } + + reln = lineart_list_append_pointer_pool_sized(&rb->triangle_buffer_pointers, + &rb->render_data_pool, + ort, + sizeof(LineartRenderElementLinkNode)); + reln->element_count = bm->totface; + reln->object_ref = orig_ob; + reln->flags |= (usage == OBJECT_LRT_NO_INTERSECTION ? LRT_ELEMENT_NO_INTERSECTION : 0); + + /* Note this memory is not from pool, will be deleted after culling. */ + orta = MEM_callocN(sizeof(LineartRenderTriangleAdjacent) * bm->totface, + "LineartRenderTriangleAdjacent"); + /* Link is minimal so we use pool anyway. */ + lineart_list_append_pointer_pool(&rb->triangle_adjacent_pointers, &rb->render_data_pool, orta); + + for (i = 0; i < bm->totvert; i++) { + v = BM_vert_at_index(bm, i); + lineart_vert_transform(v, i, orv, new_mv, new_mvp); + orv[i].index = i + global_i; + } + /* Register a global index increment. See lineart_triangle_share_edge() and + * lineart_main_load_geometries() for detailes. It's okay that global_vindex might eventually + * overflow, in such large scene it's virtually impossible for two vertex of the same numeric + * index to come close together. */ + (*global_vindex) += bm->totvert; + + rt = ort; + for (i = 0; i < bm->totface; i++) { + f = BM_face_at_index(bm, i); + + loop = f->l_first; + rt->v[0] = &orv[BM_elem_index_get(loop->v)]; + loop = loop->next; + rt->v[1] = &orv[BM_elem_index_get(loop->v)]; + loop = loop->next; + rt->v[2] = &orv[BM_elem_index_get(loop->v)]; + + /* Transparency bit assignment. */ + Material *mat = BKE_object_material_get(ob, f->mat_nr + 1); + rt->transparency_mask = ((mat && (mat->lineart.flags & LRT_MATERIAL_TRANSPARENCY_ENABLED)) ? + mat->lineart.transparency_mask : + 0); + + double gn[3]; + copy_v3db_v3fl(gn, f->no); + mul_v3_mat3_m4v3_db(rt->gn, normal, gn); + normalize_v3_db(rt->gn); + + if (usage == OBJECT_LRT_INTERSECTION_ONLY) { + rt->flags |= LRT_TRIANGLE_INTERSECTION_ONLY; + } + else if (usage == OBJECT_LRT_NO_INTERSECTION || usage == OBJECT_LRT_OCCLUSION_ONLY) { + rt->flags |= LRT_TRIANGLE_NO_INTERSECTION; + } + + /* Re-use this field to refer to adjacent info, will be cleared after culling stage. */ + rt->intersecting_verts = (void *)&orta[i]; + + rt = (LineartRenderTriangle *)(((unsigned char *)rt) + rb->triangle_size); + } + + /* Use BM_ELEM_TAG in f->head.hflag to store needed faces in the first iteration. */ + + int allocate_rl = 0; + for (i = 0; i < bm->totedge; i++) { + e = BM_edge_at_index(bm, i); + + /* Because e->head.hflag is char, so line type flags should not exceed positive 7 bits. */ + char eflag = lineart_identify_feature_line( + rb, e, ort, orv, use_crease, ob->type == OB_FONT, CanFindFreestyle, bm); + if (eflag) { + /* Only allocate for feature lines (instead of all lines) to save memory. */ + allocate_rl++; + } + /* Here we just use bm's flag for when loading actual lines, then we don't need to call + * lineart_identify_feature_line() again, e->head.hflag deleted after loading anyway. Always + * set the flag, so hflag stays 0 for lines that are not feature lines. */ + e->head.hflag = eflag; + } + + orl = lineart_mem_aquire(&rb->render_data_pool, sizeof(LineartRenderLine) * allocate_rl); + reln = lineart_list_append_pointer_pool_sized(&rb->line_buffer_pointers, + &rb->render_data_pool, + orl, + sizeof(LineartRenderElementLinkNode)); + reln->element_count = allocate_rl; + reln->object_ref = orig_ob; + + rl = orl; + for (i = 0; i < bm->totedge; i++) { + e = BM_edge_at_index(bm, i); + + /* Not a feature line, so we skip. */ + if (!e->head.hflag) { + continue; + } + + rl->l = &orv[BM_elem_index_get(e->v1)]; + rl->r = &orv[BM_elem_index_get(e->v2)]; + rl->l_obindex = rl->l->index - global_i; + rl->r_obindex = rl->r->index - global_i; + if (e->l) { + int findex = BM_elem_index_get(e->l->f); + rl->tl = lineart_triangle_from_index(rb, ort, findex); + lineart_triangle_adjacent_assign(rl->tl, &orta[findex], rl); + if (e->l->radial_next && e->l->radial_next != e->l) { + findex = BM_elem_index_get(e->l->radial_next->f); + rl->tr = lineart_triangle_from_index(rb, ort, findex); + lineart_triangle_adjacent_assign(rl->tr, &orta[findex], rl); + } + } + rl->flags = e->head.hflag; + rl->object_ref = orig_ob; + + LineartRenderLineSegment *rls = lineart_mem_aquire(&rb->render_data_pool, + sizeof(LineartRenderLineSegment)); + BLI_addtail(&rl->segments, rls); + if (usage == OBJECT_LRT_INHERENT || usage == OBJECT_LRT_INCLUDE || + usage == OBJECT_LRT_NO_INTERSECTION) { + lineart_add_line_to_list(rb, rl); + } + + rl++; + } + + LRT_MESH_FINISH + } + +#undef LRT_MESH_FINISH +} + +/* See if this object in such collection is used for generating line art, + * Disabling a collection for line art will diable all objects inside. */ +static int lineart_usage_check(Collection *c, Object *ob) +{ + + if (!c) { + return OBJECT_LRT_INHERENT; + } + + int object_is_used = (ob->lineart.usage != OBJECT_LRT_INHERENT); + + if (object_is_used) { + return ob->lineart.usage; + } + + if (c->children.first == NULL) { + if (BKE_collection_has_object(c, (Object *)(ob->id.orig_id))) { + if (ob->lineart.usage == OBJECT_LRT_INHERENT) { + switch (c->lineart_usage) { + case COLLECTION_LRT_OCCLUSION_ONLY: + return OBJECT_LRT_OCCLUSION_ONLY; + case COLLECTION_LRT_EXCLUDE: + return OBJECT_LRT_EXCLUDE; + case COLLECTION_LRT_INTERSECTION_ONLY: + return OBJECT_LRT_INTERSECTION_ONLY; + case COLLECTION_LRT_NO_INTERSECTION: + return OBJECT_LRT_NO_INTERSECTION; + } + return OBJECT_LRT_INHERENT; + } + return ob->lineart.usage; + } + return OBJECT_LRT_INHERENT; + } + + LISTBASE_FOREACH (CollectionChild *, cc, &c->children) { + int result = lineart_usage_check(cc->collection, ob); + if (result > OBJECT_LRT_INHERENT) { + return result; + } + } + + return OBJECT_LRT_INHERENT; +} + +static void lineart_main_load_geometries( + Depsgraph *depsgraph, + Scene *scene, + Object *camera /* Still use camera arg for convenience. */, + LineartRenderBuffer *rb, + bool allow_duplicates) +{ + double proj[4][4], view[4][4], result[4][4]; + float inv[4][4]; + + Camera *cam = camera->data; + float sensor = BKE_camera_sensor_size(cam->sensor_fit, cam->sensor_x, cam->sensor_y); + double fov = focallength_to_fov(cam->lens, sensor); + + double asp = ((double)rb->w / (double)rb->h); + + if (cam->type == CAM_PERSP) { + if (asp < 1) { + fov /= asp; + } + lineart_matrix_perspective_44d(proj, fov, asp, cam->clip_start, cam->clip_end); + } + else if (cam->type == CAM_ORTHO) { + double w = cam->ortho_scale / 2; + lineart_matrix_ortho_44d(proj, -w, w, -w / asp, w / asp, cam->clip_start, cam->clip_end); + } + invert_m4_m4(inv, rb->cam_obmat); + mul_m4db_m4db_m4fl_uniq(result, proj, inv); + copy_m4_m4_db(proj, result); + copy_m4_m4_db(rb->view_projection, proj); + + unit_m4_db(view); + + BLI_listbase_clear(&rb->triangle_buffer_pointers); + BLI_listbase_clear(&rb->vertex_buffer_pointers); + + int flags = DEG_ITER_OBJECT_FLAG_LINKED_DIRECTLY | DEG_ITER_OBJECT_FLAG_LINKED_VIA_SET | + DEG_ITER_OBJECT_FLAG_VISIBLE; + + /* Instance duplicated & particles. */ + if (allow_duplicates) { + flags |= DEG_ITER_OBJECT_FLAG_DUPLI; + } + + /* This is to serialize vertex index in the whole scene, so lineart_triangle_share_edge() can + * work properly from the lack of triangle adjacent info. */ + int global_i = 0; + + DEG_OBJECT_ITER_BEGIN (depsgraph, ob, flags) { + int usage = lineart_usage_check(scene->master_collection, ob); + + lineart_geometry_object_load(depsgraph, ob, view, proj, rb, usage, &global_i); + } + DEG_OBJECT_ITER_END; +} + +/* Returns the two other verts of the triangle given a vertex. Returns false if the given vertex + * doesn't belong to this triangle. */ +static bool lineart_triangle_get_other_verts(const LineartRenderTriangle *rt, + const LineartRenderVert *rv, + LineartRenderVert **l, + LineartRenderVert **r) +{ + if (rt->v[0] == rv) { + *l = rt->v[1]; + *r = rt->v[2]; + return true; + } + if (rt->v[1] == rv) { + *l = rt->v[2]; + *r = rt->v[0]; + return true; + } + if (rt->v[2] == rv) { + *l = rt->v[0]; + *r = rt->v[1]; + return true; + } + return false; +} + +static bool lineart_edge_from_triangle(const LineartRenderTriangle *rt, + const LineartRenderLine *rl, + bool allow_overlapping_edges) +{ + /* Normally we just determine from the pointer address. */ + if (rl->tl == rt || rl->tr == rt) { + return true; + } + /* If allows overlapping, then we compare the vertex coordinates one by one to determine if one + * edge is from specific triangle. This is slower but can handle edge split cases very well. */ + if (allow_overlapping_edges) { +#define LRT_TRI_SAME_POINT(rt, i, pt) \ + ((LRT_DOUBLE_CLOSE_ENOUGH(rt->v[i]->gloc[0], pt->gloc[0]) && \ + LRT_DOUBLE_CLOSE_ENOUGH(rt->v[i]->gloc[1], pt->gloc[1]) && \ + LRT_DOUBLE_CLOSE_ENOUGH(rt->v[i]->gloc[2], pt->gloc[2])) || \ + (LRT_DOUBLE_CLOSE_ENOUGH(rt->v[i]->gloc[0], pt->gloc[0]) && \ + LRT_DOUBLE_CLOSE_ENOUGH(rt->v[i]->gloc[1], pt->gloc[1]) && \ + LRT_DOUBLE_CLOSE_ENOUGH(rt->v[i]->gloc[2], pt->gloc[2]))) + if ((LRT_TRI_SAME_POINT(rt, 0, rl->l) || LRT_TRI_SAME_POINT(rt, 1, rl->l) || + LRT_TRI_SAME_POINT(rt, 2, rl->l)) && + (LRT_TRI_SAME_POINT(rt, 0, rl->r) || LRT_TRI_SAME_POINT(rt, 1, rl->r) || + LRT_TRI_SAME_POINT(rt, 2, rl->r))) { + return true; + } +#undef LRT_TRI_SAME_POINT + } + return false; +} + +/* Sorting three intersection points from min to max, + * the order for each intersection is set in lst[0] to lst[2].*/ +#define INTERSECT_SORT_MIN_TO_MAX_3(ia, ib, ic, lst) \ + { \ + lst[0] = LRT_MIN3_INDEX(ia, ib, ic); \ + lst[1] = (((ia <= ib && ib <= ic) || (ic <= ib && ib <= ia)) ? \ + 1 : \ + (((ic <= ia && ia <= ib) || (ib < ia && ia <= ic)) ? 0 : 2)); \ + lst[2] = LRT_MAX3_INDEX(ia, ib, ic); \ + } + +/* ia ib ic are ordered. */ +#define INTERSECT_JUST_GREATER(is, order, num, index) \ + { \ + index = (num < is[order[0]] ? \ + order[0] : \ + (num < is[order[1]] ? order[1] : (num < is[order[2]] ? order[2] : order[2]))); \ + } + +/* ia ib ic are ordered. */ +#define INTERSECT_JUST_SMALLER(is, order, num, index) \ + { \ + index = (num > is[order[2]] ? \ + order[2] : \ + (num > is[order[1]] ? order[1] : (num > is[order[0]] ? order[0] : order[0]))); \ + } + +/* This is the main function to calculate + * the occlusion status between 1(one) triangle and 1(one) line. + * if returns true, then from/to will carry the occludded segments + * in ratio from rl->l to rl->r. The line is later cut with these two values. + */ +static bool lineart_triangle_line_image_space_occlusion(SpinLock *UNUSED(spl), + const LineartRenderTriangle *rt, + const LineartRenderLine *rl, + const double *override_camera_loc, + const bool override_cam_is_persp, + const bool allow_overlapping_edges, + const double vp[4][4], + const double *camera_dir, + const float cam_shift_x, + const float cam_shift_y, + double *from, + double *to) +{ + double is[3] = {0}; + int order[3]; + int LCross = -1, RCross = -1; + int a, b, c; + int st_l = 0, st_r = 0; + + double Lv[3]; + double Rv[3]; + double vd4[4]; + double Cv[3]; + double dot_l, dot_r, dot_la, dot_ra; + double dot_f; + double gloc[4], trans[4]; + double cut = -1; + + double *LFBC = rl->l->fbcoord, *RFBC = rl->r->fbcoord, *FBC0 = rt->v[0]->fbcoord, + *FBC1 = rt->v[1]->fbcoord, *FBC2 = rt->v[2]->fbcoord; + + /* Overlapping not possible, return early. */ + if ((MAX3(FBC0[0], FBC1[0], FBC2[0]) < MIN2(LFBC[0], RFBC[0])) || + (MIN3(FBC0[0], FBC1[0], FBC2[0]) > MAX2(LFBC[0], RFBC[0])) || + (MAX3(FBC0[1], FBC1[1], FBC2[1]) < MIN2(LFBC[1], RFBC[1])) || + (MIN3(FBC0[1], FBC1[1], FBC2[1]) > MAX2(LFBC[1], RFBC[1])) || + (MIN3(FBC0[3], FBC1[3], FBC2[3]) > MAX2(LFBC[3], RFBC[3]))) { + return false; + } + + /* If the the line is one of the edge in the triangle, then it's not occludded. */ + if (lineart_edge_from_triangle(rt, rl, allow_overlapping_edges)) { + return false; + } + + /* Check if the line visually crosses one of the edge in the triangle. */ + a = lineart_LineIntersectTest2d(LFBC, RFBC, FBC0, FBC1, &is[0]); + b = lineart_LineIntersectTest2d(LFBC, RFBC, FBC1, FBC2, &is[1]); + c = lineart_LineIntersectTest2d(LFBC, RFBC, FBC2, FBC0, &is[2]); + + /* Sort the intersection distance. */ + INTERSECT_SORT_MIN_TO_MAX_3(is[0], is[1], is[2], order); + + sub_v3_v3v3_db(Lv, rl->l->gloc, rt->v[0]->gloc); + sub_v3_v3v3_db(Rv, rl->r->gloc, rt->v[0]->gloc); + + copy_v3_v3_db(Cv, camera_dir); + + if (override_cam_is_persp) { + copy_v3_v3_db(vd4, override_camera_loc); + } + else { + copy_v4_v4_db(vd4, override_camera_loc); + } + if (override_cam_is_persp) { + sub_v3_v3v3_db(Cv, vd4, rt->v[0]->gloc); + } + + dot_l = dot_v3v3_db(Lv, rt->gn); + dot_r = dot_v3v3_db(Rv, rt->gn); + dot_f = dot_v3v3_db(Cv, rt->gn); + + if (!dot_f) { + return false; + } + + if (!a && !b && !c) { + if (!(st_l = lineart_point_triangle_relation(LFBC, FBC0, FBC1, FBC2)) && + !(st_r = lineart_point_triangle_relation(RFBC, FBC0, FBC1, FBC2))) { + return 0; /* Intersection point is not inside triangle. */ + } + } + + st_l = lineart_point_triangle_relation(LFBC, FBC0, FBC1, FBC2); + st_r = lineart_point_triangle_relation(RFBC, FBC0, FBC1, FBC2); + + /* Determine the cut position. */ + + dot_la = fabs(dot_l); + if (dot_la < DBL_EPSILON) { + dot_la = 0; + dot_l = 0; + } + dot_ra = fabs(dot_r); + if (dot_ra < DBL_EPSILON) { + dot_ra = 0; + dot_r = 0; + } + if (dot_l - dot_r == 0) { + cut = 100000; + } + else if (dot_l * dot_r <= 0) { + cut = dot_la / fabs(dot_l - dot_r); + } + else { + cut = fabs(dot_r + dot_l) / fabs(dot_l - dot_r); + cut = dot_ra > dot_la ? 1 - cut : cut; + } + + /* Transform the cut from geometry space to image space. */ + if (override_cam_is_persp) { + interp_v3_v3v3_db(gloc, rl->l->gloc, rl->r->gloc, cut); + mul_v4_m4v3_db(trans, vp, gloc); + mul_v3db_db(trans, (1 / trans[3])); + } + else { + interp_v3_v3v3_db(trans, rl->l->fbcoord, rl->r->fbcoord, cut); + } + trans[0] -= cam_shift_x * 2; + trans[1] -= cam_shift_y * 2; + + /* To accomodate k=0 and k=inf (vertical) lines. here the cut is in image space. */ + if (fabs(rl->l->fbcoord[0] - rl->r->fbcoord[0]) > fabs(rl->l->fbcoord[1] - rl->r->fbcoord[1])) { + cut = ratiod(rl->l->fbcoord[0], rl->r->fbcoord[0], trans[0]); + } + else { + cut = ratiod(rl->l->fbcoord[1], rl->r->fbcoord[1], trans[1]); + } + + /* Determine the pair of edges that the line has crossed. */ + + if (st_l == 2) { + if (st_r == 2) { + INTERSECT_JUST_SMALLER(is, order, DBL_TRIANGLE_LIM, LCross); + INTERSECT_JUST_GREATER(is, order, 1 - DBL_TRIANGLE_LIM, RCross); + } + else if (st_r == 1) { + INTERSECT_JUST_SMALLER(is, order, DBL_TRIANGLE_LIM, LCross); + INTERSECT_JUST_GREATER(is, order, 1 - DBL_TRIANGLE_LIM, RCross); + } + else if (st_r == 0) { + INTERSECT_JUST_SMALLER(is, order, DBL_TRIANGLE_LIM, LCross); + INTERSECT_JUST_GREATER(is, order, 0, RCross); + } + } + else if (st_l == 1) { + if (st_r == 2) { + INTERSECT_JUST_SMALLER(is, order, DBL_TRIANGLE_LIM, LCross); + INTERSECT_JUST_GREATER(is, order, 1 - DBL_TRIANGLE_LIM, RCross); + } + else if (st_r == 1) { + INTERSECT_JUST_SMALLER(is, order, DBL_TRIANGLE_LIM, LCross); + INTERSECT_JUST_GREATER(is, order, 1 - DBL_TRIANGLE_LIM, RCross); + } + else if (st_r == 0) { + INTERSECT_JUST_GREATER(is, order, DBL_TRIANGLE_LIM, RCross); + if (LRT_ABC(RCross) && is[RCross] > (DBL_TRIANGLE_LIM)) { + INTERSECT_JUST_SMALLER(is, order, DBL_TRIANGLE_LIM, LCross); + } + else { + INTERSECT_JUST_SMALLER(is, order, -DBL_TRIANGLE_LIM, LCross); + INTERSECT_JUST_GREATER(is, order, -DBL_TRIANGLE_LIM, RCross); + } + } + } + else if (st_l == 0) { + if (st_r == 2) { + INTERSECT_JUST_SMALLER(is, order, 1 - DBL_TRIANGLE_LIM, LCross); + INTERSECT_JUST_GREATER(is, order, 1 - DBL_TRIANGLE_LIM, RCross); + } + else if (st_r == 1) { + INTERSECT_JUST_SMALLER(is, order, 1 - DBL_TRIANGLE_LIM, LCross); + if (LRT_ABC(LCross) && is[LCross] < (1 - DBL_TRIANGLE_LIM)) { + INTERSECT_JUST_GREATER(is, order, 1 - DBL_TRIANGLE_LIM, RCross); + } + else { + INTERSECT_JUST_SMALLER(is, order, 1 + DBL_TRIANGLE_LIM, LCross); + INTERSECT_JUST_GREATER(is, order, 1 + DBL_TRIANGLE_LIM, RCross); + } + } + else if (st_r == 0) { + INTERSECT_JUST_GREATER(is, order, 0, LCross); + if (LRT_ABC(LCross) && is[LCross] > 0) { + INTERSECT_JUST_GREATER(is, order, is[LCross], RCross); + } + else { + INTERSECT_JUST_GREATER(is, order, is[LCross], LCross); + INTERSECT_JUST_GREATER(is, order, is[LCross], RCross); + } + } + } + + double LF = dot_l * dot_f, RF = dot_r * dot_f; + + /* Determine the start and end point of image space cut on a line. */ + if (LF <= 0 && RF <= 0 && (dot_l || dot_r)) { + *from = MAX2(0, is[LCross]); + *to = MIN2(1, is[RCross]); + if (*from >= *to) { + return false; + } + return true; + } + if (LF >= 0 && RF <= 0 && (dot_l || dot_r)) { + *from = MAX2(cut, is[LCross]); + *to = MIN2(1, is[RCross]); + if (*from >= *to) { + return false; + } + return true; + } + if (LF <= 0 && RF >= 0 && (dot_l || dot_r)) { + *from = MAX2(0, is[LCross]); + *to = MIN2(cut, is[RCross]); + if (*from >= *to) { + return false; + } + return true; + } + + /* Unlikely, but here's the default failed value if anything fall through. */ + return false; +} + +#undef INTERSECT_SORT_MIN_TO_MAX_3 +#undef INTERSECT_JUST_GREATER +#undef INTERSECT_JUST_SMALLER + +/* At this stage of the computation we don't have triangle adjacent info anymore, so we can only + * compare the global vert index. */ +static bool lineart_triangle_share_edge(const LineartRenderTriangle *l, + const LineartRenderTriangle *r) +{ + if (l->v[0]->index == r->v[0]->index) { + if (l->v[1]->index == r->v[1]->index || l->v[1]->index == r->v[2]->index || + l->v[2]->index == r->v[2]->index || l->v[2]->index == r->v[1]->index) { + return true; + } + } + if (l->v[0]->index == r->v[1]->index) { + if (l->v[1]->index == r->v[0]->index || l->v[1]->index == r->v[2]->index || + l->v[2]->index == r->v[2]->index || l->v[2]->index == r->v[0]->index) { + return true; + } + } + if (l->v[0]->index == r->v[2]->index) { + if (l->v[1]->index == r->v[1]->index || l->v[1]->index == r->v[0]->index || + l->v[2]->index == r->v[0]->index || l->v[2]->index == r->v[1]->index) { + return true; + } + } + if (l->v[1]->index == r->v[0]->index) { + if (l->v[2]->index == r->v[1]->index || l->v[2]->index == r->v[2]->index || + l->v[0]->index == r->v[2]->index || l->v[0]->index == r->v[1]->index) { + return true; + } + } + if (l->v[1]->index == r->v[1]->index) { + if (l->v[2]->index == r->v[0]->index || l->v[2]->index == r->v[2]->index || + l->v[0]->index == r->v[2]->index || l->v[0]->index == r->v[0]->index) { + return true; + } + } + if (l->v[1]->index == r->v[2]->index) { + if (l->v[2]->index == r->v[1]->index || l->v[2]->index == r->v[0]->index || + l->v[0]->index == r->v[0]->index || l->v[0]->index == r->v[1]->index) { + return true; + } + } + + /* Otherwise not possible. */ + return false; +} + +static LineartRenderVert *lineart_triangle_share_point(const LineartRenderTriangle *l, + const LineartRenderTriangle *r) +{ + if (l->v[0] == r->v[0]) { + return r->v[0]; + } + if (l->v[0] == r->v[1]) { + return r->v[1]; + } + if (l->v[0] == r->v[2]) { + return r->v[2]; + } + if (l->v[1] == r->v[0]) { + return r->v[0]; + } + if (l->v[1] == r->v[1]) { + return r->v[1]; + } + if (l->v[1] == r->v[2]) { + return r->v[2]; + } + if (l->v[2] == r->v[0]) { + return r->v[0]; + } + if (l->v[2] == r->v[1]) { + return r->v[1]; + } + if (l->v[2] == r->v[2]) { + return r->v[2]; + } + return NULL; +} + +/* To save time and prevent overlapping lines when computing intersection lines. */ +static bool lineart_vert_already_intersected_2v(LineartRenderVertIntersection *rv, + LineartRenderVertIntersection *v1, + LineartRenderVertIntersection *v2) +{ + return ((rv->isec1 == v1->base.index && rv->isec2 == v2->base.index) || + (rv->isec2 == v2->base.index && rv->isec1 == v1->base.index)); +} + +static void lineart_vert_set_intersection_2v(LineartRenderVert *rv, + LineartRenderVert *v1, + LineartRenderVert *v2) +{ + LineartRenderVertIntersection *irv = (LineartRenderVertIntersection *)rv; + irv->isec1 = v1->index; + irv->isec2 = v2->index; +} + +/* This tests a triangle against a virtual line represented by v1---v2. The vertices returned after + * repeated calls to this function is then used to create a triangle/triangle intersection line. */ +static LineartRenderVert *lineart_triangle_2v_intersection_test(LineartRenderBuffer *rb, + LineartRenderVert *v1, + LineartRenderVert *v2, + LineartRenderTriangle *rt, + LineartRenderTriangle *testing, + LineartRenderVert *last) +{ + double Lv[3]; + double Rv[3]; + double dot_l, dot_r; + LineartRenderVert *result; + double gloc[3]; + LineartRenderVert *l = v1, *r = v2; + + for (LinkNode *ln = (void *)testing->intersecting_verts; ln; ln = ln->next) { + LineartRenderVertIntersection *rv = ln->link; + if (rv->intersecting_with == rt && + lineart_vert_already_intersected_2v( + rv, (LineartRenderVertIntersection *)l, (LineartRenderVertIntersection *)r)) { + return (LineartRenderVert *)rv; + } + } + + sub_v3_v3v3_db(Lv, l->gloc, testing->v[0]->gloc); + sub_v3_v3v3_db(Rv, r->gloc, testing->v[0]->gloc); + + dot_l = dot_v3v3_db(Lv, testing->gn); + dot_r = dot_v3v3_db(Rv, testing->gn); + + if (dot_l * dot_r > 0 || (!dot_l && !dot_r)) { + return 0; + } + + dot_l = fabs(dot_l); + dot_r = fabs(dot_r); + + interp_v3_v3v3_db(gloc, l->gloc, r->gloc, dot_l / (dot_l + dot_r)); + + /* Due to precision issue, we might end up with the same point as the one we already detected. */ + if (last && LRT_DOUBLE_CLOSE_ENOUGH(last->gloc[0], gloc[0]) && + LRT_DOUBLE_CLOSE_ENOUGH(last->gloc[1], gloc[1]) && + LRT_DOUBLE_CLOSE_ENOUGH(last->gloc[2], gloc[2])) { + return NULL; + } + + if (!(lineart_point_inside_triangle3d( + gloc, testing->v[0]->gloc, testing->v[1]->gloc, testing->v[2]->gloc))) { + return NULL; + } + + /* This is an intersection vert, the size is bigger than LineartRenderVert, + * allocated separately. */ + result = lineart_mem_aquire(&rb->render_data_pool, sizeof(LineartRenderVertIntersection)); + + /* Indicate the data structure difference. */ + result->flag = LRT_VERT_HAS_INTERSECTION_DATA; + + copy_v3_v3_db(result->gloc, gloc); + + lineart_prepend_pool(&testing->intersecting_verts, &rb->render_data_pool, result); + + return result; +} + +/* Test if two triangles intersect. Generates one intersection line if the check succeeds */ +static LineartRenderLine *lineart_triangle_intersect(LineartRenderBuffer *rb, + LineartRenderTriangle *rt, + LineartRenderTriangle *testing) +{ + LineartRenderVert *l = 0, *r = 0; + LineartRenderVert **next = &l; + LineartRenderLine *result; + LineartRenderVert *E0T = 0; + LineartRenderVert *E1T = 0; + LineartRenderVert *E2T = 0; + LineartRenderVert *TE0 = 0; + LineartRenderVert *TE1 = 0; + LineartRenderVert *TE2 = 0; + LineartRenderVert *sv1, *sv2; + double cl[3]; + + double ZMin, ZMax; + ZMax = rb->far_clip; + ZMin = rb->near_clip; + copy_v3_v3_db(cl, rb->camera_pos); + LineartRenderVert *share = lineart_triangle_share_point(testing, rt); + + if (share) { + /* If triangles have sharing points like (abc) and (acd), then we only need to detect bc + * against acd or cd against abc.*/ + + LineartRenderVert *new_share; + lineart_triangle_get_other_verts(rt, share, &sv1, &sv2); + + l = new_share = lineart_mem_aquire(&rb->render_data_pool, + (sizeof(LineartRenderVertIntersection))); + + new_share->flag = LRT_VERT_HAS_INTERSECTION_DATA; + + copy_v3_v3_db(new_share->gloc, share->gloc); + + r = lineart_triangle_2v_intersection_test(rb, sv1, sv2, rt, testing, 0); + + if (r == NULL) { + lineart_triangle_get_other_verts(testing, share, &sv1, &sv2); + r = lineart_triangle_2v_intersection_test(rb, sv1, sv2, testing, rt, 0); + if (r == NULL) { + return 0; + } + lineart_prepend_pool(&testing->intersecting_verts, &rb->render_data_pool, new_share); + } + else { + lineart_prepend_pool(&rt->intersecting_verts, &rb->render_data_pool, new_share); + } + } + else { + /* If not sharing any points, then we need to try all the possibilities. */ + + E0T = lineart_triangle_2v_intersection_test(rb, rt->v[0], rt->v[1], rt, testing, 0); + if (E0T && (!(*next))) { + (*next) = E0T; + lineart_vert_set_intersection_2v((*next), rt->v[0], rt->v[1]); + next = &r; + } + E1T = lineart_triangle_2v_intersection_test(rb, rt->v[1], rt->v[2], rt, testing, l); + if (E1T && (!(*next))) { + (*next) = E1T; + lineart_vert_set_intersection_2v((*next), rt->v[1], rt->v[2]); + next = &r; + } + if (!(*next)) { + E2T = lineart_triangle_2v_intersection_test(rb, rt->v[2], rt->v[0], rt, testing, l); + } + if (E2T && (!(*next))) { + (*next) = E2T; + lineart_vert_set_intersection_2v((*next), rt->v[2], rt->v[0]); + next = &r; + } + + if (!(*next)) { + TE0 = lineart_triangle_2v_intersection_test( + rb, testing->v[0], testing->v[1], testing, rt, l); + } + if (TE0 && (!(*next))) { + (*next) = TE0; + lineart_vert_set_intersection_2v((*next), testing->v[0], testing->v[1]); + next = &r; + } + if (!(*next)) { + TE1 = lineart_triangle_2v_intersection_test( + rb, testing->v[1], testing->v[2], testing, rt, l); + } + if (TE1 && (!(*next))) { + (*next) = TE1; + lineart_vert_set_intersection_2v((*next), testing->v[1], testing->v[2]); + next = &r; + } + if (!(*next)) { + TE2 = lineart_triangle_2v_intersection_test( + rb, testing->v[2], testing->v[0], testing, rt, l); + } + if (TE2 && (!(*next))) { + (*next) = TE2; + lineart_vert_set_intersection_2v((*next), testing->v[2], testing->v[0]); + next = &r; + } + + if (!(*next)) { + return 0; + } + } + + /* The intersection line has been generated only in geometry space, so we need to transform them + * as well. */ + mul_v4_m4v3_db(l->fbcoord, rb->view_projection, l->gloc); + mul_v4_m4v3_db(r->fbcoord, rb->view_projection, r->gloc); + mul_v3db_db(l->fbcoord, (1 / l->fbcoord[3])); + mul_v3db_db(r->fbcoord, (1 / r->fbcoord[3])); + + l->fbcoord[0] -= rb->shift_x * 2; + l->fbcoord[1] -= rb->shift_y * 2; + r->fbcoord[0] -= rb->shift_x * 2; + r->fbcoord[1] -= rb->shift_y * 2; + + /* This z transformation is not the same as the rest of the part, because the data don't go + * through normal perspective division calls in the pipeline, but this way the 3D result and + * occlution on the generated line is correct, and we don't really use 2D for viewport stroke + * generation anyway.*/ + l->fbcoord[2] = ZMin * ZMax / (ZMax - fabs(l->fbcoord[2]) * (ZMax - ZMin)); + r->fbcoord[2] = ZMin * ZMax / (ZMax - fabs(r->fbcoord[2]) * (ZMax - ZMin)); + + ((LineartRenderVertIntersection *)l)->intersecting_with = rt; + ((LineartRenderVertIntersection *)r)->intersecting_with = testing; + + result = lineart_mem_aquire(&rb->render_data_pool, sizeof(LineartRenderLine)); + result->l = l; + result->r = r; + result->tl = rt; + result->tr = testing; + + LineartRenderLineSegment *rls = lineart_mem_aquire(&rb->render_data_pool, + sizeof(LineartRenderLineSegment)); + BLI_addtail(&result->segments, rls); + /* Don't need to OR flags right now, just a type mark. */ + result->flags = LRT_EDGE_FLAG_INTERSECTION; + lineart_prepend_line_direct(&rb->intersection_lines, result); + int r1, r2, c1, c2, row, col; + if (lineart_get_line_bounding_areas(rb, result, &r1, &r2, &c1, &c2)) { + for (row = r1; row != r2 + 1; row++) { + for (col = c1; col != c2 + 1; col++) { + lineart_bounding_area_link_line( + rb, &rb->initial_bounding_areas[row * LRT_BA_ROWS + col], result); + } + } + } + + rb->intersection_count++; + + return result; +} + +static void lineart_triangle_intersect_in_bounding_area(LineartRenderBuffer *rb, + LineartRenderTriangle *rt, + LineartBoundingArea *ba) +{ + /* Testing_triangle->testing[0] is used to store pairing triangle reference. + * See definition of LineartRenderTriangleThread for more info. */ + LineartRenderTriangle *testing_triangle; + LineartRenderTriangleThread *rtt; + LinkData *lip, *next_lip; + + double *G0 = rt->v[0]->gloc, *G1 = rt->v[1]->gloc, *G2 = rt->v[2]->gloc; + + /* If this is not the smallest subdiv bounding area.*/ + if (ba->child) { + lineart_triangle_intersect_in_bounding_area(rb, rt, &ba->child[0]); + lineart_triangle_intersect_in_bounding_area(rb, rt, &ba->child[1]); + lineart_triangle_intersect_in_bounding_area(rb, rt, &ba->child[2]); + lineart_triangle_intersect_in_bounding_area(rb, rt, &ba->child[3]); + return; + } + + /* If this _is_ the smallest subdiv bounding area, then do the intersections there. */ + for (lip = ba->linked_triangles.first; lip; lip = next_lip) { + next_lip = lip->next; + testing_triangle = lip->data; + rtt = (LineartRenderTriangleThread *)testing_triangle; + + if (testing_triangle == rt || rtt->testing[0] == (LineartRenderLine *)rt || + (testing_triangle->flags & LRT_TRIANGLE_NO_INTERSECTION) || + ((testing_triangle->flags & LRT_TRIANGLE_INTERSECTION_ONLY) && + (rt->flags & LRT_TRIANGLE_INTERSECTION_ONLY)) || + lineart_triangle_share_edge(rt, testing_triangle)) { + continue; + } + + rtt->testing[0] = (LineartRenderLine *)rt; + double *RG0 = testing_triangle->v[0]->gloc, *RG1 = testing_triangle->v[1]->gloc, + *RG2 = testing_triangle->v[2]->gloc; + + /* Bounding box not overlapping, not potential of intersecting. */ + if ((MIN3(G0[2], G1[2], G2[2]) > MAX3(RG0[2], RG1[2], RG2[2])) || + (MAX3(G0[2], G1[2], G2[2]) < MIN3(RG0[2], RG1[2], RG2[2])) || + (MIN3(G0[0], G1[0], G2[0]) > MAX3(RG0[0], RG1[0], RG2[0])) || + (MAX3(G0[0], G1[0], G2[0]) < MIN3(RG0[0], RG1[0], RG2[0])) || + (MIN3(G0[1], G1[1], G2[1]) > MAX3(RG0[1], RG1[1], RG2[1])) || + (MAX3(G0[1], G1[1], G2[1]) < MIN3(RG0[1], RG1[1], RG2[1]))) { + continue; + } + + /* If we do need to compute intersection, then finally do it. */ + lineart_triangle_intersect(rb, rt, testing_triangle); + } +} + +/* The calculated view vector will point towards the far-plane from the camera position. */ +static void lineart_main_get_view_vector(LineartRenderBuffer *rb) +{ + float direction[3] = {0, 0, 1}; + float trans[3]; + float inv[4][4]; + float obmat_no_scale[4][4]; + + copy_m4_m4(obmat_no_scale, rb->cam_obmat); + + normalize_v3(obmat_no_scale[0]); + normalize_v3(obmat_no_scale[1]); + normalize_v3(obmat_no_scale[2]); + invert_m4_m4(inv, obmat_no_scale); + transpose_m4(inv); + mul_v3_mat3_m4v3(trans, inv, direction); + copy_m4_m4(rb->cam_obmat, obmat_no_scale); + copy_v3db_v3fl(rb->view_vector, trans); +} + +static void lineart_destroy_render_data(LineartRenderBuffer *rb) +{ + if (rb == NULL) { + return; + } + + rb->contour_count = 0; + rb->contour_managed = NULL; + rb->intersection_count = 0; + rb->intersection_managed = NULL; + rb->material_line_count = 0; + rb->material_managed = NULL; + rb->crease_count = 0; + rb->crease_managed = NULL; + rb->edge_mark_count = 0; + rb->edge_mark_managed = NULL; + + rb->contours = NULL; + rb->intersection_lines = NULL; + rb->crease_lines = NULL; + rb->material_lines = NULL; + rb->edge_marks = NULL; + + BLI_listbase_clear(&rb->chains); + BLI_listbase_clear(&rb->wasted_cuts); + + BLI_listbase_clear(&rb->vertex_buffer_pointers); + BLI_listbase_clear(&rb->line_buffer_pointers); + BLI_listbase_clear(&rb->triangle_buffer_pointers); + + BLI_spin_end(&rb->lock_task); + BLI_spin_end(&rb->lock_cuts); + BLI_spin_end(&rb->render_data_pool.lock_mem); + + lineart_mem_destroy(&rb->render_data_pool); +} + +void MOD_lineart_destroy_render_data(LineartGpencilModifierData *lmd) +{ + LineartRenderBuffer *rb = lmd->render_buffer; + + lineart_destroy_render_data(rb); + + if (rb) { + MEM_freeN(rb); + lmd->render_buffer = NULL; + } + + if (G.debug_value == 4000) { + printf("LRT: Destroyed render data.\n"); + } +} + +static LineartRenderBuffer *lineart_create_render_buffer(Scene *scene, + LineartGpencilModifierData *lmd) +{ + LineartRenderBuffer *rb = MEM_callocN(sizeof(LineartRenderBuffer), "Line Art render buffer"); + + lmd->render_buffer = rb; + + if (!scene || !scene->camera) { + return NULL; + } + Camera *c = scene->camera->data; + double clipping_offset = 0; + + if (lmd->calculation_flags & LRT_ALLOW_CLIPPING_BOUNDARIES) { + /* This way the clipped lines are "stablely visible" by prevents depth buffer artefacts. */ + clipping_offset = 0.0001; + } + + copy_v3db_v3fl(rb->camera_pos, scene->camera->obmat[3]); + copy_m4_m4(rb->cam_obmat, scene->camera->obmat); + rb->cam_is_persp = (c->type == CAM_PERSP); + rb->near_clip = c->clip_start + clipping_offset; + rb->far_clip = c->clip_end - clipping_offset; + rb->w = scene->r.xsch; + rb->h = scene->r.ysch; + + double asp = ((double)rb->w / (double)rb->h); + rb->shift_x = (asp >= 1) ? c->shiftx : c->shiftx * asp; + rb->shift_y = (asp <= 1) ? c->shifty : c->shifty * asp; + + rb->crease_threshold = cos(M_PI - lmd->crease_threshold); + rb->angle_splitting_threshold = lmd->angle_splitting_threshold; + rb->chaining_image_threshold = lmd->chaining_image_threshold; + rb->chaining_geometry_threshold = lmd->chaining_geometry_threshold; + + rb->fuzzy_intersections = (lmd->calculation_flags & LRT_INTERSECTION_AS_CONTOUR) != 0; + rb->fuzzy_everything = (lmd->calculation_flags & LRT_EVERYTHING_AS_CONTOUR) != 0; + rb->allow_boundaries = (lmd->calculation_flags & LRT_ALLOW_CLIPPING_BOUNDARIES) != 0; + rb->remove_doubles = (lmd->calculation_flags & LRT_REMOVE_DOUBLES) != 0; + + /* See lineart_edge_from_triangle() for how this option may impact performance. */ + rb->allow_overlapping_edges = (lmd->calculation_flags & LRT_ALLOW_OVERLAPPING_EDGES) != 0; + + rb->use_contour = (lmd->line_types & LRT_EDGE_FLAG_CONTOUR) != 0; + rb->use_crease = (lmd->line_types & LRT_EDGE_FLAG_CREASE) != 0; + rb->use_material = (lmd->line_types & LRT_EDGE_FLAG_MATERIAL) != 0; + rb->use_edge_marks = (lmd->line_types & LRT_EDGE_FLAG_EDGE_MARK) != 0; + rb->use_intersections = (lmd->line_types & LRT_EDGE_FLAG_INTERSECTION) != 0; + + BLI_spin_init(&rb->lock_task); + BLI_spin_init(&rb->lock_cuts); + BLI_spin_init(&rb->render_data_pool.lock_mem); + + return rb; +} + +static int lineart_triangle_size_get(const Scene *scene, LineartRenderBuffer *rb) +{ + if (rb->thread_count == 0) { + rb->thread_count = BKE_render_num_threads(&scene->r); + } + return sizeof(LineartRenderTriangle) + (sizeof(LineartRenderLine *) * (rb->thread_count)); +} + +static void lineart_main_bounding_area_make_initial(LineartRenderBuffer *rb) +{ + /* Initial tile split is defined as 4 (subdivided as 4*4), increasing the value allows the + * algorithm to build the acceleration structure for bigger scenes a little faster but not as + * efficient at handling medium to small scenes. */ + int sp_w = LRT_BA_ROWS; + int sp_h = LRT_BA_ROWS; + int row, col; + LineartBoundingArea *ba; + + /* Because NDC (Normalized Device Coordinates) range is (-1,1), + * so the span for each initial tile is double of that in the (0,1) range. */ + double span_w = (double)1 / sp_w * 2.0; + double span_h = (double)1 / sp_h * 2.0; + + rb->tile_count_x = sp_w; + rb->tile_count_y = sp_h; + rb->width_per_tile = span_w; + rb->height_per_tile = span_h; + + rb->bounding_area_count = sp_w * sp_h; + rb->initial_bounding_areas = lineart_mem_aquire( + &rb->render_data_pool, sizeof(LineartBoundingArea) * rb->bounding_area_count); + + /* Initialize tiles. */ + for (row = 0; row < sp_h; row++) { + for (col = 0; col < sp_w; col++) { + ba = &rb->initial_bounding_areas[row * LRT_BA_ROWS + col]; + + /* Set the four direction limits. */ + ba->l = span_w * col - 1.0; + ba->r = (col == sp_w - 1) ? 1.0 : (span_w * (col + 1) - 1.0); + ba->u = 1.0 - span_h * row; + ba->b = (row == sp_h - 1) ? -1.0 : (1.0 - span_h * (row + 1)); + + ba->cx = (ba->l + ba->r) / 2; + ba->cy = (ba->u + ba->b) / 2; + + /* Link adjacent ones. */ + if (row) { + lineart_list_append_pointer_pool( + &ba->up, + &rb->render_data_pool, + &rb->initial_bounding_areas[(row - 1) * LRT_BA_ROWS + col]); + } + if (col) { + lineart_list_append_pointer_pool(&ba->lp, + &rb->render_data_pool, + &rb->initial_bounding_areas[row * LRT_BA_ROWS + col - 1]); + } + if (row != sp_h - 1) { + lineart_list_append_pointer_pool( + &ba->bp, + &rb->render_data_pool, + &rb->initial_bounding_areas[(row + 1) * LRT_BA_ROWS + col]); + } + if (col != sp_w - 1) { + lineart_list_append_pointer_pool(&ba->rp, + &rb->render_data_pool, + &rb->initial_bounding_areas[row * LRT_BA_ROWS + col + 1]); + } + } + } +} + +/* Re-link adjacent tiles after one gets subdivided. */ +static void lineart_bounding_areas_connect_new(LineartRenderBuffer *rb, LineartBoundingArea *root) +{ + LineartBoundingArea *ba = root->child, *tba; + LinkData *lip2, *next_lip; + LineartStaticMemPool *mph = &rb->render_data_pool; + + /* Inter-connection with newly created 4 child bounding areas. */ + lineart_list_append_pointer_pool(&ba[1].rp, mph, &ba[0]); + lineart_list_append_pointer_pool(&ba[0].lp, mph, &ba[1]); + lineart_list_append_pointer_pool(&ba[1].bp, mph, &ba[2]); + lineart_list_append_pointer_pool(&ba[2].up, mph, &ba[1]); + lineart_list_append_pointer_pool(&ba[2].rp, mph, &ba[3]); + lineart_list_append_pointer_pool(&ba[3].lp, mph, &ba[2]); + lineart_list_append_pointer_pool(&ba[3].up, mph, &ba[0]); + lineart_list_append_pointer_pool(&ba[0].bp, mph, &ba[3]); + + /* Connect 4 child bounding areas to other areas that are + * adjacent to their original parents. */ + LISTBASE_FOREACH (LinkData *, lip, &root->lp) { + + /* For example, we are dealing with parent's left side + * "tba" represents each adjacent neighbor of the parent. */ + tba = lip->data; + + /* if this neighbor is adjacent to + * the two new areas on the left side of the parent, + * then add them to the adjacent list as well. */ + if (ba[1].u > tba->b && ba[1].b < tba->u) { + lineart_list_append_pointer_pool(&ba[1].lp, mph, tba); + lineart_list_append_pointer_pool(&tba->rp, mph, &ba[1]); + } + if (ba[2].u > tba->b && ba[2].b < tba->u) { + lineart_list_append_pointer_pool(&ba[2].lp, mph, tba); + lineart_list_append_pointer_pool(&tba->rp, mph, &ba[2]); + } + } + LISTBASE_FOREACH (LinkData *, lip, &root->rp) { + tba = lip->data; + if (ba[0].u > tba->b && ba[0].b < tba->u) { + lineart_list_append_pointer_pool(&ba[0].rp, mph, tba); + lineart_list_append_pointer_pool(&tba->lp, mph, &ba[0]); + } + if (ba[3].u > tba->b && ba[3].b < tba->u) { + lineart_list_append_pointer_pool(&ba[3].rp, mph, tba); + lineart_list_append_pointer_pool(&tba->lp, mph, &ba[3]); + } + } + LISTBASE_FOREACH (LinkData *, lip, &root->up) { + tba = lip->data; + if (ba[0].r > tba->l && ba[0].l < tba->r) { + lineart_list_append_pointer_pool(&ba[0].up, mph, tba); + lineart_list_append_pointer_pool(&tba->bp, mph, &ba[0]); + } + if (ba[1].r > tba->l && ba[1].l < tba->r) { + lineart_list_append_pointer_pool(&ba[1].up, mph, tba); + lineart_list_append_pointer_pool(&tba->bp, mph, &ba[1]); + } + } + LISTBASE_FOREACH (LinkData *, lip, &root->bp) { + tba = lip->data; + if (ba[2].r > tba->l && ba[2].l < tba->r) { + lineart_list_append_pointer_pool(&ba[2].bp, mph, tba); + lineart_list_append_pointer_pool(&tba->up, mph, &ba[2]); + } + if (ba[3].r > tba->l && ba[3].l < tba->r) { + lineart_list_append_pointer_pool(&ba[3].bp, mph, tba); + lineart_list_append_pointer_pool(&tba->up, mph, &ba[3]); + } + } + + /* Then remove the parent bounding areas from + * their original adjacent areas. */ + LISTBASE_FOREACH (LinkData *, lip, &root->lp) { + for (lip2 = ((LineartBoundingArea *)lip->data)->rp.first; lip2; lip2 = next_lip) { + next_lip = lip2->next; + tba = lip2->data; + if (tba == root) { + lineart_list_remove_pointer_item_no_free(&((LineartBoundingArea *)lip->data)->rp, lip2); + if (ba[1].u > tba->b && ba[1].b < tba->u) { + lineart_list_append_pointer_pool(&tba->rp, mph, &ba[1]); + } + if (ba[2].u > tba->b && ba[2].b < tba->u) { + lineart_list_append_pointer_pool(&tba->rp, mph, &ba[2]); + } + } + } + } + LISTBASE_FOREACH (LinkData *, lip, &root->rp) { + for (lip2 = ((LineartBoundingArea *)lip->data)->lp.first; lip2; lip2 = next_lip) { + next_lip = lip2->next; + tba = lip2->data; + if (tba == root) { + lineart_list_remove_pointer_item_no_free(&((LineartBoundingArea *)lip->data)->lp, lip2); + if (ba[0].u > tba->b && ba[0].b < tba->u) { + lineart_list_append_pointer_pool(&tba->lp, mph, &ba[0]); + } + if (ba[3].u > tba->b && ba[3].b < tba->u) { + lineart_list_append_pointer_pool(&tba->lp, mph, &ba[3]); + } + } + } + } + LISTBASE_FOREACH (LinkData *, lip, &root->up) { + for (lip2 = ((LineartBoundingArea *)lip->data)->bp.first; lip2; lip2 = next_lip) { + next_lip = lip2->next; + tba = lip2->data; + if (tba == root) { + lineart_list_remove_pointer_item_no_free(&((LineartBoundingArea *)lip->data)->bp, lip2); + if (ba[0].r > tba->l && ba[0].l < tba->r) { + lineart_list_append_pointer_pool(&tba->up, mph, &ba[0]); + } + if (ba[1].r > tba->l && ba[1].l < tba->r) { + lineart_list_append_pointer_pool(&tba->up, mph, &ba[1]); + } + } + } + } + LISTBASE_FOREACH (LinkData *, lip, &root->bp) { + for (lip2 = ((LineartBoundingArea *)lip->data)->up.first; lip2; lip2 = next_lip) { + next_lip = lip2->next; + tba = lip2->data; + if (tba == root) { + lineart_list_remove_pointer_item_no_free(&((LineartBoundingArea *)lip->data)->up, lip2); + if (ba[2].r > tba->l && ba[2].l < tba->r) { + lineart_list_append_pointer_pool(&tba->bp, mph, &ba[2]); + } + if (ba[3].r > tba->l && ba[3].l < tba->r) { + lineart_list_append_pointer_pool(&tba->bp, mph, &ba[3]); + } + } + } + } + + /* Finally clear parent'scene adjacent list. */ + BLI_listbase_clear(&root->lp); + BLI_listbase_clear(&root->rp); + BLI_listbase_clear(&root->up); + BLI_listbase_clear(&root->bp); +} + +/* Subdivide a tile after one tile contains too many triangles. */ +static void lineart_bounding_area_split(LineartRenderBuffer *rb, + LineartBoundingArea *root, + int recursive_level) +{ + LineartBoundingArea *ba = lineart_mem_aquire(&rb->render_data_pool, + sizeof(LineartBoundingArea) * 4); + LineartRenderTriangle *rt; + LineartRenderLine *rl; + + ba[0].l = root->cx; + ba[0].r = root->r; + ba[0].u = root->u; + ba[0].b = root->cy; + ba[0].cx = (ba[0].l + ba[0].r) / 2; + ba[0].cy = (ba[0].u + ba[0].b) / 2; + + ba[1].l = root->l; + ba[1].r = root->cx; + ba[1].u = root->u; + ba[1].b = root->cy; + ba[1].cx = (ba[1].l + ba[1].r) / 2; + ba[1].cy = (ba[1].u + ba[1].b) / 2; + + ba[2].l = root->l; + ba[2].r = root->cx; + ba[2].u = root->cy; + ba[2].b = root->b; + ba[2].cx = (ba[2].l + ba[2].r) / 2; + ba[2].cy = (ba[2].u + ba[2].b) / 2; + + ba[3].l = root->cx; + ba[3].r = root->r; + ba[3].u = root->cy; + ba[3].b = root->b; + ba[3].cx = (ba[3].l + ba[3].r) / 2; + ba[3].cy = (ba[3].u + ba[3].b) / 2; + + root->child = ba; + + lineart_bounding_areas_connect_new(rb, root); + + while ((rt = lineart_list_pop_pointer_no_free(&root->linked_triangles)) != NULL) { + LineartBoundingArea *cba = root->child; + double b[4]; + b[0] = MIN3(rt->v[0]->fbcoord[0], rt->v[1]->fbcoord[0], rt->v[2]->fbcoord[0]); + b[1] = MAX3(rt->v[0]->fbcoord[0], rt->v[1]->fbcoord[0], rt->v[2]->fbcoord[0]); + b[2] = MAX3(rt->v[0]->fbcoord[1], rt->v[1]->fbcoord[1], rt->v[2]->fbcoord[1]); + b[3] = MIN3(rt->v[0]->fbcoord[1], rt->v[1]->fbcoord[1], rt->v[2]->fbcoord[1]); + if (LRT_BOUND_AREA_CROSSES(b, &cba[0].l)) { + lineart_bounding_area_link_triangle(rb, &cba[0], rt, b, 0, recursive_level + 1, false); + } + if (LRT_BOUND_AREA_CROSSES(b, &cba[1].l)) { + lineart_bounding_area_link_triangle(rb, &cba[1], rt, b, 0, recursive_level + 1, false); + } + if (LRT_BOUND_AREA_CROSSES(b, &cba[2].l)) { + lineart_bounding_area_link_triangle(rb, &cba[2], rt, b, 0, recursive_level + 1, false); + } + if (LRT_BOUND_AREA_CROSSES(b, &cba[3].l)) { + lineart_bounding_area_link_triangle(rb, &cba[3], rt, b, 0, recursive_level + 1, false); + } + } + + while ((rl = lineart_list_pop_pointer_no_free(&root->linked_lines)) != NULL) { + lineart_bounding_area_link_line(rb, root, rl); + } + + rb->bounding_area_count += 3; +} + +static bool lineart_bounding_area_line_intersect(LineartRenderBuffer *UNUSED(fb), + const double l[2], + const double r[2], + LineartBoundingArea *ba) +{ + double vx, vy; + double converted[4]; + double c1, c; + + if (((converted[0] = (double)ba->l) > MAX2(l[0], r[0])) || + ((converted[1] = (double)ba->r) < MIN2(l[0], r[0])) || + ((converted[2] = (double)ba->b) > MAX2(l[1], r[1])) || + ((converted[3] = (double)ba->u) < MIN2(l[1], r[1]))) { + return false; + } + + vx = l[0] - r[0]; + vy = l[1] - r[1]; + + c1 = vx * (converted[2] - l[1]) - vy * (converted[0] - l[0]); + c = c1; + + c1 = vx * (converted[2] - l[1]) - vy * (converted[1] - l[0]); + if (c1 * c <= 0) { + return true; + } + c = c1; + + c1 = vx * (converted[3] - l[1]) - vy * (converted[0] - l[0]); + if (c1 * c <= 0) { + return true; + } + c = c1; + + c1 = vx * (converted[3] - l[1]) - vy * (converted[1] - l[0]); + if (c1 * c <= 0) { + return true; + } + c = c1; + + return false; +} + +static bool lineart_bounding_area_triangle_intersect(LineartRenderBuffer *fb, + LineartRenderTriangle *rt, + LineartBoundingArea *ba) +{ + double p1[2], p2[2], p3[2], p4[2]; + double *FBC1 = rt->v[0]->fbcoord, *FBC2 = rt->v[1]->fbcoord, *FBC3 = rt->v[2]->fbcoord; + + p3[0] = p1[0] = (double)ba->l; + p2[1] = p1[1] = (double)ba->b; + p2[0] = p4[0] = (double)ba->r; + p3[1] = p4[1] = (double)ba->u; + + if ((FBC1[0] >= p1[0] && FBC1[0] <= p2[0] && FBC1[1] >= p1[1] && FBC1[1] <= p3[1]) || + (FBC2[0] >= p1[0] && FBC2[0] <= p2[0] && FBC2[1] >= p1[1] && FBC2[1] <= p3[1]) || + (FBC3[0] >= p1[0] && FBC3[0] <= p2[0] && FBC3[1] >= p1[1] && FBC3[1] <= p3[1])) { + return true; + } + + if (lineart_point_inside_triangle(p1, FBC1, FBC2, FBC3) || + lineart_point_inside_triangle(p2, FBC1, FBC2, FBC3) || + lineart_point_inside_triangle(p3, FBC1, FBC2, FBC3) || + lineart_point_inside_triangle(p4, FBC1, FBC2, FBC3)) { + return true; + } + + if ((lineart_bounding_area_line_intersect(fb, FBC1, FBC2, ba)) || + (lineart_bounding_area_line_intersect(fb, FBC2, FBC3, ba)) || + (lineart_bounding_area_line_intersect(fb, FBC3, FBC1, ba))) { + return true; + } + + return false; +} + +/* 1) Link triangles with bounding areas for later occlusion test. + * 2) Test triangles with existing(added previously) triangles for intersection lines. */ +static void lineart_bounding_area_link_triangle(LineartRenderBuffer *rb, + LineartBoundingArea *root_ba, + LineartRenderTriangle *rt, + double *LRUB, + int recursive, + int recursive_level, + bool do_intersection) +{ + if (!lineart_bounding_area_triangle_intersect(rb, rt, root_ba)) { + return; + } + if (root_ba->child == NULL) { + lineart_list_append_pointer_pool(&root_ba->linked_triangles, &rb->render_data_pool, rt); + root_ba->triangle_count++; + /* If splitting doesn't improve triangle separation, then shouldn't allow splitting anymore. + * Here we use recursive limit. This is espetially useful in ortho render, where a lot of + * faces could easily line up perfectly in image space, which can not be separated by simply + * slicing the image tile. */ + if (root_ba->triangle_count > 200 && recursive && recursive_level < 10) { + lineart_bounding_area_split(rb, root_ba, recursive_level); + } + if (recursive && do_intersection && rb->use_intersections) { + lineart_triangle_intersect_in_bounding_area(rb, rt, root_ba); + } + } + else { + LineartBoundingArea *ba = root_ba->child; + double *B1 = LRUB; + double b[4]; + if (!LRUB) { + b[0] = MIN3(rt->v[0]->fbcoord[0], rt->v[1]->fbcoord[0], rt->v[2]->fbcoord[0]); + b[1] = MAX3(rt->v[0]->fbcoord[0], rt->v[1]->fbcoord[0], rt->v[2]->fbcoord[0]); + b[2] = MAX3(rt->v[0]->fbcoord[1], rt->v[1]->fbcoord[1], rt->v[2]->fbcoord[1]); + b[3] = MIN3(rt->v[0]->fbcoord[1], rt->v[1]->fbcoord[1], rt->v[2]->fbcoord[1]); + B1 = b; + } + if (LRT_BOUND_AREA_CROSSES(B1, &ba[0].l)) { + lineart_bounding_area_link_triangle( + rb, &ba[0], rt, B1, recursive, recursive_level + 1, do_intersection); + } + if (LRT_BOUND_AREA_CROSSES(B1, &ba[1].l)) { + lineart_bounding_area_link_triangle( + rb, &ba[1], rt, B1, recursive, recursive_level + 1, do_intersection); + } + if (LRT_BOUND_AREA_CROSSES(B1, &ba[2].l)) { + lineart_bounding_area_link_triangle( + rb, &ba[2], rt, B1, recursive, recursive_level + 1, do_intersection); + } + if (LRT_BOUND_AREA_CROSSES(B1, &ba[3].l)) { + lineart_bounding_area_link_triangle( + rb, &ba[3], rt, B1, recursive, recursive_level + 1, do_intersection); + } + } +} + +static void lineart_bounding_area_link_line(LineartRenderBuffer *rb, + LineartBoundingArea *root_ba, + LineartRenderLine *rl) +{ + if (root_ba->child == NULL) { + lineart_list_append_pointer_pool(&root_ba->linked_lines, &rb->render_data_pool, rl); + } + else { + if (lineart_bounding_area_line_intersect( + rb, rl->l->fbcoord, rl->r->fbcoord, &root_ba->child[0])) { + lineart_bounding_area_link_line(rb, &root_ba->child[0], rl); + } + if (lineart_bounding_area_line_intersect( + rb, rl->l->fbcoord, rl->r->fbcoord, &root_ba->child[1])) { + lineart_bounding_area_link_line(rb, &root_ba->child[1], rl); + } + if (lineart_bounding_area_line_intersect( + rb, rl->l->fbcoord, rl->r->fbcoord, &root_ba->child[2])) { + lineart_bounding_area_link_line(rb, &root_ba->child[2], rl); + } + if (lineart_bounding_area_line_intersect( + rb, rl->l->fbcoord, rl->r->fbcoord, &root_ba->child[3])) { + lineart_bounding_area_link_line(rb, &root_ba->child[3], rl); + } + } +} + +/* Link lines to their respective bounding areas. */ +static void lineart_main_link_lines(LineartRenderBuffer *rb) +{ + LRT_ITER_ALL_LINES_BEGIN + { + int r1, r2, c1, c2, row, col; + if (lineart_get_line_bounding_areas(rb, rl, &r1, &r2, &c1, &c2)) { + for (row = r1; row != r2 + 1; row++) { + for (col = c1; col != c2 + 1; col++) { + lineart_bounding_area_link_line( + rb, &rb->initial_bounding_areas[row * LRT_BA_ROWS + col], rl); + } + } + } + } + LRT_ITER_ALL_LINES_END +} + +static bool lineart_get_triangle_bounding_areas(LineartRenderBuffer *rb, + LineartRenderTriangle *rt, + int *rowbegin, + int *rowend, + int *colbegin, + int *colend) +{ + double sp_w = rb->width_per_tile, sp_h = rb->height_per_tile; + double b[4]; + + if (!rt->v[0] || !rt->v[1] || !rt->v[2]) { + return false; + } + + b[0] = MIN3(rt->v[0]->fbcoord[0], rt->v[1]->fbcoord[0], rt->v[2]->fbcoord[0]); + b[1] = MAX3(rt->v[0]->fbcoord[0], rt->v[1]->fbcoord[0], rt->v[2]->fbcoord[0]); + b[2] = MIN3(rt->v[0]->fbcoord[1], rt->v[1]->fbcoord[1], rt->v[2]->fbcoord[1]); + b[3] = MAX3(rt->v[0]->fbcoord[1], rt->v[1]->fbcoord[1], rt->v[2]->fbcoord[1]); + + if (b[0] > 1 || b[1] < -1 || b[2] > 1 || b[3] < -1) { + return false; + } + + (*colbegin) = (int)((b[0] + 1.0) / sp_w); + (*colend) = (int)((b[1] + 1.0) / sp_w); + (*rowend) = rb->tile_count_y - (int)((b[2] + 1.0) / sp_h) - 1; + (*rowbegin) = rb->tile_count_y - (int)((b[3] + 1.0) / sp_h) - 1; + + if ((*colend) >= rb->tile_count_x) { + (*colend) = rb->tile_count_x - 1; + } + if ((*rowend) >= rb->tile_count_y) { + (*rowend) = rb->tile_count_y - 1; + } + if ((*colbegin) < 0) { + (*colbegin) = 0; + } + if ((*rowbegin) < 0) { + (*rowbegin) = 0; + } + + return true; +} + +static bool lineart_get_line_bounding_areas(LineartRenderBuffer *rb, + LineartRenderLine *rl, + int *rowbegin, + int *rowend, + int *colbegin, + int *colend) +{ + double sp_w = rb->width_per_tile, sp_h = rb->height_per_tile; + double b[4]; + + if (!rl->l || !rl->r) { + return false; + } + + if (rl->l->fbcoord[0] != rl->l->fbcoord[0] || rl->r->fbcoord[0] != rl->r->fbcoord[0]) { + return false; + } + + b[0] = MIN2(rl->l->fbcoord[0], rl->r->fbcoord[0]); + b[1] = MAX2(rl->l->fbcoord[0], rl->r->fbcoord[0]); + b[2] = MIN2(rl->l->fbcoord[1], rl->r->fbcoord[1]); + b[3] = MAX2(rl->l->fbcoord[1], rl->r->fbcoord[1]); + + if (b[0] > 1 || b[1] < -1 || b[2] > 1 || b[3] < -1) { + return false; + } + + (*colbegin) = (int)((b[0] + 1.0) / sp_w); + (*colend) = (int)((b[1] + 1.0) / sp_w); + (*rowend) = rb->tile_count_y - (int)((b[2] + 1.0) / sp_h) - 1; + (*rowbegin) = rb->tile_count_y - (int)((b[3] + 1.0) / sp_h) - 1; + + /* It'scene possible that the line stretches too much out to the side, resulting negative value + . */ + if ((*rowend) < (*rowbegin)) { + (*rowend) = rb->tile_count_y - 1; + } + + if ((*colend) < (*colbegin)) { + (*colend) = rb->tile_count_x - 1; + } + + CLAMP((*colbegin), 0, rb->tile_count_x - 1); + CLAMP((*rowbegin), 0, rb->tile_count_y - 1); + CLAMP((*colend), 0, rb->tile_count_x - 1); + CLAMP((*rowend), 0, rb->tile_count_y - 1); + + return true; +} + +/* This only gets initial "biggest" tile. */ +LineartBoundingArea *MOD_lineart_get_parent_bounding_area(LineartRenderBuffer *rb, + double x, + double y) +{ + double sp_w = rb->width_per_tile, sp_h = rb->height_per_tile; + int col, row; + + if (x > 1 || x < -1 || y > 1 || y < -1) { + return 0; + } + + col = (int)((x + 1.0) / sp_w); + row = rb->tile_count_y - (int)((y + 1.0) / sp_h) - 1; + + if (col >= rb->tile_count_x) { + col = rb->tile_count_x - 1; + } + if (row >= rb->tile_count_y) { + row = rb->tile_count_y - 1; + } + if (col < 0) { + col = 0; + } + if (row < 0) { + row = 0; + } + + return &rb->initial_bounding_areas[row * LRT_BA_ROWS + col]; +} + +static LineartBoundingArea *lineart_get_bounding_area(LineartRenderBuffer *rb, double x, double y) +{ + LineartBoundingArea *iba; + double sp_w = rb->width_per_tile, sp_h = rb->height_per_tile; + int c = (int)((x + 1.0) / sp_w); + int r = rb->tile_count_y - (int)((y + 1.0) / sp_h) - 1; + if (r < 0) { + r = 0; + } + if (c < 0) { + c = 0; + } + if (r >= rb->tile_count_y) { + r = rb->tile_count_y - 1; + } + if (c >= rb->tile_count_x) { + c = rb->tile_count_x - 1; + } + + iba = &rb->initial_bounding_areas[r * LRT_BA_ROWS + c]; + while (iba->child) { + if (x > iba->cx) { + if (y > iba->cy) { + iba = &iba->child[0]; + } + else { + iba = &iba->child[3]; + } + } + else { + if (y > iba->cy) { + iba = &iba->child[1]; + } + else { + iba = &iba->child[2]; + } + } + } + return iba; +} + +/* Wrapper for more convenience. */ +LineartBoundingArea *MOD_lineart_get_bounding_area(LineartRenderBuffer *rb, double x, double y) +{ + LineartBoundingArea *ba; + if ((ba = MOD_lineart_get_parent_bounding_area(rb, x, y)) != NULL) { + return lineart_get_bounding_area(rb, x, y); + } + return NULL; +} + +/* Sequentially add triangles into render buffer. This also does intersection along the way. */ +static void lineart_main_add_triangles(LineartRenderBuffer *rb) +{ + LineartRenderTriangle *rt; + int i, lim; + int x1, x2, y1, y2; + int r, co; + + LISTBASE_FOREACH (LineartRenderElementLinkNode *, reln, &rb->triangle_buffer_pointers) { + rt = reln->pointer; + lim = reln->element_count; + for (i = 0; i < lim; i++) { + if ((rt->flags & LRT_CULL_USED) || (rt->flags & LRT_CULL_DISCARD)) { + rt = (void *)(((unsigned char *)rt) + rb->triangle_size); + continue; + } + if (lineart_get_triangle_bounding_areas(rb, rt, &y1, &y2, &x1, &x2)) { + for (co = x1; co <= x2; co++) { + for (r = y1; r <= y2; r++) { + lineart_bounding_area_link_triangle(rb, + &rb->initial_bounding_areas[r * LRT_BA_ROWS + co], + rt, + 0, + 1, + 0, + (!(rt->flags & LRT_TRIANGLE_NO_INTERSECTION))); + } + } + } /* Else throw away. */ + rt = (void *)(((unsigned char *)rt) + rb->triangle_size); + } + } +} + +/* This function gets the tile for the point rl->l, and later use lineart_bounding_area_next() to + * get next along the way. */ +static LineartBoundingArea *lineart_line_first_bounding_area(LineartRenderBuffer *rb, + LineartRenderLine *rl) +{ + double data[2] = {rl->l->fbcoord[0], rl->l->fbcoord[1]}; + double LU[2] = {-1, 1}, RU[2] = {1, 1}, LB[2] = {-1, -1}, RB[2] = {1, -1}; + double r = 1, sr = 1; + + if (data[0] > -1 && data[0] < 1 && data[1] > -1 && data[1] < 1) { + return lineart_get_bounding_area(rb, data[0], data[1]); + } + + if (lineart_LineIntersectTest2d(rl->l->fbcoord, rl->r->fbcoord, LU, RU, &sr) && sr < r && + sr > 0) { + r = sr; + } + if (lineart_LineIntersectTest2d(rl->l->fbcoord, rl->r->fbcoord, LB, RB, &sr) && sr < r && + sr > 0) { + r = sr; + } + if (lineart_LineIntersectTest2d(rl->l->fbcoord, rl->r->fbcoord, LB, LU, &sr) && sr < r && + sr > 0) { + r = sr; + } + if (lineart_LineIntersectTest2d(rl->l->fbcoord, rl->r->fbcoord, RB, RU, &sr) && sr < r && + sr > 0) { + r = sr; + } + interp_v2_v2v2_db(data, rl->l->fbcoord, rl->r->fbcoord, r); + + return lineart_get_bounding_area(rb, data[0], data[1]); +} + +/* This march along one render line in image space and + * get the next bounding area the line is crossing. */ +static LineartBoundingArea *lineart_bounding_area_next(LineartBoundingArea *this, + LineartRenderLine *rl, + double x, + double y, + double k, + int positive_x, + int positive_y, + double *next_x, + double *next_y) +{ + double rx, ry, ux, uy, lx, ly, bx, by; + double r1, r2; + LineartBoundingArea *ba; + + /* If we are marching towards the right. */ + if (positive_x > 0) { + rx = this->r; + ry = y + k * (rx - x); + + /* If we are marching towards the top. */ + if (positive_y > 0) { + uy = this->u; + ux = x + (uy - y) / k; + r1 = ratiod(rl->l->fbcoord[0], rl->r->fbcoord[0], rx); + r2 = ratiod(rl->l->fbcoord[0], rl->r->fbcoord[0], ux); + if (MIN2(r1, r2) > 1) { + return 0; + } + + /* We reached the right side before the top side. */ + if (r1 <= r2) { + LISTBASE_FOREACH (LinkData *, lip, &this->rp) { + ba = lip->data; + if (ba->u >= ry && ba->b < ry) { + *next_x = rx; + *next_y = ry; + return ba; + } + } + } + /* We reached the top side before the right side. */ + else { + LISTBASE_FOREACH (LinkData *, lip, &this->up) { + ba = lip->data; + if (ba->r >= ux && ba->l < ux) { + *next_x = ux; + *next_y = uy; + return ba; + } + } + } + } + /* If we are marching towards the bottom. */ + else if (positive_y < 0) { + by = this->b; + bx = x + (by - y) / k; + r1 = ratiod(rl->l->fbcoord[0], rl->r->fbcoord[0], rx); + r2 = ratiod(rl->l->fbcoord[0], rl->r->fbcoord[0], bx); + if (MIN2(r1, r2) > 1) { + return 0; + } + if (r1 <= r2) { + LISTBASE_FOREACH (LinkData *, lip, &this->rp) { + ba = lip->data; + if (ba->u >= ry && ba->b < ry) { + *next_x = rx; + *next_y = ry; + return ba; + } + } + } + else { + LISTBASE_FOREACH (LinkData *, lip, &this->bp) { + ba = lip->data; + if (ba->r >= bx && ba->l < bx) { + *next_x = bx; + *next_y = by; + return ba; + } + } + } + } + /* If the line is compeletely horizontal, in which Y diffence == 0. */ + else { + r1 = ratiod(rl->l->fbcoord[0], rl->r->fbcoord[0], this->r); + if (r1 > 1) { + return 0; + } + LISTBASE_FOREACH (LinkData *, lip, &this->rp) { + ba = lip->data; + if (ba->u >= y && ba->b < y) { + *next_x = this->r; + *next_y = y; + return ba; + } + } + } + } + + /* If we are marching towards the left. */ + else if (positive_x < 0) { + lx = this->l; + ly = y + k * (lx - x); + + /* If we are marching towards the top. */ + if (positive_y > 0) { + uy = this->u; + ux = x + (uy - y) / k; + r1 = ratiod(rl->l->fbcoord[0], rl->r->fbcoord[0], lx); + r2 = ratiod(rl->l->fbcoord[0], rl->r->fbcoord[0], ux); + if (MIN2(r1, r2) > 1) { + return 0; + } + if (r1 <= r2) { + LISTBASE_FOREACH (LinkData *, lip, &this->lp) { + ba = lip->data; + if (ba->u >= ly && ba->b < ly) { + *next_x = lx; + *next_y = ly; + return ba; + } + } + } + else { + LISTBASE_FOREACH (LinkData *, lip, &this->up) { + ba = lip->data; + if (ba->r >= ux && ba->l < ux) { + *next_x = ux; + *next_y = uy; + return ba; + } + } + } + } + + /* If we are marching towards the bottom. */ + else if (positive_y < 0) { + by = this->b; + bx = x + (by - y) / k; + r1 = ratiod(rl->l->fbcoord[0], rl->r->fbcoord[0], lx); + r2 = ratiod(rl->l->fbcoord[0], rl->r->fbcoord[0], bx); + if (MIN2(r1, r2) > 1) { + return 0; + } + if (r1 <= r2) { + LISTBASE_FOREACH (LinkData *, lip, &this->lp) { + ba = lip->data; + if (ba->u >= ly && ba->b < ly) { + *next_x = lx; + *next_y = ly; + return ba; + } + } + } + else { + LISTBASE_FOREACH (LinkData *, lip, &this->bp) { + ba = lip->data; + if (ba->r >= bx && ba->l < bx) { + *next_x = bx; + *next_y = by; + return ba; + } + } + } + } + /* Again, horizontal. */ + else { + r1 = ratiod(rl->l->fbcoord[0], rl->r->fbcoord[0], this->l); + if (r1 > 1) { + return 0; + } + LISTBASE_FOREACH (LinkData *, lip, &this->lp) { + ba = lip->data; + if (ba->u >= y && ba->b < y) { + *next_x = this->l; + *next_y = y; + return ba; + } + } + } + } + /* If the line is completely vertical, hence X difference == 0. */ + else { + if (positive_y > 0) { + r1 = ratiod(rl->l->fbcoord[1], rl->r->fbcoord[1], this->u); + if (r1 > 1) { + return 0; + } + LISTBASE_FOREACH (LinkData *, lip, &this->up) { + ba = lip->data; + if (ba->r > x && ba->l <= x) { + *next_x = x; + *next_y = this->u; + return ba; + } + } + } + else if (positive_y < 0) { + r1 = ratiod(rl->l->fbcoord[1], rl->r->fbcoord[1], this->b); + if (r1 > 1) { + return 0; + } + LISTBASE_FOREACH (LinkData *, lip, &this->bp) { + ba = lip->data; + if (ba->r > x && ba->l <= x) { + *next_x = x; + *next_y = this->b; + return ba; + } + } + } + else { + /* egment has no length. */ + return 0; + } + } + return 0; +} + +/* This is the entry point of all line art calculations. */ +int MOD_lineart_compute_feature_lines(Depsgraph *depsgraph, LineartGpencilModifierData *lmd) +{ + LineartRenderBuffer *rb; + Scene *scene = DEG_get_evaluated_scene(depsgraph); + int intersections_only = 0; /* Not used right now, but preserve for future. */ + + if (!scene->camera) { + return OPERATOR_CANCELLED; + } + + rb = lineart_create_render_buffer(scene, lmd); + + /* Triangle thread testing data size varies depending on the thread count. + * See definition of LineartRenderTriangleThread for details. */ + rb->triangle_size = lineart_triangle_size_get(scene, rb); + + /* This is used to limit calculation to a certain level to save time, lines who have higher + * occlusion levels will get ignored. */ + rb->max_occlusion_level = MAX2(lmd->level_start, lmd->level_end); + + /* Get view vector before loading geometries, because we detect feature lines there. */ + lineart_main_get_view_vector(rb); + lineart_main_load_geometries( + depsgraph, scene, scene->camera, rb, lmd->calculation_flags & LRT_ALLOW_DUPLI_OBJECTS); + + if (!rb->vertex_buffer_pointers.first) { + /* No geometry loaded, return early. */ + return OPERATOR_FINISHED; + } + + /* Initialize the bounding box acceleration structure, it's a lot like BVH in 3D. */ + lineart_main_bounding_area_make_initial(rb); + + /* We need to get cut into triangles that are crossing near/far plans, only this way can we get + * correct coordinates of those clipped lines. Done in two steps, + * setting clip_far==false for near plane. */ + lineart_main_cull_triangles(rb, false); + /* clip_far==true for far plane. */ + lineart_main_cull_triangles(rb, true); + + /* At this point triangle adjacent info pointers is no longer needed, free them. */ + lineart_main_free_adjacent_data(rb); + + /* Do the perspective division after clipping is done. */ + lineart_main_perspective_division(rb); + + /* Triangle intersections are done here during sequential adding of them. Only after this, + * triangles and lines are all linked with acceleration structure, and the 2D occlusion stage can + * do its job. */ + lineart_main_add_triangles(rb); + + /* Link lines to acceleration structure, this can only be done after perspective division, if we + * do it after triangles being added, the acceleration structure has already been subdivided, + * this way we do less list manipulations. */ + lineart_main_link_lines(rb); + + /* "intersection_only" is preserved for being called in a standalone fashion. + * If so the data will already be available at the stage. Otherwise we do the occlusion and + * chaining etc.*/ + + if (!intersections_only) { + + /* Occlusion is work-and-wait. This call will not return before work is completed. */ + lineart_main_occlusion_begin(rb); + + /* Chaining is all single threaded. See lineart_chain.c + * In this particular call, only lines that are geometrically connected (share the _exact_ same + * end point) will be chained together. */ + MOD_lineart_chain_feature_lines(rb); + + /* We are unable to take care of occlusion if we only connect end points, so here we do a spit, + * where the splitting point could be any cut in rl->segments. */ + MOD_lineart_chain_split_for_fixed_occlusion(rb); + + /* Then we connect chains based on the _proximity_ of their end points in geometry or image + * space, here's the place threashold value gets involved. */ + + /* If both chaining thresholds are zero, then we allow at least image space chaining to do a + * little bit of work so we don't end up in fragmented strokes. */ + float *t_image = &lmd->chaining_image_threshold; + float *t_geom = &lmd->chaining_geometry_threshold; + if (*t_image < FLT_EPSILON && *t_geom < FLT_EPSILON) { + *t_geom = 0.0f; + *t_image = 0.001f; + } + + /* do_geometry_space = true. */ + MOD_lineart_chain_connect(rb, true); + + /* After chaining, we need to clear flags so we can do another round in image space. */ + MOD_lineart_chain_clear_picked_flag(rb); + + /* do_geometry_space = false (it's image_space). */ + MOD_lineart_chain_connect(rb, false); + + /* Clear again so we don't confuse GPencil generation calls. */ + MOD_lineart_chain_clear_picked_flag(rb); + + /* This configuration ensures there won't be accidental lost of short unchained segments. */ + MOD_lineart_chain_discard_short(rb, MIN3(*t_image, *t_geom, 0.001f) - FLT_EPSILON); + + if (rb->angle_splitting_threshold > FLT_EPSILON) { + MOD_lineart_chain_split_angle(rb, rb->angle_splitting_threshold); + } + } + + if (G.debug_value == 4000) { + lineart_count_and_print_render_buffer_memory(rb); + } + + return OPERATOR_FINISHED; +} + +static int lineart_rb_line_types(LineartRenderBuffer *rb) +{ + int types = 0; + types |= rb->use_contour ? LRT_EDGE_FLAG_CONTOUR : 0; + types |= rb->use_crease ? LRT_EDGE_FLAG_CREASE : 0; + types |= rb->use_material ? LRT_EDGE_FLAG_MATERIAL : 0; + types |= rb->use_edge_marks ? LRT_EDGE_FLAG_EDGE_MARK : 0; + types |= rb->use_intersections ? LRT_EDGE_FLAG_INTERSECTION : 0; + return types; +} + +static void lineart_gpencil_generate(LineartRenderBuffer *rb, + Depsgraph *depsgraph, + Object *gpencil_object, + float (*gp_obmat_inverse)[4], + bGPDlayer *UNUSED(gpl), + bGPDframe *gpf, + int level_start, + int level_end, + int material_nr, + Object *source_object, + Collection *source_collection, + int types, + unsigned char transparency_flags, + unsigned char transparency_mask, + short thickness, + float opacity, + float pre_sample_length, + const char *source_vgname, + const char *vgname, + int modifier_flags) +{ + if (rb == NULL) { + if (G.debug_value == 4000) { + printf("NULL Lineart rb!\n"); + } + return; + } + + int stroke_count = 0; + int color_idx = 0; + + Object *orig_ob = NULL; + if (source_object) { + orig_ob = source_object->id.orig_id ? (Object *)source_object->id.orig_id : source_object; + } + + Collection *orig_col = NULL; + if (source_collection) { + orig_col = source_collection->id.orig_id ? (Collection *)source_collection->id.orig_id : + source_collection; + } + + /* (!orig_col && !orig_ob) means the whole scene is selected. */ + + float mat[4][4]; + unit_m4(mat); + + int enabled_types = lineart_rb_line_types(rb); + bool invert_input = modifier_flags & LRT_GPENCIL_INVERT_SOURCE_VGROUP; + bool match_output = modifier_flags & LRT_GPENCIL_MATCH_OUTPUT_VGROUP; + bool preserve_weight = modifier_flags & LRT_GPENCIL_SOFT_SELECTION; + + LISTBASE_FOREACH (LineartRenderLineChain *, rlc, &rb->chains) { + + if (rlc->picked) { + continue; + } + if (!(rlc->type & (types & enabled_types))) { + continue; + } + if (rlc->level > level_end || rlc->level < level_start) { + continue; + } + if (orig_ob && orig_ob != rlc->object_ref) { + continue; + } + if (orig_col && rlc->object_ref) { + if (!BKE_collection_has_object_recursive_instanced(orig_col, (Object *)rlc->object_ref)) { + continue; + } + } + if (transparency_flags & LRT_GPENCIL_TRANSPARENCY_ENABLE) { + if (transparency_flags & LRT_GPENCIL_TRANSPARENCY_MATCH) { + if (rlc->transparency_mask != transparency_mask) { + continue; + } + } + else { + if (!(rlc->transparency_mask & transparency_mask)) { + continue; + } + } + } + + /* Preserved: If we ever do async generation, this picked flag should be set here. */ + /* rlc->picked = 1;. */ + + int array_idx = 0; + int count = MOD_lineart_chain_count(rlc); + bGPDstroke *gps = BKE_gpencil_stroke_add(gpf, color_idx, count, thickness, false); + + float *stroke_data = MEM_callocN(sizeof(float) * count * GP_PRIM_DATABUF_SIZE, + "line art add stroke"); + + LISTBASE_FOREACH (LineartRenderLineChainItem *, rlci, &rlc->chain) { + stroke_data[array_idx] = rlci->gpos[0]; + stroke_data[array_idx + 1] = rlci->gpos[1]; + stroke_data[array_idx + 2] = rlci->gpos[2]; + mul_m4_v3(gp_obmat_inverse, &stroke_data[array_idx]); + stroke_data[array_idx + 3] = 1; /* thickness. */ + stroke_data[array_idx + 4] = opacity; /* hardness?. */ + array_idx += 5; + } + + BKE_gpencil_stroke_add_points(gps, stroke_data, count, mat); + BKE_gpencil_dvert_ensure(gps); + gps->mat_nr = material_nr; + + MEM_freeN(stroke_data); + + if (source_vgname && vgname) { + Object *eval_ob = DEG_get_evaluated_object(depsgraph, rlc->object_ref); + int gpdg = -1; + if ((match_output || (gpdg = BKE_object_defgroup_name_index(gpencil_object, vgname)) >= 0)) { + if (eval_ob && eval_ob->type == OB_MESH) { + int dindex = 0; + Mesh *me = (Mesh *)eval_ob->data; + if (me->dvert) { + LISTBASE_FOREACH (bDeformGroup *, db, &eval_ob->defbase) { + if ((!source_vgname) || strstr(db->name, source_vgname) == db->name) { + if (match_output) { + gpdg = BKE_object_defgroup_name_index(gpencil_object, db->name); + if (gpdg < 0) { + continue; + } + } + int sindex = 0, vindex; + LISTBASE_FOREACH (LineartRenderLineChainItem *, rlci, &rlc->chain) { + vindex = rlci->index; + if (vindex >= me->totvert) { + break; + } + MDeformWeight *mdw = BKE_defvert_ensure_index(&me->dvert[vindex], dindex); + MDeformWeight *gdw = BKE_defvert_ensure_index(&gps->dvert[sindex], gpdg); + if (preserve_weight) { + float use_weight = mdw->weight; + if (invert_input) { + use_weight = 1 - use_weight; + } + gdw->weight = MAX2(use_weight, gdw->weight); + } + else { + if (mdw->weight > 0.999f) { + gdw->weight = 1.0f; + } + } + sindex++; + } + } + dindex++; + } + } + } + } + } + + if (pre_sample_length > 0.0001) { + BKE_gpencil_stroke_sample(gpencil_object->data, gps, pre_sample_length, false); + } + if (G.debug_value == 4000) { + BKE_gpencil_stroke_set_random_color(gps); + } + BKE_gpencil_stroke_geometry_update(gpencil_object->data, gps); + stroke_count++; + } + + if (G.debug_value == 4000) { + printf("LRT: Generated %d strokes.\n", stroke_count); + } +} + +/* Wrapper for external calls. */ +void MOD_lineart_gpencil_generate(LineartRenderBuffer *rb, + Depsgraph *depsgraph, + Object *ob, + bGPDlayer *gpl, + bGPDframe *gpf, + char source_type, + void *source_reference, + int level_start, + int level_end, + int mat_nr, + short line_types, + unsigned char transparency_flags, + unsigned char transparency_mask, + short thickness, + float opacity, + float pre_sample_length, + const char *source_vgname, + const char *vgname, + int modifier_flags) +{ + + if (!gpl || !gpf || !ob) { + return; + } + + Object *source_object = NULL; + Collection *source_collection = NULL; + short use_types = 0; + if (source_type == LRT_SOURCE_OBJECT) { + if (!source_reference) { + return; + } + source_object = (Object *)source_reference; + /* Note that intersection lines will only be in collection. */ + use_types = line_types & (~LRT_EDGE_FLAG_INTERSECTION); + } + else if (source_type == LRT_SOURCE_COLLECTION) { + if (!source_reference) { + return; + } + source_collection = (Collection *)source_reference; + use_types = line_types; + } + else { + /* Whole scene. */ + use_types = line_types; + } + float gp_obmat_inverse[4][4]; + invert_m4_m4(gp_obmat_inverse, ob->obmat); + lineart_gpencil_generate(rb, + depsgraph, + ob, + gp_obmat_inverse, + gpl, + gpf, + level_start, + level_end, + mat_nr, + source_object, + source_collection, + use_types, + transparency_flags, + transparency_mask, + thickness, + opacity, + pre_sample_length, + source_vgname, + vgname, + modifier_flags); +} diff --git a/source/blender/gpencil_modifiers/intern/lineart/lineart_intern.h b/source/blender/gpencil_modifiers/intern/lineart/lineart_intern.h new file mode 100644 index 00000000000..99cfa06b2fe --- /dev/null +++ b/source/blender/gpencil_modifiers/intern/lineart/lineart_intern.h @@ -0,0 +1,113 @@ +/* + * 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) 2019 Blender Foundation. + * All rights reserved. + */ + +/** \file + * \ingroup editors + */ + +#pragma once + +#include "BLI_linklist.h" +#include "BLI_listbase.h" +#include "BLI_math.h" +#include "BLI_threads.h" + +#include "DNA_lineart_types.h" + +#include <math.h> +#include <string.h> + +struct LineartStaticMemPool; +struct LineartStaticMemPoolNode; +struct LineartRenderLine; +struct LineartRenderBuffer; + +void *lineart_list_append_pointer_pool(ListBase *h, struct LineartStaticMemPool *smp, void *data); +void *lineart_list_append_pointer_pool_sized(ListBase *h, + struct LineartStaticMemPool *smp, + void *data, + int size); +void *list_push_pointer_static(ListBase *h, struct LineartStaticMemPool *smp, void *p); +void *list_push_pointer_static_sized(ListBase *h, + struct LineartStaticMemPool *smp, + void *p, + int size); + +void *lineart_list_pop_pointer_no_free(ListBase *h); +void lineart_list_remove_pointer_item_no_free(ListBase *h, LinkData *lip); + +struct LineartStaticMemPoolNode *lineart_mem_new_static_pool(struct LineartStaticMemPool *smp, + size_t size); +void *lineart_mem_aquire(struct LineartStaticMemPool *smp, size_t size); +void *lineart_mem_aquire_thread(struct LineartStaticMemPool *smp, size_t size); +void lineart_mem_destroy(struct LineartStaticMemPool *smp); + +void lineart_prepend_line_direct(struct LineartRenderLine **first, void *node); +void lineart_prepend_pool(LinkNode **first, struct LineartStaticMemPool *smp, void *link); + +void lineart_matrix_ortho_44d(double (*mProjection)[4], + double xMin, + double xMax, + double yMin, + double yMax, + double zMin, + double zMax); +void lineart_matrix_perspective_44d( + double (*mProjection)[4], double fFov_rad, double fAspect, double zMin, double zMax); + +int lineart_count_intersection_segment_count(struct LineartRenderBuffer *rb); + +void lineart_count_and_print_render_buffer_memory(struct LineartRenderBuffer *rb); + +#define LRT_ITER_ALL_LINES_BEGIN \ + LineartRenderLine *rl, *next_rl, **current_list; \ + rl = rb->contours; \ + for (current_list = &rb->contours; rl; rl = next_rl) { \ + next_rl = rl->next; + +#define LRT_ITER_ALL_LINES_NEXT \ + while (!next_rl) { \ + if (current_list == &rb->contours) { \ + current_list = &rb->crease_lines; \ + } \ + else if (current_list == &rb->crease_lines) { \ + current_list = &rb->material_lines; \ + } \ + else if (current_list == &rb->material_lines) { \ + current_list = &rb->edge_marks; \ + } \ + else if (current_list == &rb->edge_marks) { \ + current_list = &rb->intersection_lines; \ + } \ + else { \ + break; \ + } \ + next_rl = *current_list; \ + } + +#define LRT_ITER_ALL_LINES_END \ + LRT_ITER_ALL_LINES_NEXT \ + } + +#define LRT_BOUND_AREA_CROSSES(b1, b2) \ + ((b1)[0] < (b2)[1] && (b1)[1] > (b2)[0] && (b1)[3] < (b2)[2] && (b1)[2] > (b2)[3]) + +/* Initial bounding area row/column count, setting 4 is the simplest way algorithm could function + * efficiently. */ +#define LRT_BA_ROWS 4 diff --git a/source/blender/gpencil_modifiers/intern/lineart/lineart_ops.c b/source/blender/gpencil_modifiers/intern/lineart/lineart_ops.c new file mode 100644 index 00000000000..d5bd232cc9c --- /dev/null +++ b/source/blender/gpencil_modifiers/intern/lineart/lineart_ops.c @@ -0,0 +1,439 @@ +/* + * 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) 2019 Blender Foundation. + * All rights reserved. + */ + +/** \file + * \ingroup editors + */ + +#include <stdlib.h> + +#include "MEM_guardedalloc.h" + +#include "BKE_collection.h" +#include "BKE_context.h" +#include "BKE_global.h" +#include "BKE_gpencil.h" +#include "BKE_report.h" +#include "BKE_scene.h" + +#include "DEG_depsgraph_query.h" + +#include "BLI_utildefines.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "DNA_gpencil_modifier_types.h" +#include "DNA_gpencil_types.h" +#include "DNA_scene_types.h" + +#include "UI_resources.h" + +#include "MOD_gpencil_lineart.h" +#include "MOD_lineart.h" + +#include "lineart_intern.h" + +static void clear_strokes(Object *ob, GpencilModifierData *md, int frame) +{ + if (md->type != eGpencilModifierType_Lineart) { + return; + } + LineartGpencilModifierData *lmd = (LineartGpencilModifierData *)md; + bGPdata *gpd = ob->data; + + bGPDlayer *gpl = BKE_gpencil_layer_get_by_name(gpd, lmd->target_layer, 1); + if (!gpl) { + return; + } + bGPDframe *gpf = BKE_gpencil_layer_frame_find(gpl, frame); + + if (!gpf) { + /* No greasepencil frame found. */ + return; + } + + BKE_gpencil_layer_frame_delete(gpl, gpf); +} + +static bool bake_strokes(Object *ob, Depsgraph *dg, GpencilModifierData *md, int frame) +{ + LineartGpencilModifierData *lmd = (LineartGpencilModifierData *)md; + bGPdata *gpd = ob->data; + + bGPDlayer *gpl = BKE_gpencil_layer_get_by_name(gpd, lmd->target_layer, 1); + if (!gpl) { + return false; + } + bool only_use_existing_gp_frames = false; + bGPDframe *gpf = (only_use_existing_gp_frames ? + BKE_gpencil_layer_frame_find(gpl, frame) : + BKE_gpencil_layer_frame_get(gpl, frame, GP_GETFRAME_ADD_NEW)); + + if (!gpf) { + /* No greasepencil frame created or found. */ + return false; + } + + MOD_lineart_compute_feature_lines(dg, lmd); + + MOD_lineart_gpencil_generate( + lmd->render_buffer, + dg, + ob, + gpl, + gpf, + lmd->source_type, + lmd->source_type == LRT_SOURCE_OBJECT ? (void *)lmd->source_object : + (void *)lmd->source_collection, + lmd->level_start, + lmd->use_multiple_levels ? lmd->level_end : lmd->level_start, + lmd->target_material ? BKE_gpencil_object_material_index_get(ob, lmd->target_material) : 0, + lmd->line_types, + lmd->transparency_flags, + lmd->transparency_mask, + lmd->thickness, + lmd->opacity, + lmd->pre_sample_length, + lmd->source_vertex_group, + lmd->vgname, + lmd->flags); + + MOD_lineart_destroy_render_data(lmd); + + return true; +} + +typedef struct LineartBakeJob { + wmWindowManager *wm; + void *owner; + short *stop, *do_update; + float *progress; + + /* C or ob must have one != NULL. */ + bContext *C; + LinkNode *objects; + Scene *scene; + Depsgraph *dg; + int frame; + int frame_begin; + int frame_end; + int frame_orig; + int frame_increment; + bool overwrite_frames; +} LineartBakeJob; + +static bool lineart_gpencil_bake_single_target(LineartBakeJob *bj, Object *ob, int frame) +{ + bool touched = false; + if (ob->type != OB_GPENCIL || G.is_break) { + return false; + } + + if (bj->overwrite_frames) { + LISTBASE_FOREACH (GpencilModifierData *, md, &ob->greasepencil_modifiers) { + clear_strokes(ob, md, frame); + } + } + + LISTBASE_FOREACH (GpencilModifierData *, md, &ob->greasepencil_modifiers) { + if (bake_strokes(ob, bj->dg, md, frame)) { + touched = true; + } + } + + return touched; +} + +static void lineart_gpencil_guard_modifiers(LineartBakeJob *bj) +{ + for (LinkNode *l = bj->objects; l; l = l->next) { + Object *ob = l->link; + LISTBASE_FOREACH (GpencilModifierData *, md, &ob->greasepencil_modifiers) { + if (md->type == eGpencilModifierType_Lineart) { + LineartGpencilModifierData *lmd = (LineartGpencilModifierData *)md; + lmd->flags |= LRT_GPENCIL_IS_BAKED; + } + } + } +} + +static void lineart_gpencil_bake_startjob(void *customdata, + short *stop, + short *do_update, + float *progress) +{ + LineartBakeJob *bj = (LineartBakeJob *)customdata; + bj->stop = stop; + bj->do_update = do_update; + bj->progress = progress; + + lineart_gpencil_guard_modifiers(bj); + + for (int frame = bj->frame_begin; frame <= bj->frame_end; frame += bj->frame_increment) { + + if (G.is_break) { + G.is_break = false; + break; + } + + BKE_scene_frame_set(bj->scene, frame); + BKE_scene_graph_update_for_newframe(bj->dg); + + for (LinkNode *l = bj->objects; l; l = l->next) { + Object *ob = l->link; + if (lineart_gpencil_bake_single_target(bj, ob, frame)) { + DEG_id_tag_update((struct ID *)ob->data, ID_RECALC_GEOMETRY); + WM_event_add_notifier(bj->C, NC_GPENCIL | ND_DATA | NA_EDITED, ob); + } + } + + /* Update and refresh the progress bar. */ + *bj->progress = (float)(frame - bj->frame_begin) / (bj->frame_end - bj->frame_begin); + *bj->do_update = true; + } + + /* This need to be reset manually. */ + G.is_break = false; + + /* Restore original frame. */ + BKE_scene_frame_set(bj->scene, bj->frame_orig); + BKE_scene_graph_update_for_newframe(bj->dg); +} + +static void lineart_gpencil_bake_endjob(void *customdata) +{ + LineartBakeJob *bj = customdata; + + WM_set_locked_interface(CTX_wm_manager(bj->C), false); + + WM_main_add_notifier(NC_SCENE | ND_FRAME, bj->scene); + + for (LinkNode *l = bj->objects; l; l = l->next) { + WM_main_add_notifier(NC_GPENCIL | ND_DATA | NA_EDITED, (Object *)l->link); + } + + BLI_linklist_free(bj->objects, NULL); +} + +static int lineart_gpencil_bake_common(bContext *C, + wmOperator *op, + bool bake_all_targets, + bool do_background) +{ + LineartBakeJob *bj = MEM_callocN(sizeof(LineartBakeJob), "LineartBakeJob"); + + if (!bake_all_targets) { + Object *ob = CTX_data_active_object(C); + if (!ob || ob->type != OB_GPENCIL) { + WM_report(RPT_ERROR, "No active object or active object isn't a GPencil object."); + return OPERATOR_FINISHED; + } + BLI_linklist_prepend(&bj->objects, ob); + } + else { + /* CTX_DATA_BEGIN is not available for interating in objects while using the Job system. */ + CTX_DATA_BEGIN (C, Object *, ob, visible_objects) { + if (ob->type == OB_GPENCIL) { + LISTBASE_FOREACH (GpencilModifierData *, md, &ob->greasepencil_modifiers) { + if (md->type == eGpencilModifierType_Lineart) { + BLI_linklist_prepend(&bj->objects, ob); + } + } + } + } + CTX_DATA_END; + } + bj->C = C; + Scene *scene = CTX_data_scene(C); + bj->scene = scene; + bj->dg = CTX_data_depsgraph_pointer(C); + bj->frame_begin = scene->r.sfra; + bj->frame_end = scene->r.efra; + bj->frame_orig = scene->r.cfra; + bj->frame_increment = scene->r.frame_step; + bj->overwrite_frames = true; + + if (do_background) { + wmJob *wm_job = WM_jobs_get(CTX_wm_manager(C), + CTX_wm_window(C), + scene, + "Line Art", + WM_JOB_PROGRESS, + WM_JOB_TYPE_LINEART); + + WM_jobs_customdata_set(wm_job, bj, MEM_freeN); + WM_jobs_timer(wm_job, 0.1, NC_GPENCIL | ND_DATA | NA_EDITED, NC_GPENCIL | ND_DATA | NA_EDITED); + WM_jobs_callbacks( + wm_job, lineart_gpencil_bake_startjob, NULL, NULL, lineart_gpencil_bake_endjob); + + WM_set_locked_interface(CTX_wm_manager(C), true); + + WM_jobs_start(CTX_wm_manager(C), wm_job); + + WM_event_add_modal_handler(C, op); + + return OPERATOR_RUNNING_MODAL; + } + + float pseduo_progress; + short pseduo_do_update; + lineart_gpencil_bake_startjob(bj, NULL, &pseduo_do_update, &pseduo_progress); + + BLI_linklist_free(bj->objects, NULL); + MEM_freeN(bj); + + return OPERATOR_FINISHED; +} + +static int lineart_gpencil_bake_strokes_all_invoke(bContext *C, + wmOperator *op, + const wmEvent *UNUSED(event)) +{ + return lineart_gpencil_bake_common(C, op, true, true); +} +static int lineart_gpencil_bake_strokes_all_exec(bContext *C, wmOperator *op) +{ + return lineart_gpencil_bake_common(C, op, true, false); +} +static int lineart_gpencil_bake_strokes_invoke(bContext *C, + wmOperator *op, + const wmEvent *UNUSED(event)) +{ + return lineart_gpencil_bake_common(C, op, false, true); +} +static int lineart_gpencil_bake_strokes_exec(bContext *C, wmOperator *op) +{ + return lineart_gpencil_bake_common(C, op, false, false); + + return OPERATOR_FINISHED; +} +static int lineart_gpencil_bake_strokes_commom_modal(bContext *C, + wmOperator *op, + const wmEvent *UNUSED(event)) +{ + Scene *scene = (Scene *)op->customdata; + + /* no running blender, remove handler and pass through. */ + if (0 == WM_jobs_test(CTX_wm_manager(C), scene, WM_JOB_TYPE_LINEART)) { + return OPERATOR_FINISHED | OPERATOR_PASS_THROUGH; + } + + return OPERATOR_PASS_THROUGH; +} + +static void lineart_gpencil_clear_strokes_exec_common(Object *ob) +{ + if (ob->type != OB_GPENCIL) { + return; + } + LISTBASE_FOREACH (GpencilModifierData *, md, &ob->greasepencil_modifiers) { + if (md->type != eGpencilModifierType_Lineart) { + continue; + } + LineartGpencilModifierData *lmd = (LineartGpencilModifierData *)md; + bGPdata *gpd = ob->data; + + bGPDlayer *gpl = BKE_gpencil_layer_get_by_name(gpd, lmd->target_layer, 1); + if (!gpl) { + continue; + } + BKE_gpencil_free_frames(gpl); + + md->mode |= eGpencilModifierMode_Realtime | eGpencilModifierMode_Render; + + lmd->flags &= (~LRT_GPENCIL_IS_BAKED); + } + DEG_id_tag_update((struct ID *)ob->data, ID_RECALC_GEOMETRY); +} + +static int lineart_gpencil_clear_strokes_exec(bContext *C, wmOperator *UNUSED(op)) +{ + Object *ob = CTX_data_active_object(C); + + lineart_gpencil_clear_strokes_exec_common(ob); + + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, ob); + + return OPERATOR_FINISHED; +} +static int lineart_gpencil_clear_strokes_all_exec(bContext *C, wmOperator *op) +{ + CTX_DATA_BEGIN (C, Object *, ob, visible_objects) { + lineart_gpencil_clear_strokes_exec_common(ob); + WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, ob); + } + CTX_DATA_END; + + BKE_report(op->reports, RPT_INFO, "All line art objects are now cleared."); + + return OPERATOR_FINISHED; +} + +/* Bake all line art modifiers on the current object. */ +void OBJECT_OT_lineart_bake_strokes(wmOperatorType *ot) +{ + ot->name = "Bake Line Art"; + ot->description = "Bake Line Art for current GPencil object"; + ot->idname = "OBJECT_OT_lineart_bake_strokes"; + + ot->invoke = lineart_gpencil_bake_strokes_invoke; + ot->exec = lineart_gpencil_bake_strokes_exec; + ot->modal = lineart_gpencil_bake_strokes_commom_modal; +} + +/* Bake all lineart objects in the scene. */ +void OBJECT_OT_lineart_bake_strokes_all(wmOperatorType *ot) +{ + ot->name = "Bake Line Art (All)"; + ot->description = "Bake all GPencil objects who has at least one Line Art modifier"; + ot->idname = "OBJECT_OT_lineart_bake_strokes_all"; + + ot->invoke = lineart_gpencil_bake_strokes_all_invoke; + ot->exec = lineart_gpencil_bake_strokes_all_exec; + ot->modal = lineart_gpencil_bake_strokes_commom_modal; +} + +/* clear all line art modifiers on the current object. */ +void OBJECT_OT_lineart_clear(wmOperatorType *ot) +{ + ot->name = "Clear Baked Line Art"; + ot->description = "Clear all strokes in current GPencil obejct"; + ot->idname = "OBJECT_OT_lineart_clear"; + + ot->exec = lineart_gpencil_clear_strokes_exec; +} + +/* clear all lineart objects in the scene. */ +void OBJECT_OT_lineart_clear_all(wmOperatorType *ot) +{ + ot->name = "Clear Baked Line Art (All)"; + ot->description = "Clear all strokes in all GPencil obejcts who has a Line Art modifier"; + ot->idname = "OBJECT_OT_lineart_clear_all"; + + ot->exec = lineart_gpencil_clear_strokes_all_exec; +} + +void WM_operatortypes_lineart(void) +{ + WM_operatortype_append(OBJECT_OT_lineart_bake_strokes); + WM_operatortype_append(OBJECT_OT_lineart_bake_strokes_all); + WM_operatortype_append(OBJECT_OT_lineart_clear); + WM_operatortype_append(OBJECT_OT_lineart_clear_all); +} diff --git a/source/blender/gpencil_modifiers/intern/lineart/lineart_util.c b/source/blender/gpencil_modifiers/intern/lineart/lineart_util.c new file mode 100644 index 00000000000..0d145042e08 --- /dev/null +++ b/source/blender/gpencil_modifiers/intern/lineart/lineart_util.c @@ -0,0 +1,233 @@ +/* + * 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) 2019 Blender Foundation. + * All rights reserved. + */ + +/** \file + * \ingroup editors + */ + +#include <stdio.h> +#include <stdlib.h> +/* #include <time.h> */ +#include "MEM_guardedalloc.h" +#include "MOD_lineart.h" +#include <math.h> + +#include "lineart_intern.h" + +/* Line art memory and list helper */ + +void *lineart_list_append_pointer_pool(ListBase *h, LineartStaticMemPool *smp, void *data) +{ + LinkData *lip; + if (h == NULL) { + return 0; + } + lip = lineart_mem_aquire(smp, sizeof(LinkData)); + lip->data = data; + BLI_addtail(h, lip); + return lip; +} +void *lineart_list_append_pointer_pool_sized(ListBase *h, + LineartStaticMemPool *smp, + void *data, + int size) +{ + LinkData *lip; + if (h == NULL) { + return 0; + } + lip = lineart_mem_aquire(smp, size); + lip->data = data; + BLI_addtail(h, lip); + return lip; +} + +void *lineart_list_pop_pointer_no_free(ListBase *h) +{ + LinkData *lip; + void *rev = 0; + if (h == NULL) { + return 0; + } + lip = BLI_pophead(h); + rev = lip ? lip->data : 0; + return rev; +} +void lineart_list_remove_pointer_item_no_free(ListBase *h, LinkData *lip) +{ + BLI_remlink(h, (void *)lip); +} + +LineartStaticMemPoolNode *lineart_mem_new_static_pool(LineartStaticMemPool *smp, size_t size) +{ + size_t set_size = size; + if (set_size < LRT_MEMORY_POOL_64MB) { + set_size = LRT_MEMORY_POOL_64MB; /* Prevent too many small allocations. */ + } + size_t total_size = size + sizeof(LineartStaticMemPoolNode); + LineartStaticMemPoolNode *smpn = MEM_callocN(total_size, "mempool"); + smpn->size = total_size; + smpn->used_byte = sizeof(LineartStaticMemPoolNode); + BLI_addhead(&smp->pools, smpn); + return smpn; +} +void *lineart_mem_aquire(LineartStaticMemPool *smp, size_t size) +{ + LineartStaticMemPoolNode *smpn = smp->pools.first; + void *ret; + + if (!smpn || (smpn->used_byte + size) > smpn->size) { + smpn = lineart_mem_new_static_pool(smp, size); + } + + ret = ((unsigned char *)smpn) + smpn->used_byte; + + smpn->used_byte += size; + + return ret; +} +void *lineart_mem_aquire_thread(LineartStaticMemPool *smp, size_t size) +{ + void *ret; + + BLI_spin_lock(&smp->lock_mem); + + LineartStaticMemPoolNode *smpn = smp->pools.first; + + if (!smpn || (smpn->used_byte + size) > smpn->size) { + smpn = lineart_mem_new_static_pool(smp, size); + } + + ret = ((unsigned char *)smpn) + smpn->used_byte; + + smpn->used_byte += size; + + BLI_spin_unlock(&smp->lock_mem); + + return ret; +} +void lineart_mem_destroy(LineartStaticMemPool *smp) +{ + LineartStaticMemPoolNode *smpn; + while ((smpn = BLI_pophead(&smp->pools)) != NULL) { + MEM_freeN(smpn); + } +} + +void lineart_prepend_line_direct(LineartRenderLine **first, void *node) +{ + LineartRenderLine *ln = (LineartRenderLine *)node; + ln->next = (*first); + (*first) = ln; +} + +void lineart_prepend_pool(LinkNode **first, LineartStaticMemPool *smp, void *link) +{ + LinkNode *ln = lineart_mem_aquire_thread(smp, sizeof(LinkNode)); + ln->next = (*first); + ln->link = link; + (*first) = ln; +} + +/* =======================================================================[str] */ + +void lineart_matrix_perspective_44d( + double (*mProjection)[4], double fFov_rad, double fAspect, double zMin, double zMax) +{ + double yMax; + double yMin; + double xMin; + double xMax; + + if (fAspect < 1) { + yMax = zMin * tan(fFov_rad * 0.5f); + yMin = -yMax; + xMin = yMin * fAspect; + xMax = -xMin; + } + else { + xMax = zMin * tan(fFov_rad * 0.5f); + xMin = -xMax; + yMin = xMin / fAspect; + yMax = -yMin; + } + + unit_m4_db(mProjection); + + mProjection[0][0] = (2.0f * zMin) / (xMax - xMin); + mProjection[1][1] = (2.0f * zMin) / (yMax - yMin); + mProjection[2][0] = (xMax + xMin) / (xMax - xMin); + mProjection[2][1] = (yMax + yMin) / (yMax - yMin); + mProjection[2][2] = -((zMax + zMin) / (zMax - zMin)); + mProjection[2][3] = -1.0f; + mProjection[3][2] = -((2.0f * (zMax * zMin)) / (zMax - zMin)); + mProjection[3][3] = 0.0f; +} +void lineart_matrix_ortho_44d(double (*mProjection)[4], + double xMin, + double xMax, + double yMin, + double yMax, + double zMin, + double zMax) +{ + unit_m4_db(mProjection); + + mProjection[0][0] = 2.0f / (xMax - xMin); + mProjection[1][1] = 2.0f / (yMax - yMin); + mProjection[2][2] = -2.0f / (zMax - zMin); + mProjection[3][0] = -((xMax + xMin) / (xMax - xMin)); + mProjection[3][1] = -((yMax + yMin) / (yMax - yMin)); + mProjection[3][2] = -((zMax + zMin) / (zMax - zMin)); + mProjection[3][3] = 1.0f; +} + +void lineart_count_and_print_render_buffer_memory(LineartRenderBuffer *rb) +{ + size_t total = 0; + size_t sum_this = 0; + size_t count_this = 0; + + LISTBASE_FOREACH (LineartStaticMemPoolNode *, smpn, &rb->render_data_pool.pools) { + count_this++; + sum_this += LRT_MEMORY_POOL_64MB; + } + printf("LANPR Memory allocated %lu Standalone nodes, total %lu Bytes.\n", count_this, sum_this); + total += sum_this; + sum_this = 0; + count_this = 0; + + LISTBASE_FOREACH (LineartRenderElementLinkNode *, reln, &rb->line_buffer_pointers) { + count_this++; + sum_this += reln->element_count * sizeof(LineartRenderLine); + } + printf(" allocated %lu edge blocks, total %lu Bytes.\n", count_this, sum_this); + total += sum_this; + sum_this = 0; + count_this = 0; + + LISTBASE_FOREACH (LineartRenderElementLinkNode *, reln, &rb->triangle_buffer_pointers) { + count_this++; + sum_this += reln->element_count * rb->triangle_size; + } + printf(" allocated %lu triangle blocks, total %lu Bytes.\n", count_this, sum_this); + total += sum_this; + sum_this = 0; + count_this = 0; +} diff --git a/source/blender/makesdna/DNA_collection_types.h b/source/blender/makesdna/DNA_collection_types.h index 0a80e00d456..f5fcb0b190e 100644 --- a/source/blender/makesdna/DNA_collection_types.h +++ b/source/blender/makesdna/DNA_collection_types.h @@ -46,6 +46,14 @@ typedef struct CollectionChild { struct Collection *collection; } CollectionChild; +enum CollectionFeatureLine_Usage { + COLLECTION_LRT_INCLUDE = 0, + COLLECTION_LRT_OCCLUSION_ONLY = (1 << 0), + COLLECTION_LRT_EXCLUDE = (1 << 1), + COLLECTION_LRT_INTERSECTION_ONLY = (1 << 2), + COLLECTION_LRT_NO_INTERSECTION = (1 << 3), +}; + typedef struct Collection { ID id; @@ -63,8 +71,10 @@ typedef struct Collection { /* Runtime-only, always cleared on file load. */ short tag; + /** Line Art engine specific */ + short lineart_usage; + int16_t color_tag; - char _pad[2]; /* Runtime. Cache of objects in this collection and all its * children. This is created on demand when e.g. some physics @@ -72,6 +82,9 @@ typedef struct Collection { * collections due to memory usage reasons. */ ListBase object_cache; + /* Need this for line art sub-collection selections. */ + ListBase object_cache_instanced; + /* Runtime. List of collections that are a parent of this * datablock. */ ListBase parents; @@ -89,6 +102,7 @@ enum { COLLECTION_RESTRICT_RENDER = (1 << 3), /* Disable in renders. */ COLLECTION_HAS_OBJECT_CACHE = (1 << 4), /* Runtime: object_cache is populated. */ COLLECTION_IS_MASTER = (1 << 5), /* Is master collection embedded in the scene. */ + COLLECTION_HAS_OBJECT_CACHE_INSTANCED = (1 << 6), /* for object_cache_instanced. */ }; /* Collection->tag */ diff --git a/source/blender/makesdna/DNA_gpencil_modifier_defaults.h b/source/blender/makesdna/DNA_gpencil_modifier_defaults.h index 11d4ee1b2cd..baf2b2414fa 100644 --- a/source/blender/makesdna/DNA_gpencil_modifier_defaults.h +++ b/source/blender/makesdna/DNA_gpencil_modifier_defaults.h @@ -283,4 +283,17 @@ .colorband = NULL, \ } +#define _DNA_DEFAULT_LineartGpencilModifierData \ + { \ + .line_types = LRT_EDGE_FLAG_ALL_TYPE, \ + .thickness = 25, \ + .opacity = 1.0f, \ + .flags = LRT_GPENCIL_MATCH_OUTPUT_VGROUP | LRT_GPENCIL_SOFT_SELECTION, \ + .crease_threshold = DEG2RAD(140.0f), \ + .calculation_flags = LRT_ALLOW_DUPLI_OBJECTS | LRT_REMOVE_DOUBLES | LRT_ALLOW_OVERLAPPING_EDGES | LRT_ALLOW_CLIPPING_BOUNDARIES, \ + .angle_splitting_threshold = DEG2RAD(60.0f), \ + .chaining_geometry_threshold = 0.001f, \ + .chaining_image_threshold = 0.001f, \ + } + /* clang-format off */ diff --git a/source/blender/makesdna/DNA_gpencil_modifier_types.h b/source/blender/makesdna/DNA_gpencil_modifier_types.h index b2d62e0a5f6..a9262bf7ca4 100644 --- a/source/blender/makesdna/DNA_gpencil_modifier_types.h +++ b/source/blender/makesdna/DNA_gpencil_modifier_types.h @@ -53,6 +53,7 @@ typedef enum GpencilModifierType { eGpencilModifierType_Time = 16, eGpencilModifierType_Multiply = 17, eGpencilModifierType_Texture = 18, + eGpencilModifierType_Lineart = 19, /* Keep last. */ NUM_GREASEPENCIL_MODIFIER_TYPES, } GpencilModifierType; @@ -809,6 +810,78 @@ typedef enum eTextureGpencil_Mode { STROKE_AND_FILL = 2, } eTextureGpencil_Mode; +typedef enum eLineartGpencilModifierSource { + LRT_SOURCE_COLLECTION = 0, + LRT_SOURCE_OBJECT = 1, + LRT_SOURCE_SCENE = 2, +} eLineartGpencilModifierSource; + +typedef enum eLineArtGPencilModifierFlags { + LRT_GPENCIL_INVERT_SOURCE_VGROUP = (1 << 0), + LRT_GPENCIL_MATCH_OUTPUT_VGROUP = (1 << 1), + LRT_GPENCIL_SOFT_SELECTION = (1 << 2), + LRT_GPENCIL_IS_BAKED = (1 << 3), +} eLineArtGPencilModifierFlags; + +typedef enum eLineartGpencilTransparencyFlags { + LRT_GPENCIL_TRANSPARENCY_ENABLE = (1 << 0), + /** Set to true means using "and" instead of "or" logic on mask bits. */ + LRT_GPENCIL_TRANSPARENCY_MATCH = (1 << 1), +} eLineartGpencilTransparencyFlags; + +typedef struct LineartGpencilModifierData { + GpencilModifierData modifier; + + short line_types; /* line type enable flags, bits in eLineartEdgeFlag */ + + char source_type; /* Object or Collection, from eLineartGpencilModifierSource */ + + char use_multiple_levels; + short level_start; + short level_end; + + struct Object *source_object; + struct Collection *source_collection; + + struct Material *target_material; + char target_layer[64]; + + /** These two variables are to pass on vertex group information from mesh to strokes. + * vgname specifies which vertex groups our strokes from source_vertex_group will go to. */ + char source_vertex_group[64]; + char vgname[64]; + + float opacity; + short thickness; + + unsigned char transparency_flags; /* eLineartGpencilTransparencyFlags */ + unsigned char transparency_mask; + + /** 0-1 range for cosine angle */ + float crease_threshold; + + /** 0-PI angle, for splitting strokes at sharp points */ + float angle_splitting_threshold; + + /* CPU mode */ + float chaining_geometry_threshold; + float chaining_image_threshold; + + float pre_sample_length; + + /* Ported from SceneLineArt flags. */ + int calculation_flags; + + /* Additional Switches. */ + int flags; + + int _pad; + + /* Runtime only. */ + void *render_buffer; + +} LineartGpencilModifierData; + #ifdef __cplusplus } #endif diff --git a/source/blender/makesdna/DNA_gpencil_types.h b/source/blender/makesdna/DNA_gpencil_types.h index 9d969a29add..8facdca2f9c 100644 --- a/source/blender/makesdna/DNA_gpencil_types.h +++ b/source/blender/makesdna/DNA_gpencil_types.h @@ -34,6 +34,7 @@ extern "C" { struct AnimData; struct Curve; struct MDeformVert; +struct Curve; #define GP_DEFAULT_PIX_FACTOR 1.0f #define GP_DEFAULT_GRID_LINES 4 @@ -412,6 +413,8 @@ typedef enum eGPDframe_Flag { GP_FRAME_PAINT = (1 << 0), /* for editing in Action Editor */ GP_FRAME_SELECT = (1 << 1), + /* Line Art generation */ + GP_FRAME_LRT_CLEARED = (1 << 2), } eGPDframe_Flag; /* ***************************************** */ diff --git a/source/blender/makesdna/DNA_lineart_types.h b/source/blender/makesdna/DNA_lineart_types.h new file mode 100644 index 00000000000..2eb36cfb9d3 --- /dev/null +++ b/source/blender/makesdna/DNA_lineart_types.h @@ -0,0 +1,70 @@ +/* + * ***** 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) 2010 Blender Foundation. + * All rights reserved. + * + * The Original Code is: all of this file. + * + * Contributor(s): none yet. + * + * ***** END GPL LICENSE BLOCK ***** + */ + +#ifndef __DNA_LRT_TYPES_H__ +#define __DNA_LRT_TYPES_H__ + +/** \file DNA_lineart_types.h + * \ingroup DNA + */ + +#include "DNA_ID.h" +#include "DNA_listBase.h" + +struct Object; +struct Material; + +/* Notice that we need to have this file although no struct defines. + * Edge flags and usage flags are used by with scene/object/gpencil modifier bits, and those values + * needs to stay consistent throughout. */ + +typedef enum eLineartMainFlags { + LRT_INTERSECTION_AS_CONTOUR = (1 << 0), + LRT_EVERYTHING_AS_CONTOUR = (1 << 1), + LRT_ALLOW_DUPLI_OBJECTS = (1 << 2), + LRT_ALLOW_OVERLAPPING_EDGES = (1 << 3), + LRT_ALLOW_CLIPPING_BOUNDARIES = (1 << 4), + LRT_REMOVE_DOUBLES = (1 << 5), +} eLineartMainFlags; + +typedef enum eLineartEdgeFlag { + LRT_EDGE_FLAG_EDGE_MARK = (1 << 0), + LRT_EDGE_FLAG_CONTOUR = (1 << 1), + LRT_EDGE_FLAG_CREASE = (1 << 2), + LRT_EDGE_FLAG_MATERIAL = (1 << 3), + LRT_EDGE_FLAG_INTERSECTION = (1 << 4), + /** floating edge, unimplemented yet */ + LRT_EDGE_FLAG_FLOATING = (1 << 5), + /** also used as discarded line mark */ + LRT_EDGE_FLAG_CHAIN_PICKED = (1 << 6), + LRT_EDGE_FLAG_CLIPPED = (1 << 7), + /* Maxed out for 8 bits, DON'T ADD ANYMORE until improvements on the data structure. */ +} eLineartEdgeFlag; + +#define LRT_EDGE_FLAG_ALL_TYPE 0x3f + +#endif diff --git a/source/blender/makesdna/DNA_material_types.h b/source/blender/makesdna/DNA_material_types.h index 884c2df6480..55d5ea202f7 100644 --- a/source/blender/makesdna/DNA_material_types.h +++ b/source/blender/makesdna/DNA_material_types.h @@ -145,6 +145,16 @@ typedef enum eMaterialGPencilStyle_Mode { GP_MATERIAL_MODE_SQUARE = 2, } eMaterialGPencilStyle_Mode; +typedef struct MaterialLineArt { + int flags; /* eMaterialLineArtFlags */ + unsigned char transparency_mask; + unsigned char _pad[3]; +} MaterialLineArt; + +typedef enum eMaterialLineArtFlags { + LRT_MATERIAL_TRANSPARENCY_ENABLED = (1 << 0), +} eMaterialLineArtFlags; + typedef struct Material { ID id; /** Animation data (must be immediately after id for utilities to use it). */ @@ -210,6 +220,7 @@ typedef struct Material { /** Grease pencil color. */ struct MaterialGPencilStyle *gp_style; + struct MaterialLineArt lineart; } Material; /* **************** MATERIAL ********************* */ diff --git a/source/blender/makesdna/DNA_object_defaults.h b/source/blender/makesdna/DNA_object_defaults.h index 1bca572b963..d545b7912c0 100644 --- a/source/blender/makesdna/DNA_object_defaults.h +++ b/source/blender/makesdna/DNA_object_defaults.h @@ -66,6 +66,7 @@ .preview = NULL, \ .duplicator_visibility_flag = OB_DUPLI_FLAG_VIEWPORT | OB_DUPLI_FLAG_RENDER, \ .pc_ids = {NULL, NULL}, \ + .lineart = { .crease_threshold = DEG2RAD(140.0f) }, \ } /** \} */ diff --git a/source/blender/makesdna/DNA_object_types.h b/source/blender/makesdna/DNA_object_types.h index f6372a0c240..fa4e3953f5b 100644 --- a/source/blender/makesdna/DNA_object_types.h +++ b/source/blender/makesdna/DNA_object_types.h @@ -26,6 +26,11 @@ #include "DNA_object_enums.h" +#include "DNA_customdata_types.h" +#include "DNA_defs.h" +#include "DNA_lineart_types.h" +#include "DNA_listBase.h" + #include "DNA_ID.h" #include "DNA_action_types.h" /* bAnimVizSettings */ #include "DNA_customdata_types.h" @@ -200,6 +205,27 @@ typedef struct Object_Runtime { short _pad2[3]; } Object_Runtime; +typedef struct ObjectLineArt { + short usage; + short flags; + + /** if OBJECT_LRT_OWN_CREASE is set */ + float crease_threshold; +} ObjectLineArt; + +enum ObjectFeatureLine_Usage { + OBJECT_LRT_INHERENT = 0, + OBJECT_LRT_INCLUDE = (1 << 0), + OBJECT_LRT_OCCLUSION_ONLY = (1 << 1), + OBJECT_LRT_EXCLUDE = (1 << 2), + OBJECT_LRT_INTERSECTION_ONLY = (1 << 3), + OBJECT_LRT_NO_INTERSECTION = (1 << 4), +}; + +enum ObjectFeatureLine_Flags { + OBJECT_LRT_OWN_CREASE = (1 << 0), +}; + typedef struct Object { ID id; /** Animation data (must be immediately after id for utilities to use it). */ @@ -405,6 +431,8 @@ typedef struct Object { struct PreviewImage *preview; + ObjectLineArt lineart; + /** Runtime evaluation data (keep last). */ Object_Runtime runtime; } Object; @@ -595,6 +623,9 @@ enum { GP_EMPTY = 0, GP_STROKE = 1, GP_MONKEY = 2, + GP_LRT_SCENE = 3, + GP_LRT_OBJECT = 4, + GP_LRT_COLLECTION = 5, }; /* boundtype */ diff --git a/source/blender/makesdna/DNA_space_types.h b/source/blender/makesdna/DNA_space_types.h index de9b842312c..755010cd8a5 100644 --- a/source/blender/makesdna/DNA_space_types.h +++ b/source/blender/makesdna/DNA_space_types.h @@ -228,6 +228,7 @@ typedef enum eSpaceButtons_Context { BCONTEXT_TOOL = 14, BCONTEXT_SHADERFX = 15, BCONTEXT_OUTPUT = 16, + BCONTEXT_COLLECTION = 17, /* Keep last. */ BCONTEXT_TOT, diff --git a/source/blender/makesdna/intern/dna_defaults.c b/source/blender/makesdna/intern/dna_defaults.c index 7aca742a8e6..95272fb7804 100644 --- a/source/blender/makesdna/intern/dna_defaults.c +++ b/source/blender/makesdna/intern/dna_defaults.c @@ -315,6 +315,7 @@ SDNA_DEFAULT_DECL_STRUCT(TextureGpencilModifierData); SDNA_DEFAULT_DECL_STRUCT(ThickGpencilModifierData); SDNA_DEFAULT_DECL_STRUCT(TimeGpencilModifierData); SDNA_DEFAULT_DECL_STRUCT(TintGpencilModifierData); +SDNA_DEFAULT_DECL_STRUCT(LineartGpencilModifierData); #undef SDNA_DEFAULT_DECL_STRUCT @@ -539,6 +540,7 @@ const void *DNA_default_table[SDNA_TYPE_MAX] = { SDNA_DEFAULT_DECL(ThickGpencilModifierData), SDNA_DEFAULT_DECL(TimeGpencilModifierData), SDNA_DEFAULT_DECL(TintGpencilModifierData), + SDNA_DEFAULT_DECL(LineartGpencilModifierData), }; #undef SDNA_DEFAULT_DECL #undef SDNA_DEFAULT_DECL_EX diff --git a/source/blender/makesdna/intern/makesdna.c b/source/blender/makesdna/intern/makesdna.c index 7624649bf78..26fc56cfa1d 100644 --- a/source/blender/makesdna/intern/makesdna.c +++ b/source/blender/makesdna/intern/makesdna.c @@ -84,6 +84,7 @@ static const char *includefiles[] = { "DNA_mesh_types.h", "DNA_meshdata_types.h", "DNA_modifier_types.h", + "DNA_lineart_types.h", "DNA_lattice_types.h", "DNA_object_types.h", "DNA_object_force_types.h", @@ -1558,6 +1559,7 @@ int main(int argc, char **argv) #include "DNA_layer_types.h" #include "DNA_light_types.h" #include "DNA_lightprobe_types.h" +#include "DNA_lineart_types.h" #include "DNA_linestyle_types.h" #include "DNA_listBase.h" #include "DNA_mask_types.h" diff --git a/source/blender/makesrna/RNA_access.h b/source/blender/makesrna/RNA_access.h index a9125c78229..ba67cedfdbe 100644 --- a/source/blender/makesrna/RNA_access.h +++ b/source/blender/makesrna/RNA_access.h @@ -456,6 +456,7 @@ extern StructRNA RNA_NormalEditModifier; extern StructRNA RNA_Object; extern StructRNA RNA_ObjectBase; extern StructRNA RNA_ObjectDisplay; +extern StructRNA RNA_ObjectLineArt; extern StructRNA RNA_OceanModifier; extern StructRNA RNA_OceanTexData; extern StructRNA RNA_OffsetGpencilModifier; diff --git a/source/blender/makesrna/intern/CMakeLists.txt b/source/blender/makesrna/intern/CMakeLists.txt index 94cfd8464ae..c04ccf59327 100644 --- a/source/blender/makesrna/intern/CMakeLists.txt +++ b/source/blender/makesrna/intern/CMakeLists.txt @@ -443,6 +443,7 @@ set(LIB bf_editor_undo ) +add_definitions(${GL_DEFINITIONS}) blender_add_lib(bf_rna "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") diff --git a/source/blender/makesrna/intern/rna_collection.c b/source/blender/makesrna/intern/rna_collection.c index 20a455f5312..90e969560b7 100644 --- a/source/blender/makesrna/intern/rna_collection.c +++ b/source/blender/makesrna/intern/rna_collection.c @@ -22,6 +22,8 @@ #include "DNA_collection_types.h" +#include "DNA_lineart_types.h" + #include "BLI_utildefines.h" #include "RNA_define.h" @@ -517,6 +519,31 @@ void RNA_def_collections(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Disable in Renders", "Globally disable in renders"); RNA_def_property_update(prop, NC_SCENE | ND_LAYER_CONTENT, "rna_Collection_flag_update"); + static const EnumPropertyItem rna_collection_lineart_usage[] = { + {COLLECTION_LRT_INCLUDE, "INCLUDE", 0, "Include", "Collection will produce feature lines"}, + {COLLECTION_LRT_OCCLUSION_ONLY, + "OCCLUSION_ONLY", + 0, + "Occlusion Only", + "Only use the collection to produce occlusion"}, + {COLLECTION_LRT_EXCLUDE, "EXCLUDE", 0, "Exclude", "Don't use this collection in LRT"}, + {COLLECTION_LRT_INTERSECTION_ONLY, + "INTERSECTION_ONLY", + 0, + "Intersection Only", + "Only generate intersection lines with this collection"}, + {COLLECTION_LRT_NO_INTERSECTION, + "NO_INTERSECTION", + 0, + "No Intersection", + "Do not generate intersection lines for this collection"}, + {0, NULL, 0, NULL, NULL}}; + + prop = RNA_def_property(srna, "lineart_usage", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, rna_collection_lineart_usage); + RNA_def_property_ui_text(prop, "Usage", "How to use this collection in LRT"); + RNA_def_property_update(prop, NC_SCENE, NULL); + prop = RNA_def_property(srna, "color_tag", PROP_ENUM, PROP_NONE); RNA_def_property_enum_sdna(prop, NULL, "color_tag"); RNA_def_property_enum_funcs( diff --git a/source/blender/makesrna/intern/rna_gpencil_modifier.c b/source/blender/makesrna/intern/rna_gpencil_modifier.c index 71d5a53adb2..b84fcf4cf7c 100644 --- a/source/blender/makesrna/intern/rna_gpencil_modifier.c +++ b/source/blender/makesrna/intern/rna_gpencil_modifier.c @@ -88,6 +88,11 @@ const EnumPropertyItem rna_enum_object_greasepencil_modifier_type_items[] = { ICON_MOD_SUBSURF, "Subdivide", "Subdivide stroke adding more control points"}, + {eGpencilModifierType_Lineart, + "GP_LINEART", + ICON_MOD_EDGESPLIT, + "Line Art", + "Generate Line Art strokes from selected source"}, {0, "", 0, N_("Deform"), ""}, {eGpencilModifierType_Armature, "GP_ARMATURE", @@ -241,6 +246,8 @@ static StructRNA *rna_GpencilModifier_refine(struct PointerRNA *ptr) return &RNA_MultiplyGpencilModifier; case eGpencilModifierType_Texture: return &RNA_TextureGpencilModifier; + case eGpencilModifierType_Lineart: + return &RNA_LineartGpencilModifier; /* Default */ case eGpencilModifierType_None: case NUM_GREASEPENCIL_MODIFIER_TYPES: @@ -311,6 +318,7 @@ RNA_GP_MOD_VGROUP_NAME_SET(Offset, vgname); RNA_GP_MOD_VGROUP_NAME_SET(Armature, vgname); RNA_GP_MOD_VGROUP_NAME_SET(Texture, vgname); RNA_GP_MOD_VGROUP_NAME_SET(Tint, vgname); +RNA_GP_MOD_VGROUP_NAME_SET(Lineart, vgname); # undef RNA_GP_MOD_VGROUP_NAME_SET @@ -2305,6 +2313,281 @@ static void rna_def_modifier_gpenciltexture(BlenderRNA *brna) RNA_def_property_update(prop, 0, "rna_GpencilModifier_dependency_update"); } +static void rna_def_modifier_gpencillineart(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + static const EnumPropertyItem modifier_lineart_source_type[] = { + {LRT_SOURCE_COLLECTION, "COLLECTION", 0, "Collection", ""}, + {LRT_SOURCE_OBJECT, "OBJECT", 0, "Object", ""}, + {LRT_SOURCE_SCENE, "SCENE", 0, "Scene", ""}, + {0, NULL, 0, NULL, NULL}, + }; + + srna = RNA_def_struct(brna, "LineartGpencilModifier", "GpencilModifier"); + RNA_def_struct_ui_text( + srna, "Line Art Modifier", "Generate Line Art strokes from selected source"); + RNA_def_struct_sdna(srna, "LineartGpencilModifierData"); + RNA_def_struct_ui_icon(srna, ICON_MOD_EDGESPLIT); + + prop = RNA_def_property(srna, "fuzzy_intersections", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "calculation_flags", LRT_INTERSECTION_AS_CONTOUR); + RNA_def_property_ui_text(prop, + "Intersection With Contour", + "Treat intersection and contour lines as if they were the same type so " + "they can be chained together"); + RNA_def_property_update(prop, NC_SCENE, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "fuzzy_everything", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "calculation_flags", LRT_EVERYTHING_AS_CONTOUR); + RNA_def_property_ui_text( + prop, "All Lines", "Treat all lines as the same line type so they can be chained together"); + RNA_def_property_update(prop, NC_SCENE, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "allow_duplication", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "calculation_flags", LRT_ALLOW_DUPLI_OBJECTS); + RNA_def_property_ui_text( + prop, + "Instanced Objects", + "Allow particle objects and face/vertiex duplication to show in line art"); + RNA_def_property_update(prop, NC_SCENE, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "allow_overlapping_edges", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "calculation_flags", LRT_ALLOW_OVERLAPPING_EDGES); + RNA_def_property_ui_text(prop, + "Handle Overlapping Edges", + "Allow lines from edge split to show properly, may run slower"); + RNA_def_property_update(prop, NC_SCENE, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "allow_clipping_boundaries", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "calculation_flags", LRT_ALLOW_CLIPPING_BOUNDARIES); + RNA_def_property_ui_text( + prop, "Clipping Boundaries", "Allow lines on near/far clipping plane to be shown"); + RNA_def_property_update(prop, NC_SCENE, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "crease_threshold", PROP_FLOAT, PROP_ANGLE); + RNA_def_property_range(prop, 0, DEG2RAD(180.0f)); + RNA_def_property_ui_range(prop, 0.0f, DEG2RAD(180.0f), 0.01f, 1); + RNA_def_property_ui_text( + prop, "Crease Threshold", "Angles smaller than this will be treated as creases"); + RNA_def_property_update(prop, NC_SCENE, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "angle_splitting_threshold", PROP_FLOAT, PROP_ANGLE); + RNA_def_property_ui_text( + prop, "Angle Splitting", "Angle in screen space below which a stroke is split in two"); + /* Don't allow value very close to PI, or we get a lot of small segments.*/ + RNA_def_property_ui_range(prop, 0.0f, DEG2RAD(179.5f), 0.01f, 1); + RNA_def_property_range(prop, 0.0f, DEG2RAD(180.0f)); + RNA_def_property_update(prop, NC_SCENE, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "remove_doubles", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "calculation_flags", LRT_REMOVE_DOUBLES); + RNA_def_property_ui_text( + prop, "Remove Doubles", "Remove doubles when line art is loading geometries"); + RNA_def_property_update(prop, NC_SCENE, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "chaining_geometry_threshold", PROP_FLOAT, PROP_DISTANCE); + RNA_def_property_ui_text(prop, + "Geometry Threshold", + "Segments where their geometric distance between them lower than this " + "will be chained together"); + RNA_def_property_ui_range(prop, 0.0f, 0.5f, 0.001f, 3); + RNA_def_property_range(prop, 0.0f, 0.5f); + RNA_def_property_update(prop, NC_SCENE, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "chaining_image_threshold", PROP_FLOAT, PROP_DISTANCE); + RNA_def_property_ui_text( + prop, + "Image Threshold", + "Segments with an image distance smaller than this will be chained together"); + RNA_def_property_ui_range(prop, 0.0f, 0.3f, 0.001f, 4); + RNA_def_property_range(prop, 0.0f, 0.3f); + RNA_def_property_update(prop, NC_SCENE, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "source_type", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, modifier_lineart_source_type); + RNA_def_property_ui_text(prop, "Source Type", "Lineart stroke source type"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_dependency_update"); + + prop = RNA_def_property(srna, "source_object", PROP_POINTER, PROP_NONE); + RNA_def_property_flag(prop, PROP_EDITABLE | PROP_ID_SELF_CHECK); + RNA_def_property_struct_type(prop, "Object"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); + RNA_def_property_ui_text( + prop, "Source Object", "Source object that this modifier grabs data from"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_dependency_update"); + + prop = RNA_def_property(srna, "source_collection", PROP_POINTER, PROP_NONE); + RNA_def_property_flag(prop, PROP_EDITABLE | PROP_ID_SELF_CHECK); + RNA_def_property_struct_type(prop, "Collection"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); + RNA_def_property_ui_text( + prop, "Source Collection", "Source collection that this modifier uses data from"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_dependency_update"); + + /* types */ + prop = RNA_def_property(srna, "use_contour", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "line_types", LRT_EDGE_FLAG_CONTOUR); + RNA_def_property_ui_text(prop, "Use Contour", "Include contour lines in the result"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "use_crease", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "line_types", LRT_EDGE_FLAG_CREASE); + RNA_def_property_ui_text(prop, "Use Crease", "Include sharp edges in the result"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "use_material", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "line_types", LRT_EDGE_FLAG_MATERIAL); + RNA_def_property_ui_text( + prop, "Use Material", "Include material separation lines in the result"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "use_edge_mark", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "line_types", LRT_EDGE_FLAG_EDGE_MARK); + RNA_def_property_ui_text(prop, "Use Edge Mark", "Include freestyle edge marks in the result"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "use_intersection", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "line_types", LRT_EDGE_FLAG_INTERSECTION); + RNA_def_property_ui_text(prop, "Use Intersection", "Include intersection lines in the result"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "use_multiple_levels", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "use_multiple_levels", 0); + RNA_def_property_ui_text( + prop, "Use Multiple Levels", "Select lines from multiple occlusion levels"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "level_start", PROP_INT, PROP_NONE); + RNA_def_property_ui_text( + prop, "Level Start", "Minimum level of occlusion level that gets selected"); + RNA_def_property_range(prop, 0, 128); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "level_end", PROP_INT, PROP_NONE); + RNA_def_property_ui_text( + prop, "Level End", "Maximum level of occlusion level that gets selected"); + RNA_def_property_range(prop, 0, 128); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "target_material", PROP_POINTER, PROP_NONE); + RNA_def_property_flag(prop, PROP_EDITABLE | PROP_ID_SELF_CHECK); + RNA_def_property_struct_type(prop, "Material"); + RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY); + RNA_def_property_ui_text( + prop, "Target Material", "Grease Pencil material assigned to the generated strokes"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "target_layer", PROP_STRING, PROP_NONE); + RNA_def_property_ui_text( + prop, "Target Layer", "Grease Pencil layer assigned to the generated strokes"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "source_vertex_group", PROP_STRING, PROP_NONE); + RNA_def_property_ui_text( + prop, + "Source Vertex Group", + "Matches the beginning of vertex group names from mesh objects, match all when left empty"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "vertex_group", PROP_STRING, PROP_NONE); + RNA_def_property_string_sdna(prop, NULL, "vgname"); + RNA_def_property_string_funcs(prop, NULL, NULL, "rna_LineartGpencilModifier_vgname_set"); + RNA_def_property_ui_text(prop, "Vertex Group", "Vertex group name for selected strokes"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "invert_source_vertex_group", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flags", LRT_GPENCIL_INVERT_SOURCE_VGROUP); + RNA_def_property_ui_text(prop, "Invert Source", "Invert source vertex group values"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "match_output_vertex_group", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flags", LRT_GPENCIL_MATCH_OUTPUT_VGROUP); + RNA_def_property_ui_text(prop, "Match Output", "Match output vertex group"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "soft_selection", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flags", LRT_GPENCIL_SOFT_SELECTION); + RNA_def_property_ui_text( + prop, "Soft selection", "Preserve original vertex weight instead of clipping to 0/1"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "is_baked", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flags", LRT_GPENCIL_IS_BAKED); + RNA_def_property_ui_text(prop, "Is Baked", "This modifier has baked data"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "thickness", PROP_INT, PROP_NONE); + RNA_def_property_ui_text(prop, "Thickness", "The thickness that used to generate strokes"); + RNA_def_property_ui_range(prop, 1, 100, 1, 1); + RNA_def_property_range(prop, 1, 200); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "opacity", PROP_FLOAT, PROP_FACTOR); + RNA_def_property_ui_text(prop, "Opacity", "The strength value used to generate strokes"); + RNA_def_property_ui_range(prop, 0.0f, 1.0f, 0.01f, 2); + RNA_def_property_range(prop, 0.0f, 1.0f); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "pre_sample_length", PROP_FLOAT, PROP_NONE); + RNA_def_property_ui_text(prop, "Pre Sample Length", "Sample strokes before sending out"); + RNA_def_property_ui_range(prop, 0.0f, 1.0f, 0.01f, 2); + RNA_def_property_range(prop, 0.0f, 1.0f); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "use_transparency", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "transparency_flags", LRT_GPENCIL_TRANSPARENCY_ENABLE); + RNA_def_property_ui_text( + prop, "Use Transparency", "Use transparency mask from this material in Line Art"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "transparency_match", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "transparency_flags", LRT_GPENCIL_TRANSPARENCY_MATCH); + RNA_def_property_ui_text(prop, "Match Transparency", "Match all transparency bits"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "transparency_mask_0", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "transparency_mask", 1 << 0); + RNA_def_property_ui_text(prop, "Mask 0", "Mask bit 0"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "transparency_mask_1", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "transparency_mask", 1 << 1); + RNA_def_property_ui_text(prop, "Mask 1", "Mask bit 1"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "transparency_mask_2", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "transparency_mask", 1 << 2); + RNA_def_property_ui_text(prop, "Mask 2", "Mask bit 2"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "transparency_mask_3", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "transparency_mask", 1 << 3); + RNA_def_property_ui_text(prop, "Mask 3", "Mask bit 3"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "transparency_mask_4", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "transparency_mask", 1 << 4); + RNA_def_property_ui_text(prop, "Mask 4", "Mask bit 4"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "transparency_mask_5", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "transparency_mask", 1 << 5); + RNA_def_property_ui_text(prop, "Mask 5", "Mask bit 5"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "transparency_mask_6", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "transparency_mask", 1 << 6); + RNA_def_property_ui_text(prop, "mask 6", "Mask bit 6"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); + + prop = RNA_def_property(srna, "transparency_mask_7", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "transparency_mask", 1 << 7); + RNA_def_property_ui_text(prop, "Mask 7", "Mask bit 7"); + RNA_def_property_update(prop, 0, "rna_GpencilModifier_update"); +} + void RNA_def_greasepencil_modifier(BlenderRNA *brna) { StructRNA *srna; @@ -2379,6 +2662,7 @@ void RNA_def_greasepencil_modifier(BlenderRNA *brna) rna_def_modifier_gpencilarmature(brna); rna_def_modifier_gpencilmultiply(brna); rna_def_modifier_gpenciltexture(brna); + rna_def_modifier_gpencillineart(brna); } #endif diff --git a/source/blender/makesrna/intern/rna_material.c b/source/blender/makesrna/intern/rna_material.c index 84c831a178e..39dd3059f3d 100644 --- a/source/blender/makesrna/intern/rna_material.c +++ b/source/blender/makesrna/intern/rna_material.c @@ -127,6 +127,14 @@ static void rna_MaterialGpencil_update(Main *bmain, Scene *scene, PointerRNA *pt WM_main_add_notifier(NC_GPENCIL | ND_DATA, ma); } +static void rna_MaterialLineArt_update(Main *UNUSED(bmain), Scene *UNUSED(scene), PointerRNA *ptr) +{ + Material *ma = (Material *)ptr->owner_id; + /* Need to tag geometry for line art modifier updates. */ + DEG_id_tag_update(&ma->id, ID_RECALC_GEOMETRY); + WM_main_add_notifier(NC_MATERIAL | ND_SHADING_DRAW, ma); +} + static void rna_Material_draw_update(Main *UNUSED(bmain), Scene *UNUSED(scene), PointerRNA *ptr) { Material *ma = (Material *)ptr->owner_id; @@ -671,6 +679,70 @@ static void rna_def_material_greasepencil(BlenderRNA *brna) RNA_def_property_ui_text( prop, "Is Fill Visible", "True when opacity of fill is set high enough to be visible"); } +static void rna_def_material_lineart(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + srna = RNA_def_struct(brna, "MaterialLineArt", NULL); + RNA_def_struct_sdna(srna, "MaterialLineArt"); + RNA_def_struct_ui_text(srna, "Material Line Art", ""); + + prop = RNA_def_property(srna, "use_transparency", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_default(prop, 0); + RNA_def_property_boolean_sdna(prop, NULL, "flags", LRT_MATERIAL_TRANSPARENCY_ENABLED); + RNA_def_property_ui_text( + prop, "Use Transparency", "Use transparency mask from this material in Line Art"); + RNA_def_property_update(prop, NC_GPENCIL | ND_SHADING, "rna_MaterialLineArt_update"); + + prop = RNA_def_property(srna, "transparency_mask_0", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_default(prop, 0); + RNA_def_property_boolean_sdna(prop, NULL, "transparency_mask", 1 << 0); + RNA_def_property_ui_text(prop, "Mask 0", "Mask bit 0"); + RNA_def_property_update(prop, NC_GPENCIL | ND_SHADING, "rna_MaterialLineArt_update"); + + prop = RNA_def_property(srna, "transparency_mask_1", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_default(prop, 0); + RNA_def_property_boolean_sdna(prop, NULL, "transparency_mask", 1 << 1); + RNA_def_property_ui_text(prop, "Mask 1", "Mask bit 1"); + RNA_def_property_update(prop, NC_GPENCIL | ND_SHADING, "rna_MaterialLineArt_update"); + + prop = RNA_def_property(srna, "transparency_mask_2", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_default(prop, 0); + RNA_def_property_boolean_sdna(prop, NULL, "transparency_mask", 1 << 2); + RNA_def_property_ui_text(prop, "Mask 2", "Mask bit 2"); + RNA_def_property_update(prop, NC_GPENCIL | ND_SHADING, "rna_MaterialLineArt_update"); + + prop = RNA_def_property(srna, "transparency_mask_3", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_default(prop, 0); + RNA_def_property_boolean_sdna(prop, NULL, "transparency_mask", 1 << 3); + RNA_def_property_ui_text(prop, "Mask 3", "Mask bit 3"); + RNA_def_property_update(prop, NC_GPENCIL | ND_SHADING, "rna_MaterialLineArt_update"); + + prop = RNA_def_property(srna, "transparency_mask_4", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_default(prop, 0); + RNA_def_property_boolean_sdna(prop, NULL, "transparency_mask", 1 << 4); + RNA_def_property_ui_text(prop, "Mask 4", "Mask bit 4"); + RNA_def_property_update(prop, NC_GPENCIL | ND_SHADING, "rna_MaterialLineArt_update"); + + prop = RNA_def_property(srna, "transparency_mask_5", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_default(prop, 0); + RNA_def_property_boolean_sdna(prop, NULL, "transparency_mask", 1 << 5); + RNA_def_property_ui_text(prop, "Mask 5", "Mask bit 5"); + RNA_def_property_update(prop, NC_GPENCIL | ND_SHADING, "rna_MaterialLineArt_update"); + + prop = RNA_def_property(srna, "transparency_mask_6", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_default(prop, 0); + RNA_def_property_boolean_sdna(prop, NULL, "transparency_mask", 1 << 6); + RNA_def_property_ui_text(prop, "mask 6", "Mask bit 6"); + RNA_def_property_update(prop, NC_GPENCIL | ND_SHADING, "rna_MaterialLineArt_update"); + + prop = RNA_def_property(srna, "transparency_mask_7", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_default(prop, 0); + RNA_def_property_boolean_sdna(prop, NULL, "transparency_mask", 1 << 7); + RNA_def_property_ui_text(prop, "Mask 7", "Mask bit 7"); + RNA_def_property_update(prop, NC_GPENCIL | ND_SHADING, "rna_MaterialLineArt_update"); +} void RNA_def_material(BlenderRNA *brna) { @@ -836,7 +908,13 @@ void RNA_def_material(BlenderRNA *brna) RNA_def_property_ui_text( prop, "Is Grease Pencil", "True if this material has grease pencil data"); + /* line art */ + prop = RNA_def_property(srna, "lineart", PROP_POINTER, PROP_NONE); + RNA_def_property_pointer_sdna(prop, NULL, "lineart"); + RNA_def_property_ui_text(prop, "Line Art Settings", "Line Art settings for material"); + rna_def_material_greasepencil(brna); + rna_def_material_lineart(brna); RNA_api_material(srna); } diff --git a/source/blender/makesrna/intern/rna_object.c b/source/blender/makesrna/intern/rna_object.c index a70b776b07a..8c12ccceb5d 100644 --- a/source/blender/makesrna/intern/rna_object.c +++ b/source/blender/makesrna/intern/rna_object.c @@ -36,6 +36,7 @@ #include "DNA_shader_fx_types.h" #include "DNA_workspace_types.h" +#include "BLI_math.h" #include "BLI_utildefines.h" #include "BLT_translation.h" @@ -159,6 +160,21 @@ const EnumPropertyItem rna_enum_object_gpencil_type_items[] = { {GP_EMPTY, "EMPTY", ICON_EMPTY_AXIS, "Blank", "Create an empty grease pencil object"}, {GP_STROKE, "STROKE", ICON_STROKE, "Stroke", "Create a simple stroke with basic colors"}, {GP_MONKEY, "MONKEY", ICON_MONKEY, "Monkey", "Construct a Suzanne grease pencil object"}, + {GP_LRT_SCENE, + "LRT_SCENE", + ICON_SCENE_DATA, + "Scene Line Art", + "Quickly set up Line Art for the whole scene"}, + {GP_LRT_COLLECTION, + "LRT_COLLECTION", + ICON_OUTLINER_COLLECTION, + "Collection Line Art", + "Quickly set up Line Art for active collection"}, + {GP_LRT_OBJECT, + "LRT_OBJECT", + ICON_CUBE, + "Object Line Art", + "Quickly set up Line Art for active collection"}, {0, NULL, 0, NULL, NULL}}; static const EnumPropertyItem parent_type_items[] = { @@ -2083,6 +2099,12 @@ int rna_Object_use_dynamic_topology_sculpting_get(PointerRNA *ptr) return (ss && ss->bm); } +static void rna_object_lineart_update(Main *UNUSED(bmain), Scene *UNUSED(scene), PointerRNA *ptr) +{ + DEG_id_tag_update(ptr->owner_id, ID_RECALC_GEOMETRY); + WM_main_add_notifier(NC_OBJECT | ND_MODIFIER, ptr->owner_id); +} + #else static void rna_def_vertex_group(BlenderRNA *brna) @@ -2645,6 +2667,59 @@ static void rna_def_object_display(BlenderRNA *brna) RNA_define_lib_overridable(false); } +static void rna_def_object_lineart(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + static EnumPropertyItem prop_feature_line_usage_items[] = { + {OBJECT_LRT_INHERENT, + "INHEREIT", + 0, + "Inhereit", + "Follow settings from the parent collection"}, + {OBJECT_LRT_INCLUDE, "INCLUDE", 0, "Include", "Include this object into LRT calculation"}, + {OBJECT_LRT_OCCLUSION_ONLY, + "OCCLUSION_ONLY", + 0, + "Occlusion Only", + "Don't produce lines, only used as occlusion object"}, + {OBJECT_LRT_EXCLUDE, "EXCLUDE", 0, "Exclude", "Don't use this object for LRT rendering"}, + {OBJECT_LRT_INTERSECTION_ONLY, + "INTERSECTION_ONLY", + 0, + "Intersection Only", + "Only to generate intersection lines with this object"}, + {OBJECT_LRT_NO_INTERSECTION, + "NO_INTERSECTION", + 0, + "No Intersection", + "Include this object but do not generate intersection lines"}, + {0, NULL, 0, NULL, NULL}, + }; + + srna = RNA_def_struct(brna, "ObjectLineArt", NULL); + RNA_def_struct_ui_text(srna, "Object Line Art", "Object lineart settings"); + RNA_def_struct_sdna(srna, "ObjectLineArt"); + + prop = RNA_def_property(srna, "usage", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, prop_feature_line_usage_items); + RNA_def_property_ui_text(prop, "Usage", "How to use this object in line art calculation"); + RNA_def_property_update(prop, 0, "rna_object_lineart_update"); + + prop = RNA_def_property(srna, "use_crease_override", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flags", OBJECT_LRT_OWN_CREASE); + RNA_def_property_ui_text(prop, "Own Crease", "Use own crease setting to overwrite scene global"); + RNA_def_property_update(prop, 0, "rna_object_lineart_update"); + + prop = RNA_def_property(srna, "crease_threshold", PROP_FLOAT, PROP_ANGLE); + RNA_def_property_range(prop, 0, DEG2RAD(180.0f)); + RNA_def_property_ui_range(prop, 0.0f, DEG2RAD(180.0f), 0.01f, 1); + RNA_def_property_ui_text( + prop, "Own Crease", "Angles smaller than this will be treated as creases"); + RNA_def_property_update(prop, 0, "rna_object_lineart_update"); +} + static void rna_def_object(BlenderRNA *brna) { StructRNA *srna; @@ -3414,6 +3489,11 @@ static void rna_def_object(BlenderRNA *brna) RNA_def_property_pointer_funcs(prop, "rna_Object_display_get", NULL, NULL, NULL); RNA_def_property_ui_text(prop, "Object Display", "Object display settings for 3D viewport"); + /* Line Art */ + prop = RNA_def_property(srna, "lineart", PROP_POINTER, PROP_NONE); + RNA_def_property_struct_type(prop, "ObjectLineArt"); + RNA_def_property_ui_text(prop, "Line Art", "Line Art settings for the object"); + RNA_define_lib_overridable(false); /* anim */ @@ -3434,6 +3514,7 @@ void RNA_def_object(BlenderRNA *brna) rna_def_face_map(brna); rna_def_material_slot(brna); rna_def_object_display(brna); + rna_def_object_lineart(brna); RNA_define_animate_sdna(true); } diff --git a/source/blender/makesrna/intern/rna_space.c b/source/blender/makesrna/intern/rna_space.c index 0ddc9a61cad..8377a8fbf3f 100644 --- a/source/blender/makesrna/intern/rna_space.c +++ b/source/blender/makesrna/intern/rna_space.c @@ -458,6 +458,7 @@ static const EnumPropertyItem buttons_context_items[] = { {BCONTEXT_OUTPUT, "OUTPUT", ICON_OUTPUT, "Output", "Output Properties"}, {BCONTEXT_VIEW_LAYER, "VIEW_LAYER", ICON_RENDER_RESULT, "View Layer", "View Layer Properties"}, {BCONTEXT_WORLD, "WORLD", ICON_WORLD, "World", "World Properties"}, + {BCONTEXT_COLLECTION, "COLLECTION", ICON_GROUP, "Collection", "Collection Properties"}, {BCONTEXT_OBJECT, "OBJECT", ICON_OBJECT_DATA, "Object", "Object Properties"}, {BCONTEXT_CONSTRAINT, "CONSTRAINT", diff --git a/source/blender/windowmanager/WM_api.h b/source/blender/windowmanager/WM_api.h index 545371f4540..9f70d05cc8a 100644 --- a/source/blender/windowmanager/WM_api.h +++ b/source/blender/windowmanager/WM_api.h @@ -755,6 +755,7 @@ enum { WM_JOB_TYPE_FSMENU_BOOKMARK_VALIDATE, WM_JOB_TYPE_QUADRIFLOW_REMESH, WM_JOB_TYPE_TRACE_IMAGE, + WM_JOB_TYPE_LINEART, /* add as needed, bake, seq proxy build * if having hard coded values is a problem */ }; |