diff options
Diffstat (limited to 'source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c')
-rw-r--r-- | source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c | 3929 |
1 files changed, 3929 insertions, 0 deletions
diff --git a/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c b/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c new file mode 100644 index 00000000000..ca65fc9bd57 --- /dev/null +++ b/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c @@ -0,0 +1,3929 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * The Original Code is Copyright (C) 2019 Blender Foundation. + * All rights reserved. + */ + +/* \file + * \ingroup editors + */ + +#include "MOD_lineart.h" + +#include "BLI_alloca.h" +#include "BLI_linklist.h" +#include "BLI_listbase.h" +#include "BLI_math_matrix.h" +#include "BLI_task.h" +#include "BLI_utildefines.h" + +#include "BKE_callbacks.h" +#include "BKE_camera.h" +#include "BKE_collection.h" +#include "BKE_context.h" +#include "BKE_curve.h" +#include "BKE_customdata.h" +#include "BKE_deform.h" +#include "BKE_editmesh.h" +#include "BKE_global.h" +#include "BKE_gpencil.h" +#include "BKE_gpencil_geom.h" +#include "BKE_gpencil_modifier.h" +#include "BKE_material.h" +#include "BKE_mesh.h" +#include "BKE_object.h" +#include "BKE_scene.h" +#include "BKE_screen.h" +#include "BKE_text.h" +#include "DEG_depsgraph_query.h" +#include "DNA_camera_types.h" +#include "DNA_collection_types.h" +#include "DNA_gpencil_types.h" +#include "DNA_lineart_types.h" +#include "DNA_material_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_modifier_types.h" +#include "DNA_scene_types.h" +#include "DNA_text_types.h" +#include "MEM_guardedalloc.h" + +#include "RNA_access.h" +#include "RNA_define.h" + +#include "BLI_math.h" +#include "BLI_string_utils.h" + +#include "bmesh.h" +#include "bmesh_class.h" +#include "bmesh_tools.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "MOD_gpencil_modifiertypes.h" + +#include "lineart_intern.h" + +static LineartBoundingArea *lineart_line_first_bounding_area(LineartRenderBuffer *rb, + LineartLine *rl); + +static void lineart_bounding_area_link_line(LineartRenderBuffer *rb, + LineartBoundingArea *root_ba, + LineartLine *rl); + +static LineartBoundingArea *lineart_bounding_area_next(LineartBoundingArea *This, + LineartLine *rl, + double x, + double y, + double k, + int positive_x, + int positive_y, + double *next_x, + double *next_y); + +static bool lineart_get_line_bounding_areas(LineartRenderBuffer *rb, + LineartLine *rl, + int *rowbegin, + int *rowend, + int *colbegin, + int *colend); + +static void lineart_bounding_area_link_triangle(LineartRenderBuffer *rb, + LineartBoundingArea *root_ba, + LineartTriangle *rt, + double *LRUB, + int recursive, + int recursive_level, + bool do_intersection); + +static bool lineart_triangle_line_image_space_occlusion(SpinLock *spl, + const LineartTriangle *rt, + const LineartLine *rl, + const double *override_camera_loc, + const bool override_cam_is_persp, + const bool allow_overlapping_edges, + const double vp[4][4], + const double *camera_dir, + const float cam_shift_x, + const float cam_shift_y, + double *from, + double *to); + +static void lineart_add_line_to_list(LineartRenderBuffer *rb, LineartLine *rl); + +static void lineart_line_discard_segment(LineartRenderBuffer *rb, LineartLineSegment *rls) +{ + BLI_spin_lock(&rb->lock_cuts); + + memset(rls, 0, sizeof(LineartLineSegment)); + + /* Storing the node for potentially reuse the memory for new segment data. Line Art data is not + * freed after all calulations are done. */ + BLI_addtail(&rb->wasted_cuts, rls); + + BLI_spin_unlock(&rb->lock_cuts); +} + +static LineartLineSegment *lineart_line_give_segment(LineartRenderBuffer *rb) +{ + BLI_spin_lock(&rb->lock_cuts); + + /* See if there is any already allocated memory we can reuse. */ + if (rb->wasted_cuts.first) { + LineartLineSegment *rls = (LineartLineSegment *)BLI_pophead(&rb->wasted_cuts); + BLI_spin_unlock(&rb->lock_cuts); + memset(rls, 0, sizeof(LineartLineSegment)); + return rls; + } + BLI_spin_unlock(&rb->lock_cuts); + + /* Otherwise allocate some new memory. */ + return (LineartLineSegment *)lineart_mem_aquire_thread(&rb->render_data_pool, + sizeof(LineartLineSegment)); +} + +/* Cuts the line in image space and mark occlusion level for each segment. */ +static void lineart_line_cut(LineartRenderBuffer *rb, + LineartLine *rl, + double start, + double end, + unsigned char transparency_mask) +{ + LineartLineSegment *rls, *irls, *next_rls, *prev_rls; + LineartLineSegment *cut_start_before = 0, *cut_end_before = 0; + LineartLineSegment *ns = 0, *ns2 = 0; + int untouched = 0; + + /* If for some reason the occlusion function may give a result that has zero length, or reversed + * in direction, or NAN, we take care of them here. */ + if (LRT_DOUBLE_CLOSE_ENOUGH(start, end)) { + return; + } + if (LRT_DOUBLE_CLOSE_ENOUGH(start, 1) || LRT_DOUBLE_CLOSE_ENOUGH(end, 0)) { + return; + } + if (UNLIKELY(start != start)) { + start = 0; + } + if (UNLIKELY(end != end)) { + end = 0; + } + + if (start > end) { + double t = start; + start = end; + end = t; + } + + /* Begin looking for starting position of the segment. */ + /* Not using a list iteration macro because of it more clear when using for loops to iterate + * through the segments. */ + for (rls = rl->segments.first; rls; rls = rls->next) { + if (LRT_DOUBLE_CLOSE_ENOUGH(rls->at, start)) { + cut_start_before = rls; + ns = cut_start_before; + break; + } + if (rls->next == NULL) { + break; + } + irls = rls->next; + if (irls->at > start + 1e-09 && start > rls->at) { + cut_start_before = irls; + ns = lineart_line_give_segment(rb); + break; + } + } + if (!cut_start_before && LRT_DOUBLE_CLOSE_ENOUGH(1, end)) { + untouched = 1; + } + for (rls = cut_start_before; rls; rls = rls->next) { + /* We tried to cut at existing cutting point (e.g. where the line's occluded by a triangle + * strip). */ + if (LRT_DOUBLE_CLOSE_ENOUGH(rls->at, end)) { + cut_end_before = rls; + ns2 = cut_end_before; + break; + } + /* This check is to prevent rls->at == 1.0 (where we don't need to cut because we are at the + * end point). */ + if (!rls->next && LRT_DOUBLE_CLOSE_ENOUGH(1, end)) { + cut_end_before = rls; + ns2 = cut_end_before; + untouched = 1; + break; + } + /* When an actual cut is needed in the line. */ + if (rls->at > end) { + cut_end_before = rls; + ns2 = lineart_line_give_segment(rb); + break; + } + } + + /* When we still can't find any existing cut in the line, we allocate new ones. */ + if (ns == NULL) { + ns = lineart_line_give_segment(rb); + } + if (ns2 == NULL) { + if (untouched) { + ns2 = ns; + cut_end_before = ns2; + } + else { + ns2 = lineart_line_give_segment(rb); + } + } + + if (cut_start_before) { + if (cut_start_before != ns) { + /* Insert cutting points for when a new cut is needed. */ + irls = cut_start_before->prev ? cut_start_before->prev : NULL; + ns->occlusion = irls ? irls->occlusion : 0; + ns->transparency_mask = irls->transparency_mask; + BLI_insertlinkbefore(&rl->segments, (void *)cut_start_before, (void *)ns); + } + /* 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. */ + irls = rl->segments.last; + ns->occlusion = irls->occlusion; + ns->transparency_mask = irls->transparency_mask; + BLI_addtail(&rl->segments, ns); + } + if (cut_end_before) { + /* The same manipulation as on "cut_start_before". */ + if (cut_end_before != ns2) { + irls = cut_end_before->prev ? cut_end_before->prev : NULL; + ns2->occlusion = irls ? irls->occlusion : 0; + ns2->transparency_mask = irls ? irls->transparency_mask : 0; + BLI_insertlinkbefore(&rl->segments, (void *)cut_end_before, (void *)ns2); + } + } + else { + irls = rl->segments.last; + ns2->occlusion = irls->occlusion; + ns2->transparency_mask = irls->transparency_mask; + BLI_addtail(&rl->segments, ns2); + } + + /* If we touched the cut list, we assign the new cut position based on new cut position, this way + * we accomomdate precision lost due to multiple cut inserts. */ + ns->at = start; + if (!untouched) { + ns2->at = end; + } + else { + /* For the convenience of the loop below. */ + ns2 = ns2->next; + } + + /* Register 1 level of occlusion for all touched segments. */ + for (rls = ns; rls && rls != ns2; rls = rls->next) { + rls->occlusion++; + rls->transparency_mask |= transparency_mask; + } + + /* Reduce adjacent cutting points of the same level, which saves memory. */ + char min_occ = 127; + prev_rls = NULL; + for (rls = rl->segments.first; rls; rls = next_rls) { + next_rls = rls->next; + + if (prev_rls && prev_rls->occlusion == rls->occlusion && + prev_rls->transparency_mask == rls->transparency_mask) { + BLI_remlink(&rl->segments, rls); + /* This puts the node back to the render buffer, if more cut happens, these unused nodes get + * picked first. */ + lineart_line_discard_segment(rb, rls); + continue; + } + + min_occ = MIN2(min_occ, rls->occlusion); + + prev_rls = rls; + } + rl->min_occ = min_occ; +} + +/* To see if given line is connected to an adjacent intersection line. */ +BLI_INLINE bool lineart_occlusion_is_adjacent_intersection(LineartLine *rl, LineartTriangle *rt) +{ + LineartVertIntersection *l = (void *)rl->l; + LineartVertIntersection *r = (void *)rl->r; + return ((l->base.flag && l->intersecting_with == (void *)rt) || + (r->base.flag && r->intersecting_with == (void *)rt)); +} + +static void lineart_occlusion_single_line(LineartRenderBuffer *rb, LineartLine *rl, int thread_id) +{ + double x = rl->l->fbcoord[0], y = rl->l->fbcoord[1]; + LineartBoundingArea *ba = lineart_line_first_bounding_area(rb, rl); + LineartBoundingArea *nba = ba; + LineartTriangleThread *rt; + + /* These values are used for marching along the line. */ + double l, r; + double k = (rl->r->fbcoord[1] - rl->l->fbcoord[1]) / + (rl->r->fbcoord[0] - rl->l->fbcoord[0] + 1e-30); + int positive_x = (rl->r->fbcoord[0] - rl->l->fbcoord[0]) > 0 ? + 1 : + (rl->r->fbcoord[0] == rl->l->fbcoord[0] ? 0 : -1); + int positive_y = (rl->r->fbcoord[1] - rl->l->fbcoord[1]) > 0 ? + 1 : + (rl->r->fbcoord[1] == rl->l->fbcoord[1] ? 0 : -1); + + while (nba) { + + LISTBASE_FOREACH (LinkData *, lip, &nba->linked_triangles) { + rt = lip->data; + /* If we are already testing the line in this thread, then don't do it. */ + if (rt->testing[thread_id] == rl || (rt->base.flags & LRT_TRIANGLE_INTERSECTION_ONLY) || + lineart_occlusion_is_adjacent_intersection(rl, (LineartTriangle *)rt)) { + continue; + } + rt->testing[thread_id] = rl; + if (lineart_triangle_line_image_space_occlusion(&rb->lock_task, + (void *)rt, + rl, + rb->camera_pos, + rb->cam_is_persp, + rb->allow_overlapping_edges, + rb->view_projection, + rb->view_vector, + rb->shift_x, + rb->shift_y, + &l, + &r)) { + lineart_line_cut(rb, rl, l, r, rt->base.transparency_mask); + if (rl->min_occ > rb->max_occlusion_level) { + /* No need to caluclate any longer on this line because no level more than set value is + * going to show up in the rendered result. */ + return; + } + } + } + /* Marching along rl->l to rl->r, searching each possible bounding areas it may touch. */ + nba = lineart_bounding_area_next(nba, rl, x, y, k, positive_x, positive_y, &x, &y); + } +} + +static int lineart_occlusion_make_task_info(LineartRenderBuffer *rb, LineartRenderTaskInfo *rti) +{ + LineartLine *data; + int i; + int res = 0; + + BLI_spin_lock(&rb->lock_task); + +#define LRT_ASSIGN_OCCLUSION_TASK(name) \ + if (rb->name##_managed) { \ + data = rb->name##_managed; \ + rti->name = (void *)data; \ + for (i = 0; i < LRT_THREAD_LINE_COUNT && data; i++) { \ + data = data->next; \ + } \ + rti->name##_end = data; \ + rb->name##_managed = data; \ + res = 1; \ + } \ + else { \ + rti->name = NULL; \ + } + + LRT_ASSIGN_OCCLUSION_TASK(contour); + LRT_ASSIGN_OCCLUSION_TASK(intersection); + LRT_ASSIGN_OCCLUSION_TASK(crease); + LRT_ASSIGN_OCCLUSION_TASK(material); + LRT_ASSIGN_OCCLUSION_TASK(edge_mark); + +#undef LRT_ASSIGN_OCCLUSION_TASK + + BLI_spin_unlock(&rb->lock_task); + + return res; +} + +static void lineart_occlusion_worker(TaskPool *__restrict UNUSED(pool), LineartRenderTaskInfo *rti) +{ + LineartRenderBuffer *rb = rti->rb; + LineartLine *lip; + + while (lineart_occlusion_make_task_info(rb, rti)) { + + for (lip = (void *)rti->contour; lip && lip != rti->contour_end; lip = lip->next) { + lineart_occlusion_single_line(rb, lip, rti->thread_id); + } + + for (lip = (void *)rti->crease; lip && lip != rti->crease_end; lip = lip->next) { + lineart_occlusion_single_line(rb, lip, rti->thread_id); + } + + for (lip = (void *)rti->intersection; lip && lip != rti->intersection_end; lip = lip->next) { + lineart_occlusion_single_line(rb, lip, rti->thread_id); + } + + for (lip = (void *)rti->material; lip && lip != rti->material_end; lip = lip->next) { + lineart_occlusion_single_line(rb, lip, rti->thread_id); + } + + for (lip = (void *)rti->edge_mark; lip && lip != rti->edge_mark_end; lip = lip->next) { + lineart_occlusion_single_line(rb, lip, rti->thread_id); + } + } +} + +/* All internal functions starting with lineart_main_ is called inside + * MOD_lineart_compute_feature_lines function. + * This function handles all occlusion calculation. */ +static void lineart_main_occlusion_begin(LineartRenderBuffer *rb) +{ + int thread_count = rb->thread_count; + LineartRenderTaskInfo *rti = MEM_callocN(sizeof(LineartRenderTaskInfo) * thread_count, + "Task Pool"); + int i; + + rb->contour_managed = rb->contours; + rb->crease_managed = rb->crease_lines; + rb->intersection_managed = rb->intersection_lines; + rb->material_managed = rb->material_lines; + rb->edge_mark_managed = rb->edge_marks; + + TaskPool *tp = BLI_task_pool_create(NULL, TASK_PRIORITY_HIGH); + + for (i = 0; i < thread_count; i++) { + rti[i].thread_id = i; + rti[i].rb = rb; + BLI_task_pool_push(tp, (TaskRunFunction)lineart_occlusion_worker, &rti[i], 0, NULL); + } + BLI_task_pool_work_and_wait(tp); + BLI_task_pool_free(tp); + + MEM_freeN(rti); +} + +/* Test if v lies with in the triangle formed by v0, v1, and v2. Returns false when v is exactly on + * the edge. + * For v to be inside the triangle, it needs to be at the same side of v0->v1, v1->v2, and + * v2->v0, where the "side" is determined by checking the sign of cross(v1-v0, v1-v) and so on. + */ +static bool lineart_point_inside_triangle(const double v[2], + const double v0[2], + const double v1[2], + const double v2[2]) +{ + double cl, c; + + cl = (v0[0] - v[0]) * (v1[1] - v[1]) - (v0[1] - v[1]) * (v1[0] - v[0]); + c = cl; + + cl = (v1[0] - v[0]) * (v2[1] - v[1]) - (v1[1] - v[1]) * (v2[0] - v[0]); + if (c * cl <= 0) { + return false; + } + + c = cl; + + cl = (v2[0] - v[0]) * (v0[1] - v[1]) - (v2[1] - v[1]) * (v0[0] - v[0]); + if (c * cl <= 0) { + return false; + } + + c = cl; + + cl = (v0[0] - v[0]) * (v1[1] - v[1]) - (v0[1] - v[1]) * (v1[0] - v[0]); + if (c * cl <= 0) { + return false; + } + + return true; +} + +static int lineart_point_on_line_segment(double v[2], double v0[2], double v1[2]) +{ + /* c1!=c2 by default. */ + double c1 = 1, c2 = 0; + double l0[2], l1[2]; + + sub_v2_v2v2_db(l0, v, v0); + sub_v2_v2v2_db(l1, v, v1); + + if (v1[0] == v0[0] && v1[1] == v0[1]) { + return 0; + } + + if (v1[0] - v0[0]) { + c1 = ratiod(v0[0], v1[0], v[0]); + } + else if (v[0] == v1[0]) { + c2 = ratiod(v0[1], v1[1], v[1]); + return (c2 >= 0 && c2 <= 1); + } + + if (v1[1] - v0[1]) { + c2 = ratiod(v0[1], v1[1], v[1]); + } + else if (v[1] == v1[1]) { + c1 = ratiod(v0[0], v1[0], v[0]); + return (c1 >= 0 && c1 <= 1); + } + + if (LRT_DOUBLE_CLOSE_ENOUGH(c1, c2) && c1 >= 0 && c1 <= 1) { + return 1; + } + + return 0; +} + +/* Same algorithm as lineart_point_inside_triangle(), but returns differently: + * 0-outside 1-on the edge 2-inside. */ +static int lineart_point_triangle_relation(double v[2], double v0[2], double v1[2], double v2[2]) +{ + double cl, c; + double r; + if (lineart_point_on_line_segment(v, v0, v1) || lineart_point_on_line_segment(v, v1, v2) || + lineart_point_on_line_segment(v, v2, v0)) { + return 1; + } + + cl = (v0[0] - v[0]) * (v1[1] - v[1]) - (v0[1] - v[1]) * (v1[0] - v[0]); + c = cl; + + cl = (v1[0] - v[0]) * (v2[1] - v[1]) - (v1[1] - v[1]) * (v2[0] - v[0]); + if ((r = c * cl) < 0) { + return 0; + } + + c = cl; + + cl = (v2[0] - v[0]) * (v0[1] - v[1]) - (v2[1] - v[1]) * (v0[0] - v[0]); + if ((r = c * cl) < 0) { + return 0; + } + + c = cl; + + cl = (v0[0] - v[0]) * (v1[1] - v[1]) - (v0[1] - v[1]) * (v1[0] - v[0]); + if ((r = c * cl) < 0) { + return 0; + } + + if (r == 0) { + return 1; + } + + return 2; +} + +/* Similar with lineart_point_inside_triangle, but in 3d. + * Returns false when not co-plannar. */ +static bool lineart_point_inside_triangle3d(double v[3], double v0[3], double v1[3], double v2[3]) +{ + double l[3], r[3]; + double N1[3], N2[3]; + double d; + + sub_v3_v3v3_db(l, v1, v0); + sub_v3_v3v3_db(r, v, v1); + cross_v3_v3v3_db(N1, l, r); + + sub_v3_v3v3_db(l, v2, v1); + sub_v3_v3v3_db(r, v, v2); + cross_v3_v3v3_db(N2, l, r); + + if ((d = dot_v3v3_db(N1, N2)) < 0) { + return false; + } + + sub_v3_v3v3_db(l, v0, v2); + sub_v3_v3v3_db(r, v, v0); + cross_v3_v3v3_db(N1, l, r); + + if ((d = dot_v3v3_db(N1, N2)) < 0) { + return false; + } + + sub_v3_v3v3_db(l, v1, v0); + sub_v3_v3v3_db(r, v, v1); + cross_v3_v3v3_db(N2, l, r); + + if ((d = dot_v3v3_db(N1, N2)) < 0) { + return false; + } + + return true; +} + +/* The following lineart_memory_get_XXX_space functions are for allocating new memory for some + * modified geometries in the culling stage. */ +static LineartElementLinkNode *lineart_memory_get_triangle_space(LineartRenderBuffer *rb) +{ + LineartElementLinkNode *reln; + + /* 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_aquire(&rb->render_data_pool, + 64 * rb->triangle_size); + + reln = lineart_list_append_pointer_pool_sized(&rb->triangle_buffer_pointers, + &rb->render_data_pool, + render_triangles, + sizeof(LineartElementLinkNode)); + reln->element_count = 64; + reln->flags |= LRT_ELEMENT_IS_ADDITIONAL; + + return reln; +} + +static LineartElementLinkNode *lineart_memory_get_vert_space(LineartRenderBuffer *rb) +{ + LineartElementLinkNode *reln; + + LineartVert *render_vertices = lineart_mem_aquire(&rb->render_data_pool, + sizeof(LineartVert) * 64); + + reln = lineart_list_append_pointer_pool_sized(&rb->vertex_buffer_pointers, + &rb->render_data_pool, + render_vertices, + sizeof(LineartElementLinkNode)); + reln->element_count = 64; + reln->flags |= LRT_ELEMENT_IS_ADDITIONAL; + + return reln; +} + +static LineartElementLinkNode *lineart_memory_get_line_space(LineartRenderBuffer *rb) +{ + LineartElementLinkNode *reln; + + LineartLine *render_lines = lineart_mem_aquire(&rb->render_data_pool, sizeof(LineartLine) * 64); + + reln = lineart_list_append_pointer_pool_sized(&rb->line_buffer_pointers, + &rb->render_data_pool, + render_lines, + sizeof(LineartElementLinkNode)); + reln->element_count = 64; + reln->crease_threshold = rb->crease_threshold; + reln->flags |= LRT_ELEMENT_IS_ADDITIONAL; + + return reln; +} + +static void lineart_triangle_post(LineartTriangle *rt, LineartTriangle *orig) +{ + /* Just re-assign normal and set cull flag. */ + copy_v3_v3_db(rt->gn, orig->gn); + rt->flags = LRT_CULL_GENERATED; +} + +static void lineart_triangle_set_cull_flag(LineartTriangle *rt, unsigned char flag) +{ + unsigned char intersection_only = (rt->flags & LRT_TRIANGLE_INTERSECTION_ONLY); + rt->flags = flag; + rt->flags |= intersection_only; +} + +static bool lineart_line_match(LineartTriangle *rt, LineartLine *rl, int v1, int v2) +{ + return ((rt->v[v1] == rl->l && rt->v[v2] == rl->r) || + (rt->v[v2] == rl->l && rt->v[v1] == rl->r)); +} + +/* Does near-plane cut on 1 triangle only. When cutting with far-plane, the camera vectors gets + * reversed by the caller so don't need to implement one in a different direction. */ +static void lineart_triangle_cull_single(LineartRenderBuffer *rb, + LineartTriangle *rt, + int in0, + int in1, + int in2, + double *cam_pos, + double *view_dir, + bool allow_boundaries, + double (*vp)[4], + Object *ob, + int *r_v_count, + int *r_l_count, + int *r_t_count, + LineartElementLinkNode *veln, + LineartElementLinkNode *leln, + LineartElementLinkNode *teln) +{ + double vv1[3], vv2[3], dot1, dot2; + double a; + int v_count = *r_v_count; + int l_count = *r_l_count; + int t_count = *r_t_count; + int l_obi, r_obi; + char new_flag = 0; + + LineartLine *new_rl, *rl, *old_rl; + LineartLineSegment *rls; + LineartTriangleAdjacent *rta; + + if (rt->flags & (LRT_CULL_USED | LRT_CULL_GENERATED | LRT_CULL_DISCARD)) { + return; + } + + /* See definition of rt->intersecting_verts and the usage in + * lineart_geometry_object_load() for details. */ + rta = (void *)rt->intersecting_verts; + + LineartVert *rv = &((LineartVert *)veln->pointer)[v_count]; + LineartTriangle *rt1 = (void *)(((unsigned char *)teln->pointer) + rb->triangle_size * t_count); + LineartTriangle *rt2 = (void *)(((unsigned char *)teln->pointer) + + rb->triangle_size * (t_count + 1)); + + new_rl = &((LineartLine *)leln->pointer)[l_count]; + /* Init rl to the last rl entry. */ + rl = new_rl; + +#define INCREASE_RL \ + l_count++; \ + l_obi = rl->l_obindex; \ + r_obi = rl->r_obindex; \ + new_rl = &((LineartLine *)leln->pointer)[l_count]; \ + rl = new_rl; \ + rl->l_obindex = l_obi; \ + rl->r_obindex = r_obi; \ + rls = lineart_mem_aquire(&rb->render_data_pool, sizeof(LineartLineSegment)); \ + BLI_addtail(&rl->segments, rls); + +#define SELECT_RL(rl_num, llink, rlink, newrt) \ + if (rta->rl[rl_num]) { \ + old_rl = rta->rl[rl_num]; \ + new_flag = old_rl->flags; \ + old_rl->flags = LRT_EDGE_FLAG_CHAIN_PICKED; \ + INCREASE_RL \ + rl->l = (llink); \ + rl->r = (rlink); \ + rl->flags = new_flag; \ + rl->object_ref = ob; \ + rl->tl = ((old_rl->tl == rt) ? (newrt) : (old_rl->tl)); \ + rl->tr = ((old_rl->tr == rt) ? (newrt) : (old_rl->tr)); \ + lineart_add_line_to_list(rb, rl); \ + } + +#define RELINK_RL(rl_num, newrt) \ + if (rta->rl[rl_num]) { \ + old_rl = rta->rl[rl_num]; \ + old_rl->tl = ((old_rl->tl == rt) ? (newrt) : (old_rl->tl)); \ + old_rl->tr = ((old_rl->tr == rt) ? (newrt) : (old_rl->tr)); \ + } + +#define REMOVE_TRIANGLE_RL \ + if (rta->rl[0]) { \ + rta->rl[0]->flags = LRT_EDGE_FLAG_CHAIN_PICKED; \ + } \ + if (rta->rl[1]) { \ + rta->rl[1]->flags = LRT_EDGE_FLAG_CHAIN_PICKED; \ + } \ + if (rta->rl[2]) { \ + rta->rl[2]->flags = LRT_EDGE_FLAG_CHAIN_PICKED; \ + } + + switch (in0 + in1 + in2) { + case 0: /* Triangle is visible. Ignore this triangle. */ + return; + case 3: + /* Triangle completely behind near plane, throw it away + * also remove render lines form being computed. */ + lineart_triangle_set_cull_flag(rt, LRT_CULL_DISCARD); + REMOVE_TRIANGLE_RL + return; + case 2: + /* Two points behind near plane, cut those and + * generate 2 new points, 3 lines and 1 triangle. */ + lineart_triangle_set_cull_flag(rt, LRT_CULL_USED); + + /* (!in0) means "when point 0 is visible". + * conditons for point 1, 2 are the same idea. + * 1-----|-------0 + * | | --- + * | |--- + * | ---| + * 2-- | + * (near)---------->(far) + * Will become: + * |N******0 + * |* *** + * |N** + * | + * | + * (near)---------->(far) + */ + if (!in0) { + + /* Cut point for line 2---|-----0. */ + sub_v3_v3v3_db(vv1, rt->v[0]->gloc, cam_pos); + sub_v3_v3v3_db(vv2, cam_pos, rt->v[2]->gloc); + dot1 = dot_v3v3_db(vv1, view_dir); + dot2 = dot_v3v3_db(vv2, view_dir); + a = dot1 / (dot1 + dot2); + /* Assign it to a new point. */ + interp_v3_v3v3_db(rv[0].gloc, rt->v[0]->gloc, rt->v[2]->gloc, a); + mul_v4_m4v3_db(rv[0].fbcoord, vp, rv[0].gloc); + rv[0].index = rt->v[2]->index; + + /* Cut point for line 1---|-----0. */ + sub_v3_v3v3_db(vv1, rt->v[0]->gloc, cam_pos); + sub_v3_v3v3_db(vv2, cam_pos, rt->v[1]->gloc); + dot1 = dot_v3v3_db(vv1, view_dir); + dot2 = dot_v3v3_db(vv2, view_dir); + a = dot1 / (dot1 + dot2); + /* Assign it to another new point. */ + interp_v3_v3v3_db(rv[1].gloc, rt->v[0]->gloc, rt->v[1]->gloc, a); + mul_v4_m4v3_db(rv[1].fbcoord, vp, rv[1].gloc); + rv[1].index = rt->v[1]->index; + + /* New line connecting two new points. */ + INCREASE_RL + if (allow_boundaries) { + rl->flags = LRT_EDGE_FLAG_CONTOUR; + lineart_prepend_line_direct(&rb->contours, rl); + } + /* Note: inverting rl->l/r (left/right point) doesn't matter as long as + * rt->rl and rt->v has the same sequence. and the winding direction + * can be either CW or CCW but needs to be consistent throughout the calculation. + */ + rl->l = &rv[1]; + rl->r = &rv[0]; + /* Only one adjacent triangle, because the other side is the near plane. */ + /* Use tl or tr doesn't matter. */ + rl->tl = rt1; + rl->object_ref = ob; + + /* New line connecting original point 0 and a new point, only when it's a selected line. */ + SELECT_RL(2, rt->v[0], &rv[0], rt1) + /* New line connecting original point 0 and another new point. */ + SELECT_RL(0, rt->v[0], &rv[1], rt1) + + /* Re-assign triangle point array to two new points. */ + rt1->v[0] = rt->v[0]; + rt1->v[1] = &rv[1]; + rt1->v[2] = &rv[0]; + + lineart_triangle_post(rt1, rt); + + v_count += 2; + t_count += 1; + } + else if (!in2) { + sub_v3_v3v3_db(vv1, rt->v[2]->gloc, cam_pos); + sub_v3_v3v3_db(vv2, cam_pos, rt->v[0]->gloc); + dot1 = dot_v3v3_db(vv1, view_dir); + dot2 = dot_v3v3_db(vv2, view_dir); + a = dot1 / (dot1 + dot2); + interp_v3_v3v3_db(rv[0].gloc, rt->v[2]->gloc, rt->v[0]->gloc, a); + mul_v4_m4v3_db(rv[0].fbcoord, vp, rv[0].gloc); + rv[0].index = rt->v[0]->index; + + sub_v3_v3v3_db(vv1, rt->v[2]->gloc, cam_pos); + sub_v3_v3v3_db(vv2, cam_pos, rt->v[1]->gloc); + dot1 = dot_v3v3_db(vv1, view_dir); + dot2 = dot_v3v3_db(vv2, view_dir); + a = dot1 / (dot1 + dot2); + interp_v3_v3v3_db(rv[1].gloc, rt->v[2]->gloc, rt->v[1]->gloc, a); + mul_v4_m4v3_db(rv[1].fbcoord, vp, rv[1].gloc); + rv[1].index = rt->v[1]->index; + + INCREASE_RL + if (allow_boundaries) { + rl->flags = LRT_EDGE_FLAG_CONTOUR; + lineart_prepend_line_direct(&rb->contours, rl); + } + rl->l = &rv[0]; + rl->r = &rv[1]; + rl->tl = rt1; + rl->object_ref = ob; + + SELECT_RL(2, rt->v[2], &rv[0], rt1) + SELECT_RL(1, rt->v[2], &rv[1], rt1) + + rt1->v[0] = &rv[0]; + rt1->v[1] = &rv[1]; + rt1->v[2] = rt->v[2]; + + lineart_triangle_post(rt1, rt); + + v_count += 2; + t_count += 1; + } + else if (!in1) { + sub_v3_v3v3_db(vv1, rt->v[1]->gloc, cam_pos); + sub_v3_v3v3_db(vv2, cam_pos, rt->v[2]->gloc); + dot1 = dot_v3v3_db(vv1, view_dir); + dot2 = dot_v3v3_db(vv2, view_dir); + a = dot1 / (dot1 + dot2); + interp_v3_v3v3_db(rv[0].gloc, rt->v[1]->gloc, rt->v[2]->gloc, a); + mul_v4_m4v3_db(rv[0].fbcoord, vp, rv[0].gloc); + rv[0].index = rt->v[2]->index; + + sub_v3_v3v3_db(vv1, rt->v[1]->gloc, cam_pos); + sub_v3_v3v3_db(vv2, cam_pos, rt->v[0]->gloc); + dot1 = dot_v3v3_db(vv1, view_dir); + dot2 = dot_v3v3_db(vv2, view_dir); + a = dot1 / (dot1 + dot2); + interp_v3_v3v3_db(rv[1].gloc, rt->v[1]->gloc, rt->v[0]->gloc, a); + mul_v4_m4v3_db(rv[1].fbcoord, vp, rv[1].gloc); + rv[1].index = rt->v[0]->index; + + INCREASE_RL + if (allow_boundaries) { + rl->flags = LRT_EDGE_FLAG_CONTOUR; + lineart_prepend_line_direct(&rb->contours, rl); + } + rl->l = &rv[1]; + rl->r = &rv[0]; + rl->tl = rt1; + rl->object_ref = ob; + + SELECT_RL(1, rt->v[1], &rv[0], rt1) + SELECT_RL(0, rt->v[1], &rv[1], rt1) + + rt1->v[0] = &rv[0]; + rt1->v[1] = rt->v[1]; + rt1->v[2] = &rv[1]; + + lineart_triangle_post(rt1, rt); + + v_count += 2; + t_count += 1; + } + break; + case 1: + /* One point behind near plane, cut those and + * generate 2 new points, 4 lines and 2 triangles. */ + lineart_triangle_set_cull_flag(rt, LRT_CULL_USED); + + /* (in0) means "when point 0 is invisible". + * conditons for point 1, 2 are the same idea. + * 0------|----------1 + * -- | | + * ---| | + * |-- | + * | --- | + * | --- | + * | --2 + * (near)---------->(far) + * Will become: + * |N*********1 + * |* *** | + * |* *** | + * |N** | + * | *** | + * | *** | + * | **2 + * (near)---------->(far) + */ + if (in0) { + /* Cut point for line 0---|------1. */ + sub_v3_v3v3_db(vv1, rt->v[1]->gloc, cam_pos); + sub_v3_v3v3_db(vv2, cam_pos, rt->v[0]->gloc); + dot1 = dot_v3v3_db(vv1, view_dir); + dot2 = dot_v3v3_db(vv2, view_dir); + a = dot2 / (dot1 + dot2); + /* Assign to a new point. */ + interp_v3_v3v3_db(rv[0].gloc, rt->v[0]->gloc, rt->v[1]->gloc, a); + mul_v4_m4v3_db(rv[0].fbcoord, vp, rv[0].gloc); + rv[0].index = rt->v[0]->index; + + /* Cut point for line 0---|------2. */ + sub_v3_v3v3_db(vv1, rt->v[2]->gloc, cam_pos); + sub_v3_v3v3_db(vv2, cam_pos, rt->v[0]->gloc); + dot1 = dot_v3v3_db(vv1, view_dir); + dot2 = dot_v3v3_db(vv2, view_dir); + a = dot2 / (dot1 + dot2); + /* Assign to aother new point. */ + interp_v3_v3v3_db(rv[1].gloc, rt->v[0]->gloc, rt->v[2]->gloc, a); + mul_v4_m4v3_db(rv[1].fbcoord, vp, rv[1].gloc); + rv[1].index = rt->v[0]->index; + + /* New line connects two new points. */ + INCREASE_RL + if (allow_boundaries) { + rl->flags = LRT_EDGE_FLAG_CONTOUR; + lineart_prepend_line_direct(&rb->contours, rl); + } + rl->l = &rv[1]; + rl->r = &rv[0]; + rl->tl = rt1; + rl->object_ref = ob; + + /* New line connects new point 0 and old point 1, + * this is a border line. + */ + + SELECT_RL(0, rt->v[1], &rv[0], rt1) + SELECT_RL(2, rt->v[2], &rv[1], rt2) + RELINK_RL(1, rt2) + + /* We now have one triangle closed. */ + rt1->v[0] = rt->v[1]; + rt1->v[1] = &rv[1]; + rt1->v[2] = &rv[0]; + /* Close the second triangle. */ + rt2->v[0] = &rv[1]; + rt2->v[1] = rt->v[1]; + rt2->v[2] = rt->v[2]; + + lineart_triangle_post(rt1, rt); + lineart_triangle_post(rt2, rt); + + v_count += 2; + t_count += 2; + } + else if (in1) { + + sub_v3_v3v3_db(vv1, rt->v[1]->gloc, cam_pos); + sub_v3_v3v3_db(vv2, cam_pos, rt->v[2]->gloc); + dot1 = dot_v3v3_db(vv1, view_dir); + dot2 = dot_v3v3_db(vv2, view_dir); + a = dot1 / (dot1 + dot2); + interp_v3_v3v3_db(rv[0].gloc, rt->v[1]->gloc, rt->v[2]->gloc, a); + mul_v4_m4v3_db(rv[0].fbcoord, vp, rv[0].gloc); + rv[0].index = rt->v[1]->index; + + sub_v3_v3v3_db(vv1, rt->v[1]->gloc, cam_pos); + sub_v3_v3v3_db(vv2, cam_pos, rt->v[0]->gloc); + dot1 = dot_v3v3_db(vv1, view_dir); + dot2 = dot_v3v3_db(vv2, view_dir); + a = dot1 / (dot1 + dot2); + interp_v3_v3v3_db(rv[1].gloc, rt->v[1]->gloc, rt->v[0]->gloc, a); + mul_v4_m4v3_db(rv[1].fbcoord, vp, rv[1].gloc); + rv[1].index = rt->v[1]->index; + + INCREASE_RL + if (allow_boundaries) { + rl->flags = LRT_EDGE_FLAG_CONTOUR; + lineart_prepend_line_direct(&rb->contours, rl); + } + rl->l = &rv[1]; + rl->r = &rv[0]; + rl->tl = rt1; + rl->object_ref = ob; + + SELECT_RL(1, rt->v[2], &rv[0], rt1) + SELECT_RL(0, rt->v[0], &rv[1], rt2) + RELINK_RL(2, rt2) + + rt1->v[0] = rt->v[2]; + rt1->v[1] = &rv[1]; + rt1->v[2] = &rv[0]; + + rt2->v[0] = &rv[1]; + rt2->v[1] = rt->v[2]; + rt2->v[2] = rt->v[0]; + + lineart_triangle_post(rt1, rt); + lineart_triangle_post(rt2, rt); + + v_count += 2; + t_count += 2; + } + else if (in2) { + + sub_v3_v3v3_db(vv1, rt->v[2]->gloc, cam_pos); + sub_v3_v3v3_db(vv2, cam_pos, rt->v[0]->gloc); + dot1 = dot_v3v3_db(vv1, view_dir); + dot2 = dot_v3v3_db(vv2, view_dir); + a = dot1 / (dot1 + dot2); + interp_v3_v3v3_db(rv[0].gloc, rt->v[2]->gloc, rt->v[0]->gloc, a); + mul_v4_m4v3_db(rv[0].fbcoord, vp, rv[0].gloc); + rv[0].index = rt->v[2]->index; + + sub_v3_v3v3_db(vv1, rt->v[2]->gloc, cam_pos); + sub_v3_v3v3_db(vv2, cam_pos, rt->v[1]->gloc); + dot1 = dot_v3v3_db(vv1, view_dir); + dot2 = dot_v3v3_db(vv2, view_dir); + a = dot1 / (dot1 + dot2); + interp_v3_v3v3_db(rv[1].gloc, rt->v[2]->gloc, rt->v[1]->gloc, a); + mul_v4_m4v3_db(rv[1].fbcoord, vp, rv[1].gloc); + rv[1].index = rt->v[2]->index; + + INCREASE_RL + if (allow_boundaries) { + rl->flags = LRT_EDGE_FLAG_CONTOUR; + lineart_prepend_line_direct(&rb->contours, rl); + } + rl->l = &rv[1]; + rl->r = &rv[0]; + rl->tl = rt1; + rl->object_ref = ob; + + SELECT_RL(2, rt->v[0], &rv[0], rt1) + SELECT_RL(1, rt->v[1], &rv[1], rt2) + RELINK_RL(0, rt2) + + rt1->v[0] = rt->v[0]; + rt1->v[1] = &rv[1]; + rt1->v[2] = &rv[0]; + + rt2->v[0] = &rv[1]; + rt2->v[1] = rt->v[0]; + rt2->v[2] = rt->v[1]; + + lineart_triangle_post(rt1, rt); + lineart_triangle_post(rt2, rt); + + v_count += 2; + t_count += 2; + } + break; + } + *r_v_count = v_count; + *r_l_count = l_count; + *r_t_count = t_count; + +#undef INCREASE_RL +#undef SELECT_RL +#undef RELINK_RL +#undef REMOVE_TRIANGLE_RL +} + +/* This function cuts triangles with near- or far-plane. Setting clip_far = true for cutting with + * far-plane. For triangles that's crossing the plane, it will generate new 1 or 2 triangles with + * new topology that represents the trimmed triangle. (which then became a triangle or a square + * formed by two triangles) + */ +static void lineart_main_cull_triangles(LineartRenderBuffer *rb, bool clip_far) +{ + LineartTriangle *rt; + LineartElementLinkNode *veln, *teln, *leln; + double(*vp)[4] = rb->view_projection; + int i; + int v_count = 0, t_count = 0, l_count = 0; + Object *ob; + bool allow_boundaries = rb->allow_boundaries; + double cam_pos[3]; + double clip_start = rb->near_clip, clip_end = rb->far_clip; + double view_dir[3], clip_advance[3]; + + copy_v3_v3_db(view_dir, rb->view_vector); + copy_v3_v3_db(clip_advance, rb->view_vector); + copy_v3_v3_db(cam_pos, rb->camera_pos); + + if (clip_far) { + /* Move starting point to end plane. */ + mul_v3db_db(clip_advance, -clip_end); + add_v3_v3_db(cam_pos, clip_advance); + + /* "reverse looking". */ + mul_v3db_db(view_dir, -1.0f); + } + else { + /* Clip Near. */ + mul_v3db_db(clip_advance, -clip_start); + add_v3_v3_db(cam_pos, clip_advance); + } + + veln = lineart_memory_get_vert_space(rb); + teln = lineart_memory_get_triangle_space(rb); + leln = lineart_memory_get_line_space(rb); + + /* Additional memory space for storing generated points and triangles. */ +#define LRT_CULL_ENSURE_MEMORY \ + if (v_count > 60) { \ + veln->element_count = v_count; \ + veln = lineart_memory_get_vert_space(rb); \ + v_count = 0; \ + } \ + if (t_count > 60) { \ + teln->element_count = t_count; \ + teln = lineart_memory_get_triangle_space(rb); \ + t_count = 0; \ + } \ + if (l_count > 60) { \ + leln->element_count = l_count; \ + leln = lineart_memory_get_line_space(rb); \ + l_count = 0; \ + } + +#define LRT_CULL_DECIDE_INSIDE \ + /* These three represents points that are in the clipping range or not*/ \ + in0 = 0, in1 = 0, in2 = 0; \ + if (clip_far) { \ + /* Point outside far plane. */ \ + if (rt->v[0]->fbcoord[use_w] > clip_end) { \ + in0 = 1; \ + } \ + if (rt->v[1]->fbcoord[use_w] > clip_end) { \ + in1 = 1; \ + } \ + if (rt->v[2]->fbcoord[use_w] > clip_end) { \ + in2 = 1; \ + } \ + } \ + else { \ + /* Point inside near plane. */ \ + if (rt->v[0]->fbcoord[use_w] < clip_start) { \ + in0 = 1; \ + } \ + if (rt->v[1]->fbcoord[use_w] < clip_start) { \ + in1 = 1; \ + } \ + if (rt->v[2]->fbcoord[use_w] < clip_start) { \ + in2 = 1; \ + } \ + } + + int use_w = 3; + int in0 = 0, in1 = 0, in2 = 0; + + if (!rb->cam_is_persp) { + clip_start = -1; + clip_end = 1; + use_w = 2; + } + + /* Then go through all the other triangles. */ + LISTBASE_FOREACH (LineartElementLinkNode *, reln, &rb->triangle_buffer_pointers) { + if (reln->flags & LRT_ELEMENT_IS_ADDITIONAL) { + continue; + } + ob = reln->object_ref; + for (i = 0; i < reln->element_count; i++) { + /* Select the triangle in the array. */ + rt = (void *)(((unsigned char *)reln->pointer) + rb->triangle_size * i); + + LRT_CULL_DECIDE_INSIDE + LRT_CULL_ENSURE_MEMORY + lineart_triangle_cull_single(rb, + rt, + in0, + in1, + in2, + cam_pos, + view_dir, + allow_boundaries, + vp, + ob, + &v_count, + &l_count, + &t_count, + veln, + leln, + teln); + } + teln->element_count = t_count; + veln->element_count = v_count; + } + +#undef LRT_CULL_ENSURE_MEMORY +#undef LRT_CULL_DECIDE_INSIDE +} + +/* Adjacent data is only used during the initial stages of computing. So we can free it using this + * function when it is not needed anymore. */ +static void lineart_main_free_adjacent_data(LineartRenderBuffer *rb) +{ + LinkData *ld; + while ((ld = BLI_pophead(&rb->triangle_adjacent_pointers)) != NULL) { + MEM_freeN(ld->data); + } + LISTBASE_FOREACH (LineartElementLinkNode *, reln, &rb->triangle_buffer_pointers) { + LineartTriangle *rt = reln->pointer; + int i; + for (i = 0; i < reln->element_count; i++) { + /* See definition of rt->intersecting_verts and the usage in + * lineart_geometry_object_load() for detailes. */ + rt->intersecting_verts = NULL; + rt = (LineartTriangle *)(((unsigned char *)rt) + rb->triangle_size); + } + } +} + +static void lineart_main_perspective_division(LineartRenderBuffer *rb) +{ + LineartVert *rv; + int i; + + if (!rb->cam_is_persp) { + return; + } + + LISTBASE_FOREACH (LineartElementLinkNode *, reln, &rb->vertex_buffer_pointers) { + rv = reln->pointer; + for (i = 0; i < reln->element_count; i++) { + /* Do not divide Z, we use Z to back transform cut points in later chaining process. */ + rv[i].fbcoord[0] /= rv[i].fbcoord[3]; + rv[i].fbcoord[1] /= rv[i].fbcoord[3]; + /* Re-map z into (0-1) range, because we no longer need NDC (Normalized Device Coordinates) + * at the moment. + * The algorithm currently doesn't need Z for operation, we use W instead. If Z is needed in + * the future, the line below correctly transforms it to view space coordinates. */ + /* rv[i].fbcoord[2] = -2 * rv[i].fbcoord[2] / (far - near) - (far + near) / (far - near);. */ + rv[i].fbcoord[0] -= rb->shift_x * 2; + rv[i].fbcoord[1] -= rb->shift_y * 2; + } + } +} + +/* Transform a single vert to it's viewing position. */ +static void lineart_vert_transform( + BMVert *v, int index, LineartVert *RvBuf, double (*mv_mat)[4], double (*mvp_mat)[4]) +{ + double co[4]; + LineartVert *rv = &RvBuf[index]; + copy_v3db_v3fl(co, v->co); + mul_v3_m4v3_db(rv->gloc, mv_mat, co); + mul_v4_m4v3_db(rv->fbcoord, mvp_mat, co); +} + +/* Because we have a variable size for LineartTriangle, we need an access helper. See + * LineartTriangleThread for more info. */ +static LineartTriangle *lineart_triangle_from_index(LineartRenderBuffer *rb, + LineartTriangle *rt_array, + int index) +{ + char *b = (char *)rt_array; + b += (index * rb->triangle_size); + return (LineartTriangle *)b; +} + +static char lineart_identify_feature_line(LineartRenderBuffer *rb, + BMEdge *e, + LineartTriangle *rt_array, + LineartVert *rv_array, + float crease_threshold, + bool no_crease, + bool count_freestyle, + BMesh *bm_if_freestyle) +{ + BMLoop *ll, *lr = NULL; + ll = e->l; + if (ll) { + lr = e->l->radial_next; + } + + if (ll == lr || !lr) { + return LRT_EDGE_FLAG_CONTOUR; + } + + LineartTriangle *rt1, *rt2; + LineartVert *l; + + /* The mesh should already be triangulated now, so we can assume each face is a triangle. */ + rt1 = lineart_triangle_from_index(rb, rt_array, BM_elem_index_get(ll->f)); + rt2 = lineart_triangle_from_index(rb, rt_array, BM_elem_index_get(lr->f)); + + l = &rv_array[BM_elem_index_get(e->v1)]; + + double vv[3]; + double *view_vector = vv; + double dot_1 = 0, dot_2 = 0; + double result; + FreestyleEdge *fe; + + if (rb->cam_is_persp) { + sub_v3_v3v3_db(view_vector, l->gloc, rb->camera_pos); + } + else { + view_vector = rb->view_vector; + } + + dot_1 = dot_v3v3_db(view_vector, rt1->gn); + dot_2 = dot_v3v3_db(view_vector, rt2->gn); + + if ((result = dot_1 * dot_2) < 0 && (dot_1 + dot_2)) { + return LRT_EDGE_FLAG_CONTOUR; + } + + if (rb->use_crease && (dot_v3v3_db(rt1->gn, rt2->gn) < crease_threshold)) { + if (!no_crease) { + return LRT_EDGE_FLAG_CREASE; + } + } + else if (rb->use_material && (ll->f->mat_nr != lr->f->mat_nr)) { + return LRT_EDGE_FLAG_MATERIAL; + } + else if (count_freestyle && rb->use_edge_marks) { + fe = CustomData_bmesh_get(&bm_if_freestyle->edata, e->head.data, CD_FREESTYLE_EDGE); + if (fe->flag & FREESTYLE_EDGE_MARK) { + return LRT_EDGE_FLAG_EDGE_MARK; + } + } + return 0; +} + +static void lineart_add_line_to_list(LineartRenderBuffer *rb, LineartLine *rl) +{ + switch (rl->flags) { + case LRT_EDGE_FLAG_CONTOUR: + lineart_prepend_line_direct(&rb->contours, rl); + break; + case LRT_EDGE_FLAG_CREASE: + lineart_prepend_line_direct(&rb->crease_lines, rl); + break; + case LRT_EDGE_FLAG_MATERIAL: + lineart_prepend_line_direct(&rb->material_lines, rl); + break; + case LRT_EDGE_FLAG_EDGE_MARK: + lineart_prepend_line_direct(&rb->edge_marks, rl); + break; + case LRT_EDGE_FLAG_INTERSECTION: + lineart_prepend_line_direct(&rb->intersection_lines, rl); + break; + } +} + +static void lineart_triangle_adjacent_assign(LineartTriangle *rt, + LineartTriangleAdjacent *rta, + LineartLine *rl) +{ + if (lineart_line_match(rt, rl, 0, 1)) { + rta->rl[0] = rl; + } + else if (lineart_line_match(rt, rl, 1, 2)) { + rta->rl[1] = rl; + } + else if (lineart_line_match(rt, rl, 2, 0)) { + rta->rl[2] = rl; + } +} + +static void lineart_geometry_object_load(Depsgraph *dg, + Object *ob, + double (*mv_mat)[4], + double (*mvp_mat)[4], + LineartRenderBuffer *rb, + int override_usage, + int *global_vindex) +{ + BMesh *bm; + BMVert *v; + BMFace *f; + BMEdge *e; + BMLoop *loop; + LineartLine *rl; + LineartTriangle *rt; + LineartTriangleAdjacent *orta; + double new_mvp[4][4], new_mv[4][4], normal[4][4]; + float imat[4][4]; + LineartElementLinkNode *reln; + LineartVert *orv; + LineartLine *orl; + LineartTriangle *ort; + Object *orig_ob; + int CanFindFreestyle = 0; + int i, global_i = (*global_vindex); + Mesh *use_mesh; + float use_crease = 0; + + int usage = override_usage ? override_usage : ob->lineart.usage; + +#define LRT_MESH_FINISH \ + BM_mesh_free(bm); \ + if (ob->type != OB_MESH) { \ + BKE_mesh_free(use_mesh); \ + MEM_freeN(use_mesh); \ + } + + if (usage == OBJECT_LRT_EXCLUDE) { + return; + } + + if (ob->type == OB_MESH || ob->type == OB_MBALL || ob->type == OB_CURVE || ob->type == OB_SURF || + ob->type == OB_FONT) { + + if (ob->type == OB_MESH) { + use_mesh = DEG_get_evaluated_object(dg, ob)->data; + } + else { + use_mesh = BKE_mesh_new_from_object(NULL, ob, false); + } + + /* In case we can not get any mesh geometry data from the object */ + if (!use_mesh) { + return; + } + + /* First we need to prepare the matrix used for transforming this specific object. */ + mul_m4db_m4db_m4fl_uniq(new_mvp, mvp_mat, ob->obmat); + mul_m4db_m4db_m4fl_uniq(new_mv, mv_mat, ob->obmat); + + invert_m4_m4(imat, ob->obmat); + transpose_m4(imat); + copy_m4d_m4(normal, imat); + + if (use_mesh->edit_mesh) { + /* Do not use edit_mesh directly because we will modify it, so create a copy. */ + bm = BM_mesh_copy(use_mesh->edit_mesh->bm); + } + else { + const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(((Mesh *)(use_mesh))); + bm = BM_mesh_create(&allocsize, + &((struct BMeshCreateParams){ + .use_toolflags = true, + })); + BM_mesh_bm_from_me(bm, + use_mesh, + &((struct BMeshFromMeshParams){ + .calc_face_normal = true, + })); + } + + if (rb->remove_doubles) { + BMEditMesh *em = BKE_editmesh_create(bm, false); + BMOperator findop, weldop; + + /* See bmesh_opdefines.c and bmesh_operators.c for op names and argument formatting. */ + BMO_op_initf(bm, &findop, BMO_FLAG_DEFAULTS, "find_doubles verts=%av dist=%f", 0.0001); + + BMO_op_exec(bm, &findop); + + /* Weld the vertices. */ + BMO_op_init(bm, &weldop, BMO_FLAG_DEFAULTS, "weld_verts"); + BMO_slot_copy(&findop, slots_out, "targetmap.out", &weldop, slots_in, "targetmap"); + BMO_op_exec(bm, &weldop); + + BMO_op_finish(bm, &findop); + BMO_op_finish(bm, &weldop); + + MEM_freeN(em); + } + + BM_mesh_elem_hflag_disable_all(bm, BM_FACE | BM_EDGE, BM_ELEM_TAG, false); + BM_mesh_triangulate( + bm, MOD_TRIANGULATE_QUAD_FIXED, MOD_TRIANGULATE_NGON_BEAUTY, 4, false, NULL, NULL, NULL); + BM_mesh_normals_update(bm); + BM_mesh_elem_table_ensure(bm, BM_VERT | BM_EDGE | BM_FACE); + BM_mesh_elem_index_ensure(bm, BM_VERT | BM_EDGE | BM_FACE); + + if (CustomData_has_layer(&bm->edata, CD_FREESTYLE_EDGE)) { + CanFindFreestyle = 1; + } + + /* Only allocate memory for verts and tris as we don't know how many lines we will generate + * yet. */ + orv = lineart_mem_aquire(&rb->render_data_pool, sizeof(LineartVert) * bm->totvert); + ort = lineart_mem_aquire(&rb->render_data_pool, bm->totface * rb->triangle_size); + + orig_ob = ob->id.orig_id ? (Object *)ob->id.orig_id : ob; + + reln = lineart_list_append_pointer_pool_sized( + &rb->vertex_buffer_pointers, &rb->render_data_pool, orv, sizeof(LineartElementLinkNode)); + reln->element_count = bm->totvert; + reln->object_ref = orig_ob; + + if (ob->lineart.flags & OBJECT_LRT_OWN_CREASE) { + use_crease = cosf(M_PI - ob->lineart.crease_threshold); + } + else { + use_crease = rb->crease_threshold; + } + + /* FIXME Yiming: Hack for getting clean 3D text, the seam that extruded text object creates + * erroneous detection on creases. Future configuration should allow options. */ + if (ob->type == OB_FONT) { + reln->flags |= LRT_ELEMENT_BORDER_ONLY; + } + + reln = lineart_list_append_pointer_pool_sized( + &rb->triangle_buffer_pointers, &rb->render_data_pool, ort, sizeof(LineartElementLinkNode)); + reln->element_count = bm->totface; + reln->object_ref = orig_ob; + reln->flags |= (usage == OBJECT_LRT_NO_INTERSECTION ? LRT_ELEMENT_NO_INTERSECTION : 0); + + /* Note this memory is not from pool, will be deleted after culling. */ + orta = MEM_callocN(sizeof(LineartTriangleAdjacent) * bm->totface, "LineartTriangleAdjacent"); + /* Link is minimal so we use pool anyway. */ + lineart_list_append_pointer_pool(&rb->triangle_adjacent_pointers, &rb->render_data_pool, orta); + + for (i = 0; i < bm->totvert; i++) { + v = BM_vert_at_index(bm, i); + lineart_vert_transform(v, i, orv, new_mv, new_mvp); + orv[i].index = i + global_i; + } + /* Register a global index increment. See lineart_triangle_share_edge() and + * lineart_main_load_geometries() for detailes. It's okay that global_vindex might eventually + * overflow, in such large scene it's virtually impossible for two vertex of the same numeric + * index to come close together. */ + (*global_vindex) += bm->totvert; + + rt = ort; + for (i = 0; i < bm->totface; i++) { + f = BM_face_at_index(bm, i); + + loop = f->l_first; + rt->v[0] = &orv[BM_elem_index_get(loop->v)]; + loop = loop->next; + rt->v[1] = &orv[BM_elem_index_get(loop->v)]; + loop = loop->next; + rt->v[2] = &orv[BM_elem_index_get(loop->v)]; + + /* Transparency bit assignment. */ + Material *mat = BKE_object_material_get(ob, f->mat_nr + 1); + rt->transparency_mask = ((mat && (mat->lineart.flags & LRT_MATERIAL_TRANSPARENCY_ENABLED)) ? + mat->lineart.transparency_mask : + 0); + + double gn[3]; + copy_v3db_v3fl(gn, f->no); + mul_v3_mat3_m4v3_db(rt->gn, normal, gn); + normalize_v3_db(rt->gn); + + if (usage == OBJECT_LRT_INTERSECTION_ONLY) { + rt->flags |= LRT_TRIANGLE_INTERSECTION_ONLY; + } + else if (usage == OBJECT_LRT_NO_INTERSECTION || usage == OBJECT_LRT_OCCLUSION_ONLY) { + rt->flags |= LRT_TRIANGLE_NO_INTERSECTION; + } + + /* Re-use this field to refer to adjacent info, will be cleared after culling stage. */ + rt->intersecting_verts = (void *)&orta[i]; + + rt = (LineartTriangle *)(((unsigned char *)rt) + rb->triangle_size); + } + + /* Use BM_ELEM_TAG in f->head.hflag to store needed faces in the first iteration. */ + + int allocate_rl = 0; + for (i = 0; i < bm->totedge; i++) { + e = BM_edge_at_index(bm, i); + + /* Because e->head.hflag is char, so line type flags should not exceed positive 7 bits. */ + char eflag = lineart_identify_feature_line( + rb, e, ort, orv, use_crease, ob->type == OB_FONT, CanFindFreestyle, bm); + if (eflag) { + /* Only allocate for feature lines (instead of all lines) to save memory. */ + allocate_rl++; + } + /* Here we just use bm's flag for when loading actual lines, then we don't need to call + * lineart_identify_feature_line() again, e->head.hflag deleted after loading anyway. Always + * set the flag, so hflag stays 0 for lines that are not feature lines. */ + e->head.hflag = eflag; + } + + orl = lineart_mem_aquire(&rb->render_data_pool, sizeof(LineartLine) * allocate_rl); + reln = lineart_list_append_pointer_pool_sized( + &rb->line_buffer_pointers, &rb->render_data_pool, orl, sizeof(LineartElementLinkNode)); + reln->element_count = allocate_rl; + reln->object_ref = orig_ob; + + rl = orl; + for (i = 0; i < bm->totedge; i++) { + e = BM_edge_at_index(bm, i); + + /* Not a feature line, so we skip. */ + if (!e->head.hflag) { + continue; + } + + rl->l = &orv[BM_elem_index_get(e->v1)]; + rl->r = &orv[BM_elem_index_get(e->v2)]; + rl->l_obindex = rl->l->index - global_i; + rl->r_obindex = rl->r->index - global_i; + if (e->l) { + int findex = BM_elem_index_get(e->l->f); + rl->tl = lineart_triangle_from_index(rb, ort, findex); + lineart_triangle_adjacent_assign(rl->tl, &orta[findex], rl); + if (e->l->radial_next && e->l->radial_next != e->l) { + findex = BM_elem_index_get(e->l->radial_next->f); + rl->tr = lineart_triangle_from_index(rb, ort, findex); + lineart_triangle_adjacent_assign(rl->tr, &orta[findex], rl); + } + } + rl->flags = e->head.hflag; + rl->object_ref = orig_ob; + + LineartLineSegment *rls = lineart_mem_aquire(&rb->render_data_pool, + sizeof(LineartLineSegment)); + BLI_addtail(&rl->segments, rls); + if (usage == OBJECT_LRT_INHERENT || usage == OBJECT_LRT_INCLUDE || + usage == OBJECT_LRT_NO_INTERSECTION) { + lineart_add_line_to_list(rb, rl); + } + + rl++; + } + + LRT_MESH_FINISH + } + +#undef LRT_MESH_FINISH +} + +/* See if this object in such collection is used for generating line art, + * Disabling a collection for line art will diable all objects inside. */ +static int lineart_usage_check(Collection *c, Object *ob) +{ + + if (!c) { + return OBJECT_LRT_INHERENT; + } + + int object_is_used = (ob->lineart.usage != OBJECT_LRT_INHERENT); + + if (object_is_used) { + return ob->lineart.usage; + } + + if (c->children.first == NULL) { + if (BKE_collection_has_object(c, (Object *)(ob->id.orig_id))) { + if (ob->lineart.usage == OBJECT_LRT_INHERENT) { + switch (c->lineart_usage) { + case COLLECTION_LRT_OCCLUSION_ONLY: + return OBJECT_LRT_OCCLUSION_ONLY; + case COLLECTION_LRT_EXCLUDE: + return OBJECT_LRT_EXCLUDE; + case COLLECTION_LRT_INTERSECTION_ONLY: + return OBJECT_LRT_INTERSECTION_ONLY; + case COLLECTION_LRT_NO_INTERSECTION: + return OBJECT_LRT_NO_INTERSECTION; + } + return OBJECT_LRT_INHERENT; + } + return ob->lineart.usage; + } + return OBJECT_LRT_INHERENT; + } + + LISTBASE_FOREACH (CollectionChild *, cc, &c->children) { + int result = lineart_usage_check(cc->collection, ob); + if (result > OBJECT_LRT_INHERENT) { + return result; + } + } + + return OBJECT_LRT_INHERENT; +} + +static void lineart_main_load_geometries( + Depsgraph *depsgraph, + Scene *scene, + Object *camera /* Still use camera arg for convenience. */, + LineartRenderBuffer *rb, + bool allow_duplicates) +{ + double proj[4][4], view[4][4], result[4][4]; + float inv[4][4]; + + Camera *cam = camera->data; + float sensor = BKE_camera_sensor_size(cam->sensor_fit, cam->sensor_x, cam->sensor_y); + double fov = focallength_to_fov(cam->lens, sensor); + + double asp = ((double)rb->w / (double)rb->h); + + if (cam->type == CAM_PERSP) { + if (asp < 1) { + fov /= asp; + } + lineart_matrix_perspective_44d(proj, fov, asp, cam->clip_start, cam->clip_end); + } + else if (cam->type == CAM_ORTHO) { + double w = cam->ortho_scale / 2; + lineart_matrix_ortho_44d(proj, -w, w, -w / asp, w / asp, cam->clip_start, cam->clip_end); + } + invert_m4_m4(inv, rb->cam_obmat); + mul_m4db_m4db_m4fl_uniq(result, proj, inv); + copy_m4_m4_db(proj, result); + copy_m4_m4_db(rb->view_projection, proj); + + unit_m4_db(view); + + BLI_listbase_clear(&rb->triangle_buffer_pointers); + BLI_listbase_clear(&rb->vertex_buffer_pointers); + + int flags = DEG_ITER_OBJECT_FLAG_LINKED_DIRECTLY | DEG_ITER_OBJECT_FLAG_LINKED_VIA_SET | + DEG_ITER_OBJECT_FLAG_VISIBLE; + + /* Instance duplicated & particles. */ + if (allow_duplicates) { + flags |= DEG_ITER_OBJECT_FLAG_DUPLI; + } + + /* This is to serialize vertex index in the whole scene, so lineart_triangle_share_edge() can + * work properly from the lack of triangle adjacent info. */ + int global_i = 0; + + DEG_OBJECT_ITER_BEGIN (depsgraph, ob, flags) { + int usage = lineart_usage_check(scene->master_collection, ob); + + lineart_geometry_object_load(depsgraph, ob, view, proj, rb, usage, &global_i); + } + DEG_OBJECT_ITER_END; +} + +/* Returns the two other verts of the triangle given a vertex. Returns false if the given vertex + * doesn't belong to this triangle. */ +static bool lineart_triangle_get_other_verts(const LineartTriangle *rt, + const LineartVert *rv, + LineartVert **l, + LineartVert **r) +{ + if (rt->v[0] == rv) { + *l = rt->v[1]; + *r = rt->v[2]; + return true; + } + if (rt->v[1] == rv) { + *l = rt->v[2]; + *r = rt->v[0]; + return true; + } + if (rt->v[2] == rv) { + *l = rt->v[0]; + *r = rt->v[1]; + return true; + } + return false; +} + +static bool lineart_edge_from_triangle(const LineartTriangle *rt, + const LineartLine *rl, + bool allow_overlapping_edges) +{ + /* Normally we just determine from the pointer address. */ + if (rl->tl == rt || rl->tr == rt) { + return true; + } + /* If allows overlapping, then we compare the vertex coordinates one by one to determine if one + * edge is from specific triangle. This is slower but can handle edge split cases very well. */ + if (allow_overlapping_edges) { +#define LRT_TRI_SAME_POINT(rt, i, pt) \ + ((LRT_DOUBLE_CLOSE_ENOUGH(rt->v[i]->gloc[0], pt->gloc[0]) && \ + LRT_DOUBLE_CLOSE_ENOUGH(rt->v[i]->gloc[1], pt->gloc[1]) && \ + LRT_DOUBLE_CLOSE_ENOUGH(rt->v[i]->gloc[2], pt->gloc[2])) || \ + (LRT_DOUBLE_CLOSE_ENOUGH(rt->v[i]->gloc[0], pt->gloc[0]) && \ + LRT_DOUBLE_CLOSE_ENOUGH(rt->v[i]->gloc[1], pt->gloc[1]) && \ + LRT_DOUBLE_CLOSE_ENOUGH(rt->v[i]->gloc[2], pt->gloc[2]))) + if ((LRT_TRI_SAME_POINT(rt, 0, rl->l) || LRT_TRI_SAME_POINT(rt, 1, rl->l) || + LRT_TRI_SAME_POINT(rt, 2, rl->l)) && + (LRT_TRI_SAME_POINT(rt, 0, rl->r) || LRT_TRI_SAME_POINT(rt, 1, rl->r) || + LRT_TRI_SAME_POINT(rt, 2, rl->r))) { + return true; + } +#undef LRT_TRI_SAME_POINT + } + return false; +} + +/* Sorting three intersection points from min to max, + * the order for each intersection is set in lst[0] to lst[2].*/ +#define INTERSECT_SORT_MIN_TO_MAX_3(ia, ib, ic, lst) \ + { \ + lst[0] = LRT_MIN3_INDEX(ia, ib, ic); \ + lst[1] = (((ia <= ib && ib <= ic) || (ic <= ib && ib <= ia)) ? \ + 1 : \ + (((ic <= ia && ia <= ib) || (ib < ia && ia <= ic)) ? 0 : 2)); \ + lst[2] = LRT_MAX3_INDEX(ia, ib, ic); \ + } + +/* ia ib ic are ordered. */ +#define INTERSECT_JUST_GREATER(is, order, num, index) \ + { \ + index = (num < is[order[0]] ? \ + order[0] : \ + (num < is[order[1]] ? order[1] : (num < is[order[2]] ? order[2] : order[2]))); \ + } + +/* ia ib ic are ordered. */ +#define INTERSECT_JUST_SMALLER(is, order, num, index) \ + { \ + index = (num > is[order[2]] ? \ + order[2] : \ + (num > is[order[1]] ? order[1] : (num > is[order[0]] ? order[0] : order[0]))); \ + } + +/* This is the main function to calculate + * the occlusion status between 1(one) triangle and 1(one) line. + * if returns true, then from/to will carry the occludded segments + * in ratio from rl->l to rl->r. The line is later cut with these two values. + */ +static bool lineart_triangle_line_image_space_occlusion(SpinLock *UNUSED(spl), + const LineartTriangle *rt, + const LineartLine *rl, + const double *override_camera_loc, + const bool override_cam_is_persp, + const bool allow_overlapping_edges, + const double vp[4][4], + const double *camera_dir, + const float cam_shift_x, + const float cam_shift_y, + double *from, + double *to) +{ + double is[3] = {0}; + int order[3]; + int LCross = -1, RCross = -1; + int a, b, c; + int st_l = 0, st_r = 0; + + double Lv[3]; + double Rv[3]; + double vd4[4]; + double Cv[3]; + double dot_l, dot_r, dot_la, dot_ra; + double dot_f; + double gloc[4], trans[4]; + double cut = -1; + + double *LFBC = rl->l->fbcoord, *RFBC = rl->r->fbcoord, *FBC0 = rt->v[0]->fbcoord, + *FBC1 = rt->v[1]->fbcoord, *FBC2 = rt->v[2]->fbcoord; + + /* Overlapping not possible, return early. */ + if ((MAX3(FBC0[0], FBC1[0], FBC2[0]) < MIN2(LFBC[0], RFBC[0])) || + (MIN3(FBC0[0], FBC1[0], FBC2[0]) > MAX2(LFBC[0], RFBC[0])) || + (MAX3(FBC0[1], FBC1[1], FBC2[1]) < MIN2(LFBC[1], RFBC[1])) || + (MIN3(FBC0[1], FBC1[1], FBC2[1]) > MAX2(LFBC[1], RFBC[1])) || + (MIN3(FBC0[3], FBC1[3], FBC2[3]) > MAX2(LFBC[3], RFBC[3]))) { + return false; + } + + /* If the the line is one of the edge in the triangle, then it's not occludded. */ + if (lineart_edge_from_triangle(rt, rl, allow_overlapping_edges)) { + return false; + } + + /* Check if the line visually crosses one of the edge in the triangle. */ + a = lineart_LineIntersectTest2d(LFBC, RFBC, FBC0, FBC1, &is[0]); + b = lineart_LineIntersectTest2d(LFBC, RFBC, FBC1, FBC2, &is[1]); + c = lineart_LineIntersectTest2d(LFBC, RFBC, FBC2, FBC0, &is[2]); + + /* Sort the intersection distance. */ + INTERSECT_SORT_MIN_TO_MAX_3(is[0], is[1], is[2], order); + + sub_v3_v3v3_db(Lv, rl->l->gloc, rt->v[0]->gloc); + sub_v3_v3v3_db(Rv, rl->r->gloc, rt->v[0]->gloc); + + copy_v3_v3_db(Cv, camera_dir); + + if (override_cam_is_persp) { + copy_v3_v3_db(vd4, override_camera_loc); + } + else { + copy_v4_v4_db(vd4, override_camera_loc); + } + if (override_cam_is_persp) { + sub_v3_v3v3_db(Cv, vd4, rt->v[0]->gloc); + } + + dot_l = dot_v3v3_db(Lv, rt->gn); + dot_r = dot_v3v3_db(Rv, rt->gn); + dot_f = dot_v3v3_db(Cv, rt->gn); + + if (!dot_f) { + return false; + } + + if (!a && !b && !c) { + if (!(st_l = lineart_point_triangle_relation(LFBC, FBC0, FBC1, FBC2)) && + !(st_r = lineart_point_triangle_relation(RFBC, FBC0, FBC1, FBC2))) { + return 0; /* Intersection point is not inside triangle. */ + } + } + + st_l = lineart_point_triangle_relation(LFBC, FBC0, FBC1, FBC2); + st_r = lineart_point_triangle_relation(RFBC, FBC0, FBC1, FBC2); + + /* Determine the cut position. */ + + dot_la = fabs(dot_l); + if (dot_la < DBL_EPSILON) { + dot_la = 0; + dot_l = 0; + } + dot_ra = fabs(dot_r); + if (dot_ra < DBL_EPSILON) { + dot_ra = 0; + dot_r = 0; + } + if (dot_l - dot_r == 0) { + cut = 100000; + } + else if (dot_l * dot_r <= 0) { + cut = dot_la / fabs(dot_l - dot_r); + } + else { + cut = fabs(dot_r + dot_l) / fabs(dot_l - dot_r); + cut = dot_ra > dot_la ? 1 - cut : cut; + } + + /* Transform the cut from geometry space to image space. */ + if (override_cam_is_persp) { + interp_v3_v3v3_db(gloc, rl->l->gloc, rl->r->gloc, cut); + mul_v4_m4v3_db(trans, vp, gloc); + mul_v3db_db(trans, (1 / trans[3])); + } + else { + interp_v3_v3v3_db(trans, rl->l->fbcoord, rl->r->fbcoord, cut); + } + trans[0] -= cam_shift_x * 2; + trans[1] -= cam_shift_y * 2; + + /* To accomodate k=0 and k=inf (vertical) lines. here the cut is in image space. */ + if (fabs(rl->l->fbcoord[0] - rl->r->fbcoord[0]) > fabs(rl->l->fbcoord[1] - rl->r->fbcoord[1])) { + cut = ratiod(rl->l->fbcoord[0], rl->r->fbcoord[0], trans[0]); + } + else { + cut = ratiod(rl->l->fbcoord[1], rl->r->fbcoord[1], trans[1]); + } + + /* Determine the pair of edges that the line has crossed. */ + + if (st_l == 2) { + if (st_r == 2) { + INTERSECT_JUST_SMALLER(is, order, DBL_TRIANGLE_LIM, LCross); + INTERSECT_JUST_GREATER(is, order, 1 - DBL_TRIANGLE_LIM, RCross); + } + else if (st_r == 1) { + INTERSECT_JUST_SMALLER(is, order, DBL_TRIANGLE_LIM, LCross); + INTERSECT_JUST_GREATER(is, order, 1 - DBL_TRIANGLE_LIM, RCross); + } + else if (st_r == 0) { + INTERSECT_JUST_SMALLER(is, order, DBL_TRIANGLE_LIM, LCross); + INTERSECT_JUST_GREATER(is, order, 0, RCross); + } + } + else if (st_l == 1) { + if (st_r == 2) { + INTERSECT_JUST_SMALLER(is, order, DBL_TRIANGLE_LIM, LCross); + INTERSECT_JUST_GREATER(is, order, 1 - DBL_TRIANGLE_LIM, RCross); + } + else if (st_r == 1) { + INTERSECT_JUST_SMALLER(is, order, DBL_TRIANGLE_LIM, LCross); + INTERSECT_JUST_GREATER(is, order, 1 - DBL_TRIANGLE_LIM, RCross); + } + else if (st_r == 0) { + INTERSECT_JUST_GREATER(is, order, DBL_TRIANGLE_LIM, RCross); + if (LRT_ABC(RCross) && is[RCross] > (DBL_TRIANGLE_LIM)) { + INTERSECT_JUST_SMALLER(is, order, DBL_TRIANGLE_LIM, LCross); + } + else { + INTERSECT_JUST_SMALLER(is, order, -DBL_TRIANGLE_LIM, LCross); + INTERSECT_JUST_GREATER(is, order, -DBL_TRIANGLE_LIM, RCross); + } + } + } + else if (st_l == 0) { + if (st_r == 2) { + INTERSECT_JUST_SMALLER(is, order, 1 - DBL_TRIANGLE_LIM, LCross); + INTERSECT_JUST_GREATER(is, order, 1 - DBL_TRIANGLE_LIM, RCross); + } + else if (st_r == 1) { + INTERSECT_JUST_SMALLER(is, order, 1 - DBL_TRIANGLE_LIM, LCross); + if (LRT_ABC(LCross) && is[LCross] < (1 - DBL_TRIANGLE_LIM)) { + INTERSECT_JUST_GREATER(is, order, 1 - DBL_TRIANGLE_LIM, RCross); + } + else { + INTERSECT_JUST_SMALLER(is, order, 1 + DBL_TRIANGLE_LIM, LCross); + INTERSECT_JUST_GREATER(is, order, 1 + DBL_TRIANGLE_LIM, RCross); + } + } + else if (st_r == 0) { + INTERSECT_JUST_GREATER(is, order, 0, LCross); + if (LRT_ABC(LCross) && is[LCross] > 0) { + INTERSECT_JUST_GREATER(is, order, is[LCross], RCross); + } + else { + INTERSECT_JUST_GREATER(is, order, is[LCross], LCross); + INTERSECT_JUST_GREATER(is, order, is[LCross], RCross); + } + } + } + + double LF = dot_l * dot_f, RF = dot_r * dot_f; + + /* Determine the start and end point of image space cut on a line. */ + if (LF <= 0 && RF <= 0 && (dot_l || dot_r)) { + *from = MAX2(0, is[LCross]); + *to = MIN2(1, is[RCross]); + if (*from >= *to) { + return false; + } + return true; + } + if (LF >= 0 && RF <= 0 && (dot_l || dot_r)) { + *from = MAX2(cut, is[LCross]); + *to = MIN2(1, is[RCross]); + if (*from >= *to) { + return false; + } + return true; + } + if (LF <= 0 && RF >= 0 && (dot_l || dot_r)) { + *from = MAX2(0, is[LCross]); + *to = MIN2(cut, is[RCross]); + if (*from >= *to) { + return false; + } + return true; + } + + /* Unlikely, but here's the default failed value if anything fall through. */ + return false; +} + +#undef INTERSECT_SORT_MIN_TO_MAX_3 +#undef INTERSECT_JUST_GREATER +#undef INTERSECT_JUST_SMALLER + +/* At this stage of the computation we don't have triangle adjacent info anymore, so we can only + * compare the global vert index. */ +static bool lineart_triangle_share_edge(const 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; +} + +/* To save time and prevent overlapping lines when computing intersection lines. */ +static bool lineart_vert_already_intersected_2v(LineartVertIntersection *rv, + LineartVertIntersection *v1, + LineartVertIntersection *v2) +{ + return ((rv->isec1 == v1->base.index && rv->isec2 == v2->base.index) || + (rv->isec2 == v2->base.index && rv->isec1 == v1->base.index)); +} + +static void lineart_vert_set_intersection_2v(LineartVert *rv, LineartVert *v1, LineartVert *v2) +{ + LineartVertIntersection *irv = (LineartVertIntersection *)rv; + irv->isec1 = v1->index; + irv->isec2 = v2->index; +} + +/* This tests a triangle against a virtual line represented by v1---v2. The vertices returned after + * repeated calls to this function is then used to create a triangle/triangle intersection line. */ +static LineartVert *lineart_triangle_2v_intersection_test(LineartRenderBuffer *rb, + LineartVert *v1, + LineartVert *v2, + LineartTriangle *rt, + LineartTriangle *testing, + LineartVert *last) +{ + double Lv[3]; + double Rv[3]; + double dot_l, dot_r; + LineartVert *result; + double gloc[3]; + LineartVert *l = v1, *r = v2; + + for (LinkNode *ln = (void *)testing->intersecting_verts; ln; ln = ln->next) { + LineartVertIntersection *rv = ln->link; + if (rv->intersecting_with == rt && + lineart_vert_already_intersected_2v( + rv, (LineartVertIntersection *)l, (LineartVertIntersection *)r)) { + return (LineartVert *)rv; + } + } + + sub_v3_v3v3_db(Lv, l->gloc, testing->v[0]->gloc); + sub_v3_v3v3_db(Rv, r->gloc, testing->v[0]->gloc); + + dot_l = dot_v3v3_db(Lv, testing->gn); + dot_r = dot_v3v3_db(Rv, testing->gn); + + if (dot_l * dot_r > 0 || (!dot_l && !dot_r)) { + return 0; + } + + dot_l = fabs(dot_l); + dot_r = fabs(dot_r); + + interp_v3_v3v3_db(gloc, l->gloc, r->gloc, dot_l / (dot_l + dot_r)); + + /* Due to precision issue, we might end up with the same point as the one we already detected. */ + if (last && LRT_DOUBLE_CLOSE_ENOUGH(last->gloc[0], gloc[0]) && + LRT_DOUBLE_CLOSE_ENOUGH(last->gloc[1], gloc[1]) && + LRT_DOUBLE_CLOSE_ENOUGH(last->gloc[2], gloc[2])) { + return NULL; + } + + if (!(lineart_point_inside_triangle3d( + gloc, testing->v[0]->gloc, testing->v[1]->gloc, testing->v[2]->gloc))) { + return NULL; + } + + /* This is an intersection vert, the size is bigger than LineartVert, + * allocated separately. */ + result = lineart_mem_aquire(&rb->render_data_pool, sizeof(LineartVertIntersection)); + + /* Indicate the data structure difference. */ + result->flag = LRT_VERT_HAS_INTERSECTION_DATA; + + copy_v3_v3_db(result->gloc, gloc); + + lineart_prepend_pool(&testing->intersecting_verts, &rb->render_data_pool, result); + + return result; +} + +/* Test if two triangles intersect. Generates one intersection line if the check succeeds */ +static LineartLine *lineart_triangle_intersect(LineartRenderBuffer *rb, + LineartTriangle *rt, + LineartTriangle *testing) +{ + LineartVert *l = 0, *r = 0; + LineartVert **next = &l; + LineartLine *result; + LineartVert *E0T = 0; + LineartVert *E1T = 0; + LineartVert *E2T = 0; + LineartVert *TE0 = 0; + LineartVert *TE1 = 0; + LineartVert *TE2 = 0; + LineartVert *sv1, *sv2; + double cl[3]; + + double ZMin, ZMax; + ZMax = rb->far_clip; + ZMin = rb->near_clip; + copy_v3_v3_db(cl, rb->camera_pos); + LineartVert *share = lineart_triangle_share_point(testing, rt); + + if (share) { + /* If triangles have sharing points like (abc) and (acd), then we only need to detect bc + * against acd or cd against abc.*/ + + LineartVert *new_share; + lineart_triangle_get_other_verts(rt, share, &sv1, &sv2); + + l = new_share = lineart_mem_aquire(&rb->render_data_pool, (sizeof(LineartVertIntersection))); + + new_share->flag = LRT_VERT_HAS_INTERSECTION_DATA; + + copy_v3_v3_db(new_share->gloc, share->gloc); + + r = lineart_triangle_2v_intersection_test(rb, sv1, sv2, rt, testing, 0); + + if (r == NULL) { + lineart_triangle_get_other_verts(testing, share, &sv1, &sv2); + r = lineart_triangle_2v_intersection_test(rb, sv1, sv2, testing, rt, 0); + if (r == NULL) { + return 0; + } + lineart_prepend_pool(&testing->intersecting_verts, &rb->render_data_pool, new_share); + } + else { + lineart_prepend_pool(&rt->intersecting_verts, &rb->render_data_pool, new_share); + } + } + else { + /* If not sharing any points, then we need to try all the possibilities. */ + + E0T = lineart_triangle_2v_intersection_test(rb, rt->v[0], rt->v[1], rt, testing, 0); + if (E0T && (!(*next))) { + (*next) = E0T; + lineart_vert_set_intersection_2v((*next), rt->v[0], rt->v[1]); + next = &r; + } + E1T = lineart_triangle_2v_intersection_test(rb, rt->v[1], rt->v[2], rt, testing, l); + if (E1T && (!(*next))) { + (*next) = E1T; + lineart_vert_set_intersection_2v((*next), rt->v[1], rt->v[2]); + next = &r; + } + if (!(*next)) { + E2T = lineart_triangle_2v_intersection_test(rb, rt->v[2], rt->v[0], rt, testing, l); + } + if (E2T && (!(*next))) { + (*next) = E2T; + lineart_vert_set_intersection_2v((*next), rt->v[2], rt->v[0]); + next = &r; + } + + if (!(*next)) { + TE0 = lineart_triangle_2v_intersection_test( + rb, testing->v[0], testing->v[1], testing, rt, l); + } + if (TE0 && (!(*next))) { + (*next) = TE0; + lineart_vert_set_intersection_2v((*next), testing->v[0], testing->v[1]); + next = &r; + } + if (!(*next)) { + TE1 = lineart_triangle_2v_intersection_test( + rb, testing->v[1], testing->v[2], testing, rt, l); + } + if (TE1 && (!(*next))) { + (*next) = TE1; + lineart_vert_set_intersection_2v((*next), testing->v[1], testing->v[2]); + next = &r; + } + if (!(*next)) { + TE2 = lineart_triangle_2v_intersection_test( + rb, testing->v[2], testing->v[0], testing, rt, l); + } + if (TE2 && (!(*next))) { + (*next) = TE2; + lineart_vert_set_intersection_2v((*next), testing->v[2], testing->v[0]); + next = &r; + } + + if (!(*next)) { + return 0; + } + } + + /* The intersection line has been generated only in geometry space, so we need to transform them + * as well. */ + mul_v4_m4v3_db(l->fbcoord, rb->view_projection, l->gloc); + mul_v4_m4v3_db(r->fbcoord, rb->view_projection, r->gloc); + mul_v3db_db(l->fbcoord, (1 / l->fbcoord[3])); + mul_v3db_db(r->fbcoord, (1 / r->fbcoord[3])); + + l->fbcoord[0] -= rb->shift_x * 2; + l->fbcoord[1] -= rb->shift_y * 2; + r->fbcoord[0] -= rb->shift_x * 2; + r->fbcoord[1] -= rb->shift_y * 2; + + /* This z transformation is not the same as the rest of the part, because the data don't go + * through normal perspective division calls in the pipeline, but this way the 3D result and + * occlution on the generated line is correct, and we don't really use 2D for viewport stroke + * generation anyway.*/ + l->fbcoord[2] = ZMin * ZMax / (ZMax - fabs(l->fbcoord[2]) * (ZMax - ZMin)); + r->fbcoord[2] = ZMin * ZMax / (ZMax - fabs(r->fbcoord[2]) * (ZMax - ZMin)); + + ((LineartVertIntersection *)l)->intersecting_with = rt; + ((LineartVertIntersection *)r)->intersecting_with = testing; + + result = lineart_mem_aquire(&rb->render_data_pool, sizeof(LineartLine)); + result->l = l; + result->r = r; + result->tl = rt; + result->tr = testing; + + LineartLineSegment *rls = lineart_mem_aquire(&rb->render_data_pool, sizeof(LineartLineSegment)); + BLI_addtail(&result->segments, rls); + /* Don't need to OR flags right now, just a type mark. */ + result->flags = LRT_EDGE_FLAG_INTERSECTION; + lineart_prepend_line_direct(&rb->intersection_lines, result); + int r1, r2, c1, c2, row, col; + if (lineart_get_line_bounding_areas(rb, result, &r1, &r2, &c1, &c2)) { + for (row = r1; row != r2 + 1; row++) { + for (col = c1; col != c2 + 1; col++) { + lineart_bounding_area_link_line( + rb, &rb->initial_bounding_areas[row * LRT_BA_ROWS + col], result); + } + } + } + + rb->intersection_count++; + + return result; +} + +static void lineart_triangle_intersect_in_bounding_area(LineartRenderBuffer *rb, + LineartTriangle *rt, + LineartBoundingArea *ba) +{ + /* Testing_triangle->testing[0] is used to store pairing triangle reference. + * See definition of LineartTriangleThread for more info. */ + LineartTriangle *testing_triangle; + LineartTriangleThread *rtt; + LinkData *lip, *next_lip; + + double *G0 = rt->v[0]->gloc, *G1 = rt->v[1]->gloc, *G2 = rt->v[2]->gloc; + + /* If this is not the smallest subdiv bounding area.*/ + if (ba->child) { + lineart_triangle_intersect_in_bounding_area(rb, rt, &ba->child[0]); + lineart_triangle_intersect_in_bounding_area(rb, rt, &ba->child[1]); + lineart_triangle_intersect_in_bounding_area(rb, rt, &ba->child[2]); + lineart_triangle_intersect_in_bounding_area(rb, rt, &ba->child[3]); + return; + } + + /* If this _is_ the smallest subdiv bounding area, then do the intersections there. */ + for (lip = ba->linked_triangles.first; lip; lip = next_lip) { + next_lip = lip->next; + testing_triangle = lip->data; + rtt = (LineartTriangleThread *)testing_triangle; + + if (testing_triangle == rt || rtt->testing[0] == (LineartLine *)rt || + (testing_triangle->flags & LRT_TRIANGLE_NO_INTERSECTION) || + ((testing_triangle->flags & LRT_TRIANGLE_INTERSECTION_ONLY) && + (rt->flags & LRT_TRIANGLE_INTERSECTION_ONLY)) || + lineart_triangle_share_edge(rt, testing_triangle)) { + continue; + } + + rtt->testing[0] = (LineartLine *)rt; + double *RG0 = testing_triangle->v[0]->gloc, *RG1 = testing_triangle->v[1]->gloc, + *RG2 = testing_triangle->v[2]->gloc; + + /* Bounding box not overlapping, not potential of intersecting. */ + if ((MIN3(G0[2], G1[2], G2[2]) > MAX3(RG0[2], RG1[2], RG2[2])) || + (MAX3(G0[2], G1[2], G2[2]) < MIN3(RG0[2], RG1[2], RG2[2])) || + (MIN3(G0[0], G1[0], G2[0]) > MAX3(RG0[0], RG1[0], RG2[0])) || + (MAX3(G0[0], G1[0], G2[0]) < MIN3(RG0[0], RG1[0], RG2[0])) || + (MIN3(G0[1], G1[1], G2[1]) > MAX3(RG0[1], RG1[1], RG2[1])) || + (MAX3(G0[1], G1[1], G2[1]) < MIN3(RG0[1], RG1[1], RG2[1]))) { + continue; + } + + /* If we do need to compute intersection, then finally do it. */ + lineart_triangle_intersect(rb, rt, testing_triangle); + } +} + +/* The calculated view vector will point towards the far-plane from the camera position. */ +static void lineart_main_get_view_vector(LineartRenderBuffer *rb) +{ + float direction[3] = {0, 0, 1}; + float trans[3]; + float inv[4][4]; + float obmat_no_scale[4][4]; + + copy_m4_m4(obmat_no_scale, rb->cam_obmat); + + normalize_v3(obmat_no_scale[0]); + normalize_v3(obmat_no_scale[1]); + normalize_v3(obmat_no_scale[2]); + invert_m4_m4(inv, obmat_no_scale); + transpose_m4(inv); + mul_v3_mat3_m4v3(trans, inv, direction); + copy_m4_m4(rb->cam_obmat, obmat_no_scale); + copy_v3db_v3fl(rb->view_vector, trans); +} + +static void lineart_destroy_render_data(LineartRenderBuffer *rb) +{ + if (rb == NULL) { + return; + } + + rb->contour_count = 0; + rb->contour_managed = NULL; + rb->intersection_count = 0; + rb->intersection_managed = NULL; + rb->material_line_count = 0; + rb->material_managed = NULL; + rb->crease_count = 0; + rb->crease_managed = NULL; + rb->edge_mark_count = 0; + rb->edge_mark_managed = NULL; + + rb->contours = NULL; + rb->intersection_lines = NULL; + rb->crease_lines = NULL; + rb->material_lines = NULL; + rb->edge_marks = NULL; + + BLI_listbase_clear(&rb->chains); + BLI_listbase_clear(&rb->wasted_cuts); + + BLI_listbase_clear(&rb->vertex_buffer_pointers); + BLI_listbase_clear(&rb->line_buffer_pointers); + BLI_listbase_clear(&rb->triangle_buffer_pointers); + + BLI_spin_end(&rb->lock_task); + BLI_spin_end(&rb->lock_cuts); + BLI_spin_end(&rb->render_data_pool.lock_mem); + + lineart_mem_destroy(&rb->render_data_pool); +} + +void MOD_lineart_destroy_render_data(LineartGpencilModifierData *lmd) +{ + LineartRenderBuffer *rb = lmd->render_buffer; + + lineart_destroy_render_data(rb); + + if (rb) { + MEM_freeN(rb); + lmd->render_buffer = NULL; + } + + if (G.debug_value == 4000) { + printf("LRT: Destroyed render data.\n"); + } +} + +static LineartRenderBuffer *lineart_create_render_buffer(Scene *scene, + LineartGpencilModifierData *lmd) +{ + LineartRenderBuffer *rb = MEM_callocN(sizeof(LineartRenderBuffer), "Line Art render buffer"); + + lmd->render_buffer = rb; + + if (!scene || !scene->camera) { + return NULL; + } + Camera *c = scene->camera->data; + double clipping_offset = 0; + + if (lmd->calculation_flags & LRT_ALLOW_CLIPPING_BOUNDARIES) { + /* This way the clipped lines are "stablely visible" by prevents depth buffer artefacts. */ + clipping_offset = 0.0001; + } + + copy_v3db_v3fl(rb->camera_pos, scene->camera->obmat[3]); + copy_m4_m4(rb->cam_obmat, scene->camera->obmat); + rb->cam_is_persp = (c->type == CAM_PERSP); + rb->near_clip = c->clip_start + clipping_offset; + rb->far_clip = c->clip_end - clipping_offset; + rb->w = scene->r.xsch; + rb->h = scene->r.ysch; + + double asp = ((double)rb->w / (double)rb->h); + rb->shift_x = (asp >= 1) ? c->shiftx : c->shiftx * asp; + rb->shift_y = (asp <= 1) ? c->shifty : c->shifty * asp; + + rb->crease_threshold = cos(M_PI - lmd->crease_threshold); + rb->angle_splitting_threshold = lmd->angle_splitting_threshold; + rb->chaining_image_threshold = lmd->chaining_image_threshold; + rb->chaining_geometry_threshold = lmd->chaining_geometry_threshold; + + rb->fuzzy_intersections = (lmd->calculation_flags & LRT_INTERSECTION_AS_CONTOUR) != 0; + rb->fuzzy_everything = (lmd->calculation_flags & LRT_EVERYTHING_AS_CONTOUR) != 0; + rb->allow_boundaries = (lmd->calculation_flags & LRT_ALLOW_CLIPPING_BOUNDARIES) != 0; + rb->remove_doubles = (lmd->calculation_flags & LRT_REMOVE_DOUBLES) != 0; + + /* See lineart_edge_from_triangle() for how this option may impact performance. */ + rb->allow_overlapping_edges = (lmd->calculation_flags & LRT_ALLOW_OVERLAPPING_EDGES) != 0; + + rb->use_contour = (lmd->line_types & LRT_EDGE_FLAG_CONTOUR) != 0; + rb->use_crease = (lmd->line_types & LRT_EDGE_FLAG_CREASE) != 0; + rb->use_material = (lmd->line_types & LRT_EDGE_FLAG_MATERIAL) != 0; + rb->use_edge_marks = (lmd->line_types & LRT_EDGE_FLAG_EDGE_MARK) != 0; + rb->use_intersections = (lmd->line_types & LRT_EDGE_FLAG_INTERSECTION) != 0; + + BLI_spin_init(&rb->lock_task); + BLI_spin_init(&rb->lock_cuts); + BLI_spin_init(&rb->render_data_pool.lock_mem); + + return rb; +} + +static int lineart_triangle_size_get(const Scene *scene, LineartRenderBuffer *rb) +{ + if (rb->thread_count == 0) { + rb->thread_count = BKE_render_num_threads(&scene->r); + } + return sizeof(LineartTriangle) + (sizeof(LineartLine *) * (rb->thread_count)); +} + +static void lineart_main_bounding_area_make_initial(LineartRenderBuffer *rb) +{ + /* Initial tile split is defined as 4 (subdivided as 4*4), increasing the value allows the + * algorithm to build the acceleration structure for bigger scenes a little faster but not as + * efficient at handling medium to small scenes. */ + int sp_w = LRT_BA_ROWS; + int sp_h = LRT_BA_ROWS; + int row, col; + LineartBoundingArea *ba; + + /* Because NDC (Normalized Device Coordinates) range is (-1,1), + * so the span for each initial tile is double of that in the (0,1) range. */ + double span_w = (double)1 / sp_w * 2.0; + double span_h = (double)1 / sp_h * 2.0; + + rb->tile_count_x = sp_w; + rb->tile_count_y = sp_h; + rb->width_per_tile = span_w; + rb->height_per_tile = span_h; + + rb->bounding_area_count = sp_w * sp_h; + rb->initial_bounding_areas = lineart_mem_aquire( + &rb->render_data_pool, sizeof(LineartBoundingArea) * rb->bounding_area_count); + + /* Initialize tiles. */ + for (row = 0; row < sp_h; row++) { + for (col = 0; col < sp_w; col++) { + ba = &rb->initial_bounding_areas[row * LRT_BA_ROWS + col]; + + /* Set the four direction limits. */ + ba->l = span_w * col - 1.0; + ba->r = (col == sp_w - 1) ? 1.0 : (span_w * (col + 1) - 1.0); + ba->u = 1.0 - span_h * row; + ba->b = (row == sp_h - 1) ? -1.0 : (1.0 - span_h * (row + 1)); + + ba->cx = (ba->l + ba->r) / 2; + ba->cy = (ba->u + ba->b) / 2; + + /* Link adjacent ones. */ + if (row) { + lineart_list_append_pointer_pool( + &ba->up, + &rb->render_data_pool, + &rb->initial_bounding_areas[(row - 1) * LRT_BA_ROWS + col]); + } + if (col) { + lineart_list_append_pointer_pool(&ba->lp, + &rb->render_data_pool, + &rb->initial_bounding_areas[row * LRT_BA_ROWS + col - 1]); + } + if (row != sp_h - 1) { + lineart_list_append_pointer_pool( + &ba->bp, + &rb->render_data_pool, + &rb->initial_bounding_areas[(row + 1) * LRT_BA_ROWS + col]); + } + if (col != sp_w - 1) { + lineart_list_append_pointer_pool(&ba->rp, + &rb->render_data_pool, + &rb->initial_bounding_areas[row * LRT_BA_ROWS + col + 1]); + } + } + } +} + +/* Re-link adjacent tiles after one gets subdivided. */ +static void lineart_bounding_areas_connect_new(LineartRenderBuffer *rb, LineartBoundingArea *root) +{ + LineartBoundingArea *ba = root->child, *tba; + LinkData *lip2, *next_lip; + LineartStaticMemPool *mph = &rb->render_data_pool; + + /* Inter-connection with newly created 4 child bounding areas. */ + lineart_list_append_pointer_pool(&ba[1].rp, mph, &ba[0]); + lineart_list_append_pointer_pool(&ba[0].lp, mph, &ba[1]); + lineart_list_append_pointer_pool(&ba[1].bp, mph, &ba[2]); + lineart_list_append_pointer_pool(&ba[2].up, mph, &ba[1]); + lineart_list_append_pointer_pool(&ba[2].rp, mph, &ba[3]); + lineart_list_append_pointer_pool(&ba[3].lp, mph, &ba[2]); + lineart_list_append_pointer_pool(&ba[3].up, mph, &ba[0]); + lineart_list_append_pointer_pool(&ba[0].bp, mph, &ba[3]); + + /* Connect 4 child bounding areas to other areas that are + * adjacent to their original parents. */ + LISTBASE_FOREACH (LinkData *, lip, &root->lp) { + + /* For example, we are dealing with parent's left side + * "tba" represents each adjacent neighbor of the parent. */ + tba = lip->data; + + /* if this neighbor is adjacent to + * the two new areas on the left side of the parent, + * then add them to the adjacent list as well. */ + if (ba[1].u > tba->b && ba[1].b < tba->u) { + lineart_list_append_pointer_pool(&ba[1].lp, mph, tba); + lineart_list_append_pointer_pool(&tba->rp, mph, &ba[1]); + } + if (ba[2].u > tba->b && ba[2].b < tba->u) { + lineart_list_append_pointer_pool(&ba[2].lp, mph, tba); + lineart_list_append_pointer_pool(&tba->rp, mph, &ba[2]); + } + } + LISTBASE_FOREACH (LinkData *, lip, &root->rp) { + tba = lip->data; + if (ba[0].u > tba->b && ba[0].b < tba->u) { + lineart_list_append_pointer_pool(&ba[0].rp, mph, tba); + lineart_list_append_pointer_pool(&tba->lp, mph, &ba[0]); + } + if (ba[3].u > tba->b && ba[3].b < tba->u) { + lineart_list_append_pointer_pool(&ba[3].rp, mph, tba); + lineart_list_append_pointer_pool(&tba->lp, mph, &ba[3]); + } + } + LISTBASE_FOREACH (LinkData *, lip, &root->up) { + tba = lip->data; + if (ba[0].r > tba->l && ba[0].l < tba->r) { + lineart_list_append_pointer_pool(&ba[0].up, mph, tba); + lineart_list_append_pointer_pool(&tba->bp, mph, &ba[0]); + } + if (ba[1].r > tba->l && ba[1].l < tba->r) { + lineart_list_append_pointer_pool(&ba[1].up, mph, tba); + lineart_list_append_pointer_pool(&tba->bp, mph, &ba[1]); + } + } + LISTBASE_FOREACH (LinkData *, lip, &root->bp) { + tba = lip->data; + if (ba[2].r > tba->l && ba[2].l < tba->r) { + lineart_list_append_pointer_pool(&ba[2].bp, mph, tba); + lineart_list_append_pointer_pool(&tba->up, mph, &ba[2]); + } + if (ba[3].r > tba->l && ba[3].l < tba->r) { + lineart_list_append_pointer_pool(&ba[3].bp, mph, tba); + lineart_list_append_pointer_pool(&tba->up, mph, &ba[3]); + } + } + + /* Then remove the parent bounding areas from + * their original adjacent areas. */ + LISTBASE_FOREACH (LinkData *, lip, &root->lp) { + for (lip2 = ((LineartBoundingArea *)lip->data)->rp.first; lip2; lip2 = next_lip) { + next_lip = lip2->next; + tba = lip2->data; + if (tba == root) { + lineart_list_remove_pointer_item_no_free(&((LineartBoundingArea *)lip->data)->rp, lip2); + if (ba[1].u > tba->b && ba[1].b < tba->u) { + lineart_list_append_pointer_pool(&tba->rp, mph, &ba[1]); + } + if (ba[2].u > tba->b && ba[2].b < tba->u) { + lineart_list_append_pointer_pool(&tba->rp, mph, &ba[2]); + } + } + } + } + LISTBASE_FOREACH (LinkData *, lip, &root->rp) { + for (lip2 = ((LineartBoundingArea *)lip->data)->lp.first; lip2; lip2 = next_lip) { + next_lip = lip2->next; + tba = lip2->data; + if (tba == root) { + lineart_list_remove_pointer_item_no_free(&((LineartBoundingArea *)lip->data)->lp, lip2); + if (ba[0].u > tba->b && ba[0].b < tba->u) { + lineart_list_append_pointer_pool(&tba->lp, mph, &ba[0]); + } + if (ba[3].u > tba->b && ba[3].b < tba->u) { + lineart_list_append_pointer_pool(&tba->lp, mph, &ba[3]); + } + } + } + } + LISTBASE_FOREACH (LinkData *, lip, &root->up) { + for (lip2 = ((LineartBoundingArea *)lip->data)->bp.first; lip2; lip2 = next_lip) { + next_lip = lip2->next; + tba = lip2->data; + if (tba == root) { + lineart_list_remove_pointer_item_no_free(&((LineartBoundingArea *)lip->data)->bp, lip2); + if (ba[0].r > tba->l && ba[0].l < tba->r) { + lineart_list_append_pointer_pool(&tba->up, mph, &ba[0]); + } + if (ba[1].r > tba->l && ba[1].l < tba->r) { + lineart_list_append_pointer_pool(&tba->up, mph, &ba[1]); + } + } + } + } + LISTBASE_FOREACH (LinkData *, lip, &root->bp) { + for (lip2 = ((LineartBoundingArea *)lip->data)->up.first; lip2; lip2 = next_lip) { + next_lip = lip2->next; + tba = lip2->data; + if (tba == root) { + lineart_list_remove_pointer_item_no_free(&((LineartBoundingArea *)lip->data)->up, lip2); + if (ba[2].r > tba->l && ba[2].l < tba->r) { + lineart_list_append_pointer_pool(&tba->bp, mph, &ba[2]); + } + if (ba[3].r > tba->l && ba[3].l < tba->r) { + lineart_list_append_pointer_pool(&tba->bp, mph, &ba[3]); + } + } + } + } + + /* Finally clear parent'scene adjacent list. */ + BLI_listbase_clear(&root->lp); + BLI_listbase_clear(&root->rp); + BLI_listbase_clear(&root->up); + BLI_listbase_clear(&root->bp); +} + +/* Subdivide a tile after one tile contains too many triangles. */ +static void lineart_bounding_area_split(LineartRenderBuffer *rb, + LineartBoundingArea *root, + int recursive_level) +{ + LineartBoundingArea *ba = lineart_mem_aquire(&rb->render_data_pool, + sizeof(LineartBoundingArea) * 4); + LineartTriangle *rt; + LineartLine *rl; + + ba[0].l = root->cx; + ba[0].r = root->r; + ba[0].u = root->u; + ba[0].b = root->cy; + ba[0].cx = (ba[0].l + ba[0].r) / 2; + ba[0].cy = (ba[0].u + ba[0].b) / 2; + + ba[1].l = root->l; + ba[1].r = root->cx; + ba[1].u = root->u; + ba[1].b = root->cy; + ba[1].cx = (ba[1].l + ba[1].r) / 2; + ba[1].cy = (ba[1].u + ba[1].b) / 2; + + ba[2].l = root->l; + ba[2].r = root->cx; + ba[2].u = root->cy; + ba[2].b = root->b; + ba[2].cx = (ba[2].l + ba[2].r) / 2; + ba[2].cy = (ba[2].u + ba[2].b) / 2; + + ba[3].l = root->cx; + ba[3].r = root->r; + ba[3].u = root->cy; + ba[3].b = root->b; + ba[3].cx = (ba[3].l + ba[3].r) / 2; + ba[3].cy = (ba[3].u + ba[3].b) / 2; + + root->child = ba; + + lineart_bounding_areas_connect_new(rb, root); + + while ((rt = lineart_list_pop_pointer_no_free(&root->linked_triangles)) != NULL) { + LineartBoundingArea *cba = root->child; + double b[4]; + b[0] = MIN3(rt->v[0]->fbcoord[0], rt->v[1]->fbcoord[0], rt->v[2]->fbcoord[0]); + b[1] = MAX3(rt->v[0]->fbcoord[0], rt->v[1]->fbcoord[0], rt->v[2]->fbcoord[0]); + b[2] = MAX3(rt->v[0]->fbcoord[1], rt->v[1]->fbcoord[1], rt->v[2]->fbcoord[1]); + b[3] = MIN3(rt->v[0]->fbcoord[1], rt->v[1]->fbcoord[1], rt->v[2]->fbcoord[1]); + if (LRT_BOUND_AREA_CROSSES(b, &cba[0].l)) { + lineart_bounding_area_link_triangle(rb, &cba[0], rt, b, 0, recursive_level + 1, false); + } + if (LRT_BOUND_AREA_CROSSES(b, &cba[1].l)) { + lineart_bounding_area_link_triangle(rb, &cba[1], rt, b, 0, recursive_level + 1, false); + } + if (LRT_BOUND_AREA_CROSSES(b, &cba[2].l)) { + lineart_bounding_area_link_triangle(rb, &cba[2], rt, b, 0, recursive_level + 1, false); + } + if (LRT_BOUND_AREA_CROSSES(b, &cba[3].l)) { + lineart_bounding_area_link_triangle(rb, &cba[3], rt, b, 0, recursive_level + 1, false); + } + } + + while ((rl = lineart_list_pop_pointer_no_free(&root->linked_lines)) != NULL) { + lineart_bounding_area_link_line(rb, root, rl); + } + + rb->bounding_area_count += 3; +} + +static bool lineart_bounding_area_line_intersect(LineartRenderBuffer *UNUSED(fb), + const double l[2], + const double r[2], + LineartBoundingArea *ba) +{ + double vx, vy; + double converted[4]; + double c1, c; + + if (((converted[0] = (double)ba->l) > MAX2(l[0], r[0])) || + ((converted[1] = (double)ba->r) < MIN2(l[0], r[0])) || + ((converted[2] = (double)ba->b) > MAX2(l[1], r[1])) || + ((converted[3] = (double)ba->u) < MIN2(l[1], r[1]))) { + return false; + } + + vx = l[0] - r[0]; + vy = l[1] - r[1]; + + c1 = vx * (converted[2] - l[1]) - vy * (converted[0] - l[0]); + c = c1; + + c1 = vx * (converted[2] - l[1]) - vy * (converted[1] - l[0]); + if (c1 * c <= 0) { + return true; + } + c = c1; + + c1 = vx * (converted[3] - l[1]) - vy * (converted[0] - l[0]); + if (c1 * c <= 0) { + return true; + } + c = c1; + + c1 = vx * (converted[3] - l[1]) - vy * (converted[1] - l[0]); + if (c1 * c <= 0) { + return true; + } + c = c1; + + return false; +} + +static bool lineart_bounding_area_triangle_intersect(LineartRenderBuffer *fb, + LineartTriangle *rt, + LineartBoundingArea *ba) +{ + double p1[2], p2[2], p3[2], p4[2]; + double *FBC1 = rt->v[0]->fbcoord, *FBC2 = rt->v[1]->fbcoord, *FBC3 = rt->v[2]->fbcoord; + + p3[0] = p1[0] = (double)ba->l; + p2[1] = p1[1] = (double)ba->b; + p2[0] = p4[0] = (double)ba->r; + p3[1] = p4[1] = (double)ba->u; + + if ((FBC1[0] >= p1[0] && FBC1[0] <= p2[0] && FBC1[1] >= p1[1] && FBC1[1] <= p3[1]) || + (FBC2[0] >= p1[0] && FBC2[0] <= p2[0] && FBC2[1] >= p1[1] && FBC2[1] <= p3[1]) || + (FBC3[0] >= p1[0] && FBC3[0] <= p2[0] && FBC3[1] >= p1[1] && FBC3[1] <= p3[1])) { + return true; + } + + if (lineart_point_inside_triangle(p1, FBC1, FBC2, FBC3) || + lineart_point_inside_triangle(p2, FBC1, FBC2, FBC3) || + lineart_point_inside_triangle(p3, FBC1, FBC2, FBC3) || + lineart_point_inside_triangle(p4, FBC1, FBC2, FBC3)) { + return true; + } + + if ((lineart_bounding_area_line_intersect(fb, FBC1, FBC2, ba)) || + (lineart_bounding_area_line_intersect(fb, FBC2, FBC3, ba)) || + (lineart_bounding_area_line_intersect(fb, FBC3, FBC1, ba))) { + return true; + } + + return false; +} + +/* 1) Link triangles with bounding areas for later occlusion test. + * 2) Test triangles with existing(added previously) triangles for intersection lines. */ +static void lineart_bounding_area_link_triangle(LineartRenderBuffer *rb, + LineartBoundingArea *root_ba, + LineartTriangle *rt, + double *LRUB, + int recursive, + int recursive_level, + bool do_intersection) +{ + if (!lineart_bounding_area_triangle_intersect(rb, rt, root_ba)) { + return; + } + if (root_ba->child == NULL) { + lineart_list_append_pointer_pool(&root_ba->linked_triangles, &rb->render_data_pool, rt); + root_ba->triangle_count++; + /* If splitting doesn't improve triangle separation, then shouldn't allow splitting anymore. + * Here we use recursive limit. This is espetially useful in ortho render, where a lot of + * faces could easily line up perfectly in image space, which can not be separated by simply + * slicing the image tile. */ + if (root_ba->triangle_count > 200 && recursive && recursive_level < 10) { + lineart_bounding_area_split(rb, root_ba, recursive_level); + } + if (recursive && do_intersection && rb->use_intersections) { + lineart_triangle_intersect_in_bounding_area(rb, rt, root_ba); + } + } + else { + LineartBoundingArea *ba = root_ba->child; + double *B1 = LRUB; + double b[4]; + if (!LRUB) { + b[0] = MIN3(rt->v[0]->fbcoord[0], rt->v[1]->fbcoord[0], rt->v[2]->fbcoord[0]); + b[1] = MAX3(rt->v[0]->fbcoord[0], rt->v[1]->fbcoord[0], rt->v[2]->fbcoord[0]); + b[2] = MAX3(rt->v[0]->fbcoord[1], rt->v[1]->fbcoord[1], rt->v[2]->fbcoord[1]); + b[3] = MIN3(rt->v[0]->fbcoord[1], rt->v[1]->fbcoord[1], rt->v[2]->fbcoord[1]); + B1 = b; + } + if (LRT_BOUND_AREA_CROSSES(B1, &ba[0].l)) { + lineart_bounding_area_link_triangle( + rb, &ba[0], rt, B1, recursive, recursive_level + 1, do_intersection); + } + if (LRT_BOUND_AREA_CROSSES(B1, &ba[1].l)) { + lineart_bounding_area_link_triangle( + rb, &ba[1], rt, B1, recursive, recursive_level + 1, do_intersection); + } + if (LRT_BOUND_AREA_CROSSES(B1, &ba[2].l)) { + lineart_bounding_area_link_triangle( + rb, &ba[2], rt, B1, recursive, recursive_level + 1, do_intersection); + } + if (LRT_BOUND_AREA_CROSSES(B1, &ba[3].l)) { + lineart_bounding_area_link_triangle( + rb, &ba[3], rt, B1, recursive, recursive_level + 1, do_intersection); + } + } +} + +static void lineart_bounding_area_link_line(LineartRenderBuffer *rb, + LineartBoundingArea *root_ba, + LineartLine *rl) +{ + if (root_ba->child == NULL) { + lineart_list_append_pointer_pool(&root_ba->linked_lines, &rb->render_data_pool, rl); + } + else { + if (lineart_bounding_area_line_intersect( + rb, rl->l->fbcoord, rl->r->fbcoord, &root_ba->child[0])) { + lineart_bounding_area_link_line(rb, &root_ba->child[0], rl); + } + if (lineart_bounding_area_line_intersect( + rb, rl->l->fbcoord, rl->r->fbcoord, &root_ba->child[1])) { + lineart_bounding_area_link_line(rb, &root_ba->child[1], rl); + } + if (lineart_bounding_area_line_intersect( + rb, rl->l->fbcoord, rl->r->fbcoord, &root_ba->child[2])) { + lineart_bounding_area_link_line(rb, &root_ba->child[2], rl); + } + if (lineart_bounding_area_line_intersect( + rb, rl->l->fbcoord, rl->r->fbcoord, &root_ba->child[3])) { + lineart_bounding_area_link_line(rb, &root_ba->child[3], rl); + } + } +} + +/* Link lines to their respective bounding areas. */ +static void lineart_main_link_lines(LineartRenderBuffer *rb) +{ + LRT_ITER_ALL_LINES_BEGIN + { + int r1, r2, c1, c2, row, col; + if (lineart_get_line_bounding_areas(rb, rl, &r1, &r2, &c1, &c2)) { + for (row = r1; row != r2 + 1; row++) { + for (col = c1; col != c2 + 1; col++) { + lineart_bounding_area_link_line( + rb, &rb->initial_bounding_areas[row * LRT_BA_ROWS + col], rl); + } + } + } + } + LRT_ITER_ALL_LINES_END +} + +static bool lineart_get_triangle_bounding_areas(LineartRenderBuffer *rb, + LineartTriangle *rt, + int *rowbegin, + int *rowend, + int *colbegin, + int *colend) +{ + double sp_w = rb->width_per_tile, sp_h = rb->height_per_tile; + double b[4]; + + if (!rt->v[0] || !rt->v[1] || !rt->v[2]) { + return false; + } + + b[0] = MIN3(rt->v[0]->fbcoord[0], rt->v[1]->fbcoord[0], rt->v[2]->fbcoord[0]); + b[1] = MAX3(rt->v[0]->fbcoord[0], rt->v[1]->fbcoord[0], rt->v[2]->fbcoord[0]); + b[2] = MIN3(rt->v[0]->fbcoord[1], rt->v[1]->fbcoord[1], rt->v[2]->fbcoord[1]); + b[3] = MAX3(rt->v[0]->fbcoord[1], rt->v[1]->fbcoord[1], rt->v[2]->fbcoord[1]); + + if (b[0] > 1 || b[1] < -1 || b[2] > 1 || b[3] < -1) { + return false; + } + + (*colbegin) = (int)((b[0] + 1.0) / sp_w); + (*colend) = (int)((b[1] + 1.0) / sp_w); + (*rowend) = rb->tile_count_y - (int)((b[2] + 1.0) / sp_h) - 1; + (*rowbegin) = rb->tile_count_y - (int)((b[3] + 1.0) / sp_h) - 1; + + if ((*colend) >= rb->tile_count_x) { + (*colend) = rb->tile_count_x - 1; + } + if ((*rowend) >= rb->tile_count_y) { + (*rowend) = rb->tile_count_y - 1; + } + if ((*colbegin) < 0) { + (*colbegin) = 0; + } + if ((*rowbegin) < 0) { + (*rowbegin) = 0; + } + + return true; +} + +static bool lineart_get_line_bounding_areas(LineartRenderBuffer *rb, + LineartLine *rl, + int *rowbegin, + int *rowend, + int *colbegin, + int *colend) +{ + double sp_w = rb->width_per_tile, sp_h = rb->height_per_tile; + double b[4]; + + if (!rl->l || !rl->r) { + return false; + } + + if (rl->l->fbcoord[0] != rl->l->fbcoord[0] || rl->r->fbcoord[0] != rl->r->fbcoord[0]) { + return false; + } + + b[0] = MIN2(rl->l->fbcoord[0], rl->r->fbcoord[0]); + b[1] = MAX2(rl->l->fbcoord[0], rl->r->fbcoord[0]); + b[2] = MIN2(rl->l->fbcoord[1], rl->r->fbcoord[1]); + b[3] = MAX2(rl->l->fbcoord[1], rl->r->fbcoord[1]); + + if (b[0] > 1 || b[1] < -1 || b[2] > 1 || b[3] < -1) { + return false; + } + + (*colbegin) = (int)((b[0] + 1.0) / sp_w); + (*colend) = (int)((b[1] + 1.0) / sp_w); + (*rowend) = rb->tile_count_y - (int)((b[2] + 1.0) / sp_h) - 1; + (*rowbegin) = rb->tile_count_y - (int)((b[3] + 1.0) / sp_h) - 1; + + /* It'scene possible that the line stretches too much out to the side, resulting negative value + . */ + if ((*rowend) < (*rowbegin)) { + (*rowend) = rb->tile_count_y - 1; + } + + if ((*colend) < (*colbegin)) { + (*colend) = rb->tile_count_x - 1; + } + + CLAMP((*colbegin), 0, rb->tile_count_x - 1); + CLAMP((*rowbegin), 0, rb->tile_count_y - 1); + CLAMP((*colend), 0, rb->tile_count_x - 1); + CLAMP((*rowend), 0, rb->tile_count_y - 1); + + return true; +} + +/* This only gets initial "biggest" tile. */ +LineartBoundingArea *MOD_lineart_get_parent_bounding_area(LineartRenderBuffer *rb, + double x, + double y) +{ + double sp_w = rb->width_per_tile, sp_h = rb->height_per_tile; + int col, row; + + if (x > 1 || x < -1 || y > 1 || y < -1) { + return 0; + } + + col = (int)((x + 1.0) / sp_w); + row = rb->tile_count_y - (int)((y + 1.0) / sp_h) - 1; + + if (col >= rb->tile_count_x) { + col = rb->tile_count_x - 1; + } + if (row >= rb->tile_count_y) { + row = rb->tile_count_y - 1; + } + if (col < 0) { + col = 0; + } + if (row < 0) { + row = 0; + } + + return &rb->initial_bounding_areas[row * LRT_BA_ROWS + col]; +} + +static LineartBoundingArea *lineart_get_bounding_area(LineartRenderBuffer *rb, double x, double y) +{ + LineartBoundingArea *iba; + double sp_w = rb->width_per_tile, sp_h = rb->height_per_tile; + int c = (int)((x + 1.0) / sp_w); + int r = rb->tile_count_y - (int)((y + 1.0) / sp_h) - 1; + if (r < 0) { + r = 0; + } + if (c < 0) { + c = 0; + } + if (r >= rb->tile_count_y) { + r = rb->tile_count_y - 1; + } + if (c >= rb->tile_count_x) { + c = rb->tile_count_x - 1; + } + + iba = &rb->initial_bounding_areas[r * LRT_BA_ROWS + c]; + while (iba->child) { + if (x > iba->cx) { + if (y > iba->cy) { + iba = &iba->child[0]; + } + else { + iba = &iba->child[3]; + } + } + else { + if (y > iba->cy) { + iba = &iba->child[1]; + } + else { + iba = &iba->child[2]; + } + } + } + return iba; +} + +/* Wrapper for more convenience. */ +LineartBoundingArea *MOD_lineart_get_bounding_area(LineartRenderBuffer *rb, double x, double y) +{ + LineartBoundingArea *ba; + if ((ba = MOD_lineart_get_parent_bounding_area(rb, x, y)) != NULL) { + return lineart_get_bounding_area(rb, x, y); + } + return NULL; +} + +/* Sequentially add triangles into render buffer. This also does intersection along the way. */ +static void lineart_main_add_triangles(LineartRenderBuffer *rb) +{ + LineartTriangle *rt; + int i, lim; + int x1, x2, y1, y2; + int r, co; + + LISTBASE_FOREACH (LineartElementLinkNode *, reln, &rb->triangle_buffer_pointers) { + rt = reln->pointer; + lim = reln->element_count; + for (i = 0; i < lim; i++) { + if ((rt->flags & LRT_CULL_USED) || (rt->flags & LRT_CULL_DISCARD)) { + rt = (void *)(((unsigned char *)rt) + rb->triangle_size); + continue; + } + if (lineart_get_triangle_bounding_areas(rb, rt, &y1, &y2, &x1, &x2)) { + for (co = x1; co <= x2; co++) { + for (r = y1; r <= y2; r++) { + lineart_bounding_area_link_triangle(rb, + &rb->initial_bounding_areas[r * LRT_BA_ROWS + co], + rt, + 0, + 1, + 0, + (!(rt->flags & LRT_TRIANGLE_NO_INTERSECTION))); + } + } + } /* Else throw away. */ + rt = (void *)(((unsigned char *)rt) + rb->triangle_size); + } + } +} + +/* This function gets the tile for the point rl->l, and later use lineart_bounding_area_next() to + * get next along the way. */ +static LineartBoundingArea *lineart_line_first_bounding_area(LineartRenderBuffer *rb, + LineartLine *rl) +{ + double data[2] = {rl->l->fbcoord[0], rl->l->fbcoord[1]}; + double LU[2] = {-1, 1}, RU[2] = {1, 1}, LB[2] = {-1, -1}, RB[2] = {1, -1}; + double r = 1, sr = 1; + + if (data[0] > -1 && data[0] < 1 && data[1] > -1 && data[1] < 1) { + return lineart_get_bounding_area(rb, data[0], data[1]); + } + + if (lineart_LineIntersectTest2d(rl->l->fbcoord, rl->r->fbcoord, LU, RU, &sr) && sr < r && + sr > 0) { + r = sr; + } + if (lineart_LineIntersectTest2d(rl->l->fbcoord, rl->r->fbcoord, LB, RB, &sr) && sr < r && + sr > 0) { + r = sr; + } + if (lineart_LineIntersectTest2d(rl->l->fbcoord, rl->r->fbcoord, LB, LU, &sr) && sr < r && + sr > 0) { + r = sr; + } + if (lineart_LineIntersectTest2d(rl->l->fbcoord, rl->r->fbcoord, RB, RU, &sr) && sr < r && + sr > 0) { + r = sr; + } + interp_v2_v2v2_db(data, rl->l->fbcoord, rl->r->fbcoord, r); + + return lineart_get_bounding_area(rb, data[0], data[1]); +} + +/* This march along one render line in image space and + * get the next bounding area the line is crossing. */ +static LineartBoundingArea *lineart_bounding_area_next(LineartBoundingArea *this, + LineartLine *rl, + double x, + double y, + double k, + int positive_x, + int positive_y, + double *next_x, + double *next_y) +{ + double rx, ry, ux, uy, lx, ly, bx, by; + double r1, r2; + LineartBoundingArea *ba; + + /* If we are marching towards the right. */ + if (positive_x > 0) { + rx = this->r; + ry = y + k * (rx - x); + + /* If we are marching towards the top. */ + if (positive_y > 0) { + uy = this->u; + ux = x + (uy - y) / k; + r1 = ratiod(rl->l->fbcoord[0], rl->r->fbcoord[0], rx); + r2 = ratiod(rl->l->fbcoord[0], rl->r->fbcoord[0], ux); + if (MIN2(r1, r2) > 1) { + return 0; + } + + /* We reached the right side before the top side. */ + if (r1 <= r2) { + LISTBASE_FOREACH (LinkData *, lip, &this->rp) { + ba = lip->data; + if (ba->u >= ry && ba->b < ry) { + *next_x = rx; + *next_y = ry; + return ba; + } + } + } + /* We reached the top side before the right side. */ + else { + LISTBASE_FOREACH (LinkData *, lip, &this->up) { + ba = lip->data; + if (ba->r >= ux && ba->l < ux) { + *next_x = ux; + *next_y = uy; + return ba; + } + } + } + } + /* If we are marching towards the bottom. */ + else if (positive_y < 0) { + by = this->b; + bx = x + (by - y) / k; + r1 = ratiod(rl->l->fbcoord[0], rl->r->fbcoord[0], rx); + r2 = ratiod(rl->l->fbcoord[0], rl->r->fbcoord[0], bx); + if (MIN2(r1, r2) > 1) { + return 0; + } + if (r1 <= r2) { + LISTBASE_FOREACH (LinkData *, lip, &this->rp) { + ba = lip->data; + if (ba->u >= ry && ba->b < ry) { + *next_x = rx; + *next_y = ry; + return ba; + } + } + } + else { + LISTBASE_FOREACH (LinkData *, lip, &this->bp) { + ba = lip->data; + if (ba->r >= bx && ba->l < bx) { + *next_x = bx; + *next_y = by; + return ba; + } + } + } + } + /* If the line is compeletely horizontal, in which Y diffence == 0. */ + else { + r1 = ratiod(rl->l->fbcoord[0], rl->r->fbcoord[0], this->r); + if (r1 > 1) { + return 0; + } + LISTBASE_FOREACH (LinkData *, lip, &this->rp) { + ba = lip->data; + if (ba->u >= y && ba->b < y) { + *next_x = this->r; + *next_y = y; + return ba; + } + } + } + } + + /* If we are marching towards the left. */ + else if (positive_x < 0) { + lx = this->l; + ly = y + k * (lx - x); + + /* If we are marching towards the top. */ + if (positive_y > 0) { + uy = this->u; + ux = x + (uy - y) / k; + r1 = ratiod(rl->l->fbcoord[0], rl->r->fbcoord[0], lx); + r2 = ratiod(rl->l->fbcoord[0], rl->r->fbcoord[0], ux); + if (MIN2(r1, r2) > 1) { + return 0; + } + if (r1 <= r2) { + LISTBASE_FOREACH (LinkData *, lip, &this->lp) { + ba = lip->data; + if (ba->u >= ly && ba->b < ly) { + *next_x = lx; + *next_y = ly; + return ba; + } + } + } + else { + LISTBASE_FOREACH (LinkData *, lip, &this->up) { + ba = lip->data; + if (ba->r >= ux && ba->l < ux) { + *next_x = ux; + *next_y = uy; + return ba; + } + } + } + } + + /* If we are marching towards the bottom. */ + else if (positive_y < 0) { + by = this->b; + bx = x + (by - y) / k; + r1 = ratiod(rl->l->fbcoord[0], rl->r->fbcoord[0], lx); + r2 = ratiod(rl->l->fbcoord[0], rl->r->fbcoord[0], bx); + if (MIN2(r1, r2) > 1) { + return 0; + } + if (r1 <= r2) { + LISTBASE_FOREACH (LinkData *, lip, &this->lp) { + ba = lip->data; + if (ba->u >= ly && ba->b < ly) { + *next_x = lx; + *next_y = ly; + return ba; + } + } + } + else { + LISTBASE_FOREACH (LinkData *, lip, &this->bp) { + ba = lip->data; + if (ba->r >= bx && ba->l < bx) { + *next_x = bx; + *next_y = by; + return ba; + } + } + } + } + /* Again, horizontal. */ + else { + r1 = ratiod(rl->l->fbcoord[0], rl->r->fbcoord[0], this->l); + if (r1 > 1) { + return 0; + } + LISTBASE_FOREACH (LinkData *, lip, &this->lp) { + ba = lip->data; + if (ba->u >= y && ba->b < y) { + *next_x = this->l; + *next_y = y; + return ba; + } + } + } + } + /* If the line is completely vertical, hence X difference == 0. */ + else { + if (positive_y > 0) { + r1 = ratiod(rl->l->fbcoord[1], rl->r->fbcoord[1], this->u); + if (r1 > 1) { + return 0; + } + LISTBASE_FOREACH (LinkData *, lip, &this->up) { + ba = lip->data; + if (ba->r > x && ba->l <= x) { + *next_x = x; + *next_y = this->u; + return ba; + } + } + } + else if (positive_y < 0) { + r1 = ratiod(rl->l->fbcoord[1], rl->r->fbcoord[1], this->b); + if (r1 > 1) { + return 0; + } + LISTBASE_FOREACH (LinkData *, lip, &this->bp) { + ba = lip->data; + if (ba->r > x && ba->l <= x) { + *next_x = x; + *next_y = this->b; + return ba; + } + } + } + else { + /* egment has no length. */ + return 0; + } + } + return 0; +} + +/* This is the entry point of all line art calculations. */ +int MOD_lineart_compute_feature_lines(Depsgraph *depsgraph, LineartGpencilModifierData *lmd) +{ + LineartRenderBuffer *rb; + Scene *scene = DEG_get_evaluated_scene(depsgraph); + int intersections_only = 0; /* Not used right now, but preserve for future. */ + + if (!scene->camera) { + return OPERATOR_CANCELLED; + } + + rb = lineart_create_render_buffer(scene, lmd); + + /* Triangle thread testing data size varies depending on the thread count. + * See definition of LineartTriangleThread for details. */ + rb->triangle_size = lineart_triangle_size_get(scene, rb); + + /* This is used to limit calculation to a certain level to save time, lines who have higher + * occlusion levels will get ignored. */ + rb->max_occlusion_level = MAX2(lmd->level_start, lmd->level_end); + + /* Get view vector before loading geometries, because we detect feature lines there. */ + lineart_main_get_view_vector(rb); + lineart_main_load_geometries( + depsgraph, scene, scene->camera, rb, lmd->calculation_flags & LRT_ALLOW_DUPLI_OBJECTS); + + if (!rb->vertex_buffer_pointers.first) { + /* No geometry loaded, return early. */ + return OPERATOR_FINISHED; + } + + /* Initialize the bounding box acceleration structure, it's a lot like BVH in 3D. */ + lineart_main_bounding_area_make_initial(rb); + + /* We need to get cut into triangles that are crossing near/far plans, only this way can we get + * correct coordinates of those clipped lines. Done in two steps, + * setting clip_far==false for near plane. */ + lineart_main_cull_triangles(rb, false); + /* clip_far==true for far plane. */ + lineart_main_cull_triangles(rb, true); + + /* At this point triangle adjacent info pointers is no longer needed, free them. */ + lineart_main_free_adjacent_data(rb); + + /* Do the perspective division after clipping is done. */ + lineart_main_perspective_division(rb); + + /* Triangle intersections are done here during sequential adding of them. Only after this, + * triangles and lines are all linked with acceleration structure, and the 2D occlusion stage can + * do its job. */ + lineart_main_add_triangles(rb); + + /* Link lines to acceleration structure, this can only be done after perspective division, if we + * do it after triangles being added, the acceleration structure has already been subdivided, + * this way we do less list manipulations. */ + lineart_main_link_lines(rb); + + /* "intersection_only" is preserved for being called in a standalone fashion. + * If so the data will already be available at the stage. Otherwise we do the occlusion and + * chaining etc.*/ + + if (!intersections_only) { + + /* Occlusion is work-and-wait. This call will not return before work is completed. */ + lineart_main_occlusion_begin(rb); + + /* Chaining is all single threaded. See lineart_chain.c + * In this particular call, only lines that are geometrically connected (share the _exact_ same + * end point) will be chained together. */ + MOD_lineart_chain_feature_lines(rb); + + /* We are unable to take care of occlusion if we only connect end points, so here we do a spit, + * where the splitting point could be any cut in rl->segments. */ + MOD_lineart_chain_split_for_fixed_occlusion(rb); + + /* Then we connect chains based on the _proximity_ of their end points in geometry or image + * space, here's the place threashold value gets involved. */ + + /* If both chaining thresholds are zero, then we allow at least image space chaining to do a + * little bit of work so we don't end up in fragmented strokes. */ + float *t_image = &lmd->chaining_image_threshold; + float *t_geom = &lmd->chaining_geometry_threshold; + if (*t_image < FLT_EPSILON && *t_geom < FLT_EPSILON) { + *t_geom = 0.0f; + *t_image = 0.001f; + } + + /* do_geometry_space = true. */ + MOD_lineart_chain_connect(rb, true); + + /* After chaining, we need to clear flags so we can do another round in image space. */ + MOD_lineart_chain_clear_picked_flag(rb); + + /* do_geometry_space = false (it's image_space). */ + MOD_lineart_chain_connect(rb, false); + + /* Clear again so we don't confuse GPencil generation calls. */ + MOD_lineart_chain_clear_picked_flag(rb); + + /* This configuration ensures there won't be accidental lost of short unchained segments. */ + MOD_lineart_chain_discard_short(rb, MIN3(*t_image, *t_geom, 0.001f) - FLT_EPSILON); + + if (rb->angle_splitting_threshold > FLT_EPSILON) { + MOD_lineart_chain_split_angle(rb, rb->angle_splitting_threshold); + } + } + + if (G.debug_value == 4000) { + lineart_count_and_print_render_buffer_memory(rb); + } + + return OPERATOR_FINISHED; +} + +static int lineart_rb_line_types(LineartRenderBuffer *rb) +{ + int types = 0; + types |= rb->use_contour ? LRT_EDGE_FLAG_CONTOUR : 0; + types |= rb->use_crease ? LRT_EDGE_FLAG_CREASE : 0; + types |= rb->use_material ? LRT_EDGE_FLAG_MATERIAL : 0; + types |= rb->use_edge_marks ? LRT_EDGE_FLAG_EDGE_MARK : 0; + types |= rb->use_intersections ? LRT_EDGE_FLAG_INTERSECTION : 0; + return types; +} + +static void lineart_gpencil_generate(LineartRenderBuffer *rb, + Depsgraph *depsgraph, + Object *gpencil_object, + float (*gp_obmat_inverse)[4], + bGPDlayer *UNUSED(gpl), + bGPDframe *gpf, + int level_start, + int level_end, + int material_nr, + Object *source_object, + Collection *source_collection, + int types, + unsigned char transparency_flags, + unsigned char transparency_mask, + short thickness, + float opacity, + float pre_sample_length, + const char *source_vgname, + const char *vgname, + int modifier_flags) +{ + if (rb == NULL) { + if (G.debug_value == 4000) { + printf("NULL Lineart rb!\n"); + } + return; + } + + int stroke_count = 0; + int color_idx = 0; + + Object *orig_ob = NULL; + if (source_object) { + orig_ob = source_object->id.orig_id ? (Object *)source_object->id.orig_id : source_object; + } + + Collection *orig_col = NULL; + if (source_collection) { + orig_col = source_collection->id.orig_id ? (Collection *)source_collection->id.orig_id : + source_collection; + } + + /* (!orig_col && !orig_ob) means the whole scene is selected. */ + + float mat[4][4]; + unit_m4(mat); + + int enabled_types = lineart_rb_line_types(rb); + bool invert_input = modifier_flags & LRT_GPENCIL_INVERT_SOURCE_VGROUP; + bool match_output = modifier_flags & LRT_GPENCIL_MATCH_OUTPUT_VGROUP; + bool preserve_weight = modifier_flags & LRT_GPENCIL_SOFT_SELECTION; + + LISTBASE_FOREACH (LineartLineChain *, rlc, &rb->chains) { + + if (rlc->picked) { + continue; + } + if (!(rlc->type & (types & enabled_types))) { + continue; + } + if (rlc->level > level_end || rlc->level < level_start) { + continue; + } + if (orig_ob && orig_ob != rlc->object_ref) { + continue; + } + if (orig_col && rlc->object_ref) { + if (!BKE_collection_has_object_recursive_instanced(orig_col, (Object *)rlc->object_ref)) { + continue; + } + } + if (transparency_flags & LRT_GPENCIL_TRANSPARENCY_ENABLE) { + if (transparency_flags & LRT_GPENCIL_TRANSPARENCY_MATCH) { + if (rlc->transparency_mask != transparency_mask) { + continue; + } + } + else { + if (!(rlc->transparency_mask & transparency_mask)) { + continue; + } + } + } + + /* Preserved: If we ever do async generation, this picked flag should be set here. */ + /* rlc->picked = 1;. */ + + int array_idx = 0; + int count = MOD_lineart_chain_count(rlc); + bGPDstroke *gps = BKE_gpencil_stroke_add(gpf, color_idx, count, thickness, false); + + float *stroke_data = MEM_callocN(sizeof(float) * count * GP_PRIM_DATABUF_SIZE, + "line art add stroke"); + + LISTBASE_FOREACH (LineartLineChainItem *, rlci, &rlc->chain) { + stroke_data[array_idx] = rlci->gpos[0]; + stroke_data[array_idx + 1] = rlci->gpos[1]; + stroke_data[array_idx + 2] = rlci->gpos[2]; + mul_m4_v3(gp_obmat_inverse, &stroke_data[array_idx]); + stroke_data[array_idx + 3] = 1; /* thickness. */ + stroke_data[array_idx + 4] = opacity; /* hardness?. */ + array_idx += 5; + } + + BKE_gpencil_stroke_add_points(gps, stroke_data, count, mat); + BKE_gpencil_dvert_ensure(gps); + gps->mat_nr = material_nr; + + MEM_freeN(stroke_data); + + if (source_vgname && vgname) { + Object *eval_ob = DEG_get_evaluated_object(depsgraph, rlc->object_ref); + int gpdg = -1; + if ((match_output || (gpdg = BKE_object_defgroup_name_index(gpencil_object, vgname)) >= 0)) { + if (eval_ob && eval_ob->type == OB_MESH) { + int dindex = 0; + Mesh *me = (Mesh *)eval_ob->data; + if (me->dvert) { + LISTBASE_FOREACH (bDeformGroup *, db, &eval_ob->defbase) { + if ((!source_vgname) || strstr(db->name, source_vgname) == db->name) { + if (match_output) { + gpdg = BKE_object_defgroup_name_index(gpencil_object, db->name); + if (gpdg < 0) { + continue; + } + } + int sindex = 0, vindex; + LISTBASE_FOREACH (LineartLineChainItem *, rlci, &rlc->chain) { + vindex = rlci->index; + if (vindex >= me->totvert) { + break; + } + MDeformWeight *mdw = BKE_defvert_ensure_index(&me->dvert[vindex], dindex); + MDeformWeight *gdw = BKE_defvert_ensure_index(&gps->dvert[sindex], gpdg); + if (preserve_weight) { + float use_weight = mdw->weight; + if (invert_input) { + use_weight = 1 - use_weight; + } + gdw->weight = MAX2(use_weight, gdw->weight); + } + else { + if (mdw->weight > 0.999f) { + gdw->weight = 1.0f; + } + } + sindex++; + } + } + dindex++; + } + } + } + } + } + + if (pre_sample_length > 0.0001) { + BKE_gpencil_stroke_sample(gpencil_object->data, gps, pre_sample_length, false); + } + if (G.debug_value == 4000) { + BKE_gpencil_stroke_set_random_color(gps); + } + BKE_gpencil_stroke_geometry_update(gpencil_object->data, gps); + stroke_count++; + } + + if (G.debug_value == 4000) { + printf("LRT: Generated %d strokes.\n", stroke_count); + } +} + +/* Wrapper for external calls. */ +void MOD_lineart_gpencil_generate(LineartRenderBuffer *rb, + Depsgraph *depsgraph, + Object *ob, + bGPDlayer *gpl, + bGPDframe *gpf, + char source_type, + void *source_reference, + int level_start, + int level_end, + int mat_nr, + short line_types, + unsigned char transparency_flags, + unsigned char transparency_mask, + short thickness, + float opacity, + float pre_sample_length, + const char *source_vgname, + const char *vgname, + int modifier_flags) +{ + + if (!gpl || !gpf || !ob) { + return; + } + + Object *source_object = NULL; + Collection *source_collection = NULL; + short use_types = 0; + if (source_type == LRT_SOURCE_OBJECT) { + if (!source_reference) { + return; + } + source_object = (Object *)source_reference; + /* Note that intersection lines will only be in collection. */ + use_types = line_types & (~LRT_EDGE_FLAG_INTERSECTION); + } + else if (source_type == LRT_SOURCE_COLLECTION) { + if (!source_reference) { + return; + } + source_collection = (Collection *)source_reference; + use_types = line_types; + } + else { + /* Whole scene. */ + use_types = line_types; + } + float gp_obmat_inverse[4][4]; + invert_m4_m4(gp_obmat_inverse, ob->obmat); + lineart_gpencil_generate(rb, + depsgraph, + ob, + gp_obmat_inverse, + gpl, + gpf, + level_start, + level_end, + mat_nr, + source_object, + source_collection, + use_types, + transparency_flags, + transparency_mask, + thickness, + opacity, + pre_sample_length, + source_vgname, + vgname, + modifier_flags); +} |