From 3eb2bc2c3fd1170b0bfe8f96db3a285aff9759d2 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Sun, 13 Nov 2022 14:16:15 -0600 Subject: Cleanup: Move lineart_cpu.c to C++ To enable further mesh data structure refactoring-- access to loose edges in particular. --- source/blender/gpencil_modifiers/CMakeLists.txt | 2 +- .../gpencil_modifiers/MOD_gpencil_lineart.h | 8 + .../gpencil_modifiers/intern/lineart/MOD_lineart.h | 9 + .../gpencil_modifiers/intern/lineart/lineart_cpu.c | 5444 ------------------- .../intern/lineart/lineart_cpu.cc | 5458 ++++++++++++++++++++ .../intern/lineart/lineart_intern.h | 10 +- source/blender/makesdna/DNA_object_types.h | 3 + 7 files changed, 5484 insertions(+), 5450 deletions(-) delete mode 100644 source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c create mode 100644 source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.cc diff --git a/source/blender/gpencil_modifiers/CMakeLists.txt b/source/blender/gpencil_modifiers/CMakeLists.txt index 8dbac602c06..8c85d977263 100644 --- a/source/blender/gpencil_modifiers/CMakeLists.txt +++ b/source/blender/gpencil_modifiers/CMakeLists.txt @@ -67,7 +67,7 @@ set(SRC # Lineart code intern/lineart/lineart_chain.c intern/lineart/lineart_cpp_bridge.cc - intern/lineart/lineart_cpu.c + intern/lineart/lineart_cpu.cc intern/lineart/lineart_ops.c intern/lineart/lineart_shadow.c intern/lineart/lineart_util.c diff --git a/source/blender/gpencil_modifiers/MOD_gpencil_lineart.h b/source/blender/gpencil_modifiers/MOD_gpencil_lineart.h index 713f070de77..0c284f8e540 100644 --- a/source/blender/gpencil_modifiers/MOD_gpencil_lineart.h +++ b/source/blender/gpencil_modifiers/MOD_gpencil_lineart.h @@ -10,6 +10,10 @@ /* Operator types should be in exposed header. */ +#ifdef __cplusplus +extern "C" { +#endif + void OBJECT_OT_lineart_bake_strokes(struct wmOperatorType *ot); void OBJECT_OT_lineart_bake_strokes_all(struct wmOperatorType *ot); void OBJECT_OT_lineart_clear(struct wmOperatorType *ot); @@ -20,3 +24,7 @@ void WM_operatortypes_lineart(void); struct LineartCache; void MOD_lineart_clear_cache(struct LineartCache **lc); + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h b/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h index 22037d10a71..97a3a782131 100644 --- a/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h +++ b/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h @@ -14,6 +14,10 @@ #include +#ifdef __cplusplus +extern "C" { +#endif + typedef struct LineartStaticMemPoolNode { Link item; size_t size; @@ -74,6 +78,7 @@ typedef enum eLineArtElementNodeFlag { LRT_ELEMENT_NO_INTERSECTION = (1 << 2), LRT_ELEMENT_INTERSECTION_DATA = (1 << 3), } eLineArtElementNodeFlag; +ENUM_OPERATORS(eLineArtElementNodeFlag, LRT_ELEMENT_INTERSECTION_DATA); typedef struct LineartElementLinkNode { struct LineartElementLinkNode *next, *prev; @@ -929,3 +934,7 @@ void MOD_lineart_gpencil_generate(LineartCache *cache, float MOD_lineart_chain_compute_length(LineartEdgeChain *ec); void ED_operatortypes_lineart(void); + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c b/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c deleted file mode 100644 index 4b5c40c0e38..00000000000 --- a/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c +++ /dev/null @@ -1,5444 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2019 Blender Foundation. All rights reserved. */ - -/* \file - * \ingroup editors - */ - -#include "MOD_gpencil_lineart.h" -#include "MOD_lineart.h" - -#include "BLI_edgehash.h" -#include "BLI_linklist.h" -#include "BLI_listbase.h" -#include "BLI_math.h" -#include "BLI_task.h" -#include "BLI_utildefines.h" - -#include "PIL_time.h" - -#include "BKE_camera.h" -#include "BKE_collection.h" -#include "BKE_customdata.h" -#include "BKE_deform.h" -#include "BKE_duplilist.h" -#include "BKE_editmesh.h" -#include "BKE_global.h" -#include "BKE_gpencil.h" -#include "BKE_gpencil_geom.h" -#include "BKE_gpencil_modifier.h" -#include "BKE_lib_id.h" -#include "BKE_material.h" -#include "BKE_mesh.h" -#include "BKE_mesh_mapping.h" -#include "BKE_mesh_runtime.h" -#include "BKE_object.h" -#include "BKE_pointcache.h" -#include "BKE_scene.h" -#include "DEG_depsgraph_query.h" -#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" -#include "DNA_modifier_types.h" -#include "DNA_scene_types.h" -#include "MEM_guardedalloc.h" - -#include "lineart_intern.h" - -typedef struct LineartIsecSingle { - double v1[3], v2[3]; - LineartTriangle *tri1, *tri2; -} LineartIsecSingle; - -typedef struct LineartIsecThread { - int thread_id; - - /* Scheduled work range. */ - LineartElementLinkNode *pending_from; - LineartElementLinkNode *pending_to; - int index_from; - int index_to; - - /* Thread intersection result data. */ - LineartIsecSingle *array; - int current; - int max; - int count_test; - - /* For individual thread reference. */ - LineartData *ld; -} LineartIsecThread; - -typedef struct LineartIsecData { - LineartData *ld; - LineartIsecThread *threads; - int thread_count; -} LineartIsecData; - -static void lineart_bounding_area_link_edge(LineartData *ld, - LineartBoundingArea *root_ba, - LineartEdge *e); - -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(const LineartTriangle *tri, - const LineartEdge *e, - const double *override_camera_loc, - const bool override_cam_is_persp, - const bool allow_overlapping_edges, - 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_bounding_area_link_triangle(LineartData *ld, - LineartBoundingArea *root_ba, - LineartTriangle *tri, - double l_r_u_b[4], - int recursive, - int recursive_level, - bool do_intersection, - struct LineartIsecThread *th); - -static void lineart_free_bounding_area_memory(LineartBoundingArea *ba, bool recursive); - -static void lineart_free_bounding_area_memories(LineartData *ld); - -static LineartCache *lineart_init_cache(void); - -static void lineart_discard_segment(LineartData *ld, LineartEdgeSegment *es) -{ - BLI_spin_lock(&ld->lock_cuts); - - memset(es, 0, sizeof(LineartEdgeSegment)); - - /* Storing the node for potentially reuse the memory for new segment data. - * Line Art data is not freed after all calculations are done. */ - BLI_addtail(&ld->wasted_cuts, es); - - BLI_spin_unlock(&ld->lock_cuts); -} - -static LineartEdgeSegment *lineart_give_segment(LineartData *ld) -{ - BLI_spin_lock(&ld->lock_cuts); - - /* See if there is any already allocated memory we can reuse. */ - if (ld->wasted_cuts.first) { - LineartEdgeSegment *es = (LineartEdgeSegment *)BLI_pophead(&ld->wasted_cuts); - BLI_spin_unlock(&ld->lock_cuts); - memset(es, 0, sizeof(LineartEdgeSegment)); - return es; - } - BLI_spin_unlock(&ld->lock_cuts); - - /* Otherwise allocate some new memory. */ - 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. - */ -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; - LineartEdgeSegment *new_seg1 = 0, *new_seg2 = 0; - int untouched = 0; - - /* If for some reason the occlusion function may give a result that has zero length, or reversed - * in direction, or NAN, 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->segments.first; seg; seg = seg->next) { - if (LRT_DOUBLE_CLOSE_ENOUGH(seg->ratio, start)) { - cut_start_before = seg; - new_seg1 = cut_start_before; - break; - } - if (seg->next == NULL) { - break; - } - i_seg = seg->next; - if (i_seg->ratio > start + 1e-09 && start > seg->ratio) { - cut_start_before = i_seg; - new_seg1 = lineart_give_segment(ld); - break; - } - } - if (!cut_start_before && LRT_DOUBLE_CLOSE_ENOUGH(1, end)) { - untouched = 1; - } - for (seg = cut_start_before; seg; seg = seg->next) { - /* 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->ratio, end)) { - cut_end_before = seg; - new_seg2 = 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_seg2 = cut_end_before; - untouched = 1; - break; - } - /* When an actual cut is needed in the line. */ - if (seg->ratio > end) { - cut_end_before = seg; - new_seg2 = lineart_give_segment(ld); - break; - } - } - - /* When we still can't find any existing cut in the line, we allocate new ones. */ - if (new_seg1 == NULL) { - new_seg1 = lineart_give_segment(ld); - } - if (new_seg2 == NULL) { - if (untouched) { - new_seg2 = new_seg1; - cut_end_before = new_seg2; - } - else { - new_seg2 = lineart_give_segment(ld); - } - } - - if (cut_start_before) { - if (cut_start_before != new_seg1) { - /* Insert cutting points for when a new cut is needed. */ - i_seg = cut_start_before->prev ? cut_start_before->prev : NULL; - 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); - } - /* Otherwise we already found a existing cutting point, no need to insert a new one. */ - } - else { - /* We have yet to reach a existing cutting point even after we searched the whole line, so we - * append the new cut to the end. */ - 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) { - /* The same manipulation as on "cut_start_before". */ - if (cut_end_before != new_seg2) { - i_seg = cut_end_before->prev ? cut_end_before->prev : NULL; - 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); - } - } - else { - i_seg = e->segments.last; - 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; - 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->ratio = start; - if (!untouched) { - new_seg2->ratio = end; - } - else { - /* For the convenience of the loop below. */ - new_seg2 = new_seg2->next; - } - - /* Register 1 level of occlusion for all touched segments. */ - 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_ILLUMINATED || - e->flags & LRT_EDGE_FLAG_LIGHT_CONTOUR) { - 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_ILLUMINATED_SHAPE; - } - } - else { - seg->shadow_mask_bits |= shadow_bits; - } - } - - /* Reduce adjacent cutting points of the same level, which saves memory. */ - int8_t min_occ = 127; - prev_seg = NULL; - for (seg = e->segments.first; seg; seg = next_seg) { - next_seg = seg->next; - - if (prev_seg && prev_seg->occlusion == seg->occlusion && - 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. */ - lineart_discard_segment(ld, seg); - continue; - } - - min_occ = MIN2(min_occ, seg->occlusion); - - prev_seg = seg; - } - e->min_occ = min_occ; -} - -/** - * To see if given line is connected to an adjacent intersection line. - */ -BLI_INLINE bool lineart_occlusion_is_adjacent_intersection(LineartEdge *e, LineartTriangle *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) -{ - ba->max_triangle_count *= 2; - ba->linked_triangles = MEM_recallocN(ba->linked_triangles, - sizeof(LineartTriangle *) * ba->max_triangle_count); -} - -static void lineart_bounding_area_line_add(LineartBoundingArea *ba, LineartEdge *e) -{ - /* In case of too many lines concentrating in one point, do not add anymore, these lines will - * be either shorter than a single pixel, or will still be added into the list of other less - * dense areas. */ - if (ba->line_count >= 65535) { - return; - } - if (ba->line_count >= ba->max_line_count) { - LineartEdge **new_array = MEM_mallocN(sizeof(LineartEdge *) * ba->max_line_count * 2, - "new ba_line_array"); - memcpy(new_array, ba->linked_lines, sizeof(LineartEdge *) * ba->max_line_count); - ba->max_line_count *= 2; - MEM_freeN(ba->linked_lines); - ba->linked_lines = new_array; - } - ba->linked_lines[ba->line_count] = e; - ba->line_count++; -} - -static void lineart_occlusion_single_line(LineartData *ld, LineartEdge *e, int thread_id) -{ - LineartTriangleThread *tri; - double l, r; - 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. */ - if (tri->testing_e[thread_id] == e || (tri->base.flags & LRT_TRIANGLE_INTERSECTION_ONLY) || - /* Ignore this triangle if an intersection line directly comes from it, */ - lineart_occlusion_is_adjacent_intersection(e, (LineartTriangle *)tri) || - /* Or if this triangle isn't effectively occluding anything nor it's providing a - * material flag. */ - ((!tri->base.mat_occlusion) && (!tri->base.material_mask_bits))) { - continue; - } - tri->testing_e[thread_id] = e; - if (lineart_triangle_edge_image_space_occlusion((const LineartTriangle *)tri, - e, - ld->conf.camera_pos, - ld->conf.cam_is_persp, - ld->conf.allow_overlapping_edges, - ld->conf.view_projection, - ld->conf.view_vector, - ld->conf.shift_x, - ld->conf.shift_y, - &l, - &r)) { - 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. */ - return; - } - } - } - 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) -{ - int res = 0; - int starting_index; - - BLI_spin_lock(&ld->lock_task); - - starting_index = ld->scheduled_count; - ld->scheduled_count += LRT_THREAD_EDGE_COUNT; - - BLI_spin_unlock(&ld->lock_task); - - if (starting_index >= ld->pending_edges.next) { - res = 0; - } - else { - rti->pending_edges.array = &ld->pending_edges.array[starting_index]; - int remaining = ld->pending_edges.next - starting_index; - rti->pending_edges.max = MIN2(remaining, LRT_THREAD_EDGE_COUNT); - res = 1; - } - - return res; -} - -static void lineart_occlusion_worker(TaskPool *__restrict UNUSED(pool), LineartRenderTaskInfo *rti) -{ - LineartData *ld = rti->ld; - LineartEdge *eip; - - while (lineart_occlusion_make_task_info(ld, rti)) { - for (int i = 0; i < rti->pending_edges.max; i++) { - eip = rti->pending_edges.array[i]; - lineart_occlusion_single_line(ld, eip, rti->thread_id); - } - } -} - -/** - * All internal functions starting with lineart_main_ is called inside - * #MOD_lineart_compute_feature_lines function. - * This function handles all occlusion calculation. - */ -void lineart_main_occlusion_begin(LineartData *ld) -{ - int thread_count = ld->thread_count; - LineartRenderTaskInfo *rti = MEM_callocN(sizeof(LineartRenderTaskInfo) * thread_count, - "Task Pool"); - int i; - - TaskPool *tp = BLI_task_pool_create(NULL, TASK_PRIORITY_HIGH); - - for (i = 0; i < thread_count; i++) { - rti[i].thread_id = i; - rti[i].ld = ld; - BLI_task_pool_push(tp, (TaskRunFunction)lineart_occlusion_worker, &rti[i], 0, NULL); - } - BLI_task_pool_work_and_wait(tp); - BLI_task_pool_free(tp); - - MEM_freeN(rti); -} - -/** - * Test if v lies with in the triangle formed by v0, v1, and v2. - * Returns false when v is exactly on the edge. - * - * For v to be inside the triangle, it needs to be at the same side of v0->v1, v1->v2, and - * `v2->v0`, where the "side" is determined by checking the sign of `cross(v1-v0, v1-v)` and so on. - */ -static bool lineart_point_inside_triangle(const double v[2], - const double v0[2], - const double v1[2], - const double v2[2]) -{ - double cl, c, cl0; - - cl = (v0[0] - v[0]) * (v1[1] - v[1]) - (v0[1] - v[1]) * (v1[0] - v[0]); - c = cl0 = cl; - - cl = (v1[0] - v[0]) * (v2[1] - v[1]) - (v1[1] - v[1]) * (v2[0] - v[0]); - if (c * cl <= 0) { - return false; - } - - c = cl; - - cl = (v2[0] - v[0]) * (v0[1] - v[1]) - (v2[1] - v[1]) * (v0[0] - v[0]); - if (c * cl <= 0) { - return false; - } - - c = cl; - - if (c * cl0 <= 0) { - return false; - } - - return true; -} - -static int lineart_point_on_line_segment(double v[2], double v0[2], double v1[2]) -{ - /* `c1 != c2` by default. */ - double c1 = 1, c2 = 0; - double l0[2], l1[2]; - - sub_v2_v2v2_db(l0, v, v0); - sub_v2_v2v2_db(l1, v, v1); - - if (v1[0] == v0[0] && v1[1] == v0[1]) { - return 0; - } - - if (!LRT_DOUBLE_CLOSE_ENOUGH(v1[0], v0[0])) { - c1 = ratiod(v0[0], v1[0], v[0]); - } - else { - if (LRT_DOUBLE_CLOSE_ENOUGH(v[0], v1[0])) { - c2 = ratiod(v0[1], v1[1], v[1]); - return (c2 >= -DBL_TRIANGLE_LIM && c2 <= 1 + DBL_TRIANGLE_LIM); - } - return false; - } - - if (!LRT_DOUBLE_CLOSE_ENOUGH(v1[1], v0[1])) { - c2 = ratiod(v0[1], v1[1], v[1]); - } - else { - if (LRT_DOUBLE_CLOSE_ENOUGH(v[1], v1[1])) { - c1 = ratiod(v0[0], v1[0], v[0]); - return (c1 >= -DBL_TRIANGLE_LIM && c1 <= 1 + DBL_TRIANGLE_LIM); - } - return false; - } - - if (LRT_DOUBLE_CLOSE_ENOUGH(c1, c2) && c1 >= 0 && c1 <= 1) { - return 1; - } - - return 0; -} - -enum LineartPointTri { - LRT_OUTSIDE_TRIANGLE = 0, - LRT_ON_TRIANGLE = 1, - LRT_INSIDE_TRIANGLE = 2, -}; - -/** - * Same algorithm as lineart_point_inside_triangle(), but returns differently: - * 0-outside 1-on the edge 2-inside. - */ -static int lineart_point_triangle_relation(double v[2], double v0[2], double v1[2], double v2[2]) -{ - double cl, c; - double r; - if (lineart_point_on_line_segment(v, v0, v1) || lineart_point_on_line_segment(v, v1, v2) || - lineart_point_on_line_segment(v, v2, v0)) { - return LRT_ON_TRIANGLE; - } - - cl = (v0[0] - v[0]) * (v1[1] - v[1]) - (v0[1] - v[1]) * (v1[0] - v[0]); - c = cl; - - cl = (v1[0] - v[0]) * (v2[1] - v[1]) - (v1[1] - v[1]) * (v2[0] - v[0]); - if ((r = c * cl) < 0) { - return LRT_OUTSIDE_TRIANGLE; - } - - c = cl; - - cl = (v2[0] - v[0]) * (v0[1] - v[1]) - (v2[1] - v[1]) * (v0[0] - v[0]); - if ((r = c * cl) < 0) { - return LRT_OUTSIDE_TRIANGLE; - } - - c = cl; - - cl = (v0[0] - v[0]) * (v1[1] - v[1]) - (v0[1] - v[1]) * (v1[0] - v[0]); - if ((r = c * cl) < 0) { - return LRT_OUTSIDE_TRIANGLE; - } - - if (r == 0) { - return LRT_ON_TRIANGLE; - } - - return LRT_INSIDE_TRIANGLE; -} - -/** - * Similar with #lineart_point_inside_triangle, but in 3d. - * Returns false when not co-planar. - */ -static bool lineart_point_inside_triangle3d(double v[3], double v0[3], double v1[3], double v2[3]) -{ - double l[3], r[3]; - double N1[3], N2[3]; - double d; - - sub_v3_v3v3_db(l, v1, v0); - sub_v3_v3v3_db(r, v, v1); - cross_v3_v3v3_db(N1, l, r); - - sub_v3_v3v3_db(l, v2, v1); - sub_v3_v3v3_db(r, v, v2); - cross_v3_v3v3_db(N2, l, r); - - if ((d = dot_v3v3_db(N1, N2)) < 0) { - return false; - } - - sub_v3_v3v3_db(l, v0, v2); - sub_v3_v3v3_db(r, v, v0); - cross_v3_v3v3_db(N1, l, r); - - if ((d = dot_v3v3_db(N1, N2)) < 0) { - return false; - } - - sub_v3_v3v3_db(l, v1, v0); - sub_v3_v3v3_db(r, v, v1); - cross_v3_v3v3_db(N2, l, r); - - if ((d = dot_v3v3_db(N1, N2)) < 0) { - return false; - } - - return true; -} - -/** - * The following `lineart_memory_get_XXX_space` functions are for allocating new memory for some - * modified geometries in the culling stage. - */ -static LineartElementLinkNode *lineart_memory_get_triangle_space(LineartData *ld) -{ - LineartElementLinkNode *eln; - - /* We don't need to allocate a whole bunch of triangles because the amount of clipped triangles - * are relatively small. */ - LineartTriangle *render_triangles = lineart_mem_acquire(&ld->render_data_pool, - 64 * ld->sizeof_triangle); - - eln = lineart_list_append_pointer_pool_sized(&ld->geom.triangle_buffer_pointers, - &ld->render_data_pool, - render_triangles, - sizeof(LineartElementLinkNode)); - eln->element_count = 64; - eln->flags |= LRT_ELEMENT_IS_ADDITIONAL; - - return eln; -} - -static LineartElementLinkNode *lineart_memory_get_vert_space(LineartData *ld) -{ - LineartElementLinkNode *eln; - - LineartVert *render_vertices = lineart_mem_acquire(&ld->render_data_pool, - sizeof(LineartVert) * 64); - - eln = lineart_list_append_pointer_pool_sized(&ld->geom.vertex_buffer_pointers, - &ld->render_data_pool, - render_vertices, - sizeof(LineartElementLinkNode)); - eln->element_count = 64; - eln->flags |= LRT_ELEMENT_IS_ADDITIONAL; - - return eln; -} - -static LineartElementLinkNode *lineart_memory_get_edge_space(LineartData *ld) -{ - LineartElementLinkNode *eln; - - 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->edge_data_pool, - render_edges, - sizeof(LineartElementLinkNode)); - eln->element_count = 64; - eln->crease_threshold = ld->conf.crease_threshold; - eln->flags |= LRT_ELEMENT_IS_ADDITIONAL; - - return eln; -} - -static void lineart_triangle_post(LineartTriangle *tri, LineartTriangle *orig) -{ - /* Just re-assign normal and set cull flag. */ - copy_v3_v3_db(tri->gn, orig->gn); - tri->flags = LRT_CULL_GENERATED; - 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) -{ - uchar intersection_only = (tri->flags & LRT_TRIANGLE_INTERSECTION_ONLY); - tri->flags = flag; - tri->flags |= intersection_only; -} - -static bool lineart_edge_match(LineartTriangle *tri, LineartEdge *e, int v1, int v2) -{ - return ((tri->v[v1] == e->v1 && tri->v[v2] == e->v2) || - (tri->v[v2] == e->v1 && tri->v[v1] == e->v2)); -} - -static void lineart_discard_duplicated_edges(LineartEdge *old_e) -{ - LineartEdge *e = old_e; - while (e->flags & LRT_EDGE_FLAG_NEXT_IS_DUPLICATION) { - e++; - e->flags |= LRT_EDGE_FLAG_CHAIN_PICKED; - } -} - -/** - * Does near-plane cut on 1 triangle only. When cutting with far-plane, the camera vectors gets - * reversed by the caller so don't need to implement one in a different direction. - */ -static void lineart_triangle_cull_single(LineartData *ld, - LineartTriangle *tri, - int in0, - int in1, - int in2, - double cam_pos[3], - double view_dir[3], - bool allow_boundaries, - double m_view_projection[4][4], - Object *ob, - int *r_v_count, - int *r_e_count, - int *r_t_count, - LineartElementLinkNode *v_eln, - LineartElementLinkNode *e_eln, - LineartElementLinkNode *t_eln) -{ - double span_v1[3], span_v2[3], dot_v1, dot_v2; - double a; - int v_count = *r_v_count; - int e_count = *r_e_count; - int t_count = *r_t_count; - uint16_t new_flag = 0; - - LineartEdge *new_e, *e, *old_e; - LineartEdgeSegment *es; - LineartTriangleAdjacent *tri_adj; - - if (tri->flags & (LRT_CULL_USED | LRT_CULL_GENERATED | LRT_CULL_DISCARD)) { - return; - } - - /* See definition of tri->intersecting_verts and the usage in - * lineart_geometry_object_load() for details. */ - tri_adj = (void *)tri->intersecting_verts; - - LineartVert *vt = &((LineartVert *)v_eln->pointer)[v_count]; - LineartTriangle *tri1 = (void *)(((uchar *)t_eln->pointer) + ld->sizeof_triangle * t_count); - LineartTriangle *tri2 = (void *)(((uchar *)t_eln->pointer) + - ld->sizeof_triangle * (t_count + 1)); - - new_e = &((LineartEdge *)e_eln->pointer)[e_count]; - /* Init `edge` to the last `edge` entry. */ - e = new_e; - -#define INCREASE_EDGE \ - new_e = &((LineartEdge *)e_eln->pointer)[e_count]; \ - e_count++; \ - e = new_e; \ - es = lineart_mem_acquire(&ld->render_data_pool, sizeof(LineartEdgeSegment)); \ - BLI_addtail(&e->segments, es); - -#define SELECT_EDGE(e_num, v1_link, v2_link, new_tri) \ - if (tri_adj->e[e_num]) { \ - old_e = tri_adj->e[e_num]; \ - new_flag = old_e->flags; \ - old_e->flags = LRT_EDGE_FLAG_CHAIN_PICKED; \ - lineart_discard_duplicated_edges(old_e); \ - INCREASE_EDGE \ - e->v1 = (v1_link); \ - e->v2 = (v2_link); \ - e->v1->index = (v1_link)->index; \ - e->v2->index = (v1_link)->index; \ - e->flags = new_flag; \ - e->object_ref = ob; \ - e->t1 = ((old_e->t1 == tri) ? (new_tri) : (old_e->t1)); \ - e->t2 = ((old_e->t2 == tri) ? (new_tri) : (old_e->t2)); \ - lineart_add_edge_to_array(&ld->pending_edges, e); \ - } - -#define RELINK_EDGE(e_num, new_tri) \ - if (tri_adj->e[e_num]) { \ - old_e = tri_adj->e[e_num]; \ - old_e->t1 = ((old_e->t1 == tri) ? (new_tri) : (old_e->t1)); \ - old_e->t2 = ((old_e->t2 == tri) ? (new_tri) : (old_e->t2)); \ - } - -#define REMOVE_TRIANGLE_EDGE \ - if (tri_adj->e[0]) { \ - tri_adj->e[0]->flags = LRT_EDGE_FLAG_CHAIN_PICKED; \ - lineart_discard_duplicated_edges(tri_adj->e[0]); \ - } \ - if (tri_adj->e[1]) { \ - tri_adj->e[1]->flags = LRT_EDGE_FLAG_CHAIN_PICKED; \ - lineart_discard_duplicated_edges(tri_adj->e[1]); \ - } \ - if (tri_adj->e[2]) { \ - tri_adj->e[2]->flags = LRT_EDGE_FLAG_CHAIN_PICKED; \ - lineart_discard_duplicated_edges(tri_adj->e[2]); \ - } - - switch (in0 + in1 + in2) { - case 0: /* Triangle is visible. Ignore this triangle. */ - return; - case 3: - /* Triangle completely behind near plane, throw it away - * also remove render lines form being computed. */ - lineart_triangle_set_cull_flag(tri, LRT_CULL_DISCARD); - REMOVE_TRIANGLE_EDGE - return; - case 2: - /* Two points behind near plane, cut those and - * generate 2 new points, 3 lines and 1 triangle. */ - lineart_triangle_set_cull_flag(tri, LRT_CULL_USED); - - /** - * (!in0) means "when point 0 is visible". - * conditions for point 1, 2 are the same idea. - * - * \code{.txt}identify - * 1-----|-------0 - * | | --- - * | |--- - * | ---| - * 2-- | - * (near)---------->(far) - * Will become: - * |N******0 - * |* *** - * |N** - * | - * | - * (near)---------->(far) - * \endcode - */ - if (!in0) { - - /* Cut point for line 2---|-----0. */ - sub_v3_v3v3_db(span_v1, tri->v[0]->gloc, cam_pos); - sub_v3_v3v3_db(span_v2, cam_pos, tri->v[2]->gloc); - dot_v1 = dot_v3v3_db(span_v1, view_dir); - dot_v2 = dot_v3v3_db(span_v2, view_dir); - 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, m_view_projection, vt[0].gloc); - vt[0].index = tri->v[2]->index; - - /* Cut point for line 1---|-----0. */ - sub_v3_v3v3_db(span_v1, tri->v[0]->gloc, cam_pos); - sub_v3_v3v3_db(span_v2, cam_pos, tri->v[1]->gloc); - dot_v1 = dot_v3v3_db(span_v1, view_dir); - dot_v2 = dot_v3v3_db(span_v2, view_dir); - 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, m_view_projection, vt[1].gloc); - vt[1].index = tri->v[1]->index; - - /* New line connecting two new points. */ - INCREASE_EDGE - if (allow_boundaries) { - e->flags = LRT_EDGE_FLAG_CONTOUR; - lineart_add_edge_to_array(&ld->pending_edges, e); - } - /* NOTE: inverting `e->v1/v2` (left/right point) doesn't matter as long as - * `tri->edge` and `tri->v` has the same sequence. and the winding direction - * can be either CW or CCW but needs to be consistent throughout the calculation. */ - e->v1 = &vt[1]; - e->v2 = &vt[0]; - /* Only one adjacent triangle, because the other side is the near plane. */ - /* Use `tl` or `tr` doesn't matter. */ - e->t1 = tri1; - e->object_ref = ob; - - /* New line connecting original point 0 and a new point, only when it's a selected line. */ - SELECT_EDGE(2, tri->v[0], &vt[0], tri1) - /* New line connecting original point 0 and another new point. */ - SELECT_EDGE(0, tri->v[0], &vt[1], tri1) - - /* Re-assign triangle point array to two new points. */ - tri1->v[0] = tri->v[0]; - tri1->v[1] = &vt[1]; - tri1->v[2] = &vt[0]; - - lineart_triangle_post(tri1, tri); - - v_count += 2; - t_count += 1; - } - else if (!in2) { - sub_v3_v3v3_db(span_v1, tri->v[2]->gloc, cam_pos); - sub_v3_v3v3_db(span_v2, cam_pos, tri->v[0]->gloc); - dot_v1 = dot_v3v3_db(span_v1, view_dir); - 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, 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); - sub_v3_v3v3_db(span_v2, cam_pos, tri->v[1]->gloc); - dot_v1 = dot_v3v3_db(span_v1, view_dir); - 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, m_view_projection, vt[1].gloc); - vt[1].index = tri->v[1]->index; - - INCREASE_EDGE - if (allow_boundaries) { - e->flags = LRT_EDGE_FLAG_CONTOUR; - lineart_add_edge_to_array(&ld->pending_edges, e); - } - e->v1 = &vt[0]; - e->v2 = &vt[1]; - e->t1 = tri1; - e->object_ref = ob; - - SELECT_EDGE(2, tri->v[2], &vt[0], tri1) - SELECT_EDGE(1, tri->v[2], &vt[1], tri1) - - tri1->v[0] = &vt[0]; - tri1->v[1] = &vt[1]; - tri1->v[2] = tri->v[2]; - - lineart_triangle_post(tri1, tri); - - v_count += 2; - t_count += 1; - } - else if (!in1) { - sub_v3_v3v3_db(span_v1, tri->v[1]->gloc, cam_pos); - sub_v3_v3v3_db(span_v2, cam_pos, tri->v[2]->gloc); - dot_v1 = dot_v3v3_db(span_v1, view_dir); - 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, 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); - sub_v3_v3v3_db(span_v2, cam_pos, tri->v[0]->gloc); - dot_v1 = dot_v3v3_db(span_v1, view_dir); - 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, m_view_projection, vt[1].gloc); - vt[1].index = tri->v[0]->index; - - INCREASE_EDGE - if (allow_boundaries) { - e->flags = LRT_EDGE_FLAG_CONTOUR; - lineart_add_edge_to_array(&ld->pending_edges, e); - } - e->v1 = &vt[1]; - e->v2 = &vt[0]; - e->t1 = tri1; - e->object_ref = ob; - - SELECT_EDGE(1, tri->v[1], &vt[0], tri1) - SELECT_EDGE(0, tri->v[1], &vt[1], tri1) - - tri1->v[0] = &vt[0]; - tri1->v[1] = tri->v[1]; - tri1->v[2] = &vt[1]; - - lineart_triangle_post(tri1, tri); - - v_count += 2; - t_count += 1; - } - break; - case 1: - /* One point behind near plane, cut those and - * generate 2 new points, 4 lines and 2 triangles. */ - lineart_triangle_set_cull_flag(tri, LRT_CULL_USED); - - /** - * (in0) means "when point 0 is invisible". - * conditions for point 1, 2 are the same idea. - * \code{.txt} - * 0------|----------1 - * -- | | - * ---| | - * |-- | - * | --- | - * | --- | - * | --2 - * (near)---------->(far) - * Will become: - * |N*********1 - * |* *** | - * |* *** | - * |N** | - * | *** | - * | *** | - * | **2 - * (near)---------->(far) - * \endcode - */ - if (in0) { - /* Cut point for line 0---|------1. */ - sub_v3_v3v3_db(span_v1, tri->v[1]->gloc, cam_pos); - sub_v3_v3v3_db(span_v2, cam_pos, tri->v[0]->gloc); - dot_v1 = dot_v3v3_db(span_v1, view_dir); - dot_v2 = dot_v3v3_db(span_v2, view_dir); - 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, m_view_projection, vt[0].gloc); - vt[0].index = tri->v[0]->index; - - /* Cut point for line 0---|------2. */ - sub_v3_v3v3_db(span_v1, tri->v[2]->gloc, cam_pos); - sub_v3_v3v3_db(span_v2, cam_pos, tri->v[0]->gloc); - dot_v1 = dot_v3v3_db(span_v1, view_dir); - dot_v2 = dot_v3v3_db(span_v2, view_dir); - 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, m_view_projection, vt[1].gloc); - vt[1].index = tri->v[0]->index; - - /* New line connects two new points. */ - INCREASE_EDGE - if (allow_boundaries) { - e->flags = LRT_EDGE_FLAG_CONTOUR; - lineart_add_edge_to_array(&ld->pending_edges, e); - } - e->v1 = &vt[1]; - e->v2 = &vt[0]; - e->t1 = tri1; - e->object_ref = ob; - - /* New line connects new point 0 and old point 1, - * this is a border line. */ - - SELECT_EDGE(0, tri->v[1], &vt[0], tri1) - SELECT_EDGE(2, tri->v[2], &vt[1], tri2) - RELINK_EDGE(1, tri2) - - /* We now have one triangle closed. */ - tri1->v[0] = tri->v[1]; - tri1->v[1] = &vt[1]; - tri1->v[2] = &vt[0]; - /* Close the second triangle. */ - tri2->v[0] = &vt[1]; - tri2->v[1] = tri->v[1]; - tri2->v[2] = tri->v[2]; - - lineart_triangle_post(tri1, tri); - lineart_triangle_post(tri2, tri); - - v_count += 2; - t_count += 2; - } - else if (in1) { - - sub_v3_v3v3_db(span_v1, tri->v[1]->gloc, cam_pos); - sub_v3_v3v3_db(span_v2, cam_pos, tri->v[2]->gloc); - dot_v1 = dot_v3v3_db(span_v1, view_dir); - 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, 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); - sub_v3_v3v3_db(span_v2, cam_pos, tri->v[0]->gloc); - dot_v1 = dot_v3v3_db(span_v1, view_dir); - 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, m_view_projection, vt[1].gloc); - vt[1].index = tri->v[1]->index; - - INCREASE_EDGE - if (allow_boundaries) { - e->flags = LRT_EDGE_FLAG_CONTOUR; - lineart_add_edge_to_array(&ld->pending_edges, e); - } - e->v1 = &vt[1]; - e->v2 = &vt[0]; - - e->t1 = tri1; - e->object_ref = ob; - - SELECT_EDGE(1, tri->v[2], &vt[0], tri1) - SELECT_EDGE(0, tri->v[0], &vt[1], tri2) - RELINK_EDGE(2, tri2) - - tri1->v[0] = tri->v[2]; - tri1->v[1] = &vt[1]; - tri1->v[2] = &vt[0]; - - tri2->v[0] = &vt[1]; - tri2->v[1] = tri->v[2]; - tri2->v[2] = tri->v[0]; - - lineart_triangle_post(tri1, tri); - lineart_triangle_post(tri2, tri); - - v_count += 2; - t_count += 2; - } - else if (in2) { - - sub_v3_v3v3_db(span_v1, tri->v[2]->gloc, cam_pos); - sub_v3_v3v3_db(span_v2, cam_pos, tri->v[0]->gloc); - dot_v1 = dot_v3v3_db(span_v1, view_dir); - 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, 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); - sub_v3_v3v3_db(span_v2, cam_pos, tri->v[1]->gloc); - dot_v1 = dot_v3v3_db(span_v1, view_dir); - 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, m_view_projection, vt[1].gloc); - vt[1].index = tri->v[2]->index; - - INCREASE_EDGE - if (allow_boundaries) { - e->flags = LRT_EDGE_FLAG_CONTOUR; - lineart_add_edge_to_array(&ld->pending_edges, e); - } - e->v1 = &vt[1]; - e->v2 = &vt[0]; - - e->t1 = tri1; - e->object_ref = ob; - - SELECT_EDGE(2, tri->v[0], &vt[0], tri1) - SELECT_EDGE(1, tri->v[1], &vt[1], tri2) - RELINK_EDGE(0, tri2) - - tri1->v[0] = tri->v[0]; - tri1->v[1] = &vt[1]; - tri1->v[2] = &vt[0]; - - tri2->v[0] = &vt[1]; - tri2->v[1] = tri->v[0]; - tri2->v[2] = tri->v[1]; - - lineart_triangle_post(tri1, tri); - lineart_triangle_post(tri2, tri); - - v_count += 2; - t_count += 2; - } - break; - } - *r_v_count = v_count; - *r_e_count = e_count; - *r_t_count = t_count; - -#undef INCREASE_EDGE -#undef SELECT_EDGE -#undef RELINK_EDGE -#undef REMOVE_TRIANGLE_EDGE -} - -/** - * This function cuts triangles with near- or far-plane. Setting clip_far = true for cutting with - * far-plane. For triangles that's crossing the plane, it will generate new 1 or 2 triangles with - * new topology that represents the trimmed triangle. (which then became a triangle or a square - * formed by two triangles) - */ -void lineart_main_cull_triangles(LineartData *ld, bool clip_far) -{ - LineartTriangle *tri; - LineartElementLinkNode *v_eln, *t_eln, *e_eln; - double(*m_view_projection)[4] = ld->conf.view_projection; - int i; - int v_count = 0, t_count = 0, e_count = 0; - Object *ob; - bool allow_boundaries = ld->conf.allow_boundaries; - double cam_pos[3]; - double clip_start = ld->conf.near_clip, clip_end = ld->conf.far_clip; - double view_dir[3], clip_advance[3]; - - copy_v3_v3_db(view_dir, ld->conf.view_vector); - copy_v3_v3_db(clip_advance, ld->conf.view_vector); - copy_v3_v3_db(cam_pos, ld->conf.camera_pos); - - if (clip_far) { - /* Move starting point to end plane. */ - mul_v3db_db(clip_advance, -clip_end); - add_v3_v3_db(cam_pos, clip_advance); - - /* "reverse looking". */ - mul_v3db_db(view_dir, -1.0f); - } - else { - /* Clip Near. */ - mul_v3db_db(clip_advance, -clip_start); - add_v3_v3_db(cam_pos, clip_advance); - } - - v_eln = lineart_memory_get_vert_space(ld); - t_eln = lineart_memory_get_triangle_space(ld); - e_eln = lineart_memory_get_edge_space(ld); - - /* Additional memory space for storing generated points and triangles. */ -#define LRT_CULL_ENSURE_MEMORY \ - if (v_count > 60) { \ - v_eln->element_count = v_count; \ - v_eln = lineart_memory_get_vert_space(ld); \ - v_count = 0; \ - } \ - if (t_count > 60) { \ - t_eln->element_count = t_count; \ - t_eln = lineart_memory_get_triangle_space(ld); \ - t_count = 0; \ - } \ - if (e_count > 60) { \ - e_eln->element_count = e_count; \ - e_eln = lineart_memory_get_edge_space(ld); \ - e_count = 0; \ - } - -#define LRT_CULL_DECIDE_INSIDE \ - /* These three represents points that are in the clipping range or not. */ \ - in0 = 0, in1 = 0, in2 = 0; \ - if (clip_far) { \ - /* Point outside far plane. */ \ - if (tri->v[0]->fbcoord[use_w] > clip_end) { \ - in0 = 1; \ - } \ - if (tri->v[1]->fbcoord[use_w] > clip_end) { \ - in1 = 1; \ - } \ - if (tri->v[2]->fbcoord[use_w] > clip_end) { \ - in2 = 1; \ - } \ - } \ - else { \ - /* Point inside near plane. */ \ - if (tri->v[0]->fbcoord[use_w] < clip_start) { \ - in0 = 1; \ - } \ - if (tri->v[1]->fbcoord[use_w] < clip_start) { \ - in1 = 1; \ - } \ - if (tri->v[2]->fbcoord[use_w] < clip_start) { \ - in2 = 1; \ - } \ - } - - int use_w = 3; - int in0 = 0, in1 = 0, in2 = 0; - - if (!ld->conf.cam_is_persp) { - clip_start = -1; - clip_end = 1; - use_w = 2; - } - - /* Then go through all the other triangles. */ - LISTBASE_FOREACH (LineartElementLinkNode *, eln, &ld->geom.triangle_buffer_pointers) { - if (eln->flags & LRT_ELEMENT_IS_ADDITIONAL) { - continue; - } - ob = eln->object_ref; - for (i = 0; i < eln->element_count; i++) { - /* Select the triangle in the array. */ - tri = (void *)(((uchar *)eln->pointer) + ld->sizeof_triangle * i); - - if (tri->flags & LRT_CULL_DISCARD) { - continue; - } - - LRT_CULL_DECIDE_INSIDE - LRT_CULL_ENSURE_MEMORY - lineart_triangle_cull_single(ld, - tri, - in0, - in1, - in2, - cam_pos, - view_dir, - allow_boundaries, - m_view_projection, - ob, - &v_count, - &e_count, - &t_count, - v_eln, - e_eln, - t_eln); - } - t_eln->element_count = t_count; - v_eln->element_count = v_count; - } - -#undef LRT_CULL_ENSURE_MEMORY -#undef LRT_CULL_DECIDE_INSIDE -} - -/** - * Adjacent data is only used during the initial stages of computing. - * So we can free it using this function when it is not needed anymore. - */ -void lineart_main_free_adjacent_data(LineartData *ld) -{ - LinkData *link; - while ((link = BLI_pophead(&ld->geom.triangle_adjacent_pointers)) != NULL) { - MEM_freeN(link->data); - } - LISTBASE_FOREACH (LineartElementLinkNode *, eln, &ld->geom.triangle_buffer_pointers) { - LineartTriangle *tri = eln->pointer; - int i; - for (i = 0; i < eln->element_count; i++) { - /* See definition of tri->intersecting_verts and the usage in - * lineart_geometry_object_load() for detailed. */ - tri->intersecting_verts = NULL; - tri = (LineartTriangle *)(((uchar *)tri) + ld->sizeof_triangle); - } - } -} - -void lineart_main_perspective_division(LineartData *ld) -{ - LineartVert *vt; - int i; - - LISTBASE_FOREACH (LineartElementLinkNode *, eln, &ld->geom.vertex_buffer_pointers) { - vt = eln->pointer; - for (i = 0; i < eln->element_count; i++) { - if (ld->conf.cam_is_persp) { - /* Do not divide Z, we use Z to back transform cut points in later chaining process. */ - vt[i].fbcoord[0] /= vt[i].fbcoord[3]; - vt[i].fbcoord[1] /= vt[i].fbcoord[3]; - /* Re-map z into (0-1) range, because we no longer need NDC (Normalized Device Coordinates) - * at the moment. - * The algorithm currently doesn't need Z for operation, we use W instead. If Z is needed - * in the future, the line below correctly transforms it to view space coordinates. */ - // `vt[i].fbcoord[2] = -2 * vt[i].fbcoord[2] / (far - near) - (far + near) / (far - near); - } - /* Shifting is always needed. */ - vt[i].fbcoord[0] -= ld->conf.shift_x * 2; - vt[i].fbcoord[1] -= ld->conf.shift_y * 2; - } - } -} - -void lineart_main_discard_out_of_frame_edges(LineartData *ld) -{ - LineartEdge *e; - int i; - -#define LRT_VERT_OUT_OF_BOUND(v) \ - (v && (v->fbcoord[0] < -1 || v->fbcoord[0] > 1 || v->fbcoord[1] < -1 || v->fbcoord[1] > 1)) - - LISTBASE_FOREACH (LineartElementLinkNode *, eln, &ld->geom.line_buffer_pointers) { - e = (LineartEdge *)eln->pointer; - for (i = 0; i < eln->element_count; i++) { - if (LRT_VERT_OUT_OF_BOUND(e[i].v1) && LRT_VERT_OUT_OF_BOUND(e[i].v2)) { - e[i].flags = LRT_EDGE_FLAG_CHAIN_PICKED; - } - } - } -} - -typedef struct LineartEdgeNeighbor { - int e; - uint16_t flags; - int v1, v2; -} LineartEdgeNeighbor; - -typedef struct VertData { - const MVert *mvert; - LineartVert *v_arr; - double (*model_view)[4]; - double (*model_view_proj)[4]; -} VertData; - -static void lineart_mvert_transform_task(void *__restrict userdata, - const int i, - const TaskParallelTLS *__restrict UNUSED(tls)) -{ - VertData *vert_task_data = (VertData *)userdata; - const MVert *m_v = &vert_task_data->mvert[i]; - double co[4]; - LineartVert *v = &vert_task_data->v_arr[i]; - copy_v3db_v3fl(co, m_v->co); - mul_v3_m4v3_db(v->gloc, vert_task_data->model_view, co); - mul_v4_m4v3_db(v->fbcoord, vert_task_data->model_view_proj, co); - v->index = i; -} - -static const int LRT_MESH_EDGE_TYPES[] = { - LRT_EDGE_FLAG_EDGE_MARK, - LRT_EDGE_FLAG_CONTOUR, - LRT_EDGE_FLAG_CREASE, - LRT_EDGE_FLAG_MATERIAL, - LRT_EDGE_FLAG_LOOSE, - LRT_EDGE_FLAG_CONTOUR_SECONDARY, -}; - -#define LRT_MESH_EDGE_TYPES_COUNT 6 - -static int lineart_edge_type_duplication_count(int eflag) -{ - int count = 0; - /* See eLineartEdgeFlag for details. */ - for (int i = 0; i < LRT_MESH_EDGE_TYPES_COUNT; i++) { - if (eflag & LRT_MESH_EDGE_TYPES[i]) { - count++; - } - } - return count; -} - -/** - * Because we have a variable size for #LineartTriangle, we need an access helper. - * See #LineartTriangleThread for more info. - */ -static LineartTriangle *lineart_triangle_from_index(LineartData *ld, - LineartTriangle *rt_array, - int index) -{ - int8_t *b = (int8_t *)rt_array; - b += (index * ld->sizeof_triangle); - return (LineartTriangle *)b; -} - -typedef struct EdgeFeatData { - LineartData *ld; - Mesh *me; - Object *ob_eval; /* For evaluated materials. */ - const MLoopTri *mlooptri; - const int *material_indices; - LineartTriangle *tri_array; - LineartVert *v_array; - float crease_threshold; - bool use_auto_smooth; - bool use_freestyle_face; - int freestyle_face_index; - bool use_freestyle_edge; - int freestyle_edge_index; - LineartEdgeNeighbor *edge_nabr; -} EdgeFeatData; - -typedef struct EdgeFeatReduceData { - int feat_edges; -} EdgeFeatReduceData; - -static void feat_data_sum_reduce(const void *__restrict UNUSED(userdata), - void *__restrict chunk_join, - void *__restrict chunk) -{ - EdgeFeatReduceData *feat_chunk_join = (EdgeFeatReduceData *)chunk_join; - EdgeFeatReduceData *feat_chunk = (EdgeFeatReduceData *)chunk; - feat_chunk_join->feat_edges += feat_chunk->feat_edges; -} - -static void lineart_identify_mlooptri_feature_edges(void *__restrict userdata, - const int i, - const TaskParallelTLS *__restrict tls) -{ - EdgeFeatData *e_feat_data = (EdgeFeatData *)userdata; - EdgeFeatReduceData *reduce_data = (EdgeFeatReduceData *)tls->userdata_chunk; - Mesh *me = e_feat_data->me; - const int *material_indices = e_feat_data->material_indices; - Object *ob_eval = e_feat_data->ob_eval; - LineartEdgeNeighbor *edge_nabr = e_feat_data->edge_nabr; - const MLoopTri *mlooptri = e_feat_data->mlooptri; - - uint16_t edge_flag_result = 0; - - /* Because the edge neighbor array contains loop edge pairs, we only need to process the first - * edge in the pair. Otherwise we would add the same edge that the loops represent twice. */ - if (i < edge_nabr[i].e) { - return; - } - - bool face_mark_filtered = false; - bool enable_face_mark = (e_feat_data->use_freestyle_face && - e_feat_data->ld->conf.filter_face_mark); - bool only_contour = false; - if (enable_face_mark) { - FreestyleFace *ff1, *ff2; - int index = e_feat_data->freestyle_face_index; - if (index > -1) { - ff1 = &((FreestyleFace *)me->pdata.layers[index].data)[mlooptri[i / 3].poly]; - } - if (edge_nabr[i].e > -1) { - ff2 = &((FreestyleFace *)me->pdata.layers[index].data)[mlooptri[edge_nabr[i].e / 3].poly]; - } - else { - /* Handle mesh boundary cases: We want mesh boundaries to respect - * `filter_face_mark_boundaries` option the same way as face mark boundaries, and the code - * path is simper when it's assuming both ff1 and ff2 not NULL. */ - ff2 = ff1; - } - if (e_feat_data->ld->conf.filter_face_mark_boundaries ^ - e_feat_data->ld->conf.filter_face_mark_invert) { - if ((ff1->flag & FREESTYLE_FACE_MARK) || (ff2->flag & FREESTYLE_FACE_MARK)) { - face_mark_filtered = true; - } - } - else { - if ((ff1->flag & FREESTYLE_FACE_MARK) && (ff2->flag & FREESTYLE_FACE_MARK) && (ff2 != ff1)) { - face_mark_filtered = true; - } - } - if (e_feat_data->ld->conf.filter_face_mark_invert) { - face_mark_filtered = !face_mark_filtered; - } - if (!face_mark_filtered) { - edge_nabr[i].flags = LRT_EDGE_FLAG_INHIBIT; - if (e_feat_data->ld->conf.filter_face_mark_keep_contour) { - only_contour = true; - } - } - } - - if (enable_face_mark && !face_mark_filtered && !only_contour) { - return; - } - - /* Mesh boundary */ - if (edge_nabr[i].e == -1) { - edge_nabr[i].flags = LRT_EDGE_FLAG_CONTOUR; - reduce_data->feat_edges += 1; - return; - } - - LineartTriangle *tri1, *tri2; - LineartVert *vert; - LineartData *ld = e_feat_data->ld; - - int f1 = i / 3, f2 = edge_nabr[i].e / 3; - - /* The mesh should already be triangulated now, so we can assume each face is a triangle. */ - tri1 = lineart_triangle_from_index(ld, e_feat_data->tri_array, f1); - tri2 = lineart_triangle_from_index(ld, e_feat_data->tri_array, f2); - - vert = &e_feat_data->v_array[edge_nabr[i].v1]; - - double view_vector_persp[3]; - double *view_vector = view_vector_persp; - double dot_v1 = 0, dot_v2 = 0; - double result; - bool material_back_face = ((tri1->flags | tri2->flags) & LRT_TRIANGLE_MAT_BACK_FACE_CULLING); - - if (ld->conf.use_contour || ld->conf.use_back_face_culling || material_back_face) { - if (ld->conf.cam_is_persp) { - sub_v3_v3v3_db(view_vector, ld->conf.camera_pos, vert->gloc); - } - else { - view_vector = ld->conf.view_vector; - } - - 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; - } - - if (ld->conf.use_back_face_culling) { - if (dot_v1 < 0) { - tri1->flags |= LRT_CULL_DISCARD; - } - if (dot_v2 < 0) { - tri2->flags |= LRT_CULL_DISCARD; - } - } - if (material_back_face) { - if (tri1->flags & LRT_TRIANGLE_MAT_BACK_FACE_CULLING && dot_v1 < 0) { - tri1->flags |= LRT_CULL_DISCARD; - } - if (tri2->flags & LRT_TRIANGLE_MAT_BACK_FACE_CULLING && dot_v2 < 0) { - tri2->flags |= LRT_CULL_DISCARD; - } - } - } - - 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) { - const MPoly *polys = BKE_mesh_polys(me); - - if (ld->conf.use_crease) { - bool do_crease = true; - if (!ld->conf.force_crease && !e_feat_data->use_auto_smooth && - (polys[mlooptri[f1].poly].flag & ME_SMOOTH) && - (polys[mlooptri[f2].poly].flag & ME_SMOOTH)) { - do_crease = false; - } - if (do_crease && (dot_v3v3_db(tri1->gn, tri2->gn) < e_feat_data->crease_threshold)) { - edge_flag_result |= LRT_EDGE_FLAG_CREASE; - } - } - - int mat1 = material_indices ? material_indices[mlooptri[f1].poly] : 0; - int mat2 = material_indices ? material_indices[mlooptri[f2].poly] : 0; - - if (mat1 != mat2) { - Material *m1 = BKE_object_material_get_eval(ob_eval, mat1 + 1); - Material *m2 = BKE_object_material_get_eval(ob_eval, 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 */ - if (!edge_flag_result) { /* Other edge types inhibited */ - return; - } - } - - const MEdge *edges = BKE_mesh_edges(me); - - int real_edges[3]; - BKE_mesh_looptri_get_real_edges(me, &mlooptri[i / 3], real_edges); - - if (real_edges[i % 3] >= 0) { - const MEdge *medge = &edges[real_edges[i % 3]]; - - if (ld->conf.use_crease && ld->conf.sharp_as_crease && (medge->flag & ME_SHARP)) { - edge_flag_result |= LRT_EDGE_FLAG_CREASE; - } - - if (ld->conf.use_edge_marks && e_feat_data->use_freestyle_edge) { - FreestyleEdge *fe; - int index = e_feat_data->freestyle_edge_index; - fe = &((FreestyleEdge *)me->edata.layers[index].data)[real_edges[i % 3]]; - if (fe->flag & FREESTYLE_EDGE_MARK) { - edge_flag_result |= LRT_EDGE_FLAG_EDGE_MARK; - } - } - } - - edge_nabr[i].flags = edge_flag_result; - - if (edge_flag_result) { - /* Only allocate for feature edge (instead of all edges) to save memory. - * If allow duplicated edges, one edge gets added multiple times if it has multiple types. - */ - reduce_data->feat_edges += e_feat_data->ld->conf.allow_duplicated_types ? - lineart_edge_type_duplication_count(edge_flag_result) : - 1; - } -} - -typedef struct LooseEdgeData { - int loose_count; - int loose_max; - int *loose_array; - const MEdge *edges; -} LooseEdgeData; - -static void lineart_loose_data_reallocate(LooseEdgeData *loose_data, int count) -{ - int *new_arr = MEM_calloc_arrayN(count, sizeof(int), "loose edge array"); - if (loose_data->loose_array) { - memcpy(new_arr, loose_data->loose_array, sizeof(int) * loose_data->loose_max); - MEM_SAFE_FREE(loose_data->loose_array); - } - loose_data->loose_max = count; - loose_data->loose_array = new_arr; -} - -static void lineart_join_loose_edge_arr(LooseEdgeData *loose_data, LooseEdgeData *to_be_joined) -{ - if (!to_be_joined->loose_array) { - return; - } - int new_count = loose_data->loose_count + to_be_joined->loose_count; - if (new_count >= loose_data->loose_max) { - lineart_loose_data_reallocate(loose_data, new_count); - } - memcpy(&loose_data->loose_array[loose_data->loose_count], - to_be_joined->loose_array, - sizeof(int) * to_be_joined->loose_count); - loose_data->loose_count += to_be_joined->loose_count; - MEM_freeN(to_be_joined->loose_array); - to_be_joined->loose_array = NULL; -} - -static void lineart_add_loose_edge(LooseEdgeData *loose_data, const int i) -{ - if (loose_data->loose_count >= loose_data->loose_max) { - int min_amount = MAX2(100, loose_data->loose_count * 2); - lineart_loose_data_reallocate(loose_data, min_amount); - } - loose_data->loose_array[loose_data->loose_count] = i; - loose_data->loose_count++; -} - -static void lineart_identify_loose_edges(void *__restrict UNUSED(userdata), - const int i, - const TaskParallelTLS *__restrict tls) -{ - LooseEdgeData *loose_data = (LooseEdgeData *)tls->userdata_chunk; - - if (loose_data->edges[i].flag & ME_LOOSEEDGE) { - lineart_add_loose_edge(loose_data, i); - } -} - -static void loose_data_sum_reduce(const void *__restrict UNUSED(userdata), - void *__restrict chunk_join, - void *__restrict chunk) -{ - LooseEdgeData *final = (LooseEdgeData *)chunk_join; - LooseEdgeData *loose_chunk = (LooseEdgeData *)chunk; - lineart_join_loose_edge_arr(final, loose_chunk); -} - -void lineart_add_edge_to_array(LineartPendingEdges *pe, LineartEdge *e) -{ - if (pe->next >= pe->max || !pe->max) { - if (!pe->max) { - pe->max = 1000; - } - - LineartEdge **new_array = MEM_mallocN(sizeof(LineartEdge *) * pe->max * 2, - "LineartPendingEdges array"); - if (LIKELY(pe->array)) { - memcpy(new_array, pe->array, sizeof(LineartEdge *) * pe->max); - MEM_freeN(pe->array); - } - pe->max *= 2; - pe->array = new_array; - } - 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); -} - -/* NOTE: For simplicity, this function doesn't actually do anything if you already have data in - * #pe. */ -void lineart_finalize_object_edge_array_reserve(LineartPendingEdges *pe, int count) -{ - if (pe->max || pe->array || count == 0) { - return; - } - - pe->max = count; - LineartEdge **new_array = MEM_mallocN(sizeof(LineartEdge *) * pe->max, - "LineartPendingEdges array final"); - pe->array = new_array; -} - -static void lineart_finalize_object_edge_array(LineartPendingEdges *pe, LineartObjectInfo *obi) -{ - /* In case of line art "occlusion only" or contour not enabled, it's possible for an object to - * not produce any feature lines. */ - if (!obi->pending_edges.array) { - return; - } - memcpy(&pe->array[pe->next], - obi->pending_edges.array, - sizeof(LineartEdge *) * obi->pending_edges.next); - MEM_freeN(obi->pending_edges.array); - pe->next += obi->pending_edges.next; -} - -static void lineart_triangle_adjacent_assign(LineartTriangle *tri, - LineartTriangleAdjacent *tri_adj, - LineartEdge *e) -{ - if (lineart_edge_match(tri, e, 0, 1)) { - tri_adj->e[0] = e; - } - else if (lineart_edge_match(tri, e, 1, 2)) { - tri_adj->e[1] = e; - } - else if (lineart_edge_match(tri, e, 2, 0)) { - tri_adj->e[2] = e; - } -} - -typedef struct TriData { - LineartObjectInfo *ob_info; - const MLoopTri *mlooptri; - const int *material_indices; - LineartVert *vert_arr; - LineartTriangle *tri_arr; - int lineart_triangle_size; - LineartTriangleAdjacent *tri_adj; -} TriData; - -static void lineart_load_tri_task(void *__restrict userdata, - const int i, - const TaskParallelTLS *__restrict UNUSED(tls)) -{ - TriData *tri_task_data = (TriData *)userdata; - Mesh *me = tri_task_data->ob_info->original_me; - LineartObjectInfo *ob_info = tri_task_data->ob_info; - const MLoopTri *mlooptri = &tri_task_data->mlooptri[i]; - const int *material_indices = tri_task_data->material_indices; - LineartVert *vert_arr = tri_task_data->vert_arr; - LineartTriangle *tri = tri_task_data->tri_arr; - const MLoop *loops = BKE_mesh_loops(me); - - tri = (LineartTriangle *)(((uchar *)tri) + tri_task_data->lineart_triangle_size * i); - - int v1 = loops[mlooptri->tri[0]].v; - int v2 = loops[mlooptri->tri[1]].v; - int v3 = loops[mlooptri->tri[2]].v; - - tri->v[0] = &vert_arr[v1]; - tri->v[1] = &vert_arr[v2]; - tri->v[2] = &vert_arr[v3]; - - /* Material mask bits and occlusion effectiveness assignment. */ - Material *mat = BKE_object_material_get( - ob_info->original_ob_eval, material_indices ? material_indices[mlooptri->poly] + 1 : 1); - tri->material_mask_bits |= ((mat && (mat->lineart.flags & LRT_MATERIAL_MASK_ENABLED)) ? - 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]; - const MVert *verts = BKE_mesh_verts(me); - normal_tri_v3(no, verts[v1].co, verts[v2].co, verts[v3].co); - copy_v3db_v3fl(gn, no); - mul_v3_mat3_m4v3_db(tri->gn, ob_info->normal, gn); - normalize_v3_db(tri->gn); - - if (ob_info->usage == OBJECT_LRT_INTERSECTION_ONLY) { - tri->flags |= LRT_TRIANGLE_INTERSECTION_ONLY; - } - else if (ob_info->usage == OBJECT_LRT_FORCE_INTERSECTION) { - tri->flags |= LRT_TRIANGLE_FORCE_INTERSECTION; - } - else if (ELEM(ob_info->usage, OBJECT_LRT_NO_INTERSECTION, OBJECT_LRT_OCCLUSION_ONLY)) { - tri->flags |= LRT_TRIANGLE_NO_INTERSECTION; - } - - /* Re-use this field to refer to adjacent info, will be cleared after culling stage. */ - tri->intersecting_verts = (void *)&tri_task_data->tri_adj[i]; -} -typedef struct EdgeNeighborData { - LineartEdgeNeighbor *edge_nabr; - LineartAdjacentEdge *adj_e; - const MLoopTri *mlooptri; - const MLoop *mloop; -} EdgeNeighborData; - -static void lineart_edge_neighbor_init_task(void *__restrict userdata, - const int i, - const TaskParallelTLS *__restrict UNUSED(tls)) -{ - EdgeNeighborData *en_data = (EdgeNeighborData *)userdata; - LineartAdjacentEdge *adj_e = &en_data->adj_e[i]; - const MLoopTri *looptri = &en_data->mlooptri[i / 3]; - LineartEdgeNeighbor *edge_nabr = &en_data->edge_nabr[i]; - const MLoop *mloop = en_data->mloop; - - adj_e->e = i; - adj_e->v1 = mloop[looptri->tri[i % 3]].v; - adj_e->v2 = mloop[looptri->tri[(i + 1) % 3]].v; - if (adj_e->v1 > adj_e->v2) { - SWAP(uint32_t, adj_e->v1, adj_e->v2); - } - edge_nabr->e = -1; - - edge_nabr->v1 = adj_e->v1; - edge_nabr->v2 = adj_e->v2; - edge_nabr->flags = 0; -} - -static LineartEdgeNeighbor *lineart_build_edge_neighbor(Mesh *me, int total_edges) -{ - /* Because the mesh is triangulated, so `me->totedge` should be reliable? */ - LineartAdjacentEdge *adj_e = MEM_mallocN(sizeof(LineartAdjacentEdge) * total_edges, - "LineartAdjacentEdge arr"); - LineartEdgeNeighbor *edge_nabr = MEM_mallocN(sizeof(LineartEdgeNeighbor) * total_edges, - "LineartEdgeNeighbor arr"); - - TaskParallelSettings en_settings; - BLI_parallel_range_settings_defaults(&en_settings); - /* Set the minimum amount of edges a thread has to process. */ - en_settings.min_iter_per_thread = 50000; - - EdgeNeighborData en_data; - en_data.adj_e = adj_e; - en_data.edge_nabr = edge_nabr; - en_data.mlooptri = BKE_mesh_runtime_looptri_ensure(me); - en_data.mloop = BKE_mesh_loops(me); - - BLI_task_parallel_range(0, total_edges, &en_data, lineart_edge_neighbor_init_task, &en_settings); - - lineart_sort_adjacent_items(adj_e, total_edges); - - for (int i = 0; i < total_edges - 1; i++) { - if (adj_e[i].v1 == adj_e[i + 1].v1 && adj_e[i].v2 == adj_e[i + 1].v2) { - edge_nabr[adj_e[i].e].e = adj_e[i + 1].e; - edge_nabr[adj_e[i + 1].e].e = adj_e[i].e; - } - } - - MEM_freeN(adj_e); - - return edge_nabr; -} - -static void lineart_geometry_object_load(LineartObjectInfo *ob_info, - LineartData *la_data, - ListBase *shadow_elns) -{ - LineartElementLinkNode *elem_link_node; - LineartVert *la_v_arr; - LineartEdge *la_edge_arr; - LineartEdgeSegment *la_seg_arr; - LineartTriangle *la_tri_arr; - - Mesh *me = ob_info->original_me; - - if (!me->totedge) { - return; - } - - /* Triangulate. */ - const MLoopTri *mlooptri = BKE_mesh_runtime_looptri_ensure(me); - const int tot_tri = BKE_mesh_runtime_looptri_len(me); - - const int *material_indices = (const int *)CustomData_get_layer_named( - &me->pdata, CD_PROP_INT32, "material_index"); - - /* Check if we should look for custom data tags like Freestyle edges or faces. */ - bool can_find_freestyle_edge = false; - int layer_index = CustomData_get_active_layer_index(&me->edata, CD_FREESTYLE_EDGE); - if (layer_index != -1) { - can_find_freestyle_edge = true; - } - - bool can_find_freestyle_face = false; - layer_index = CustomData_get_active_layer_index(&me->pdata, CD_FREESTYLE_FACE); - if (layer_index != -1) { - can_find_freestyle_face = true; - } - - /* If we allow duplicated edges, one edge should get added multiple times if is has been - * classified as more than one edge type. This is so we can create multiple different line type - * chains containing the same edge. */ - la_v_arr = lineart_mem_acquire_thread(&la_data->render_data_pool, - sizeof(LineartVert) * me->totvert); - la_tri_arr = lineart_mem_acquire_thread(&la_data->render_data_pool, - tot_tri * la_data->sizeof_triangle); - - Object *orig_ob = ob_info->original_ob; - - BLI_spin_lock(&la_data->lock_task); - elem_link_node = lineart_list_append_pointer_pool_sized_thread( - &la_data->geom.vertex_buffer_pointers, - &la_data->render_data_pool, - la_v_arr, - 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; - - bool use_auto_smooth = false; - float crease_angle = 0; - if (orig_ob->lineart.flags & OBJECT_LRT_OWN_CREASE) { - crease_angle = cosf(M_PI - orig_ob->lineart.crease_threshold); - } - else if (ob_info->original_me->flag & ME_AUTOSMOOTH) { - crease_angle = cosf(ob_info->original_me->smoothresh); - use_auto_smooth = true; - } - else { - crease_angle = la_data->conf.crease_threshold; - } - - /* FIXME(Yiming): Hack for getting clean 3D text, the seam that extruded text object creates - * erroneous detection on creases. Future configuration should allow options. */ - if (orig_ob->type == OB_FONT) { - elem_link_node->flags |= LRT_ELEMENT_BORDER_ONLY; - } - - BLI_spin_lock(&la_data->lock_task); - elem_link_node = lineart_list_append_pointer_pool_sized_thread( - &la_data->geom.triangle_buffer_pointers, - &la_data->render_data_pool, - la_tri_arr, - sizeof(LineartElementLinkNode)); - BLI_spin_unlock(&la_data->lock_task); - - int usage = ob_info->usage; - - elem_link_node->element_count = tot_tri; - elem_link_node->object_ref = orig_ob; - elem_link_node->flags |= (usage == OBJECT_LRT_NO_INTERSECTION ? LRT_ELEMENT_NO_INTERSECTION : 0); - - /* Note this memory is not from pool, will be deleted after culling. */ - LineartTriangleAdjacent *tri_adj = MEM_callocN(sizeof(LineartTriangleAdjacent) * tot_tri, - "LineartTriangleAdjacent"); - /* Link is minimal so we use pool anyway. */ - BLI_spin_lock(&la_data->lock_task); - lineart_list_append_pointer_pool_thread( - &la_data->geom.triangle_adjacent_pointers, &la_data->render_data_pool, tri_adj); - BLI_spin_unlock(&la_data->lock_task); - - /* Convert all vertices to lineart verts. */ - TaskParallelSettings vert_settings; - BLI_parallel_range_settings_defaults(&vert_settings); - /* Set the minimum amount of verts a thread has to process. */ - vert_settings.min_iter_per_thread = 4000; - - VertData vert_data; - vert_data.mvert = BKE_mesh_verts(me); - vert_data.v_arr = la_v_arr; - vert_data.model_view = ob_info->model_view; - vert_data.model_view_proj = ob_info->model_view_proj; - - BLI_task_parallel_range( - 0, me->totvert, &vert_data, lineart_mvert_transform_task, &vert_settings); - - /* Convert all mesh triangles into lineart triangles. - * Also create an edge map to get connectivity between edges and triangles. */ - TaskParallelSettings tri_settings; - BLI_parallel_range_settings_defaults(&tri_settings); - /* Set the minimum amount of triangles a thread has to process. */ - tri_settings.min_iter_per_thread = 4000; - - TriData tri_data; - tri_data.ob_info = ob_info; - tri_data.mlooptri = mlooptri; - tri_data.material_indices = material_indices; - tri_data.vert_arr = la_v_arr; - tri_data.tri_arr = la_tri_arr; - tri_data.lineart_triangle_size = la_data->sizeof_triangle; - tri_data.tri_adj = tri_adj; - - uint32_t total_edges = tot_tri * 3; - - BLI_task_parallel_range(0, tot_tri, &tri_data, lineart_load_tri_task, &tri_settings); - - /* Check for contour lines in the mesh. - * IE check if the triangle edges lies in area where the triangles go from front facing to back - * facing. - */ - EdgeFeatReduceData edge_reduce = {0}; - TaskParallelSettings edge_feat_settings; - BLI_parallel_range_settings_defaults(&edge_feat_settings); - /* Set the minimum amount of edges a thread has to process. */ - edge_feat_settings.min_iter_per_thread = 4000; - edge_feat_settings.userdata_chunk = &edge_reduce; - edge_feat_settings.userdata_chunk_size = sizeof(EdgeFeatReduceData); - edge_feat_settings.func_reduce = feat_data_sum_reduce; - - EdgeFeatData edge_feat_data = {0}; - edge_feat_data.ld = la_data; - edge_feat_data.me = me; - edge_feat_data.ob_eval = ob_info->original_ob_eval; - edge_feat_data.mlooptri = mlooptri; - edge_feat_data.material_indices = material_indices; - edge_feat_data.edge_nabr = lineart_build_edge_neighbor(me, total_edges); - edge_feat_data.tri_array = la_tri_arr; - edge_feat_data.v_array = la_v_arr; - edge_feat_data.crease_threshold = crease_angle; - edge_feat_data.use_auto_smooth = use_auto_smooth; - edge_feat_data.use_freestyle_face = can_find_freestyle_face; - edge_feat_data.use_freestyle_edge = can_find_freestyle_edge; - if (edge_feat_data.use_freestyle_face) { - edge_feat_data.freestyle_face_index = CustomData_get_layer_index(&me->pdata, - CD_FREESTYLE_FACE); - } - if (edge_feat_data.use_freestyle_edge) { - edge_feat_data.freestyle_edge_index = CustomData_get_layer_index(&me->edata, - CD_FREESTYLE_EDGE); - } - - BLI_task_parallel_range(0, - total_edges, - &edge_feat_data, - lineart_identify_mlooptri_feature_edges, - &edge_feat_settings); - - LooseEdgeData loose_data = {0}; - - if (la_data->conf.use_loose) { - /* Only identifying floating edges at this point because other edges has been taken care of - * inside #lineart_identify_mlooptri_feature_edges function. */ - TaskParallelSettings edge_loose_settings; - BLI_parallel_range_settings_defaults(&edge_loose_settings); - edge_loose_settings.min_iter_per_thread = 4000; - edge_loose_settings.func_reduce = loose_data_sum_reduce; - edge_loose_settings.userdata_chunk = &loose_data; - edge_loose_settings.userdata_chunk_size = sizeof(LooseEdgeData); - loose_data.edges = BKE_mesh_edges(me); - BLI_task_parallel_range( - 0, me->totedge, &loose_data, lineart_identify_loose_edges, &edge_loose_settings); - } - - int allocate_la_e = edge_reduce.feat_edges + loose_data.loose_count; - - 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->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->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; - LineartEdgeSegment *la_seg; - la_edge = la_edge_arr; - la_seg = la_seg_arr; - - for (int i = 0; i < total_edges; i++) { - LineartEdgeNeighbor *edge_nabr = &edge_feat_data.edge_nabr[i]; - - if (i < edge_nabr->e) { - continue; - } - - /* Not a feature line, so we skip. */ - if (edge_nabr->flags == 0) { - continue; - } - - LineartEdge *edge_added = NULL; - - /* See eLineartEdgeFlag for details. */ - for (int flag_bit = 0; flag_bit < LRT_MESH_EDGE_TYPES_COUNT; flag_bit++) { - int use_type = LRT_MESH_EDGE_TYPES[flag_bit]; - if (!(use_type & edge_nabr->flags)) { - continue; - } - - la_edge->v1 = &la_v_arr[edge_nabr->v1]; - la_edge->v2 = &la_v_arr[edge_nabr->v2]; - int findex = i / 3; - la_edge->t1 = lineart_triangle_from_index(la_data, la_tri_arr, findex); - if (!edge_added) { - lineart_triangle_adjacent_assign(la_edge->t1, &tri_adj[findex], la_edge); - } - if (edge_nabr->e != -1) { - findex = edge_nabr->e / 3; - la_edge->t2 = lineart_triangle_from_index(la_data, la_tri_arr, findex); - if (!edge_added) { - lineart_triangle_adjacent_assign(la_edge->t2, &tri_adj[findex], la_edge); - } - } - 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 (ELEM(usage, - OBJECT_LRT_INHERIT, - OBJECT_LRT_INCLUDE, - OBJECT_LRT_NO_INTERSECTION, - OBJECT_LRT_FORCE_INTERSECTION)) { - lineart_add_edge_to_array_thread(ob_info, la_edge); - } - - if (edge_added) { - edge_added->flags |= LRT_EDGE_FLAG_NEXT_IS_DUPLICATION; - } - - edge_added = la_edge; - - la_edge++; - la_seg++; - - if (!la_data->conf.allow_duplicated_types) { - break; - } - } - } - - if (loose_data.loose_array) { - for (int i = 0; i < loose_data.loose_count; i++) { - const MEdge *edge = &loose_data.edges[loose_data.loose_array[i]]; - la_edge->v1 = &la_v_arr[edge->v1]; - la_edge->v2 = &la_v_arr[edge->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 (ELEM(usage, - OBJECT_LRT_INHERIT, - OBJECT_LRT_INCLUDE, - OBJECT_LRT_NO_INTERSECTION, - OBJECT_LRT_FORCE_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++; - } - MEM_SAFE_FREE(loose_data.loose_array); - } - - MEM_freeN(edge_feat_data.edge_nabr); - - if (ob_info->free_use_mesh) { - BKE_id_free(NULL, me); - } -} - -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, olti->shadow_elns); - } -} - -static uchar lineart_intersection_mask_check(Collection *c, Object *ob) -{ - LISTBASE_FOREACH (CollectionChild *, cc, &c->children) { - uchar result = lineart_intersection_mask_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_MASK) { - return c->lineart_intersection_mask; - } - } - - 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. - */ -static int lineart_usage_check(Collection *c, Object *ob, bool is_render) -{ - - if (!c) { - return OBJECT_LRT_INHERIT; - } - - int object_has_special_usage = (ob->lineart.usage != OBJECT_LRT_INHERIT); - - if (object_has_special_usage) { - return ob->lineart.usage; - } - - if (c->gobject.first) { - if (BKE_collection_has_object(c, (Object *)(ob->id.orig_id))) { - if ((is_render && (c->flag & COLLECTION_HIDE_RENDER)) || - ((!is_render) && (c->flag & COLLECTION_HIDE_VIEWPORT))) { - return OBJECT_LRT_EXCLUDE; - } - if (ob->lineart.usage == OBJECT_LRT_INHERIT) { - switch (c->lineart_usage) { - case COLLECTION_LRT_OCCLUSION_ONLY: - return OBJECT_LRT_OCCLUSION_ONLY; - case COLLECTION_LRT_EXCLUDE: - return OBJECT_LRT_EXCLUDE; - case COLLECTION_LRT_INTERSECTION_ONLY: - return OBJECT_LRT_INTERSECTION_ONLY; - case COLLECTION_LRT_NO_INTERSECTION: - return OBJECT_LRT_NO_INTERSECTION; - case COLLECTION_LRT_FORCE_INTERSECTION: - return OBJECT_LRT_FORCE_INTERSECTION; - } - return OBJECT_LRT_INHERIT; - } - return ob->lineart.usage; - } - } - - LISTBASE_FOREACH (CollectionChild *, cc, &c->children) { - int result = lineart_usage_check(cc->collection, ob, is_render); - if (result > OBJECT_LRT_INHERIT) { - return result; - } - } - - return OBJECT_LRT_INHERIT; -} - -static void lineart_geometry_load_assign_thread(LineartObjectLoadTaskInfo *olti_list, - LineartObjectInfo *obi, - int thread_count, - int this_face_count) -{ - LineartObjectLoadTaskInfo *use_olti = olti_list; - uint64_t min_face = use_olti->total_faces; - for (int i = 0; i < thread_count; i++) { - if (olti_list[i].total_faces < min_face) { - min_face = olti_list[i].total_faces; - use_olti = &olti_list[i]; - } - } - - use_olti->total_faces += this_face_count; - obi->next = use_olti->pending; - use_olti->pending = obi; -} - -static bool lineart_geometry_check_visible(double model_view_proj[4][4], - double shift_x, - double shift_y, - Mesh *use_mesh) -{ - if (!use_mesh) { - return false; - } - float mesh_min[3], mesh_max[3]; - INIT_MINMAX(mesh_min, mesh_max); - BKE_mesh_minmax(use_mesh, mesh_min, mesh_max); - BoundBox bb = {0}; - BKE_boundbox_init_from_minmax(&bb, mesh_min, mesh_max); - - double co[8][4]; - double tmp[3]; - for (int i = 0; i < 8; i++) { - copy_v3db_v3fl(co[i], bb.vec[i]); - copy_v3_v3_db(tmp, co[i]); - mul_v4_m4v3_db(co[i], model_view_proj, tmp); - co[i][0] -= shift_x * 2 * co[i][3]; - co[i][1] -= shift_y * 2 * co[i][3]; - } - - bool cond[6] = {true, true, true, true, true, true}; - /* Because for a point to be inside clip space, it must satisfy `-Wc <= XYCc <= Wc`, here if - * all verts falls to the same side of the clip space border, we know it's outside view. */ - for (int i = 0; i < 8; i++) { - cond[0] &= (co[i][0] < -co[i][3]); - cond[1] &= (co[i][0] > co[i][3]); - cond[2] &= (co[i][1] < -co[i][3]); - cond[3] &= (co[i][1] > co[i][3]); - cond[4] &= (co[i][2] < -co[i][3]); - cond[5] &= (co[i][2] > co[i][3]); - } - for (int i = 0; i < 6; i++) { - if (cond[i]) { - return false; - } - } - return true; -} - -static void lineart_object_load_single_instance(LineartData *ld, - Depsgraph *depsgraph, - Scene *scene, - Object *ob, - Object *ref_ob, - float use_mat[4][4], - bool is_render, - LineartObjectLoadTaskInfo *olti, - 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); - mul_m4db_m4db_m4fl_uniq(obi->model_view, ld->conf.view, use_mat); - - if (!ELEM(ob->type, OB_MESH, OB_MBALL, OB_CURVES_LEGACY, OB_SURF, OB_FONT)) { - return; - } - if (ob->type == OB_MESH) { - use_mesh = BKE_object_get_evaluated_mesh(ob); - if (use_mesh->edit_mesh) { - /* If the object is being edited, then the mesh is not evaluated fully into the final - * result, do not load them. */ - return; - } - } - else { - use_mesh = BKE_mesh_new_from_object(depsgraph, ob, true, true); - } - - /* In case we still can not get any mesh geometry data from the object */ - if (!use_mesh) { - return; - } - - if (!lineart_geometry_check_visible( - obi->model_view_proj, ld->conf.shift_x, ld->conf.shift_y, use_mesh)) { - return; - } - - if (ob->type != OB_MESH) { - obi->free_use_mesh = true; - } - - /* Make normal matrix. */ - float imat[4][4]; - invert_m4_m4(imat, use_mat); - transpose_m4(imat); - copy_m4d_m4(obi->normal, imat); - - obi->original_me = use_mesh; - obi->original_ob = (ref_ob->id.orig_id ? (Object *)ref_ob->id.orig_id : (Object *)ref_ob); - obi->original_ob_eval = DEG_get_evaluated_object(depsgraph, obi->original_ob); - lineart_geometry_load_assign_thread(olti, obi, thread_count, use_mesh->totpoly); -} - -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]; - - 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); - } - 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); - - 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); - - double t_start; - if (G.debug_value == 4000) { - t_start = PIL_check_seconds_timer(); - } - - 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( - &ld->render_data_pool, sizeof(LineartObjectLoadTaskInfo) * thread_count); - - eEvaluationMode eval_mode = DEG_get_mode(depsgraph); - bool is_render = eval_mode == DAG_EVAL_RENDER; - - int flags = DEG_ITER_OBJECT_FLAG_LINKED_DIRECTLY | DEG_ITER_OBJECT_FLAG_LINKED_VIA_SET | - DEG_ITER_OBJECT_FLAG_VISIBLE; - - /* Instance duplicated & particles. */ - if (allow_duplicates) { - flags |= DEG_ITER_OBJECT_FLAG_DUPLI; - } - - DEGObjectIterSettings deg_iter_settings = {0}; - deg_iter_settings.depsgraph = depsgraph; - deg_iter_settings.flags = flags; - - /* XXX(@Yiming): Temporary solution, this iterator is technically unsafe to use *during* - * depsgraph evaluation, see D14997 for detailed explanations. */ - DEG_OBJECT_ITER_BEGIN (°_iter_settings, ob) { - - obindex++; - - Object *eval_ob = DEG_get_evaluated_object(depsgraph, ob); - - if (!eval_ob) { - continue; - } - - /* DEG_OBJECT_ITER_BEGIN will include the instanced mesh of these curve object types, so don't - * load them twice. */ - if (allow_duplicates && ELEM(ob->type, OB_CURVES_LEGACY, OB_FONT, OB_SURF)) { - continue; - } - - 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->object_to_world, - is_render, - olti, - thread_count, - obindex); - } - } - DEG_OBJECT_ITER_END; - - TaskPool *tp = BLI_task_pool_create(NULL, TASK_PRIORITY_HIGH); - - if (G.debug_value == 4000) { - printf("thread count: %d\n", thread_count); - } - 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); - } - BLI_task_pool_work_and_wait(tp); - BLI_task_pool_free(tp); - - /* The step below is to serialize vertex index in the whole scene, so - * lineart_triangle_share_edge() can work properly from the lack of triangle adjacent info. */ - int global_i = 0; - - int edge_count = 0; - for (int i = 0; i < thread_count; i++) { - for (LineartObjectInfo *obi = olti[i].pending; obi; obi = obi->next) { - if (!obi->v_eln) { - continue; - } - edge_count += obi->pending_edges.next; - } - } - lineart_finalize_object_edge_array_reserve(&ld->pending_edges, edge_count); - - for (int i = 0; i < thread_count; i++) { - for (LineartObjectInfo *obi = olti[i].pending; obi; obi = obi->next) { - if (!obi->v_eln) { - continue; - } - LineartVert *v = (LineartVert *)obi->v_eln->pointer; - int v_count = obi->v_eln->element_count; - obi->v_eln->global_index_offset = global_i; - for (int vi = 0; vi < v_count; vi++) { - v[vi].index += global_i; - } - /* Register a global index increment. See #lineart_triangle_share_edge() and - * #lineart_main_load_geometries() for detailed. It's okay that global_vindex might - * eventually overflow, in such large scene it's virtually impossible for two vertex of the - * same numeric index to come close together. */ - obi->global_i_offset = global_i; - global_i += v_count; - lineart_finalize_object_edge_array(&ld->pending_edges, obi); - } - } - - if (G.debug_value == 4000) { - double t_elapsed = PIL_check_seconds_timer() - t_start; - printf("Line art loading time: %lf\n", t_elapsed); - printf("Discarded %d object from bound box check\n", bound_box_discard_count); - } -} - -/** - * Returns the two other verts of the triangle given a vertex. Returns false if the given vertex - * doesn't belong to this triangle. - */ -static bool lineart_triangle_get_other_verts(const LineartTriangle *tri, - const LineartVert *vt, - LineartVert **l, - LineartVert **r) -{ - if (tri->v[0] == vt) { - *l = tri->v[1]; - *r = tri->v[2]; - return true; - } - if (tri->v[1] == vt) { - *l = tri->v[2]; - *r = tri->v[0]; - return true; - } - if (tri->v[2] == vt) { - *l = tri->v[0]; - *r = tri->v[1]; - return true; - } - return false; -} - -bool lineart_edge_from_triangle(const LineartTriangle *tri, - const LineartEdge *e, - bool allow_overlapping_edges) -{ - 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) { -#define LRT_TRI_SAME_POINT(tri, i, pt) \ - ((LRT_DOUBLE_CLOSE_ENOUGH(tri->v[i]->gloc[0], pt->gloc[0]) && \ - LRT_DOUBLE_CLOSE_ENOUGH(tri->v[i]->gloc[1], pt->gloc[1]) && \ - LRT_DOUBLE_CLOSE_ENOUGH(tri->v[i]->gloc[2], pt->gloc[2])) || \ - (LRT_DOUBLE_CLOSE_ENOUGH(tri->v[i]->gloc[0], pt->gloc[0]) && \ - LRT_DOUBLE_CLOSE_ENOUGH(tri->v[i]->gloc[1], pt->gloc[1]) && \ - LRT_DOUBLE_CLOSE_ENOUGH(tri->v[i]->gloc[2], pt->gloc[2]))) - if ((LRT_TRI_SAME_POINT(tri, 0, e->v1) || LRT_TRI_SAME_POINT(tri, 1, e->v1) || - LRT_TRI_SAME_POINT(tri, 2, e->v1)) && - (LRT_TRI_SAME_POINT(tri, 0, e->v2) || LRT_TRI_SAME_POINT(tri, 1, e->v2) || - LRT_TRI_SAME_POINT(tri, 2, e->v2))) { - return true; - } -#undef LRT_TRI_SAME_POINT - } - return false; -} - -/* Sorting three intersection points from min to max, - * the order for each intersection is set in `lst[0]` to `lst[2]`. */ -#define INTERSECT_SORT_MIN_TO_MAX_3(ia, ib, ic, lst) \ - { \ - lst[0] = LRT_MIN3_INDEX(ia, ib, ic); \ - lst[1] = (((ia <= ib && ib <= ic) || (ic <= ib && ib <= ia)) ? \ - 1 : \ - (((ic <= ia && ia <= ib) || (ib < ia && ia <= ic)) ? 0 : 2)); \ - lst[2] = LRT_MAX3_INDEX(ia, ib, ic); \ - } - -/* `ia ib ic` are ordered. */ -#define INTERSECT_JUST_GREATER(is, order, num, index) \ - { \ - index = (num < is[order[0]] ? \ - order[0] : \ - (num < is[order[1]] ? order[1] : (num < is[order[2]] ? order[2] : -1))); \ - } - -/* `ia ib ic` are ordered. */ -#define INTERSECT_JUST_SMALLER(is, order, num, index) \ - { \ - index = (num > is[order[2]] ? \ - order[2] : \ - (num > is[order[1]] ? order[1] : (num > is[order[0]] ? order[0] : -1))); \ - } - -#define LRT_ISEC(index) (index == 0 ? isec_e1 : (index == 1 ? isec_e2 : isec_e3)) -#define LRT_PARALLEL(index) (index == 0 ? para_e1 : (index == 1 ? para_e2 : para_e3)) - -/** - * This is the main function to calculate - * the occlusion status between 1(one) triangle and 1(one) line. - * if returns true, then from/to will carry the occluded segments - * in ratio from `e->v1` to `e->v2`. The line is later cut with these two values. - * - * TODO(@Yiming): This function uses a convoluted method that needs to be redesigned. - * - * 1) The #lineart_intersect_seg_seg() and #lineart_point_triangle_relation() are separate calls, - * which would potentially return results that doesn't agree, especially when it's an edge - * extruding from one of the triangle's point. To get the information using one math process can - * solve this problem. - * - * 2) Currently using discrete a/b/c/para_e1/para_e2/para_e3/is[3] values for storing - * intersection/edge_aligned/intersection_order info, which isn't optimal, needs a better - * representation (likely a struct) for readability and clarity of code path. - * - * I keep this function as-is because it's still fast, and more importantly the output value - * threshold is already in tune with the cutting function in the next stage. - * 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(const LineartTriangle *tri, - const LineartEdge *e, - const double *override_camera_loc, - const bool override_cam_is_persp, - const bool allow_overlapping_edges, - 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) -{ - double cross_ratios[3] = {0}; - int cross_order[3]; - int cross_v1 = -1, cross_v2 = -1; - /* If the edge intersects with the triangle edges (including extensions). */ - int isec_e1, isec_e2, isec_e3; - /* If edge is parallel to one of the edges in the triangle. */ - bool para_e1, para_e2, para_e3; - enum LineartPointTri state_v1 = LRT_OUTSIDE_TRIANGLE, state_v2 = LRT_OUTSIDE_TRIANGLE; - - double dir_v1[3]; - double dir_v2[3]; - double view_vector[4]; - double dir_cam[3]; - double dot_v1, dot_v2, dot_v1a, dot_v2a; - double dot_f; - double gloc[4], trans[4]; - double cut = -1; - - double *LFBC = e->v1->fbcoord, *RFBC = e->v2->fbcoord, *FBC0 = tri->v[0]->fbcoord, - *FBC1 = tri->v[1]->fbcoord, *FBC2 = tri->v[2]->fbcoord; - - /* Overlapping not possible, return early. */ - if ((MAX3(FBC0[0], FBC1[0], FBC2[0]) < MIN2(LFBC[0], RFBC[0])) || - (MIN3(FBC0[0], FBC1[0], FBC2[0]) > MAX2(LFBC[0], RFBC[0])) || - (MAX3(FBC0[1], FBC1[1], FBC2[1]) < MIN2(LFBC[1], RFBC[1])) || - (MIN3(FBC0[1], FBC1[1], FBC2[1]) > MAX2(LFBC[1], RFBC[1])) || - (MIN3(FBC0[3], FBC1[3], FBC2[3]) > MAX2(LFBC[3], RFBC[3]))) { - return false; - } - - /* If the line is one of the edge in the triangle, then it's not occluded. */ - if (lineart_edge_from_triangle(tri, e, allow_overlapping_edges)) { - return false; - } - - /* Check if the line visually crosses one of the edge in the triangle. */ - isec_e1 = lineart_intersect_seg_seg(LFBC, RFBC, FBC0, FBC1, &cross_ratios[0], ¶_e1); - isec_e2 = lineart_intersect_seg_seg(LFBC, RFBC, FBC1, FBC2, &cross_ratios[1], ¶_e2); - isec_e3 = lineart_intersect_seg_seg(LFBC, RFBC, FBC2, FBC0, &cross_ratios[2], ¶_e3); - - /* Sort the intersection distance. */ - INTERSECT_SORT_MIN_TO_MAX_3(cross_ratios[0], cross_ratios[1], cross_ratios[2], cross_order); - - sub_v3_v3v3_db(dir_v1, e->v1->gloc, tri->v[0]->gloc); - sub_v3_v3v3_db(dir_v2, e->v2->gloc, tri->v[0]->gloc); - - copy_v3_v3_db(dir_cam, camera_dir); - copy_v3_v3_db(view_vector, override_camera_loc); - if (override_cam_is_persp) { - sub_v3_v3v3_db(dir_cam, view_vector, tri->v[0]->gloc); - } - - dot_v1 = dot_v3v3_db(dir_v1, tri->gn); - 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 - * files. - */ - if (fabs(dot_f) < FLT_EPSILON) { - return false; - } - - /* Whether two end points are inside/on_the_edge/outside of the triangle. */ - state_v1 = lineart_point_triangle_relation(LFBC, FBC0, FBC1, FBC2); - state_v2 = lineart_point_triangle_relation(RFBC, FBC0, FBC1, FBC2); - - /* If the edge doesn't visually cross any edge of the triangle... */ - if (!isec_e1 && !isec_e2 && !isec_e3) { - /* And if both end point from the edge is outside of the triangle... */ - if ((!state_v1) && (!state_v2)) { - return 0; /* We don't have any occlusion. */ - } - } - - /* Determine the cut position. */ - - dot_v1a = fabs(dot_v1); - if (dot_v1a < DBL_EPSILON) { - dot_v1a = 0; - dot_v1 = 0; - } - dot_v2a = fabs(dot_v2); - if (dot_v2a < DBL_EPSILON) { - dot_v2a = 0; - dot_v2 = 0; - } - if (dot_v1 - dot_v2 == 0) { - cut = 100000; - } - else if (dot_v1 * dot_v2 <= 0) { - cut = dot_v1a / fabs(dot_v1 - dot_v2); - } - else { - cut = fabs(dot_v2 + dot_v1) / fabs(dot_v1 - dot_v2); - cut = dot_v2a > dot_v1a ? 1 - cut : cut; - } - - /* 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, m_view_projection, gloc); - mul_v3db_db(trans, (1 / trans[3])); - trans[0] -= cam_shift_x * 2; - trans[1] -= cam_shift_y * 2; - /* To accommodate `k=0` and `k=inf` (vertical) lines. here the cut is in image space. */ - if (fabs(e->v1->fbcoord[0] - e->v2->fbcoord[0]) > - fabs(e->v1->fbcoord[1] - e->v2->fbcoord[1])) { - cut = ratiod(e->v1->fbcoord[0], e->v2->fbcoord[0], trans[0]); - } - else { - cut = ratiod(e->v1->fbcoord[1], e->v2->fbcoord[1], trans[1]); - } - } - -#define LRT_GUARD_NOT_FOUND \ - if (cross_v1 < 0 || cross_v2 < 0) { \ - return false; \ - } - - /* Determine the pair of edges that the line has crossed. The "|" symbol in the comment - * indicates triangle boundary. DBL_TRIANGLE_LIM is needed to for floating point precision - * tolerance. */ - - if (state_v1 == LRT_INSIDE_TRIANGLE) { - /* Left side is in the triangle. */ - if (state_v2 == LRT_INSIDE_TRIANGLE) { - /* | l---r | */ - INTERSECT_JUST_SMALLER(cross_ratios, cross_order, DBL_TRIANGLE_LIM, cross_v1); - INTERSECT_JUST_GREATER(cross_ratios, cross_order, 1 - DBL_TRIANGLE_LIM, cross_v2); - } - else if (state_v2 == LRT_ON_TRIANGLE) { - /* | l------r| */ - INTERSECT_JUST_SMALLER(cross_ratios, cross_order, DBL_TRIANGLE_LIM, cross_v1); - INTERSECT_JUST_GREATER(cross_ratios, cross_order, 1 - DBL_TRIANGLE_LIM, cross_v2); - } - else if (state_v2 == LRT_OUTSIDE_TRIANGLE) { - /* | l-------|------r */ - INTERSECT_JUST_SMALLER(cross_ratios, cross_order, DBL_TRIANGLE_LIM, cross_v1); - INTERSECT_JUST_GREATER(cross_ratios, cross_order, 0, cross_v2); - } - } - else if (state_v1 == LRT_ON_TRIANGLE) { - /* Left side is on some edge of the triangle. */ - if (state_v2 == LRT_INSIDE_TRIANGLE) { - /* |l------r | */ - INTERSECT_JUST_SMALLER(cross_ratios, cross_order, DBL_TRIANGLE_LIM, cross_v1); - INTERSECT_JUST_GREATER(cross_ratios, cross_order, 1 - DBL_TRIANGLE_LIM, cross_v2); - } - else if (state_v2 == LRT_ON_TRIANGLE) { - /* |l---------r| */ - INTERSECT_JUST_SMALLER(cross_ratios, cross_order, DBL_TRIANGLE_LIM, cross_v1); - INTERSECT_JUST_GREATER(cross_ratios, cross_order, 1 - DBL_TRIANGLE_LIM, cross_v2); - } - else if (state_v2 == LRT_OUTSIDE_TRIANGLE) { - /* |l----------|-------r (crossing the triangle) [OR] - * r---------|l | (not crossing the triangle) */ - INTERSECT_JUST_GREATER(cross_ratios, cross_order, DBL_TRIANGLE_LIM, cross_v2); - if (cross_v2 >= 0 && LRT_ISEC(cross_v2) && cross_ratios[cross_v2] > (DBL_TRIANGLE_LIM)) { - INTERSECT_JUST_SMALLER(cross_ratios, cross_order, DBL_TRIANGLE_LIM, cross_v1); - } - else { - INTERSECT_JUST_SMALLER(cross_ratios, cross_order, DBL_TRIANGLE_LIM, cross_v2); - if (cross_v2 > 0) { - INTERSECT_JUST_SMALLER(cross_ratios, cross_order, cross_ratios[cross_v2], cross_v1); - } - } - LRT_GUARD_NOT_FOUND - /* We could have the edge being completely parallel to the triangle where there isn't a - * viable occlusion result. */ - if ((LRT_PARALLEL(cross_v1) && !LRT_ISEC(cross_v1)) || - (LRT_PARALLEL(cross_v2) && !LRT_ISEC(cross_v2))) { - return false; - } - } - } - else if (state_v1 == LRT_OUTSIDE_TRIANGLE) { - /* Left side is outside of the triangle. */ - if (state_v2 == LRT_INSIDE_TRIANGLE) { - /* l---|---r | */ - INTERSECT_JUST_SMALLER(cross_ratios, cross_order, 1 - DBL_TRIANGLE_LIM, cross_v1); - INTERSECT_JUST_GREATER(cross_ratios, cross_order, 1 - DBL_TRIANGLE_LIM, cross_v2); - } - else if (state_v2 == LRT_ON_TRIANGLE) { - /* |r----------|-------l (crossing the triangle) [OR] - * l---------|r | (not crossing the triangle) */ - INTERSECT_JUST_SMALLER(cross_ratios, cross_order, 1 - DBL_TRIANGLE_LIM, cross_v1); - if (cross_v1 >= 0 && LRT_ISEC(cross_v1) && cross_ratios[cross_v1] < (1 - DBL_TRIANGLE_LIM)) { - INTERSECT_JUST_GREATER(cross_ratios, cross_order, 1 - DBL_TRIANGLE_LIM, cross_v2); - } - else { - INTERSECT_JUST_GREATER(cross_ratios, cross_order, 1 - DBL_TRIANGLE_LIM, cross_v1); - if (cross_v1 > 0) { - INTERSECT_JUST_GREATER(cross_ratios, cross_order, cross_ratios[cross_v1], cross_v2); - } - } - LRT_GUARD_NOT_FOUND - /* The same logic applies as above case. */ - if ((LRT_PARALLEL(cross_v1) && !LRT_ISEC(cross_v1)) || - (LRT_PARALLEL(cross_v2) && !LRT_ISEC(cross_v2))) { - return false; - } - } - else if (state_v2 == LRT_OUTSIDE_TRIANGLE) { - /* l---|----|----r (crossing the triangle) [OR] - * l----r | | (not crossing the triangle) */ - INTERSECT_JUST_GREATER(cross_ratios, cross_order, -DBL_TRIANGLE_LIM, cross_v1); - if (cross_v1 >= 0 && LRT_ISEC(cross_v1)) { - INTERSECT_JUST_GREATER(cross_ratios, cross_order, cross_ratios[cross_v1], cross_v2); - } - else { - if (cross_v1 >= 0) { - INTERSECT_JUST_GREATER(cross_ratios, cross_order, cross_ratios[cross_v1], cross_v1); - if (cross_v1 >= 0) { - INTERSECT_JUST_GREATER(cross_ratios, cross_order, cross_ratios[cross_v1], cross_v2); - } - } - } - } - } - - LRT_GUARD_NOT_FOUND - - double dot_1f = dot_v1 * dot_f, dot_2f = dot_v2 * dot_f; - - /* Determine the start and end point of image space cut on a line. */ - if (dot_1f <= 0 && dot_2f <= 0 && (dot_v1 || dot_v2)) { - *from = MAX2(0, cross_ratios[cross_v1]); - *to = MIN2(1, cross_ratios[cross_v2]); - if (*from >= *to) { - return false; - } - return true; - } - if (dot_1f >= 0 && dot_2f <= 0 && (dot_v1 || dot_v2)) { - *from = MAX2(cut, cross_ratios[cross_v1]); - *to = MIN2(1, cross_ratios[cross_v2]); - if (*from >= *to) { - return false; - } - return true; - } - if (dot_1f <= 0 && dot_2f >= 0 && (dot_v1 || dot_v2)) { - *from = MAX2(0, cross_ratios[cross_v1]); - *to = MIN2(cut, cross_ratios[cross_v2]); - if (*from >= *to) { - return false; - } - return true; - } - - /* Unlikely, but here's the default failed value if anything fall through. */ - return false; -} - -#undef INTERSECT_SORT_MIN_TO_MAX_3 -#undef INTERSECT_JUST_GREATER -#undef INTERSECT_JUST_SMALLER -#undef LRT_ISEC -#undef LRT_PARALLEL - -/** - * At this stage of the computation we don't have triangle adjacent info anymore, - * so we can only compare the global vert index. - */ -static bool lineart_triangle_share_edge(const LineartTriangle *l, const LineartTriangle *r) -{ - if (l->v[0]->index == r->v[0]->index) { - if (l->v[1]->index == r->v[1]->index || l->v[1]->index == r->v[2]->index || - l->v[2]->index == r->v[2]->index || l->v[2]->index == r->v[1]->index) { - return true; - } - } - if (l->v[0]->index == r->v[1]->index) { - if (l->v[1]->index == r->v[0]->index || l->v[1]->index == r->v[2]->index || - l->v[2]->index == r->v[2]->index || l->v[2]->index == r->v[0]->index) { - return true; - } - } - if (l->v[0]->index == r->v[2]->index) { - if (l->v[1]->index == r->v[1]->index || l->v[1]->index == r->v[0]->index || - l->v[2]->index == r->v[0]->index || l->v[2]->index == r->v[1]->index) { - return true; - } - } - if (l->v[1]->index == r->v[0]->index) { - if (l->v[2]->index == r->v[1]->index || l->v[2]->index == r->v[2]->index || - l->v[0]->index == r->v[2]->index || l->v[0]->index == r->v[1]->index) { - return true; - } - } - if (l->v[1]->index == r->v[1]->index) { - if (l->v[2]->index == r->v[0]->index || l->v[2]->index == r->v[2]->index || - l->v[0]->index == r->v[2]->index || l->v[0]->index == r->v[0]->index) { - return true; - } - } - if (l->v[1]->index == r->v[2]->index) { - if (l->v[2]->index == r->v[1]->index || l->v[2]->index == r->v[0]->index || - l->v[0]->index == r->v[0]->index || l->v[0]->index == r->v[1]->index) { - return true; - } - } - - /* Otherwise not possible. */ - return false; -} - -static LineartVert *lineart_triangle_share_point(const LineartTriangle *l, - const LineartTriangle *r) -{ - if (l->v[0] == r->v[0]) { - return r->v[0]; - } - if (l->v[0] == r->v[1]) { - return r->v[1]; - } - if (l->v[0] == r->v[2]) { - return r->v[2]; - } - if (l->v[1] == r->v[0]) { - return r->v[0]; - } - if (l->v[1] == r->v[1]) { - return r->v[1]; - } - if (l->v[1] == r->v[2]) { - return r->v[2]; - } - if (l->v[2] == r->v[0]) { - return r->v[0]; - } - if (l->v[2] == r->v[1]) { - return r->v[1]; - } - if (l->v[2] == r->v[2]) { - return r->v[2]; - } - return NULL; -} - -static bool lineart_triangle_2v_intersection_math( - LineartVert *v1, LineartVert *v2, LineartTriangle *tri, const double *last, double *rv) -{ - /* Direction vectors for the edge verts. We will check if the verts are on the same side of the - * triangle or not. */ - double dir_v1[3], dir_v2[3]; - double dot_v1, dot_v2; - double gloc[3]; - - sub_v3_v3v3_db(dir_v1, v1->gloc, tri->v[0]->gloc); - sub_v3_v3v3_db(dir_v2, v2->gloc, tri->v[0]->gloc); - - dot_v1 = dot_v3v3_db(dir_v1, tri->gn); - dot_v2 = dot_v3v3_db(dir_v2, tri->gn); - - if (dot_v1 * dot_v2 > 0 || (!dot_v1 && !dot_v2)) { - return false; - } - - dot_v1 = fabs(dot_v1); - dot_v2 = fabs(dot_v2); - - interp_v3_v3v3_db(gloc, v1->gloc, v2->gloc, dot_v1 / (dot_v1 + dot_v2)); - - /* Due to precision issue, we might end up with the same point as the one we already detected. */ - if (last && LRT_DOUBLE_CLOSE_ENOUGH(last[0], gloc[0]) && - LRT_DOUBLE_CLOSE_ENOUGH(last[1], gloc[1]) && LRT_DOUBLE_CLOSE_ENOUGH(last[2], gloc[2])) { - return false; - } - - if (!lineart_point_inside_triangle3d(gloc, tri->v[0]->gloc, tri->v[1]->gloc, tri->v[2]->gloc)) { - return false; - } - - copy_v3_v3_db(rv, gloc); - - return true; -} - -static bool lineart_triangle_intersect_math(LineartTriangle *tri, - LineartTriangle *t2, - double *v1, - double *v2) -{ - double *next = v1, *last = NULL; - LineartVert *sv1, *sv2; - - LineartVert *share = lineart_triangle_share_point(t2, tri); - - if (share) { - /* If triangles have sharing points like `abc` and `acd`, then we only need to detect `bc` - * against `acd` or `cd` against `abc`. */ - - lineart_triangle_get_other_verts(tri, share, &sv1, &sv2); - - copy_v3_v3_db(v1, share->gloc); - - if (!lineart_triangle_2v_intersection_math(sv1, sv2, t2, 0, v2)) { - lineart_triangle_get_other_verts(t2, share, &sv1, &sv2); - if (lineart_triangle_2v_intersection_math(sv1, sv2, tri, 0, v2)) { - return true; - } - } - } - else { - /* If not sharing any points, then we need to try all the possibilities. */ - - if (lineart_triangle_2v_intersection_math(tri->v[0], tri->v[1], t2, 0, v1)) { - next = v2; - last = v1; - } - - if (lineart_triangle_2v_intersection_math(tri->v[1], tri->v[2], t2, last, next)) { - if (last) { - return true; - } - next = v2; - last = v1; - } - if (lineart_triangle_2v_intersection_math(tri->v[2], tri->v[0], t2, last, next)) { - if (last) { - return true; - } - next = v2; - last = v1; - } - - if (lineart_triangle_2v_intersection_math(t2->v[0], t2->v[1], tri, last, next)) { - if (last) { - return true; - } - next = v2; - last = v1; - } - if (lineart_triangle_2v_intersection_math(t2->v[1], t2->v[2], tri, last, next)) { - if (last) { - return true; - } - next = v2; - last = v1; - } - if (lineart_triangle_2v_intersection_math(t2->v[2], t2->v[0], tri, last, next)) { - if (last) { - return true; - } - next = v2; - last = v1; - } - } - return false; -} - -static void lineart_add_isec_thread(LineartIsecThread *th, - const double *v1, - const double *v2, - LineartTriangle *tri1, - LineartTriangle *tri2) -{ - if (th->current == th->max) { - - LineartIsecSingle *new_array = MEM_mallocN(sizeof(LineartIsecSingle) * th->max * 2, - "LineartIsecSingle"); - memcpy(new_array, th->array, sizeof(LineartIsecSingle) * th->max); - th->max *= 2; - MEM_freeN(th->array); - th->array = new_array; - } - LineartIsecSingle *isec_single = &th->array[th->current]; - copy_v3_v3_db(isec_single->v1, v1); - copy_v3_v3_db(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++; -} - -#define LRT_ISECT_TRIANGLE_PER_THREAD 4096 - -static bool lineart_schedule_new_triangle_task(LineartIsecThread *th) -{ - LineartData *ld = th->ld; - int remaining = LRT_ISECT_TRIANGLE_PER_THREAD; - - BLI_spin_lock(&ld->lock_task); - LineartElementLinkNode *eln = ld->isect_scheduled_up_to; - - if (!eln) { - BLI_spin_unlock(&ld->lock_task); - return false; - } - - th->pending_from = eln; - th->index_from = ld->isect_scheduled_up_to_index; - - while (remaining > 0 && eln) { - int remaining_this_eln = eln->element_count - ld->isect_scheduled_up_to_index; - int added_count = MIN2(remaining, remaining_this_eln); - remaining -= added_count; - if (remaining || added_count == remaining_this_eln) { - eln = eln->next; - ld->isect_scheduled_up_to = eln; - ld->isect_scheduled_up_to_index = 0; - } - else { - ld->isect_scheduled_up_to_index += added_count; - } - } - - th->pending_to = eln ? eln : ld->geom.triangle_buffer_pointers.last; - th->index_to = ld->isect_scheduled_up_to_index; - - BLI_spin_unlock(&ld->lock_task); - - return true; -} - -/* This function initializes two things: - * 1) Triangle array scheduling info, for each worker thread to get its chunk from the scheduler. - * 2) Per-thread intersection result array. Does not store actual #LineartEdge, these results will - * be finalized by #lineart_create_edges_from_isec_data - */ -static void lineart_init_isec_thread(LineartIsecData *d, LineartData *ld, int thread_count) -{ - d->threads = MEM_callocN(sizeof(LineartIsecThread) * thread_count, "LineartIsecThread arr"); - d->ld = ld; - d->thread_count = thread_count; - - ld->isect_scheduled_up_to = ld->geom.triangle_buffer_pointers.first; - ld->isect_scheduled_up_to_index = 0; - - for (int i = 0; i < thread_count; i++) { - LineartIsecThread *it = &d->threads[i]; - it->array = MEM_mallocN(sizeof(LineartIsecSingle) * 100, "LineartIsecSingle arr"); - it->max = 100; - it->current = 0; - it->thread_id = i; - it->ld = ld; - } -} - -static void lineart_destroy_isec_thread(LineartIsecData *d) -{ - for (int i = 0; i < d->thread_count; i++) { - LineartIsecThread *it = &d->threads[i]; - MEM_freeN(it->array); - } - MEM_freeN(d->threads); -} - -static void lineart_triangle_intersect_in_bounding_area(LineartTriangle *tri, - LineartBoundingArea *ba, - LineartIsecThread *th, - int up_to) -{ - BLI_assert(th != NULL); - - if (!th) { - return; - } - - double *G0 = tri->v[0]->gloc, *G1 = tri->v[1]->gloc, *G2 = tri->v[2]->gloc; - - /* If this _is_ the smallest subdivision bounding area, then do the intersections there. */ - for (int i = 0; i < up_to; i++) { - /* Testing_triangle->testing[0] is used to store pairing triangle reference. - * See definition of LineartTriangleThread for more info. */ - LineartTriangle *testing_triangle = ba->linked_triangles[i]; - LineartTriangleThread *tt = (LineartTriangleThread *)testing_triangle; - - if (testing_triangle == tri || tt->testing_e[th->thread_id] == (LineartEdge *)tri) { - continue; - } - tt->testing_e[th->thread_id] = (LineartEdge *)tri; - - if (!((testing_triangle->flags | tri->flags) & LRT_TRIANGLE_FORCE_INTERSECTION)) { - if (((testing_triangle->flags | tri->flags) & LRT_TRIANGLE_NO_INTERSECTION) || - (testing_triangle->flags & tri->flags & LRT_TRIANGLE_INTERSECTION_ONLY)) { - continue; - } - } - - double *RG0 = testing_triangle->v[0]->gloc, *RG1 = testing_triangle->v[1]->gloc, - *RG2 = testing_triangle->v[2]->gloc; - - /* Bounding box not overlapping or triangles share edges, not potential of intersecting. */ - if ((MIN3(G0[2], G1[2], G2[2]) > MAX3(RG0[2], RG1[2], RG2[2])) || - (MAX3(G0[2], G1[2], G2[2]) < MIN3(RG0[2], RG1[2], RG2[2])) || - (MIN3(G0[0], G1[0], G2[0]) > MAX3(RG0[0], RG1[0], RG2[0])) || - (MAX3(G0[0], G1[0], G2[0]) < MIN3(RG0[0], RG1[0], RG2[0])) || - (MIN3(G0[1], G1[1], G2[1]) > MAX3(RG0[1], RG1[1], RG2[1])) || - (MAX3(G0[1], G1[1], G2[1]) < MIN3(RG0[1], RG1[1], RG2[1])) || - lineart_triangle_share_edge(tri, testing_triangle)) { - continue; - } - - /* If we do need to compute intersection, then finally do it. */ - - double iv1[3], iv2[3]; - if (lineart_triangle_intersect_math(tri, testing_triangle, iv1, iv2)) { - lineart_add_isec_thread(th, iv1, iv2, tri, testing_triangle); - } - } -} - -/** - * The calculated view vector will point towards the far-plane from the camera position. - */ -void lineart_main_get_view_vector(LineartData *ld) -{ - float direction[3] = {0, 0, 1}; - float trans[3]; - float inv[4][4]; - float obmat_no_scale[4][4]; - - copy_m4_m4(obmat_no_scale, ld->conf.cam_obmat); - normalize_v3(obmat_no_scale[0]); - normalize_v3(obmat_no_scale[1]); - normalize_v3(obmat_no_scale[2]); - invert_m4_m4(inv, obmat_no_scale); - transpose_m4(inv); - mul_v3_mat3_m4v3(trans, inv, direction); - copy_m4_m4(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_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; - } - - BLI_listbase_clear(&ld->chains); - BLI_listbase_clear(&ld->wasted_cuts); - - BLI_listbase_clear(&ld->geom.vertex_buffer_pointers); - BLI_listbase_clear(&ld->geom.line_buffer_pointers); - BLI_listbase_clear(&ld->geom.triangle_buffer_pointers); - - 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; - - lineart_destroy_render_data(ld); - - if (ld) { - MEM_freeN(ld); - lmd->la_data_ptr = NULL; - } - - if (G.debug_value == 4000) { - printf("LRT: Destroyed render data.\n"); - } -} - -static LineartCache *lineart_init_cache(void) -{ - LineartCache *lc = MEM_callocN(sizeof(LineartCache), "Lineart Cache"); - return lc; -} - -void MOD_lineart_clear_cache(struct LineartCache **lc) -{ - if (!(*lc)) { - return; - } - lineart_mem_destroy(&((*lc)->chain_data_pool)); - MEM_freeN(*lc); - (*lc) = NULL; -} - -static LineartData *lineart_create_render_buffer(Scene *scene, - LineartGpencilModifierData *lmd, - Object *camera, - Object *active_camera, - LineartCache *lc) -{ - LineartData *ld = MEM_callocN(sizeof(LineartData), "Line Art render buffer"); - - lmd->cache = lc; - lmd->la_data_ptr = ld; - lc->all_enabled_edge_types = lmd->edge_types_override; - - if (!scene || !camera || !lc) { - return NULL; - } - Camera *c = camera->data; - double clipping_offset = 0; - - if (lmd->calculation_flags & LRT_ALLOW_CLIPPING_BOUNDARIES) { - /* This way the clipped lines are "stably visible" by prevents depth buffer artifacts. */ - clipping_offset = 0.0001; - } - - copy_v3db_v3fl(ld->conf.camera_pos, camera->object_to_world[3]); - if (active_camera) { - copy_v3db_v3fl(ld->conf.active_camera_pos, active_camera->object_to_world[3]); - } - copy_m4_m4(ld->conf.cam_obmat, camera->object_to_world); - - ld->conf.cam_is_persp = (c->type == CAM_PERSP); - ld->conf.near_clip = c->clip_start + clipping_offset; - ld->conf.far_clip = c->clip_end - clipping_offset; - ld->w = scene->r.xsch; - ld->h = scene->r.ysch; - - if (ld->conf.cam_is_persp) { - ld->qtree.recursive_level = LRT_TILE_RECURSIVE_PERSPECTIVE; - } - else { - ld->qtree.recursive_level = LRT_TILE_RECURSIVE_ORTHO; - } - - double asp = ((double)ld->w / (double)ld->h); - int fit = BKE_camera_sensor_fit(c->sensor_fit, ld->w, ld->h); - ld->conf.shift_x = fit == CAMERA_SENSOR_FIT_HOR ? c->shiftx : c->shiftx / asp; - ld->conf.shift_y = fit == CAMERA_SENSOR_FIT_VERT ? c->shifty : c->shifty * asp; - - ld->conf.overscan = lmd->overscan; - - 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->object_to_world[3]); - copy_m4_m4(ld->conf.cam_obmat_secondary, light_obj->object_to_world); - 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; - ld->conf.chain_smooth_tolerance = lmd->chain_smooth_tolerance; - - ld->conf.fuzzy_intersections = (lmd->calculation_flags & LRT_INTERSECTION_AS_CONTOUR) != 0; - ld->conf.fuzzy_everything = (lmd->calculation_flags & LRT_EVERYTHING_AS_CONTOUR) != 0; - ld->conf.allow_boundaries = (lmd->calculation_flags & LRT_ALLOW_CLIPPING_BOUNDARIES) != 0; - ld->conf.use_loose_as_contour = (lmd->calculation_flags & LRT_LOOSE_AS_CONTOUR) != 0; - ld->conf.use_loose_edge_chain = (lmd->calculation_flags & LRT_CHAIN_LOOSE_EDGES) != 0; - ld->conf.use_geometry_space_chain = (lmd->calculation_flags & LRT_CHAIN_GEOMETRY_SPACE) != 0; - ld->conf.use_image_boundary_trimming = (lmd->calculation_flags & - LRT_USE_IMAGE_BOUNDARY_TRIMMING) != 0; - - /* See lineart_edge_from_triangle() for how this option may impact performance. */ - ld->conf.allow_overlapping_edges = (lmd->calculation_flags & LRT_ALLOW_OVERLAPPING_EDGES) != 0; - - ld->conf.allow_duplicated_types = (lmd->calculation_flags & LRT_ALLOW_OVERLAP_EDGE_TYPES) != 0; - - ld->conf.force_crease = (lmd->calculation_flags & LRT_USE_CREASE_ON_SMOOTH_SURFACES) != 0; - ld->conf.sharp_as_crease = (lmd->calculation_flags & LRT_USE_CREASE_ON_SHARP_EDGES) != 0; - - ld->conf.chain_preserve_details = (lmd->calculation_flags & LRT_CHAIN_PRESERVE_DETAILS) != 0; - - /* This is used to limit calculation to a certain level to save time, lines who have higher - * occlusion levels will get ignored. */ - ld->conf.max_occlusion_level = lmd->level_end_override; - - 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->shadow_selection_override == - LRT_SHADOW_FILTER_ILLUMINATED_ENCLOSED_SHAPES; - 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; - ld->conf.filter_face_mark_boundaries = (lmd->calculation_flags & - LRT_FILTER_FACE_MARK_BOUNDARIES) != 0; - ld->conf.filter_face_mark_keep_contour = (lmd->calculation_flags & - LRT_FILTER_FACE_MARK_KEEP_CONTOUR) != 0; - - ld->chain_data_pool = &lc->chain_data_pool; - - /* See #LineartData::edge_data_pool for explanation. */ - 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); - - ld->thread_count = BKE_render_num_threads(&scene->r); - - return ld; -} - -static int lineart_triangle_size_get(LineartData *ld) -{ - return sizeof(LineartTriangle) + (sizeof(LineartEdge *) * (ld->thread_count)); -} - -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 - * efficient at handling medium to small scenes. */ - int sp_w = LRT_BA_ROWS; - int sp_h = LRT_BA_ROWS; - int row, col; - LineartBoundingArea *ba; - - /* Always make sure the shortest side has at least LRT_BA_ROWS tiles. */ - if (ld->w > ld->h) { - sp_w = sp_h * ld->w / ld->h; - } - else { - sp_h = sp_w * ld->h / ld->w; - } - - /* Because NDC (Normalized Device Coordinates) range is (-1,1), - * so the span for each initial tile is double of that in the (0,1) range. */ - double span_w = (double)1 / sp_w * 2.0; - double span_h = (double)1 / sp_h * 2.0; - - ld->qtree.count_x = sp_w; - ld->qtree.count_y = sp_h; - ld->qtree.tile_width = span_w; - ld->qtree.tile_height = span_h; - - 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++) { - for (col = 0; col < sp_w; col++) { - ba = &ld->qtree.initials[row * ld->qtree.count_x + col]; - - /* Set the four direction limits. */ - ba->l = span_w * col - 1.0; - ba->r = (col == sp_w - 1) ? 1.0 : (span_w * (col + 1) - 1.0); - ba->u = 1.0 - span_h * row; - ba->b = (row == sp_h - 1) ? -1.0 : (1.0 - span_h * (row + 1)); - - ba->cx = (ba->l + ba->r) / 2; - ba->cy = (ba->u + ba->b) / 2; - - /* Init linked_triangles array. */ - ba->max_triangle_count = LRT_TILE_SPLITTING_TRIANGLE_LIMIT; - ba->max_line_count = LRT_TILE_EDGE_COUNT_INITIAL; - ba->linked_triangles = MEM_callocN(sizeof(LineartTriangle *) * ba->max_triangle_count, - "ba_linked_triangles"); - ba->linked_lines = MEM_callocN(sizeof(LineartEdge *) * ba->max_line_count, - "ba_linked_lines"); - - BLI_spin_init(&ba->lock); - } - } -} - -/** - * Re-link adjacent tiles after one gets subdivided. - */ -static void lineart_bounding_areas_connect_new(LineartData *ld, LineartBoundingArea *root) -{ - LineartBoundingArea *ba = root->child, *tba; - LinkData *lip2, *next_lip; - LineartStaticMemPool *mph = &ld->render_data_pool; - - /* Inter-connection with newly created 4 child bounding areas. */ - lineart_list_append_pointer_pool(&ba[1].rp, mph, &ba[0]); - lineart_list_append_pointer_pool(&ba[0].lp, mph, &ba[1]); - lineart_list_append_pointer_pool(&ba[1].bp, mph, &ba[2]); - lineart_list_append_pointer_pool(&ba[2].up, mph, &ba[1]); - lineart_list_append_pointer_pool(&ba[2].rp, mph, &ba[3]); - lineart_list_append_pointer_pool(&ba[3].lp, mph, &ba[2]); - lineart_list_append_pointer_pool(&ba[3].up, mph, &ba[0]); - lineart_list_append_pointer_pool(&ba[0].bp, mph, &ba[3]); - - /* Connect 4 child bounding areas to other areas that are - * adjacent to their original parents. */ - LISTBASE_FOREACH (LinkData *, lip, &root->lp) { - - /* For example, we are dealing with parent's left side - * "tba" represents each adjacent neighbor of the parent. */ - tba = lip->data; - - /* if this neighbor is adjacent to - * the two new areas on the left side of the parent, - * then add them to the adjacent list as well. */ - if (ba[1].u > tba->b && ba[1].b < tba->u) { - lineart_list_append_pointer_pool(&ba[1].lp, mph, tba); - lineart_list_append_pointer_pool(&tba->rp, mph, &ba[1]); - } - if (ba[2].u > tba->b && ba[2].b < tba->u) { - lineart_list_append_pointer_pool(&ba[2].lp, mph, tba); - lineart_list_append_pointer_pool(&tba->rp, mph, &ba[2]); - } - } - LISTBASE_FOREACH (LinkData *, lip, &root->rp) { - tba = lip->data; - if (ba[0].u > tba->b && ba[0].b < tba->u) { - lineart_list_append_pointer_pool(&ba[0].rp, mph, tba); - lineart_list_append_pointer_pool(&tba->lp, mph, &ba[0]); - } - if (ba[3].u > tba->b && ba[3].b < tba->u) { - lineart_list_append_pointer_pool(&ba[3].rp, mph, tba); - lineart_list_append_pointer_pool(&tba->lp, mph, &ba[3]); - } - } - LISTBASE_FOREACH (LinkData *, lip, &root->up) { - tba = lip->data; - if (ba[0].r > tba->l && ba[0].l < tba->r) { - lineart_list_append_pointer_pool(&ba[0].up, mph, tba); - lineart_list_append_pointer_pool(&tba->bp, mph, &ba[0]); - } - if (ba[1].r > tba->l && ba[1].l < tba->r) { - lineart_list_append_pointer_pool(&ba[1].up, mph, tba); - lineart_list_append_pointer_pool(&tba->bp, mph, &ba[1]); - } - } - LISTBASE_FOREACH (LinkData *, lip, &root->bp) { - tba = lip->data; - if (ba[2].r > tba->l && ba[2].l < tba->r) { - lineart_list_append_pointer_pool(&ba[2].bp, mph, tba); - lineart_list_append_pointer_pool(&tba->up, mph, &ba[2]); - } - if (ba[3].r > tba->l && ba[3].l < tba->r) { - lineart_list_append_pointer_pool(&ba[3].bp, mph, tba); - lineart_list_append_pointer_pool(&tba->up, mph, &ba[3]); - } - } - - /* Then remove the parent bounding areas from - * their original adjacent areas. */ - LISTBASE_FOREACH (LinkData *, lip, &root->lp) { - for (lip2 = ((LineartBoundingArea *)lip->data)->rp.first; lip2; lip2 = next_lip) { - next_lip = lip2->next; - tba = lip2->data; - if (tba == root) { - lineart_list_remove_pointer_item_no_free(&((LineartBoundingArea *)lip->data)->rp, lip2); - if (ba[1].u > tba->b && ba[1].b < tba->u) { - lineart_list_append_pointer_pool(&tba->rp, mph, &ba[1]); - } - if (ba[2].u > tba->b && ba[2].b < tba->u) { - lineart_list_append_pointer_pool(&tba->rp, mph, &ba[2]); - } - } - } - } - LISTBASE_FOREACH (LinkData *, lip, &root->rp) { - for (lip2 = ((LineartBoundingArea *)lip->data)->lp.first; lip2; lip2 = next_lip) { - next_lip = lip2->next; - tba = lip2->data; - if (tba == root) { - lineart_list_remove_pointer_item_no_free(&((LineartBoundingArea *)lip->data)->lp, lip2); - if (ba[0].u > tba->b && ba[0].b < tba->u) { - lineart_list_append_pointer_pool(&tba->lp, mph, &ba[0]); - } - if (ba[3].u > tba->b && ba[3].b < tba->u) { - lineart_list_append_pointer_pool(&tba->lp, mph, &ba[3]); - } - } - } - } - LISTBASE_FOREACH (LinkData *, lip, &root->up) { - for (lip2 = ((LineartBoundingArea *)lip->data)->bp.first; lip2; lip2 = next_lip) { - next_lip = lip2->next; - tba = lip2->data; - if (tba == root) { - lineart_list_remove_pointer_item_no_free(&((LineartBoundingArea *)lip->data)->bp, lip2); - if (ba[0].r > tba->l && ba[0].l < tba->r) { - lineart_list_append_pointer_pool(&tba->up, mph, &ba[0]); - } - if (ba[1].r > tba->l && ba[1].l < tba->r) { - lineart_list_append_pointer_pool(&tba->up, mph, &ba[1]); - } - } - } - } - LISTBASE_FOREACH (LinkData *, lip, &root->bp) { - for (lip2 = ((LineartBoundingArea *)lip->data)->up.first; lip2; lip2 = next_lip) { - next_lip = lip2->next; - tba = lip2->data; - if (tba == root) { - lineart_list_remove_pointer_item_no_free(&((LineartBoundingArea *)lip->data)->up, lip2); - if (ba[2].r > tba->l && ba[2].l < tba->r) { - lineart_list_append_pointer_pool(&tba->bp, mph, &ba[2]); - } - if (ba[3].r > tba->l && ba[3].l < tba->r) { - lineart_list_append_pointer_pool(&tba->bp, mph, &ba[3]); - } - } - } - } - - /* Finally clear parent's adjacent list. */ - BLI_listbase_clear(&root->lp); - BLI_listbase_clear(&root->rp); - BLI_listbase_clear(&root->up); - BLI_listbase_clear(&root->bp); -} - -static void lineart_bounding_areas_connect_recursive(LineartData *ld, LineartBoundingArea *root) -{ - if (root->child) { - lineart_bounding_areas_connect_new(ld, root); - for (int i = 0; i < 4; i++) { - lineart_bounding_areas_connect_recursive(ld, &root->child[i]); - } - } -} - -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; - - for (int row = 0; row < ld->qtree.count_y; row++) { - for (int col = 0; col < ld->qtree.count_x; col++) { - LineartBoundingArea *ba = &ld->qtree.initials[row * tiles_per_row + col]; - /* Link adjacent ones. */ - if (row) { - lineart_list_append_pointer_pool( - &ba->up, &ld->render_data_pool, &ld->qtree.initials[(row - 1) * tiles_per_row + col]); - } - if (col) { - lineart_list_append_pointer_pool( - &ba->lp, &ld->render_data_pool, &ld->qtree.initials[row * tiles_per_row + col - 1]); - } - if (row != ld->qtree.count_y - 1) { - lineart_list_append_pointer_pool( - &ba->bp, &ld->render_data_pool, &ld->qtree.initials[(row + 1) * tiles_per_row + col]); - } - if (col != ld->qtree.count_x - 1) { - lineart_list_append_pointer_pool( - &ba->rp, &ld->render_data_pool, &ld->qtree.initials[row * tiles_per_row + col + 1]); - } - } - } - for (int i = 0; i < total_tile_initial; i++) { - lineart_bounding_areas_connect_recursive(ld, &ld->qtree.initials[i]); - } -} - -/** - * Subdivide a tile after one tile contains too many triangles, then re-link triangles into all the - * child tiles. - */ -static void lineart_bounding_area_split(LineartData *ld, - LineartBoundingArea *root, - int recursive_level) -{ - - LineartBoundingArea *ba = lineart_mem_acquire_thread(&ld->render_data_pool, - sizeof(LineartBoundingArea) * 4); - ba[0].l = root->cx; - ba[0].r = root->r; - ba[0].u = root->u; - ba[0].b = root->cy; - ba[0].cx = (ba[0].l + ba[0].r) / 2; - ba[0].cy = (ba[0].u + ba[0].b) / 2; - - ba[1].l = root->l; - ba[1].r = root->cx; - ba[1].u = root->u; - ba[1].b = root->cy; - ba[1].cx = (ba[1].l + ba[1].r) / 2; - ba[1].cy = (ba[1].u + ba[1].b) / 2; - - ba[2].l = root->l; - ba[2].r = root->cx; - ba[2].u = root->cy; - ba[2].b = root->b; - ba[2].cx = (ba[2].l + ba[2].r) / 2; - ba[2].cy = (ba[2].u + ba[2].b) / 2; - - ba[3].l = root->cx; - ba[3].r = root->r; - ba[3].u = root->cy; - ba[3].b = root->b; - ba[3].cx = (ba[3].l + ba[3].r) / 2; - ba[3].cy = (ba[3].u + ba[3].b) / 2; - - /* Init linked_triangles array and locks. */ - for (int i = 0; i < 4; i++) { - ba[i].max_triangle_count = LRT_TILE_SPLITTING_TRIANGLE_LIMIT; - ba[i].max_line_count = LRT_TILE_EDGE_COUNT_INITIAL; - ba[i].linked_triangles = MEM_callocN(sizeof(LineartTriangle *) * ba[i].max_triangle_count, - "ba_linked_triangles"); - ba[i].linked_lines = MEM_callocN(sizeof(LineartEdge *) * ba[i].max_line_count, - "ba_linked_lines"); - BLI_spin_init(&ba[i].lock); - } - - for (uint32_t i = 0; i < root->triangle_count; i++) { - LineartTriangle *tri = root->linked_triangles[i]; - - double b[4]; - 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]); - b[3] = MIN3(tri->v[0]->fbcoord[1], tri->v[1]->fbcoord[1], tri->v[2]->fbcoord[1]); - - /* Re-link triangles into child tiles, not doing intersection lines during this because this - * batch of triangles are all tested with each other for intersections. */ - if (LRT_BOUND_AREA_CROSSES(b, &ba[0].l)) { - lineart_bounding_area_link_triangle(ld, &ba[0], tri, b, 0, recursive_level + 1, false, NULL); - } - if (LRT_BOUND_AREA_CROSSES(b, &ba[1].l)) { - lineart_bounding_area_link_triangle(ld, &ba[1], tri, b, 0, recursive_level + 1, false, NULL); - } - if (LRT_BOUND_AREA_CROSSES(b, &ba[2].l)) { - lineart_bounding_area_link_triangle(ld, &ba[2], tri, b, 0, recursive_level + 1, false, NULL); - } - if (LRT_BOUND_AREA_CROSSES(b, &ba[3].l)) { - lineart_bounding_area_link_triangle(ld, &ba[3], tri, b, 0, recursive_level + 1, false, NULL); - } - } - - /* 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; -} - -static bool lineart_bounding_area_edge_intersect(LineartData *UNUSED(fb), - const double l[2], - const double r[2], - LineartBoundingArea *ba) -{ - double dx, dy; - double converted[4]; - double c1, c; - - if (((converted[0] = ba->l) > MAX2(l[0], r[0])) || ((converted[1] = ba->r) < MIN2(l[0], r[0])) || - ((converted[2] = ba->b) > MAX2(l[1], r[1])) || ((converted[3] = ba->u) < MIN2(l[1], r[1]))) { - return false; - } - - dx = l[0] - r[0]; - dy = l[1] - r[1]; - - c1 = dx * (converted[2] - l[1]) - dy * (converted[0] - l[0]); - c = c1; - - c1 = dx * (converted[2] - l[1]) - dy * (converted[1] - l[0]); - if (c1 * c <= 0) { - return true; - } - c = c1; - - c1 = dx * (converted[3] - l[1]) - dy * (converted[0] - l[0]); - if (c1 * c <= 0) { - return true; - } - c = c1; - - c1 = dx * (converted[3] - l[1]) - dy * (converted[1] - l[0]); - if (c1 * c <= 0) { - return true; - } - c = c1; - - return false; -} - -static bool lineart_bounding_area_triangle_intersect(LineartData *fb, - LineartTriangle *tri, - LineartBoundingArea *ba, - bool *r_triangle_vert_inside) -{ - double p1[2], p2[2], p3[2], p4[2]; - double *FBC1 = tri->v[0]->fbcoord, *FBC2 = tri->v[1]->fbcoord, *FBC3 = tri->v[2]->fbcoord; - - p3[0] = p1[0] = ba->l; - p2[1] = p1[1] = ba->b; - p2[0] = p4[0] = ba->r; - p3[1] = p4[1] = ba->u; - - if ((FBC1[0] >= p1[0] && FBC1[0] <= p2[0] && FBC1[1] >= p1[1] && FBC1[1] <= p3[1]) || - (FBC2[0] >= p1[0] && FBC2[0] <= p2[0] && FBC2[1] >= p1[1] && FBC2[1] <= p3[1]) || - (FBC3[0] >= p1[0] && FBC3[0] <= p2[0] && FBC3[1] >= p1[1] && FBC3[1] <= p3[1])) { - *r_triangle_vert_inside = true; - return true; - } - - *r_triangle_vert_inside = false; - - if (lineart_point_inside_triangle(p1, FBC1, FBC2, FBC3) || - lineart_point_inside_triangle(p2, FBC1, FBC2, FBC3) || - lineart_point_inside_triangle(p3, FBC1, FBC2, FBC3) || - lineart_point_inside_triangle(p4, FBC1, FBC2, FBC3)) { - return true; - } - - if (lineart_bounding_area_edge_intersect(fb, FBC1, FBC2, ba) || - lineart_bounding_area_edge_intersect(fb, FBC2, FBC3, ba) || - lineart_bounding_area_edge_intersect(fb, FBC3, FBC1, ba)) { - return true; - } - - return false; -} - -/** - * This function does two things: - * - * 1) Builds a quad-tree under ld->InitialBoundingAreas to achieve good geometry separation for - * fast overlapping test between triangles and lines. This acceleration structure makes the - * occlusion stage much faster. - * - * 2) Test triangles with other triangles that are previously linked into each tile - * (#LineartBoundingArea) for intersection lines. When splitting the tile into 4 children and - * re-linking triangles into the child tiles, intersections are inhibited so we don't get - * duplicated intersection lines. - */ -static void lineart_bounding_area_link_triangle(LineartData *ld, - LineartBoundingArea *root_ba, - LineartTriangle *tri, - double l_r_u_b[4], - int recursive, - int recursive_level, - bool do_intersection, - struct LineartIsecThread *th) -{ - bool triangle_vert_inside; - if (!lineart_bounding_area_triangle_intersect(ld, tri, root_ba, &triangle_vert_inside)) { - return; - } - - LineartBoundingArea *old_ba = root_ba; - - 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 = l_r_u_b; - double b[4]; - 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]); - b[3] = MIN3(tri->v[0]->fbcoord[1], tri->v[1]->fbcoord[1], tri->v[2]->fbcoord[1]); - B1 = b; - } - for (int iba = 0; iba < 4; iba++) { - if (LRT_BOUND_AREA_CROSSES(B1, &old_ba->child[iba].l)) { - lineart_bounding_area_link_triangle( - ld, &old_ba->child[iba], tri, B1, recursive, recursive_level + 1, do_intersection, th); - } - } - return; - } - - /* When splitting tiles, triangles are relinked into new tiles by a single thread, #th is NULL - * in that situation. */ - if (th) { - BLI_spin_lock(&old_ba->lock); - } - - /* If there are still space left in this tile for insertion. */ - if (old_ba->triangle_count < old_ba->max_triangle_count) { - const uint32_t old_tri_count = old_ba->triangle_count; - - old_ba->linked_triangles[old_tri_count] = tri; - - if (triangle_vert_inside) { - old_ba->insider_triangle_count++; - } - old_ba->triangle_count++; - - /* Do intersections in place. */ - if (do_intersection && ld->conf.use_intersections) { - lineart_triangle_intersect_in_bounding_area(tri, old_ba, th, old_tri_count); - } - - if (th) { - BLI_spin_unlock(&old_ba->lock); - } - } - else { /* We need to wait for either splitting or array extension to be done. */ - - if (recursive_level < ld->qtree.recursive_level && - old_ba->insider_triangle_count >= LRT_TILE_SPLITTING_TRIANGLE_LIMIT) { - if (!old_ba->child) { - /* old_ba->child==NULL, means we are the thread that's doing the splitting. */ - lineart_bounding_area_split(ld, old_ba, recursive_level); - } /* Otherwise other thread has completed the splitting process. */ - } - else { - if (old_ba->triangle_count == old_ba->max_triangle_count) { - /* Means we are the thread that's doing the extension. */ - lineart_bounding_area_triangle_reallocate(old_ba); - } /* Otherwise other thread has completed the extending the array. */ - } - - /* Unlock before going into recursive call. */ - if (th) { - BLI_spin_unlock(&old_ba->lock); - } - - /* Of course we still have our own triangle needs to be added. */ - lineart_bounding_area_link_triangle( - ld, root_ba, tri, l_r_u_b, recursive, recursive_level, do_intersection, th); - } -} - -static void lineart_free_bounding_area_memory(LineartBoundingArea *ba, bool recursive) -{ - BLI_spin_end(&ba->lock); - if (ba->linked_lines) { - MEM_freeN(ba->linked_lines); - } - if (ba->linked_triangles) { - MEM_freeN(ba->linked_triangles); - } - if (recursive && ba->child) { - for (int i = 0; i < 4; i++) { - lineart_free_bounding_area_memory(&ba->child[i], recursive); - } - } -} -static void lineart_free_bounding_area_memories(LineartData *ld) -{ - for (int i = 0; i < ld->qtree.count_y; i++) { - for (int j = 0; j < ld->qtree.count_x; j++) { - lineart_free_bounding_area_memory(&ld->qtree.initials[i * ld->qtree.count_x + j], true); - } - } -} - -static void lineart_bounding_area_link_edge(LineartData *ld, - LineartBoundingArea *root_ba, - LineartEdge *e) -{ - if (root_ba->child == NULL) { - lineart_bounding_area_line_add(root_ba, e); - } - else { - if (lineart_bounding_area_edge_intersect( - ld, e->v1->fbcoord, e->v2->fbcoord, &root_ba->child[0])) { - lineart_bounding_area_link_edge(ld, &root_ba->child[0], e); - } - if (lineart_bounding_area_edge_intersect( - ld, e->v1->fbcoord, e->v2->fbcoord, &root_ba->child[1])) { - lineart_bounding_area_link_edge(ld, &root_ba->child[1], e); - } - if (lineart_bounding_area_edge_intersect( - ld, e->v1->fbcoord, e->v2->fbcoord, &root_ba->child[2])) { - lineart_bounding_area_link_edge(ld, &root_ba->child[2], e); - } - if (lineart_bounding_area_edge_intersect( - ld, e->v1->fbcoord, e->v2->fbcoord, &root_ba->child[3])) { - lineart_bounding_area_link_edge(ld, &root_ba->child[3], e); - } - } -} - -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. - */ -void lineart_main_link_lines(LineartData *ld) -{ - LRT_ITER_ALL_LINES_BEGIN - { - int r1, r2, c1, c2, row, col; - if (lineart_get_edge_bounding_areas(ld, e, &r1, &r2, &c1, &c2)) { - for (row = r1; row != r2 + 1; row++) { - for (col = c1; col != c2 + 1; col++) { - lineart_bounding_area_link_edge( - ld, &ld->qtree.initials[row * ld->qtree.count_x + col], e); - } - } - } - } - LRT_ITER_ALL_LINES_END -} - -static void lineart_main_remove_unused_lines_recursive(LineartBoundingArea *ba, - uint8_t max_occlusion) -{ - if (ba->child) { - for (int i = 0; i < 4; i++) { - lineart_main_remove_unused_lines_recursive(&ba->child[i], max_occlusion); - } - return; - } - - if (!ba->line_count) { - return; - } - - int usable_count = 0; - for (int i = 0; i < ba->line_count; i++) { - LineartEdge *e = ba->linked_lines[i]; - if (e->min_occ > max_occlusion) { - continue; - } - usable_count++; - } - - if (!usable_count) { - ba->line_count = 0; - return; - } - - LineartEdge **new_array = MEM_callocN(sizeof(LineartEdge *) * usable_count, - "cleaned lineart edge array"); - - int new_i = 0; - for (int i = 0; i < ba->line_count; i++) { - LineartEdge *e = ba->linked_lines[i]; - if (e->min_occ > max_occlusion) { - continue; - } - new_array[new_i] = e; - new_i++; - } - - MEM_freeN(ba->linked_lines); - ba->linked_lines = new_array; - ba->max_line_count = ba->line_count = usable_count; -} - -static void lineart_main_remove_unused_lines_from_tiles(LineartData *ld) -{ - for (int row = 0; row < ld->qtree.count_y; row++) { - for (int col = 0; col < ld->qtree.count_x; col++) { - lineart_main_remove_unused_lines_recursive( - &ld->qtree.initials[row * ld->qtree.count_x + col], ld->conf.max_occlusion_level); - } - } -} - -static bool lineart_get_triangle_bounding_areas( - LineartData *ld, LineartTriangle *tri, int *rowbegin, int *rowend, int *colbegin, int *colend) -{ - double sp_w = ld->qtree.tile_width, sp_h = ld->qtree.tile_height; - double b[4]; - - if (!tri->v[0] || !tri->v[1] || !tri->v[2]) { - return false; - } - - 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] = MIN3(tri->v[0]->fbcoord[1], tri->v[1]->fbcoord[1], tri->v[2]->fbcoord[1]); - b[3] = MAX3(tri->v[0]->fbcoord[1], tri->v[1]->fbcoord[1], tri->v[2]->fbcoord[1]); - - if (b[0] > 1 || b[1] < -1 || b[2] > 1 || b[3] < -1) { - return false; - } - - (*colbegin) = (int)((b[0] + 1.0) / sp_w); - (*colend) = (int)((b[1] + 1.0) / sp_w); - (*rowend) = ld->qtree.count_y - (int)((b[2] + 1.0) / sp_h) - 1; - (*rowbegin) = ld->qtree.count_y - (int)((b[3] + 1.0) / sp_h) - 1; - - if ((*colend) >= ld->qtree.count_x) { - (*colend) = ld->qtree.count_x - 1; - } - if ((*rowend) >= ld->qtree.count_y) { - (*rowend) = ld->qtree.count_y - 1; - } - if ((*colbegin) < 0) { - (*colbegin) = 0; - } - if ((*rowbegin) < 0) { - (*rowbegin) = 0; - } - - return true; -} - -static bool lineart_get_edge_bounding_areas( - LineartData *ld, LineartEdge *e, int *rowbegin, int *rowend, int *colbegin, int *colend) -{ - double sp_w = ld->qtree.tile_width, sp_h = ld->qtree.tile_height; - double b[4]; - - if (!e->v1 || !e->v2) { - return false; - } - - if (e->v1->fbcoord[0] != e->v1->fbcoord[0] || e->v2->fbcoord[0] != e->v2->fbcoord[0]) { - return false; - } - - b[0] = MIN2(e->v1->fbcoord[0], e->v2->fbcoord[0]); - b[1] = MAX2(e->v1->fbcoord[0], e->v2->fbcoord[0]); - b[2] = MIN2(e->v1->fbcoord[1], e->v2->fbcoord[1]); - b[3] = MAX2(e->v1->fbcoord[1], e->v2->fbcoord[1]); - - if (b[0] > 1 || b[1] < -1 || b[2] > 1 || b[3] < -1) { - return false; - } - - (*colbegin) = (int)((b[0] + 1.0) / sp_w); - (*colend) = (int)((b[1] + 1.0) / sp_w); - (*rowend) = ld->qtree.count_y - (int)((b[2] + 1.0) / sp_h) - 1; - (*rowbegin) = ld->qtree.count_y - (int)((b[3] + 1.0) / sp_h) - 1; - - /* It's possible that the line stretches too much out to the side, resulting negative value. */ - if ((*rowend) < (*rowbegin)) { - (*rowend) = ld->qtree.count_y - 1; - } - - if ((*colend) < (*colbegin)) { - (*colend) = ld->qtree.count_x - 1; - } - - CLAMP((*colbegin), 0, ld->qtree.count_x - 1); - CLAMP((*rowbegin), 0, ld->qtree.count_y - 1); - CLAMP((*colend), 0, ld->qtree.count_x - 1); - CLAMP((*rowend), 0, ld->qtree.count_y - 1); - - return true; -} - -LineartBoundingArea *MOD_lineart_get_parent_bounding_area(LineartData *ld, double x, double y) -{ - double sp_w = ld->qtree.tile_width, sp_h = ld->qtree.tile_height; - int col, row; - - if (x > 1 || x < -1 || y > 1 || y < -1) { - return 0; - } - - col = (int)((x + 1.0) / sp_w); - row = ld->qtree.count_y - (int)((y + 1.0) / sp_h) - 1; - - if (col >= ld->qtree.count_x) { - col = ld->qtree.count_x - 1; - } - if (row >= ld->qtree.count_y) { - row = ld->qtree.count_y - 1; - } - if (col < 0) { - col = 0; - } - if (row < 0) { - row = 0; - } - - return &ld->qtree.initials[row * ld->qtree.count_x + col]; -} - -static LineartBoundingArea *lineart_get_bounding_area(LineartData *ld, double x, double y) -{ - LineartBoundingArea *iba; - double sp_w = ld->qtree.tile_width, sp_h = ld->qtree.tile_height; - int c = (int)((x + 1.0) / sp_w); - int r = ld->qtree.count_y - (int)((y + 1.0) / sp_h) - 1; - if (r < 0) { - r = 0; - } - if (c < 0) { - c = 0; - } - if (r >= ld->qtree.count_y) { - r = ld->qtree.count_y - 1; - } - if (c >= ld->qtree.count_x) { - c = ld->qtree.count_x - 1; - } - - iba = &ld->qtree.initials[r * ld->qtree.count_x + c]; - while (iba->child) { - if (x > iba->cx) { - if (y > iba->cy) { - iba = &iba->child[0]; - } - else { - iba = &iba->child[3]; - } - } - else { - if (y > iba->cy) { - iba = &iba->child[1]; - } - else { - iba = &iba->child[2]; - } - } - } - return iba; -} - -LineartBoundingArea *MOD_lineart_get_bounding_area(LineartData *ld, double x, double y) -{ - LineartBoundingArea *ba; - if ((ba = MOD_lineart_get_parent_bounding_area(ld, x, y)) != NULL) { - return lineart_get_bounding_area(ld, x, y); - } - return NULL; -} - -static void lineart_add_triangles_worker(TaskPool *__restrict UNUSED(pool), LineartIsecThread *th) -{ - LineartData *ld = th->ld; - int _dir_control = 0; - while (lineart_schedule_new_triangle_task(th)) { - for (LineartElementLinkNode *eln = th->pending_from; eln != th->pending_to->next; - eln = eln->next) { - int index_start = eln == th->pending_from ? th->index_from : 0; - int index_end = eln == th->pending_to ? th->index_to : eln->element_count; - LineartTriangle *tri = (void *)(((uchar *)eln->pointer) + ld->sizeof_triangle * index_start); - for (int ei = index_start; ei < index_end; ei++) { - int x1, x2, y1, y2; - int r, co; - if ((tri->flags & LRT_CULL_USED) || (tri->flags & LRT_CULL_DISCARD)) { - tri = (void *)(((uchar *)tri) + ld->sizeof_triangle); - continue; - } - if (lineart_get_triangle_bounding_areas(ld, tri, &y1, &y2, &x1, &x2)) { - _dir_control++; - for (co = x1; co <= x2; co++) { - for (r = y1; r <= y2; r++) { - lineart_bounding_area_link_triangle( - ld, &ld->qtree.initials[r * ld->qtree.count_x + co], tri, 0, 1, 0, 1, th); - } - } - } /* Else throw away. */ - tri = (void *)(((uchar *)tri) + ld->sizeof_triangle); - } - } - } -} - -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]; - if (G.debug_value == 4000) { - printf("Thread %d isec generated %d lines.\n", i, th->current); - } - if (!th->current) { - continue; - } - total_lines += th->current; - } - - if (!total_lines) { - return; - } - - /* 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. */ - 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++) { - LineartIsecSingle *is = &th->array[j]; - LineartVert *v1 = v; - LineartVert *v2 = v + 1; - copy_v3_v3_db(v1->gloc, is->v1); - copy_v3_v3_db(v2->gloc, is->v2); - /* The intersection line has been generated only in geometry space, so we need to transform - * them as well. */ - mul_v4_m4v3_db(v1->fbcoord, ld->conf.view_projection, v1->gloc); - mul_v4_m4v3_db(v2->fbcoord, ld->conf.view_projection, v2->gloc); - mul_v3db_db(v1->fbcoord, (1 / v1->fbcoord[3])); - mul_v3db_db(v2->fbcoord, (1 / v2->fbcoord[3])); - - v1->fbcoord[0] -= ld->conf.shift_x * 2; - v1->fbcoord[1] -= ld->conf.shift_y * 2; - v2->fbcoord[0] -= ld->conf.shift_x * 2; - v2->fbcoord[1] -= ld->conf.shift_y * 2; - - /* This z transformation is not the same as the rest of the part, because the data don't go - * through normal perspective division calls in the pipeline, but this way the 3D result and - * occlusion on the generated line is correct, and we don't really use 2D for viewport stroke - * generation anyway. */ - v1->fbcoord[2] = ZMin * ZMax / (ZMax - fabs(v1->fbcoord[2]) * (ZMax - ZMin)); - v2->fbcoord[2] = ZMin * ZMax / (ZMax - fabs(v2->fbcoord[2]) * (ZMax - ZMin)); - e->v1 = v1; - 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 ambiguous if intersection lines comes from different objects. */ - e->object_ref = ob1; - } - } - - lineart_add_edge_to_array(&ld->pending_edges, e); - - v += 2; - e++; - es++; - } - } -} - -/** - * Sequentially add triangles into render buffer, intersection lines between those triangles will - * also be computed at the same time. - */ -void lineart_main_add_triangles(LineartData *ld) -{ - double t_start; - if (G.debug_value == 4000) { - t_start = PIL_check_seconds_timer(); - } - - /* Initialize per-thread data for thread task scheduling information and storing intersection - * results. */ - LineartIsecData d = {0}; - lineart_init_isec_thread(&d, ld, ld->thread_count); - - TaskPool *tp = BLI_task_pool_create(NULL, TASK_PRIORITY_HIGH); - for (int i = 0; i < ld->thread_count; i++) { - BLI_task_pool_push(tp, (TaskRunFunction)lineart_add_triangles_worker, &d.threads[i], 0, NULL); - } - BLI_task_pool_work_and_wait(tp); - BLI_task_pool_free(tp); - - if (ld->conf.use_intersections) { - lineart_create_edges_from_isec_data(&d); - } - - lineart_destroy_isec_thread(&d); - - if (G.debug_value == 4000) { - double t_elapsed = PIL_check_seconds_timer() - t_start; - printf("Line art intersection time: %f\n", t_elapsed); - } -} - -/** - * This function gets the tile for the point `e->v1`, and later use #lineart_bounding_area_next() - * to get next along the way. - */ -LineartBoundingArea *lineart_edge_first_bounding_area(LineartData *ld, - double *fbcoord1, - double *fbcoord2) -{ - 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; - - if (data[0] > -1 && data[0] < 1 && data[1] > -1 && data[1] < 1) { - return lineart_get_bounding_area(ld, data[0], data[1]); - } - - if (lineart_intersect_seg_seg(fbcoord1, fbcoord2, LU, RU, &sr, &p_unused) && sr < r && sr > 0) { - r = sr; - } - if (lineart_intersect_seg_seg(fbcoord1, fbcoord2, LB, RB, &sr, &p_unused) && sr < r && sr > 0) { - r = sr; - } - if (lineart_intersect_seg_seg(fbcoord1, fbcoord2, LB, LU, &sr, &p_unused) && sr < r && sr > 0) { - r = sr; - } - if (lineart_intersect_seg_seg(fbcoord1, fbcoord2, RB, RU, &sr, &p_unused) && sr < r && sr > 0) { - r = sr; - } - interp_v2_v2v2_db(data, fbcoord1, fbcoord2, r); - - return lineart_get_bounding_area(ld, data[0], data[1]); -} - -/** - * This march along one render line in image space and - * get the next bounding area the line is crossing. - */ -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; - LineartBoundingArea *ba; - - /* If we are marching towards the right. */ - if (positive_x > 0) { - rx = this->r; - ry = y + k * (rx - x); - - /* If we are marching towards the top. */ - if (positive_y > 0) { - uy = this->u; - ux = x + (uy - y) / k; - r1 = ratiod(fbcoord1[0], fbcoord2[0], rx); - r2 = ratiod(fbcoord1[0], fbcoord2[0], ux); - if (MIN2(r1, r2) > 1) { - return 0; - } - - /* We reached the right side before the top side. */ - if (r1 <= r2) { - LISTBASE_FOREACH (LinkData *, lip, &this->rp) { - ba = lip->data; - if (ba->u >= ry && ba->b < ry) { - *next_x = rx; - *next_y = ry; - return ba; - } - } - } - /* We reached the top side before the right side. */ - else { - LISTBASE_FOREACH (LinkData *, lip, &this->up) { - ba = lip->data; - if (ba->r >= ux && ba->l < ux) { - *next_x = ux; - *next_y = uy; - return ba; - } - } - } - } - /* If we are marching towards the bottom. */ - else if (positive_y < 0) { - by = this->b; - bx = x + (by - y) / k; - r1 = ratiod(fbcoord1[0], fbcoord2[0], rx); - r2 = ratiod(fbcoord1[0], fbcoord2[0], bx); - if (MIN2(r1, r2) > 1) { - return 0; - } - if (r1 <= r2) { - LISTBASE_FOREACH (LinkData *, lip, &this->rp) { - ba = lip->data; - if (ba->u >= ry && ba->b < ry) { - *next_x = rx; - *next_y = ry; - return ba; - } - } - } - else { - LISTBASE_FOREACH (LinkData *, lip, &this->bp) { - ba = lip->data; - if (ba->r >= bx && ba->l < bx) { - *next_x = bx; - *next_y = by; - return ba; - } - } - } - } - /* If the line is completely horizontal, in which Y difference == 0. */ - else { - r1 = ratiod(fbcoord1[0], fbcoord2[0], this->r); - if (r1 > 1) { - return 0; - } - LISTBASE_FOREACH (LinkData *, lip, &this->rp) { - ba = lip->data; - if (ba->u >= y && ba->b < y) { - *next_x = this->r; - *next_y = y; - return ba; - } - } - } - } - - /* If we are marching towards the left. */ - else if (positive_x < 0) { - lx = this->l; - ly = y + k * (lx - x); - - /* If we are marching towards the top. */ - if (positive_y > 0) { - uy = this->u; - ux = x + (uy - y) / k; - r1 = ratiod(fbcoord1[0], fbcoord2[0], lx); - r2 = ratiod(fbcoord1[0], fbcoord2[0], ux); - if (MIN2(r1, r2) > 1) { - return 0; - } - if (r1 <= r2) { - LISTBASE_FOREACH (LinkData *, lip, &this->lp) { - ba = lip->data; - if (ba->u >= ly && ba->b < ly) { - *next_x = lx; - *next_y = ly; - return ba; - } - } - } - else { - LISTBASE_FOREACH (LinkData *, lip, &this->up) { - ba = lip->data; - if (ba->r >= ux && ba->l < ux) { - *next_x = ux; - *next_y = uy; - return ba; - } - } - } - } - - /* If we are marching towards the bottom. */ - else if (positive_y < 0) { - by = this->b; - bx = x + (by - y) / k; - r1 = ratiod(fbcoord1[0], fbcoord2[0], lx); - r2 = ratiod(fbcoord1[0], fbcoord2[0], bx); - if (MIN2(r1, r2) > 1) { - return 0; - } - if (r1 <= r2) { - LISTBASE_FOREACH (LinkData *, lip, &this->lp) { - ba = lip->data; - if (ba->u >= ly && ba->b < ly) { - *next_x = lx; - *next_y = ly; - return ba; - } - } - } - else { - LISTBASE_FOREACH (LinkData *, lip, &this->bp) { - ba = lip->data; - if (ba->r >= bx && ba->l < bx) { - *next_x = bx; - *next_y = by; - return ba; - } - } - } - } - /* Again, horizontal. */ - else { - r1 = ratiod(fbcoord1[0], fbcoord2[0], this->l); - if (r1 > 1) { - return 0; - } - LISTBASE_FOREACH (LinkData *, lip, &this->lp) { - ba = lip->data; - if (ba->u >= y && ba->b < y) { - *next_x = this->l; - *next_y = y; - return ba; - } - } - } - } - /* If the line is completely vertical, hence X difference == 0. */ - else { - if (positive_y > 0) { - r1 = ratiod(fbcoord1[1], fbcoord2[1], this->u); - if (r1 > 1) { - return 0; - } - LISTBASE_FOREACH (LinkData *, lip, &this->up) { - ba = lip->data; - if (ba->r > x && ba->l <= x) { - *next_x = x; - *next_y = this->u; - return ba; - } - } - } - else if (positive_y < 0) { - r1 = ratiod(fbcoord1[1], fbcoord2[1], this->b); - if (r1 > 1) { - return 0; - } - LISTBASE_FOREACH (LinkData *, lip, &this->bp) { - ba = lip->data; - if (ba->r > x && ba->l <= x) { - *next_x = x; - *next_y = this->b; - return ba; - } - } - } - else { - /* Segment has no length. */ - return 0; - } - } - 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, - bool enable_stroke_depth_offset) -{ - LineartData *ld; - Scene *scene = DEG_get_evaluated_scene(depsgraph); - int intersections_only = 0; /* Not used right now, but preserve for future. */ - Object *use_camera; - - double t_start; - if (G.debug_value == 4000) { - t_start = PIL_check_seconds_timer(); - } - - if (lmd->calculation_flags & LRT_USE_CUSTOM_CAMERA) { - if (!lmd->source_camera || - (use_camera = DEG_get_evaluated_object(depsgraph, lmd->source_camera))->type != - OB_CAMERA) { - return false; - } - } - else { - - BKE_scene_camera_switch_update(scene); - - if (!scene->camera) { - return false; - } - use_camera = scene->camera; - } - - LineartCache *lc = lineart_init_cache(); - *cached_result = lc; - - ld = lineart_create_render_buffer(scene, lmd, use_camera, scene->camera, lc); - - /* Triangle thread testing data size varies depending on the thread count. - * 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, - 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. */ - return true; - } - - /* Initialize the bounding box acceleration structure, it's a lot like BVH in 3D. */ - lineart_main_bounding_area_make_initial(ld); - - /* We need to get cut into triangles that are crossing near/far plans, only this way can we get - * correct coordinates of those clipped lines. Done in two steps, - * setting clip_far==false for near plane. */ - lineart_main_cull_triangles(ld, false); - /* `clip_far == true` for far plane. */ - lineart_main_cull_triangles(ld, true); - - /* At this point triangle adjacent info pointers is no longer needed, free them. */ - lineart_main_free_adjacent_data(ld); - - /* Do the perspective division after clipping is done. */ - lineart_main_perspective_division(ld); - - lineart_main_discard_out_of_frame_edges(ld); - - /* Triangle intersections are done here during sequential adding of them. Only after this, - * triangles and lines are all linked with acceleration structure, and the 2D occlusion stage - * can do its job. */ - lineart_main_add_triangles(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); - - /* Link lines to acceleration structure, this can only be done after perspective division, if - * we do it after triangles being added, the acceleration structure has already been - * subdivided, this way we do less list manipulations. */ - lineart_main_link_lines(ld); - - /* "intersection_only" is preserved for being called in a standalone fashion. - * If so the data will already be available at the stage. Otherwise we do the occlusion and - * chaining etc. */ - - if (!intersections_only) { - - /* Occlusion is work-and-wait. This call will not return before work is completed. */ - lineart_main_occlusion_begin(ld); - - lineart_main_make_enclosed_shapes(ld, shadow_rb); - - lineart_main_remove_unused_lines_from_tiles(ld); - - /* Chaining is all single threaded. See lineart_chain.c - * In this particular call, only lines that are geometrically connected (share the _exact_ - * same end point) will be chained together. */ - MOD_lineart_chain_feature_lines(ld); - - /* We are unable to take care of occlusion if we only connect end points, so here we do a - * spit, where the splitting point could be any cut in e->segments. */ - MOD_lineart_chain_split_for_fixed_occlusion(ld); - - /* Then we connect chains based on the _proximity_ of their end points in image space, here's - * the place threshold value gets involved. */ - MOD_lineart_chain_connect(ld); - - if (ld->conf.chain_smooth_tolerance > FLT_EPSILON) { - /* Keeping UI range of 0-1 for ease of read while scaling down the actual value for best - * effective range in image-space (Coordinate only goes from -1 to 1). This value is - * somewhat arbitrary, but works best for the moment. */ - MOD_lineart_smooth_chains(ld, ld->conf.chain_smooth_tolerance / 50); - } - - if (ld->conf.use_image_boundary_trimming) { - MOD_lineart_chain_clip_at_border(ld); - } - - if (ld->conf.angle_splitting_threshold > FLT_EPSILON) { - MOD_lineart_chain_split_angle(ld, ld->conf.angle_splitting_threshold); - } - - if (enable_stroke_depth_offset && lmd->stroke_depth_offset > FLT_EPSILON) { - MOD_lineart_chain_offset_towards_camera( - 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)); - - /* At last, we need to clear flags so we don't confuse GPencil generation calls. */ - MOD_lineart_chain_clear_picked_flag(lc); - - MOD_lineart_finalize_chains(ld); - } - - 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); - - double t_elapsed = PIL_check_seconds_timer() - t_start; - printf("Line art total time: %lf\n", t_elapsed); - } - - return true; -} - -static void lineart_gpencil_generate(LineartCache *cache, - Depsgraph *depsgraph, - Object *gpencil_object, - float (*gp_obmat_inverse)[4], - bGPDlayer *UNUSED(gpl), - bGPDframe *gpf, - int level_start, - int level_end, - int material_nr, - Object *source_object, - Collection *source_collection, - int types, - uchar mask_switches, - uchar material_mask_bits, - uchar intersection_mask, - int16_t thickness, - float opacity, - uchar shaodow_selection, - uchar silhouette_mode, - const char *source_vgname, - const char *vgname, - int modifier_flags) -{ - if (cache == NULL) { - if (G.debug_value == 4000) { - printf("NULL Lineart cache!\n"); - } - return; - } - - int stroke_count = 0; - int color_idx = 0; - - Object *orig_ob = NULL; - if (source_object) { - orig_ob = source_object->id.orig_id ? (Object *)source_object->id.orig_id : source_object; - } - - Collection *orig_col = NULL; - if (source_collection) { - orig_col = source_collection->id.orig_id ? (Collection *)source_collection->id.orig_id : - source_collection; - } - - /* (!orig_col && !orig_ob) means the whole scene is selected. */ - - 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) { - - if (ec->picked) { - continue; - } - if (!(ec->type & (types & enabled_types))) { - continue; - } - if (ec->level > level_end || ec->level < level_start) { - continue; - } - if (orig_ob && orig_ob != ec->object_ref) { - continue; - } - if (orig_col && ec->object_ref) { - if (BKE_collection_has_object_recursive_instanced(orig_col, (Object *)ec->object_ref)) { - if (modifier_flags & LRT_GPENCIL_INVERT_COLLECTION) { - continue; - } - } - else { - if (!(modifier_flags & LRT_GPENCIL_INVERT_COLLECTION)) { - continue; - } - } - } - if (mask_switches & LRT_GPENCIL_MATERIAL_MASK_ENABLE) { - if (mask_switches & LRT_GPENCIL_MATERIAL_MASK_MATCH) { - if (ec->material_mask_bits != material_mask_bits) { - continue; - } - } - else { - if (!(ec->material_mask_bits & material_mask_bits)) { - continue; - } - } - } - if (ec->type & LRT_EDGE_FLAG_INTERSECTION) { - if (mask_switches & LRT_GPENCIL_INTERSECTION_MATCH) { - if (ec->intersection_mask != intersection_mask) { - continue; - } - } - else { - if ((intersection_mask) && !(ec->intersection_mask & intersection_mask)) { - continue; - } - } - } - if (shaodow_selection) { - if (ec->shadow_mask_bits != LRT_SHADOW_MASK_UNDEFINED) { - /* TODO(@Yiming): Give a behavior option for how to display undefined shadow info. */ - if (shaodow_selection == LRT_SHADOW_FILTER_ILLUMINATED && - !(ec->shadow_mask_bits & LRT_SHADOW_MASK_ILLUMINATED)) { - continue; - } - if (shaodow_selection == LRT_SHADOW_FILTER_SHADED && - !(ec->shadow_mask_bits & LRT_SHADOW_MASK_SHADED)) { - continue; - } - if (shaodow_selection == LRT_SHADOW_FILTER_ILLUMINATED_ENCLOSED_SHAPES) { - uint32_t test_bits = ec->shadow_mask_bits & LRT_SHADOW_TEST_SHAPE_BITS; - if ((test_bits != LRT_SHADOW_MASK_ILLUMINATED) && - (test_bits != (LRT_SHADOW_MASK_SHADED | LRT_SHADOW_MASK_ILLUMINATED_SHAPE))) { - 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; - LISTBASE_FOREACH_INDEX (LineartEdgeChainItem *, eci, &ec->chain, i) { - bGPDspoint *point = &gps->points[i]; - mul_v3_m4v3(&point->x, gp_obmat_inverse, eci->gpos); - point->pressure = 1.0f; - point->strength = opacity; - } - - BKE_gpencil_dvert_ensure(gps); - gps->mat_nr = max_ii(material_nr, 0); - - if (source_vgname && vgname) { - Object *eval_ob = DEG_get_evaluated_object(depsgraph, ec->object_ref); - int gpdg = -1; - if (match_output || (gpdg = BKE_object_defgroup_name_index(gpencil_object, vgname)) >= 0) { - if (eval_ob && eval_ob->type == OB_MESH) { - int dindex = 0; - Mesh *me = BKE_object_get_evaluated_mesh(eval_ob); - MDeformVert *dvert = BKE_mesh_deform_verts_for_write(me); - if (dvert) { - LISTBASE_FOREACH (bDeformGroup *, db, &me->vertex_group_names) { - if ((!source_vgname) || strstr(db->name, source_vgname) == db->name) { - if (match_output) { - gpdg = BKE_object_defgroup_name_index(gpencil_object, db->name); - if (gpdg < 0) { - continue; - } - } - int sindex = 0, vindex; - LISTBASE_FOREACH (LineartEdgeChainItem *, eci, &ec->chain) { - vindex = eci->index; - if (vindex >= me->totvert) { - break; - } - MDeformWeight *mdw = BKE_defvert_ensure_index(&dvert[vindex], dindex); - MDeformWeight *gdw = BKE_defvert_ensure_index(&gps->dvert[sindex], gpdg); - - float use_weight = mdw->weight; - if (invert_input) { - use_weight = 1 - use_weight; - } - gdw->weight = MAX2(use_weight, gdw->weight); - - sindex++; - } - } - dindex++; - } - } - } - } - } - - if (G.debug_value == 4000) { - BKE_gpencil_stroke_set_random_color(gps); - } - BKE_gpencil_stroke_geometry_update(gpencil_object->data, gps); - stroke_count++; - } - - if (G.debug_value == 4000) { - printf("LRT: Generated %d strokes.\n", stroke_count); - } -} - -void MOD_lineart_gpencil_generate(LineartCache *cache, - Depsgraph *depsgraph, - Object *ob, - bGPDlayer *gpl, - bGPDframe *gpf, - int8_t source_type, - void *source_reference, - int level_start, - int level_end, - int mat_nr, - int16_t edge_types, - uchar mask_switches, - uchar material_mask_bits, - uchar intersection_mask, - int16_t thickness, - float opacity, - uchar shadow_selection, - uchar silhouette_mode, - const char *source_vgname, - const char *vgname, - int modifier_flags) -{ - - if (!gpl || !gpf || !ob) { - return; - } - - Object *source_object = NULL; - Collection *source_collection = NULL; - int16_t use_types = edge_types; - if (source_type == LRT_SOURCE_OBJECT) { - if (!source_reference) { - return; - } - source_object = (Object *)source_reference; - } - else if (source_type == LRT_SOURCE_COLLECTION) { - if (!source_reference) { - return; - } - source_collection = (Collection *)source_reference; - } - - float gp_obmat_inverse[4][4]; - invert_m4_m4(gp_obmat_inverse, ob->object_to_world); - lineart_gpencil_generate(cache, - depsgraph, - ob, - gp_obmat_inverse, - gpl, - gpf, - level_start, - level_end, - mat_nr, - source_object, - source_collection, - use_types, - mask_switches, - material_mask_bits, - intersection_mask, - thickness, - opacity, - shadow_selection, - silhouette_mode, - source_vgname, - vgname, - modifier_flags); -} diff --git a/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.cc b/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.cc new file mode 100644 index 00000000000..b4025454022 --- /dev/null +++ b/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.cc @@ -0,0 +1,5458 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2019 Blender Foundation. All rights reserved. */ + +/* \file + * \ingroup editors + */ + +#include "MOD_gpencil_lineart.h" +#include "MOD_lineart.h" + +#include "BLI_edgehash.h" +#include "BLI_linklist.h" +#include "BLI_listbase.h" +#include "BLI_math.h" +#include "BLI_task.h" +#include "BLI_utildefines.h" + +#include "PIL_time.h" + +#include "BKE_camera.h" +#include "BKE_collection.h" +#include "BKE_customdata.h" +#include "BKE_deform.h" +#include "BKE_duplilist.h" +#include "BKE_editmesh.h" +#include "BKE_global.h" +#include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" +#include "BKE_gpencil_modifier.h" +#include "BKE_lib_id.h" +#include "BKE_material.h" +#include "BKE_mesh.h" +#include "BKE_mesh_mapping.h" +#include "BKE_mesh_runtime.h" +#include "BKE_object.h" +#include "BKE_pointcache.h" +#include "BKE_scene.h" +#include "DEG_depsgraph_query.h" +#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" +#include "DNA_modifier_types.h" +#include "DNA_scene_types.h" +#include "MEM_guardedalloc.h" + +#include "lineart_intern.h" + +struct LineartIsecSingle { + double v1[3], v2[3]; + LineartTriangle *tri1, *tri2; +}; + +struct LineartIsecThread { + int thread_id; + + /* Scheduled work range. */ + LineartElementLinkNode *pending_from; + LineartElementLinkNode *pending_to; + int index_from; + int index_to; + + /* Thread intersection result data. */ + LineartIsecSingle *array; + int current; + int max; + int count_test; + + /* For individual thread reference. */ + LineartData *ld; +}; + +struct LineartIsecData { + LineartData *ld; + LineartIsecThread *threads; + int thread_count; +}; + +static void lineart_bounding_area_link_edge(LineartData *ld, + LineartBoundingArea *root_ba, + LineartEdge *e); + +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(const LineartTriangle *tri, + const LineartEdge *e, + const double *override_camera_loc, + const bool override_cam_is_persp, + const bool allow_overlapping_edges, + 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_bounding_area_link_triangle(LineartData *ld, + LineartBoundingArea *root_ba, + LineartTriangle *tri, + double l_r_u_b[4], + int recursive, + int recursive_level, + bool do_intersection, + LineartIsecThread *th); + +static void lineart_free_bounding_area_memory(LineartBoundingArea *ba, bool recursive); + +static void lineart_free_bounding_area_memories(LineartData *ld); + +static LineartCache *lineart_init_cache(void); + +static void lineart_discard_segment(LineartData *ld, LineartEdgeSegment *es) +{ + BLI_spin_lock(&ld->lock_cuts); + + memset(es, 0, sizeof(LineartEdgeSegment)); + + /* Storing the node for potentially reuse the memory for new segment data. + * Line Art data is not freed after all calculations are done. */ + BLI_addtail(&ld->wasted_cuts, es); + + BLI_spin_unlock(&ld->lock_cuts); +} + +static LineartEdgeSegment *lineart_give_segment(LineartData *ld) +{ + BLI_spin_lock(&ld->lock_cuts); + + /* See if there is any already allocated memory we can reuse. */ + if (ld->wasted_cuts.first) { + LineartEdgeSegment *es = (LineartEdgeSegment *)BLI_pophead(&ld->wasted_cuts); + BLI_spin_unlock(&ld->lock_cuts); + memset(es, 0, sizeof(LineartEdgeSegment)); + return es; + } + BLI_spin_unlock(&ld->lock_cuts); + + /* Otherwise allocate some new memory. */ + 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. + */ +void lineart_edge_cut(LineartData *ld, + LineartEdge *e, + double start, + double end, + uchar material_mask_bits, + uchar mat_occlusion, + uint32_t shadow_bits) +{ + LineartEdgeSegment *i_seg, *prev_seg; + LineartEdgeSegment *cut_start_before = 0, *cut_end_before = 0; + LineartEdgeSegment *new_seg1 = 0, *new_seg2 = 0; + int untouched = 0; + + /* If for some reason the occlusion function may give a result that has zero length, or reversed + * in direction, or NAN, 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. */ + LISTBASE_FOREACH (LineartEdgeSegment *, seg, &e->segments) { + if (LRT_DOUBLE_CLOSE_ENOUGH(seg->ratio, start)) { + cut_start_before = seg; + new_seg1 = cut_start_before; + break; + } + if (seg->next == nullptr) { + break; + } + i_seg = seg->next; + if (i_seg->ratio > start + 1e-09 && start > seg->ratio) { + cut_start_before = i_seg; + new_seg1 = lineart_give_segment(ld); + break; + } + } + if (!cut_start_before && LRT_DOUBLE_CLOSE_ENOUGH(1, end)) { + untouched = 1; + } + for (LineartEdgeSegment *seg = cut_start_before; seg; seg = seg->next) { + /* 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->ratio, end)) { + cut_end_before = seg; + new_seg2 = 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_seg2 = cut_end_before; + untouched = 1; + break; + } + /* When an actual cut is needed in the line. */ + if (seg->ratio > end) { + cut_end_before = seg; + new_seg2 = lineart_give_segment(ld); + break; + } + } + + /* When we still can't find any existing cut in the line, we allocate new ones. */ + if (new_seg1 == nullptr) { + new_seg1 = lineart_give_segment(ld); + } + if (new_seg2 == nullptr) { + if (untouched) { + new_seg2 = new_seg1; + cut_end_before = new_seg2; + } + else { + new_seg2 = lineart_give_segment(ld); + } + } + + if (cut_start_before) { + if (cut_start_before != new_seg1) { + /* Insert cutting points for when a new cut is needed. */ + i_seg = cut_start_before->prev ? cut_start_before->prev : nullptr; + 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); + } + /* Otherwise we already found a existing cutting point, no need to insert a new one. */ + } + else { + /* We have yet to reach a existing cutting point even after we searched the whole line, so we + * append the new cut to the end. */ + i_seg = static_cast(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) { + /* The same manipulation as on "cut_start_before". */ + if (cut_end_before != new_seg2) { + i_seg = cut_end_before->prev ? cut_end_before->prev : nullptr; + 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); + } + } + else { + i_seg = static_cast(e->segments.last); + 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; + 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->ratio = start; + if (!untouched) { + new_seg2->ratio = end; + } + else { + /* For the convenience of the loop below. */ + new_seg2 = new_seg2->next; + } + + /* Register 1 level of occlusion for all touched segments. */ + for (LineartEdgeSegment *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_ILLUMINATED || + e->flags & LRT_EDGE_FLAG_LIGHT_CONTOUR) { + 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_ILLUMINATED_SHAPE; + } + } + else { + seg->shadow_mask_bits |= shadow_bits; + } + } + + /* Reduce adjacent cutting points of the same level, which saves memory. */ + int8_t min_occ = 127; + prev_seg = nullptr; + LISTBASE_FOREACH_MUTABLE (LineartEdgeSegment *, seg, &e->segments) { + + if (prev_seg && prev_seg->occlusion == seg->occlusion && + 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. */ + lineart_discard_segment(ld, seg); + continue; + } + + min_occ = MIN2(min_occ, seg->occlusion); + + prev_seg = seg; + } + e->min_occ = min_occ; +} + +/** + * To see if given line is connected to an adjacent intersection line. + */ +BLI_INLINE bool lineart_occlusion_is_adjacent_intersection(LineartEdge *e, LineartTriangle *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) +{ + ba->max_triangle_count *= 2; + ba->linked_triangles = static_cast( + MEM_recallocN(ba->linked_triangles, sizeof(LineartTriangle *) * ba->max_triangle_count)); +} + +static void lineart_bounding_area_line_add(LineartBoundingArea *ba, LineartEdge *e) +{ + /* In case of too many lines concentrating in one point, do not add anymore, these lines will + * be either shorter than a single pixel, or will still be added into the list of other less + * dense areas. */ + if (ba->line_count >= 65535) { + return; + } + if (ba->line_count >= ba->max_line_count) { + LineartEdge **new_array = static_cast( + MEM_malloc_arrayN(ba->max_line_count * 2, sizeof(LineartEdge *), __func__)); + memcpy(new_array, ba->linked_lines, sizeof(LineartEdge *) * ba->max_line_count); + ba->max_line_count *= 2; + MEM_freeN(ba->linked_lines); + ba->linked_lines = new_array; + } + ba->linked_lines[ba->line_count] = e; + ba->line_count++; +} + +static void lineart_occlusion_single_line(LineartData *ld, LineartEdge *e, int thread_id) +{ + LineartTriangleThread *tri; + double l, r; + 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. */ + if (tri->testing_e[thread_id] == e || (tri->base.flags & LRT_TRIANGLE_INTERSECTION_ONLY) || + /* Ignore this triangle if an intersection line directly comes from it, */ + lineart_occlusion_is_adjacent_intersection(e, (LineartTriangle *)tri) || + /* Or if this triangle isn't effectively occluding anything nor it's providing a + * material flag. */ + ((!tri->base.mat_occlusion) && (!tri->base.material_mask_bits))) { + continue; + } + tri->testing_e[thread_id] = e; + if (lineart_triangle_edge_image_space_occlusion((const LineartTriangle *)tri, + e, + ld->conf.camera_pos, + ld->conf.cam_is_persp, + ld->conf.allow_overlapping_edges, + ld->conf.view_projection, + ld->conf.view_vector, + ld->conf.shift_x, + ld->conf.shift_y, + &l, + &r)) { + 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. */ + return; + } + } + } + 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) +{ + int res = 0; + int starting_index; + + BLI_spin_lock(&ld->lock_task); + + starting_index = ld->scheduled_count; + ld->scheduled_count += LRT_THREAD_EDGE_COUNT; + + BLI_spin_unlock(&ld->lock_task); + + if (starting_index >= ld->pending_edges.next) { + res = 0; + } + else { + rti->pending_edges.array = &ld->pending_edges.array[starting_index]; + int remaining = ld->pending_edges.next - starting_index; + rti->pending_edges.max = MIN2(remaining, LRT_THREAD_EDGE_COUNT); + res = 1; + } + + return res; +} + +static void lineart_occlusion_worker(TaskPool *__restrict /*pool*/, LineartRenderTaskInfo *rti) +{ + LineartData *ld = rti->ld; + LineartEdge *eip; + + while (lineart_occlusion_make_task_info(ld, rti)) { + for (int i = 0; i < rti->pending_edges.max; i++) { + eip = rti->pending_edges.array[i]; + lineart_occlusion_single_line(ld, eip, rti->thread_id); + } + } +} + +/** + * All internal functions starting with lineart_main_ is called inside + * #MOD_lineart_compute_feature_lines function. + * This function handles all occlusion calculation. + */ +void lineart_main_occlusion_begin(LineartData *ld) +{ + int thread_count = ld->thread_count; + LineartRenderTaskInfo *rti = static_cast( + MEM_callocN(sizeof(LineartRenderTaskInfo) * thread_count, __func__)); + int i; + + TaskPool *tp = BLI_task_pool_create(nullptr, TASK_PRIORITY_HIGH); + + for (i = 0; i < thread_count; i++) { + rti[i].thread_id = i; + rti[i].ld = ld; + BLI_task_pool_push(tp, (TaskRunFunction)lineart_occlusion_worker, &rti[i], 0, nullptr); + } + BLI_task_pool_work_and_wait(tp); + BLI_task_pool_free(tp); + + MEM_freeN(rti); +} + +/** + * Test if v lies with in the triangle formed by v0, v1, and v2. + * Returns false when v is exactly on the edge. + * + * For v to be inside the triangle, it needs to be at the same side of v0->v1, v1->v2, and + * `v2->v0`, where the "side" is determined by checking the sign of `cross(v1-v0, v1-v)` and so on. + */ +static bool lineart_point_inside_triangle(const double v[2], + const double v0[2], + const double v1[2], + const double v2[2]) +{ + double cl, c, cl0; + + cl = (v0[0] - v[0]) * (v1[1] - v[1]) - (v0[1] - v[1]) * (v1[0] - v[0]); + c = cl0 = cl; + + cl = (v1[0] - v[0]) * (v2[1] - v[1]) - (v1[1] - v[1]) * (v2[0] - v[0]); + if (c * cl <= 0) { + return false; + } + + c = cl; + + cl = (v2[0] - v[0]) * (v0[1] - v[1]) - (v2[1] - v[1]) * (v0[0] - v[0]); + if (c * cl <= 0) { + return false; + } + + c = cl; + + if (c * cl0 <= 0) { + return false; + } + + return true; +} + +static int lineart_point_on_line_segment(double v[2], double v0[2], double v1[2]) +{ + /* `c1 != c2` by default. */ + double c1 = 1, c2 = 0; + double l0[2], l1[2]; + + sub_v2_v2v2_db(l0, v, v0); + sub_v2_v2v2_db(l1, v, v1); + + if (v1[0] == v0[0] && v1[1] == v0[1]) { + return 0; + } + + if (!LRT_DOUBLE_CLOSE_ENOUGH(v1[0], v0[0])) { + c1 = ratiod(v0[0], v1[0], v[0]); + } + else { + if (LRT_DOUBLE_CLOSE_ENOUGH(v[0], v1[0])) { + c2 = ratiod(v0[1], v1[1], v[1]); + return (c2 >= -DBL_TRIANGLE_LIM && c2 <= 1 + DBL_TRIANGLE_LIM); + } + return false; + } + + if (!LRT_DOUBLE_CLOSE_ENOUGH(v1[1], v0[1])) { + c2 = ratiod(v0[1], v1[1], v[1]); + } + else { + if (LRT_DOUBLE_CLOSE_ENOUGH(v[1], v1[1])) { + c1 = ratiod(v0[0], v1[0], v[0]); + return (c1 >= -DBL_TRIANGLE_LIM && c1 <= 1 + DBL_TRIANGLE_LIM); + } + return false; + } + + if (LRT_DOUBLE_CLOSE_ENOUGH(c1, c2) && c1 >= 0 && c1 <= 1) { + return 1; + } + + return 0; +} + +enum LineartPointTri { + LRT_OUTSIDE_TRIANGLE = 0, + LRT_ON_TRIANGLE = 1, + LRT_INSIDE_TRIANGLE = 2, +}; + +/** + * Same algorithm as lineart_point_inside_triangle(), but returns differently: + * 0-outside 1-on the edge 2-inside. + */ +static LineartPointTri lineart_point_triangle_relation(double v[2], + double v0[2], + double v1[2], + double v2[2]) +{ + double cl, c; + double r; + if (lineart_point_on_line_segment(v, v0, v1) || lineart_point_on_line_segment(v, v1, v2) || + lineart_point_on_line_segment(v, v2, v0)) { + return LRT_ON_TRIANGLE; + } + + cl = (v0[0] - v[0]) * (v1[1] - v[1]) - (v0[1] - v[1]) * (v1[0] - v[0]); + c = cl; + + cl = (v1[0] - v[0]) * (v2[1] - v[1]) - (v1[1] - v[1]) * (v2[0] - v[0]); + if ((r = c * cl) < 0) { + return LRT_OUTSIDE_TRIANGLE; + } + + c = cl; + + cl = (v2[0] - v[0]) * (v0[1] - v[1]) - (v2[1] - v[1]) * (v0[0] - v[0]); + if ((r = c * cl) < 0) { + return LRT_OUTSIDE_TRIANGLE; + } + + c = cl; + + cl = (v0[0] - v[0]) * (v1[1] - v[1]) - (v0[1] - v[1]) * (v1[0] - v[0]); + if ((r = c * cl) < 0) { + return LRT_OUTSIDE_TRIANGLE; + } + + if (r == 0) { + return LRT_ON_TRIANGLE; + } + + return LRT_INSIDE_TRIANGLE; +} + +/** + * Similar with #lineart_point_inside_triangle, but in 3d. + * Returns false when not co-planar. + */ +static bool lineart_point_inside_triangle3d(double v[3], double v0[3], double v1[3], double v2[3]) +{ + double l[3], r[3]; + double N1[3], N2[3]; + double d; + + sub_v3_v3v3_db(l, v1, v0); + sub_v3_v3v3_db(r, v, v1); + cross_v3_v3v3_db(N1, l, r); + + sub_v3_v3v3_db(l, v2, v1); + sub_v3_v3v3_db(r, v, v2); + cross_v3_v3v3_db(N2, l, r); + + if ((d = dot_v3v3_db(N1, N2)) < 0) { + return false; + } + + sub_v3_v3v3_db(l, v0, v2); + sub_v3_v3v3_db(r, v, v0); + cross_v3_v3v3_db(N1, l, r); + + if ((d = dot_v3v3_db(N1, N2)) < 0) { + return false; + } + + sub_v3_v3v3_db(l, v1, v0); + sub_v3_v3v3_db(r, v, v1); + cross_v3_v3v3_db(N2, l, r); + + if ((d = dot_v3v3_db(N1, N2)) < 0) { + return false; + } + + return true; +} + +/** + * The following `lineart_memory_get_XXX_space` functions are for allocating new memory for some + * modified geometries in the culling stage. + */ +static LineartElementLinkNode *lineart_memory_get_triangle_space(LineartData *ld) +{ + /* We don't need to allocate a whole bunch of triangles because the amount of clipped triangles + * are relatively small. */ + LineartTriangle *render_triangles = static_cast( + lineart_mem_acquire(&ld->render_data_pool, 64 * ld->sizeof_triangle)); + + LineartElementLinkNode *eln = static_cast( + lineart_list_append_pointer_pool_sized(&ld->geom.triangle_buffer_pointers, + &ld->render_data_pool, + render_triangles, + sizeof(LineartElementLinkNode))); + eln->element_count = 64; + eln->flags |= LRT_ELEMENT_IS_ADDITIONAL; + + return eln; +} + +static LineartElementLinkNode *lineart_memory_get_vert_space(LineartData *ld) +{ + LineartVert *render_vertices = static_cast( + lineart_mem_acquire(&ld->render_data_pool, sizeof(LineartVert) * 64)); + + LineartElementLinkNode *eln = static_cast( + lineart_list_append_pointer_pool_sized(&ld->geom.vertex_buffer_pointers, + &ld->render_data_pool, + render_vertices, + sizeof(LineartElementLinkNode))); + eln->element_count = 64; + eln->flags |= LRT_ELEMENT_IS_ADDITIONAL; + + return eln; +} + +static LineartElementLinkNode *lineart_memory_get_edge_space(LineartData *ld) +{ + LineartEdge *render_edges = static_cast( + lineart_mem_acquire(ld->edge_data_pool, sizeof(LineartEdge) * 64)); + + LineartElementLinkNode *eln = static_cast( + lineart_list_append_pointer_pool_sized(&ld->geom.line_buffer_pointers, + ld->edge_data_pool, + render_edges, + sizeof(LineartElementLinkNode))); + eln->element_count = 64; + eln->crease_threshold = ld->conf.crease_threshold; + eln->flags |= LRT_ELEMENT_IS_ADDITIONAL; + + return eln; +} + +static void lineart_triangle_post(LineartTriangle *tri, LineartTriangle *orig) +{ + /* Just re-assign normal and set cull flag. */ + copy_v3_v3_db(tri->gn, orig->gn); + tri->flags = LRT_CULL_GENERATED; + 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) +{ + uchar intersection_only = (tri->flags & LRT_TRIANGLE_INTERSECTION_ONLY); + tri->flags = flag; + tri->flags |= intersection_only; +} + +static bool lineart_edge_match(LineartTriangle *tri, LineartEdge *e, int v1, int v2) +{ + return ((tri->v[v1] == e->v1 && tri->v[v2] == e->v2) || + (tri->v[v2] == e->v1 && tri->v[v1] == e->v2)); +} + +static void lineart_discard_duplicated_edges(LineartEdge *old_e) +{ + LineartEdge *e = old_e; + while (e->flags & LRT_EDGE_FLAG_NEXT_IS_DUPLICATION) { + e++; + e->flags |= LRT_EDGE_FLAG_CHAIN_PICKED; + } +} + +/** + * Does near-plane cut on 1 triangle only. When cutting with far-plane, the camera vectors gets + * reversed by the caller so don't need to implement one in a different direction. + */ +static void lineart_triangle_cull_single(LineartData *ld, + LineartTriangle *tri, + int in0, + int in1, + int in2, + double cam_pos[3], + double view_dir[3], + bool allow_boundaries, + double m_view_projection[4][4], + Object *ob, + int *r_v_count, + int *r_e_count, + int *r_t_count, + LineartElementLinkNode *v_eln, + LineartElementLinkNode *e_eln, + LineartElementLinkNode *t_eln) +{ + double span_v1[3], span_v2[3], dot_v1, dot_v2; + double a; + int v_count = *r_v_count; + int e_count = *r_e_count; + int t_count = *r_t_count; + uint16_t new_flag = 0; + + LineartEdge *new_e, *e, *old_e; + LineartEdgeSegment *es; + + if (tri->flags & (LRT_CULL_USED | LRT_CULL_GENERATED | LRT_CULL_DISCARD)) { + return; + } + + /* See definition of tri->intersecting_verts and the usage in + * lineart_geometry_object_load() for details. */ + LineartTriangleAdjacent *tri_adj = reinterpret_cast( + tri->intersecting_verts); + + LineartVert *vt = &((LineartVert *)v_eln->pointer)[v_count]; + LineartTriangle *tri1 = static_cast( + (void *)(((uchar *)t_eln->pointer) + ld->sizeof_triangle * t_count)); + LineartTriangle *tri2 = static_cast( + (void *)(((uchar *)t_eln->pointer) + ld->sizeof_triangle * (t_count + 1))); + + new_e = &((LineartEdge *)e_eln->pointer)[e_count]; + /* Init `edge` to the last `edge` entry. */ + e = new_e; + +#define INCREASE_EDGE \ + new_e = &((LineartEdge *)e_eln->pointer)[e_count]; \ + e_count++; \ + e = new_e; \ + es = static_cast( \ + lineart_mem_acquire(&ld->render_data_pool, sizeof(LineartEdgeSegment))); \ + BLI_addtail(&e->segments, es); + +#define SELECT_EDGE(e_num, v1_link, v2_link, new_tri) \ + if (tri_adj->e[e_num]) { \ + old_e = tri_adj->e[e_num]; \ + new_flag = old_e->flags; \ + old_e->flags = LRT_EDGE_FLAG_CHAIN_PICKED; \ + lineart_discard_duplicated_edges(old_e); \ + INCREASE_EDGE \ + e->v1 = (v1_link); \ + e->v2 = (v2_link); \ + e->v1->index = (v1_link)->index; \ + e->v2->index = (v1_link)->index; \ + e->flags = new_flag; \ + e->object_ref = ob; \ + e->t1 = ((old_e->t1 == tri) ? (new_tri) : (old_e->t1)); \ + e->t2 = ((old_e->t2 == tri) ? (new_tri) : (old_e->t2)); \ + lineart_add_edge_to_array(&ld->pending_edges, e); \ + } + +#define RELINK_EDGE(e_num, new_tri) \ + if (tri_adj->e[e_num]) { \ + old_e = tri_adj->e[e_num]; \ + old_e->t1 = ((old_e->t1 == tri) ? (new_tri) : (old_e->t1)); \ + old_e->t2 = ((old_e->t2 == tri) ? (new_tri) : (old_e->t2)); \ + } + +#define REMOVE_TRIANGLE_EDGE \ + if (tri_adj->e[0]) { \ + tri_adj->e[0]->flags = LRT_EDGE_FLAG_CHAIN_PICKED; \ + lineart_discard_duplicated_edges(tri_adj->e[0]); \ + } \ + if (tri_adj->e[1]) { \ + tri_adj->e[1]->flags = LRT_EDGE_FLAG_CHAIN_PICKED; \ + lineart_discard_duplicated_edges(tri_adj->e[1]); \ + } \ + if (tri_adj->e[2]) { \ + tri_adj->e[2]->flags = LRT_EDGE_FLAG_CHAIN_PICKED; \ + lineart_discard_duplicated_edges(tri_adj->e[2]); \ + } + + switch (in0 + in1 + in2) { + case 0: /* Triangle is visible. Ignore this triangle. */ + return; + case 3: + /* Triangle completely behind near plane, throw it away + * also remove render lines form being computed. */ + lineart_triangle_set_cull_flag(tri, LRT_CULL_DISCARD); + REMOVE_TRIANGLE_EDGE + return; + case 2: + /* Two points behind near plane, cut those and + * generate 2 new points, 3 lines and 1 triangle. */ + lineart_triangle_set_cull_flag(tri, LRT_CULL_USED); + + /** + * (!in0) means "when point 0 is visible". + * conditions for point 1, 2 are the same idea. + * + * \code{.txt}identify + * 1-----|-------0 + * | | --- + * | |--- + * | ---| + * 2-- | + * (near)---------->(far) + * Will become: + * |N******0 + * |* *** + * |N** + * | + * | + * (near)---------->(far) + * \endcode + */ + if (!in0) { + + /* Cut point for line 2---|-----0. */ + sub_v3_v3v3_db(span_v1, tri->v[0]->gloc, cam_pos); + sub_v3_v3v3_db(span_v2, cam_pos, tri->v[2]->gloc); + dot_v1 = dot_v3v3_db(span_v1, view_dir); + dot_v2 = dot_v3v3_db(span_v2, view_dir); + 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, m_view_projection, vt[0].gloc); + vt[0].index = tri->v[2]->index; + + /* Cut point for line 1---|-----0. */ + sub_v3_v3v3_db(span_v1, tri->v[0]->gloc, cam_pos); + sub_v3_v3v3_db(span_v2, cam_pos, tri->v[1]->gloc); + dot_v1 = dot_v3v3_db(span_v1, view_dir); + dot_v2 = dot_v3v3_db(span_v2, view_dir); + 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, m_view_projection, vt[1].gloc); + vt[1].index = tri->v[1]->index; + + /* New line connecting two new points. */ + INCREASE_EDGE + if (allow_boundaries) { + e->flags = LRT_EDGE_FLAG_CONTOUR; + lineart_add_edge_to_array(&ld->pending_edges, e); + } + /* NOTE: inverting `e->v1/v2` (left/right point) doesn't matter as long as + * `tri->edge` and `tri->v` has the same sequence. and the winding direction + * can be either CW or CCW but needs to be consistent throughout the calculation. */ + e->v1 = &vt[1]; + e->v2 = &vt[0]; + /* Only one adjacent triangle, because the other side is the near plane. */ + /* Use `tl` or `tr` doesn't matter. */ + e->t1 = tri1; + e->object_ref = ob; + + /* New line connecting original point 0 and a new point, only when it's a selected line. */ + SELECT_EDGE(2, tri->v[0], &vt[0], tri1) + /* New line connecting original point 0 and another new point. */ + SELECT_EDGE(0, tri->v[0], &vt[1], tri1) + + /* Re-assign triangle point array to two new points. */ + tri1->v[0] = tri->v[0]; + tri1->v[1] = &vt[1]; + tri1->v[2] = &vt[0]; + + lineart_triangle_post(tri1, tri); + + v_count += 2; + t_count += 1; + } + else if (!in2) { + sub_v3_v3v3_db(span_v1, tri->v[2]->gloc, cam_pos); + sub_v3_v3v3_db(span_v2, cam_pos, tri->v[0]->gloc); + dot_v1 = dot_v3v3_db(span_v1, view_dir); + 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, 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); + sub_v3_v3v3_db(span_v2, cam_pos, tri->v[1]->gloc); + dot_v1 = dot_v3v3_db(span_v1, view_dir); + 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, m_view_projection, vt[1].gloc); + vt[1].index = tri->v[1]->index; + + INCREASE_EDGE + if (allow_boundaries) { + e->flags = LRT_EDGE_FLAG_CONTOUR; + lineart_add_edge_to_array(&ld->pending_edges, e); + } + e->v1 = &vt[0]; + e->v2 = &vt[1]; + e->t1 = tri1; + e->object_ref = ob; + + SELECT_EDGE(2, tri->v[2], &vt[0], tri1) + SELECT_EDGE(1, tri->v[2], &vt[1], tri1) + + tri1->v[0] = &vt[0]; + tri1->v[1] = &vt[1]; + tri1->v[2] = tri->v[2]; + + lineart_triangle_post(tri1, tri); + + v_count += 2; + t_count += 1; + } + else if (!in1) { + sub_v3_v3v3_db(span_v1, tri->v[1]->gloc, cam_pos); + sub_v3_v3v3_db(span_v2, cam_pos, tri->v[2]->gloc); + dot_v1 = dot_v3v3_db(span_v1, view_dir); + 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, 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); + sub_v3_v3v3_db(span_v2, cam_pos, tri->v[0]->gloc); + dot_v1 = dot_v3v3_db(span_v1, view_dir); + 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, m_view_projection, vt[1].gloc); + vt[1].index = tri->v[0]->index; + + INCREASE_EDGE + if (allow_boundaries) { + e->flags = LRT_EDGE_FLAG_CONTOUR; + lineart_add_edge_to_array(&ld->pending_edges, e); + } + e->v1 = &vt[1]; + e->v2 = &vt[0]; + e->t1 = tri1; + e->object_ref = ob; + + SELECT_EDGE(1, tri->v[1], &vt[0], tri1) + SELECT_EDGE(0, tri->v[1], &vt[1], tri1) + + tri1->v[0] = &vt[0]; + tri1->v[1] = tri->v[1]; + tri1->v[2] = &vt[1]; + + lineart_triangle_post(tri1, tri); + + v_count += 2; + t_count += 1; + } + break; + case 1: + /* One point behind near plane, cut those and + * generate 2 new points, 4 lines and 2 triangles. */ + lineart_triangle_set_cull_flag(tri, LRT_CULL_USED); + + /** + * (in0) means "when point 0 is invisible". + * conditions for point 1, 2 are the same idea. + * \code{.txt} + * 0------|----------1 + * -- | | + * ---| | + * |-- | + * | --- | + * | --- | + * | --2 + * (near)---------->(far) + * Will become: + * |N*********1 + * |* *** | + * |* *** | + * |N** | + * | *** | + * | *** | + * | **2 + * (near)---------->(far) + * \endcode + */ + if (in0) { + /* Cut point for line 0---|------1. */ + sub_v3_v3v3_db(span_v1, tri->v[1]->gloc, cam_pos); + sub_v3_v3v3_db(span_v2, cam_pos, tri->v[0]->gloc); + dot_v1 = dot_v3v3_db(span_v1, view_dir); + dot_v2 = dot_v3v3_db(span_v2, view_dir); + 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, m_view_projection, vt[0].gloc); + vt[0].index = tri->v[0]->index; + + /* Cut point for line 0---|------2. */ + sub_v3_v3v3_db(span_v1, tri->v[2]->gloc, cam_pos); + sub_v3_v3v3_db(span_v2, cam_pos, tri->v[0]->gloc); + dot_v1 = dot_v3v3_db(span_v1, view_dir); + dot_v2 = dot_v3v3_db(span_v2, view_dir); + 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, m_view_projection, vt[1].gloc); + vt[1].index = tri->v[0]->index; + + /* New line connects two new points. */ + INCREASE_EDGE + if (allow_boundaries) { + e->flags = LRT_EDGE_FLAG_CONTOUR; + lineart_add_edge_to_array(&ld->pending_edges, e); + } + e->v1 = &vt[1]; + e->v2 = &vt[0]; + e->t1 = tri1; + e->object_ref = ob; + + /* New line connects new point 0 and old point 1, + * this is a border line. */ + + SELECT_EDGE(0, tri->v[1], &vt[0], tri1) + SELECT_EDGE(2, tri->v[2], &vt[1], tri2) + RELINK_EDGE(1, tri2) + + /* We now have one triangle closed. */ + tri1->v[0] = tri->v[1]; + tri1->v[1] = &vt[1]; + tri1->v[2] = &vt[0]; + /* Close the second triangle. */ + tri2->v[0] = &vt[1]; + tri2->v[1] = tri->v[1]; + tri2->v[2] = tri->v[2]; + + lineart_triangle_post(tri1, tri); + lineart_triangle_post(tri2, tri); + + v_count += 2; + t_count += 2; + } + else if (in1) { + + sub_v3_v3v3_db(span_v1, tri->v[1]->gloc, cam_pos); + sub_v3_v3v3_db(span_v2, cam_pos, tri->v[2]->gloc); + dot_v1 = dot_v3v3_db(span_v1, view_dir); + 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, 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); + sub_v3_v3v3_db(span_v2, cam_pos, tri->v[0]->gloc); + dot_v1 = dot_v3v3_db(span_v1, view_dir); + 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, m_view_projection, vt[1].gloc); + vt[1].index = tri->v[1]->index; + + INCREASE_EDGE + if (allow_boundaries) { + e->flags = LRT_EDGE_FLAG_CONTOUR; + lineart_add_edge_to_array(&ld->pending_edges, e); + } + e->v1 = &vt[1]; + e->v2 = &vt[0]; + + e->t1 = tri1; + e->object_ref = ob; + + SELECT_EDGE(1, tri->v[2], &vt[0], tri1) + SELECT_EDGE(0, tri->v[0], &vt[1], tri2) + RELINK_EDGE(2, tri2) + + tri1->v[0] = tri->v[2]; + tri1->v[1] = &vt[1]; + tri1->v[2] = &vt[0]; + + tri2->v[0] = &vt[1]; + tri2->v[1] = tri->v[2]; + tri2->v[2] = tri->v[0]; + + lineart_triangle_post(tri1, tri); + lineart_triangle_post(tri2, tri); + + v_count += 2; + t_count += 2; + } + else if (in2) { + + sub_v3_v3v3_db(span_v1, tri->v[2]->gloc, cam_pos); + sub_v3_v3v3_db(span_v2, cam_pos, tri->v[0]->gloc); + dot_v1 = dot_v3v3_db(span_v1, view_dir); + 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, 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); + sub_v3_v3v3_db(span_v2, cam_pos, tri->v[1]->gloc); + dot_v1 = dot_v3v3_db(span_v1, view_dir); + 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, m_view_projection, vt[1].gloc); + vt[1].index = tri->v[2]->index; + + INCREASE_EDGE + if (allow_boundaries) { + e->flags = LRT_EDGE_FLAG_CONTOUR; + lineart_add_edge_to_array(&ld->pending_edges, e); + } + e->v1 = &vt[1]; + e->v2 = &vt[0]; + + e->t1 = tri1; + e->object_ref = ob; + + SELECT_EDGE(2, tri->v[0], &vt[0], tri1) + SELECT_EDGE(1, tri->v[1], &vt[1], tri2) + RELINK_EDGE(0, tri2) + + tri1->v[0] = tri->v[0]; + tri1->v[1] = &vt[1]; + tri1->v[2] = &vt[0]; + + tri2->v[0] = &vt[1]; + tri2->v[1] = tri->v[0]; + tri2->v[2] = tri->v[1]; + + lineart_triangle_post(tri1, tri); + lineart_triangle_post(tri2, tri); + + v_count += 2; + t_count += 2; + } + break; + } + *r_v_count = v_count; + *r_e_count = e_count; + *r_t_count = t_count; + +#undef INCREASE_EDGE +#undef SELECT_EDGE +#undef RELINK_EDGE +#undef REMOVE_TRIANGLE_EDGE +} + +/** + * This function cuts triangles with near- or far-plane. Setting clip_far = true for cutting with + * far-plane. For triangles that's crossing the plane, it will generate new 1 or 2 triangles with + * new topology that represents the trimmed triangle. (which then became a triangle or a square + * formed by two triangles) + */ +void lineart_main_cull_triangles(LineartData *ld, bool clip_far) +{ + LineartTriangle *tri; + LineartElementLinkNode *v_eln, *t_eln, *e_eln; + double(*m_view_projection)[4] = ld->conf.view_projection; + int i; + int v_count = 0, t_count = 0, e_count = 0; + Object *ob; + bool allow_boundaries = ld->conf.allow_boundaries; + double cam_pos[3]; + double clip_start = ld->conf.near_clip, clip_end = ld->conf.far_clip; + double view_dir[3], clip_advance[3]; + + copy_v3_v3_db(view_dir, ld->conf.view_vector); + copy_v3_v3_db(clip_advance, ld->conf.view_vector); + copy_v3_v3_db(cam_pos, ld->conf.camera_pos); + + if (clip_far) { + /* Move starting point to end plane. */ + mul_v3db_db(clip_advance, -clip_end); + add_v3_v3_db(cam_pos, clip_advance); + + /* "reverse looking". */ + mul_v3db_db(view_dir, -1.0f); + } + else { + /* Clip Near. */ + mul_v3db_db(clip_advance, -clip_start); + add_v3_v3_db(cam_pos, clip_advance); + } + + v_eln = lineart_memory_get_vert_space(ld); + t_eln = lineart_memory_get_triangle_space(ld); + e_eln = lineart_memory_get_edge_space(ld); + + /* Additional memory space for storing generated points and triangles. */ +#define LRT_CULL_ENSURE_MEMORY \ + if (v_count > 60) { \ + v_eln->element_count = v_count; \ + v_eln = lineart_memory_get_vert_space(ld); \ + v_count = 0; \ + } \ + if (t_count > 60) { \ + t_eln->element_count = t_count; \ + t_eln = lineart_memory_get_triangle_space(ld); \ + t_count = 0; \ + } \ + if (e_count > 60) { \ + e_eln->element_count = e_count; \ + e_eln = lineart_memory_get_edge_space(ld); \ + e_count = 0; \ + } + +#define LRT_CULL_DECIDE_INSIDE \ + /* These three represents points that are in the clipping range or not. */ \ + in0 = 0, in1 = 0, in2 = 0; \ + if (clip_far) { \ + /* Point outside far plane. */ \ + if (tri->v[0]->fbcoord[use_w] > clip_end) { \ + in0 = 1; \ + } \ + if (tri->v[1]->fbcoord[use_w] > clip_end) { \ + in1 = 1; \ + } \ + if (tri->v[2]->fbcoord[use_w] > clip_end) { \ + in2 = 1; \ + } \ + } \ + else { \ + /* Point inside near plane. */ \ + if (tri->v[0]->fbcoord[use_w] < clip_start) { \ + in0 = 1; \ + } \ + if (tri->v[1]->fbcoord[use_w] < clip_start) { \ + in1 = 1; \ + } \ + if (tri->v[2]->fbcoord[use_w] < clip_start) { \ + in2 = 1; \ + } \ + } + + int use_w = 3; + int in0 = 0, in1 = 0, in2 = 0; + + if (!ld->conf.cam_is_persp) { + clip_start = -1; + clip_end = 1; + use_w = 2; + } + + /* Then go through all the other triangles. */ + LISTBASE_FOREACH (LineartElementLinkNode *, eln, &ld->geom.triangle_buffer_pointers) { + if (eln->flags & LRT_ELEMENT_IS_ADDITIONAL) { + continue; + } + ob = static_cast(eln->object_ref); + for (i = 0; i < eln->element_count; i++) { + /* Select the triangle in the array. */ + tri = static_cast( + (void *)(((uchar *)eln->pointer) + ld->sizeof_triangle * i)); + + if (tri->flags & LRT_CULL_DISCARD) { + continue; + } + + LRT_CULL_DECIDE_INSIDE + LRT_CULL_ENSURE_MEMORY + lineart_triangle_cull_single(ld, + tri, + in0, + in1, + in2, + cam_pos, + view_dir, + allow_boundaries, + m_view_projection, + ob, + &v_count, + &e_count, + &t_count, + v_eln, + e_eln, + t_eln); + } + t_eln->element_count = t_count; + v_eln->element_count = v_count; + } + +#undef LRT_CULL_ENSURE_MEMORY +#undef LRT_CULL_DECIDE_INSIDE +} + +/** + * Adjacent data is only used during the initial stages of computing. + * So we can free it using this function when it is not needed anymore. + */ +void lineart_main_free_adjacent_data(LineartData *ld) +{ + LinkData *link; + while ((link = static_cast(BLI_pophead(&ld->geom.triangle_adjacent_pointers))) != + nullptr) { + MEM_freeN(link->data); + } + LISTBASE_FOREACH (LineartElementLinkNode *, eln, &ld->geom.triangle_buffer_pointers) { + LineartTriangle *tri = static_cast(eln->pointer); + int i; + for (i = 0; i < eln->element_count; i++) { + /* See definition of tri->intersecting_verts and the usage in + * lineart_geometry_object_load() for detailed. */ + tri->intersecting_verts = nullptr; + tri = (LineartTriangle *)(((uchar *)tri) + ld->sizeof_triangle); + } + } +} + +void lineart_main_perspective_division(LineartData *ld) +{ + LISTBASE_FOREACH (LineartElementLinkNode *, eln, &ld->geom.vertex_buffer_pointers) { + LineartVert *vt = static_cast(eln->pointer); + for (int i = 0; i < eln->element_count; i++) { + if (ld->conf.cam_is_persp) { + /* Do not divide Z, we use Z to back transform cut points in later chaining process. */ + vt[i].fbcoord[0] /= vt[i].fbcoord[3]; + vt[i].fbcoord[1] /= vt[i].fbcoord[3]; + /* Re-map z into (0-1) range, because we no longer need NDC (Normalized Device Coordinates) + * at the moment. + * The algorithm currently doesn't need Z for operation, we use W instead. If Z is needed + * in the future, the line below correctly transforms it to view space coordinates. */ + // `vt[i].fbcoord[2] = -2 * vt[i].fbcoord[2] / (far - near) - (far + near) / (far - near); + } + /* Shifting is always needed. */ + vt[i].fbcoord[0] -= ld->conf.shift_x * 2; + vt[i].fbcoord[1] -= ld->conf.shift_y * 2; + } + } +} + +void lineart_main_discard_out_of_frame_edges(LineartData *ld) +{ + LineartEdge *e; + int i; + +#define LRT_VERT_OUT_OF_BOUND(v) \ + (v && (v->fbcoord[0] < -1 || v->fbcoord[0] > 1 || v->fbcoord[1] < -1 || v->fbcoord[1] > 1)) + + LISTBASE_FOREACH (LineartElementLinkNode *, eln, &ld->geom.line_buffer_pointers) { + e = (LineartEdge *)eln->pointer; + for (i = 0; i < eln->element_count; i++) { + if (LRT_VERT_OUT_OF_BOUND(e[i].v1) && LRT_VERT_OUT_OF_BOUND(e[i].v2)) { + e[i].flags = LRT_EDGE_FLAG_CHAIN_PICKED; + } + } + } +} + +struct LineartEdgeNeighbor { + int e; + uint16_t flags; + int v1, v2; +}; + +struct VertData { + const MVert *mvert; + LineartVert *v_arr; + double (*model_view)[4]; + double (*model_view_proj)[4]; +}; + +static void lineart_mvert_transform_task(void *__restrict userdata, + const int i, + const TaskParallelTLS *__restrict /*tls*/) +{ + VertData *vert_task_data = (VertData *)userdata; + const MVert *m_v = &vert_task_data->mvert[i]; + double co[4]; + LineartVert *v = &vert_task_data->v_arr[i]; + copy_v3db_v3fl(co, m_v->co); + mul_v3_m4v3_db(v->gloc, vert_task_data->model_view, co); + mul_v4_m4v3_db(v->fbcoord, vert_task_data->model_view_proj, co); + v->index = i; +} + +static const int LRT_MESH_EDGE_TYPES[] = { + LRT_EDGE_FLAG_EDGE_MARK, + LRT_EDGE_FLAG_CONTOUR, + LRT_EDGE_FLAG_CREASE, + LRT_EDGE_FLAG_MATERIAL, + LRT_EDGE_FLAG_LOOSE, + LRT_EDGE_FLAG_CONTOUR_SECONDARY, +}; + +#define LRT_MESH_EDGE_TYPES_COUNT 6 + +static int lineart_edge_type_duplication_count(int eflag) +{ + int count = 0; + /* See eLineartEdgeFlag for details. */ + for (int i = 0; i < LRT_MESH_EDGE_TYPES_COUNT; i++) { + if (eflag & LRT_MESH_EDGE_TYPES[i]) { + count++; + } + } + return count; +} + +/** + * Because we have a variable size for #LineartTriangle, we need an access helper. + * See #LineartTriangleThread for more info. + */ +static LineartTriangle *lineart_triangle_from_index(LineartData *ld, + LineartTriangle *rt_array, + int index) +{ + int8_t *b = (int8_t *)rt_array; + b += (index * ld->sizeof_triangle); + return (LineartTriangle *)b; +} + +struct EdgeFeatData { + LineartData *ld; + Mesh *me; + Object *ob_eval; /* For evaluated materials. */ + const MLoopTri *mlooptri; + const int *material_indices; + LineartTriangle *tri_array; + LineartVert *v_array; + float crease_threshold; + bool use_auto_smooth; + bool use_freestyle_face; + int freestyle_face_index; + bool use_freestyle_edge; + int freestyle_edge_index; + LineartEdgeNeighbor *edge_nabr; +}; + +struct EdgeFeatReduceData { + int feat_edges; +}; + +static void feat_data_sum_reduce(const void *__restrict /*userdata*/, + void *__restrict chunk_join, + void *__restrict chunk) +{ + EdgeFeatReduceData *feat_chunk_join = (EdgeFeatReduceData *)chunk_join; + EdgeFeatReduceData *feat_chunk = (EdgeFeatReduceData *)chunk; + feat_chunk_join->feat_edges += feat_chunk->feat_edges; +} + +static void lineart_identify_mlooptri_feature_edges(void *__restrict userdata, + const int i, + const TaskParallelTLS *__restrict tls) +{ + EdgeFeatData *e_feat_data = (EdgeFeatData *)userdata; + EdgeFeatReduceData *reduce_data = (EdgeFeatReduceData *)tls->userdata_chunk; + Mesh *me = e_feat_data->me; + const int *material_indices = e_feat_data->material_indices; + Object *ob_eval = e_feat_data->ob_eval; + LineartEdgeNeighbor *edge_nabr = e_feat_data->edge_nabr; + const MLoopTri *mlooptri = e_feat_data->mlooptri; + + uint16_t edge_flag_result = 0; + + /* Because the edge neighbor array contains loop edge pairs, we only need to process the first + * edge in the pair. Otherwise we would add the same edge that the loops represent twice. */ + if (i < edge_nabr[i].e) { + return; + } + + bool face_mark_filtered = false; + bool enable_face_mark = (e_feat_data->use_freestyle_face && + e_feat_data->ld->conf.filter_face_mark); + bool only_contour = false; + if (enable_face_mark) { + FreestyleFace *ff1, *ff2; + int index = e_feat_data->freestyle_face_index; + if (index > -1) { + ff1 = &((FreestyleFace *)me->pdata.layers[index].data)[mlooptri[i / 3].poly]; + } + if (edge_nabr[i].e > -1) { + ff2 = &((FreestyleFace *)me->pdata.layers[index].data)[mlooptri[edge_nabr[i].e / 3].poly]; + } + else { + /* Handle mesh boundary cases: We want mesh boundaries to respect + * `filter_face_mark_boundaries` option the same way as face mark boundaries, and the code + * path is simper when it's assuming both ff1 and ff2 not nullptr. */ + ff2 = ff1; + } + if (e_feat_data->ld->conf.filter_face_mark_boundaries ^ + e_feat_data->ld->conf.filter_face_mark_invert) { + if ((ff1->flag & FREESTYLE_FACE_MARK) || (ff2->flag & FREESTYLE_FACE_MARK)) { + face_mark_filtered = true; + } + } + else { + if ((ff1->flag & FREESTYLE_FACE_MARK) && (ff2->flag & FREESTYLE_FACE_MARK) && (ff2 != ff1)) { + face_mark_filtered = true; + } + } + if (e_feat_data->ld->conf.filter_face_mark_invert) { + face_mark_filtered = !face_mark_filtered; + } + if (!face_mark_filtered) { + edge_nabr[i].flags = LRT_EDGE_FLAG_INHIBIT; + if (e_feat_data->ld->conf.filter_face_mark_keep_contour) { + only_contour = true; + } + } + } + + if (enable_face_mark && !face_mark_filtered && !only_contour) { + return; + } + + /* Mesh boundary */ + if (edge_nabr[i].e == -1) { + edge_nabr[i].flags = LRT_EDGE_FLAG_CONTOUR; + reduce_data->feat_edges += 1; + return; + } + + LineartTriangle *tri1, *tri2; + LineartVert *vert; + LineartData *ld = e_feat_data->ld; + + int f1 = i / 3, f2 = edge_nabr[i].e / 3; + + /* The mesh should already be triangulated now, so we can assume each face is a triangle. */ + tri1 = lineart_triangle_from_index(ld, e_feat_data->tri_array, f1); + tri2 = lineart_triangle_from_index(ld, e_feat_data->tri_array, f2); + + vert = &e_feat_data->v_array[edge_nabr[i].v1]; + + double view_vector_persp[3]; + double *view_vector = view_vector_persp; + double dot_v1 = 0, dot_v2 = 0; + double result; + bool material_back_face = ((tri1->flags | tri2->flags) & LRT_TRIANGLE_MAT_BACK_FACE_CULLING); + + if (ld->conf.use_contour || ld->conf.use_back_face_culling || material_back_face) { + if (ld->conf.cam_is_persp) { + sub_v3_v3v3_db(view_vector, ld->conf.camera_pos, vert->gloc); + } + else { + view_vector = ld->conf.view_vector; + } + + 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; + } + + if (ld->conf.use_back_face_culling) { + if (dot_v1 < 0) { + tri1->flags |= LRT_CULL_DISCARD; + } + if (dot_v2 < 0) { + tri2->flags |= LRT_CULL_DISCARD; + } + } + if (material_back_face) { + if (tri1->flags & LRT_TRIANGLE_MAT_BACK_FACE_CULLING && dot_v1 < 0) { + tri1->flags |= LRT_CULL_DISCARD; + } + if (tri2->flags & LRT_TRIANGLE_MAT_BACK_FACE_CULLING && dot_v2 < 0) { + tri2->flags |= LRT_CULL_DISCARD; + } + } + } + + 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) { + const MPoly *polys = BKE_mesh_polys(me); + + if (ld->conf.use_crease) { + bool do_crease = true; + if (!ld->conf.force_crease && !e_feat_data->use_auto_smooth && + (polys[mlooptri[f1].poly].flag & ME_SMOOTH) && + (polys[mlooptri[f2].poly].flag & ME_SMOOTH)) { + do_crease = false; + } + if (do_crease && (dot_v3v3_db(tri1->gn, tri2->gn) < e_feat_data->crease_threshold)) { + edge_flag_result |= LRT_EDGE_FLAG_CREASE; + } + } + + int mat1 = material_indices ? material_indices[mlooptri[f1].poly] : 0; + int mat2 = material_indices ? material_indices[mlooptri[f2].poly] : 0; + + if (mat1 != mat2) { + Material *m1 = BKE_object_material_get_eval(ob_eval, mat1 + 1); + Material *m2 = BKE_object_material_get_eval(ob_eval, 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 */ + if (!edge_flag_result) { /* Other edge types inhibited */ + return; + } + } + + const MEdge *edges = BKE_mesh_edges(me); + + int real_edges[3]; + BKE_mesh_looptri_get_real_edges(me, &mlooptri[i / 3], real_edges); + + if (real_edges[i % 3] >= 0) { + const MEdge *medge = &edges[real_edges[i % 3]]; + + if (ld->conf.use_crease && ld->conf.sharp_as_crease && (medge->flag & ME_SHARP)) { + edge_flag_result |= LRT_EDGE_FLAG_CREASE; + } + + if (ld->conf.use_edge_marks && e_feat_data->use_freestyle_edge) { + FreestyleEdge *fe; + int index = e_feat_data->freestyle_edge_index; + fe = &((FreestyleEdge *)me->edata.layers[index].data)[real_edges[i % 3]]; + if (fe->flag & FREESTYLE_EDGE_MARK) { + edge_flag_result |= LRT_EDGE_FLAG_EDGE_MARK; + } + } + } + + edge_nabr[i].flags = edge_flag_result; + + if (edge_flag_result) { + /* Only allocate for feature edge (instead of all edges) to save memory. + * If allow duplicated edges, one edge gets added multiple times if it has multiple types. + */ + reduce_data->feat_edges += e_feat_data->ld->conf.allow_duplicated_types ? + lineart_edge_type_duplication_count(edge_flag_result) : + 1; + } +} + +struct LooseEdgeData { + int loose_count; + int loose_max; + int *loose_array; + const MEdge *edges; +}; + +static void lineart_loose_data_reallocate(LooseEdgeData *loose_data, int count) +{ + int *new_arr = static_cast(MEM_calloc_arrayN(count, sizeof(int), "loose edge array")); + if (loose_data->loose_array) { + memcpy(new_arr, loose_data->loose_array, sizeof(int) * loose_data->loose_max); + MEM_SAFE_FREE(loose_data->loose_array); + } + loose_data->loose_max = count; + loose_data->loose_array = new_arr; +} + +static void lineart_join_loose_edge_arr(LooseEdgeData *loose_data, LooseEdgeData *to_be_joined) +{ + if (!to_be_joined->loose_array) { + return; + } + int new_count = loose_data->loose_count + to_be_joined->loose_count; + if (new_count >= loose_data->loose_max) { + lineart_loose_data_reallocate(loose_data, new_count); + } + memcpy(&loose_data->loose_array[loose_data->loose_count], + to_be_joined->loose_array, + sizeof(int) * to_be_joined->loose_count); + loose_data->loose_count += to_be_joined->loose_count; + MEM_freeN(to_be_joined->loose_array); + to_be_joined->loose_array = nullptr; +} + +static void lineart_add_loose_edge(LooseEdgeData *loose_data, const int i) +{ + if (loose_data->loose_count >= loose_data->loose_max) { + int min_amount = MAX2(100, loose_data->loose_count * 2); + lineart_loose_data_reallocate(loose_data, min_amount); + } + loose_data->loose_array[loose_data->loose_count] = i; + loose_data->loose_count++; +} + +static void lineart_identify_loose_edges(void *__restrict /*userdata*/, + const int i, + const TaskParallelTLS *__restrict tls) +{ + LooseEdgeData *loose_data = (LooseEdgeData *)tls->userdata_chunk; + + if (loose_data->edges[i].flag & ME_LOOSEEDGE) { + lineart_add_loose_edge(loose_data, i); + } +} + +static void loose_data_sum_reduce(const void *__restrict /*userdata*/, + void *__restrict chunk_join, + void *__restrict chunk) +{ + LooseEdgeData *final = (LooseEdgeData *)chunk_join; + LooseEdgeData *loose_chunk = (LooseEdgeData *)chunk; + lineart_join_loose_edge_arr(final, loose_chunk); +} + +void lineart_add_edge_to_array(LineartPendingEdges *pe, LineartEdge *e) +{ + if (pe->next >= pe->max || !pe->max) { + if (!pe->max) { + pe->max = 1000; + } + + LineartEdge **new_array = static_cast( + MEM_mallocN(sizeof(LineartEdge *) * pe->max * 2, "LineartPendingEdges array")); + if (LIKELY(pe->array)) { + memcpy(new_array, pe->array, sizeof(LineartEdge *) * pe->max); + MEM_freeN(pe->array); + } + pe->max *= 2; + pe->array = new_array; + } + 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); +} + +/* NOTE: For simplicity, this function doesn't actually do anything if you already have data in + * #pe. */ +void lineart_finalize_object_edge_array_reserve(LineartPendingEdges *pe, int count) +{ + if (pe->max || pe->array || count == 0) { + return; + } + + pe->max = count; + LineartEdge **new_array = static_cast( + MEM_mallocN(sizeof(LineartEdge *) * pe->max, "LineartPendingEdges array final")); + pe->array = new_array; +} + +static void lineart_finalize_object_edge_array(LineartPendingEdges *pe, LineartObjectInfo *obi) +{ + /* In case of line art "occlusion only" or contour not enabled, it's possible for an object to + * not produce any feature lines. */ + if (!obi->pending_edges.array) { + return; + } + memcpy(&pe->array[pe->next], + obi->pending_edges.array, + sizeof(LineartEdge *) * obi->pending_edges.next); + MEM_freeN(obi->pending_edges.array); + pe->next += obi->pending_edges.next; +} + +static void lineart_triangle_adjacent_assign(LineartTriangle *tri, + LineartTriangleAdjacent *tri_adj, + LineartEdge *e) +{ + if (lineart_edge_match(tri, e, 0, 1)) { + tri_adj->e[0] = e; + } + else if (lineart_edge_match(tri, e, 1, 2)) { + tri_adj->e[1] = e; + } + else if (lineart_edge_match(tri, e, 2, 0)) { + tri_adj->e[2] = e; + } +} + +struct TriData { + LineartObjectInfo *ob_info; + const MLoopTri *mlooptri; + const int *material_indices; + LineartVert *vert_arr; + LineartTriangle *tri_arr; + int lineart_triangle_size; + LineartTriangleAdjacent *tri_adj; +}; + +static void lineart_load_tri_task(void *__restrict userdata, + const int i, + const TaskParallelTLS *__restrict /*tls*/) +{ + TriData *tri_task_data = (TriData *)userdata; + Mesh *me = tri_task_data->ob_info->original_me; + LineartObjectInfo *ob_info = tri_task_data->ob_info; + const MLoopTri *mlooptri = &tri_task_data->mlooptri[i]; + const int *material_indices = tri_task_data->material_indices; + LineartVert *vert_arr = tri_task_data->vert_arr; + LineartTriangle *tri = tri_task_data->tri_arr; + const MLoop *loops = BKE_mesh_loops(me); + + tri = (LineartTriangle *)(((uchar *)tri) + tri_task_data->lineart_triangle_size * i); + + int v1 = loops[mlooptri->tri[0]].v; + int v2 = loops[mlooptri->tri[1]].v; + int v3 = loops[mlooptri->tri[2]].v; + + tri->v[0] = &vert_arr[v1]; + tri->v[1] = &vert_arr[v2]; + tri->v[2] = &vert_arr[v3]; + + /* Material mask bits and occlusion effectiveness assignment. */ + Material *mat = BKE_object_material_get( + ob_info->original_ob_eval, material_indices ? material_indices[mlooptri->poly] + 1 : 1); + tri->material_mask_bits |= ((mat && (mat->lineart.flags & LRT_MATERIAL_MASK_ENABLED)) ? + 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]; + const MVert *verts = BKE_mesh_verts(me); + normal_tri_v3(no, verts[v1].co, verts[v2].co, verts[v3].co); + copy_v3db_v3fl(gn, no); + mul_v3_mat3_m4v3_db(tri->gn, ob_info->normal, gn); + normalize_v3_db(tri->gn); + + if (ob_info->usage == OBJECT_LRT_INTERSECTION_ONLY) { + tri->flags |= LRT_TRIANGLE_INTERSECTION_ONLY; + } + else if (ob_info->usage == OBJECT_LRT_FORCE_INTERSECTION) { + tri->flags |= LRT_TRIANGLE_FORCE_INTERSECTION; + } + else if (ELEM(ob_info->usage, OBJECT_LRT_NO_INTERSECTION, OBJECT_LRT_OCCLUSION_ONLY)) { + tri->flags |= LRT_TRIANGLE_NO_INTERSECTION; + } + + /* Re-use this field to refer to adjacent info, will be cleared after culling stage. */ + tri->intersecting_verts = static_cast((void *)&tri_task_data->tri_adj[i]); +} +struct EdgeNeighborData { + LineartEdgeNeighbor *edge_nabr; + LineartAdjacentEdge *adj_e; + const MLoopTri *mlooptri; + const MLoop *mloop; +}; + +static void lineart_edge_neighbor_init_task(void *__restrict userdata, + const int i, + const TaskParallelTLS *__restrict /*tls*/) +{ + EdgeNeighborData *en_data = (EdgeNeighborData *)userdata; + LineartAdjacentEdge *adj_e = &en_data->adj_e[i]; + const MLoopTri *looptri = &en_data->mlooptri[i / 3]; + LineartEdgeNeighbor *edge_nabr = &en_data->edge_nabr[i]; + const MLoop *mloop = en_data->mloop; + + adj_e->e = i; + adj_e->v1 = mloop[looptri->tri[i % 3]].v; + adj_e->v2 = mloop[looptri->tri[(i + 1) % 3]].v; + if (adj_e->v1 > adj_e->v2) { + SWAP(uint32_t, adj_e->v1, adj_e->v2); + } + edge_nabr->e = -1; + + edge_nabr->v1 = adj_e->v1; + edge_nabr->v2 = adj_e->v2; + edge_nabr->flags = 0; +} + +static LineartEdgeNeighbor *lineart_build_edge_neighbor(Mesh *me, int total_edges) +{ + /* Because the mesh is triangulated, so `me->totedge` should be reliable? */ + LineartAdjacentEdge *adj_e = static_cast( + MEM_mallocN(sizeof(LineartAdjacentEdge) * total_edges, "LineartAdjacentEdge arr")); + LineartEdgeNeighbor *edge_nabr = static_cast( + MEM_mallocN(sizeof(LineartEdgeNeighbor) * total_edges, "LineartEdgeNeighbor arr")); + + TaskParallelSettings en_settings; + BLI_parallel_range_settings_defaults(&en_settings); + /* Set the minimum amount of edges a thread has to process. */ + en_settings.min_iter_per_thread = 50000; + + EdgeNeighborData en_data; + en_data.adj_e = adj_e; + en_data.edge_nabr = edge_nabr; + en_data.mlooptri = BKE_mesh_runtime_looptri_ensure(me); + en_data.mloop = BKE_mesh_loops(me); + + BLI_task_parallel_range(0, total_edges, &en_data, lineart_edge_neighbor_init_task, &en_settings); + + lineart_sort_adjacent_items(adj_e, total_edges); + + for (int i = 0; i < total_edges - 1; i++) { + if (adj_e[i].v1 == adj_e[i + 1].v1 && adj_e[i].v2 == adj_e[i + 1].v2) { + edge_nabr[adj_e[i].e].e = adj_e[i + 1].e; + edge_nabr[adj_e[i + 1].e].e = adj_e[i].e; + } + } + + MEM_freeN(adj_e); + + return edge_nabr; +} + +static void lineart_geometry_object_load(LineartObjectInfo *ob_info, + LineartData *la_data, + ListBase *shadow_elns) +{ + Mesh *me = ob_info->original_me; + if (!me->totedge) { + return; + } + + /* Triangulate. */ + const MLoopTri *mlooptri = BKE_mesh_runtime_looptri_ensure(me); + const int tot_tri = BKE_mesh_runtime_looptri_len(me); + + const int *material_indices = (const int *)CustomData_get_layer_named( + &me->pdata, CD_PROP_INT32, "material_index"); + + /* Check if we should look for custom data tags like Freestyle edges or faces. */ + bool can_find_freestyle_edge = false; + int layer_index = CustomData_get_active_layer_index(&me->edata, CD_FREESTYLE_EDGE); + if (layer_index != -1) { + can_find_freestyle_edge = true; + } + + bool can_find_freestyle_face = false; + layer_index = CustomData_get_active_layer_index(&me->pdata, CD_FREESTYLE_FACE); + if (layer_index != -1) { + can_find_freestyle_face = true; + } + + /* If we allow duplicated edges, one edge should get added multiple times if is has been + * classified as more than one edge type. This is so we can create multiple different line type + * chains containing the same edge. */ + LineartVert *la_v_arr = static_cast( + lineart_mem_acquire_thread(&la_data->render_data_pool, sizeof(LineartVert) * me->totvert)); + LineartTriangle *la_tri_arr = static_cast( + lineart_mem_acquire_thread(&la_data->render_data_pool, tot_tri * la_data->sizeof_triangle)); + + Object *orig_ob = ob_info->original_ob; + + BLI_spin_lock(&la_data->lock_task); + LineartElementLinkNode *elem_link_node = static_cast( + lineart_list_append_pointer_pool_sized_thread(&la_data->geom.vertex_buffer_pointers, + &la_data->render_data_pool, + la_v_arr, + 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; + + bool use_auto_smooth = false; + float crease_angle = 0; + if (orig_ob->lineart.flags & OBJECT_LRT_OWN_CREASE) { + crease_angle = cosf(M_PI - orig_ob->lineart.crease_threshold); + } + else if (ob_info->original_me->flag & ME_AUTOSMOOTH) { + crease_angle = cosf(ob_info->original_me->smoothresh); + use_auto_smooth = true; + } + else { + crease_angle = la_data->conf.crease_threshold; + } + + /* FIXME(Yiming): Hack for getting clean 3D text, the seam that extruded text object creates + * erroneous detection on creases. Future configuration should allow options. */ + if (orig_ob->type == OB_FONT) { + elem_link_node->flags |= LRT_ELEMENT_BORDER_ONLY; + } + + BLI_spin_lock(&la_data->lock_task); + elem_link_node = static_cast( + lineart_list_append_pointer_pool_sized_thread(&la_data->geom.triangle_buffer_pointers, + &la_data->render_data_pool, + la_tri_arr, + sizeof(LineartElementLinkNode))); + BLI_spin_unlock(&la_data->lock_task); + + int usage = ob_info->usage; + + elem_link_node->element_count = tot_tri; + elem_link_node->object_ref = orig_ob; + elem_link_node->flags = eLineArtElementNodeFlag( + elem_link_node->flags | + ((usage == OBJECT_LRT_NO_INTERSECTION) ? LRT_ELEMENT_NO_INTERSECTION : 0)); + + /* Note this memory is not from pool, will be deleted after culling. */ + LineartTriangleAdjacent *tri_adj = static_cast( + MEM_callocN(sizeof(LineartTriangleAdjacent) * tot_tri, "LineartTriangleAdjacent")); + /* Link is minimal so we use pool anyway. */ + BLI_spin_lock(&la_data->lock_task); + lineart_list_append_pointer_pool_thread( + &la_data->geom.triangle_adjacent_pointers, &la_data->render_data_pool, tri_adj); + BLI_spin_unlock(&la_data->lock_task); + + /* Convert all vertices to lineart verts. */ + TaskParallelSettings vert_settings; + BLI_parallel_range_settings_defaults(&vert_settings); + /* Set the minimum amount of verts a thread has to process. */ + vert_settings.min_iter_per_thread = 4000; + + VertData vert_data; + vert_data.mvert = BKE_mesh_verts(me); + vert_data.v_arr = la_v_arr; + vert_data.model_view = ob_info->model_view; + vert_data.model_view_proj = ob_info->model_view_proj; + + BLI_task_parallel_range( + 0, me->totvert, &vert_data, lineart_mvert_transform_task, &vert_settings); + + /* Convert all mesh triangles into lineart triangles. + * Also create an edge map to get connectivity between edges and triangles. */ + TaskParallelSettings tri_settings; + BLI_parallel_range_settings_defaults(&tri_settings); + /* Set the minimum amount of triangles a thread has to process. */ + tri_settings.min_iter_per_thread = 4000; + + TriData tri_data; + tri_data.ob_info = ob_info; + tri_data.mlooptri = mlooptri; + tri_data.material_indices = material_indices; + tri_data.vert_arr = la_v_arr; + tri_data.tri_arr = la_tri_arr; + tri_data.lineart_triangle_size = la_data->sizeof_triangle; + tri_data.tri_adj = tri_adj; + + uint32_t total_edges = tot_tri * 3; + + BLI_task_parallel_range(0, tot_tri, &tri_data, lineart_load_tri_task, &tri_settings); + + /* Check for contour lines in the mesh. + * IE check if the triangle edges lies in area where the triangles go from front facing to back + * facing. + */ + EdgeFeatReduceData edge_reduce = {0}; + TaskParallelSettings edge_feat_settings; + BLI_parallel_range_settings_defaults(&edge_feat_settings); + /* Set the minimum amount of edges a thread has to process. */ + edge_feat_settings.min_iter_per_thread = 4000; + edge_feat_settings.userdata_chunk = &edge_reduce; + edge_feat_settings.userdata_chunk_size = sizeof(EdgeFeatReduceData); + edge_feat_settings.func_reduce = feat_data_sum_reduce; + + EdgeFeatData edge_feat_data = {0}; + edge_feat_data.ld = la_data; + edge_feat_data.me = me; + edge_feat_data.ob_eval = ob_info->original_ob_eval; + edge_feat_data.mlooptri = mlooptri; + edge_feat_data.material_indices = material_indices; + edge_feat_data.edge_nabr = lineart_build_edge_neighbor(me, total_edges); + edge_feat_data.tri_array = la_tri_arr; + edge_feat_data.v_array = la_v_arr; + edge_feat_data.crease_threshold = crease_angle; + edge_feat_data.use_auto_smooth = use_auto_smooth; + edge_feat_data.use_freestyle_face = can_find_freestyle_face; + edge_feat_data.use_freestyle_edge = can_find_freestyle_edge; + if (edge_feat_data.use_freestyle_face) { + edge_feat_data.freestyle_face_index = CustomData_get_layer_index(&me->pdata, + CD_FREESTYLE_FACE); + } + if (edge_feat_data.use_freestyle_edge) { + edge_feat_data.freestyle_edge_index = CustomData_get_layer_index(&me->edata, + CD_FREESTYLE_EDGE); + } + + BLI_task_parallel_range(0, + total_edges, + &edge_feat_data, + lineart_identify_mlooptri_feature_edges, + &edge_feat_settings); + + LooseEdgeData loose_data = {0}; + + if (la_data->conf.use_loose) { + /* Only identifying floating edges at this point because other edges has been taken care of + * inside #lineart_identify_mlooptri_feature_edges function. */ + TaskParallelSettings edge_loose_settings; + BLI_parallel_range_settings_defaults(&edge_loose_settings); + edge_loose_settings.min_iter_per_thread = 4000; + edge_loose_settings.func_reduce = loose_data_sum_reduce; + edge_loose_settings.userdata_chunk = &loose_data; + edge_loose_settings.userdata_chunk_size = sizeof(LooseEdgeData); + loose_data.edges = BKE_mesh_edges(me); + BLI_task_parallel_range( + 0, me->totedge, &loose_data, lineart_identify_loose_edges, &edge_loose_settings); + } + + int allocate_la_e = edge_reduce.feat_edges + loose_data.loose_count; + + LineartEdge *la_edge_arr = static_cast( + lineart_mem_acquire_thread(la_data->edge_data_pool, sizeof(LineartEdge) * allocate_la_e)); + LineartEdgeSegment *la_seg_arr = static_cast(lineart_mem_acquire_thread( + la_data->edge_data_pool, sizeof(LineartEdgeSegment) * allocate_la_e)); + BLI_spin_lock(&la_data->lock_task); + elem_link_node = static_cast( + lineart_list_append_pointer_pool_sized_thread(&la_data->geom.line_buffer_pointers, + 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 = nullptr; + if (shadow_elns) { + shadow_eln = lineart_find_matching_eln(shadow_elns, ob_info->obindex); + } + + /* Start of the edge/seg arr */ + LineartEdge *la_edge; + LineartEdgeSegment *la_seg; + la_edge = la_edge_arr; + la_seg = la_seg_arr; + + for (int i = 0; i < total_edges; i++) { + LineartEdgeNeighbor *edge_nabr = &edge_feat_data.edge_nabr[i]; + + if (i < edge_nabr->e) { + continue; + } + + /* Not a feature line, so we skip. */ + if (edge_nabr->flags == 0) { + continue; + } + + LineartEdge *edge_added = nullptr; + + /* See eLineartEdgeFlag for details. */ + for (int flag_bit = 0; flag_bit < LRT_MESH_EDGE_TYPES_COUNT; flag_bit++) { + int use_type = LRT_MESH_EDGE_TYPES[flag_bit]; + if (!(use_type & edge_nabr->flags)) { + continue; + } + + la_edge->v1 = &la_v_arr[edge_nabr->v1]; + la_edge->v2 = &la_v_arr[edge_nabr->v2]; + int findex = i / 3; + la_edge->t1 = lineart_triangle_from_index(la_data, la_tri_arr, findex); + if (!edge_added) { + lineart_triangle_adjacent_assign(la_edge->t1, &tri_adj[findex], la_edge); + } + if (edge_nabr->e != -1) { + findex = edge_nabr->e / 3; + la_edge->t2 = lineart_triangle_from_index(la_data, la_tri_arr, findex); + if (!edge_added) { + lineart_triangle_adjacent_assign(la_edge->t2, &tri_adj[findex], la_edge); + } + } + 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 (ELEM(usage, + OBJECT_LRT_INHERIT, + OBJECT_LRT_INCLUDE, + OBJECT_LRT_NO_INTERSECTION, + OBJECT_LRT_FORCE_INTERSECTION)) { + lineart_add_edge_to_array_thread(ob_info, la_edge); + } + + if (edge_added) { + edge_added->flags |= LRT_EDGE_FLAG_NEXT_IS_DUPLICATION; + } + + edge_added = la_edge; + + la_edge++; + la_seg++; + + if (!la_data->conf.allow_duplicated_types) { + break; + } + } + } + + if (loose_data.loose_array) { + for (int i = 0; i < loose_data.loose_count; i++) { + const MEdge *edge = &loose_data.edges[loose_data.loose_array[i]]; + la_edge->v1 = &la_v_arr[edge->v1]; + la_edge->v2 = &la_v_arr[edge->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 (ELEM(usage, + OBJECT_LRT_INHERIT, + OBJECT_LRT_INCLUDE, + OBJECT_LRT_NO_INTERSECTION, + OBJECT_LRT_FORCE_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++; + } + MEM_SAFE_FREE(loose_data.loose_array); + } + + MEM_freeN(edge_feat_data.edge_nabr); + + if (ob_info->free_use_mesh) { + BKE_id_free(nullptr, me); + } +} + +static void lineart_object_load_worker(TaskPool *__restrict /*pool*/, + LineartObjectLoadTaskInfo *olti) +{ + for (LineartObjectInfo *obi = olti->pending; obi; obi = obi->next) { + lineart_geometry_object_load(obi, olti->ld, olti->shadow_elns); + } +} + +static uchar lineart_intersection_mask_check(Collection *c, Object *ob) +{ + LISTBASE_FOREACH (CollectionChild *, cc, &c->children) { + uchar result = lineart_intersection_mask_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_MASK) { + return c->lineart_intersection_mask; + } + } + + 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. + */ +static int lineart_usage_check(Collection *c, Object *ob, bool is_render) +{ + + if (!c) { + return OBJECT_LRT_INHERIT; + } + + int object_has_special_usage = (ob->lineart.usage != OBJECT_LRT_INHERIT); + + if (object_has_special_usage) { + return ob->lineart.usage; + } + + if (c->gobject.first) { + if (BKE_collection_has_object(c, (Object *)(ob->id.orig_id))) { + if ((is_render && (c->flag & COLLECTION_HIDE_RENDER)) || + ((!is_render) && (c->flag & COLLECTION_HIDE_VIEWPORT))) { + return OBJECT_LRT_EXCLUDE; + } + if (ob->lineart.usage == OBJECT_LRT_INHERIT) { + switch (c->lineart_usage) { + case COLLECTION_LRT_OCCLUSION_ONLY: + return OBJECT_LRT_OCCLUSION_ONLY; + case COLLECTION_LRT_EXCLUDE: + return OBJECT_LRT_EXCLUDE; + case COLLECTION_LRT_INTERSECTION_ONLY: + return OBJECT_LRT_INTERSECTION_ONLY; + case COLLECTION_LRT_NO_INTERSECTION: + return OBJECT_LRT_NO_INTERSECTION; + case COLLECTION_LRT_FORCE_INTERSECTION: + return OBJECT_LRT_FORCE_INTERSECTION; + } + return OBJECT_LRT_INHERIT; + } + return ob->lineart.usage; + } + } + + LISTBASE_FOREACH (CollectionChild *, cc, &c->children) { + int result = lineart_usage_check(cc->collection, ob, is_render); + if (result > OBJECT_LRT_INHERIT) { + return result; + } + } + + return OBJECT_LRT_INHERIT; +} + +static void lineart_geometry_load_assign_thread(LineartObjectLoadTaskInfo *olti_list, + LineartObjectInfo *obi, + int thread_count, + int this_face_count) +{ + LineartObjectLoadTaskInfo *use_olti = olti_list; + uint64_t min_face = use_olti->total_faces; + for (int i = 0; i < thread_count; i++) { + if (olti_list[i].total_faces < min_face) { + min_face = olti_list[i].total_faces; + use_olti = &olti_list[i]; + } + } + + use_olti->total_faces += this_face_count; + obi->next = use_olti->pending; + use_olti->pending = obi; +} + +static bool lineart_geometry_check_visible(double model_view_proj[4][4], + double shift_x, + double shift_y, + Mesh *use_mesh) +{ + if (!use_mesh) { + return false; + } + float mesh_min[3], mesh_max[3]; + INIT_MINMAX(mesh_min, mesh_max); + BKE_mesh_minmax(use_mesh, mesh_min, mesh_max); + BoundBox bb = {0}; + BKE_boundbox_init_from_minmax(&bb, mesh_min, mesh_max); + + double co[8][4]; + double tmp[3]; + for (int i = 0; i < 8; i++) { + copy_v3db_v3fl(co[i], bb.vec[i]); + copy_v3_v3_db(tmp, co[i]); + mul_v4_m4v3_db(co[i], model_view_proj, tmp); + co[i][0] -= shift_x * 2 * co[i][3]; + co[i][1] -= shift_y * 2 * co[i][3]; + } + + bool cond[6] = {true, true, true, true, true, true}; + /* Because for a point to be inside clip space, it must satisfy `-Wc <= XYCc <= Wc`, here if + * all verts falls to the same side of the clip space border, we know it's outside view. */ + for (int i = 0; i < 8; i++) { + cond[0] &= (co[i][0] < -co[i][3]); + cond[1] &= (co[i][0] > co[i][3]); + cond[2] &= (co[i][1] < -co[i][3]); + cond[3] &= (co[i][1] > co[i][3]); + cond[4] &= (co[i][2] < -co[i][3]); + cond[5] &= (co[i][2] > co[i][3]); + } + for (int i = 0; i < 6; i++) { + if (cond[i]) { + return false; + } + } + return true; +} + +static void lineart_object_load_single_instance(LineartData *ld, + Depsgraph *depsgraph, + Scene *scene, + Object *ob, + Object *ref_ob, + float use_mat[4][4], + bool is_render, + LineartObjectLoadTaskInfo *olti, + int thread_count, + int obindex) +{ + LineartObjectInfo *obi = static_cast( + 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); + mul_m4db_m4db_m4fl_uniq(obi->model_view, ld->conf.view, use_mat); + + if (!ELEM(ob->type, OB_MESH, OB_MBALL, OB_CURVES_LEGACY, OB_SURF, OB_FONT)) { + return; + } + if (ob->type == OB_MESH) { + use_mesh = BKE_object_get_evaluated_mesh(ob); + if (use_mesh->edit_mesh) { + /* If the object is being edited, then the mesh is not evaluated fully into the final + * result, do not load them. */ + return; + } + } + else { + use_mesh = BKE_mesh_new_from_object(depsgraph, ob, true, true); + } + + /* In case we still can not get any mesh geometry data from the object */ + if (!use_mesh) { + return; + } + + if (!lineart_geometry_check_visible( + obi->model_view_proj, ld->conf.shift_x, ld->conf.shift_y, use_mesh)) { + return; + } + + if (ob->type != OB_MESH) { + obi->free_use_mesh = true; + } + + /* Make normal matrix. */ + float imat[4][4]; + invert_m4_m4(imat, use_mat); + transpose_m4(imat); + copy_m4d_m4(obi->normal, imat); + + obi->original_me = use_mesh; + obi->original_ob = (ref_ob->id.orig_id ? (Object *)ref_ob->id.orig_id : (Object *)ref_ob); + obi->original_ob_eval = DEG_get_evaluated_object(depsgraph, obi->original_ob); + lineart_geometry_load_assign_thread(olti, obi, thread_count, use_mesh->totpoly); +} + +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]; + + if (!do_shadow_casting) { + Camera *cam = static_cast(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); + } + 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); + + 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); + + double t_start; + if (G.debug_value == 4000) { + t_start = PIL_check_seconds_timer(); + } + + 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 = static_cast(lineart_mem_acquire( + &ld->render_data_pool, sizeof(LineartObjectLoadTaskInfo) * thread_count)); + + eEvaluationMode eval_mode = DEG_get_mode(depsgraph); + bool is_render = eval_mode == DAG_EVAL_RENDER; + + int flags = DEG_ITER_OBJECT_FLAG_LINKED_DIRECTLY | DEG_ITER_OBJECT_FLAG_LINKED_VIA_SET | + DEG_ITER_OBJECT_FLAG_VISIBLE; + + /* Instance duplicated & particles. */ + if (allow_duplicates) { + flags |= DEG_ITER_OBJECT_FLAG_DUPLI; + } + + DEGObjectIterSettings deg_iter_settings = {0}; + deg_iter_settings.depsgraph = depsgraph; + deg_iter_settings.flags = flags; + + /* XXX(@Yiming): Temporary solution, this iterator is technically unsafe to use *during* + * depsgraph evaluation, see D14997 for detailed explanations. */ + DEG_OBJECT_ITER_BEGIN (°_iter_settings, ob) { + + obindex++; + + Object *eval_ob = DEG_get_evaluated_object(depsgraph, ob); + + if (!eval_ob) { + continue; + } + + /* DEG_OBJECT_ITER_BEGIN will include the instanced mesh of these curve object types, so don't + * load them twice. */ + if (allow_duplicates && ELEM(ob->type, OB_CURVES_LEGACY, OB_FONT, OB_SURF)) { + continue; + } + + 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->object_to_world, + is_render, + olti, + thread_count, + obindex); + } + } + DEG_OBJECT_ITER_END; + + TaskPool *tp = BLI_task_pool_create(nullptr, TASK_PRIORITY_HIGH); + + if (G.debug_value == 4000) { + printf("thread count: %d\n", thread_count); + } + 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, nullptr); + } + BLI_task_pool_work_and_wait(tp); + BLI_task_pool_free(tp); + + /* The step below is to serialize vertex index in the whole scene, so + * lineart_triangle_share_edge() can work properly from the lack of triangle adjacent info. */ + int global_i = 0; + + int edge_count = 0; + for (int i = 0; i < thread_count; i++) { + for (LineartObjectInfo *obi = olti[i].pending; obi; obi = obi->next) { + if (!obi->v_eln) { + continue; + } + edge_count += obi->pending_edges.next; + } + } + lineart_finalize_object_edge_array_reserve(&ld->pending_edges, edge_count); + + for (int i = 0; i < thread_count; i++) { + for (LineartObjectInfo *obi = olti[i].pending; obi; obi = obi->next) { + if (!obi->v_eln) { + continue; + } + LineartVert *v = (LineartVert *)obi->v_eln->pointer; + int v_count = obi->v_eln->element_count; + obi->v_eln->global_index_offset = global_i; + for (int vi = 0; vi < v_count; vi++) { + v[vi].index += global_i; + } + /* Register a global index increment. See #lineart_triangle_share_edge() and + * #lineart_main_load_geometries() for detailed. It's okay that global_vindex might + * eventually overflow, in such large scene it's virtually impossible for two vertex of the + * same numeric index to come close together. */ + obi->global_i_offset = global_i; + global_i += v_count; + lineart_finalize_object_edge_array(&ld->pending_edges, obi); + } + } + + if (G.debug_value == 4000) { + double t_elapsed = PIL_check_seconds_timer() - t_start; + printf("Line art loading time: %lf\n", t_elapsed); + printf("Discarded %d object from bound box check\n", bound_box_discard_count); + } +} + +/** + * Returns the two other verts of the triangle given a vertex. Returns false if the given vertex + * doesn't belong to this triangle. + */ +static bool lineart_triangle_get_other_verts(const LineartTriangle *tri, + const LineartVert *vt, + LineartVert **l, + LineartVert **r) +{ + if (tri->v[0] == vt) { + *l = tri->v[1]; + *r = tri->v[2]; + return true; + } + if (tri->v[1] == vt) { + *l = tri->v[2]; + *r = tri->v[0]; + return true; + } + if (tri->v[2] == vt) { + *l = tri->v[0]; + *r = tri->v[1]; + return true; + } + return false; +} + +bool lineart_edge_from_triangle(const LineartTriangle *tri, + const LineartEdge *e, + bool allow_overlapping_edges) +{ + 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) { +#define LRT_TRI_SAME_POINT(tri, i, pt) \ + ((LRT_DOUBLE_CLOSE_ENOUGH(tri->v[i]->gloc[0], pt->gloc[0]) && \ + LRT_DOUBLE_CLOSE_ENOUGH(tri->v[i]->gloc[1], pt->gloc[1]) && \ + LRT_DOUBLE_CLOSE_ENOUGH(tri->v[i]->gloc[2], pt->gloc[2])) || \ + (LRT_DOUBLE_CLOSE_ENOUGH(tri->v[i]->gloc[0], pt->gloc[0]) && \ + LRT_DOUBLE_CLOSE_ENOUGH(tri->v[i]->gloc[1], pt->gloc[1]) && \ + LRT_DOUBLE_CLOSE_ENOUGH(tri->v[i]->gloc[2], pt->gloc[2]))) + if ((LRT_TRI_SAME_POINT(tri, 0, e->v1) || LRT_TRI_SAME_POINT(tri, 1, e->v1) || + LRT_TRI_SAME_POINT(tri, 2, e->v1)) && + (LRT_TRI_SAME_POINT(tri, 0, e->v2) || LRT_TRI_SAME_POINT(tri, 1, e->v2) || + LRT_TRI_SAME_POINT(tri, 2, e->v2))) { + return true; + } +#undef LRT_TRI_SAME_POINT + } + return false; +} + +/* Sorting three intersection points from min to max, + * the order for each intersection is set in `lst[0]` to `lst[2]`. */ +#define INTERSECT_SORT_MIN_TO_MAX_3(ia, ib, ic, lst) \ + { \ + lst[0] = LRT_MIN3_INDEX(ia, ib, ic); \ + lst[1] = (((ia <= ib && ib <= ic) || (ic <= ib && ib <= ia)) ? \ + 1 : \ + (((ic <= ia && ia <= ib) || (ib < ia && ia <= ic)) ? 0 : 2)); \ + lst[2] = LRT_MAX3_INDEX(ia, ib, ic); \ + } + +/* `ia ib ic` are ordered. */ +#define INTERSECT_JUST_GREATER(is, order, num, index) \ + { \ + index = (num < is[order[0]] ? \ + order[0] : \ + (num < is[order[1]] ? order[1] : (num < is[order[2]] ? order[2] : -1))); \ + } + +/* `ia ib ic` are ordered. */ +#define INTERSECT_JUST_SMALLER(is, order, num, index) \ + { \ + index = (num > is[order[2]] ? \ + order[2] : \ + (num > is[order[1]] ? order[1] : (num > is[order[0]] ? order[0] : -1))); \ + } + +#define LRT_ISEC(index) (index == 0 ? isec_e1 : (index == 1 ? isec_e2 : isec_e3)) +#define LRT_PARALLEL(index) (index == 0 ? para_e1 : (index == 1 ? para_e2 : para_e3)) + +/** + * This is the main function to calculate + * the occlusion status between 1(one) triangle and 1(one) line. + * if returns true, then from/to will carry the occluded segments + * in ratio from `e->v1` to `e->v2`. The line is later cut with these two values. + * + * TODO(@Yiming): This function uses a convoluted method that needs to be redesigned. + * + * 1) The #lineart_intersect_seg_seg() and #lineart_point_triangle_relation() are separate calls, + * which would potentially return results that doesn't agree, especially when it's an edge + * extruding from one of the triangle's point. To get the information using one math process can + * solve this problem. + * + * 2) Currently using discrete a/b/c/para_e1/para_e2/para_e3/is[3] values for storing + * intersection/edge_aligned/intersection_order info, which isn't optimal, needs a better + * representation (likely a struct) for readability and clarity of code path. + * + * I keep this function as-is because it's still fast, and more importantly the output value + * threshold is already in tune with the cutting function in the next stage. + * 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(const LineartTriangle *tri, + const LineartEdge *e, + const double *override_camera_loc, + const bool override_cam_is_persp, + const bool allow_overlapping_edges, + 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) +{ + double cross_ratios[3] = {0}; + int cross_order[3]; + int cross_v1 = -1, cross_v2 = -1; + /* If the edge intersects with the triangle edges (including extensions). */ + int isec_e1, isec_e2, isec_e3; + /* If edge is parallel to one of the edges in the triangle. */ + bool para_e1, para_e2, para_e3; + enum LineartPointTri state_v1 = LRT_OUTSIDE_TRIANGLE, state_v2 = LRT_OUTSIDE_TRIANGLE; + + double dir_v1[3]; + double dir_v2[3]; + double view_vector[4]; + double dir_cam[3]; + double dot_v1, dot_v2, dot_v1a, dot_v2a; + double dot_f; + double gloc[4], trans[4]; + double cut = -1; + + double *LFBC = e->v1->fbcoord, *RFBC = e->v2->fbcoord, *FBC0 = tri->v[0]->fbcoord, + *FBC1 = tri->v[1]->fbcoord, *FBC2 = tri->v[2]->fbcoord; + + /* Overlapping not possible, return early. */ + if ((MAX3(FBC0[0], FBC1[0], FBC2[0]) < MIN2(LFBC[0], RFBC[0])) || + (MIN3(FBC0[0], FBC1[0], FBC2[0]) > MAX2(LFBC[0], RFBC[0])) || + (MAX3(FBC0[1], FBC1[1], FBC2[1]) < MIN2(LFBC[1], RFBC[1])) || + (MIN3(FBC0[1], FBC1[1], FBC2[1]) > MAX2(LFBC[1], RFBC[1])) || + (MIN3(FBC0[3], FBC1[3], FBC2[3]) > MAX2(LFBC[3], RFBC[3]))) { + return false; + } + + /* If the line is one of the edge in the triangle, then it's not occluded. */ + if (lineart_edge_from_triangle(tri, e, allow_overlapping_edges)) { + return false; + } + + /* Check if the line visually crosses one of the edge in the triangle. */ + isec_e1 = lineart_intersect_seg_seg(LFBC, RFBC, FBC0, FBC1, &cross_ratios[0], ¶_e1); + isec_e2 = lineart_intersect_seg_seg(LFBC, RFBC, FBC1, FBC2, &cross_ratios[1], ¶_e2); + isec_e3 = lineart_intersect_seg_seg(LFBC, RFBC, FBC2, FBC0, &cross_ratios[2], ¶_e3); + + /* Sort the intersection distance. */ + INTERSECT_SORT_MIN_TO_MAX_3(cross_ratios[0], cross_ratios[1], cross_ratios[2], cross_order); + + sub_v3_v3v3_db(dir_v1, e->v1->gloc, tri->v[0]->gloc); + sub_v3_v3v3_db(dir_v2, e->v2->gloc, tri->v[0]->gloc); + + copy_v3_v3_db(dir_cam, camera_dir); + copy_v3_v3_db(view_vector, override_camera_loc); + if (override_cam_is_persp) { + sub_v3_v3v3_db(dir_cam, view_vector, tri->v[0]->gloc); + } + + dot_v1 = dot_v3v3_db(dir_v1, tri->gn); + 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 + * files. + */ + if (fabs(dot_f) < FLT_EPSILON) { + return false; + } + + /* Whether two end points are inside/on_the_edge/outside of the triangle. */ + state_v1 = lineart_point_triangle_relation(LFBC, FBC0, FBC1, FBC2); + state_v2 = lineart_point_triangle_relation(RFBC, FBC0, FBC1, FBC2); + + /* If the edge doesn't visually cross any edge of the triangle... */ + if (!isec_e1 && !isec_e2 && !isec_e3) { + /* And if both end point from the edge is outside of the triangle... */ + if ((!state_v1) && (!state_v2)) { + return 0; /* We don't have any occlusion. */ + } + } + + /* Determine the cut position. */ + + dot_v1a = fabs(dot_v1); + if (dot_v1a < DBL_EPSILON) { + dot_v1a = 0; + dot_v1 = 0; + } + dot_v2a = fabs(dot_v2); + if (dot_v2a < DBL_EPSILON) { + dot_v2a = 0; + dot_v2 = 0; + } + if (dot_v1 - dot_v2 == 0) { + cut = 100000; + } + else if (dot_v1 * dot_v2 <= 0) { + cut = dot_v1a / fabs(dot_v1 - dot_v2); + } + else { + cut = fabs(dot_v2 + dot_v1) / fabs(dot_v1 - dot_v2); + cut = dot_v2a > dot_v1a ? 1 - cut : cut; + } + + /* 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, m_view_projection, gloc); + mul_v3db_db(trans, (1 / trans[3])); + trans[0] -= cam_shift_x * 2; + trans[1] -= cam_shift_y * 2; + /* To accommodate `k=0` and `k=inf` (vertical) lines. here the cut is in image space. */ + if (fabs(e->v1->fbcoord[0] - e->v2->fbcoord[0]) > + fabs(e->v1->fbcoord[1] - e->v2->fbcoord[1])) { + cut = ratiod(e->v1->fbcoord[0], e->v2->fbcoord[0], trans[0]); + } + else { + cut = ratiod(e->v1->fbcoord[1], e->v2->fbcoord[1], trans[1]); + } + } + +#define LRT_GUARD_NOT_FOUND \ + if (cross_v1 < 0 || cross_v2 < 0) { \ + return false; \ + } + + /* Determine the pair of edges that the line has crossed. The "|" symbol in the comment + * indicates triangle boundary. DBL_TRIANGLE_LIM is needed to for floating point precision + * tolerance. */ + + if (state_v1 == LRT_INSIDE_TRIANGLE) { + /* Left side is in the triangle. */ + if (state_v2 == LRT_INSIDE_TRIANGLE) { + /* | l---r | */ + INTERSECT_JUST_SMALLER(cross_ratios, cross_order, DBL_TRIANGLE_LIM, cross_v1); + INTERSECT_JUST_GREATER(cross_ratios, cross_order, 1 - DBL_TRIANGLE_LIM, cross_v2); + } + else if (state_v2 == LRT_ON_TRIANGLE) { + /* | l------r| */ + INTERSECT_JUST_SMALLER(cross_ratios, cross_order, DBL_TRIANGLE_LIM, cross_v1); + INTERSECT_JUST_GREATER(cross_ratios, cross_order, 1 - DBL_TRIANGLE_LIM, cross_v2); + } + else if (state_v2 == LRT_OUTSIDE_TRIANGLE) { + /* | l-------|------r */ + INTERSECT_JUST_SMALLER(cross_ratios, cross_order, DBL_TRIANGLE_LIM, cross_v1); + INTERSECT_JUST_GREATER(cross_ratios, cross_order, 0, cross_v2); + } + } + else if (state_v1 == LRT_ON_TRIANGLE) { + /* Left side is on some edge of the triangle. */ + if (state_v2 == LRT_INSIDE_TRIANGLE) { + /* |l------r | */ + INTERSECT_JUST_SMALLER(cross_ratios, cross_order, DBL_TRIANGLE_LIM, cross_v1); + INTERSECT_JUST_GREATER(cross_ratios, cross_order, 1 - DBL_TRIANGLE_LIM, cross_v2); + } + else if (state_v2 == LRT_ON_TRIANGLE) { + /* |l---------r| */ + INTERSECT_JUST_SMALLER(cross_ratios, cross_order, DBL_TRIANGLE_LIM, cross_v1); + INTERSECT_JUST_GREATER(cross_ratios, cross_order, 1 - DBL_TRIANGLE_LIM, cross_v2); + } + else if (state_v2 == LRT_OUTSIDE_TRIANGLE) { + /* |l----------|-------r (crossing the triangle) [OR] + * r---------|l | (not crossing the triangle) */ + INTERSECT_JUST_GREATER(cross_ratios, cross_order, DBL_TRIANGLE_LIM, cross_v2); + if (cross_v2 >= 0 && LRT_ISEC(cross_v2) && cross_ratios[cross_v2] > (DBL_TRIANGLE_LIM)) { + INTERSECT_JUST_SMALLER(cross_ratios, cross_order, DBL_TRIANGLE_LIM, cross_v1); + } + else { + INTERSECT_JUST_SMALLER(cross_ratios, cross_order, DBL_TRIANGLE_LIM, cross_v2); + if (cross_v2 > 0) { + INTERSECT_JUST_SMALLER(cross_ratios, cross_order, cross_ratios[cross_v2], cross_v1); + } + } + LRT_GUARD_NOT_FOUND + /* We could have the edge being completely parallel to the triangle where there isn't a + * viable occlusion result. */ + if ((LRT_PARALLEL(cross_v1) && !LRT_ISEC(cross_v1)) || + (LRT_PARALLEL(cross_v2) && !LRT_ISEC(cross_v2))) { + return false; + } + } + } + else if (state_v1 == LRT_OUTSIDE_TRIANGLE) { + /* Left side is outside of the triangle. */ + if (state_v2 == LRT_INSIDE_TRIANGLE) { + /* l---|---r | */ + INTERSECT_JUST_SMALLER(cross_ratios, cross_order, 1 - DBL_TRIANGLE_LIM, cross_v1); + INTERSECT_JUST_GREATER(cross_ratios, cross_order, 1 - DBL_TRIANGLE_LIM, cross_v2); + } + else if (state_v2 == LRT_ON_TRIANGLE) { + /* |r----------|-------l (crossing the triangle) [OR] + * l---------|r | (not crossing the triangle) */ + INTERSECT_JUST_SMALLER(cross_ratios, cross_order, 1 - DBL_TRIANGLE_LIM, cross_v1); + if (cross_v1 >= 0 && LRT_ISEC(cross_v1) && cross_ratios[cross_v1] < (1 - DBL_TRIANGLE_LIM)) { + INTERSECT_JUST_GREATER(cross_ratios, cross_order, 1 - DBL_TRIANGLE_LIM, cross_v2); + } + else { + INTERSECT_JUST_GREATER(cross_ratios, cross_order, 1 - DBL_TRIANGLE_LIM, cross_v1); + if (cross_v1 > 0) { + INTERSECT_JUST_GREATER(cross_ratios, cross_order, cross_ratios[cross_v1], cross_v2); + } + } + LRT_GUARD_NOT_FOUND + /* The same logic applies as above case. */ + if ((LRT_PARALLEL(cross_v1) && !LRT_ISEC(cross_v1)) || + (LRT_PARALLEL(cross_v2) && !LRT_ISEC(cross_v2))) { + return false; + } + } + else if (state_v2 == LRT_OUTSIDE_TRIANGLE) { + /* l---|----|----r (crossing the triangle) [OR] + * l----r | | (not crossing the triangle) */ + INTERSECT_JUST_GREATER(cross_ratios, cross_order, -DBL_TRIANGLE_LIM, cross_v1); + if (cross_v1 >= 0 && LRT_ISEC(cross_v1)) { + INTERSECT_JUST_GREATER(cross_ratios, cross_order, cross_ratios[cross_v1], cross_v2); + } + else { + if (cross_v1 >= 0) { + INTERSECT_JUST_GREATER(cross_ratios, cross_order, cross_ratios[cross_v1], cross_v1); + if (cross_v1 >= 0) { + INTERSECT_JUST_GREATER(cross_ratios, cross_order, cross_ratios[cross_v1], cross_v2); + } + } + } + } + } + + LRT_GUARD_NOT_FOUND + + double dot_1f = dot_v1 * dot_f, dot_2f = dot_v2 * dot_f; + + /* Determine the start and end point of image space cut on a line. */ + if (dot_1f <= 0 && dot_2f <= 0 && (dot_v1 || dot_v2)) { + *from = MAX2(0, cross_ratios[cross_v1]); + *to = MIN2(1, cross_ratios[cross_v2]); + if (*from >= *to) { + return false; + } + return true; + } + if (dot_1f >= 0 && dot_2f <= 0 && (dot_v1 || dot_v2)) { + *from = MAX2(cut, cross_ratios[cross_v1]); + *to = MIN2(1, cross_ratios[cross_v2]); + if (*from >= *to) { + return false; + } + return true; + } + if (dot_1f <= 0 && dot_2f >= 0 && (dot_v1 || dot_v2)) { + *from = MAX2(0, cross_ratios[cross_v1]); + *to = MIN2(cut, cross_ratios[cross_v2]); + if (*from >= *to) { + return false; + } + return true; + } + + /* Unlikely, but here's the default failed value if anything fall through. */ + return false; +} + +#undef INTERSECT_SORT_MIN_TO_MAX_3 +#undef INTERSECT_JUST_GREATER +#undef INTERSECT_JUST_SMALLER +#undef LRT_ISEC +#undef LRT_PARALLEL + +/** + * At this stage of the computation we don't have triangle adjacent info anymore, + * so we can only compare the global vert index. + */ +static bool lineart_triangle_share_edge(const LineartTriangle *l, const LineartTriangle *r) +{ + if (l->v[0]->index == r->v[0]->index) { + if (l->v[1]->index == r->v[1]->index || l->v[1]->index == r->v[2]->index || + l->v[2]->index == r->v[2]->index || l->v[2]->index == r->v[1]->index) { + return true; + } + } + if (l->v[0]->index == r->v[1]->index) { + if (l->v[1]->index == r->v[0]->index || l->v[1]->index == r->v[2]->index || + l->v[2]->index == r->v[2]->index || l->v[2]->index == r->v[0]->index) { + return true; + } + } + if (l->v[0]->index == r->v[2]->index) { + if (l->v[1]->index == r->v[1]->index || l->v[1]->index == r->v[0]->index || + l->v[2]->index == r->v[0]->index || l->v[2]->index == r->v[1]->index) { + return true; + } + } + if (l->v[1]->index == r->v[0]->index) { + if (l->v[2]->index == r->v[1]->index || l->v[2]->index == r->v[2]->index || + l->v[0]->index == r->v[2]->index || l->v[0]->index == r->v[1]->index) { + return true; + } + } + if (l->v[1]->index == r->v[1]->index) { + if (l->v[2]->index == r->v[0]->index || l->v[2]->index == r->v[2]->index || + l->v[0]->index == r->v[2]->index || l->v[0]->index == r->v[0]->index) { + return true; + } + } + if (l->v[1]->index == r->v[2]->index) { + if (l->v[2]->index == r->v[1]->index || l->v[2]->index == r->v[0]->index || + l->v[0]->index == r->v[0]->index || l->v[0]->index == r->v[1]->index) { + return true; + } + } + + /* Otherwise not possible. */ + return false; +} + +static LineartVert *lineart_triangle_share_point(const LineartTriangle *l, + const LineartTriangle *r) +{ + if (l->v[0] == r->v[0]) { + return r->v[0]; + } + if (l->v[0] == r->v[1]) { + return r->v[1]; + } + if (l->v[0] == r->v[2]) { + return r->v[2]; + } + if (l->v[1] == r->v[0]) { + return r->v[0]; + } + if (l->v[1] == r->v[1]) { + return r->v[1]; + } + if (l->v[1] == r->v[2]) { + return r->v[2]; + } + if (l->v[2] == r->v[0]) { + return r->v[0]; + } + if (l->v[2] == r->v[1]) { + return r->v[1]; + } + if (l->v[2] == r->v[2]) { + return r->v[2]; + } + return nullptr; +} + +static bool lineart_triangle_2v_intersection_math( + LineartVert *v1, LineartVert *v2, LineartTriangle *tri, const double *last, double *rv) +{ + /* Direction vectors for the edge verts. We will check if the verts are on the same side of the + * triangle or not. */ + double dir_v1[3], dir_v2[3]; + double dot_v1, dot_v2; + double gloc[3]; + + sub_v3_v3v3_db(dir_v1, v1->gloc, tri->v[0]->gloc); + sub_v3_v3v3_db(dir_v2, v2->gloc, tri->v[0]->gloc); + + dot_v1 = dot_v3v3_db(dir_v1, tri->gn); + dot_v2 = dot_v3v3_db(dir_v2, tri->gn); + + if (dot_v1 * dot_v2 > 0 || (!dot_v1 && !dot_v2)) { + return false; + } + + dot_v1 = fabs(dot_v1); + dot_v2 = fabs(dot_v2); + + interp_v3_v3v3_db(gloc, v1->gloc, v2->gloc, dot_v1 / (dot_v1 + dot_v2)); + + /* Due to precision issue, we might end up with the same point as the one we already detected. */ + if (last && LRT_DOUBLE_CLOSE_ENOUGH(last[0], gloc[0]) && + LRT_DOUBLE_CLOSE_ENOUGH(last[1], gloc[1]) && LRT_DOUBLE_CLOSE_ENOUGH(last[2], gloc[2])) { + return false; + } + + if (!lineart_point_inside_triangle3d(gloc, tri->v[0]->gloc, tri->v[1]->gloc, tri->v[2]->gloc)) { + return false; + } + + copy_v3_v3_db(rv, gloc); + + return true; +} + +static bool lineart_triangle_intersect_math(LineartTriangle *tri, + LineartTriangle *t2, + double *v1, + double *v2) +{ + double *next = v1, *last = nullptr; + LineartVert *sv1, *sv2; + + LineartVert *share = lineart_triangle_share_point(t2, tri); + + if (share) { + /* If triangles have sharing points like `abc` and `acd`, then we only need to detect `bc` + * against `acd` or `cd` against `abc`. */ + + lineart_triangle_get_other_verts(tri, share, &sv1, &sv2); + + copy_v3_v3_db(v1, share->gloc); + + if (!lineart_triangle_2v_intersection_math(sv1, sv2, t2, 0, v2)) { + lineart_triangle_get_other_verts(t2, share, &sv1, &sv2); + if (lineart_triangle_2v_intersection_math(sv1, sv2, tri, 0, v2)) { + return true; + } + } + } + else { + /* If not sharing any points, then we need to try all the possibilities. */ + + if (lineart_triangle_2v_intersection_math(tri->v[0], tri->v[1], t2, 0, v1)) { + next = v2; + last = v1; + } + + if (lineart_triangle_2v_intersection_math(tri->v[1], tri->v[2], t2, last, next)) { + if (last) { + return true; + } + next = v2; + last = v1; + } + if (lineart_triangle_2v_intersection_math(tri->v[2], tri->v[0], t2, last, next)) { + if (last) { + return true; + } + next = v2; + last = v1; + } + + if (lineart_triangle_2v_intersection_math(t2->v[0], t2->v[1], tri, last, next)) { + if (last) { + return true; + } + next = v2; + last = v1; + } + if (lineart_triangle_2v_intersection_math(t2->v[1], t2->v[2], tri, last, next)) { + if (last) { + return true; + } + next = v2; + last = v1; + } + if (lineart_triangle_2v_intersection_math(t2->v[2], t2->v[0], tri, last, next)) { + if (last) { + return true; + } + next = v2; + last = v1; + } + } + return false; +} + +static void lineart_add_isec_thread(LineartIsecThread *th, + const double *v1, + const double *v2, + LineartTriangle *tri1, + LineartTriangle *tri2) +{ + if (th->current == th->max) { + + LineartIsecSingle *new_array = static_cast( + MEM_mallocN(sizeof(LineartIsecSingle) * th->max * 2, "LineartIsecSingle")); + memcpy(new_array, th->array, sizeof(LineartIsecSingle) * th->max); + th->max *= 2; + MEM_freeN(th->array); + th->array = new_array; + } + LineartIsecSingle *isec_single = &th->array[th->current]; + copy_v3_v3_db(isec_single->v1, v1); + copy_v3_v3_db(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++; +} + +#define LRT_ISECT_TRIANGLE_PER_THREAD 4096 + +static bool lineart_schedule_new_triangle_task(LineartIsecThread *th) +{ + LineartData *ld = th->ld; + int remaining = LRT_ISECT_TRIANGLE_PER_THREAD; + + BLI_spin_lock(&ld->lock_task); + LineartElementLinkNode *eln = ld->isect_scheduled_up_to; + + if (!eln) { + BLI_spin_unlock(&ld->lock_task); + return false; + } + + th->pending_from = eln; + th->index_from = ld->isect_scheduled_up_to_index; + + while (remaining > 0 && eln) { + int remaining_this_eln = eln->element_count - ld->isect_scheduled_up_to_index; + int added_count = MIN2(remaining, remaining_this_eln); + remaining -= added_count; + if (remaining || added_count == remaining_this_eln) { + eln = eln->next; + ld->isect_scheduled_up_to = eln; + ld->isect_scheduled_up_to_index = 0; + } + else { + ld->isect_scheduled_up_to_index += added_count; + } + } + + th->pending_to = eln ? eln : + static_cast( + ld->geom.triangle_buffer_pointers.last); + th->index_to = ld->isect_scheduled_up_to_index; + + BLI_spin_unlock(&ld->lock_task); + + return true; +} + +/* This function initializes two things: + * 1) Triangle array scheduling info, for each worker thread to get its chunk from the scheduler. + * 2) Per-thread intersection result array. Does not store actual #LineartEdge, these results will + * be finalized by #lineart_create_edges_from_isec_data + */ +static void lineart_init_isec_thread(LineartIsecData *d, LineartData *ld, int thread_count) +{ + d->threads = static_cast( + MEM_callocN(sizeof(LineartIsecThread) * thread_count, "LineartIsecThread arr")); + d->ld = ld; + d->thread_count = thread_count; + + ld->isect_scheduled_up_to = static_cast( + ld->geom.triangle_buffer_pointers.first); + ld->isect_scheduled_up_to_index = 0; + + for (int i = 0; i < thread_count; i++) { + LineartIsecThread *it = &d->threads[i]; + it->array = static_cast( + MEM_mallocN(sizeof(LineartIsecSingle) * 100, "LineartIsecSingle arr")); + it->max = 100; + it->current = 0; + it->thread_id = i; + it->ld = ld; + } +} + +static void lineart_destroy_isec_thread(LineartIsecData *d) +{ + for (int i = 0; i < d->thread_count; i++) { + LineartIsecThread *it = &d->threads[i]; + MEM_freeN(it->array); + } + MEM_freeN(d->threads); +} + +static void lineart_triangle_intersect_in_bounding_area(LineartTriangle *tri, + LineartBoundingArea *ba, + LineartIsecThread *th, + int up_to) +{ + BLI_assert(th != nullptr); + + if (!th) { + return; + } + + double *G0 = tri->v[0]->gloc, *G1 = tri->v[1]->gloc, *G2 = tri->v[2]->gloc; + + /* If this _is_ the smallest subdivision bounding area, then do the intersections there. */ + for (int i = 0; i < up_to; i++) { + /* Testing_triangle->testing[0] is used to store pairing triangle reference. + * See definition of LineartTriangleThread for more info. */ + LineartTriangle *testing_triangle = ba->linked_triangles[i]; + LineartTriangleThread *tt = (LineartTriangleThread *)testing_triangle; + + if (testing_triangle == tri || tt->testing_e[th->thread_id] == (LineartEdge *)tri) { + continue; + } + tt->testing_e[th->thread_id] = (LineartEdge *)tri; + + if (!((testing_triangle->flags | tri->flags) & LRT_TRIANGLE_FORCE_INTERSECTION)) { + if (((testing_triangle->flags | tri->flags) & LRT_TRIANGLE_NO_INTERSECTION) || + (testing_triangle->flags & tri->flags & LRT_TRIANGLE_INTERSECTION_ONLY)) { + continue; + } + } + + double *RG0 = testing_triangle->v[0]->gloc, *RG1 = testing_triangle->v[1]->gloc, + *RG2 = testing_triangle->v[2]->gloc; + + /* Bounding box not overlapping or triangles share edges, not potential of intersecting. */ + if ((MIN3(G0[2], G1[2], G2[2]) > MAX3(RG0[2], RG1[2], RG2[2])) || + (MAX3(G0[2], G1[2], G2[2]) < MIN3(RG0[2], RG1[2], RG2[2])) || + (MIN3(G0[0], G1[0], G2[0]) > MAX3(RG0[0], RG1[0], RG2[0])) || + (MAX3(G0[0], G1[0], G2[0]) < MIN3(RG0[0], RG1[0], RG2[0])) || + (MIN3(G0[1], G1[1], G2[1]) > MAX3(RG0[1], RG1[1], RG2[1])) || + (MAX3(G0[1], G1[1], G2[1]) < MIN3(RG0[1], RG1[1], RG2[1])) || + lineart_triangle_share_edge(tri, testing_triangle)) { + continue; + } + + /* If we do need to compute intersection, then finally do it. */ + + double iv1[3], iv2[3]; + if (lineart_triangle_intersect_math(tri, testing_triangle, iv1, iv2)) { + lineart_add_isec_thread(th, iv1, iv2, tri, testing_triangle); + } + } +} + +/** + * The calculated view vector will point towards the far-plane from the camera position. + */ +void lineart_main_get_view_vector(LineartData *ld) +{ + float direction[3] = {0, 0, 1}; + float trans[3]; + float inv[4][4]; + float obmat_no_scale[4][4]; + + copy_m4_m4(obmat_no_scale, ld->conf.cam_obmat); + normalize_v3(obmat_no_scale[0]); + normalize_v3(obmat_no_scale[1]); + normalize_v3(obmat_no_scale[2]); + invert_m4_m4(inv, obmat_no_scale); + transpose_m4(inv); + mul_v3_mat3_m4v3(trans, inv, direction); + copy_m4_m4(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_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 == nullptr) { + return; + } + + BLI_listbase_clear(&ld->chains); + BLI_listbase_clear(&ld->wasted_cuts); + + BLI_listbase_clear(&ld->geom.vertex_buffer_pointers); + BLI_listbase_clear(&ld->geom.line_buffer_pointers); + BLI_listbase_clear(&ld->geom.triangle_buffer_pointers); + + 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 == nullptr) { + 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; + + lineart_destroy_render_data(ld); + + if (ld) { + MEM_freeN(ld); + lmd->la_data_ptr = nullptr; + } + + if (G.debug_value == 4000) { + printf("LRT: Destroyed render data.\n"); + } +} + +static LineartCache *lineart_init_cache(void) +{ + LineartCache *lc = static_cast( + MEM_callocN(sizeof(LineartCache), "Lineart Cache")); + return lc; +} + +void MOD_lineart_clear_cache(LineartCache **lc) +{ + if (!(*lc)) { + return; + } + lineart_mem_destroy(&((*lc)->chain_data_pool)); + MEM_freeN(*lc); + (*lc) = nullptr; +} + +static LineartData *lineart_create_render_buffer(Scene *scene, + LineartGpencilModifierData *lmd, + Object *camera, + Object *active_camera, + LineartCache *lc) +{ + LineartData *ld = static_cast( + MEM_callocN(sizeof(LineartData), "Line Art render buffer")); + lmd->cache = lc; + lmd->la_data_ptr = ld; + lc->all_enabled_edge_types = lmd->edge_types_override; + + if (!scene || !camera || !lc) { + return nullptr; + } + Camera *c = static_cast(camera->data); + double clipping_offset = 0; + + if (lmd->calculation_flags & LRT_ALLOW_CLIPPING_BOUNDARIES) { + /* This way the clipped lines are "stably visible" by prevents depth buffer artifacts. */ + clipping_offset = 0.0001; + } + + copy_v3db_v3fl(ld->conf.camera_pos, camera->object_to_world[3]); + if (active_camera) { + copy_v3db_v3fl(ld->conf.active_camera_pos, active_camera->object_to_world[3]); + } + copy_m4_m4(ld->conf.cam_obmat, camera->object_to_world); + + ld->conf.cam_is_persp = (c->type == CAM_PERSP); + ld->conf.near_clip = c->clip_start + clipping_offset; + ld->conf.far_clip = c->clip_end - clipping_offset; + ld->w = scene->r.xsch; + ld->h = scene->r.ysch; + + if (ld->conf.cam_is_persp) { + ld->qtree.recursive_level = LRT_TILE_RECURSIVE_PERSPECTIVE; + } + else { + ld->qtree.recursive_level = LRT_TILE_RECURSIVE_ORTHO; + } + + double asp = (double(ld->w) / double(ld->h)); + int fit = BKE_camera_sensor_fit(c->sensor_fit, ld->w, ld->h); + ld->conf.shift_x = fit == CAMERA_SENSOR_FIT_HOR ? c->shiftx : c->shiftx / asp; + ld->conf.shift_y = fit == CAMERA_SENSOR_FIT_VERT ? c->shifty : c->shifty * asp; + + ld->conf.overscan = lmd->overscan; + + 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->object_to_world[3]); + copy_m4_m4(ld->conf.cam_obmat_secondary, light_obj->object_to_world); + 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; + ld->conf.chain_smooth_tolerance = lmd->chain_smooth_tolerance; + + ld->conf.fuzzy_intersections = (lmd->calculation_flags & LRT_INTERSECTION_AS_CONTOUR) != 0; + ld->conf.fuzzy_everything = (lmd->calculation_flags & LRT_EVERYTHING_AS_CONTOUR) != 0; + ld->conf.allow_boundaries = (lmd->calculation_flags & LRT_ALLOW_CLIPPING_BOUNDARIES) != 0; + ld->conf.use_loose_as_contour = (lmd->calculation_flags & LRT_LOOSE_AS_CONTOUR) != 0; + ld->conf.use_loose_edge_chain = (lmd->calculation_flags & LRT_CHAIN_LOOSE_EDGES) != 0; + ld->conf.use_geometry_space_chain = (lmd->calculation_flags & LRT_CHAIN_GEOMETRY_SPACE) != 0; + ld->conf.use_image_boundary_trimming = (lmd->calculation_flags & + LRT_USE_IMAGE_BOUNDARY_TRIMMING) != 0; + + /* See lineart_edge_from_triangle() for how this option may impact performance. */ + ld->conf.allow_overlapping_edges = (lmd->calculation_flags & LRT_ALLOW_OVERLAPPING_EDGES) != 0; + + ld->conf.allow_duplicated_types = (lmd->calculation_flags & LRT_ALLOW_OVERLAP_EDGE_TYPES) != 0; + + ld->conf.force_crease = (lmd->calculation_flags & LRT_USE_CREASE_ON_SMOOTH_SURFACES) != 0; + ld->conf.sharp_as_crease = (lmd->calculation_flags & LRT_USE_CREASE_ON_SHARP_EDGES) != 0; + + ld->conf.chain_preserve_details = (lmd->calculation_flags & LRT_CHAIN_PRESERVE_DETAILS) != 0; + + /* This is used to limit calculation to a certain level to save time, lines who have higher + * occlusion levels will get ignored. */ + ld->conf.max_occlusion_level = lmd->level_end_override; + + 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 != nullptr)); + ld->conf.use_shadow = ((edge_types & LRT_EDGE_FLAG_PROJECTED_SHADOW) != 0 && + (lmd->light_contour_object != nullptr)); + + ld->conf.shadow_selection = lmd->shadow_selection_override; + ld->conf.shadow_enclose_shapes = lmd->shadow_selection_override == + LRT_SHADOW_FILTER_ILLUMINATED_ENCLOSED_SHAPES; + 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; + ld->conf.filter_face_mark_boundaries = (lmd->calculation_flags & + LRT_FILTER_FACE_MARK_BOUNDARIES) != 0; + ld->conf.filter_face_mark_keep_contour = (lmd->calculation_flags & + LRT_FILTER_FACE_MARK_KEEP_CONTOUR) != 0; + + ld->chain_data_pool = &lc->chain_data_pool; + + /* See #LineartData::edge_data_pool for explanation. */ + 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); + + ld->thread_count = BKE_render_num_threads(&scene->r); + + return ld; +} + +static int lineart_triangle_size_get(LineartData *ld) +{ + return sizeof(LineartTriangle) + (sizeof(LineartEdge *) * (ld->thread_count)); +} + +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 + * efficient at handling medium to small scenes. */ + int sp_w = LRT_BA_ROWS; + int sp_h = LRT_BA_ROWS; + int row, col; + LineartBoundingArea *ba; + + /* Always make sure the shortest side has at least LRT_BA_ROWS tiles. */ + if (ld->w > ld->h) { + sp_w = sp_h * ld->w / ld->h; + } + else { + sp_h = sp_w * ld->h / ld->w; + } + + /* Because NDC (Normalized Device Coordinates) range is (-1,1), + * so the span for each initial tile is double of that in the (0,1) range. */ + double span_w = 1.0 / sp_w * 2.0; + double span_h = 1.0 / sp_h * 2.0; + + ld->qtree.count_x = sp_w; + ld->qtree.count_y = sp_h; + ld->qtree.tile_width = span_w; + ld->qtree.tile_height = span_h; + + ld->qtree.initial_tile_count = sp_w * sp_h; + ld->qtree.initials = static_cast(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++) { + for (col = 0; col < sp_w; col++) { + ba = &ld->qtree.initials[row * ld->qtree.count_x + col]; + + /* Set the four direction limits. */ + ba->l = span_w * col - 1.0; + ba->r = (col == sp_w - 1) ? 1.0 : (span_w * (col + 1) - 1.0); + ba->u = 1.0 - span_h * row; + ba->b = (row == sp_h - 1) ? -1.0 : (1.0 - span_h * (row + 1)); + + ba->cx = (ba->l + ba->r) / 2; + ba->cy = (ba->u + ba->b) / 2; + + /* Init linked_triangles array. */ + ba->max_triangle_count = LRT_TILE_SPLITTING_TRIANGLE_LIMIT; + ba->max_line_count = LRT_TILE_EDGE_COUNT_INITIAL; + ba->linked_triangles = static_cast( + MEM_callocN(sizeof(LineartTriangle *) * ba->max_triangle_count, "ba_linked_triangles")); + ba->linked_lines = static_cast( + MEM_callocN(sizeof(LineartEdge *) * ba->max_line_count, "ba_linked_lines")); + + BLI_spin_init(&ba->lock); + } + } +} + +/** + * Re-link adjacent tiles after one gets subdivided. + */ +static void lineart_bounding_areas_connect_new(LineartData *ld, LineartBoundingArea *root) +{ + LineartBoundingArea *ba = root->child, *tba; + LinkData *lip2, *next_lip; + LineartStaticMemPool *mph = &ld->render_data_pool; + + /* Inter-connection with newly created 4 child bounding areas. */ + lineart_list_append_pointer_pool(&ba[1].rp, mph, &ba[0]); + lineart_list_append_pointer_pool(&ba[0].lp, mph, &ba[1]); + lineart_list_append_pointer_pool(&ba[1].bp, mph, &ba[2]); + lineart_list_append_pointer_pool(&ba[2].up, mph, &ba[1]); + lineart_list_append_pointer_pool(&ba[2].rp, mph, &ba[3]); + lineart_list_append_pointer_pool(&ba[3].lp, mph, &ba[2]); + lineart_list_append_pointer_pool(&ba[3].up, mph, &ba[0]); + lineart_list_append_pointer_pool(&ba[0].bp, mph, &ba[3]); + + /* Connect 4 child bounding areas to other areas that are + * adjacent to their original parents. */ + LISTBASE_FOREACH (LinkData *, lip, &root->lp) { + + /* For example, we are dealing with parent's left side + * "tba" represents each adjacent neighbor of the parent. */ + tba = static_cast(lip->data); + + /* if this neighbor is adjacent to + * the two new areas on the left side of the parent, + * then add them to the adjacent list as well. */ + if (ba[1].u > tba->b && ba[1].b < tba->u) { + lineart_list_append_pointer_pool(&ba[1].lp, mph, tba); + lineart_list_append_pointer_pool(&tba->rp, mph, &ba[1]); + } + if (ba[2].u > tba->b && ba[2].b < tba->u) { + lineart_list_append_pointer_pool(&ba[2].lp, mph, tba); + lineart_list_append_pointer_pool(&tba->rp, mph, &ba[2]); + } + } + LISTBASE_FOREACH (LinkData *, lip, &root->rp) { + tba = static_cast(lip->data); + if (ba[0].u > tba->b && ba[0].b < tba->u) { + lineart_list_append_pointer_pool(&ba[0].rp, mph, tba); + lineart_list_append_pointer_pool(&tba->lp, mph, &ba[0]); + } + if (ba[3].u > tba->b && ba[3].b < tba->u) { + lineart_list_append_pointer_pool(&ba[3].rp, mph, tba); + lineart_list_append_pointer_pool(&tba->lp, mph, &ba[3]); + } + } + LISTBASE_FOREACH (LinkData *, lip, &root->up) { + tba = static_cast(lip->data); + if (ba[0].r > tba->l && ba[0].l < tba->r) { + lineart_list_append_pointer_pool(&ba[0].up, mph, tba); + lineart_list_append_pointer_pool(&tba->bp, mph, &ba[0]); + } + if (ba[1].r > tba->l && ba[1].l < tba->r) { + lineart_list_append_pointer_pool(&ba[1].up, mph, tba); + lineart_list_append_pointer_pool(&tba->bp, mph, &ba[1]); + } + } + LISTBASE_FOREACH (LinkData *, lip, &root->bp) { + tba = static_cast(lip->data); + if (ba[2].r > tba->l && ba[2].l < tba->r) { + lineart_list_append_pointer_pool(&ba[2].bp, mph, tba); + lineart_list_append_pointer_pool(&tba->up, mph, &ba[2]); + } + if (ba[3].r > tba->l && ba[3].l < tba->r) { + lineart_list_append_pointer_pool(&ba[3].bp, mph, tba); + lineart_list_append_pointer_pool(&tba->up, mph, &ba[3]); + } + } + + /* Then remove the parent bounding areas from + * their original adjacent areas. */ + LISTBASE_FOREACH (LinkData *, lip, &root->lp) { + for (lip2 = static_cast(((LineartBoundingArea *)lip->data)->rp.first); lip2; + lip2 = next_lip) { + next_lip = lip2->next; + tba = static_cast(lip2->data); + if (tba == root) { + lineart_list_remove_pointer_item_no_free(&((LineartBoundingArea *)lip->data)->rp, lip2); + if (ba[1].u > tba->b && ba[1].b < tba->u) { + lineart_list_append_pointer_pool(&tba->rp, mph, &ba[1]); + } + if (ba[2].u > tba->b && ba[2].b < tba->u) { + lineart_list_append_pointer_pool(&tba->rp, mph, &ba[2]); + } + } + } + } + LISTBASE_FOREACH (LinkData *, lip, &root->rp) { + for (lip2 = static_cast(((LineartBoundingArea *)lip->data)->lp.first); lip2; + lip2 = next_lip) { + next_lip = lip2->next; + tba = static_cast(lip2->data); + if (tba == root) { + lineart_list_remove_pointer_item_no_free(&((LineartBoundingArea *)lip->data)->lp, lip2); + if (ba[0].u > tba->b && ba[0].b < tba->u) { + lineart_list_append_pointer_pool(&tba->lp, mph, &ba[0]); + } + if (ba[3].u > tba->b && ba[3].b < tba->u) { + lineart_list_append_pointer_pool(&tba->lp, mph, &ba[3]); + } + } + } + } + LISTBASE_FOREACH (LinkData *, lip, &root->up) { + for (lip2 = static_cast(((LineartBoundingArea *)lip->data)->bp.first); lip2; + lip2 = next_lip) { + next_lip = lip2->next; + tba = static_cast(lip2->data); + if (tba == root) { + lineart_list_remove_pointer_item_no_free(&((LineartBoundingArea *)lip->data)->bp, lip2); + if (ba[0].r > tba->l && ba[0].l < tba->r) { + lineart_list_append_pointer_pool(&tba->up, mph, &ba[0]); + } + if (ba[1].r > tba->l && ba[1].l < tba->r) { + lineart_list_append_pointer_pool(&tba->up, mph, &ba[1]); + } + } + } + } + LISTBASE_FOREACH (LinkData *, lip, &root->bp) { + for (lip2 = static_cast(((LineartBoundingArea *)lip->data)->up.first); lip2; + lip2 = next_lip) { + next_lip = lip2->next; + tba = static_cast(lip2->data); + if (tba == root) { + lineart_list_remove_pointer_item_no_free(&((LineartBoundingArea *)lip->data)->up, lip2); + if (ba[2].r > tba->l && ba[2].l < tba->r) { + lineart_list_append_pointer_pool(&tba->bp, mph, &ba[2]); + } + if (ba[3].r > tba->l && ba[3].l < tba->r) { + lineart_list_append_pointer_pool(&tba->bp, mph, &ba[3]); + } + } + } + } + + /* Finally clear parent's adjacent list. */ + BLI_listbase_clear(&root->lp); + BLI_listbase_clear(&root->rp); + BLI_listbase_clear(&root->up); + BLI_listbase_clear(&root->bp); +} + +static void lineart_bounding_areas_connect_recursive(LineartData *ld, LineartBoundingArea *root) +{ + if (root->child) { + lineart_bounding_areas_connect_new(ld, root); + for (int i = 0; i < 4; i++) { + lineart_bounding_areas_connect_recursive(ld, &root->child[i]); + } + } +} + +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; + + for (int row = 0; row < ld->qtree.count_y; row++) { + for (int col = 0; col < ld->qtree.count_x; col++) { + LineartBoundingArea *ba = &ld->qtree.initials[row * tiles_per_row + col]; + /* Link adjacent ones. */ + if (row) { + lineart_list_append_pointer_pool( + &ba->up, &ld->render_data_pool, &ld->qtree.initials[(row - 1) * tiles_per_row + col]); + } + if (col) { + lineart_list_append_pointer_pool( + &ba->lp, &ld->render_data_pool, &ld->qtree.initials[row * tiles_per_row + col - 1]); + } + if (row != ld->qtree.count_y - 1) { + lineart_list_append_pointer_pool( + &ba->bp, &ld->render_data_pool, &ld->qtree.initials[(row + 1) * tiles_per_row + col]); + } + if (col != ld->qtree.count_x - 1) { + lineart_list_append_pointer_pool( + &ba->rp, &ld->render_data_pool, &ld->qtree.initials[row * tiles_per_row + col + 1]); + } + } + } + for (int i = 0; i < total_tile_initial; i++) { + lineart_bounding_areas_connect_recursive(ld, &ld->qtree.initials[i]); + } +} + +/** + * Subdivide a tile after one tile contains too many triangles, then re-link triangles into all the + * child tiles. + */ +static void lineart_bounding_area_split(LineartData *ld, + LineartBoundingArea *root, + int recursive_level) +{ + LineartBoundingArea *ba = static_cast( + lineart_mem_acquire_thread(&ld->render_data_pool, sizeof(LineartBoundingArea) * 4)); + ba[0].l = root->cx; + ba[0].r = root->r; + ba[0].u = root->u; + ba[0].b = root->cy; + ba[0].cx = (ba[0].l + ba[0].r) / 2; + ba[0].cy = (ba[0].u + ba[0].b) / 2; + + ba[1].l = root->l; + ba[1].r = root->cx; + ba[1].u = root->u; + ba[1].b = root->cy; + ba[1].cx = (ba[1].l + ba[1].r) / 2; + ba[1].cy = (ba[1].u + ba[1].b) / 2; + + ba[2].l = root->l; + ba[2].r = root->cx; + ba[2].u = root->cy; + ba[2].b = root->b; + ba[2].cx = (ba[2].l + ba[2].r) / 2; + ba[2].cy = (ba[2].u + ba[2].b) / 2; + + ba[3].l = root->cx; + ba[3].r = root->r; + ba[3].u = root->cy; + ba[3].b = root->b; + ba[3].cx = (ba[3].l + ba[3].r) / 2; + ba[3].cy = (ba[3].u + ba[3].b) / 2; + + /* Init linked_triangles array and locks. */ + for (int i = 0; i < 4; i++) { + ba[i].max_triangle_count = LRT_TILE_SPLITTING_TRIANGLE_LIMIT; + ba[i].max_line_count = LRT_TILE_EDGE_COUNT_INITIAL; + ba[i].linked_triangles = static_cast( + MEM_callocN(sizeof(LineartTriangle *) * ba[i].max_triangle_count, "ba_linked_triangles")); + ba[i].linked_lines = static_cast( + MEM_callocN(sizeof(LineartEdge *) * ba[i].max_line_count, "ba_linked_lines")); + BLI_spin_init(&ba[i].lock); + } + + for (uint32_t i = 0; i < root->triangle_count; i++) { + LineartTriangle *tri = root->linked_triangles[i]; + + double b[4]; + 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]); + b[3] = MIN3(tri->v[0]->fbcoord[1], tri->v[1]->fbcoord[1], tri->v[2]->fbcoord[1]); + + /* Re-link triangles into child tiles, not doing intersection lines during this because this + * batch of triangles are all tested with each other for intersections. */ + if (LRT_BOUND_AREA_CROSSES(b, &ba[0].l)) { + lineart_bounding_area_link_triangle( + ld, &ba[0], tri, b, 0, recursive_level + 1, false, nullptr); + } + if (LRT_BOUND_AREA_CROSSES(b, &ba[1].l)) { + lineart_bounding_area_link_triangle( + ld, &ba[1], tri, b, 0, recursive_level + 1, false, nullptr); + } + if (LRT_BOUND_AREA_CROSSES(b, &ba[2].l)) { + lineart_bounding_area_link_triangle( + ld, &ba[2], tri, b, 0, recursive_level + 1, false, nullptr); + } + if (LRT_BOUND_AREA_CROSSES(b, &ba[3].l)) { + lineart_bounding_area_link_triangle( + ld, &ba[3], tri, b, 0, recursive_level + 1, false, nullptr); + } + } + + /* 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; +} + +static bool lineart_bounding_area_edge_intersect(LineartData * /*fb*/, + const double l[2], + const double r[2], + LineartBoundingArea *ba) +{ + double dx, dy; + double converted[4]; + double c1, c; + + if (((converted[0] = ba->l) > MAX2(l[0], r[0])) || ((converted[1] = ba->r) < MIN2(l[0], r[0])) || + ((converted[2] = ba->b) > MAX2(l[1], r[1])) || ((converted[3] = ba->u) < MIN2(l[1], r[1]))) { + return false; + } + + dx = l[0] - r[0]; + dy = l[1] - r[1]; + + c1 = dx * (converted[2] - l[1]) - dy * (converted[0] - l[0]); + c = c1; + + c1 = dx * (converted[2] - l[1]) - dy * (converted[1] - l[0]); + if (c1 * c <= 0) { + return true; + } + c = c1; + + c1 = dx * (converted[3] - l[1]) - dy * (converted[0] - l[0]); + if (c1 * c <= 0) { + return true; + } + c = c1; + + c1 = dx * (converted[3] - l[1]) - dy * (converted[1] - l[0]); + if (c1 * c <= 0) { + return true; + } + c = c1; + + return false; +} + +static bool lineart_bounding_area_triangle_intersect(LineartData *fb, + LineartTriangle *tri, + LineartBoundingArea *ba, + bool *r_triangle_vert_inside) +{ + double p1[2], p2[2], p3[2], p4[2]; + double *FBC1 = tri->v[0]->fbcoord, *FBC2 = tri->v[1]->fbcoord, *FBC3 = tri->v[2]->fbcoord; + + p3[0] = p1[0] = ba->l; + p2[1] = p1[1] = ba->b; + p2[0] = p4[0] = ba->r; + p3[1] = p4[1] = ba->u; + + if ((FBC1[0] >= p1[0] && FBC1[0] <= p2[0] && FBC1[1] >= p1[1] && FBC1[1] <= p3[1]) || + (FBC2[0] >= p1[0] && FBC2[0] <= p2[0] && FBC2[1] >= p1[1] && FBC2[1] <= p3[1]) || + (FBC3[0] >= p1[0] && FBC3[0] <= p2[0] && FBC3[1] >= p1[1] && FBC3[1] <= p3[1])) { + *r_triangle_vert_inside = true; + return true; + } + + *r_triangle_vert_inside = false; + + if (lineart_point_inside_triangle(p1, FBC1, FBC2, FBC3) || + lineart_point_inside_triangle(p2, FBC1, FBC2, FBC3) || + lineart_point_inside_triangle(p3, FBC1, FBC2, FBC3) || + lineart_point_inside_triangle(p4, FBC1, FBC2, FBC3)) { + return true; + } + + if (lineart_bounding_area_edge_intersect(fb, FBC1, FBC2, ba) || + lineart_bounding_area_edge_intersect(fb, FBC2, FBC3, ba) || + lineart_bounding_area_edge_intersect(fb, FBC3, FBC1, ba)) { + return true; + } + + return false; +} + +/** + * This function does two things: + * + * 1) Builds a quad-tree under ld->InitialBoundingAreas to achieve good geometry separation for + * fast overlapping test between triangles and lines. This acceleration structure makes the + * occlusion stage much faster. + * + * 2) Test triangles with other triangles that are previously linked into each tile + * (#LineartBoundingArea) for intersection lines. When splitting the tile into 4 children and + * re-linking triangles into the child tiles, intersections are inhibited so we don't get + * duplicated intersection lines. + */ +static void lineart_bounding_area_link_triangle(LineartData *ld, + LineartBoundingArea *root_ba, + LineartTriangle *tri, + double l_r_u_b[4], + int recursive, + int recursive_level, + bool do_intersection, + LineartIsecThread *th) +{ + bool triangle_vert_inside; + if (!lineart_bounding_area_triangle_intersect(ld, tri, root_ba, &triangle_vert_inside)) { + return; + } + + LineartBoundingArea *old_ba = root_ba; + + if (old_ba->child) { + /* If old_ba->child is not nullptr, then tile splitting is fully finished, safe to directly + * insert into child tiles. */ + double *B1 = l_r_u_b; + double b[4]; + 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]); + b[3] = MIN3(tri->v[0]->fbcoord[1], tri->v[1]->fbcoord[1], tri->v[2]->fbcoord[1]); + B1 = b; + } + for (int iba = 0; iba < 4; iba++) { + if (LRT_BOUND_AREA_CROSSES(B1, &old_ba->child[iba].l)) { + lineart_bounding_area_link_triangle( + ld, &old_ba->child[iba], tri, B1, recursive, recursive_level + 1, do_intersection, th); + } + } + return; + } + + /* When splitting tiles, triangles are relinked into new tiles by a single thread, #th is nullptr + * in that situation. */ + if (th) { + BLI_spin_lock(&old_ba->lock); + } + + /* If there are still space left in this tile for insertion. */ + if (old_ba->triangle_count < old_ba->max_triangle_count) { + const uint32_t old_tri_count = old_ba->triangle_count; + + old_ba->linked_triangles[old_tri_count] = tri; + + if (triangle_vert_inside) { + old_ba->insider_triangle_count++; + } + old_ba->triangle_count++; + + /* Do intersections in place. */ + if (do_intersection && ld->conf.use_intersections) { + lineart_triangle_intersect_in_bounding_area(tri, old_ba, th, old_tri_count); + } + + if (th) { + BLI_spin_unlock(&old_ba->lock); + } + } + else { /* We need to wait for either splitting or array extension to be done. */ + + if (recursive_level < ld->qtree.recursive_level && + old_ba->insider_triangle_count >= LRT_TILE_SPLITTING_TRIANGLE_LIMIT) { + if (!old_ba->child) { + /* old_ba->child==nullptr, means we are the thread that's doing the splitting. */ + lineart_bounding_area_split(ld, old_ba, recursive_level); + } /* Otherwise other thread has completed the splitting process. */ + } + else { + if (old_ba->triangle_count == old_ba->max_triangle_count) { + /* Means we are the thread that's doing the extension. */ + lineart_bounding_area_triangle_reallocate(old_ba); + } /* Otherwise other thread has completed the extending the array. */ + } + + /* Unlock before going into recursive call. */ + if (th) { + BLI_spin_unlock(&old_ba->lock); + } + + /* Of course we still have our own triangle needs to be added. */ + lineart_bounding_area_link_triangle( + ld, root_ba, tri, l_r_u_b, recursive, recursive_level, do_intersection, th); + } +} + +static void lineart_free_bounding_area_memory(LineartBoundingArea *ba, bool recursive) +{ + BLI_spin_end(&ba->lock); + if (ba->linked_lines) { + MEM_freeN(ba->linked_lines); + } + if (ba->linked_triangles) { + MEM_freeN(ba->linked_triangles); + } + if (recursive && ba->child) { + for (int i = 0; i < 4; i++) { + lineart_free_bounding_area_memory(&ba->child[i], recursive); + } + } +} +static void lineart_free_bounding_area_memories(LineartData *ld) +{ + for (int i = 0; i < ld->qtree.count_y; i++) { + for (int j = 0; j < ld->qtree.count_x; j++) { + lineart_free_bounding_area_memory(&ld->qtree.initials[i * ld->qtree.count_x + j], true); + } + } +} + +static void lineart_bounding_area_link_edge(LineartData *ld, + LineartBoundingArea *root_ba, + LineartEdge *e) +{ + if (root_ba->child == nullptr) { + lineart_bounding_area_line_add(root_ba, e); + } + else { + if (lineart_bounding_area_edge_intersect( + ld, e->v1->fbcoord, e->v2->fbcoord, &root_ba->child[0])) { + lineart_bounding_area_link_edge(ld, &root_ba->child[0], e); + } + if (lineart_bounding_area_edge_intersect( + ld, e->v1->fbcoord, e->v2->fbcoord, &root_ba->child[1])) { + lineart_bounding_area_link_edge(ld, &root_ba->child[1], e); + } + if (lineart_bounding_area_edge_intersect( + ld, e->v1->fbcoord, e->v2->fbcoord, &root_ba->child[2])) { + lineart_bounding_area_link_edge(ld, &root_ba->child[2], e); + } + if (lineart_bounding_area_edge_intersect( + ld, e->v1->fbcoord, e->v2->fbcoord, &root_ba->child[3])) { + lineart_bounding_area_link_edge(ld, &root_ba->child[3], e); + } + } +} + +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 = static_cast( + 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. + */ +void lineart_main_link_lines(LineartData *ld) +{ + LRT_ITER_ALL_LINES_BEGIN + { + int r1, r2, c1, c2, row, col; + if (lineart_get_edge_bounding_areas(ld, e, &r1, &r2, &c1, &c2)) { + for (row = r1; row != r2 + 1; row++) { + for (col = c1; col != c2 + 1; col++) { + lineart_bounding_area_link_edge( + ld, &ld->qtree.initials[row * ld->qtree.count_x + col], e); + } + } + } + } + LRT_ITER_ALL_LINES_END +} + +static void lineart_main_remove_unused_lines_recursive(LineartBoundingArea *ba, + uint8_t max_occlusion) +{ + if (ba->child) { + for (int i = 0; i < 4; i++) { + lineart_main_remove_unused_lines_recursive(&ba->child[i], max_occlusion); + } + return; + } + + if (!ba->line_count) { + return; + } + + int usable_count = 0; + for (int i = 0; i < ba->line_count; i++) { + LineartEdge *e = ba->linked_lines[i]; + if (e->min_occ > max_occlusion) { + continue; + } + usable_count++; + } + + if (!usable_count) { + ba->line_count = 0; + return; + } + + LineartEdge **new_array = static_cast( + MEM_callocN(sizeof(LineartEdge *) * usable_count, "cleaned lineart edge array")); + + int new_i = 0; + for (int i = 0; i < ba->line_count; i++) { + LineartEdge *e = ba->linked_lines[i]; + if (e->min_occ > max_occlusion) { + continue; + } + new_array[new_i] = e; + new_i++; + } + + MEM_freeN(ba->linked_lines); + ba->linked_lines = new_array; + ba->max_line_count = ba->line_count = usable_count; +} + +static void lineart_main_remove_unused_lines_from_tiles(LineartData *ld) +{ + for (int row = 0; row < ld->qtree.count_y; row++) { + for (int col = 0; col < ld->qtree.count_x; col++) { + lineart_main_remove_unused_lines_recursive( + &ld->qtree.initials[row * ld->qtree.count_x + col], ld->conf.max_occlusion_level); + } + } +} + +static bool lineart_get_triangle_bounding_areas( + LineartData *ld, LineartTriangle *tri, int *rowbegin, int *rowend, int *colbegin, int *colend) +{ + double sp_w = ld->qtree.tile_width, sp_h = ld->qtree.tile_height; + double b[4]; + + if (!tri->v[0] || !tri->v[1] || !tri->v[2]) { + return false; + } + + 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] = MIN3(tri->v[0]->fbcoord[1], tri->v[1]->fbcoord[1], tri->v[2]->fbcoord[1]); + b[3] = MAX3(tri->v[0]->fbcoord[1], tri->v[1]->fbcoord[1], tri->v[2]->fbcoord[1]); + + if (b[0] > 1 || b[1] < -1 || b[2] > 1 || b[3] < -1) { + return false; + } + + (*colbegin) = int((b[0] + 1.0) / sp_w); + (*colend) = int((b[1] + 1.0) / sp_w); + (*rowend) = ld->qtree.count_y - int((b[2] + 1.0) / sp_h) - 1; + (*rowbegin) = ld->qtree.count_y - int((b[3] + 1.0) / sp_h) - 1; + + if ((*colend) >= ld->qtree.count_x) { + (*colend) = ld->qtree.count_x - 1; + } + if ((*rowend) >= ld->qtree.count_y) { + (*rowend) = ld->qtree.count_y - 1; + } + if ((*colbegin) < 0) { + (*colbegin) = 0; + } + if ((*rowbegin) < 0) { + (*rowbegin) = 0; + } + + return true; +} + +static bool lineart_get_edge_bounding_areas( + LineartData *ld, LineartEdge *e, int *rowbegin, int *rowend, int *colbegin, int *colend) +{ + double sp_w = ld->qtree.tile_width, sp_h = ld->qtree.tile_height; + double b[4]; + + if (!e->v1 || !e->v2) { + return false; + } + + if (e->v1->fbcoord[0] != e->v1->fbcoord[0] || e->v2->fbcoord[0] != e->v2->fbcoord[0]) { + return false; + } + + b[0] = MIN2(e->v1->fbcoord[0], e->v2->fbcoord[0]); + b[1] = MAX2(e->v1->fbcoord[0], e->v2->fbcoord[0]); + b[2] = MIN2(e->v1->fbcoord[1], e->v2->fbcoord[1]); + b[3] = MAX2(e->v1->fbcoord[1], e->v2->fbcoord[1]); + + if (b[0] > 1 || b[1] < -1 || b[2] > 1 || b[3] < -1) { + return false; + } + + (*colbegin) = int((b[0] + 1.0) / sp_w); + (*colend) = int((b[1] + 1.0) / sp_w); + (*rowend) = ld->qtree.count_y - int((b[2] + 1.0) / sp_h) - 1; + (*rowbegin) = ld->qtree.count_y - int((b[3] + 1.0) / sp_h) - 1; + + /* It's possible that the line stretches too much out to the side, resulting negative value. */ + if ((*rowend) < (*rowbegin)) { + (*rowend) = ld->qtree.count_y - 1; + } + + if ((*colend) < (*colbegin)) { + (*colend) = ld->qtree.count_x - 1; + } + + CLAMP((*colbegin), 0, ld->qtree.count_x - 1); + CLAMP((*rowbegin), 0, ld->qtree.count_y - 1); + CLAMP((*colend), 0, ld->qtree.count_x - 1); + CLAMP((*rowend), 0, ld->qtree.count_y - 1); + + return true; +} + +LineartBoundingArea *MOD_lineart_get_parent_bounding_area(LineartData *ld, double x, double y) +{ + double sp_w = ld->qtree.tile_width, sp_h = ld->qtree.tile_height; + int col, row; + + if (x > 1 || x < -1 || y > 1 || y < -1) { + return 0; + } + + col = int((x + 1.0) / sp_w); + row = ld->qtree.count_y - int((y + 1.0) / sp_h) - 1; + + if (col >= ld->qtree.count_x) { + col = ld->qtree.count_x - 1; + } + if (row >= ld->qtree.count_y) { + row = ld->qtree.count_y - 1; + } + if (col < 0) { + col = 0; + } + if (row < 0) { + row = 0; + } + + return &ld->qtree.initials[row * ld->qtree.count_x + col]; +} + +static LineartBoundingArea *lineart_get_bounding_area(LineartData *ld, double x, double y) +{ + LineartBoundingArea *iba; + double sp_w = ld->qtree.tile_width, sp_h = ld->qtree.tile_height; + int c = int((x + 1.0) / sp_w); + int r = ld->qtree.count_y - int((y + 1.0) / sp_h) - 1; + if (r < 0) { + r = 0; + } + if (c < 0) { + c = 0; + } + if (r >= ld->qtree.count_y) { + r = ld->qtree.count_y - 1; + } + if (c >= ld->qtree.count_x) { + c = ld->qtree.count_x - 1; + } + + iba = &ld->qtree.initials[r * ld->qtree.count_x + c]; + while (iba->child) { + if (x > iba->cx) { + if (y > iba->cy) { + iba = &iba->child[0]; + } + else { + iba = &iba->child[3]; + } + } + else { + if (y > iba->cy) { + iba = &iba->child[1]; + } + else { + iba = &iba->child[2]; + } + } + } + return iba; +} + +LineartBoundingArea *MOD_lineart_get_bounding_area(LineartData *ld, double x, double y) +{ + LineartBoundingArea *ba; + if ((ba = MOD_lineart_get_parent_bounding_area(ld, x, y)) != nullptr) { + return lineart_get_bounding_area(ld, x, y); + } + return nullptr; +} + +static void lineart_add_triangles_worker(TaskPool *__restrict /*pool*/, LineartIsecThread *th) +{ + LineartData *ld = th->ld; + int _dir_control = 0; + while (lineart_schedule_new_triangle_task(th)) { + for (LineartElementLinkNode *eln = th->pending_from; eln != th->pending_to->next; + eln = eln->next) { + int index_start = eln == th->pending_from ? th->index_from : 0; + int index_end = eln == th->pending_to ? th->index_to : eln->element_count; + LineartTriangle *tri = static_cast( + (void *)(((uchar *)eln->pointer) + ld->sizeof_triangle * index_start)); + for (int ei = index_start; ei < index_end; ei++) { + int x1, x2, y1, y2; + int r, co; + if ((tri->flags & LRT_CULL_USED) || (tri->flags & LRT_CULL_DISCARD)) { + tri = static_cast((void *)(((uchar *)tri) + ld->sizeof_triangle)); + continue; + } + if (lineart_get_triangle_bounding_areas(ld, tri, &y1, &y2, &x1, &x2)) { + _dir_control++; + for (co = x1; co <= x2; co++) { + for (r = y1; r <= y2; r++) { + lineart_bounding_area_link_triangle( + ld, &ld->qtree.initials[r * ld->qtree.count_x + co], tri, 0, 1, 0, 1, th); + } + } + } /* Else throw away. */ + tri = static_cast((void *)(((uchar *)tri) + ld->sizeof_triangle)); + } + } + } +} + +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]; + if (G.debug_value == 4000) { + printf("Thread %d isec generated %d lines.\n", i, th->current); + } + if (!th->current) { + continue; + } + total_lines += th->current; + } + + if (!total_lines) { + return; + } + + /* 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. */ + LineartVert *v = static_cast( + lineart_mem_acquire(ld->edge_data_pool, sizeof(LineartVert) * total_lines * 2)); + LineartEdge *e = static_cast( + lineart_mem_acquire(ld->edge_data_pool, sizeof(LineartEdge) * total_lines)); + LineartEdgeSegment *es = static_cast( + lineart_mem_acquire(ld->edge_data_pool, sizeof(LineartEdgeSegment) * total_lines)); + + LineartElementLinkNode *eln = static_cast( + 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++) { + LineartIsecSingle *is = &th->array[j]; + LineartVert *v1 = v; + LineartVert *v2 = v + 1; + copy_v3_v3_db(v1->gloc, is->v1); + copy_v3_v3_db(v2->gloc, is->v2); + /* The intersection line has been generated only in geometry space, so we need to transform + * them as well. */ + mul_v4_m4v3_db(v1->fbcoord, ld->conf.view_projection, v1->gloc); + mul_v4_m4v3_db(v2->fbcoord, ld->conf.view_projection, v2->gloc); + mul_v3db_db(v1->fbcoord, (1 / v1->fbcoord[3])); + mul_v3db_db(v2->fbcoord, (1 / v2->fbcoord[3])); + + v1->fbcoord[0] -= ld->conf.shift_x * 2; + v1->fbcoord[1] -= ld->conf.shift_y * 2; + v2->fbcoord[0] -= ld->conf.shift_x * 2; + v2->fbcoord[1] -= ld->conf.shift_y * 2; + + /* This z transformation is not the same as the rest of the part, because the data don't go + * through normal perspective division calls in the pipeline, but this way the 3D result and + * occlusion on the generated line is correct, and we don't really use 2D for viewport stroke + * generation anyway. */ + v1->fbcoord[2] = ZMin * ZMax / (ZMax - fabs(v1->fbcoord[2]) * (ZMax - ZMin)); + v2->fbcoord[2] = ZMin * ZMax / (ZMax - fabs(v2->fbcoord[2]) * (ZMax - ZMin)); + e->v1 = v1; + 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 ? static_cast(eln1->object_ref) : nullptr; + Object *ob2 = eln2 ? static_cast(eln2->object_ref) : nullptr; + 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 ambiguous if intersection lines comes from different objects. */ + e->object_ref = ob1; + } + } + + lineart_add_edge_to_array(&ld->pending_edges, e); + + v += 2; + e++; + es++; + } + } +} + +/** + * Sequentially add triangles into render buffer, intersection lines between those triangles will + * also be computed at the same time. + */ +void lineart_main_add_triangles(LineartData *ld) +{ + double t_start; + if (G.debug_value == 4000) { + t_start = PIL_check_seconds_timer(); + } + + /* Initialize per-thread data for thread task scheduling information and storing intersection + * results. */ + LineartIsecData d = {0}; + lineart_init_isec_thread(&d, ld, ld->thread_count); + + TaskPool *tp = BLI_task_pool_create(nullptr, TASK_PRIORITY_HIGH); + for (int i = 0; i < ld->thread_count; i++) { + BLI_task_pool_push( + tp, (TaskRunFunction)lineart_add_triangles_worker, &d.threads[i], 0, nullptr); + } + BLI_task_pool_work_and_wait(tp); + BLI_task_pool_free(tp); + + if (ld->conf.use_intersections) { + lineart_create_edges_from_isec_data(&d); + } + + lineart_destroy_isec_thread(&d); + + if (G.debug_value == 4000) { + double t_elapsed = PIL_check_seconds_timer() - t_start; + printf("Line art intersection time: %f\n", t_elapsed); + } +} + +/** + * This function gets the tile for the point `e->v1`, and later use #lineart_bounding_area_next() + * to get next along the way. + */ +LineartBoundingArea *lineart_edge_first_bounding_area(LineartData *ld, + double *fbcoord1, + double *fbcoord2) +{ + 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; + + if (data[0] > -1 && data[0] < 1 && data[1] > -1 && data[1] < 1) { + return lineart_get_bounding_area(ld, data[0], data[1]); + } + + if (lineart_intersect_seg_seg(fbcoord1, fbcoord2, LU, RU, &sr, &p_unused) && sr < r && sr > 0) { + r = sr; + } + if (lineart_intersect_seg_seg(fbcoord1, fbcoord2, LB, RB, &sr, &p_unused) && sr < r && sr > 0) { + r = sr; + } + if (lineart_intersect_seg_seg(fbcoord1, fbcoord2, LB, LU, &sr, &p_unused) && sr < r && sr > 0) { + r = sr; + } + if (lineart_intersect_seg_seg(fbcoord1, fbcoord2, RB, RU, &sr, &p_unused) && sr < r && sr > 0) { + r = sr; + } + interp_v2_v2v2_db(data, fbcoord1, fbcoord2, r); + + return lineart_get_bounding_area(ld, data[0], data[1]); +} + +/** + * This march along one render line in image space and + * get the next bounding area the line is crossing. + */ +LineartBoundingArea *lineart_bounding_area_next(LineartBoundingArea *self, + 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; + LineartBoundingArea *ba; + + /* If we are marching towards the right. */ + if (positive_x > 0) { + rx = self->r; + ry = y + k * (rx - x); + + /* If we are marching towards the top. */ + if (positive_y > 0) { + uy = self->u; + ux = x + (uy - y) / k; + r1 = ratiod(fbcoord1[0], fbcoord2[0], rx); + r2 = ratiod(fbcoord1[0], fbcoord2[0], ux); + if (MIN2(r1, r2) > 1) { + return 0; + } + + /* We reached the right side before the top side. */ + if (r1 <= r2) { + LISTBASE_FOREACH (LinkData *, lip, &self->rp) { + ba = static_cast(lip->data); + if (ba->u >= ry && ba->b < ry) { + *next_x = rx; + *next_y = ry; + return ba; + } + } + } + /* We reached the top side before the right side. */ + else { + LISTBASE_FOREACH (LinkData *, lip, &self->up) { + ba = static_cast(lip->data); + if (ba->r >= ux && ba->l < ux) { + *next_x = ux; + *next_y = uy; + return ba; + } + } + } + } + /* If we are marching towards the bottom. */ + else if (positive_y < 0) { + by = self->b; + bx = x + (by - y) / k; + r1 = ratiod(fbcoord1[0], fbcoord2[0], rx); + r2 = ratiod(fbcoord1[0], fbcoord2[0], bx); + if (MIN2(r1, r2) > 1) { + return 0; + } + if (r1 <= r2) { + LISTBASE_FOREACH (LinkData *, lip, &self->rp) { + ba = static_cast(lip->data); + if (ba->u >= ry && ba->b < ry) { + *next_x = rx; + *next_y = ry; + return ba; + } + } + } + else { + LISTBASE_FOREACH (LinkData *, lip, &self->bp) { + ba = static_cast(lip->data); + if (ba->r >= bx && ba->l < bx) { + *next_x = bx; + *next_y = by; + return ba; + } + } + } + } + /* If the line is completely horizontal, in which Y difference == 0. */ + else { + r1 = ratiod(fbcoord1[0], fbcoord2[0], self->r); + if (r1 > 1) { + return 0; + } + LISTBASE_FOREACH (LinkData *, lip, &self->rp) { + ba = static_cast(lip->data); + if (ba->u >= y && ba->b < y) { + *next_x = self->r; + *next_y = y; + return ba; + } + } + } + } + + /* If we are marching towards the left. */ + else if (positive_x < 0) { + lx = self->l; + ly = y + k * (lx - x); + + /* If we are marching towards the top. */ + if (positive_y > 0) { + uy = self->u; + ux = x + (uy - y) / k; + r1 = ratiod(fbcoord1[0], fbcoord2[0], lx); + r2 = ratiod(fbcoord1[0], fbcoord2[0], ux); + if (MIN2(r1, r2) > 1) { + return 0; + } + if (r1 <= r2) { + LISTBASE_FOREACH (LinkData *, lip, &self->lp) { + ba = static_cast(lip->data); + if (ba->u >= ly && ba->b < ly) { + *next_x = lx; + *next_y = ly; + return ba; + } + } + } + else { + LISTBASE_FOREACH (LinkData *, lip, &self->up) { + ba = static_cast(lip->data); + if (ba->r >= ux && ba->l < ux) { + *next_x = ux; + *next_y = uy; + return ba; + } + } + } + } + + /* If we are marching towards the bottom. */ + else if (positive_y < 0) { + by = self->b; + bx = x + (by - y) / k; + r1 = ratiod(fbcoord1[0], fbcoord2[0], lx); + r2 = ratiod(fbcoord1[0], fbcoord2[0], bx); + if (MIN2(r1, r2) > 1) { + return 0; + } + if (r1 <= r2) { + LISTBASE_FOREACH (LinkData *, lip, &self->lp) { + ba = static_cast(lip->data); + if (ba->u >= ly && ba->b < ly) { + *next_x = lx; + *next_y = ly; + return ba; + } + } + } + else { + LISTBASE_FOREACH (LinkData *, lip, &self->bp) { + ba = static_cast(lip->data); + if (ba->r >= bx && ba->l < bx) { + *next_x = bx; + *next_y = by; + return ba; + } + } + } + } + /* Again, horizontal. */ + else { + r1 = ratiod(fbcoord1[0], fbcoord2[0], self->l); + if (r1 > 1) { + return 0; + } + LISTBASE_FOREACH (LinkData *, lip, &self->lp) { + ba = static_cast(lip->data); + if (ba->u >= y && ba->b < y) { + *next_x = self->l; + *next_y = y; + return ba; + } + } + } + } + /* If the line is completely vertical, hence X difference == 0. */ + else { + if (positive_y > 0) { + r1 = ratiod(fbcoord1[1], fbcoord2[1], self->u); + if (r1 > 1) { + return 0; + } + LISTBASE_FOREACH (LinkData *, lip, &self->up) { + ba = static_cast(lip->data); + if (ba->r > x && ba->l <= x) { + *next_x = x; + *next_y = self->u; + return ba; + } + } + } + else if (positive_y < 0) { + r1 = ratiod(fbcoord1[1], fbcoord2[1], self->b); + if (r1 > 1) { + return 0; + } + LISTBASE_FOREACH (LinkData *, lip, &self->bp) { + ba = static_cast(lip->data); + if (ba->r > x && ba->l <= x) { + *next_x = x; + *next_y = self->b; + return ba; + } + } + } + else { + /* Segment has no length. */ + return 0; + } + } + 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, + bool enable_stroke_depth_offset) +{ + LineartData *ld; + Scene *scene = DEG_get_evaluated_scene(depsgraph); + int intersections_only = 0; /* Not used right now, but preserve for future. */ + Object *use_camera; + + double t_start; + if (G.debug_value == 4000) { + t_start = PIL_check_seconds_timer(); + } + + if (lmd->calculation_flags & LRT_USE_CUSTOM_CAMERA) { + if (!lmd->source_camera || + (use_camera = DEG_get_evaluated_object(depsgraph, lmd->source_camera))->type != + OB_CAMERA) { + return false; + } + } + else { + + BKE_scene_camera_switch_update(scene); + + if (!scene->camera) { + return false; + } + use_camera = scene->camera; + } + + LineartCache *lc = lineart_init_cache(); + *cached_result = lc; + + ld = lineart_create_render_buffer(scene, lmd, use_camera, scene->camera, lc); + + /* Triangle thread testing data size varies depending on the thread count. + * See definition of LineartTriangleThread for details. */ + ld->sizeof_triangle = lineart_triangle_size_get(ld); + + LineartData *shadow_rb = nullptr; + LineartElementLinkNode *shadow_veln, *shadow_eeln; + ListBase *shadow_elns = ld->conf.shadow_selection ? &lc->shadow_elns : nullptr; + 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, + 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. */ + return true; + } + + /* Initialize the bounding box acceleration structure, it's a lot like BVH in 3D. */ + lineart_main_bounding_area_make_initial(ld); + + /* We need to get cut into triangles that are crossing near/far plans, only this way can we get + * correct coordinates of those clipped lines. Done in two steps, + * setting clip_far==false for near plane. */ + lineart_main_cull_triangles(ld, false); + /* `clip_far == true` for far plane. */ + lineart_main_cull_triangles(ld, true); + + /* At this point triangle adjacent info pointers is no longer needed, free them. */ + lineart_main_free_adjacent_data(ld); + + /* Do the perspective division after clipping is done. */ + lineart_main_perspective_division(ld); + + lineart_main_discard_out_of_frame_edges(ld); + + /* Triangle intersections are done here during sequential adding of them. Only after this, + * triangles and lines are all linked with acceleration structure, and the 2D occlusion stage + * can do its job. */ + lineart_main_add_triangles(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); + + /* Link lines to acceleration structure, this can only be done after perspective division, if + * we do it after triangles being added, the acceleration structure has already been + * subdivided, this way we do less list manipulations. */ + lineart_main_link_lines(ld); + + /* "intersection_only" is preserved for being called in a standalone fashion. + * If so the data will already be available at the stage. Otherwise we do the occlusion and + * chaining etc. */ + + if (!intersections_only) { + + /* Occlusion is work-and-wait. This call will not return before work is completed. */ + lineart_main_occlusion_begin(ld); + + lineart_main_make_enclosed_shapes(ld, shadow_rb); + + lineart_main_remove_unused_lines_from_tiles(ld); + + /* Chaining is all single threaded. See lineart_chain.c + * In this particular call, only lines that are geometrically connected (share the _exact_ + * same end point) will be chained together. */ + MOD_lineart_chain_feature_lines(ld); + + /* We are unable to take care of occlusion if we only connect end points, so here we do a + * spit, where the splitting point could be any cut in e->segments. */ + MOD_lineart_chain_split_for_fixed_occlusion(ld); + + /* Then we connect chains based on the _proximity_ of their end points in image space, here's + * the place threshold value gets involved. */ + MOD_lineart_chain_connect(ld); + + if (ld->conf.chain_smooth_tolerance > FLT_EPSILON) { + /* Keeping UI range of 0-1 for ease of read while scaling down the actual value for best + * effective range in image-space (Coordinate only goes from -1 to 1). This value is + * somewhat arbitrary, but works best for the moment. */ + MOD_lineart_smooth_chains(ld, ld->conf.chain_smooth_tolerance / 50); + } + + if (ld->conf.use_image_boundary_trimming) { + MOD_lineart_chain_clip_at_border(ld); + } + + if (ld->conf.angle_splitting_threshold > FLT_EPSILON) { + MOD_lineart_chain_split_angle(ld, ld->conf.angle_splitting_threshold); + } + + if (enable_stroke_depth_offset && lmd->stroke_depth_offset > FLT_EPSILON) { + MOD_lineart_chain_offset_towards_camera( + 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)); + + /* At last, we need to clear flags so we don't confuse GPencil generation calls. */ + MOD_lineart_chain_clear_picked_flag(lc); + + MOD_lineart_finalize_chains(ld); + } + + 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); + + double t_elapsed = PIL_check_seconds_timer() - t_start; + printf("Line art total time: %lf\n", t_elapsed); + } + + return true; +} + +static void lineart_gpencil_generate(LineartCache *cache, + Depsgraph *depsgraph, + Object *gpencil_object, + float (*gp_obmat_inverse)[4], + bGPDlayer * /*gpl*/, + bGPDframe *gpf, + int level_start, + int level_end, + int material_nr, + Object *source_object, + Collection *source_collection, + int types, + uchar mask_switches, + uchar material_mask_bits, + uchar intersection_mask, + int16_t thickness, + float opacity, + uchar shaodow_selection, + uchar silhouette_mode, + const char *source_vgname, + const char *vgname, + int modifier_flags) +{ + if (cache == nullptr) { + if (G.debug_value == 4000) { + printf("nullptr Lineart cache!\n"); + } + return; + } + + int stroke_count = 0; + int color_idx = 0; + + Object *orig_ob = nullptr; + if (source_object) { + orig_ob = source_object->id.orig_id ? (Object *)source_object->id.orig_id : source_object; + } + + Collection *orig_col = nullptr; + if (source_collection) { + orig_col = source_collection->id.orig_id ? (Collection *)source_collection->id.orig_id : + source_collection; + } + + /* (!orig_col && !orig_ob) means the whole scene is selected. */ + + 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) { + + if (ec->picked) { + continue; + } + if (!(ec->type & (types & enabled_types))) { + continue; + } + if (ec->level > level_end || ec->level < level_start) { + continue; + } + if (orig_ob && orig_ob != ec->object_ref) { + continue; + } + if (orig_col && ec->object_ref) { + if (BKE_collection_has_object_recursive_instanced(orig_col, (Object *)ec->object_ref)) { + if (modifier_flags & LRT_GPENCIL_INVERT_COLLECTION) { + continue; + } + } + else { + if (!(modifier_flags & LRT_GPENCIL_INVERT_COLLECTION)) { + continue; + } + } + } + if (mask_switches & LRT_GPENCIL_MATERIAL_MASK_ENABLE) { + if (mask_switches & LRT_GPENCIL_MATERIAL_MASK_MATCH) { + if (ec->material_mask_bits != material_mask_bits) { + continue; + } + } + else { + if (!(ec->material_mask_bits & material_mask_bits)) { + continue; + } + } + } + if (ec->type & LRT_EDGE_FLAG_INTERSECTION) { + if (mask_switches & LRT_GPENCIL_INTERSECTION_MATCH) { + if (ec->intersection_mask != intersection_mask) { + continue; + } + } + else { + if ((intersection_mask) && !(ec->intersection_mask & intersection_mask)) { + continue; + } + } + } + if (shaodow_selection) { + if (ec->shadow_mask_bits != LRT_SHADOW_MASK_UNDEFINED) { + /* TODO(@Yiming): Give a behavior option for how to display undefined shadow info. */ + if (shaodow_selection == LRT_SHADOW_FILTER_ILLUMINATED && + !(ec->shadow_mask_bits & LRT_SHADOW_MASK_ILLUMINATED)) { + continue; + } + if (shaodow_selection == LRT_SHADOW_FILTER_SHADED && + !(ec->shadow_mask_bits & LRT_SHADOW_MASK_SHADED)) { + continue; + } + if (shaodow_selection == LRT_SHADOW_FILTER_ILLUMINATED_ENCLOSED_SHAPES) { + uint32_t test_bits = ec->shadow_mask_bits & LRT_SHADOW_TEST_SHAPE_BITS; + if ((test_bits != LRT_SHADOW_MASK_ILLUMINATED) && + (test_bits != (LRT_SHADOW_MASK_SHADED | LRT_SHADOW_MASK_ILLUMINATED_SHAPE))) { + 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; + LISTBASE_FOREACH_INDEX (LineartEdgeChainItem *, eci, &ec->chain, i) { + bGPDspoint *point = &gps->points[i]; + mul_v3_m4v3(&point->x, gp_obmat_inverse, eci->gpos); + point->pressure = 1.0f; + point->strength = opacity; + } + + BKE_gpencil_dvert_ensure(gps); + gps->mat_nr = max_ii(material_nr, 0); + + if (source_vgname && vgname) { + Object *eval_ob = DEG_get_evaluated_object(depsgraph, ec->object_ref); + int gpdg = -1; + if (match_output || (gpdg = BKE_object_defgroup_name_index(gpencil_object, vgname)) >= 0) { + if (eval_ob && eval_ob->type == OB_MESH) { + int dindex = 0; + Mesh *me = BKE_object_get_evaluated_mesh(eval_ob); + MDeformVert *dvert = BKE_mesh_deform_verts_for_write(me); + if (dvert) { + LISTBASE_FOREACH (bDeformGroup *, db, &me->vertex_group_names) { + if ((!source_vgname) || strstr(db->name, source_vgname) == db->name) { + if (match_output) { + gpdg = BKE_object_defgroup_name_index(gpencil_object, db->name); + if (gpdg < 0) { + continue; + } + } + int sindex = 0, vindex; + LISTBASE_FOREACH (LineartEdgeChainItem *, eci, &ec->chain) { + vindex = eci->index; + if (vindex >= me->totvert) { + break; + } + MDeformWeight *mdw = BKE_defvert_ensure_index(&dvert[vindex], dindex); + MDeformWeight *gdw = BKE_defvert_ensure_index(&gps->dvert[sindex], gpdg); + + float use_weight = mdw->weight; + if (invert_input) { + use_weight = 1 - use_weight; + } + gdw->weight = MAX2(use_weight, gdw->weight); + + sindex++; + } + } + dindex++; + } + } + } + } + } + + if (G.debug_value == 4000) { + BKE_gpencil_stroke_set_random_color(gps); + } + BKE_gpencil_stroke_geometry_update(static_cast(gpencil_object->data), gps); + stroke_count++; + } + + if (G.debug_value == 4000) { + printf("LRT: Generated %d strokes.\n", stroke_count); + } +} + +void MOD_lineart_gpencil_generate(LineartCache *cache, + Depsgraph *depsgraph, + Object *ob, + bGPDlayer *gpl, + bGPDframe *gpf, + int8_t source_type, + void *source_reference, + int level_start, + int level_end, + int mat_nr, + int16_t edge_types, + uchar mask_switches, + uchar material_mask_bits, + uchar intersection_mask, + int16_t thickness, + float opacity, + uchar shadow_selection, + uchar silhouette_mode, + const char *source_vgname, + const char *vgname, + int modifier_flags) +{ + + if (!gpl || !gpf || !ob) { + return; + } + + Object *source_object = nullptr; + Collection *source_collection = nullptr; + int16_t use_types = edge_types; + if (source_type == LRT_SOURCE_OBJECT) { + if (!source_reference) { + return; + } + source_object = (Object *)source_reference; + } + else if (source_type == LRT_SOURCE_COLLECTION) { + if (!source_reference) { + return; + } + source_collection = (Collection *)source_reference; + } + + float gp_obmat_inverse[4][4]; + invert_m4_m4(gp_obmat_inverse, ob->object_to_world); + lineart_gpencil_generate(cache, + depsgraph, + ob, + gp_obmat_inverse, + gpl, + gpf, + level_start, + level_end, + mat_nr, + source_object, + source_collection, + use_types, + mask_switches, + material_mask_bits, + 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 947586aaec4..f33208ff931 100644 --- a/source/blender/gpencil_modifiers/intern/lineart/lineart_intern.h +++ b/source/blender/gpencil_modifiers/intern/lineart/lineart_intern.h @@ -17,6 +17,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + struct LineartEdge; struct LineartData; struct LineartStaticMemPool; @@ -148,7 +152,7 @@ bool lineart_edge_from_triangle(const struct LineartTriangle *tri, LineartBoundingArea *lineart_edge_first_bounding_area(struct LineartData *ld, double *fbcoord1, double *fbcoord2); -LineartBoundingArea *lineart_bounding_area_next(struct LineartBoundingArea *_this, +LineartBoundingArea *lineart_bounding_area_next(struct LineartBoundingArea *self, double *fbcoord1, double *fbcoord2, double x, @@ -169,10 +173,6 @@ void lineart_add_edge_to_array(struct LineartPendingEdges *pe, struct LineartEdg 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(struct LineartAdjacentEdge *ai, int length); #ifdef __cplusplus diff --git a/source/blender/makesdna/DNA_object_types.h b/source/blender/makesdna/DNA_object_types.h index 494295f7bb9..0e0da516ebd 100644 --- a/source/blender/makesdna/DNA_object_types.h +++ b/source/blender/makesdna/DNA_object_types.h @@ -8,6 +8,8 @@ #pragma once +#include "BLI_utildefines.h" + #include "DNA_object_enums.h" #include "DNA_customdata_types.h" @@ -233,6 +235,7 @@ enum eObjectLineArt_Usage { OBJECT_LRT_NO_INTERSECTION = (1 << 4), OBJECT_LRT_FORCE_INTERSECTION = (1 << 5), }; +ENUM_OPERATORS(eObjectLineArt_Usage, OBJECT_LRT_FORCE_INTERSECTION); enum eObjectLineArt_Flags { OBJECT_LRT_OWN_CREASE = (1 << 0), -- cgit v1.2.3