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

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/gpencil_modifiers/intern/lineart/lineart_shadow.c')
-rw-r--r--source/blender/gpencil_modifiers/intern/lineart/lineart_shadow.c1419
1 files changed, 1419 insertions, 0 deletions
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..257184bae1e
--- /dev/null
+++ b/source/blender/gpencil_modifiers/intern/lineart/lineart_shadow.c
@@ -0,0 +1,1419 @@
+/* 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_ILLUMINATED;
+
+ if (lineart_contour_viewed_from_dark_side(ld, e) &&
+ shadow_bits == LRT_SHADOW_MASK_ILLUMINATED) {
+ 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 frame-buffer coordinates, only z,w are different.
+ * We will get the closest z/w as well as the corresponding global coordinates.
+ *
+ * \code{.unparsed}
+ * (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-` |
+ * \endcode
+ *
+ * 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 contour, 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 certainly 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 vice-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 bound-box 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-occluded 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 configurations 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 explanation. */
+ 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 cast 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 re-projecting 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 re-projects 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);
+ }
+}