From 6dd8ceef2a21f64cbb61a96560c50c162f9dae39 Mon Sep 17 00:00:00 2001 From: Yiming Wu Date: Wed, 29 Jun 2022 22:54:29 +0800 Subject: LineArt: Shadow and related functionalities. This patch includes the full shadow functionality for LineArt: - Light contour and cast shadow lines. - Lit/shaded region selection. - Enclosed light/shadow shape calculation. - Silhouette/anti-silhouette selection. - Intersection priority based on shadow edge identifier. Reviewed By: Sebastian Parborg (zeddb) Differential Revision: https://developer.blender.org/D15109 --- source/blender/gpencil_modifiers/CMakeLists.txt | 1 + .../gpencil_modifiers/intern/MOD_gpencillineart.c | 112 +- .../gpencil_modifiers/intern/lineart/MOD_lineart.h | 277 +++- .../intern/lineart/lineart_chain.c | 139 +- .../gpencil_modifiers/intern/lineart/lineart_cpu.c | 794 +++++++---- .../intern/lineart/lineart_intern.h | 93 +- .../gpencil_modifiers/intern/lineart/lineart_ops.c | 2 + .../intern/lineart/lineart_shadow.c | 1414 ++++++++++++++++++++ 8 files changed, 2513 insertions(+), 319 deletions(-) create mode 100644 source/blender/gpencil_modifiers/intern/lineart/lineart_shadow.c (limited to 'source/blender/gpencil_modifiers') diff --git a/source/blender/gpencil_modifiers/CMakeLists.txt b/source/blender/gpencil_modifiers/CMakeLists.txt index 69fc26c99e9..947fc32f8c0 100644 --- a/source/blender/gpencil_modifiers/CMakeLists.txt +++ b/source/blender/gpencil_modifiers/CMakeLists.txt @@ -68,6 +68,7 @@ set(SRC intern/lineart/lineart_cpp_bridge.cc intern/lineart/lineart_cpu.c intern/lineart/lineart_ops.c + intern/lineart/lineart_shadow.c intern/lineart/lineart_util.c intern/lineart/MOD_lineart.h diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c b/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c index 8d77fb50c71..1c5485d2640 100644 --- a/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c +++ b/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c @@ -88,6 +88,8 @@ static void generate_strokes_actual( lmd->intersection_mask, lmd->thickness, lmd->opacity, + lmd->shadow_selection, + lmd->silhouette_selection, lmd->source_vertex_group, lmd->vgname, lmd->flags); @@ -193,6 +195,7 @@ static void bakeModifier(Main *UNUSED(bmain), * modifiers in the stack. */ lmd->edge_types_override = lmd->edge_types; lmd->level_end_override = lmd->level_end; + lmd->shadow_selection_override = lmd->shadow_selection; MOD_lineart_compute_feature_lines( depsgraph, lmd, &gpd->runtime.lineart_cache, (!(ob->dtx & OB_DRAW_IN_FRONT))); @@ -263,6 +266,10 @@ static void updateDepsgraph(GpencilModifierData *md, DEG_add_object_relation( ctx->node, ctx->scene->camera, DEG_OB_COMP_PARAMETERS, "Line Art Modifier"); } + if (lmd->light_contour_object) { + DEG_add_object_relation( + ctx->node, lmd->light_contour_object, DEG_OB_COMP_TRANSFORM, "Line Art Modifier"); + } } static void foreachIDLink(GpencilModifierData *md, Object *ob, IDWalkFunc walk, void *userData) @@ -274,6 +281,7 @@ static void foreachIDLink(GpencilModifierData *md, Object *ob, IDWalkFunc walk, walk(userData, ob, (ID **)&lmd->source_object, IDWALK_CB_NOP); walk(userData, ob, (ID **)&lmd->source_camera, IDWALK_CB_NOP); + walk(userData, ob, (ID **)&lmd->light_contour_object, IDWALK_CB_NOP); } static void panel_draw(const bContext *UNUSED(C), Panel *panel) @@ -340,31 +348,107 @@ static void edge_types_panel_draw(const bContext *UNUSED(C), Panel *panel) const bool is_baked = RNA_boolean_get(ptr, "is_baked"); const bool use_cache = RNA_boolean_get(ptr, "use_cache"); const bool is_first = BKE_gpencil_is_first_lineart_in_stack(ob_ptr.data, ptr->data); + const bool has_light = RNA_pointer_get(ptr, "light_contour_object").data != NULL; uiLayoutSetEnabled(layout, !is_baked); uiLayoutSetPropSep(layout, true); + uiLayout *sub = uiLayoutRow(layout, false); + uiLayoutSetActive(sub, has_light); + uiItemR(sub, ptr, "shadow_region_filtering", 0, IFACE_("Illumination Filtering"), ICON_NONE); + uiLayout *col = uiLayoutColumn(layout, true); - uiItemR(col, ptr, "use_contour", 0, IFACE_("Contour"), ICON_NONE); - uiItemR(col, ptr, "use_loose", 0, IFACE_("Loose"), ICON_NONE); - uiItemR(col, ptr, "use_material", 0, IFACE_("Material Borders"), ICON_NONE); - uiItemR(col, ptr, "use_edge_mark", 0, IFACE_("Edge Marks"), ICON_NONE); - uiItemR(col, ptr, "use_intersection", 0, IFACE_("Intersections"), ICON_NONE); + sub = uiLayoutRowWithHeading(col, false, IFACE_("Create")); + uiItemR(sub, ptr, "use_contour", 0, "", ICON_NONE); + + uiLayout *entry = uiLayoutRow(sub, true); + uiLayoutSetActive(entry, RNA_boolean_get(ptr, "use_contour")); + uiItemR(entry, ptr, "silhouette_filtering", 0, "", ICON_NONE); + + const int silhouette_filtering = RNA_enum_get(ptr, "silhouette_filtering"); + if (silhouette_filtering != LRT_SILHOUETTE_FILTER_NONE) { + uiItemR(entry, ptr, "use_invert_silhouette", 0, "", ICON_ARROW_LEFTRIGHT); + } - uiLayout *sub = uiLayoutRowWithHeading(col, false, IFACE_("Crease")); + sub = uiLayoutRow(col, false); uiItemR(sub, ptr, "use_crease", 0, "", ICON_NONE); - uiLayout *entry = uiLayoutRow(sub, false); + entry = uiLayoutColumn(sub, false); + uiItemL(entry, IFACE_("Crease"), ICON_NONE); uiLayoutSetActive(entry, RNA_boolean_get(ptr, "use_crease") || is_first); if (use_cache && !is_first) { - uiItemL(entry, IFACE_("Angle Cached"), ICON_INFO); + uiItemL(entry, IFACE_("Crease Angle Cached"), ICON_INFO); + } + else { + uiItemR(entry, + ptr, + "crease_threshold", + UI_ITEM_R_SLIDER | UI_ITEM_R_FORCE_BLANK_DECORATE, + IFACE_("Default Angle"), + ICON_NONE); + } + + uiItemR(col, ptr, "use_intersection", 0, IFACE_("Intersections"), ICON_NONE); + uiItemR(col, ptr, "use_material", 0, IFACE_("Material Borders"), ICON_NONE); + uiItemR(col, ptr, "use_edge_mark", 0, IFACE_("Edge Marks"), ICON_NONE); + uiItemR(col, ptr, "use_loose", 0, IFACE_("Loose"), ICON_NONE); + + entry = uiLayoutColumn(col, false); + uiLayoutSetActive(entry, has_light); + + sub = uiLayoutRow(entry, false); + uiItemR(sub, ptr, "use_light_contour", 0, IFACE_("Light Contour"), ICON_NONE); + + uiItemR(entry, ptr, "use_shadow", 0, IFACE_("Cast Shadow"), ICON_NONE); + + uiItemL(layout, IFACE_("Options"), ICON_NONE); + + sub = uiLayoutColumn(layout, false); + if (use_cache && !is_first) { + uiItemL(sub, IFACE_("Type overlapping cached"), ICON_INFO); } else { - uiItemR(entry, ptr, "crease_threshold", UI_ITEM_R_SLIDER, " ", ICON_NONE); + uiItemR(sub, + ptr, + "use_overlap_edge_type_support", + 0, + IFACE_("Allow Overlapping Types"), + ICON_NONE); } +} + +static void options_light_reference_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); + + const bool is_baked = RNA_boolean_get(ptr, "is_baked"); + const bool use_cache = RNA_boolean_get(ptr, "use_cache"); + const bool has_light = RNA_pointer_get(ptr, "light_contour_object").data != NULL; + const bool is_first = BKE_gpencil_is_first_lineart_in_stack(ob_ptr.data, ptr->data); - uiItemR(layout, ptr, "use_overlap_edge_type_support", 0, IFACE_("Allow Overlap"), ICON_NONE); + uiLayoutSetPropSep(layout, true); + uiLayoutSetEnabled(layout, !is_baked); + + if (use_cache && !is_first) { + uiItemL(layout, "Cached from the first line art modifier.", ICON_INFO); + return; + } + + uiItemR(layout, ptr, "light_contour_object", 0, NULL, ICON_NONE); + + uiLayout *remaining = uiLayoutColumn(layout, false); + uiLayoutSetActive(remaining, has_light); + + uiItemR(remaining, ptr, "shadow_camera_size", 0, NULL, ICON_NONE); + + uiLayout *col = uiLayoutColumn(remaining, true); + uiItemR(col, ptr, "shadow_camera_near", 0, "Near", ICON_NONE); + uiItemR(col, ptr, "shadow_camera_far", 0, "Far", ICON_NONE); + + uiItemR(layout, ptr, "shadow_enclosed_shapes", 0, IFACE_("Eclosed Shapes"), ICON_NONE); } static void options_panel_draw(const bContext *UNUSED(C), Panel *panel) @@ -584,7 +668,7 @@ static void chaining_panel_draw(const bContext *UNUSED(C), Panel *panel) uiItemR(col, ptr, "use_fuzzy_intersections", 0, NULL, ICON_NONE); uiItemR(col, ptr, "use_fuzzy_all", 0, NULL, ICON_NONE); uiItemR(col, ptr, "use_loose_edge_chain", 0, IFACE_("Loose Edges"), ICON_NONE); - uiItemR(col, ptr, "use_loose_as_contour", 0, NULL, ICON_NONE); + uiItemR(col, ptr, "use_loose_as_contour", 0, IFACE_("Loose Edges As Contour"), ICON_NONE); uiItemR(col, ptr, "use_detail_preserve", 0, NULL, ICON_NONE); uiItemR(col, ptr, "use_geometry_space_chain", 0, IFACE_("Geometry Space"), ICON_NONE); @@ -695,6 +779,12 @@ static void panelRegister(ARegionType *region_type) gpencil_modifier_subpanel_register( region_type, "edge_types", "Edge Types", NULL, edge_types_panel_draw, panel_type); + gpencil_modifier_subpanel_register(region_type, + "light_reference", + "Light Reference", + NULL, + options_light_reference_draw, + panel_type); gpencil_modifier_subpanel_register( region_type, "geometry", "Geometry Processing", NULL, options_panel_draw, panel_type); PanelType *occlusion_panel = gpencil_modifier_subpanel_register( diff --git a/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h b/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h index f2744613205..5e9b2556fe0 100644 --- a/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h +++ b/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h @@ -41,6 +41,12 @@ typedef struct LineartTriangle { uint8_t mat_occlusion; uint8_t flags; /* #eLineartTriangleFlags */ + /* target_reference = (obi->obindex | triangle_index) */ + /* higher 12 bits-------^ ^-----index in object, lower 20 bits */ + uint32_t target_reference; + + uint8_t intersection_priority; + /** * 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 @@ -66,6 +72,7 @@ typedef enum eLineArtElementNodeFlag { LRT_ELEMENT_IS_ADDITIONAL = (1 << 0), LRT_ELEMENT_BORDER_ONLY = (1 << 1), LRT_ELEMENT_NO_INTERSECTION = (1 << 2), + LRT_ELEMENT_INTERSECTION_DATA = (1 << 3), } eLineArtElementNodeFlag; typedef struct LineartElementLinkNode { @@ -75,52 +82,80 @@ typedef struct LineartElementLinkNode { void *object_ref; eLineArtElementNodeFlag flags; + /* For edge element link nodes, used for shadow edge matching. */ + int obindex; + /** Per object value, always set, if not enabled by #ObjectLineArt, then it's set to global. */ float crease_threshold; } LineartElementLinkNode; typedef struct LineartEdgeSegment { struct LineartEdgeSegment *next, *prev; - /** at==0: left at==1: right (this is in 2D projected space) */ - double at; - /** Occlusion level after "at" point */ + /** The point after which a property of the segment is changed, e.g. occlusion/material mask etc. + * ratio==0: v1 ratio==1: v2 (this is in 2D projected space), */ + double ratio; + /** Occlusion level after "ratio" point */ uint8_t occlusion; /* Used to filter line art occlusion edges */ uint8_t material_mask_bits; + + /* Lit/shaded flag for shadow is stored here. + * TODO(Yiming): Transfer material masks from shadow results + * onto here so then we can even filter transparent shadows. */ + uint32_t shadow_mask_bits; } LineartEdgeSegment; +typedef struct LineartShadowEdge { + struct LineartShadowEdge *next, *prev; + /* Two end points in framebuffer coordinates viewed from the light source. */ + double fbc1[4], fbc2[4]; + double g1[3], g2[3]; + bool orig1, orig2; + struct LineartEdge *e_ref; + struct LineartEdge *e_ref_light_contour; + struct LineartEdgeSegment *es_ref; /* Only for 3rd stage casting. */ + ListBase shadow_segments; +} LineartShadowEdge; + +enum eLineartShadowSegmentFlag { + LRT_SHADOW_CASTED = 1, + LRT_SHADOW_FACING_LIGHT = 2, +}; + +/* Represents a cutting point on a #LineartShadowEdge */ +typedef struct LineartShadowSegment { + struct LineartShadowSegment *next, *prev; + /* eLineartShadowSegmentFlag */ + int flag; + /* The point after which a property of the segment is changed. e.g. shadow mask/target_ref etc. + * Coordinates in NDC during shadow caluclation but transformed to global linear before cutting + * onto edges during the loading stage of the "actual" rendering. */ + double ratio; + /* Left and right pos, because when casting shadows at some point there will be + * non-continuous cuts, see #lineart_shadow_edge_cut for detailed explaination. */ + double fbc1[4], fbc2[4]; + /* Global position. */ + double g1[4], g2[4]; + uint32_t target_reference; + uint32_t shadow_mask_bits; +} LineartShadowSegment; + typedef struct LineartVert { 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. - */ - uint8_t flag; - } LineartVert; -typedef struct LineartVertIntersection { - struct LineartVert base; - /** Use vert index because we only use this to check vertex equal. This way we save 8 Bytes. */ - int isec1, isec2; - struct LineartTriangle *intersecting_with; -} LineartVertIntersection; - -typedef enum eLineArtVertFlags { - LRT_VERT_HAS_INTERSECTION_DATA = (1 << 0), - LRT_VERT_EDGE_USED = (1 << 1), -} eLineArtVertFlags; - typedef struct LineartEdge { struct LineartVert *v1, *v2; + + /** These two variables are also used to specify original edge and segment during 3rd stage + * reprojection, So we can easily find out the line which results come from. */ struct LineartTriangle *t1, *t2; + ListBase segments; int8_t min_occ; @@ -128,6 +163,19 @@ typedef struct LineartEdge { uint16_t flags; uint8_t intersection_mask; + /** Matches the shadow result, used to determine whether a line is in the shadow or not. + * #edge_identifier usages: + * - Intersection lines: + * ((e->t1->target_reference << 32) | e->t2->target_reference); + * - Other lines: LRT_EDGE_IDENTIFIER(obi, e); + * - After shadow calculation: (search the shadow result and set reference to that); + */ + uint64_t edge_identifier; + + /** - Light contour: original_e->t1->target_reference | original_e->t2->target_reference. + * - Cast shadow: triangle_projected_onto->target_reference. */ + uint64_t target_reference; + /** * Still need this entry because culled lines will not add to object * #LineartElementLinkNode node (known as `eln` internally). @@ -146,8 +194,8 @@ typedef struct LineartEdgeChain { float length; /** Used when re-connecting and grease-pencil stroke generation. */ - int8_t picked; - int8_t level; + uint8_t picked; + uint8_t level; /** Chain now only contains one type of segments */ int type; @@ -155,8 +203,10 @@ typedef struct LineartEdgeChain { int loop_id; uint8_t material_mask_bits; uint8_t intersection_mask; + uint32_t shadow_mask_bits; struct Object *object_ref; + struct Object *silhouette_backdrop; } LineartEdgeChain; typedef struct LineartEdgeChainItem { @@ -170,6 +220,7 @@ typedef struct LineartEdgeChainItem { uint8_t occlusion; uint8_t material_mask_bits; uint8_t intersection_mask; + uint32_t shadow_mask_bits; size_t index; } LineartEdgeChainItem; @@ -201,6 +252,11 @@ enum eLineArtTileRecursiveLimit { #define LRT_TILE_SPLITTING_TRIANGLE_LIMIT 100 #define LRT_TILE_EDGE_COUNT_INITIAL 32 +enum eLineartShadowCameraType { + LRT_SHADOW_CAMERA_DIRECTIONAL = 1, + LRT_SHADOW_CAMERA_POINT = 2, +}; + typedef struct LineartPendingEdges { LineartEdge **array; int max; @@ -216,6 +272,14 @@ typedef struct LineartData { LineartStaticMemPool render_data_pool; /* A pointer to LineartCache::chain_data_pool, which acts as a cache for edge chains. */ LineartStaticMemPool *chain_data_pool; + /* Reference to LineartCache::shadow_data_pool, stay available until the final round of line art + * calculation is finished. */ + LineartStaticMemPool *shadow_data_pool; + + /* Storing shadow edge eln, array, and cuts for shadow information, so it's avaliable when line + * art runs the second time for occlusion. Either a reference to LineartCache::shadow_data_pool + * (shadow stage) or a reference to LineartData::render_data_pool (final stage). */ + LineartStaticMemPool *edge_data_pool; struct _qtree { @@ -230,7 +294,7 @@ typedef struct LineartData { struct LineartBoundingArea *initials; - uint32_t tile_count; + uint32_t initial_tile_count; } qtree; @@ -267,6 +331,14 @@ typedef struct LineartData { bool use_edge_marks; bool use_intersections; bool use_loose; + bool use_light_contour; + bool use_shadow; + bool use_contour_secondary; /* From viewing camera, during shadow calculation. */ + + int shadow_selection; /* Needs to be numeric because it's not just on/off. */ + bool shadow_enclose_shapes; + bool shadow_use_silhouette; + bool fuzzy_intersections; bool fuzzy_everything; bool allow_boundaries; @@ -289,13 +361,21 @@ typedef struct LineartData { bool chain_preserve_details; + bool do_shadow_cast; + bool light_reference_available; + /* Keep an copy of these data so when line art is running it's self-contained. */ bool cam_is_persp; + bool cam_is_persp_secondary; /* "Secondary" ones are from viewing camera (as opposed to shadow + camera), during shadow calculation. */ float cam_obmat[4][4]; + float cam_obmat_secondary[4][4]; double camera_pos[3]; + double camera_pos_secondary[3]; double active_camera_pos[3]; /* Stroke offset calculation may use active or selected camera. */ double near_clip, far_clip; float shift_x, shift_y; + float crease_threshold; float chaining_image_threshold; float angle_splitting_threshold; @@ -303,7 +383,7 @@ typedef struct LineartData { float chain_smooth_tolerance; double view_vector[3]; - + double view_vector_secondary[3]; /* For shadow. */ } conf; LineartElementLinkNode *isect_scheduled_up_to; @@ -313,22 +393,32 @@ typedef struct LineartData { struct LineartPendingEdges pending_edges; int scheduled_count; + /* Intermediate shadow results, list of LineartShadowEdge */ + LineartShadowEdge *shadow_edges; + int shadow_edges_count; + ListBase chains; ListBase wasted_cuts; + ListBase wasted_shadow_cuts; SpinLock lock_cuts; SpinLock lock_task; } LineartData; typedef struct LineartCache { - /** Separate memory pool for chain data, this goes to the cache, so when we free the main pool, - * chains will still be available. */ + /** Separate memory pool for chain data and shadow, this goes to the cache, so when we free the + * main pool, chains and shadows will still be available. */ LineartStaticMemPool chain_data_pool; + LineartStaticMemPool shadow_data_pool; /** A copy of ld->chains so we have that data available after ld has been destroyed. */ ListBase chains; + /** Shadow-computed feature lines from original meshes to be matched with the second load of + * meshes thus providing lit/shade info in the second run of line art. */ + ListBase shadow_elns; + /** Cache only contains edge types specified in this variable. */ uint16_t all_enabled_edge_types; } LineartCache; @@ -348,6 +438,14 @@ typedef enum eLineartTriangleFlags { LRT_TRIANGLE_MAT_BACK_FACE_CULLING = (1 << 5), } eLineartTriangleFlags; +#define LRT_SHADOW_MASK_UNDEFINED 0 +#define LRT_SHADOW_MASK_LIT (1 << 0) +#define LRT_SHADOW_MASK_SHADED (1 << 1) +#define LRT_SHADOW_MASK_ENCLOSED_SHAPE (1 << 2) +#define LRT_SHADOW_MASK_INHIBITED (1 << 3) +#define LRT_SHADOW_SILHOUETTE_ERASED_GROUP (1 << 4) +#define LRT_SHADOW_SILHOUETTE_ERASED_OBJECT (1 << 5) + /** * Controls how many edges a worker thread is processing at one request. * There's no significant performance impact on choosing different values. @@ -368,6 +466,14 @@ typedef struct LineartRenderTaskInfo { } LineartRenderTaskInfo; +#define LRT_OBINDEX_SHIFT 20 +#define LRT_OBINDEX_LOWER 0x0FFFFF /* Lower 20 bits. */ +#define LRT_OBINDEX_HIGHER 0xFFF00000 /* Higher 12 bits. */ +#define LRT_EDGE_IDENTIFIER(obi, e) \ + (((uint64_t)(obi->obindex | (e->v1->index & LRT_OBINDEX_LOWER)) << 32) | \ + (obi->obindex | (e->v2->index & LRT_OBINDEX_LOWER))) +#define LRT_LIGHT_CONTOUR_TARGET 0xFFFFFFFF + typedef struct LineartObjectInfo { struct LineartObjectInfo *next; struct Object *original_ob; @@ -378,8 +484,12 @@ typedef struct LineartObjectInfo { LineartElementLinkNode *v_eln; int usage; uint8_t override_intersection_mask; + uint8_t intersection_priority; int global_i_offset; + /* Shifted LRT_OBINDEX_SHIFT bits to be combined with object triangle index. */ + int obindex; + bool free_use_mesh; /** NOTE: Data inside #pending_edges are allocated with MEM_xxx call instead of in pool. */ @@ -394,6 +504,7 @@ typedef struct LineartObjectLoadTaskInfo { LineartObjectInfo *pending; /* Used to spread the load across several threads. This can not overflow. */ uint64_t total_faces; + ListBase *shadow_elns; } LineartObjectLoadTaskInfo; /** @@ -466,6 +577,10 @@ typedef struct LineartBoundingArea { #define LRT_DOUBLE_CLOSE_ENOUGH_TRI(a, b) \ (((a) + DBL_TRIANGLE_LIM) >= (b) && ((a)-DBL_TRIANGLE_LIM) <= (b)) +#define LRT_CLOSE_LOOSER_v3(a, b) \ + (LRT_DOUBLE_CLOSE_LOOSER(a[0], b[0]) && LRT_DOUBLE_CLOSE_LOOSER(a[1], b[1]) && \ + LRT_DOUBLE_CLOSE_LOOSER(a[2], b[2])) + /* Notes on this function: * * r_ratio: The ratio on segment a1-a2. When r_ratio is very close to zero or one, it @@ -478,10 +593,10 @@ typedef struct LineartBoundingArea { * segment a is shared with segment b. If it's a1 then r_ratio is 0, else then r_ratio is 1. This * extra information is needed for line art occlusion stage to work correctly in such cases. */ -BLI_INLINE int lineart_intersect_seg_seg(const double *a1, - const double *a2, - const double *b1, - const double *b2, +BLI_INLINE int lineart_intersect_seg_seg(const double a1[2], + const double a2[2], + const double b1[2], + const double b2[2], double *r_ratio, bool *r_aligned) { @@ -622,6 +737,99 @@ BLI_INLINE int lineart_intersect_seg_seg(const double *a1, #endif } +/* This is a special convenience function to lineart_intersect_seg_seg which will return true when + * the intersection point falls in the range of a1-a2 but not necessarily in the range of b1-b2. */ +BLI_INLINE int lineart_line_isec_2d_ignore_line2pos(const double a1[2], + const double a2[2], + const double b1[2], + const double b2[2], + double *r_a_ratio) +{ + /* The define here is used to check how vector or slope method handles boundary cases. The result + * of lim(div->0) and lim(k->0) could both produce some unwanted flickers in line art, the + * influence of which is still not fully understood, so keep the switch there for futher + * investigations. */ +#define USE_VECTOR_LINE_INTERSECTION_IGN +#ifdef USE_VECTOR_LINE_INTERSECTION_IGN + + /* 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; + + if (fabs(a2[0] - a1[0]) > fabs(a2[1] - a1[1])) { + *r_a_ratio = ratiod(a1[0], a2[0], rx); + if ((*r_a_ratio) >= -DBL_EDGE_LIM && (*r_a_ratio) <= 1 + DBL_EDGE_LIM) { + return 1; + } + return 0; + } + + *r_a_ratio = ratiod(a1[1], a2[1], ry); + if ((*r_a_ratio) >= -DBL_EDGE_LIM && (*r_a_ratio) <= 1 + DBL_EDGE_LIM) { + 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)) { + *r_a_ratio = 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); + *r_a_ratio = 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); + *r_a_ratio = 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; + + *r_a_ratio = ratio; + } + } + + if (ratio <= 0 || ratio >= 1) + return 0; + + return 1; +#endif +} + struct Depsgraph; struct LineartGpencilModifierData; struct LineartData; @@ -646,6 +854,7 @@ void MOD_lineart_chain_clip_at_border(LineartData *ld); void MOD_lineart_chain_split_angle(LineartData *ld, float angle_threshold_rad); void MOD_lineart_smooth_chains(LineartData *ld, float tolerance); void MOD_lineart_chain_offset_towards_camera(LineartData *ld, float dist, bool use_custom_camera); +void MOD_lineart_chain_find_silhouette_backdrop_objects(LineartData *ld); int MOD_lineart_chain_count(const LineartEdgeChain *ec); void MOD_lineart_chain_clear_picked_flag(LineartCache *lc); @@ -694,6 +903,8 @@ void MOD_lineart_gpencil_generate(LineartCache *cache, uint8_t intersection_mask, int16_t thickness, float opacity, + uint8_t shadow_selection, + uint8_t silhouette_mode, const char *source_vgname, const char *vgname, int modifier_flags); diff --git a/source/blender/gpencil_modifiers/intern/lineart/lineart_chain.c b/source/blender/gpencil_modifiers/intern/lineart/lineart_chain.c index 1d84bb8e232..4ee18c6738f 100644 --- a/source/blender/gpencil_modifiers/intern/lineart/lineart_chain.c +++ b/source/blender/gpencil_modifiers/intern/lineart/lineart_chain.c @@ -17,13 +17,16 @@ #define LRT_OTHER_VERT(e, vt) ((vt) == (e)->v1 ? (e)->v2 : ((vt) == (e)->v2 ? (e)->v1 : NULL)) +struct Object; + /* 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 LineartEdge *lineart_line_get_connected(LineartBoundingArea *ba, LineartVert *vt, LineartVert **new_vt, int match_flag, - uint8_t match_isec_mask) + uint8_t match_isec_mask, + struct Object *match_isec_object) { for (int i = 0; i < ba->line_count; i++) { LineartEdge *n_e = ba->linked_lines[i]; @@ -46,6 +49,9 @@ static LineartEdge *lineart_line_get_connected(LineartBoundingArea *ba, } if (n_e->flags & LRT_EDGE_FLAG_INTERSECTION) { + if (n_e->object_ref != match_isec_object) { + continue; + } if (vt->fbcoord[0] == n_e->v1->fbcoord[0] && vt->fbcoord[1] == n_e->v1->fbcoord[1]) { *new_vt = LRT_OTHER_VERT(n_e, n_e->v1); return n_e; @@ -87,12 +93,13 @@ static bool lineart_point_overlapping(LineartEdgeChainItem *eci, static LineartEdgeChainItem *lineart_chain_append_point(LineartData *ld, LineartEdgeChain *ec, - float *fbcoord, - float *gpos, - float *normal, + float fbcoord[4], + float gpos[3], + float normal[3], uint8_t type, int level, uint8_t material_mask_bits, + uint32_t shadow_mask_bits, size_t index) { LineartEdgeChainItem *eci; @@ -105,6 +112,7 @@ static LineartEdgeChainItem *lineart_chain_append_point(LineartData *ld, old_eci->line_type = type; old_eci->occlusion = level; old_eci->material_mask_bits = material_mask_bits; + old_eci->shadow_mask_bits = shadow_mask_bits; return old_eci; } @@ -117,6 +125,7 @@ static LineartEdgeChainItem *lineart_chain_append_point(LineartData *ld, eci->line_type = type & LRT_EDGE_FLAG_ALL_TYPE; eci->occlusion = level; eci->material_mask_bits = material_mask_bits; + eci->shadow_mask_bits = shadow_mask_bits; BLI_addtail(&ec->chain, eci); return eci; @@ -124,12 +133,13 @@ static LineartEdgeChainItem *lineart_chain_append_point(LineartData *ld, static LineartEdgeChainItem *lineart_chain_prepend_point(LineartData *ld, LineartEdgeChain *ec, - float *fbcoord, - float *gpos, - float *normal, + float fbcoord[4], + float gpos[3], + float normal[3], uint8_t type, int level, uint8_t material_mask_bits, + uint32_t shadow_mask_bits, size_t index) { LineartEdgeChainItem *eci; @@ -147,6 +157,7 @@ static LineartEdgeChainItem *lineart_chain_prepend_point(LineartData *ld, eci->line_type = type & LRT_EDGE_FLAG_ALL_TYPE; eci->occlusion = level; eci->material_mask_bits = material_mask_bits; + eci->shadow_mask_bits = shadow_mask_bits; BLI_addhead(&ec->chain, eci); return eci; @@ -160,6 +171,7 @@ void MOD_lineart_chain_feature_lines(LineartData *ld) LineartEdgeSegment *es; int last_occlusion; uint8_t last_transparency; + uint32_t last_shadow; /* Used when converting from double. */ float use_fbcoord[4]; float use_gpos[3]; @@ -219,9 +231,10 @@ void MOD_lineart_chain_feature_lines(LineartData *ld) e->flags, es->occlusion, es->material_mask_bits, + es->shadow_mask_bits, e->v1->index); while (ba && (new_e = lineart_line_get_connected( - ba, new_vt, &new_vt, e->flags, e->intersection_mask))) { + ba, new_vt, &new_vt, e->flags, ec->intersection_mask, ec->object_ref))) { new_e->flags |= LRT_EDGE_FLAG_CHAIN_PICKED; if (new_e->t1 || new_e->t2) { @@ -243,8 +256,8 @@ void MOD_lineart_chain_feature_lines(LineartData *ld) for (es = new_e->segments.last; es; es = es->prev) { double gpos[3], lpos[3]; double *lfb = new_e->v1->fbcoord, *rfb = new_e->v2->fbcoord; - double global_at = lfb[3] * es->at / (es->at * lfb[3] + (1 - es->at) * rfb[3]); - interp_v3_v3v3_db(lpos, new_e->v1->fbcoord, new_e->v2->fbcoord, es->at); + double global_at = lfb[3] * es->ratio / (es->ratio * lfb[3] + (1 - es->ratio) * rfb[3]); + interp_v3_v3v3_db(lpos, new_e->v1->fbcoord, new_e->v2->fbcoord, es->ratio); interp_v3_v3v3_db(gpos, new_e->v1->gloc, new_e->v2->gloc, global_at); use_fbcoord[3] = interpf(new_e->v2->fbcoord[3], new_e->v1->fbcoord[3], global_at); POS_TO_FLOAT(lpos, gpos) @@ -256,21 +269,24 @@ void MOD_lineart_chain_feature_lines(LineartData *ld) new_e->flags, es->occlusion, es->material_mask_bits, + es->shadow_mask_bits, new_e->v1->index); last_occlusion = es->occlusion; last_transparency = es->material_mask_bits; + last_shadow = es->shadow_mask_bits; } } else if (new_vt == new_e->v2) { es = new_e->segments.first; last_occlusion = es->occlusion; last_transparency = es->material_mask_bits; + last_shadow = es->shadow_mask_bits; es = es->next; for (; es; es = es->next) { double gpos[3], lpos[3]; double *lfb = new_e->v1->fbcoord, *rfb = new_e->v2->fbcoord; - double global_at = lfb[3] * es->at / (es->at * lfb[3] + (1 - es->at) * rfb[3]); - interp_v3_v3v3_db(lpos, new_e->v1->fbcoord, new_e->v2->fbcoord, es->at); + double global_at = lfb[3] * es->ratio / (es->ratio * lfb[3] + (1 - es->ratio) * rfb[3]); + interp_v3_v3v3_db(lpos, new_e->v1->fbcoord, new_e->v2->fbcoord, es->ratio); interp_v3_v3v3_db(gpos, new_e->v1->gloc, new_e->v2->gloc, global_at); use_fbcoord[3] = interpf(new_e->v2->fbcoord[3], new_e->v1->fbcoord[3], global_at); POS_TO_FLOAT(lpos, gpos) @@ -282,9 +298,11 @@ void MOD_lineart_chain_feature_lines(LineartData *ld) new_e->flags, last_occlusion, last_transparency, + last_shadow, new_e->v2->index); last_occlusion = es->occlusion; last_transparency = es->material_mask_bits; + last_shadow = es->shadow_mask_bits; } VERT_COORD_TO_FLOAT(new_e->v2); lineart_chain_prepend_point(ld, @@ -295,6 +313,7 @@ void MOD_lineart_chain_feature_lines(LineartData *ld) new_e->flags, last_occlusion, last_transparency, + last_shadow, new_e->v2->index); } ba = MOD_lineart_get_bounding_area(ld, new_vt->fbcoord[0], new_vt->fbcoord[1]); @@ -318,13 +337,14 @@ void MOD_lineart_chain_feature_lines(LineartData *ld) /* Step 2: Adding all cuts from the given line, so we can continue connecting the right side * of the line. */ es = e->segments.first; - last_occlusion = ((LineartEdgeSegment *)es)->occlusion; - last_transparency = ((LineartEdgeSegment *)es)->material_mask_bits; + last_occlusion = es->occlusion; + last_transparency = es->material_mask_bits; + last_shadow = es->shadow_mask_bits; for (es = es->next; es; es = es->next) { double gpos[3], lpos[3]; double *lfb = e->v1->fbcoord, *rfb = e->v2->fbcoord; - double global_at = lfb[3] * es->at / (es->at * lfb[3] + (1 - es->at) * rfb[3]); - interp_v3_v3v3_db(lpos, e->v1->fbcoord, e->v2->fbcoord, es->at); + double global_at = lfb[3] * es->ratio / (es->ratio * lfb[3] + (1 - es->ratio) * rfb[3]); + interp_v3_v3v3_db(lpos, e->v1->fbcoord, e->v2->fbcoord, es->ratio); interp_v3_v3v3_db(gpos, e->v1->gloc, e->v2->gloc, global_at); use_fbcoord[3] = interpf(e->v2->fbcoord[3], e->v1->fbcoord[3], global_at); POS_TO_FLOAT(lpos, gpos) @@ -336,9 +356,11 @@ void MOD_lineart_chain_feature_lines(LineartData *ld) e->flags, es->occlusion, es->material_mask_bits, + es->shadow_mask_bits, e->v1->index); last_occlusion = es->occlusion; last_transparency = es->material_mask_bits; + last_shadow = es->shadow_mask_bits; } VERT_COORD_TO_FLOAT(e->v2) lineart_chain_append_point(ld, @@ -349,13 +371,14 @@ void MOD_lineart_chain_feature_lines(LineartData *ld) e->flags, last_occlusion, last_transparency, + last_shadow, e->v2->index); /* Step 3: grow right. */ ba = MOD_lineart_get_bounding_area(ld, e->v2->fbcoord[0], e->v2->fbcoord[1]); new_vt = e->v2; while (ba && (new_e = lineart_line_get_connected( - ba, new_vt, &new_vt, e->flags, e->intersection_mask))) { + ba, new_vt, &new_vt, e->flags, ec->intersection_mask, ec->object_ref))) { new_e->flags |= LRT_EDGE_FLAG_CHAIN_PICKED; if (new_e->t1 || new_e->t2) { @@ -381,18 +404,21 @@ void MOD_lineart_chain_feature_lines(LineartData *ld) es = new_e->segments.last; last_occlusion = es->occlusion; last_transparency = es->material_mask_bits; + last_shadow = es->shadow_mask_bits; /* Fix leading vertex occlusion. */ eci->occlusion = last_occlusion; eci->material_mask_bits = last_transparency; + eci->shadow_mask_bits = last_shadow; for (es = new_e->segments.last; es; es = es->prev) { double gpos[3], lpos[3]; double *lfb = new_e->v1->fbcoord, *rfb = new_e->v2->fbcoord; - double global_at = lfb[3] * es->at / (es->at * lfb[3] + (1 - es->at) * rfb[3]); - interp_v3_v3v3_db(lpos, new_e->v1->fbcoord, new_e->v2->fbcoord, es->at); + double global_at = lfb[3] * es->ratio / (es->ratio * lfb[3] + (1 - es->ratio) * rfb[3]); + interp_v3_v3v3_db(lpos, new_e->v1->fbcoord, new_e->v2->fbcoord, es->ratio); interp_v3_v3v3_db(gpos, new_e->v1->gloc, new_e->v2->gloc, global_at); use_fbcoord[3] = interpf(new_e->v2->fbcoord[3], new_e->v1->fbcoord[3], global_at); last_occlusion = es->prev ? es->prev->occlusion : last_occlusion; last_transparency = es->prev ? es->prev->material_mask_bits : last_transparency; + last_shadow = es->prev ? es->prev->shadow_mask_bits : last_shadow; POS_TO_FLOAT(lpos, gpos) lineart_chain_append_point(ld, ec, @@ -402,6 +428,7 @@ void MOD_lineart_chain_feature_lines(LineartData *ld) new_e->flags, last_occlusion, last_transparency, + last_shadow, new_e->v1->index); } } @@ -409,14 +436,16 @@ void MOD_lineart_chain_feature_lines(LineartData *ld) es = new_e->segments.first; last_occlusion = es->occlusion; last_transparency = es->material_mask_bits; + last_shadow = es->shadow_mask_bits; eci->occlusion = last_occlusion; eci->material_mask_bits = last_transparency; + eci->shadow_mask_bits = last_shadow; es = es->next; for (; es; es = es->next) { double gpos[3], lpos[3]; double *lfb = new_e->v1->fbcoord, *rfb = new_e->v2->fbcoord; - double global_at = lfb[3] * es->at / (es->at * lfb[3] + (1 - es->at) * rfb[3]); - interp_v3_v3v3_db(lpos, new_e->v1->fbcoord, new_e->v2->fbcoord, es->at); + double global_at = lfb[3] * es->ratio / (es->ratio * lfb[3] + (1 - es->ratio) * rfb[3]); + interp_v3_v3v3_db(lpos, new_e->v1->fbcoord, new_e->v2->fbcoord, es->ratio); interp_v3_v3v3_db(gpos, new_e->v1->gloc, new_e->v2->gloc, global_at); use_fbcoord[3] = interpf(new_e->v2->fbcoord[3], new_e->v1->fbcoord[3], global_at); POS_TO_FLOAT(lpos, gpos) @@ -428,9 +457,11 @@ void MOD_lineart_chain_feature_lines(LineartData *ld) new_e->flags, es->occlusion, es->material_mask_bits, + es->shadow_mask_bits, new_e->v2->index); last_occlusion = es->occlusion; last_transparency = es->material_mask_bits; + last_shadow = es->shadow_mask_bits; } VERT_COORD_TO_FLOAT(new_e->v2) lineart_chain_append_point(ld, @@ -441,6 +472,7 @@ void MOD_lineart_chain_feature_lines(LineartData *ld) new_e->flags, last_occlusion, last_transparency, + last_shadow, new_e->v2->index); } ba = MOD_lineart_get_bounding_area(ld, new_vt->fbcoord[0], new_vt->fbcoord[1]); @@ -509,7 +541,7 @@ static void lineart_bounding_area_link_point_recursive(LineartData *ld, { if (root->child == NULL) { LineartChainRegisterEntry *cre = lineart_list_append_pointer_pool_sized( - &root->linked_chains, &ld->render_data_pool, ec, sizeof(LineartChainRegisterEntry)); + &root->linked_chains, ld->chain_data_pool, ec, sizeof(LineartChainRegisterEntry)); cre->eci = eci; @@ -565,6 +597,7 @@ static bool lineart_chain_fix_ambiguous_segments(LineartEdgeChain *ec, int fixed_occ = last_matching_eci->occlusion; uint8_t fixed_mask = last_matching_eci->material_mask_bits; + uint32_t fixed_shadow = last_matching_eci->shadow_mask_bits; LineartEdgeChainItem *can_skip_to = NULL; LineartEdgeChainItem *last_eci = last_matching_eci; @@ -579,7 +612,8 @@ static bool lineart_chain_fix_ambiguous_segments(LineartEdgeChain *ec, if (eci->occlusion < fixed_occ) { break; } - if (eci->material_mask_bits == fixed_mask && eci->occlusion == fixed_occ) { + if (eci->material_mask_bits == fixed_mask && eci->occlusion == fixed_occ && + eci->shadow_mask_bits == fixed_shadow) { can_skip_to = eci; } } @@ -589,12 +623,14 @@ static bool lineart_chain_fix_ambiguous_segments(LineartEdgeChain *ec, LineartEdgeChainItem *next_eci; for (LineartEdgeChainItem *eci = last_matching_eci->next; eci != can_skip_to; eci = next_eci) { next_eci = eci->next; - if (eci->material_mask_bits == fixed_mask && eci->occlusion == fixed_occ) { + if (eci->material_mask_bits == fixed_mask && eci->occlusion == fixed_occ && + eci->shadow_mask_bits == fixed_shadow) { continue; } if (preserve_details) { eci->material_mask_bits = fixed_mask; eci->occlusion = fixed_occ; + eci->shadow_mask_bits = fixed_shadow; } else { BLI_remlink(&ec->chain, eci); @@ -628,11 +664,14 @@ void MOD_lineart_chain_split_for_fixed_occlusion(LineartData *ld) LineartEdgeChainItem *first_eci = (LineartEdgeChainItem *)ec->chain.first; int fixed_occ = first_eci->occlusion; uint8_t fixed_mask = first_eci->material_mask_bits; + uint32_t fixed_shadow = first_eci->shadow_mask_bits; ec->level = fixed_occ; ec->material_mask_bits = fixed_mask; + ec->shadow_mask_bits = fixed_shadow; for (eci = first_eci->next; eci; eci = next_eci) { next_eci = eci->next; - if (eci->occlusion != fixed_occ || eci->material_mask_bits != fixed_mask) { + if (eci->occlusion != fixed_occ || eci->material_mask_bits != fixed_mask || + eci->shadow_mask_bits != fixed_shadow) { if (next_eci) { if (lineart_point_overlapping(next_eci, eci->pos[0], eci->pos[1], 1e-5)) { continue; @@ -649,6 +688,7 @@ void MOD_lineart_chain_split_for_fixed_occlusion(LineartData *ld) /* Set the same occlusion level for the end vertex, so when further connection is needed * the backwards occlusion info is also correct. */ eci->occlusion = fixed_occ; + eci->shadow_mask_bits = fixed_shadow; eci->material_mask_bits = fixed_mask; /* No need to split at the last point anyway. */ break; @@ -670,6 +710,7 @@ void MOD_lineart_chain_split_for_fixed_occlusion(LineartData *ld) eci->line_type, fixed_occ, fixed_mask, + fixed_shadow, eci->index); new_ec->object_ref = ec->object_ref; new_ec->type = ec->type; @@ -677,8 +718,10 @@ void MOD_lineart_chain_split_for_fixed_occlusion(LineartData *ld) ec = new_ec; fixed_occ = eci->occlusion; fixed_mask = eci->material_mask_bits; + fixed_shadow = eci->shadow_mask_bits; ec->level = fixed_occ; ec->material_mask_bits = fixed_mask; + ec->shadow_mask_bits = fixed_shadow; } } } @@ -749,6 +792,7 @@ static LineartChainRegisterEntry *lineart_chain_get_closest_cre(LineartData *ld, int occlusion, uint8_t material_mask_bits, uint8_t isec_mask, + uint32_t shadow_mask, int loop_id, float dist, float *result_new_len, @@ -779,7 +823,7 @@ static LineartChainRegisterEntry *lineart_chain_get_closest_cre(LineartData *ld, } if (cre->ec == ec || (!cre->ec->chain.first) || (cre->ec->level != occlusion) || (cre->ec->material_mask_bits != material_mask_bits) || - (cre->ec->intersection_mask != isec_mask)) { + (cre->ec->intersection_mask != isec_mask) || (cre->ec->shadow_mask_bits != shadow_mask)) { continue; } if (!ld->conf.fuzzy_everything) { @@ -826,6 +870,7 @@ static LineartChainRegisterEntry *lineart_chain_get_closest_cre(LineartData *ld, occlusion, \ material_mask_bits, \ isec_mask, \ + shadow_mask, \ loop_id, \ dist, \ &adjacent_new_len, \ @@ -856,8 +901,9 @@ void MOD_lineart_chain_connect(LineartData *ld) LineartChainRegisterEntry *closest_cre_l, *closest_cre_r, *closest_cre; float dist = ld->conf.chaining_image_threshold; float dist_l, dist_r; - int occlusion, reverse_main, loop_id; - uint8_t material_mask_bits, isec_mask; + int reverse_main, loop_id; + uint8_t occlusion, material_mask_bits, isec_mask; + uint32_t shadow_mask; ListBase swap = {0}; if (ld->conf.chaining_image_threshold < 0.0001) { @@ -871,7 +917,7 @@ void MOD_lineart_chain_connect(LineartData *ld) while ((ec = BLI_pophead(&swap)) != NULL) { ec->next = ec->prev = NULL; - if (ec->picked) { + if (ec->picked || ec->chain.first == ec->chain.last) { continue; } BLI_addtail(&ld->chains, ec); @@ -884,6 +930,7 @@ void MOD_lineart_chain_connect(LineartData *ld) occlusion = ec->level; material_mask_bits = ec->material_mask_bits; isec_mask = ec->intersection_mask; + shadow_mask = ec->shadow_mask_bits; eci_l = ec->chain.first; eci_r = ec->chain.last; @@ -896,6 +943,7 @@ void MOD_lineart_chain_connect(LineartData *ld) occlusion, material_mask_bits, isec_mask, + shadow_mask, loop_id, dist, &dist_l, @@ -907,6 +955,7 @@ void MOD_lineart_chain_connect(LineartData *ld) occlusion, material_mask_bits, isec_mask, + shadow_mask, loop_id, dist, &dist_r, @@ -1072,20 +1121,22 @@ static LineartEdgeChainItem *lineart_chain_create_crossing_point(LineartData *ld LineartEdgeChainItem *eci_outside) { float isec[2]; - float LU[2] = {-1.0f, 1.0f}, LB[2] = {-1.0f, -1.0f}, RU[2] = {1.0f, 1.0f}, RB[2] = {1.0f, -1.0f}; + /* l: left, r: right, b: bottom, u: top. */ + float ref_lu[2] = {-1.0f, 1.0f}, ref_lb[2] = {-1.0f, -1.0f}, ref_ru[2] = {1.0f, 1.0f}, + ref_rb[2] = {1.0f, -1.0f}; bool found = false; LineartEdgeChainItem *eci2 = eci_outside, *eci1 = eci_inside; if (eci2->pos[0] < -1.0f) { - found = (isect_seg_seg_v2_point(eci1->pos, eci2->pos, LU, LB, isec) > 0); + found = (isect_seg_seg_v2_point(eci1->pos, eci2->pos, ref_lu, ref_lb, isec) > 0); } if (!found && eci2->pos[0] > 1.0f) { - found = (isect_seg_seg_v2_point(eci1->pos, eci2->pos, RU, RB, isec) > 0); + found = (isect_seg_seg_v2_point(eci1->pos, eci2->pos, ref_ru, ref_rb, isec) > 0); } if (!found && eci2->pos[1] < -1.0f) { - found = (isect_seg_seg_v2_point(eci1->pos, eci2->pos, LB, RB, isec) > 0); + found = (isect_seg_seg_v2_point(eci1->pos, eci2->pos, ref_lb, ref_rb, isec) > 0); } if (!found && eci2->pos[1] > 1.0f) { - found = (isect_seg_seg_v2_point(eci1->pos, eci2->pos, LU, RU, isec) > 0); + found = (isect_seg_seg_v2_point(eci1->pos, eci2->pos, ref_lu, ref_ru, isec) > 0); } if (UNLIKELY(!found)) { @@ -1126,7 +1177,7 @@ void MOD_lineart_chain_clip_at_border(LineartData *ld) LineartEdgeChainItem *first_eci = (LineartEdgeChainItem *)ec->chain.first; is_inside = LRT_ECI_INSIDE(first_eci) ? true : false; if (!is_inside) { - ec->picked = true; + ec->picked = 1; } for (eci = first_eci->next; eci; eci = next_eci) { next_eci = eci->next; @@ -1219,6 +1270,7 @@ void MOD_lineart_chain_split_angle(LineartData *ld, float angle_threshold_rad) eci->line_type, ec->level, eci->material_mask_bits, + eci->shadow_mask_bits, eci->index); new_ec->object_ref = ec->object_ref; new_ec->type = ec->type; @@ -1226,6 +1278,7 @@ void MOD_lineart_chain_split_angle(LineartData *ld, float angle_threshold_rad) new_ec->loop_id = ec->loop_id; new_ec->intersection_mask = ec->intersection_mask; new_ec->material_mask_bits = ec->material_mask_bits; + new_ec->shadow_mask_bits = ec->shadow_mask_bits; ec = new_ec; } } @@ -1270,3 +1323,19 @@ void MOD_lineart_chain_offset_towards_camera(LineartData *ld, float dist, bool u } } } + +void MOD_lineart_chain_find_silhouette_backdrop_objects(LineartData *ld) +{ + LISTBASE_FOREACH (LineartEdgeChain *, ec, &ld->chains) { + if (ec->type == LRT_EDGE_FLAG_CONTOUR && + ec->shadow_mask_bits & LRT_SHADOW_SILHOUETTE_ERASED_GROUP) { + uint32_t target = ec->shadow_mask_bits & LRT_OBINDEX_HIGHER; + LineartElementLinkNode *eln = lineart_find_matching_eln(&ld->geom.line_buffer_pointers, + target); + if (!eln) { + continue; + } + ec->silhouette_backdrop = eln->object_ref; + } + } +} \ No newline at end of file diff --git a/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c b/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c index 7d0150c9f9e..c17827b7228 100644 --- a/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c +++ b/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c @@ -39,6 +39,7 @@ #include "DNA_camera_types.h" #include "DNA_collection_types.h" #include "DNA_gpencil_types.h" +#include "DNA_light_types.h" #include "DNA_material_types.h" #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" @@ -78,44 +79,29 @@ typedef struct LineartIsecData { int thread_count; } LineartIsecData; -static LineartBoundingArea *lineart_edge_first_bounding_area(LineartData *ld, LineartEdge *e); - static void lineart_bounding_area_link_edge(LineartData *ld, LineartBoundingArea *root_ba, LineartEdge *e); -static LineartBoundingArea *lineart_bounding_area_next(LineartBoundingArea *this, - LineartEdge *e, - double x, - double y, - double k, - int positive_x, - int positive_y, - double *next_x, - double *next_y); - static bool lineart_get_edge_bounding_areas( LineartData *ld, LineartEdge *e, int *rowbegin, int *rowend, int *colbegin, int *colend); -static bool lineart_triangle_edge_image_space_occlusion(SpinLock *spl, - const LineartTriangle *tri, +static bool lineart_triangle_edge_image_space_occlusion(const LineartTriangle *tri, const LineartEdge *e, 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 double m_view_projection[4][4], + const double camera_dir[3], const float cam_shift_x, const float cam_shift_y, double *from, double *to); -static void lineart_add_edge_to_array(LineartPendingEdges *pe, LineartEdge *e); - static void lineart_bounding_area_link_triangle(LineartData *ld, LineartBoundingArea *root_ba, LineartTriangle *tri, - double *LRUB, + double l_r_u_b[4], int recursive, int recursive_level, bool do_intersection, @@ -154,19 +140,20 @@ static LineartEdgeSegment *lineart_give_segment(LineartData *ld) BLI_spin_unlock(&ld->lock_cuts); /* Otherwise allocate some new memory. */ - return (LineartEdgeSegment *)lineart_mem_acquire_thread(&ld->render_data_pool, + return (LineartEdgeSegment *)lineart_mem_acquire_thread(ld->edge_data_pool, sizeof(LineartEdgeSegment)); } /** * Cuts the edge in image space and mark occlusion level for each segment. */ -static void lineart_edge_cut(LineartData *ld, - LineartEdge *e, - double start, - double end, - uchar material_mask_bits, - uchar mat_occlusion) +void lineart_edge_cut(LineartData *ld, + LineartEdge *e, + double start, + double end, + uchar material_mask_bits, + uchar mat_occlusion, + uint32_t shadow_bits) { LineartEdgeSegment *seg, *i_seg, *next_seg, *prev_seg; LineartEdgeSegment *cut_start_before = 0, *cut_end_before = 0; @@ -198,7 +185,7 @@ static void lineart_edge_cut(LineartData *ld, /* Not using a list iteration macro because of it more clear when using for loops to iterate * through the segments. */ for (seg = e->segments.first; seg; seg = seg->next) { - if (LRT_DOUBLE_CLOSE_ENOUGH(seg->at, start)) { + if (LRT_DOUBLE_CLOSE_ENOUGH(seg->ratio, start)) { cut_start_before = seg; new_seg1 = cut_start_before; break; @@ -207,7 +194,7 @@ static void lineart_edge_cut(LineartData *ld, break; } i_seg = seg->next; - if (i_seg->at > start + 1e-09 && start > seg->at) { + if (i_seg->ratio > start + 1e-09 && start > seg->ratio) { cut_start_before = i_seg; new_seg1 = lineart_give_segment(ld); break; @@ -217,15 +204,15 @@ static void lineart_edge_cut(LineartData *ld, untouched = 1; } for (seg = cut_start_before; seg; seg = seg->next) { - /* We tried to cut at existing cutting point (e.g. where the line's occluded by a triangle + /* We tried to cut ratio existing cutting point (e.g. where the line's occluded by a triangle * strip). */ - if (LRT_DOUBLE_CLOSE_ENOUGH(seg->at, end)) { + if (LRT_DOUBLE_CLOSE_ENOUGH(seg->ratio, end)) { cut_end_before = seg; new_seg2 = cut_end_before; break; } - /* This check is to prevent `es->at == 1.0` (where we don't need to cut because we are at the - * end point). */ + /* This check is to prevent `es->ratio == 1.0` (where we don't need to cut because we are ratio + * the end point). */ if (!seg->next && LRT_DOUBLE_CLOSE_ENOUGH(1, end)) { cut_end_before = seg; new_seg2 = cut_end_before; @@ -233,7 +220,7 @@ static void lineart_edge_cut(LineartData *ld, break; } /* When an actual cut is needed in the line. */ - if (seg->at > end) { + if (seg->ratio > end) { cut_end_before = seg; new_seg2 = lineart_give_segment(ld); break; @@ -261,6 +248,7 @@ static void lineart_edge_cut(LineartData *ld, if (i_seg) { new_seg1->occlusion = i_seg->occlusion; new_seg1->material_mask_bits = i_seg->material_mask_bits; + new_seg1->shadow_mask_bits = i_seg->shadow_mask_bits; } BLI_insertlinkbefore(&e->segments, cut_start_before, new_seg1); } @@ -272,6 +260,7 @@ static void lineart_edge_cut(LineartData *ld, i_seg = e->segments.last; new_seg1->occlusion = i_seg->occlusion; new_seg1->material_mask_bits = i_seg->material_mask_bits; + new_seg1->shadow_mask_bits = i_seg->shadow_mask_bits; BLI_addtail(&e->segments, new_seg1); } if (cut_end_before) { @@ -281,6 +270,7 @@ static void lineart_edge_cut(LineartData *ld, if (i_seg) { new_seg2->occlusion = i_seg->occlusion; new_seg2->material_mask_bits = i_seg->material_mask_bits; + new_seg2->shadow_mask_bits = i_seg->shadow_mask_bits; } BLI_insertlinkbefore(&e->segments, cut_end_before, new_seg2); } @@ -289,14 +279,17 @@ static void lineart_edge_cut(LineartData *ld, i_seg = e->segments.last; new_seg2->occlusion = i_seg->occlusion; new_seg2->material_mask_bits = i_seg->material_mask_bits; - BLI_addtail(&e->segments, new_seg2); + new_seg2->shadow_mask_bits = i_seg->shadow_mask_bits; + if (!untouched) { + BLI_addtail(&e->segments, new_seg2); + } } /* If we touched the cut list, we assign the new cut position based on new cut position, * this way we accommodate precision lost due to multiple cut inserts. */ - new_seg1->at = start; + new_seg1->ratio = start; if (!untouched) { - new_seg2->at = end; + new_seg2->ratio = end; } else { /* For the convenience of the loop below. */ @@ -307,6 +300,22 @@ static void lineart_edge_cut(LineartData *ld, for (seg = new_seg1; seg && seg != new_seg2; seg = seg->next) { seg->occlusion += mat_occlusion; seg->material_mask_bits |= material_mask_bits; + + /* The enclosed shape flag will override regular lit/shaded + * flags. See LineartEdgeSegment::shadow_mask_bits for details. */ + if (shadow_bits == LRT_SHADOW_MASK_ENCLOSED_SHAPE) { + if (seg->shadow_mask_bits & LRT_SHADOW_MASK_LIT || e->flags & LRT_EDGE_FLAG_LIGHT_CONTOUR) { + seg->shadow_mask_bits &= ~LRT_SHADOW_MASK_LIT; + seg->shadow_mask_bits |= LRT_SHADOW_MASK_INHIBITED; + } + else if (seg->shadow_mask_bits & LRT_SHADOW_MASK_SHADED) { + seg->shadow_mask_bits &= ~LRT_SHADOW_MASK_SHADED; + seg->shadow_mask_bits |= LRT_SHADOW_MASK_LIT; + } + } + else { + seg->shadow_mask_bits |= shadow_bits; + } } /* Reduce adjacent cutting points of the same level, which saves memory. */ @@ -316,7 +325,8 @@ static void lineart_edge_cut(LineartData *ld, next_seg = seg->next; if (prev_seg && prev_seg->occlusion == seg->occlusion && - prev_seg->material_mask_bits == seg->material_mask_bits) { + prev_seg->material_mask_bits == seg->material_mask_bits && + prev_seg->shadow_mask_bits == seg->shadow_mask_bits) { BLI_remlink(&e->segments, seg); /* This puts the node back to the render buffer, if more cut happens, these unused nodes get * picked first. */ @@ -336,10 +346,8 @@ static void lineart_edge_cut(LineartData *ld, */ BLI_INLINE bool lineart_occlusion_is_adjacent_intersection(LineartEdge *e, LineartTriangle *tri) { - LineartVertIntersection *v1 = (void *)e->v1; - LineartVertIntersection *v2 = (void *)e->v2; - return ((v1->base.flag && v1->intersecting_with == tri) || - (v2->base.flag && v2->intersecting_with == tri)); + return (((e->target_reference & LRT_LIGHT_CONTOUR_TARGET) == tri->target_reference) || + (((e->target_reference >> 32) & LRT_LIGHT_CONTOUR_TARGET) == tri->target_reference)); } static void lineart_bounding_area_triangle_reallocate(LineartBoundingArea *ba) @@ -371,24 +379,10 @@ static void lineart_bounding_area_line_add(LineartBoundingArea *ba, LineartEdge static void lineart_occlusion_single_line(LineartData *ld, LineartEdge *e, int thread_id) { - double x = e->v1->fbcoord[0], y = e->v1->fbcoord[1]; - LineartBoundingArea *ba = lineart_edge_first_bounding_area(ld, e); - LineartBoundingArea *nba = ba; LineartTriangleThread *tri; - - /* These values are used for marching along the line. */ double l, r; - double k = (e->v2->fbcoord[1] - e->v1->fbcoord[1]) / - (e->v2->fbcoord[0] - e->v1->fbcoord[0] + 1e-30); - int positive_x = (e->v2->fbcoord[0] - e->v1->fbcoord[0]) > 0 ? - 1 : - (e->v2->fbcoord[0] == e->v1->fbcoord[0] ? 0 : -1); - int positive_y = (e->v2->fbcoord[1] - e->v1->fbcoord[1]) > 0 ? - 1 : - (e->v2->fbcoord[1] == e->v1->fbcoord[1] ? 0 : -1); - - while (nba) { - + LRT_EDGE_BA_MARCHING_BEGIN(e->v1->fbcoord, e->v2->fbcoord) + { for (int i = 0; i < nba->triangle_count; i++) { tri = (LineartTriangleThread *)nba->linked_triangles[i]; /* If we are already testing the line in this thread, then don't do it. */ @@ -401,8 +395,7 @@ static void lineart_occlusion_single_line(LineartData *ld, LineartEdge *e, int t continue; } tri->testing_e[thread_id] = e; - if (lineart_triangle_edge_image_space_occlusion(&ld->lock_task, - (const LineartTriangle *)tri, + if (lineart_triangle_edge_image_space_occlusion((const LineartTriangle *)tri, e, ld->conf.camera_pos, ld->conf.cam_is_persp, @@ -413,7 +406,7 @@ static void lineart_occlusion_single_line(LineartData *ld, LineartEdge *e, int t ld->conf.shift_y, &l, &r)) { - lineart_edge_cut(ld, e, l, r, tri->base.material_mask_bits, tri->base.mat_occlusion); + lineart_edge_cut(ld, e, l, r, tri->base.material_mask_bits, tri->base.mat_occlusion, 0); if (e->min_occ > ld->conf.max_occlusion_level) { /* No need to calculate any longer on this line because no level more than set value is * going to show up in the rendered result. */ @@ -421,9 +414,9 @@ static void lineart_occlusion_single_line(LineartData *ld, LineartEdge *e, int t } } } - /* Marching along `e->v1` to `e->v2`, searching each possible bounding areas it may touch. */ - nba = lineart_bounding_area_next(nba, e, x, y, k, positive_x, positive_y, &x, &y); + LRT_EDGE_BA_MARCHING_NEXT(e->v1->fbcoord, e->v2->fbcoord) } + LRT_EDGE_BA_MARCHING_END } static int lineart_occlusion_make_task_info(LineartData *ld, LineartRenderTaskInfo *rti) @@ -469,7 +462,7 @@ static void lineart_occlusion_worker(TaskPool *__restrict UNUSED(pool), LineartR * #MOD_lineart_compute_feature_lines function. * This function handles all occlusion calculation. */ -static void lineart_main_occlusion_begin(LineartData *ld) +void lineart_main_occlusion_begin(LineartData *ld) { int thread_count = ld->thread_count; LineartRenderTaskInfo *rti = MEM_callocN(sizeof(LineartRenderTaskInfo) * thread_count, @@ -703,10 +696,10 @@ static LineartElementLinkNode *lineart_memory_get_edge_space(LineartData *ld) { LineartElementLinkNode *eln; - LineartEdge *render_edges = lineart_mem_acquire(&ld->render_data_pool, sizeof(LineartEdge) * 64); + LineartEdge *render_edges = lineart_mem_acquire(ld->edge_data_pool, sizeof(LineartEdge) * 64); eln = lineart_list_append_pointer_pool_sized(&ld->geom.line_buffer_pointers, - &ld->render_data_pool, + ld->edge_data_pool, render_edges, sizeof(LineartElementLinkNode)); eln->element_count = 64; @@ -724,6 +717,8 @@ static void lineart_triangle_post(LineartTriangle *tri, LineartTriangle *orig) tri->intersection_mask = orig->intersection_mask; tri->material_mask_bits = orig->material_mask_bits; tri->mat_occlusion = orig->mat_occlusion; + tri->intersection_priority = orig->intersection_priority; + tri->target_reference = orig->target_reference; } static void lineart_triangle_set_cull_flag(LineartTriangle *tri, uchar flag) @@ -757,10 +752,10 @@ static void lineart_triangle_cull_single(LineartData *ld, int in0, int in1, int in2, - double *cam_pos, - double *view_dir, + double cam_pos[3], + double view_dir[3], bool allow_boundaries, - double (*vp)[4], + double m_view_projection[4][4], Object *ob, int *r_v_count, int *r_e_count, @@ -887,7 +882,7 @@ static void lineart_triangle_cull_single(LineartData *ld, a = dot_v1 / (dot_v1 + dot_v2); /* Assign it to a new point. */ interp_v3_v3v3_db(vt[0].gloc, tri->v[0]->gloc, tri->v[2]->gloc, a); - mul_v4_m4v3_db(vt[0].fbcoord, vp, vt[0].gloc); + mul_v4_m4v3_db(vt[0].fbcoord, m_view_projection, vt[0].gloc); vt[0].index = tri->v[2]->index; /* Cut point for line 1---|-----0. */ @@ -898,7 +893,7 @@ static void lineart_triangle_cull_single(LineartData *ld, a = dot_v1 / (dot_v1 + dot_v2); /* Assign it to another new point. */ interp_v3_v3v3_db(vt[1].gloc, tri->v[0]->gloc, tri->v[1]->gloc, a); - mul_v4_m4v3_db(vt[1].fbcoord, vp, vt[1].gloc); + mul_v4_m4v3_db(vt[1].fbcoord, m_view_projection, vt[1].gloc); vt[1].index = tri->v[1]->index; /* New line connecting two new points. */ @@ -939,7 +934,7 @@ static void lineart_triangle_cull_single(LineartData *ld, dot_v2 = dot_v3v3_db(span_v2, view_dir); a = dot_v1 / (dot_v1 + dot_v2); interp_v3_v3v3_db(vt[0].gloc, tri->v[2]->gloc, tri->v[0]->gloc, a); - mul_v4_m4v3_db(vt[0].fbcoord, vp, vt[0].gloc); + mul_v4_m4v3_db(vt[0].fbcoord, m_view_projection, vt[0].gloc); vt[0].index = tri->v[0]->index; sub_v3_v3v3_db(span_v1, tri->v[2]->gloc, cam_pos); @@ -948,7 +943,7 @@ static void lineart_triangle_cull_single(LineartData *ld, dot_v2 = dot_v3v3_db(span_v2, view_dir); a = dot_v1 / (dot_v1 + dot_v2); interp_v3_v3v3_db(vt[1].gloc, tri->v[2]->gloc, tri->v[1]->gloc, a); - mul_v4_m4v3_db(vt[1].fbcoord, vp, vt[1].gloc); + mul_v4_m4v3_db(vt[1].fbcoord, m_view_projection, vt[1].gloc); vt[1].index = tri->v[1]->index; INCREASE_EDGE @@ -980,7 +975,7 @@ static void lineart_triangle_cull_single(LineartData *ld, dot_v2 = dot_v3v3_db(span_v2, view_dir); a = dot_v1 / (dot_v1 + dot_v2); interp_v3_v3v3_db(vt[0].gloc, tri->v[1]->gloc, tri->v[2]->gloc, a); - mul_v4_m4v3_db(vt[0].fbcoord, vp, vt[0].gloc); + mul_v4_m4v3_db(vt[0].fbcoord, m_view_projection, vt[0].gloc); vt[0].index = tri->v[2]->index; sub_v3_v3v3_db(span_v1, tri->v[1]->gloc, cam_pos); @@ -989,7 +984,7 @@ static void lineart_triangle_cull_single(LineartData *ld, dot_v2 = dot_v3v3_db(span_v2, view_dir); a = dot_v1 / (dot_v1 + dot_v2); interp_v3_v3v3_db(vt[1].gloc, tri->v[1]->gloc, tri->v[0]->gloc, a); - mul_v4_m4v3_db(vt[1].fbcoord, vp, vt[1].gloc); + mul_v4_m4v3_db(vt[1].fbcoord, m_view_projection, vt[1].gloc); vt[1].index = tri->v[0]->index; INCREASE_EDGE @@ -1052,7 +1047,7 @@ static void lineart_triangle_cull_single(LineartData *ld, a = dot_v2 / (dot_v1 + dot_v2); /* Assign to a new point. */ interp_v3_v3v3_db(vt[0].gloc, tri->v[0]->gloc, tri->v[1]->gloc, a); - mul_v4_m4v3_db(vt[0].fbcoord, vp, vt[0].gloc); + mul_v4_m4v3_db(vt[0].fbcoord, m_view_projection, vt[0].gloc); vt[0].index = tri->v[0]->index; /* Cut point for line 0---|------2. */ @@ -1063,7 +1058,7 @@ static void lineart_triangle_cull_single(LineartData *ld, a = dot_v2 / (dot_v1 + dot_v2); /* Assign to other new point. */ interp_v3_v3v3_db(vt[1].gloc, tri->v[0]->gloc, tri->v[2]->gloc, a); - mul_v4_m4v3_db(vt[1].fbcoord, vp, vt[1].gloc); + mul_v4_m4v3_db(vt[1].fbcoord, m_view_projection, vt[1].gloc); vt[1].index = tri->v[0]->index; /* New line connects two new points. */ @@ -1107,7 +1102,7 @@ static void lineart_triangle_cull_single(LineartData *ld, dot_v2 = dot_v3v3_db(span_v2, view_dir); a = dot_v1 / (dot_v1 + dot_v2); interp_v3_v3v3_db(vt[0].gloc, tri->v[1]->gloc, tri->v[2]->gloc, a); - mul_v4_m4v3_db(vt[0].fbcoord, vp, vt[0].gloc); + mul_v4_m4v3_db(vt[0].fbcoord, m_view_projection, vt[0].gloc); vt[0].index = tri->v[1]->index; sub_v3_v3v3_db(span_v1, tri->v[1]->gloc, cam_pos); @@ -1116,7 +1111,7 @@ static void lineart_triangle_cull_single(LineartData *ld, dot_v2 = dot_v3v3_db(span_v2, view_dir); a = dot_v1 / (dot_v1 + dot_v2); interp_v3_v3v3_db(vt[1].gloc, tri->v[1]->gloc, tri->v[0]->gloc, a); - mul_v4_m4v3_db(vt[1].fbcoord, vp, vt[1].gloc); + mul_v4_m4v3_db(vt[1].fbcoord, m_view_projection, vt[1].gloc); vt[1].index = tri->v[1]->index; INCREASE_EDGE @@ -1156,7 +1151,7 @@ static void lineart_triangle_cull_single(LineartData *ld, dot_v2 = dot_v3v3_db(span_v2, view_dir); a = dot_v1 / (dot_v1 + dot_v2); interp_v3_v3v3_db(vt[0].gloc, tri->v[2]->gloc, tri->v[0]->gloc, a); - mul_v4_m4v3_db(vt[0].fbcoord, vp, vt[0].gloc); + mul_v4_m4v3_db(vt[0].fbcoord, m_view_projection, vt[0].gloc); vt[0].index = tri->v[2]->index; sub_v3_v3v3_db(span_v1, tri->v[2]->gloc, cam_pos); @@ -1165,7 +1160,7 @@ static void lineart_triangle_cull_single(LineartData *ld, dot_v2 = dot_v3v3_db(span_v2, view_dir); a = dot_v1 / (dot_v1 + dot_v2); interp_v3_v3v3_db(vt[1].gloc, tri->v[2]->gloc, tri->v[1]->gloc, a); - mul_v4_m4v3_db(vt[1].fbcoord, vp, vt[1].gloc); + mul_v4_m4v3_db(vt[1].fbcoord, m_view_projection, vt[1].gloc); vt[1].index = tri->v[2]->index; INCREASE_EDGE @@ -1215,11 +1210,11 @@ static void lineart_triangle_cull_single(LineartData *ld, * 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(LineartData *ld, bool clip_far) +void lineart_main_cull_triangles(LineartData *ld, bool clip_far) { LineartTriangle *tri; LineartElementLinkNode *v_eln, *t_eln, *e_eln; - double(*vp)[4] = ld->conf.view_projection; + double(*m_view_projection)[4] = ld->conf.view_projection; int i; int v_count = 0, t_count = 0, e_count = 0; Object *ob; @@ -1329,7 +1324,7 @@ static void lineart_main_cull_triangles(LineartData *ld, bool clip_far) cam_pos, view_dir, allow_boundaries, - vp, + m_view_projection, ob, &v_count, &e_count, @@ -1350,7 +1345,7 @@ static void lineart_main_cull_triangles(LineartData *ld, bool clip_far) * 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(LineartData *ld) +void lineart_main_free_adjacent_data(LineartData *ld) { LinkData *link; while ((link = BLI_pophead(&ld->geom.triangle_adjacent_pointers)) != NULL) { @@ -1368,7 +1363,7 @@ static void lineart_main_free_adjacent_data(LineartData *ld) } } -static void lineart_main_perspective_division(LineartData *ld) +void lineart_main_perspective_division(LineartData *ld) { LineartVert *vt; int i; @@ -1393,7 +1388,7 @@ static void lineart_main_perspective_division(LineartData *ld) } } -static void lineart_main_discard_out_of_frame_edges(LineartData *ld) +void lineart_main_discard_out_of_frame_edges(LineartData *ld) { LineartEdge *e; int i; @@ -1444,9 +1439,10 @@ static const int LRT_MESH_EDGE_TYPES[] = { LRT_EDGE_FLAG_CREASE, LRT_EDGE_FLAG_MATERIAL, LRT_EDGE_FLAG_LOOSE, + LRT_EDGE_FLAG_CONTOUR_SECONDARY, }; -#define LRT_MESH_EDGE_TYPES_COUNT 5 +#define LRT_MESH_EDGE_TYPES_COUNT 6 static int lineart_edge_type_duplication_count(int eflag) { @@ -1476,6 +1472,7 @@ static LineartTriangle *lineart_triangle_from_index(LineartData *ld, typedef struct EdgeFeatData { LineartData *ld; Mesh *me; + Object *ob; const MLoopTri *mlooptri; LineartTriangle *tri_array; LineartVert *v_array; @@ -1508,6 +1505,7 @@ static void lineart_identify_mlooptri_feature_edges(void *__restrict userdata, EdgeFeatData *e_feat_data = (EdgeFeatData *)userdata; EdgeFeatReduceData *reduce_data = (EdgeFeatReduceData *)tls->userdata_chunk; Mesh *me = e_feat_data->me; + Object *ob = e_feat_data->ob; LineartEdgeNeighbor *edge_nabr = e_feat_data->edge_nabr; const MLoopTri *mlooptri = e_feat_data->mlooptri; @@ -1622,6 +1620,23 @@ static void lineart_identify_mlooptri_feature_edges(void *__restrict userdata, } } + if (ld->conf.use_contour_secondary) { + view_vector = view_vector_persp; + if (ld->conf.cam_is_persp_secondary) { + sub_v3_v3v3_db(view_vector, vert->gloc, ld->conf.camera_pos_secondary); + } + else { + view_vector = ld->conf.view_vector_secondary; + } + + dot_v1 = dot_v3v3_db(view_vector, tri1->gn); + dot_v2 = dot_v3v3_db(view_vector, tri2->gn); + + if ((result = dot_v1 * dot_v2) <= 0 && (dot_v1 + dot_v2)) { + edge_flag_result |= LRT_EDGE_FLAG_CONTOUR_SECONDARY; + } + } + if (!only_contour) { if (ld->conf.use_crease) { @@ -1639,8 +1654,19 @@ static void lineart_identify_mlooptri_feature_edges(void *__restrict userdata, int mat1 = me->mpoly[mlooptri[f1].poly].mat_nr; int mat2 = me->mpoly[mlooptri[f2].poly].mat_nr; - if (ld->conf.use_material && mat1 != mat2) { - edge_flag_result |= LRT_EDGE_FLAG_MATERIAL; + if (mat1 != mat2) { + Material *m1 = BKE_object_material_get(ob, mat1 + 1); + Material *m2 = BKE_object_material_get(ob, mat2 + 1); + if (m1 && m2 && + ((m1->lineart.mat_occlusion == 0 && m2->lineart.mat_occlusion != 0) || + (m2->lineart.mat_occlusion == 0 && m1->lineart.mat_occlusion != 0))) { + if (ld->conf.use_contour) { + edge_flag_result |= LRT_EDGE_FLAG_CONTOUR; + } + } + if (ld->conf.use_material) { + edge_flag_result |= LRT_EDGE_FLAG_MATERIAL; + } } } else { /* only_contour */ @@ -1747,7 +1773,7 @@ static void loose_data_sum_reduce(const void *__restrict UNUSED(userdata), lineart_join_loose_edge_arr(final, loose_chunk); } -static void lineart_add_edge_to_array(LineartPendingEdges *pe, LineartEdge *e) +void lineart_add_edge_to_array(LineartPendingEdges *pe, LineartEdge *e) { if (pe->next >= pe->max || !pe->max) { if (!pe->max) { @@ -1766,7 +1792,6 @@ static void lineart_add_edge_to_array(LineartPendingEdges *pe, LineartEdge *e) pe->array[pe->next] = e; pe->next++; } - static void lineart_add_edge_to_array_thread(LineartObjectInfo *obi, LineartEdge *e) { lineart_add_edge_to_array(&obi->pending_edges, e); @@ -1774,7 +1799,7 @@ static void lineart_add_edge_to_array_thread(LineartObjectInfo *obi, LineartEdge /* NOTE: For simplicity, this function doesn't actually do anything if you already have data in * #pe. */ -static void lineart_finalize_object_edge_array_reserve(LineartPendingEdges *pe, int count) +void lineart_finalize_object_edge_array_reserve(LineartPendingEdges *pe, int count) { if (pe->max || pe->array) { return; @@ -1852,12 +1877,18 @@ static void lineart_load_tri_task(void *__restrict userdata, mat->lineart.material_mask_bits : 0); tri->mat_occlusion |= (mat ? mat->lineart.mat_occlusion : 1); + tri->intersection_priority = ((mat && (mat->lineart.flags & + LRT_MATERIAL_CUSTOM_INTERSECTION_PRIORITY)) ? + mat->lineart.intersection_priority : + ob_info->intersection_priority); tri->flags |= (mat && (mat->blend_flag & MA_BL_CULL_BACKFACE)) ? LRT_TRIANGLE_MAT_BACK_FACE_CULLING : 0; tri->intersection_mask = ob_info->override_intersection_mask; + tri->target_reference = (ob_info->obindex | (i & LRT_OBINDEX_LOWER)); + double gn[3]; float no[3]; normal_tri_v3(no, me->mvert[v1].co, me->mvert[v2].co, me->mvert[v3].co); @@ -1943,7 +1974,9 @@ static LineartEdgeNeighbor *lineart_build_edge_neighbor(Mesh *me, int total_edge return edge_nabr; } -static void lineart_geometry_object_load(LineartObjectInfo *ob_info, LineartData *la_data) +static void lineart_geometry_object_load(LineartObjectInfo *ob_info, + LineartData *la_data, + ListBase *shadow_elns) { LineartElementLinkNode *elem_link_node; LineartVert *la_v_arr; @@ -1992,6 +2025,7 @@ static void lineart_geometry_object_load(LineartObjectInfo *ob_info, LineartData sizeof(LineartElementLinkNode)); BLI_spin_unlock(&la_data->lock_task); + elem_link_node->obindex = ob_info->obindex; elem_link_node->element_count = me->totvert; elem_link_node->object_ref = orig_ob; ob_info->v_eln = elem_link_node; @@ -2088,6 +2122,7 @@ static void lineart_geometry_object_load(LineartObjectInfo *ob_info, LineartData EdgeFeatData edge_feat_data = {0}; edge_feat_data.ld = la_data; edge_feat_data.me = me; + edge_feat_data.ob = orig_ob; edge_feat_data.mlooptri = mlooptri; edge_feat_data.edge_nabr = lineart_build_edge_neighbor(me, total_edges); edge_feat_data.tri_array = la_tri_arr; @@ -2128,19 +2163,25 @@ static void lineart_geometry_object_load(LineartObjectInfo *ob_info, LineartData int allocate_la_e = edge_reduce.feat_edges + loose_data.loose_count; - la_edge_arr = lineart_mem_acquire_thread(&la_data->render_data_pool, + la_edge_arr = lineart_mem_acquire_thread(la_data->edge_data_pool, sizeof(LineartEdge) * allocate_la_e); - la_seg_arr = lineart_mem_acquire_thread(&la_data->render_data_pool, + la_seg_arr = lineart_mem_acquire_thread(la_data->edge_data_pool, sizeof(LineartEdgeSegment) * allocate_la_e); BLI_spin_lock(&la_data->lock_task); elem_link_node = lineart_list_append_pointer_pool_sized_thread( &la_data->geom.line_buffer_pointers, - &la_data->render_data_pool, + la_data->edge_data_pool, la_edge_arr, sizeof(LineartElementLinkNode)); BLI_spin_unlock(&la_data->lock_task); elem_link_node->element_count = allocate_la_e; elem_link_node->object_ref = orig_ob; + elem_link_node->obindex = ob_info->obindex; + + LineartElementLinkNode *shadow_eln = NULL; + if (shadow_elns) { + shadow_eln = lineart_find_matching_eln(shadow_elns, ob_info->obindex); + } /* Start of the edge/seg arr */ LineartEdge *la_edge; @@ -2185,7 +2226,19 @@ static void lineart_geometry_object_load(LineartObjectInfo *ob_info, LineartData } la_edge->flags = use_type; la_edge->object_ref = orig_ob; + la_edge->edge_identifier = LRT_EDGE_IDENTIFIER(ob_info, la_edge); BLI_addtail(&la_edge->segments, la_seg); + + if (shadow_eln) { + /* TODO(Yiming): It's gonna be faster to do this operation after second stage occlusion if + * we only need visible segments to have shadow info, however that way we lose information + * on "shadow behind transparency window" type of region. */ + LineartEdge *shadow_e = lineart_find_matching_edge(shadow_eln, la_edge->edge_identifier); + if (shadow_e) { + lineart_register_shadow_cuts(la_data, la_edge, shadow_e); + } + } + if (usage == OBJECT_LRT_INHERIT || usage == OBJECT_LRT_INCLUDE || usage == OBJECT_LRT_NO_INTERSECTION) { lineart_add_edge_to_array_thread(ob_info, la_edge); @@ -2212,10 +2265,17 @@ static void lineart_geometry_object_load(LineartObjectInfo *ob_info, LineartData la_edge->v2 = &la_v_arr[loose_data.loose_array[i]->v2]; la_edge->flags = LRT_EDGE_FLAG_LOOSE; la_edge->object_ref = orig_ob; + la_edge->edge_identifier = LRT_EDGE_IDENTIFIER(ob_info, la_edge); BLI_addtail(&la_edge->segments, la_seg); if (usage == OBJECT_LRT_INHERIT || usage == OBJECT_LRT_INCLUDE || usage == OBJECT_LRT_NO_INTERSECTION) { lineart_add_edge_to_array_thread(ob_info, la_edge); + if (shadow_eln) { + LineartEdge *shadow_e = lineart_find_matching_edge(shadow_eln, la_edge->edge_identifier); + if (shadow_e) { + lineart_register_shadow_cuts(la_data, la_edge, shadow_e); + } + } } la_edge++; la_seg++; @@ -2234,7 +2294,7 @@ static void lineart_object_load_worker(TaskPool *__restrict UNUSED(pool), LineartObjectLoadTaskInfo *olti) { for (LineartObjectInfo *obi = olti->pending; obi; obi = obi->next) { - lineart_geometry_object_load(obi, olti->ld); + lineart_geometry_object_load(obi, olti->ld, olti->shadow_elns); } } @@ -2256,6 +2316,26 @@ static uchar lineart_intersection_mask_check(Collection *c, Object *ob) return 0; } +static uchar lineart_intersection_priority_check(Collection *c, Object *ob) +{ + if (ob->lineart.flags & OBJECT_LRT_OWN_INTERSECTION_PRIORITY) { + return ob->lineart.intersection_priority; + } + + LISTBASE_FOREACH (CollectionChild *, cc, &c->children) { + uchar result = lineart_intersection_priority_check(cc->collection, ob); + if (result) { + return result; + } + } + if (BKE_collection_has_object(c, (Object *)(ob->id.orig_id))) { + if (c->lineart_flags & COLLECTION_LRT_USE_INTERSECTION_PRIORITY) { + return c->lineart_intersection_priority; + } + } + return 0; +} + /** * See if this object in such collection is used for generating line art, * Disabling a collection for line art will doable all objects inside. @@ -2325,7 +2405,7 @@ static void lineart_geometry_load_assign_thread(LineartObjectLoadTaskInfo *olti_ use_olti->pending = obi; } -static bool lineart_geometry_check_visible(double (*model_view_proj)[4], +static bool lineart_geometry_check_visible(double model_view_proj[4][4], double shift_x, double shift_y, Mesh *use_mesh) @@ -2376,17 +2456,21 @@ static void lineart_object_load_single_instance(LineartData *ld, float use_mat[4][4], bool is_render, LineartObjectLoadTaskInfo *olti, - int thread_count) + int thread_count, + int obindex) { LineartObjectInfo *obi = lineart_mem_acquire(&ld->render_data_pool, sizeof(LineartObjectInfo)); obi->usage = lineart_usage_check(scene->master_collection, ob, is_render); obi->override_intersection_mask = lineart_intersection_mask_check(scene->master_collection, ob); + obi->intersection_priority = lineart_intersection_priority_check(scene->master_collection, ob); Mesh *use_mesh; if (obi->usage == OBJECT_LRT_EXCLUDE) { return; } + obi->obindex = obindex << LRT_OBINDEX_SHIFT; + /* Prepare the matrix used for transforming this specific object (instance). This has to be * done before mesh boundbox check because the function needs that. */ mul_m4db_m4db_m4fl_uniq(obi->model_view_proj, ld->conf.view_projection, use_mat); @@ -2432,44 +2516,45 @@ static void lineart_object_load_single_instance(LineartData *ld, lineart_geometry_load_assign_thread(olti, obi, thread_count, use_mesh->totpoly); } -static void lineart_main_load_geometries( - Depsgraph *depsgraph, - Scene *scene, - Object *camera /* Still use camera arg for convenience. */, - LineartData *ld, - bool allow_duplicates) +void lineart_main_load_geometries(Depsgraph *depsgraph, + Scene *scene, + Object *camera /* Still use camera arg for convenience. */, + LineartData *ld, + bool allow_duplicates, + bool do_shadow_casting, + ListBase *shadow_elns) { 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); - int fit = BKE_camera_sensor_fit(cam->sensor_fit, ld->w, ld->h); - double asp = ((double)ld->w / (double)ld->h); - - int bound_box_discard_count = 0; - if (cam->type == CAM_PERSP) { - if (fit == CAMERA_SENSOR_FIT_VERT && asp > 1) { - sensor *= asp; + if (!do_shadow_casting) { + Camera *cam = camera->data; + float sensor = BKE_camera_sensor_size(cam->sensor_fit, cam->sensor_x, cam->sensor_y); + int fit = BKE_camera_sensor_fit(cam->sensor_fit, ld->w, ld->h); + double asp = ((double)ld->w / (double)ld->h); + if (cam->type == CAM_PERSP) { + if (fit == CAMERA_SENSOR_FIT_VERT && asp > 1) { + sensor *= asp; + } + if (fit == CAMERA_SENSOR_FIT_HOR && asp < 1) { + sensor /= asp; + } + const double fov = focallength_to_fov(cam->lens / (1 + ld->conf.overscan), sensor); + lineart_matrix_perspective_44d(proj, fov, asp, cam->clip_start, cam->clip_end); } - if (fit == CAMERA_SENSOR_FIT_HOR && asp < 1) { - sensor /= asp; + else if (cam->type == CAM_ORTHO) { + const double w = cam->ortho_scale / 2; + lineart_matrix_ortho_44d(proj, -w, w, -w / asp, w / asp, cam->clip_start, cam->clip_end); } - const double fov = focallength_to_fov(cam->lens / (1 + ld->conf.overscan), sensor); - lineart_matrix_perspective_44d(proj, fov, asp, cam->clip_start, cam->clip_end); - } - else if (cam->type == CAM_ORTHO) { - const 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, ld->conf.cam_obmat); - mul_m4db_m4db_m4fl_uniq(result, proj, inv); - copy_m4_m4_db(proj, result); - copy_m4_m4_db(ld->conf.view_projection, proj); + invert_m4_m4(inv, ld->conf.cam_obmat); + mul_m4db_m4db_m4fl_uniq(result, proj, inv); + copy_m4_m4_db(proj, result); + copy_m4_m4_db(ld->conf.view_projection, proj); - unit_m4_db(view); - copy_m4_m4_db(ld->conf.view, view); + unit_m4_db(view); + copy_m4_m4_db(ld->conf.view, view); + } BLI_listbase_clear(&ld->geom.triangle_buffer_pointers); BLI_listbase_clear(&ld->geom.vertex_buffer_pointers); @@ -2480,6 +2565,8 @@ static void lineart_main_load_geometries( } int thread_count = ld->thread_count; + int bound_box_discard_count = 0; + int obindex = 0; /* This memory is in render buffer memory pool. So we don't need to free those after loading. */ LineartObjectLoadTaskInfo *olti = lineart_mem_acquire( @@ -2499,6 +2586,9 @@ static void lineart_main_load_geometries( /* XXX(@Yiming): Temporary solution, this iterator is technically unsafe to use *during* * depsgraph evaluation, see D14997 for detailed explanations. */ DEG_OBJECT_ITER_BEGIN (depsgraph, ob, flags) { + + obindex++; + Object *eval_ob = DEG_get_evaluated_object(depsgraph, ob); if (!eval_ob) { @@ -2512,8 +2602,16 @@ static void lineart_main_load_geometries( } if (BKE_object_visibility(eval_ob, eval_mode) & OB_VISIBLE_SELF) { - lineart_object_load_single_instance( - ld, depsgraph, scene, eval_ob, eval_ob, eval_ob->obmat, is_render, olti, thread_count); + lineart_object_load_single_instance(ld, + depsgraph, + scene, + eval_ob, + eval_ob, + eval_ob->obmat, + is_render, + olti, + thread_count, + obindex); } } DEG_OBJECT_ITER_END; @@ -2525,6 +2623,7 @@ static void lineart_main_load_geometries( } for (int i = 0; i < thread_count; i++) { olti[i].ld = ld; + olti[i].shadow_elns = shadow_elns; olti[i].thread_id = i; BLI_task_pool_push(tp, (TaskRunFunction)lineart_object_load_worker, &olti[i], 0, NULL); } @@ -2600,14 +2699,25 @@ static bool lineart_triangle_get_other_verts(const LineartTriangle *tri, return false; } -static bool lineart_edge_from_triangle(const LineartTriangle *tri, - const LineartEdge *e, - bool allow_overlapping_edges) +bool lineart_edge_from_triangle(const LineartTriangle *tri, + const LineartEdge *e, + bool allow_overlapping_edges) { - /* Normally we just determine from the pointer address. */ - if (e->t1 == tri || e->t2 == tri) { - return true; + const LineartEdge *use_e = e; + if (e->flags & LRT_EDGE_FLAG_LIGHT_CONTOUR) { + if (((e->target_reference & LRT_LIGHT_CONTOUR_TARGET) == tri->target_reference) || + (((e->target_reference >> 32) & LRT_LIGHT_CONTOUR_TARGET) == tri->target_reference)) { + return true; + } + } + else { + /* Normally we just determine from identifiers of adjacent triangles. */ + if ((use_e->t1 && use_e->t1->target_reference == tri->target_reference) || + (use_e->t2 && use_e->t2->target_reference == tri->target_reference)) { + 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) { @@ -2681,14 +2791,13 @@ static bool lineart_edge_from_triangle(const LineartTriangle *tri, * While current "edge aligned" fix isn't ideal, it does solve most of the precision issue * especially in orthographic camera mode. */ -static bool lineart_triangle_edge_image_space_occlusion(SpinLock *UNUSED(spl), - const LineartTriangle *tri, +static bool lineart_triangle_edge_image_space_occlusion(const LineartTriangle *tri, const LineartEdge *e, 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 double m_view_projection[4][4], + const double camera_dir[3], const float cam_shift_x, const float cam_shift_y, double *from, @@ -2750,6 +2859,18 @@ static bool lineart_triangle_edge_image_space_occlusion(SpinLock *UNUSED(spl), dot_v2 = dot_v3v3_db(dir_v2, tri->gn); dot_f = dot_v3v3_db(dir_cam, tri->gn); + if ((e->flags & LRT_EDGE_FLAG_PROJECTED_SHADOW) && + (e->target_reference == tri->target_reference)) { + if (((dot_f > 0) && (e->flags & LRT_EDGE_FLAG_SHADOW_FACING_LIGHT)) || + ((dot_f < 0) && (!(e->flags & LRT_EDGE_FLAG_SHADOW_FACING_LIGHT)))) { + *from = 0.0f; + *to = 1.0f; + return true; + } + + return false; + } + /* NOTE(Yiming): When we don't use `dot_f==0` here, it's theoretically possible that _some_ * faces in perspective mode would get erroneously caught in this condition where they really * are legit faces that would produce occlusion, but haven't encountered those yet in my test @@ -2797,7 +2918,7 @@ static bool lineart_triangle_edge_image_space_occlusion(SpinLock *UNUSED(spl), /* Transform the cut from geometry space to image space. */ if (override_cam_is_persp) { interp_v3_v3v3_db(gloc, e->v1->gloc, e->v2->gloc, cut); - mul_v4_m4v3_db(trans, vp, gloc); + mul_v4_m4v3_db(trans, m_view_projection, gloc); mul_v3db_db(trans, (1 / trans[3])); trans[0] -= cam_shift_x * 2; trans[1] -= cam_shift_y * 2; @@ -3170,6 +3291,9 @@ static void lineart_add_isec_thread(LineartIsecThread *th, copy_v3fl_v3db(isec_single->v2, v2); isec_single->tri1 = tri1; isec_single->tri2 = tri2; + if (tri1->target_reference > tri2->target_reference) { + SWAP(LineartTriangle *, isec_single->tri1, isec_single->tri2); + } th->current++; } @@ -3303,7 +3427,7 @@ static void lineart_triangle_intersect_in_bounding_area(LineartTriangle *tri, /** * The calculated view vector will point towards the far-plane from the camera position. */ -static void lineart_main_get_view_vector(LineartData *ld) +void lineart_main_get_view_vector(LineartData *ld) { float direction[3] = {0, 0, 1}; float trans[3]; @@ -3311,7 +3435,6 @@ static void lineart_main_get_view_vector(LineartData *ld) float obmat_no_scale[4][4]; copy_m4_m4(obmat_no_scale, ld->conf.cam_obmat); - normalize_v3(obmat_no_scale[0]); normalize_v3(obmat_no_scale[1]); normalize_v3(obmat_no_scale[2]); @@ -3320,9 +3443,31 @@ static void lineart_main_get_view_vector(LineartData *ld) mul_v3_mat3_m4v3(trans, inv, direction); copy_m4_m4(ld->conf.cam_obmat, obmat_no_scale); copy_v3db_v3fl(ld->conf.view_vector, trans); + + if (ld->conf.light_reference_available) { + copy_m4_m4(obmat_no_scale, ld->conf.cam_obmat_secondary); + 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(ld->conf.cam_obmat_secondary, obmat_no_scale); + copy_v3db_v3fl(ld->conf.view_vector_secondary, trans); + } } -static void lineart_destroy_render_data(LineartData *ld) +static void lineart_end_bounding_area_recursive(LineartBoundingArea *ba) +{ + BLI_spin_end(&ba->lock); + if (ba->child) { + for (int i = 0; i < 4; i++) { + lineart_end_bounding_area_recursive(&ba->child[i]); + } + } +} + +void lineart_destroy_render_data_keep_init(LineartData *ld) { if (ld == NULL) { return; @@ -3335,19 +3480,33 @@ static void lineart_destroy_render_data(LineartData *ld) BLI_listbase_clear(&ld->geom.line_buffer_pointers); BLI_listbase_clear(&ld->geom.triangle_buffer_pointers); - BLI_spin_end(&ld->lock_task); - BLI_spin_end(&ld->lock_cuts); - BLI_spin_end(&ld->render_data_pool.lock_mem); - if (ld->pending_edges.array) { MEM_freeN(ld->pending_edges.array); } + for (int i = 0; i < ld->qtree.initial_tile_count; i++) { + lineart_end_bounding_area_recursive(&ld->qtree.initials[i]); + } lineart_free_bounding_area_memories(ld); lineart_mem_destroy(&ld->render_data_pool); } +static void lineart_destroy_render_data(LineartData *ld) +{ + if (ld == NULL) { + return; + } + + BLI_spin_end(&ld->lock_task); + BLI_spin_end(&ld->lock_cuts); + BLI_spin_end(&ld->render_data_pool.lock_mem); + + lineart_destroy_render_data_keep_init(ld); + + lineart_mem_destroy(&ld->render_data_pool); +} + void MOD_lineart_destroy_render_data(LineartGpencilModifierData *lmd) { LineartData *ld = lmd->la_data_ptr; @@ -3432,6 +3591,16 @@ static LineartData *lineart_create_render_buffer(Scene *scene, ld->conf.shift_x /= (1 + ld->conf.overscan); ld->conf.shift_y /= (1 + ld->conf.overscan); + if (lmd->light_contour_object) { + Object *light_obj = lmd->light_contour_object; + copy_v3db_v3fl(ld->conf.camera_pos_secondary, light_obj->obmat[3]); + copy_m4_m4(ld->conf.cam_obmat_secondary, light_obj->obmat); + ld->conf.light_reference_available = true; + if (light_obj->type == OB_LAMP) { + ld->conf.cam_is_persp_secondary = ((Light *)light_obj->data)->type != LA_SUN; + } + } + ld->conf.crease_threshold = cos(M_PI - lmd->crease_threshold); ld->conf.chaining_image_threshold = lmd->chaining_image_threshold; ld->conf.angle_splitting_threshold = lmd->angle_splitting_threshold; @@ -3460,16 +3629,25 @@ static LineartData *lineart_create_render_buffer(Scene *scene, * occlusion levels will get ignored. */ ld->conf.max_occlusion_level = lmd->level_end_override; - ld->conf.use_back_face_culling = (lmd->calculation_flags & LRT_USE_BACK_FACE_CULLING) != 0; - int16_t edge_types = lmd->edge_types_override; + /* lmd->edge_types_override contains all used flags in the modifier stack. */ ld->conf.use_contour = (edge_types & LRT_EDGE_FLAG_CONTOUR) != 0; ld->conf.use_crease = (edge_types & LRT_EDGE_FLAG_CREASE) != 0; ld->conf.use_material = (edge_types & LRT_EDGE_FLAG_MATERIAL) != 0; ld->conf.use_edge_marks = (edge_types & LRT_EDGE_FLAG_EDGE_MARK) != 0; ld->conf.use_intersections = (edge_types & LRT_EDGE_FLAG_INTERSECTION) != 0; ld->conf.use_loose = (edge_types & LRT_EDGE_FLAG_LOOSE) != 0; + ld->conf.use_light_contour = ((edge_types & LRT_EDGE_FLAG_LIGHT_CONTOUR) != 0 && + (lmd->light_contour_object != NULL)); + ld->conf.use_shadow = ((edge_types & LRT_EDGE_FLAG_PROJECTED_SHADOW) != 0 && + (lmd->light_contour_object != NULL)); + + ld->conf.shadow_selection = lmd->shadow_selection_override; + ld->conf.shadow_enclose_shapes = (lmd->calculation_flags & LRT_SHADOW_ENCLOSED_SHAPES) != 0; + ld->conf.shadow_use_silhouette = lmd->shadow_use_silhouette_override != 0; + + ld->conf.use_back_face_culling = (lmd->calculation_flags & LRT_USE_BACK_FACE_CULLING) != 0; ld->conf.filter_face_mark_invert = (lmd->calculation_flags & LRT_FILTER_FACE_MARK_INVERT) != 0; ld->conf.filter_face_mark = (lmd->calculation_flags & LRT_FILTER_FACE_MARK) != 0; @@ -3480,6 +3658,9 @@ static LineartData *lineart_create_render_buffer(Scene *scene, ld->chain_data_pool = &lc->chain_data_pool; + /* See LineartData::edge_data_pool for explaination. */ + ld->edge_data_pool = &ld->render_data_pool; + BLI_spin_init(&ld->lock_task); BLI_spin_init(&ld->lock_cuts); BLI_spin_init(&ld->render_data_pool.lock_mem); @@ -3494,7 +3675,7 @@ static int lineart_triangle_size_get(LineartData *ld) return sizeof(LineartTriangle) + (sizeof(LineartEdge *) * (ld->thread_count)); } -static void lineart_main_bounding_area_make_initial(LineartData *ld) +void lineart_main_bounding_area_make_initial(LineartData *ld) { /* 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 @@ -3522,9 +3703,12 @@ static void lineart_main_bounding_area_make_initial(LineartData *ld) ld->qtree.tile_width = span_w; ld->qtree.tile_height = span_h; - ld->qtree.tile_count = sp_w * sp_h; - ld->qtree.initials = lineart_mem_acquire(&ld->render_data_pool, - sizeof(LineartBoundingArea) * ld->qtree.tile_count); + ld->qtree.initial_tile_count = sp_w * sp_h; + ld->qtree.initials = lineart_mem_acquire( + &ld->render_data_pool, sizeof(LineartBoundingArea) * ld->qtree.initial_tile_count); + for (int i = 0; i < ld->qtree.initial_tile_count; i++) { + BLI_spin_init(&ld->qtree.initials[i].lock); + } /* Initialize tiles. */ for (row = 0; row < sp_h; row++) { @@ -3706,7 +3890,7 @@ static void lineart_bounding_areas_connect_recursive(LineartData *ld, LineartBou } } -static void lineart_main_bounding_areas_connect_post(LineartData *ld) +void lineart_main_bounding_areas_connect_post(LineartData *ld) { int total_tile_initial = ld->qtree.count_x * ld->qtree.count_y; int tiles_per_row = ld->qtree.count_x; @@ -3816,8 +4000,6 @@ static void lineart_bounding_area_split(LineartData *ld, /* At this point the child tiles are fully initialized and it's safe for new triangles to be * inserted, so assign root->child for #lineart_bounding_area_link_triangle to use. */ root->child = ba; - - ld->qtree.tile_count += 3; } static bool lineart_bounding_area_edge_intersect(LineartData *UNUSED(fb), @@ -3913,7 +4095,7 @@ static bool lineart_bounding_area_triangle_intersect(LineartData *fb, static void lineart_bounding_area_link_triangle(LineartData *ld, LineartBoundingArea *root_ba, LineartTriangle *tri, - double *LRUB, + double l_r_u_b[4], int recursive, int recursive_level, bool do_intersection, @@ -3928,9 +4110,9 @@ static void lineart_bounding_area_link_triangle(LineartData *ld, if (old_ba->child) { /* If old_ba->child is not NULL, then tile splitting is fully finished, safe to directly insert * into child tiles. */ - double *B1 = LRUB; + double *B1 = l_r_u_b; double b[4]; - if (!LRUB) { + if (!l_r_u_b) { b[0] = MIN3(tri->v[0]->fbcoord[0], tri->v[1]->fbcoord[0], tri->v[2]->fbcoord[0]); b[1] = MAX3(tri->v[0]->fbcoord[0], tri->v[1]->fbcoord[0], tri->v[2]->fbcoord[0]); b[2] = MAX3(tri->v[0]->fbcoord[1], tri->v[1]->fbcoord[1], tri->v[2]->fbcoord[1]); @@ -3989,7 +4171,7 @@ static void lineart_bounding_area_link_triangle(LineartData *ld, /* Of course we still have our own triangle needs to be added. */ lineart_bounding_area_link_triangle( - ld, root_ba, tri, LRUB, recursive, recursive_level, do_intersection, th); + ld, root_ba, tri, l_r_u_b, recursive, recursive_level, do_intersection, th); } } @@ -4044,10 +4226,35 @@ static void lineart_bounding_area_link_edge(LineartData *ld, } } +static void lineart_clear_linked_edges_recursive(LineartData *ld, LineartBoundingArea *root_ba) +{ + if (root_ba->child) { + for (int i = 0; i < 4; i++) { + lineart_clear_linked_edges_recursive(ld, &root_ba->child[i]); + } + } + if (root_ba->linked_lines) { + MEM_freeN(root_ba->linked_lines); + } + root_ba->line_count = 0; + root_ba->max_line_count = 128; + root_ba->linked_lines = MEM_callocN(sizeof(LineartEdge *) * root_ba->max_line_count, + "cleared lineart edges"); +} +void lineart_main_clear_linked_edges(LineartData *ld) +{ + LineartBoundingArea *ba = ld->qtree.initials; + for (int i = 0; i < ld->qtree.count_y; i++) { + for (int j = 0; j < ld->qtree.count_x; j++) { + lineart_clear_linked_edges_recursive(ld, &ba[i * ld->qtree.count_x + j]); + } + } +} + /** * Link lines to their respective bounding areas. */ -static void lineart_main_link_lines(LineartData *ld) +void lineart_main_link_lines(LineartData *ld) { LRT_ITER_ALL_LINES_BEGIN { @@ -4270,6 +4477,7 @@ static void lineart_create_edges_from_isec_data(LineartIsecData *d) LineartData *ld = d->ld; double ZMax = ld->conf.far_clip; double ZMin = ld->conf.near_clip; + int total_lines = 0; for (int i = 0; i < d->thread_count; i++) { LineartIsecThread *th = &d->threads[i]; @@ -4279,23 +4487,37 @@ static void lineart_create_edges_from_isec_data(LineartIsecData *d) if (!th->current) { continue; } - /* We don't care about removing duplicated vert in this method, chaining can handle that, - * and it saves us from using locks and look up tables. */ - LineartVertIntersection *v = lineart_mem_acquire( - &ld->render_data_pool, sizeof(LineartVertIntersection) * th->current * 2); - LineartEdge *e = lineart_mem_acquire(&ld->render_data_pool, sizeof(LineartEdge) * th->current); - LineartEdgeSegment *es = lineart_mem_acquire(&ld->render_data_pool, - sizeof(LineartEdgeSegment) * th->current); + total_lines += th->current; + } + + if (!total_lines) { + return; + } + + /* We don't care about removing duplicated vert in this method, chaning can handle that, + * and it saves us from using locks and look up tables. */ + LineartVert *v = lineart_mem_acquire(ld->edge_data_pool, sizeof(LineartVert) * total_lines * 2); + LineartEdge *e = lineart_mem_acquire(ld->edge_data_pool, sizeof(LineartEdge) * total_lines); + LineartEdgeSegment *es = lineart_mem_acquire(ld->edge_data_pool, + sizeof(LineartEdgeSegment) * total_lines); + + LineartElementLinkNode *eln = lineart_mem_acquire(ld->edge_data_pool, + sizeof(LineartElementLinkNode)); + eln->element_count = total_lines; + eln->pointer = e; + eln->flags |= LRT_ELEMENT_INTERSECTION_DATA; + BLI_addhead(&ld->geom.line_buffer_pointers, eln); + + for (int i = 0; i < d->thread_count; i++) { + LineartIsecThread *th = &d->threads[i]; + if (!th->current) { + continue; + } + for (int j = 0; j < th->current; j++) { - LineartVertIntersection *v1i = v; - LineartVertIntersection *v2i = v + 1; LineartIsecSingle *is = &th->array[j]; - v1i->intersecting_with = is->tri1; - v2i->intersecting_with = is->tri2; - LineartVert *v1 = (LineartVert *)v1i; - LineartVert *v2 = (LineartVert *)v2i; - v1->flag |= LRT_VERT_HAS_INTERSECTION_DATA; - v2->flag |= LRT_VERT_HAS_INTERSECTION_DATA; + LineartVert *v1 = v; + LineartVert *v2 = v + 1; copy_v3db_v3fl(v1->gloc, is->v1); copy_v3db_v3fl(v2->gloc, is->v2); /* The intersection line has been generated only in geometry space, so we need to transform @@ -4320,10 +4542,34 @@ static void lineart_create_edges_from_isec_data(LineartIsecData *d) e->v2 = v2; e->t1 = is->tri1; e->t2 = is->tri2; + /* This is so we can also match intersection edges from shadow to later viewing stage. */ + e->edge_identifier = (((uint64_t)e->t1->target_reference) << 32) | e->t2->target_reference; e->flags = LRT_EDGE_FLAG_INTERSECTION; e->intersection_mask = (is->tri1->intersection_mask | is->tri2->intersection_mask); BLI_addtail(&e->segments, es); + int obi1 = (e->t1->target_reference & LRT_OBINDEX_HIGHER); + int obi2 = (e->t2->target_reference & LRT_OBINDEX_HIGHER); + LineartElementLinkNode *eln1 = lineart_find_matching_eln(&ld->geom.line_buffer_pointers, + obi1); + LineartElementLinkNode *eln2 = obi1 == obi2 ? eln1 : + lineart_find_matching_eln( + &ld->geom.line_buffer_pointers, obi2); + Object *ob1 = eln1 ? eln1->object_ref : NULL; + Object *ob2 = eln2 ? eln2->object_ref : NULL; + if (e->t1->intersection_priority > e->t2->intersection_priority) { + e->object_ref = ob1; + } + else if (e->t1->intersection_priority < e->t2->intersection_priority) { + e->object_ref = ob2; + } + else { /* equal priority */ + if (ob1 == ob2) { + /* object_ref should be ambigious if intersection lines comes from different objects. */ + e->object_ref = ob1; + } + } + lineart_add_edge_to_array(&ld->pending_edges, e); v += 2; @@ -4337,7 +4583,7 @@ static void lineart_create_edges_from_isec_data(LineartIsecData *d) * Sequentially add triangles into render buffer, intersection lines between those triangles will * also be computed at the same time. */ -static void lineart_main_add_triangles(LineartData *ld) +void lineart_main_add_triangles(LineartData *ld) { double t_start; if (G.debug_value == 4000) { @@ -4356,8 +4602,9 @@ static void lineart_main_add_triangles(LineartData *ld) BLI_task_pool_work_and_wait(tp); BLI_task_pool_free(tp); - /* Create actual lineart edges from intersection results. */ - lineart_create_edges_from_isec_data(&d); + if (ld->conf.use_intersections) { + lineart_create_edges_from_isec_data(&d); + } lineart_destroy_isec_thread(&d); @@ -4371,9 +4618,11 @@ static void lineart_main_add_triangles(LineartData *ld) * This function gets the tile for the point `e->v1`, and later use #lineart_bounding_area_next() * to get next along the way. */ -static LineartBoundingArea *lineart_edge_first_bounding_area(LineartData *ld, LineartEdge *e) +LineartBoundingArea *lineart_edge_first_bounding_area(LineartData *ld, + double *fbcoord1, + double *fbcoord2) { - double data[2] = {e->v1->fbcoord[0], e->v1->fbcoord[1]}; + double data[2] = {fbcoord1[0], fbcoord1[1]}; double LU[2] = {-1, 1}, RU[2] = {1, 1}, LB[2] = {-1, -1}, RB[2] = {1, -1}; double r = 1, sr = 1; bool p_unused; @@ -4382,23 +4631,19 @@ static LineartBoundingArea *lineart_edge_first_bounding_area(LineartData *ld, Li return lineart_get_bounding_area(ld, data[0], data[1]); } - if (lineart_intersect_seg_seg(e->v1->fbcoord, e->v2->fbcoord, LU, RU, &sr, &p_unused) && - sr < r && sr > 0) { + if (lineart_intersect_seg_seg(fbcoord1, fbcoord2, LU, RU, &sr, &p_unused) && sr < r && sr > 0) { r = sr; } - if (lineart_intersect_seg_seg(e->v1->fbcoord, e->v2->fbcoord, LB, RB, &sr, &p_unused) && - sr < r && sr > 0) { + if (lineart_intersect_seg_seg(fbcoord1, fbcoord2, LB, RB, &sr, &p_unused) && sr < r && sr > 0) { r = sr; } - if (lineart_intersect_seg_seg(e->v1->fbcoord, e->v2->fbcoord, LB, LU, &sr, &p_unused) && - sr < r && sr > 0) { + if (lineart_intersect_seg_seg(fbcoord1, fbcoord2, LB, LU, &sr, &p_unused) && sr < r && sr > 0) { r = sr; } - if (lineart_intersect_seg_seg(e->v1->fbcoord, e->v2->fbcoord, RB, RU, &sr, &p_unused) && - sr < r && sr > 0) { + if (lineart_intersect_seg_seg(fbcoord1, fbcoord2, RB, RU, &sr, &p_unused) && sr < r && sr > 0) { r = sr; } - interp_v2_v2v2_db(data, e->v1->fbcoord, e->v2->fbcoord, r); + interp_v2_v2v2_db(data, fbcoord1, fbcoord2, r); return lineart_get_bounding_area(ld, data[0], data[1]); } @@ -4407,15 +4652,16 @@ static LineartBoundingArea *lineart_edge_first_bounding_area(LineartData *ld, Li * 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, - LineartEdge *e, - double x, - double y, - double k, - int positive_x, - int positive_y, - double *next_x, - double *next_y) +LineartBoundingArea *lineart_bounding_area_next(LineartBoundingArea *this, + double *fbcoord1, + double *fbcoord2, + 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; @@ -4430,8 +4676,8 @@ static LineartBoundingArea *lineart_bounding_area_next(LineartBoundingArea *this if (positive_y > 0) { uy = this->u; ux = x + (uy - y) / k; - r1 = ratiod(e->v1->fbcoord[0], e->v2->fbcoord[0], rx); - r2 = ratiod(e->v1->fbcoord[0], e->v2->fbcoord[0], ux); + r1 = ratiod(fbcoord1[0], fbcoord2[0], rx); + r2 = ratiod(fbcoord1[0], fbcoord2[0], ux); if (MIN2(r1, r2) > 1) { return 0; } @@ -4463,8 +4709,8 @@ static LineartBoundingArea *lineart_bounding_area_next(LineartBoundingArea *this else if (positive_y < 0) { by = this->b; bx = x + (by - y) / k; - r1 = ratiod(e->v1->fbcoord[0], e->v2->fbcoord[0], rx); - r2 = ratiod(e->v1->fbcoord[0], e->v2->fbcoord[0], bx); + r1 = ratiod(fbcoord1[0], fbcoord2[0], rx); + r2 = ratiod(fbcoord1[0], fbcoord2[0], bx); if (MIN2(r1, r2) > 1) { return 0; } @@ -4491,7 +4737,7 @@ static LineartBoundingArea *lineart_bounding_area_next(LineartBoundingArea *this } /* If the line is completely horizontal, in which Y difference == 0. */ else { - r1 = ratiod(e->v1->fbcoord[0], e->v2->fbcoord[0], this->r); + r1 = ratiod(fbcoord1[0], fbcoord2[0], this->r); if (r1 > 1) { return 0; } @@ -4515,8 +4761,8 @@ static LineartBoundingArea *lineart_bounding_area_next(LineartBoundingArea *this if (positive_y > 0) { uy = this->u; ux = x + (uy - y) / k; - r1 = ratiod(e->v1->fbcoord[0], e->v2->fbcoord[0], lx); - r2 = ratiod(e->v1->fbcoord[0], e->v2->fbcoord[0], ux); + r1 = ratiod(fbcoord1[0], fbcoord2[0], lx); + r2 = ratiod(fbcoord1[0], fbcoord2[0], ux); if (MIN2(r1, r2) > 1) { return 0; } @@ -4546,8 +4792,8 @@ static LineartBoundingArea *lineart_bounding_area_next(LineartBoundingArea *this else if (positive_y < 0) { by = this->b; bx = x + (by - y) / k; - r1 = ratiod(e->v1->fbcoord[0], e->v2->fbcoord[0], lx); - r2 = ratiod(e->v1->fbcoord[0], e->v2->fbcoord[0], bx); + r1 = ratiod(fbcoord1[0], fbcoord2[0], lx); + r2 = ratiod(fbcoord1[0], fbcoord2[0], bx); if (MIN2(r1, r2) > 1) { return 0; } @@ -4574,7 +4820,7 @@ static LineartBoundingArea *lineart_bounding_area_next(LineartBoundingArea *this } /* Again, horizontal. */ else { - r1 = ratiod(e->v1->fbcoord[0], e->v2->fbcoord[0], this->l); + r1 = ratiod(fbcoord1[0], fbcoord2[0], this->l); if (r1 > 1) { return 0; } @@ -4591,7 +4837,7 @@ static LineartBoundingArea *lineart_bounding_area_next(LineartBoundingArea *this /* If the line is completely vertical, hence X difference == 0. */ else { if (positive_y > 0) { - r1 = ratiod(e->v1->fbcoord[1], e->v2->fbcoord[1], this->u); + r1 = ratiod(fbcoord1[1], fbcoord2[1], this->u); if (r1 > 1) { return 0; } @@ -4605,7 +4851,7 @@ static LineartBoundingArea *lineart_bounding_area_next(LineartBoundingArea *this } } else if (positive_y < 0) { - r1 = ratiod(e->v1->fbcoord[1], e->v2->fbcoord[1], this->b); + r1 = ratiod(fbcoord1[1], fbcoord2[1], this->b); if (r1 > 1) { return 0; } @@ -4626,6 +4872,11 @@ static LineartBoundingArea *lineart_bounding_area_next(LineartBoundingArea *this return 0; } +/** + * This is the entry point of all line art calculations. + * + * \return True when a change is made. + */ bool MOD_lineart_compute_feature_lines(Depsgraph *depsgraph, LineartGpencilModifierData *lmd, LineartCache **cached_result, @@ -4637,7 +4888,6 @@ bool MOD_lineart_compute_feature_lines(Depsgraph *depsgraph, Object *use_camera; double t_start; - if (G.debug_value == 4000) { t_start = PIL_check_seconds_timer(); } @@ -4668,10 +4918,33 @@ bool MOD_lineart_compute_feature_lines(Depsgraph *depsgraph, * See definition of LineartTriangleThread for details. */ ld->sizeof_triangle = lineart_triangle_size_get(ld); + LineartData *shadow_rb = NULL; + LineartElementLinkNode *shadow_veln, *shadow_eeln; + ListBase *shadow_elns = ld->conf.shadow_selection ? &lc->shadow_elns : NULL; + bool shadow_generated = lineart_main_try_generate_shadow(depsgraph, + scene, + ld, + lmd, + &lc->shadow_data_pool, + &shadow_veln, + &shadow_eeln, + shadow_elns, + &shadow_rb); + /* Get view vector before loading geometries, because we detect feature lines there. */ lineart_main_get_view_vector(ld); - lineart_main_load_geometries( - depsgraph, scene, use_camera, ld, lmd->calculation_flags & LRT_ALLOW_DUPLI_OBJECTS); + + lineart_main_load_geometries(depsgraph, + scene, + use_camera, + ld, + lmd->calculation_flags & LRT_ALLOW_DUPLI_OBJECTS, + false, + shadow_elns); + + if (shadow_generated) { + lineart_main_transform_and_add_shadow(ld, shadow_veln, shadow_eeln); + } if (!ld->geom.vertex_buffer_pointers.first) { /* No geometry loaded, return early. */ @@ -4701,6 +4974,9 @@ bool MOD_lineart_compute_feature_lines(Depsgraph *depsgraph, * can do its job. */ lineart_main_add_triangles(ld); + /* Add shadow cuts to intersection lines as well. */ + lineart_register_intersection_shadow_cuts(ld, shadow_elns); + /* Re-link bounding areas because they have been subdivided by worker threads and we need * adjacent info. */ lineart_main_bounding_areas_connect_post(ld); @@ -4719,6 +4995,8 @@ bool MOD_lineart_compute_feature_lines(Depsgraph *depsgraph, /* Occlusion is work-and-wait. This call will not return before work is completed. */ lineart_main_occlusion_begin(ld); + lineart_main_make_enclosed_shapes(ld, shadow_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. */ @@ -4756,6 +5034,10 @@ bool MOD_lineart_compute_feature_lines(Depsgraph *depsgraph, ld, lmd->stroke_depth_offset, lmd->flags & LRT_GPENCIL_OFFSET_TOWARDS_CUSTOM_CAMERA); } + if (ld->conf.shadow_use_silhouette) { + MOD_lineart_chain_find_silhouette_backdrop_objects(ld); + } + /* Finally transfer the result list into cache. */ memcpy(&lc->chains, &ld->chains, sizeof(ListBase)); @@ -4763,6 +5045,13 @@ bool MOD_lineart_compute_feature_lines(Depsgraph *depsgraph, MOD_lineart_chain_clear_picked_flag(lc); } + lineart_mem_destroy(&lc->shadow_data_pool); + + if (ld->conf.shadow_enclose_shapes && shadow_rb) { + lineart_destroy_render_data_keep_init(shadow_rb); + MEM_freeN(shadow_rb); + } + if (G.debug_value == 4000) { lineart_count_and_print_render_buffer_memory(ld); @@ -4773,18 +5062,6 @@ bool MOD_lineart_compute_feature_lines(Depsgraph *depsgraph, return true; } -static int UNUSED_FUNCTION(lineart_rb_edge_types)(LineartData *ld) -{ - int types = 0; - types |= ld->conf.use_contour ? LRT_EDGE_FLAG_CONTOUR : 0; - types |= ld->conf.use_crease ? LRT_EDGE_FLAG_CREASE : 0; - types |= ld->conf.use_material ? LRT_EDGE_FLAG_MATERIAL : 0; - types |= ld->conf.use_edge_marks ? LRT_EDGE_FLAG_EDGE_MARK : 0; - types |= ld->conf.use_intersections ? LRT_EDGE_FLAG_INTERSECTION : 0; - types |= ld->conf.use_loose ? LRT_EDGE_FLAG_LOOSE : 0; - return types; -} - static void lineart_gpencil_generate(LineartCache *cache, Depsgraph *depsgraph, Object *gpencil_object, @@ -4802,6 +5079,8 @@ static void lineart_gpencil_generate(LineartCache *cache, uchar intersection_mask, int16_t thickness, float opacity, + uchar shaodow_selection, + uchar silhouette_mode, const char *source_vgname, const char *vgname, int modifier_flags) @@ -4832,6 +5111,7 @@ static void lineart_gpencil_generate(LineartCache *cache, int enabled_types = cache->all_enabled_edge_types; bool invert_input = modifier_flags & LRT_GPENCIL_INVERT_SOURCE_VGROUP; bool match_output = modifier_flags & LRT_GPENCIL_MATCH_OUTPUT_VGROUP; + bool inverse_silhouette = modifier_flags & LRT_GPENCIL_INVERT_SILHOUETTE_FILTER; LISTBASE_FOREACH (LineartEdgeChain *, ec, &cache->chains) { @@ -4883,11 +5163,55 @@ static void lineart_gpencil_generate(LineartCache *cache, } } } + if (shaodow_selection) { + if (ec->shadow_mask_bits != LRT_SHADOW_MASK_UNDEFINED) { + /* TODO(Yiming): Give a behaviour option for how to display undefined shadow info. */ + if ((shaodow_selection == LRT_SHADOW_FILTER_LIT && + (!(ec->shadow_mask_bits & LRT_SHADOW_MASK_LIT))) || + (shaodow_selection == LRT_SHADOW_FILTER_SHADED && + (!(ec->shadow_mask_bits & LRT_SHADOW_MASK_SHADED)))) { + continue; + } + } + } + if (silhouette_mode && (ec->type & (LRT_EDGE_FLAG_CONTOUR))) { + bool is_silhouette = false; + if (orig_col) { + if (!ec->silhouette_backdrop) { + is_silhouette = true; + } + else if (!BKE_collection_has_object_recursive_instanced(orig_col, + ec->silhouette_backdrop)) { + is_silhouette = true; + } + } + else { + if ((!orig_ob) && (!ec->silhouette_backdrop)) { + is_silhouette = true; + } + } + + if ((silhouette_mode == LRT_SILHOUETTE_FILTER_INDIVIDUAL || orig_ob) && + ec->silhouette_backdrop != ec->object_ref) { + is_silhouette = true; + } + + if (inverse_silhouette) { + is_silhouette = !is_silhouette; + } + if (!is_silhouette) { + continue; + } + } /* Preserved: If we ever do asynchronous generation, this picked flag should be set here. */ // ec->picked = 1; const int count = MOD_lineart_chain_count(ec); + if (count < 2) { + continue; + } + bGPDstroke *gps = BKE_gpencil_stroke_add(gpf, color_idx, count, thickness, false); int i; @@ -4970,6 +5294,8 @@ void MOD_lineart_gpencil_generate(LineartCache *cache, uchar intersection_mask, int16_t thickness, float opacity, + uchar shadow_selection, + uchar silhouette_mode, const char *source_vgname, const char *vgname, int modifier_flags) @@ -4981,26 +5307,20 @@ void MOD_lineart_gpencil_generate(LineartCache *cache, Object *source_object = NULL; Collection *source_collection = NULL; - int16_t use_types = 0; + int16_t use_types = edge_types; 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 = edge_types & (~LRT_EDGE_FLAG_INTERSECTION); } else if (source_type == LRT_SOURCE_COLLECTION) { if (!source_reference) { return; } source_collection = (Collection *)source_reference; - use_types = edge_types; - } - else { - /* Whole scene. */ - use_types = edge_types; } + float gp_obmat_inverse[4][4]; invert_m4_m4(gp_obmat_inverse, ob->obmat); lineart_gpencil_generate(cache, @@ -5020,6 +5340,8 @@ void MOD_lineart_gpencil_generate(LineartCache *cache, intersection_mask, thickness, opacity, + shadow_selection, + silhouette_mode, 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 index 5e24061bd78..3668f1dc6d7 100644 --- a/source/blender/gpencil_modifiers/intern/lineart/lineart_intern.h +++ b/source/blender/gpencil_modifiers/intern/lineart/lineart_intern.h @@ -66,14 +66,16 @@ int lineart_count_intersection_segment_count(struct LineartData *ld); void lineart_count_and_print_render_buffer_memory(struct LineartData *ld); #define LRT_ITER_ALL_LINES_BEGIN \ - LineartEdge *e; \ - for (int i = 0; i < ld->pending_edges.next; i++) { \ - e = ld->pending_edges.array[i]; + { \ + LineartEdge *e; \ + for (int __i = 0; __i < ld->pending_edges.next; __i++) { \ + e = ld->pending_edges.array[__i]; #define LRT_ITER_ALL_LINES_NEXT ; /* Doesn't do anything now with new array setup. */ #define LRT_ITER_ALL_LINES_END \ LRT_ITER_ALL_LINES_NEXT \ + } \ } #define LRT_BOUND_AREA_CROSSES(b1, b2) \ @@ -83,11 +85,94 @@ void lineart_count_and_print_render_buffer_memory(struct LineartData *ld); * performance under current algorithm. */ #define LRT_BA_ROWS 10 +#define LRT_EDGE_BA_MARCHING_BEGIN(fb1, fb2) \ + double x = fb1[0], y = fb1[1]; \ + LineartBoundingArea *ba = lineart_edge_first_bounding_area(ld, fb1, fb2); \ + LineartBoundingArea *nba = ba; \ + double k = (fb2[1] - fb1[1]) / (fb2[0] - fb1[0] + 1e-30); \ + int positive_x = (fb2[0] - fb1[0]) > 0 ? 1 : (fb2[0] == fb1[0] ? 0 : -1); \ + int positive_y = (fb2[1] - fb1[1]) > 0 ? 1 : (fb2[1] == fb1[1] ? 0 : -1); \ + while (nba) + +#define LRT_EDGE_BA_MARCHING_NEXT(fb1, fb2) \ + /* Marching along `e->v1` to `e->v2`, searching each possible bounding areas it may touch. */ \ + nba = lineart_bounding_area_next(nba, fb1, fb2, x, y, k, positive_x, positive_y, &x, &y); + +#define LRT_EDGE_BA_MARCHING_END + +void lineart_main_occlusion_begin(struct LineartData *ld); +void lineart_main_cull_triangles(struct LineartData *ld, bool clip_far); +void lineart_main_free_adjacent_data(struct LineartData *ld); +void lineart_main_perspective_division(struct LineartData *ld); +void lineart_main_discard_out_of_frame_edges(struct LineartData *ld); +void lineart_main_load_geometries(struct Depsgraph *depsgraph, + struct Scene *scene, + struct Object *camera, + struct LineartData *ld, + bool allow_duplicates, + bool do_shadow_casting, + struct ListBase *shadow_elns); +void lineart_main_get_view_vector(struct LineartData *ld); +void lineart_main_bounding_area_make_initial(struct LineartData *ld); +void lineart_main_bounding_areas_connect_post(struct LineartData *ld); +void lineart_main_clear_linked_edges(struct LineartData *ld); +void lineart_main_link_lines(struct LineartData *ld); +void lineart_main_add_triangles(struct LineartData *ld); +bool lineart_main_try_generate_shadow(struct Depsgraph *depsgraph, + struct Scene *scene, + struct LineartData *original_ld, + struct LineartGpencilModifierData *lmd, + struct LineartStaticMemPool *shadow_data_pool, + struct LineartElementLinkNode **r_veln, + struct LineartElementLinkNode **r_eeln, + struct ListBase *r_calculated_edges_eln_list, + struct LineartData **r_shadow_ld_if_reproject); +void lineart_main_make_enclosed_shapes(struct LineartData *ld, struct LineartData *shadow_ld); +void lineart_main_transform_and_add_shadow(struct LineartData *ld, + struct LineartElementLinkNode *veln, + struct LineartElementLinkNode *eeln); + +LineartElementLinkNode *lineart_find_matching_eln(struct ListBase *shadow_elns, int obindex); +LineartEdge *lineart_find_matching_edge(struct LineartElementLinkNode *shadow_eln, + uint64_t edge_identifier); +void lineart_register_shadow_cuts(struct LineartData *ld, + struct LineartEdge *e, + struct LineartEdge *shadow_edge); +void lineart_register_intersection_shadow_cuts(struct LineartData *ld, + struct ListBase *shadow_elns); + +bool lineart_edge_from_triangle(const struct LineartTriangle *tri, + const struct LineartEdge *e, + bool allow_overlapping_edges); +LineartBoundingArea *lineart_edge_first_bounding_area(struct LineartData *ld, + double *fbcoord1, + double *fbcoord2); +LineartBoundingArea *lineart_bounding_area_next(struct LineartBoundingArea *_this, + double *fbcoord1, + double *fbcoord2, + double x, + double y, + double k, + int positive_x, + int positive_y, + double *next_x, + double *next_y); +void lineart_edge_cut(struct LineartData *ld, + struct LineartEdge *e, + double start, + double end, + uchar material_mask_bits, + uchar mat_occlusion, + uint32_t shadow_bits); +void lineart_add_edge_to_array(struct LineartPendingEdges *pe, struct LineartEdge *e); +void lineart_finalize_object_edge_array_reserve(struct LineartPendingEdges *pe, int count); +void lineart_destroy_render_data_keep_init(struct LineartData *ld); + #ifdef __cplusplus extern "C" { #endif -void lineart_sort_adjacent_items(LineartAdjacentEdge *ai, int length); +void lineart_sort_adjacent_items(struct LineartAdjacentEdge *ai, int length); #ifdef __cplusplus } diff --git a/source/blender/gpencil_modifiers/intern/lineart/lineart_ops.c b/source/blender/gpencil_modifiers/intern/lineart/lineart_ops.c index bbf88985e6a..138c016e2e2 100644 --- a/source/blender/gpencil_modifiers/intern/lineart/lineart_ops.c +++ b/source/blender/gpencil_modifiers/intern/lineart/lineart_ops.c @@ -132,6 +132,8 @@ static bool bake_strokes(Object *ob, lmd->intersection_mask, lmd->thickness, lmd->opacity, + lmd->shadow_selection, + lmd->silhouette_selection, lmd->source_vertex_group, lmd->vgname, lmd->flags); diff --git a/source/blender/gpencil_modifiers/intern/lineart/lineart_shadow.c b/source/blender/gpencil_modifiers/intern/lineart/lineart_shadow.c new file mode 100644 index 00000000000..ec1d9a14233 --- /dev/null +++ b/source/blender/gpencil_modifiers/intern/lineart/lineart_shadow.c @@ -0,0 +1,1414 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup modifiers + */ + +#include "MOD_gpencil_lineart.h" +#include "MOD_lineart.h" + +#include "lineart_intern.h" + +#include "BKE_global.h" +#include "BKE_gpencil_modifier.h" +#include "BKE_lib_id.h" +#include "BKE_material.h" +#include "BKE_object.h" +#include "BKE_scene.h" +#include "DEG_depsgraph_query.h" +#include "DNA_collection_types.h" +#include "DNA_gpencil_types.h" +#include "DNA_light_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 "MEM_guardedalloc.h" + +#include "BLI_task.h" +#include "PIL_time.h" + +/* Shadow loading etc. ================== */ + +LineartElementLinkNode *lineart_find_matching_eln(ListBase *shadow_elns, int obindex) +{ + LISTBASE_FOREACH (LineartElementLinkNode *, eln, shadow_elns) { + if (eln->obindex == obindex) { + return eln; + } + } + return NULL; +} + +LineartEdge *lineart_find_matching_edge(LineartElementLinkNode *shadow_eln, + uint64_t edge_identifier) +{ + LineartEdge *elist = (LineartEdge *)shadow_eln->pointer; + for (int i = 0; i < shadow_eln->element_count; i++) { + if (elist[i].edge_identifier == edge_identifier) { + return &elist[i]; + } + } + return NULL; +} + +static bool lineart_contour_viewed_from_dark_side(LineartData *ld, LineartEdge *e) +{ + + if (!(e->flags & (LRT_EDGE_FLAG_CONTOUR | LRT_EDGE_FLAG_CONTOUR_SECONDARY))) { + return false; + } + double view_vector[3]; + double light_vector[3]; + bool side_1_facing_light = false; + bool side_2_facing_light = false; + bool side_1_facing_camera = false; + if (ld->conf.cam_is_persp_secondary) { + sub_v3_v3v3_db(light_vector, ld->conf.camera_pos_secondary, e->v1->gloc); + } + else { + copy_v3_v3_db(light_vector, ld->conf.view_vector_secondary); + } + double dot_light_1 = dot_v3v3_db(light_vector, e->t1->gn); + side_1_facing_light = (dot_light_1 > 0); + if (e->t2) { + double dot_light_2 = dot_v3v3_db(light_vector, e->t2->gn); + side_2_facing_light = (dot_light_2 > 0); + } + else { + side_2_facing_light = !side_1_facing_light; + } + + if (ld->conf.cam_is_persp) { + sub_v3_v3v3_db(view_vector, ld->conf.camera_pos, e->v1->gloc); + } + else { + copy_v3_v3_db(view_vector, ld->conf.view_vector); + } + double dot_view_1 = dot_v3v3_db(view_vector, e->t1->gn); + side_1_facing_camera = (dot_view_1 > 0); + + if ((side_1_facing_camera && (!side_1_facing_light) && side_2_facing_light) || + ((!side_1_facing_camera) && side_1_facing_light && (!side_2_facing_light))) { + return true; + } + return false; +} + +/* Cuts the original edge based on the occlusion results under light-camera, if segment + * is occluded in light-camera, then that segment on the original edge must be shaded. */ +void lineart_register_shadow_cuts(LineartData *ld, LineartEdge *e, LineartEdge *shadow_edge) +{ + LISTBASE_FOREACH (LineartEdgeSegment *, es, &shadow_edge->segments) { + /* Convert to view space cutting points. */ + double la1 = es->ratio; + double la2 = es->next ? es->next->ratio : 1.0f; + la1 = la1 * e->v2->fbcoord[3] / + (e->v1->fbcoord[3] - la1 * (e->v1->fbcoord[3] - e->v2->fbcoord[3])); + la2 = la2 * e->v2->fbcoord[3] / + (e->v1->fbcoord[3] - la2 * (e->v1->fbcoord[3] - e->v2->fbcoord[3])); + unsigned char shadow_bits = (es->occlusion != 0) ? LRT_SHADOW_MASK_SHADED : + LRT_SHADOW_MASK_LIT; + + if (lineart_contour_viewed_from_dark_side(ld, e) && shadow_bits == LRT_SHADOW_MASK_LIT) { + shadow_bits = LRT_SHADOW_MASK_SHADED; + } + + lineart_edge_cut(ld, e, la1, la2, 0, 0, shadow_bits); + } +} + +void lineart_register_intersection_shadow_cuts(LineartData *ld, ListBase *shadow_elns) +{ + if (!shadow_elns) { + return; + } + + LineartElementLinkNode *eln_isect_shadow = NULL; + LineartElementLinkNode *eln_isect_original = NULL; + + LISTBASE_FOREACH (LineartElementLinkNode *, eln, shadow_elns) { + if (eln->flags & LRT_ELEMENT_INTERSECTION_DATA) { + eln_isect_shadow = eln; + break; + } + } + LISTBASE_FOREACH (LineartElementLinkNode *, eln, &ld->geom.line_buffer_pointers) { + if (eln->flags & LRT_ELEMENT_INTERSECTION_DATA) { + eln_isect_original = eln; + break; + } + } + if (!eln_isect_shadow || !eln_isect_original) { + return; + } + + /* Keeping it single threaded for now because a simple parallel_for could end up getting the same + * #shadow_e in different threads. */ + for (int i = 0; i < eln_isect_original->element_count; i++) { + LineartEdge *e = &((LineartEdge *)eln_isect_original->pointer)[i]; + LineartEdge *shadow_e = lineart_find_matching_edge(eln_isect_shadow, + (uint64_t)e->edge_identifier); + if (shadow_e) { + lineart_register_shadow_cuts(ld, e, shadow_e); + } + } +} + +/* Shadow computation part ================== */ + +static LineartShadowSegment *lineart_give_shadow_segment(LineartData *ld) +{ + BLI_spin_lock(&ld->lock_cuts); + + /* See if there is any already allocated memory we can reuse. */ + if (ld->wasted_shadow_cuts.first) { + LineartShadowSegment *es = (LineartShadowSegment *)BLI_pophead(&ld->wasted_shadow_cuts); + BLI_spin_unlock(&ld->lock_cuts); + memset(es, 0, sizeof(LineartShadowSegment)); + return (LineartShadowSegment *)es; + } + BLI_spin_unlock(&ld->lock_cuts); + + /* Otherwise allocate some new memory. */ + return (LineartShadowSegment *)lineart_mem_acquire_thread(&ld->render_data_pool, + sizeof(LineartShadowSegment)); +} + +static void lineart_shadow_segment_slice_get(double *fb_co_1, + double *fb_co_2, + double *gloc_1, + double *gloc_2, + double ratio, + double at_1, + double at_2, + double *r_fb_co, + double *r_gloc) +{ + double real_at = ((at_2 - at_1) == 0) ? 0 : ((ratio - at_1) / (at_2 - at_1)); + double ga = fb_co_1[3] * real_at / (fb_co_2[3] * (1.0f - real_at) + fb_co_1[3] * real_at); + interp_v3_v3v3_db(r_fb_co, fb_co_1, fb_co_2, real_at); + r_fb_co[3] = interpd(fb_co_2[3], fb_co_1[3], ga); + interp_v3_v3v3_db(r_gloc, gloc_1, gloc_2, ga); +} + +/* This function tries to get the closest projected segments along two end points. + * The x,y of s1, s2 are aligned in framebuffer coordinates, only z,w are different. + * We will get the closest z/w as well as the corresponding global coordinates. + * + * (far side) + * l-------r [s1] ^ + * _-r [s2] | In this situation it will essentially return the coordinates of s2. + * _-` | + * l-` | + * + * (far side) + * _-r [s2] ^ + * _-` | In this case the return coordinates would be `s2l` and `s1r`, + * l-----_c`-----r [s1] | and `r_new` will be assigned coordinates of `c`. + * _-` | + * l-` | + * + * Returns true when a new cut (`c`) is needed in the middle, otherwise returns false, and + * `*r_new_xxx` are not touched. */ +static bool lineart_do_closest_segment(bool is_persp, + double *s1_fb_co_1, + double *s1_fb_co_2, + double *s2_fb_co_1, + double *s2_fb_co_2, + double *s1_gloc_1, + double *s1_gloc_2, + double *s2_gloc_1, + double *s2_gloc_2, + double *r_fb_co_1, + double *r_fb_co_2, + double *r_gloc_1, + double *r_gloc_2, + double *r_new_in_the_middle, + double *r_new_in_the_middle_global, + double *r_new_at, + bool *is_side_2r, + bool *use_new_ref) +{ + int side = 0; + int z_index = is_persp ? 3 : 2; + /* Always use the closest point to the light camera. */ + if (s1_fb_co_1[z_index] >= s2_fb_co_1[z_index]) { + copy_v4_v4_db(r_fb_co_1, s2_fb_co_1); + copy_v3_v3_db(r_gloc_1, s2_gloc_1); + side++; + } + if (s1_fb_co_2[z_index] >= s2_fb_co_2[z_index]) { + copy_v4_v4_db(r_fb_co_2, s2_fb_co_2); + copy_v3_v3_db(r_gloc_2, s2_gloc_2); + *is_side_2r = true; + side++; + } + if (s1_fb_co_1[z_index] <= s2_fb_co_1[z_index]) { + copy_v4_v4_db(r_fb_co_1, s1_fb_co_1); + copy_v3_v3_db(r_gloc_1, s1_gloc_1); + side--; + } + if (s1_fb_co_2[z_index] <= s2_fb_co_2[z_index]) { + copy_v4_v4_db(r_fb_co_2, s1_fb_co_2); + copy_v3_v3_db(r_gloc_2, s1_gloc_2); + *is_side_2r = false; + side--; + } + + /* No need to cut in the middle, because one segment completely overlaps the other. */ + if (side) { + if (side > 0) { + *is_side_2r = true; + *use_new_ref = true; + } + else if (side < 0) { + *is_side_2r = false; + *use_new_ref = false; + } + return false; + } + + /* Else there must be an intersection point in the middle. Use "w" value to linearly plot the + * position and get image space "ratio" position. */ + double dl = s1_fb_co_1[z_index] - s2_fb_co_1[z_index]; + double dr = s1_fb_co_2[z_index] - s2_fb_co_2[z_index]; + double ga = ratiod(dl, dr, 0); + *r_new_at = is_persp ? s2_fb_co_2[3] * ga / (s2_fb_co_1[3] * (1.0f - ga) + s2_fb_co_2[3] * ga) : + ga; + interp_v3_v3v3_db(r_new_in_the_middle, s2_fb_co_1, s2_fb_co_2, *r_new_at); + r_new_in_the_middle[3] = interpd(s2_fb_co_2[3], s2_fb_co_1[3], ga); + interp_v3_v3v3_db(r_new_in_the_middle_global, s1_gloc_1, s1_gloc_2, ga); + *use_new_ref = true; + + return true; +} + +/* For each visible [segment] of the edge, create 1 shadow edge. Note if the original edge has + * multiple visible cuts, multiple shadow edges should be generated. */ +static void lineart_shadow_create_shadow_edge_array(LineartData *ld, + bool transform_edge_cuts, + bool do_light_contour) +{ + /* If the segment is short enough, we ignore them because it's not prominently visible anyway. */ +#define DISCARD_NONSENSE_SEGMENTS \ + if (es->occlusion != 0 || \ + (es->next && \ + LRT_DOUBLE_CLOSE_ENOUGH(es->ratio, ((LineartEdgeSegment *)es->next)->ratio))) { \ + LRT_ITER_ALL_LINES_NEXT; \ + continue; \ + } + + /* Count and allocate at once to save time. */ + int segment_count = 0; + uint16_t accept_types = (LRT_EDGE_FLAG_CONTOUR | LRT_EDGE_FLAG_LOOSE); + if (do_light_contour) { + accept_types |= LRT_EDGE_FLAG_LIGHT_CONTOUR; + } + LRT_ITER_ALL_LINES_BEGIN + { + /* Only contour and loose edges can actually cast shadows. We allow light contour here because + * we want to see if it also doubles as a view contouror, in that case we also need to project + * them. */ + if (!(e->flags & accept_types)) { + continue; + } + if (e->flags == LRT_EDGE_FLAG_LIGHT_CONTOUR) { + /* Check if the light contour also doubles as a view contour. */ + LineartEdge *orig_e = (LineartEdge *)e->t1; + if (!orig_e->t2) { + e->flags |= LRT_EDGE_FLAG_CONTOUR; + } + else { + double vv[3]; + double *view_vector = vv; + double dot_1 = 0, dot_2 = 0; + double result; + if (ld->conf.cam_is_persp) { + sub_v3_v3v3_db(view_vector, orig_e->v1->gloc, ld->conf.camera_pos); + } + else { + view_vector = ld->conf.view_vector; + } + + dot_1 = dot_v3v3_db(view_vector, orig_e->t1->gn); + dot_2 = dot_v3v3_db(view_vector, orig_e->t2->gn); + + if ((result = dot_1 * dot_2) <= 0 && (dot_1 + dot_2)) { + /* If this edge is both a light contour and a view contour, mark it for the convenience + * of generating it in the next iteration. */ + e->flags |= LRT_EDGE_FLAG_CONTOUR; + } + } + if (!(e->flags & LRT_EDGE_FLAG_CONTOUR)) { + continue; + } + } + LISTBASE_FOREACH (LineartEdgeSegment *, es, &e->segments) { + DISCARD_NONSENSE_SEGMENTS + segment_count++; + } + } + LRT_ITER_ALL_LINES_END + + LineartShadowEdge *sedge = lineart_mem_acquire(&ld->render_data_pool, + sizeof(LineartShadowEdge) * segment_count); + LineartShadowSegment *sseg = lineart_mem_acquire( + &ld->render_data_pool, sizeof(LineartShadowSegment) * segment_count * 2); + + ld->shadow_edges = sedge; + ld->shadow_edges_count = segment_count; + + int i = 0; + LRT_ITER_ALL_LINES_BEGIN + { + if (!(e->flags & (LRT_EDGE_FLAG_CONTOUR | LRT_EDGE_FLAG_LOOSE))) { + continue; + } + LISTBASE_FOREACH (LineartEdgeSegment *, es, &e->segments) { + DISCARD_NONSENSE_SEGMENTS + + double next_at = es->next ? ((LineartEdgeSegment *)es->next)->ratio : 1.0f; + /* Get correct XYZ and W coordinates. */ + interp_v3_v3v3_db(sedge[i].fbc1, e->v1->fbcoord, e->v2->fbcoord, es->ratio); + interp_v3_v3v3_db(sedge[i].fbc2, e->v1->fbcoord, e->v2->fbcoord, next_at); + + /* Global coord for light-shadow separation line (occlusion-corrected light contour). */ + double ga1 = e->v1->fbcoord[3] * es->ratio / + (es->ratio * e->v1->fbcoord[3] + (1 - es->ratio) * e->v2->fbcoord[3]); + double ga2 = e->v1->fbcoord[3] * next_at / + (next_at * e->v1->fbcoord[3] + (1 - next_at) * e->v2->fbcoord[3]); + interp_v3_v3v3_db(sedge[i].g1, e->v1->gloc, e->v2->gloc, ga1); + interp_v3_v3v3_db(sedge[i].g2, e->v1->gloc, e->v2->gloc, ga2); + + /* Assign an absurdly big W for initial distance so when triangles show up to catch the + * shadow, their w must certainlly be smaller than this value so the shadow catches + * successfully. */ + sedge[i].fbc1[3] = 1e30; + sedge[i].fbc2[3] = 1e30; + sedge[i].fbc1[2] = 1e30; + sedge[i].fbc2[2] = 1e30; + + /* Assign to the first segment's right and the last segment's left position */ + copy_v4_v4_db(sseg[i * 2].fbc2, sedge[i].fbc1); + copy_v4_v4_db(sseg[i * 2 + 1].fbc1, sedge[i].fbc2); + sseg[i * 2].ratio = 0.0f; + sseg[i * 2 + 1].ratio = 1.0f; + BLI_addtail(&sedge[i].shadow_segments, &sseg[i * 2]); + BLI_addtail(&sedge[i].shadow_segments, &sseg[i * 2 + 1]); + + if (e->flags & LRT_EDGE_FLAG_LIGHT_CONTOUR) { + sedge[i].e_ref = (LineartEdge *)e->t1; + sedge[i].e_ref_light_contour = e; + /* Restore original edge flag for edges "who is both view and light contour" so we still + * have correct edge flags. */ + e->flags &= (~LRT_EDGE_FLAG_CONTOUR); + } + else { + sedge[i].e_ref = e; + } + + sedge[i].es_ref = es; + + i++; + } + } + LRT_ITER_ALL_LINES_END + + /* Transform the cutting position to global space for regular feature lines. This is for + * convenience of reusing the shadow cast function for both shadow line generation and silhouette + * registration, which the latter one needs view-space coordinates, while cast shadow needs + * global-space coordinates. */ + if (transform_edge_cuts) { + LRT_ITER_ALL_LINES_BEGIN + { + LISTBASE_FOREACH (LineartEdgeSegment *, es, &e->segments) { + es->ratio = e->v1->fbcoord[3] * es->ratio / + (es->ratio * e->v1->fbcoord[3] + (1 - es->ratio) * e->v2->fbcoord[3]); + } + } + LRT_ITER_ALL_LINES_END + } + + if (G.debug_value == 4000) { + printf("Shadow: Added %d raw shadow_edges\n", segment_count); + } +} + +/* This function does the actual cutting on a given "shadow edge". + * #start / #end determines the view(from light camera) space cutting ratio. + * #start/end_gloc/fbc are the respective start/end coordinates. + * #facing_light is set from the caller which determines if this edge landed on a triangle's light + * facing side or not. + * + * Visually this function does this: (Top is the far side of the camera) + * _-end + * _-` + * l[-------------_-`--------------]r [e] 1) Calls for cut on top of #e. + * _-` + * _-` + * start-` + * + * _-end + * _-` + * l[-----][------_-`----][--------]r [e] 2) Add cutting points on #e at #start/#end. + * _-` + * _-` + * start-` + * + * _-end + * _-` + * [------_-`----] 3) Call lineart_shadow_segment_slice_get() to + * _-` get coordinates of a visually aligned segment on + * _-` #e with the incoming segment. + * start-` + * + * _c-----] 4) Call lineart_do_closest_segment() to find out the + * _-` actual geometry after cut, add a new cut if needed. + * _-` + * [` + * + * l[-----] _][----][--------]r [e] 5) Write coordinates on cuts. + * _-` + * _-` + * [` + * + * This process is repeated on each existing segments of the shadow edge (#e), which ensures they + * all have been tested for closest segments after cutting. And in the diagram it's clear that the + * left/right side of cuts are likely to be discontinuous, each cut's left side designates the + * right side of the last segment, and vise versa. */ +static void lineart_shadow_edge_cut(LineartData *ld, + LineartShadowEdge *e, + double start, + double end, + double *start_gloc, + double *end_gloc, + double *start_fb_co, + double *end_fb_co, + bool facing_light, + uint32_t target_reference) +{ + LineartShadowSegment *seg, *i_seg; + LineartShadowSegment *cut_start_after = e->shadow_segments.first, + *cut_end_before = e->shadow_segments.last; + LineartShadowSegment *new_seg_1 = NULL, *new_seg_2 = NULL, *seg_1 = NULL, *seg_2 = NULL; + int untouched = 0; + + /* If for some reason the occlusion function may give a result that has zero length, or + * reversed in direction, or NAN, 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 (seg = e->shadow_segments.first; seg; seg = seg->next) { + if (LRT_DOUBLE_CLOSE_ENOUGH(seg->ratio, start)) { + cut_start_after = seg; + new_seg_1 = cut_start_after; + break; + } + if (seg->next == NULL) { + break; + } + i_seg = seg->next; + if (i_seg->ratio > start + 1e-09 && start > seg->ratio) { + cut_start_after = seg; + new_seg_1 = lineart_give_shadow_segment(ld); + break; + } + } + if (!cut_start_after && LRT_DOUBLE_CLOSE_ENOUGH(1, end)) { + untouched = 1; + } + for (seg = cut_start_after->next; seg; seg = seg->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(seg->ratio, end)) { + cut_end_before = seg; + new_seg_2 = cut_end_before; + break; + } + /* This check is to prevent `es->ratio == 1.0` (where we don't need to cut because we are ratio + * the end point). */ + if (!seg->next && LRT_DOUBLE_CLOSE_ENOUGH(1, end)) { + cut_end_before = seg; + new_seg_2 = cut_end_before; + untouched = 1; + break; + } + /* When an actual cut is needed in the line. */ + if (seg->ratio > end) { + cut_end_before = seg; + new_seg_2 = lineart_give_shadow_segment(ld); + break; + } + } + + /* When we still can't find any existing cut in the line, we allocate new ones. */ + if (new_seg_1 == NULL) { + new_seg_1 = lineart_give_shadow_segment(ld); + } + if (new_seg_2 == NULL) { + if (untouched) { + new_seg_2 = new_seg_1; + cut_end_before = new_seg_2; + } + else { + new_seg_2 = lineart_give_shadow_segment(ld); + } + } + + /* If we touched the cut list, we assign the new cut position based on new cut position, + * this way we accommodate precision lost due to multiple cut inserts. */ + new_seg_1->ratio = start; + if (!untouched) { + new_seg_2->ratio = end; + } + + double r_fb_co_1[4], r_fb_co_2[4], r_gloc_1[3], r_gloc_2[3]; + double r_new_in_the_middle[4], r_new_in_the_middle_global[3], r_new_at; + double *s1_fb_co_1, *s1_fb_co_2, *s1_gloc_1, *s1_gloc_2; + + /* Temporary coordinate records and "middle" records. */ + double t_g1[3], t_g2[3], t_fbc1[4], t_fbc2[4], m_g1[3], m_fbc1[4], m_g2[3], m_fbc2[4]; + bool is_side_2r, has_middle = false, use_new_ref; + copy_v4_v4_db(t_fbc1, start_fb_co); + copy_v3_v3_db(t_g1, start_gloc); + + /* Do max stuff before insert. */ + LineartShadowSegment *nes; + for (seg = cut_start_after; seg != cut_end_before; seg = nes) { + nes = seg->next; + + s1_fb_co_1 = seg->fbc2, s1_fb_co_2 = nes->fbc1; + s1_gloc_1 = seg->g2, s1_gloc_2 = nes->g1; + seg_1 = seg, seg_2 = nes; + if (seg == cut_start_after) { + lineart_shadow_segment_slice_get(seg->fbc2, + nes->fbc1, + seg->g2, + nes->g1, + new_seg_1->ratio, + seg->ratio, + nes->ratio, + m_fbc1, + m_g1); + s1_fb_co_1 = m_fbc1, s1_gloc_1 = m_g1; + seg_1 = new_seg_1; + if (cut_start_after != new_seg_1) { + BLI_insertlinkafter(&e->shadow_segments, cut_start_after, new_seg_1); + copy_v4_v4_db(new_seg_1->fbc1, m_fbc1); + copy_v3_v3_db(new_seg_1->g1, m_g1); + } + } + if (nes == cut_end_before) { + lineart_shadow_segment_slice_get(seg->fbc2, + nes->fbc1, + seg->g2, + nes->g1, + new_seg_2->ratio, + seg->ratio, + nes->ratio, + m_fbc2, + m_g2); + s1_fb_co_2 = m_fbc2, s1_gloc_2 = m_g2; + seg_2 = new_seg_2; + if (cut_end_before != new_seg_2) { + BLI_insertlinkbefore(&e->shadow_segments, cut_end_before, new_seg_2); + copy_v4_v4_db(new_seg_2->fbc2, m_fbc2); + copy_v3_v3_db(new_seg_2->g2, m_g2); + /* Need to restore the flag for next segment's reference. */ + seg_2->flag = seg->flag; + seg_2->target_reference = seg->target_reference; + } + } + + lineart_shadow_segment_slice_get( + start_fb_co, end_fb_co, start_gloc, end_gloc, seg_2->ratio, start, end, t_fbc2, t_g2); + + if ((has_middle = lineart_do_closest_segment(ld->conf.cam_is_persp, + s1_fb_co_1, + s1_fb_co_2, + t_fbc1, + t_fbc2, + s1_gloc_1, + s1_gloc_2, + t_g1, + t_g2, + r_fb_co_1, + r_fb_co_2, + r_gloc_1, + r_gloc_2, + r_new_in_the_middle, + r_new_in_the_middle_global, + &r_new_at, + &is_side_2r, + &use_new_ref))) { + LineartShadowSegment *ss_middle = lineart_give_shadow_segment(ld); + ss_middle->ratio = interpf(seg_2->ratio, seg_1->ratio, r_new_at); + ss_middle->flag = LRT_SHADOW_CASTED | + (use_new_ref ? (facing_light ? LRT_SHADOW_FACING_LIGHT : 0) : seg_1->flag); + ss_middle->target_reference = (use_new_ref ? (target_reference) : seg_1->target_reference); + copy_v3_v3_db(ss_middle->g1, r_new_in_the_middle_global); + copy_v3_v3_db(ss_middle->g2, r_new_in_the_middle_global); + copy_v4_v4_db(ss_middle->fbc1, r_new_in_the_middle); + copy_v4_v4_db(ss_middle->fbc2, r_new_in_the_middle); + BLI_insertlinkafter(&e->shadow_segments, seg_1, ss_middle); + } + /* Always assign the "closest" value to the segment. */ + copy_v4_v4_db(seg_1->fbc2, r_fb_co_1); + copy_v3_v3_db(seg_1->g2, r_gloc_1); + copy_v4_v4_db(seg_2->fbc1, r_fb_co_2); + copy_v3_v3_db(seg_2->g1, r_gloc_2); + + if (has_middle) { + seg_1->flag = LRT_SHADOW_CASTED | + (is_side_2r ? seg->flag : (facing_light ? LRT_SHADOW_FACING_LIGHT : 0)); + seg_1->target_reference = is_side_2r ? seg->target_reference : target_reference; + } + else { + seg_1->flag = LRT_SHADOW_CASTED | + (use_new_ref ? (facing_light ? LRT_SHADOW_FACING_LIGHT : 0) : seg->flag); + seg_1->target_reference = use_new_ref ? target_reference : seg->target_reference; + } + + copy_v4_v4_db(t_fbc1, t_fbc2); + copy_v3_v3_db(t_g1, t_g2); + } +} + +static bool lineart_shadow_cast_onto_triangle(LineartData *ld, + LineartTriangle *tri, + LineartShadowEdge *sedge, + double *r_at_1, + double *r_at_2, + double *r_fb_co_1, + double *r_fb_co_2, + double *r_gloc_1, + double *r_gloc_2, + bool *r_facing_light) +{ + + double *LFBC = sedge->fbc1, *RFBC = sedge->fbc2, *FBC0 = tri->v[0]->fbcoord, + *FBC1 = tri->v[1]->fbcoord, *FBC2 = tri->v[2]->fbcoord; + + /* Bound box check. Because we have already done occlusion in the shadow camera, so any visual + * intersection found in this function must mean that the triangle is behind the given line so it + * will always project a shadow, hence no need to do depth boundbox check. */ + 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]))) { + return false; + } + + bool is_persp = ld->conf.cam_is_persp; + double ratio[2]; + int trie[2]; + int pi = 0; + if (lineart_line_isec_2d_ignore_line2pos(FBC0, FBC1, LFBC, RFBC, &ratio[pi])) { + trie[pi] = 0; + pi++; + } + if (lineart_line_isec_2d_ignore_line2pos(FBC1, FBC2, LFBC, RFBC, &ratio[pi])) { + /* ratio[0] == 1 && ratio[1] == 0 means we found a intersection at the same point of the + * edge (FBC1), ignore this one and try get the intersection point from the other side of + * the edge + */ + if (!(pi && LRT_DOUBLE_CLOSE_ENOUGH(ratio[0], 1.0f) && + LRT_DOUBLE_CLOSE_ENOUGH(ratio[1], 0.0f))) { + trie[pi] = 1; + pi++; + } + } + if (!pi) { + return false; + } + if (pi == 1 && lineart_line_isec_2d_ignore_line2pos(FBC2, FBC0, LFBC, RFBC, &ratio[pi])) { + + if ((trie[0] == 0 && LRT_DOUBLE_CLOSE_ENOUGH(ratio[0], 0.0f) && + LRT_DOUBLE_CLOSE_ENOUGH(ratio[1], 1.0f)) || + (trie[0] == 1 && LRT_DOUBLE_CLOSE_ENOUGH(ratio[0], 1.0f) && + LRT_DOUBLE_CLOSE_ENOUGH(ratio[1], 0.0f))) { + return false; + } + trie[pi] = 2; + pi++; + } + + if (pi != 2) { + return false; + } + + /* Get projected global position. */ + + double gpos1[3], gpos2[3]; + double *v1 = (trie[0] == 0 ? FBC0 : (trie[0] == 1 ? FBC1 : FBC2)); + double *v2 = (trie[0] == 0 ? FBC1 : (trie[0] == 1 ? FBC2 : FBC0)); + double *v3 = (trie[1] == 0 ? FBC0 : (trie[1] == 1 ? FBC1 : FBC2)); + double *v4 = (trie[1] == 0 ? FBC1 : (trie[1] == 1 ? FBC2 : FBC0)); + double *gv1 = (trie[0] == 0 ? tri->v[0]->gloc : + (trie[0] == 1 ? tri->v[1]->gloc : tri->v[2]->gloc)); + double *gv2 = (trie[0] == 0 ? tri->v[1]->gloc : + (trie[0] == 1 ? tri->v[2]->gloc : tri->v[0]->gloc)); + double *gv3 = (trie[1] == 0 ? tri->v[0]->gloc : + (trie[1] == 1 ? tri->v[1]->gloc : tri->v[2]->gloc)); + double *gv4 = (trie[1] == 0 ? tri->v[1]->gloc : + (trie[1] == 1 ? tri->v[2]->gloc : tri->v[0]->gloc)); + double gr1 = is_persp ? v1[3] * ratio[0] / (ratio[0] * v1[3] + (1 - ratio[0]) * v2[3]) : + ratio[0]; + double gr2 = is_persp ? v3[3] * ratio[1] / (ratio[1] * v3[3] + (1 - ratio[1]) * v4[3]) : + ratio[1]; + interp_v3_v3v3_db(gpos1, gv1, gv2, gr1); + interp_v3_v3v3_db(gpos2, gv3, gv4, gr2); + + double fbc1[4], fbc2[4]; + + mul_v4_m4v3_db(fbc1, ld->conf.view_projection, gpos1); + mul_v4_m4v3_db(fbc2, ld->conf.view_projection, gpos2); + if (is_persp) { + mul_v3db_db(fbc1, 1.0f / fbc1[3]); + mul_v3db_db(fbc2, 1.0f / fbc2[3]); + } + + int use = (fabs(LFBC[0] - RFBC[0]) > fabs(LFBC[1] - RFBC[1])) ? 0 : 1; + double at1 = ratiod(LFBC[use], RFBC[use], fbc1[use]); + double at2 = ratiod(LFBC[use], RFBC[use], fbc2[use]); + if (at1 > at2) { + swap_v3_v3_db(gpos1, gpos2); + swap_v4_v4_db(fbc1, fbc2); + SWAP(double, at1, at2); + } + + /* If not effectively projecting anything. */ + if (at1 > (1.0f - FLT_EPSILON) || at2 < FLT_EPSILON) { + return false; + } + + /* Trim to edge's end points. */ + + double t_fbc1[4], t_fbc2[4], t_gpos1[3], t_gpos2[3]; + bool trimmed1 = false, trimmed2 = false; + if (at1 < 0 || at2 > 1) { + double rat1 = (-at1) / (at2 - at1); + double rat2 = (1.0f - at1) / (at2 - at1); + double gat1 = is_persp ? fbc1[3] * rat1 / (rat1 * fbc1[3] + (1 - rat1) * fbc2[3]) : rat1; + double gat2 = is_persp ? fbc1[3] * rat2 / (rat2 * fbc1[3] + (1 - rat2) * fbc2[3]) : rat2; + if (at1 < 0) { + interp_v3_v3v3_db(t_gpos1, gpos1, gpos2, gat1); + interp_v3_v3v3_db(t_fbc1, fbc1, fbc2, rat1); + t_fbc1[3] = interpd(fbc2[3], fbc1[3], gat1); + at1 = 0, trimmed1 = true; + } + if (at2 > 1) { + interp_v3_v3v3_db(t_gpos2, gpos1, gpos2, gat2); + interp_v3_v3v3_db(t_fbc2, fbc1, fbc2, rat2); + t_fbc2[3] = interpd(fbc2[3], fbc1[3], gat2); + at2 = 1, trimmed2 = true; + } + } + if (trimmed1) { + copy_v4_v4_db(fbc1, t_fbc1); + copy_v3_v3_db(gpos1, t_gpos1); + } + if (trimmed2) { + copy_v4_v4_db(fbc2, t_fbc2); + copy_v3_v3_db(gpos2, t_gpos2); + } + + *r_at_1 = at1; + *r_at_2 = at2; + copy_v4_v4_db(r_fb_co_1, fbc1); + copy_v4_v4_db(r_fb_co_2, fbc2); + copy_v3_v3_db(r_gloc_1, gpos1); + copy_v3_v3_db(r_gloc_2, gpos2); + + double camera_vector[3]; + + if (is_persp) { + sub_v3_v3v3_db(camera_vector, ld->conf.camera_pos, tri->v[0]->gloc); + } + else { + copy_v3_v3_db(camera_vector, ld->conf.view_vector); + } + + double dot_f = dot_v3v3_db(camera_vector, tri->gn); + *r_facing_light = (dot_f < 0); + + return true; +} + +/* The one step all to cast all visible edges in light camera back to other geometries behind them, + * the result of this step can then be generated as actual LineartEdge's for occlusion test in view + * camera. */ +static void lineart_shadow_cast(LineartData *ld, bool transform_edge_cuts, bool do_light_contour) +{ + + lineart_shadow_create_shadow_edge_array(ld, transform_edge_cuts, do_light_contour); + + /* Keep it single threaded for now because the loop will write "done" pointers to triangles. */ + for (int edge_i = 0; edge_i < ld->shadow_edges_count; edge_i++) { + LineartShadowEdge *sedge = &ld->shadow_edges[edge_i]; + + LineartTriangleThread *tri; + double at_1, at_2; + double fb_co_1[4], fb_co_2[4]; + double global_1[3], global_2[3]; + bool facing_light; + + LRT_EDGE_BA_MARCHING_BEGIN(sedge->fbc1, sedge->fbc2) + { + for (int i = 0; i < nba->triangle_count; i++) { + tri = (LineartTriangleThread *)nba->linked_triangles[i]; + if (tri->testing_e[0] == (LineartEdge *)sedge || tri->base.mat_occlusion == 0 || + lineart_edge_from_triangle( + (LineartTriangle *)tri, sedge->e_ref, ld->conf.allow_overlapping_edges)) { + continue; + } + tri->testing_e[0] = (LineartEdge *)sedge; + + if (lineart_shadow_cast_onto_triangle(ld, + (LineartTriangle *)tri, + sedge, + &at_1, + &at_2, + fb_co_1, + fb_co_2, + global_1, + global_2, + &facing_light)) { + lineart_shadow_edge_cut(ld, + sedge, + at_1, + at_2, + global_1, + global_2, + fb_co_1, + fb_co_2, + facing_light, + tri->base.target_reference); + } + } + LRT_EDGE_BA_MARCHING_NEXT(sedge->fbc1, sedge->fbc2); + } + LRT_EDGE_BA_MARCHING_END; + } +} + +/* For each [segment] on a shadow shadow_edge, 1 LineartEdge will be generated with a cast shadow + * edge flag (if that segment failed to cast onto anything then it's not generated). The original + * shadow shadow_edge is optionally generated as a light contour. */ +static bool lineart_shadow_cast_generate_edges(LineartData *ld, + bool do_original_edges, + LineartElementLinkNode **r_veln, + LineartElementLinkNode **r_eeln) +{ + int tot_edges = 0; + int tot_orig_edges = 0; + for (int i = 0; i < ld->shadow_edges_count; i++) { + LineartShadowEdge *sedge = &ld->shadow_edges[i]; + LISTBASE_FOREACH (LineartShadowSegment *, sseg, &sedge->shadow_segments) { + if (!(sseg->flag & LRT_SHADOW_CASTED)) { + continue; + } + if (!sseg->next) { + break; + } + tot_edges++; + } + tot_orig_edges++; + } + + int edge_alloc = tot_edges + (do_original_edges ? tot_orig_edges : 0); + + if (G.debug_value == 4000) { + printf("Line art shadow segments total: %d\n", tot_edges); + } + + if (!edge_alloc) { + return false; + } + LineartElementLinkNode *veln = lineart_mem_acquire(ld->shadow_data_pool, + sizeof(LineartElementLinkNode)); + LineartElementLinkNode *eeln = lineart_mem_acquire(ld->shadow_data_pool, + sizeof(LineartElementLinkNode)); + veln->pointer = lineart_mem_acquire(ld->shadow_data_pool, sizeof(LineartVert) * edge_alloc * 2); + eeln->pointer = lineart_mem_acquire(ld->shadow_data_pool, sizeof(LineartEdge) * edge_alloc); + LineartEdgeSegment *es = lineart_mem_acquire(ld->shadow_data_pool, + sizeof(LineartEdgeSegment) * edge_alloc); + *r_veln = veln; + *r_eeln = eeln; + + veln->element_count = edge_alloc * 2; + eeln->element_count = edge_alloc; + + LineartVert *vlist = veln->pointer; + LineartEdge *elist = eeln->pointer; + + int ei = 0; + for (int i = 0; i < ld->shadow_edges_count; i++) { + LineartShadowEdge *sedge = &ld->shadow_edges[i]; + LISTBASE_FOREACH (LineartShadowSegment *, sseg, &sedge->shadow_segments) { + if (!(sseg->flag & LRT_SHADOW_CASTED)) { + continue; + } + if (!sseg->next) { + break; + } + LineartEdge *e = &elist[ei]; + BLI_addtail(&e->segments, &es[ei]); + LineartVert *v1 = &vlist[ei * 2], *v2 = &vlist[ei * 2 + 1]; + copy_v3_v3_db(v1->gloc, sseg->g2); + copy_v3_v3_db(v2->gloc, ((LineartShadowSegment *)sseg->next)->g1); + e->v1 = v1; + e->v2 = v2; + e->t1 = (LineartTriangle *)sedge->e_ref; /* See LineartEdge::t1 for usage. */ + e->t2 = (LineartTriangle *)(sedge->e_ref_light_contour ? sedge->e_ref_light_contour : + sedge->e_ref); + e->target_reference = sseg->target_reference; + e->edge_identifier = sedge->e_ref->edge_identifier; + e->flags = (LRT_EDGE_FLAG_PROJECTED_SHADOW | + ((sseg->flag & LRT_SHADOW_FACING_LIGHT) ? LRT_EDGE_FLAG_SHADOW_FACING_LIGHT : + 0)); + ei++; + } + if (do_original_edges) { + /* Occlusion-corrected light contour. */ + LineartEdge *e = &elist[ei]; + BLI_addtail(&e->segments, &es[ei]); + LineartVert *v1 = &vlist[ei * 2], *v2 = &vlist[ei * 2 + 1]; + v1->index = sedge->e_ref->v1->index; + v2->index = sedge->e_ref->v2->index; + copy_v3_v3_db(v1->gloc, sedge->g1); + copy_v3_v3_db(v2->gloc, sedge->g2); + uint64_t ref_1 = sedge->e_ref->t1 ? sedge->e_ref->t1->target_reference : 0; + uint64_t ref_2 = sedge->e_ref->t2 ? sedge->e_ref->t2->target_reference : 0; + e->edge_identifier = sedge->e_ref->edge_identifier; + e->target_reference = ((ref_1 << 32) | ref_2); + e->v1 = v1; + e->v2 = v2; + e->t1 = e->t2 = (LineartTriangle *)sedge->e_ref; + e->flags = LRT_EDGE_FLAG_LIGHT_CONTOUR; + if (lineart_contour_viewed_from_dark_side(ld, sedge->e_ref)) { + lineart_edge_cut(ld, e, 0.0f, 1.0f, 0, 0, LRT_SHADOW_MASK_SHADED); + } + ei++; + } + } + return true; +} + +static void lineart_shadow_register_silhouette(LineartData *ld) +{ + /* Keeping it single threaded for now because a simple parallel_for could end up getting the same + * #sedge->e_ref in different threads. */ + for (int i = 0; i < ld->shadow_edges_count; i++) { + LineartShadowEdge *sedge = &ld->shadow_edges[i]; + + LineartEdge *e = sedge->e_ref; + LineartEdgeSegment *es = sedge->es_ref; + double es_start = es->ratio, es_end = es->next ? es->next->ratio : 1.0f; + LISTBASE_FOREACH (LineartShadowSegment *, sseg, &sedge->shadow_segments) { + if (!(sseg->flag & LRT_SHADOW_CASTED)) { + continue; + } + if (!sseg->next) { + break; + } + + uint32_t silhouette_flags = (sseg->target_reference & LRT_OBINDEX_HIGHER) | + LRT_SHADOW_SILHOUETTE_ERASED_GROUP; + + double at_start = interpd(es_end, es_start, sseg->ratio); + double at_end = interpd(es_end, es_start, sseg->next->ratio); + lineart_edge_cut(ld, e, at_start, at_end, 0, 0, silhouette_flags); + } + } +} + +/* To achieve enclosed shape effect, we need to: + * 1) Show shaded segments against lit background. + * 2) Erase lit segments against lit background. */ +static void lineart_shadow_register_enclosed_shapes(LineartData *ld, LineartData *shadow_ld) +{ + LineartEdge *e; + LineartEdgeSegment *es; + for (int i = 0; i < shadow_ld->pending_edges.next; i++) { + e = shadow_ld->pending_edges.array[i]; + + /* Only care about shade-on-light and light-on-light situations, hence we only need + * non-occludded segments in shadow buffer. */ + if (e->min_occ > 0) { + continue; + } + for (es = e->segments.first; es; es = es->next) { + if (es->occlusion > 0) { + continue; + } + double next_at = es->next ? ((LineartEdgeSegment *)es->next)->ratio : 1.0f; + LineartEdge *orig_e = (LineartEdge *)e->t2; + + /* Shadow view space to global. */ + double ga1 = e->v1->fbcoord[3] * es->ratio / + (es->ratio * e->v1->fbcoord[3] + (1 - es->ratio) * e->v2->fbcoord[3]); + double ga2 = e->v1->fbcoord[3] * next_at / + (next_at * e->v1->fbcoord[3] + (1 - next_at) * e->v2->fbcoord[3]); + double g1[3], g2[3], g1v[4], g2v[4]; + interp_v3_v3v3_db(g1, e->v1->gloc, e->v2->gloc, ga1); + interp_v3_v3v3_db(g2, e->v1->gloc, e->v2->gloc, ga2); + mul_v4_m4v3_db(g1v, ld->conf.view_projection, g1); + mul_v4_m4v3_db(g2v, ld->conf.view_projection, g2); + + if (ld->conf.cam_is_persp) { + mul_v3db_db(g1v, (1 / g1v[3])); + mul_v3db_db(g2v, (1 / g2v[3])); + } + + g1v[0] -= ld->conf.shift_x * 2; + g1v[1] -= ld->conf.shift_y * 2; + g2v[0] -= ld->conf.shift_x * 2; + g2v[1] -= ld->conf.shift_y * 2; + +#define GET_RATIO(n) \ + (fabs(orig_e->v2->fbcoord[0] - orig_e->v1->fbcoord[0]) > \ + fabs(orig_e->v2->fbcoord[1] - orig_e->v1->fbcoord[1])) ? \ + ((g##n##v[0] - orig_e->v1->fbcoord[0]) / \ + (orig_e->v2->fbcoord[0] - orig_e->v1->fbcoord[0])) : \ + ((g##n##v[1] - orig_e->v1->fbcoord[1]) / (orig_e->v2->fbcoord[1] - orig_e->v1->fbcoord[1])) + double la1, la2; + la1 = GET_RATIO(1); + la2 = GET_RATIO(2); +#undef GET_RATIO + + lineart_edge_cut(ld, orig_e, la1, la2, 0, 0, LRT_SHADOW_MASK_ENCLOSED_SHAPE); + } + } +} + +/* This call would internally duplicate #original_ld, override necessary configureations for shadow + * computations. It will return: + * + * 1) Generated shadow edges in format of `LineartElementLinkNode` which can be directly loaded + * into later main view camera occlusion stage. + * 2) Shadow render buffer if 3rd stage reprojection is need for silhouette/lit/shaded region + * selection. Otherwise the shadow render buffer is deleted before this function returns. + */ +bool lineart_main_try_generate_shadow(Depsgraph *depsgraph, + Scene *scene, + LineartData *original_ld, + LineartGpencilModifierData *lmd, + LineartStaticMemPool *shadow_data_pool, + LineartElementLinkNode **r_veln, + LineartElementLinkNode **r_eeln, + ListBase *r_calculated_edges_eln_list, + LineartData **r_shadow_ld_if_reproject) +{ + if ((!original_ld->conf.use_shadow && !original_ld->conf.use_light_contour && + !original_ld->conf.shadow_selection) || + (!lmd->light_contour_object)) { + return false; + } + + double t_start; + if (G.debug_value == 4000) { + t_start = PIL_check_seconds_timer(); + } + + bool is_persp = true; + + if (lmd->light_contour_object->type == OB_LAMP) { + Light *la = (Light *)lmd->light_contour_object->data; + if (la->type == LA_SUN) { + is_persp = false; + } + } + + LineartData *ld = MEM_callocN(sizeof(LineartData), "LineArt render buffer copied"); + memcpy(ld, original_ld, sizeof(LineartData)); + + BLI_spin_init(&ld->lock_task); + BLI_spin_init(&ld->lock_cuts); + BLI_spin_init(&ld->render_data_pool.lock_mem); + + ld->conf.do_shadow_cast = true; + ld->shadow_data_pool = shadow_data_pool; + + /* See LineartData::edge_data_pool for explaination. */ + if (ld->conf.shadow_selection) { + ld->edge_data_pool = shadow_data_pool; + } + else { + ld->edge_data_pool = &ld->render_data_pool; + } + + copy_v3_v3_db(ld->conf.camera_pos_secondary, ld->conf.camera_pos); + copy_m4_m4(ld->conf.cam_obmat_secondary, ld->conf.cam_obmat); + + copy_m4_m4(ld->conf.cam_obmat, lmd->light_contour_object->obmat); + copy_v3db_v3fl(ld->conf.camera_pos, ld->conf.cam_obmat[3]); + ld->conf.cam_is_persp_secondary = ld->conf.cam_is_persp; + ld->conf.cam_is_persp = is_persp; + ld->conf.near_clip = is_persp ? lmd->shadow_camera_near : -lmd->shadow_camera_far; + ld->conf.far_clip = lmd->shadow_camera_far; + ld->w = lmd->shadow_camera_size; + ld->h = lmd->shadow_camera_size; + /* Need to prevent wrong camera configuration so that shadow computation won't stall. */ + if (!ld->w || !ld->h) { + ld->w = ld->h = 200; + } + if (!ld->conf.near_clip || !ld->conf.far_clip) { + ld->conf.near_clip = 0.1f; + ld->conf.far_clip = 200.0f; + } + ld->qtree.recursive_level = is_persp ? LRT_TILE_RECURSIVE_PERSPECTIVE : LRT_TILE_RECURSIVE_ORTHO; + + /* Contour and loose edge from light viewing direction will be casted as shadow, so only + * force them on. If we need lit/shaded information for other line types, they are then + * enabled as-is so that cutting positions can also be calculated through shadow projection. + */ + if (!ld->conf.shadow_selection) { + ld->conf.use_crease = ld->conf.use_material = ld->conf.use_edge_marks = + ld->conf.use_intersections = ld->conf.use_light_contour = false; + } + else { + ld->conf.use_contour_secondary = true; + ld->conf.allow_duplicated_types = true; + } + ld->conf.use_loose = true; + ld->conf.use_contour = true; + + ld->conf.max_occlusion_level = 0; /* No point getting see-through projections there. */ + ld->conf.use_back_face_culling = false; + + /* Override matrices to light "camera". */ + double proj[4][4], view[4][4], result[4][4]; + float inv[4][4]; + if (is_persp) { + lineart_matrix_perspective_44d(proj, DEG2RAD(160), 1, ld->conf.near_clip, ld->conf.far_clip); + } + else { + lineart_matrix_ortho_44d( + proj, -ld->w, ld->w, -ld->h, ld->h, ld->conf.near_clip, ld->conf.far_clip); + } + invert_m4_m4(inv, ld->conf.cam_obmat); + mul_m4db_m4db_m4fl_uniq(result, proj, inv); + copy_m4_m4_db(proj, result); + copy_m4_m4_db(ld->conf.view_projection, proj); + unit_m4_db(view); + copy_m4_m4_db(ld->conf.view, view); + + lineart_main_get_view_vector(ld); + + lineart_main_load_geometries( + depsgraph, scene, NULL, ld, lmd->flags & LRT_ALLOW_DUPLI_OBJECTS, true, NULL); + + if (!ld->geom.vertex_buffer_pointers.first) { + /* No geometry loaded, return early. */ + lineart_destroy_render_data_keep_init(ld); + MEM_freeN(ld); + return false; + } + + /* The exact same process as in MOD_lineart_compute_feature_lines() until occlusion finishes. + */ + + lineart_main_bounding_area_make_initial(ld); + lineart_main_cull_triangles(ld, false); + lineart_main_cull_triangles(ld, true); + lineart_main_free_adjacent_data(ld); + lineart_main_perspective_division(ld); + lineart_main_discard_out_of_frame_edges(ld); + lineart_main_add_triangles(ld); + lineart_main_bounding_areas_connect_post(ld); + lineart_main_link_lines(ld); + lineart_main_occlusion_begin(ld); + + /* Do shadow cast stuff then get generated vert/edge data. */ + lineart_shadow_cast(ld, true, false); + bool any_generated = lineart_shadow_cast_generate_edges(ld, true, r_veln, r_eeln); + + if (ld->conf.shadow_selection) { + memcpy(r_calculated_edges_eln_list, &ld->geom.line_buffer_pointers, sizeof(ListBase)); + } + + if (ld->conf.shadow_enclose_shapes) { + /* Need loaded data for reprojecting the 3rd time to get shape boundary against lit/shaded + * region. */ + (*r_shadow_ld_if_reproject) = ld; + } + else { + lineart_destroy_render_data_keep_init(ld); + MEM_freeN(ld); + } + + if (G.debug_value == 4000) { + double t_elapsed = PIL_check_seconds_timer() - t_start; + printf("Line art shadow stage 1 time: %f\n", t_elapsed); + } + + return any_generated; +} + +typedef struct LineartShadowFinalizeData { + LineartData *ld; + LineartVert *v; + LineartEdge *e; +} LineartShadowFinalizeData; + +static void lineart_shadow_transform_task(void *__restrict userdata, + const int element_index, + const TaskParallelTLS *__restrict UNUSED(tls)) +{ + LineartShadowFinalizeData *data = (LineartShadowFinalizeData *)userdata; + LineartData *ld = data->ld; + LineartVert *v = &data->v[element_index]; + mul_v4_m4v3_db(v->fbcoord, ld->conf.view_projection, v->gloc); +} + +static void lineart_shadow_finalize_shadow_edges_task( + void *__restrict userdata, const int i, const TaskParallelTLS *__restrict UNUSED(tls)) +{ + LineartShadowFinalizeData *data = (LineartShadowFinalizeData *)userdata; + LineartData *ld = data->ld; + LineartEdge *e = data->e; + + if (e[i].flags & LRT_EDGE_FLAG_LIGHT_CONTOUR) { + LineartElementLinkNode *eln = lineart_find_matching_eln( + &ld->geom.vertex_buffer_pointers, e[i].edge_identifier & LRT_OBINDEX_HIGHER); + if (eln) { + int v1i = (((e[i].edge_identifier) >> 32) & LRT_OBINDEX_LOWER); + int v2i = (e[i].edge_identifier & LRT_OBINDEX_LOWER); + LineartVert *v = (LineartVert *)eln->pointer; + /* If the global position is close enough, use the original vertex to prevent flickering + * caused by very slim boundary condition in point_triangle_relation().*/ + if (LRT_CLOSE_LOOSER_v3(e[i].v1->gloc, v[v1i].gloc)) { + e[i].v1 = &v[v1i]; + } + if (LRT_CLOSE_LOOSER_v3(e[i].v2->gloc, v[v2i].gloc)) { + e[i].v2 = &v[v2i]; + } + } + } +} + +/* Shadow segments needs to be transformed to view-camera space, just like any other objects. + */ +void lineart_main_transform_and_add_shadow(LineartData *ld, + LineartElementLinkNode *veln, + LineartElementLinkNode *eeln) +{ + + TaskParallelSettings transform_settings; + BLI_parallel_range_settings_defaults(&transform_settings); + /* Set the minimum amount of edges a thread has to process. */ + transform_settings.min_iter_per_thread = 8192; + + LineartShadowFinalizeData data = {0}; + data.ld = ld; + data.v = (LineartVert *)veln->pointer; + data.e = (LineartEdge *)eeln->pointer; + + BLI_task_parallel_range( + 0, veln->element_count, &data, lineart_shadow_transform_task, &transform_settings); + BLI_task_parallel_range(0, + eeln->element_count, + &data, + lineart_shadow_finalize_shadow_edges_task, + &transform_settings); + for (int i = 0; i < eeln->element_count; i++) { + lineart_add_edge_to_array(&ld->pending_edges, &data.e[i]); + } + + BLI_addtail(&ld->geom.vertex_buffer_pointers, veln); + BLI_addtail(&ld->geom.line_buffer_pointers, eeln); +} + +/* Does the 3rd stage reprojection, will not re-load objects because #shadow_ld is not deleted. + * Only reprojects view camera edges and check visibility in light camera, then we can determine + * whether an edge landed on a lit or shaded area. */ +void lineart_main_make_enclosed_shapes(LineartData *ld, LineartData *shadow_ld) +{ + double t_start; + if (G.debug_value == 4000) { + t_start = PIL_check_seconds_timer(); + } + + if (shadow_ld || ld->conf.shadow_use_silhouette) { + lineart_shadow_cast(ld, false, shadow_ld ? true : false); + if (ld->conf.shadow_use_silhouette) { + lineart_shadow_register_silhouette(ld); + } + } + + if (G.debug_value == 4000) { + double t_elapsed = PIL_check_seconds_timer() - t_start; + printf("Line art shadow stage 2 cast and silhouette time: %f\n", t_elapsed); + } + + if (!shadow_ld) { + return; + } + + ld->shadow_data_pool = &ld->render_data_pool; + + if (shadow_ld->pending_edges.array) { + MEM_freeN(shadow_ld->pending_edges.array); + shadow_ld->pending_edges.array = NULL; + shadow_ld->pending_edges.next = shadow_ld->pending_edges.max = 0; + } + + LineartElementLinkNode *shadow_veln, *shadow_eeln; + + bool any_generated = lineart_shadow_cast_generate_edges(ld, false, &shadow_veln, &shadow_eeln); + + if (!any_generated) { + return; + } + + LineartVert *v = shadow_veln->pointer; + for (int i = 0; i < shadow_veln->element_count; i++) { + mul_v4_m4v3_db(v[i].fbcoord, shadow_ld->conf.view_projection, v[i].gloc); + if (shadow_ld->conf.cam_is_persp) { + mul_v3db_db(v[i].fbcoord, (1 / v[i].fbcoord[3])); + } + } + + lineart_finalize_object_edge_array_reserve(&shadow_ld->pending_edges, + shadow_eeln->element_count); + + LineartEdge *se = shadow_eeln->pointer; + for (int i = 0; i < shadow_eeln->element_count; i++) { + lineart_add_edge_to_array(&shadow_ld->pending_edges, &se[i]); + } + + shadow_ld->scheduled_count = 0; + + lineart_main_clear_linked_edges(shadow_ld); + lineart_main_link_lines(shadow_ld); + lineart_main_occlusion_begin(shadow_ld); + + lineart_shadow_register_enclosed_shapes(ld, shadow_ld); + + if (G.debug_value == 4000) { + double t_elapsed = PIL_check_seconds_timer() - t_start; + printf("Line art shadow stage 2 total time: %f\n", t_elapsed); + } +} -- cgit v1.2.3