3971 lines
132 KiB
C
3971 lines
132 KiB
C
/*
|
|
* 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_edge_first_bounding_area(LineartRenderBuffer *rb,
|
|
LineartEdge *e);
|
|
|
|
static void lineart_bounding_area_link_line(LineartRenderBuffer *rb,
|
|
LineartBoundingArea *root_ba,
|
|
LineartEdge *e);
|
|
|
|
static LineartBoundingArea *lineart_bounding_area_next(LineartBoundingArea *this,
|
|
LineartEdge *e,
|
|
double x,
|
|
double y,
|
|
double k,
|
|
int positive_x,
|
|
int positive_y,
|
|
double *next_x,
|
|
double *next_y);
|
|
|
|
static bool lineart_get_edge_bounding_areas(LineartRenderBuffer *rb,
|
|
LineartEdge *e,
|
|
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_edge_image_space_occlusion(SpinLock *spl,
|
|
const LineartTriangle *rt,
|
|
const LineartEdge *e,
|
|
const double *override_camera_loc,
|
|
const bool override_cam_is_persp,
|
|
const bool allow_overlapping_edges,
|
|
const double vp[4][4],
|
|
const double *camera_dir,
|
|
const float cam_shift_x,
|
|
const float cam_shift_y,
|
|
double *from,
|
|
double *to);
|
|
|
|
static void lineart_add_edge_to_list(LineartRenderBuffer *rb, LineartEdge *e);
|
|
|
|
static void lineart_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_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 edge in image space and mark occlusion level for each segment. */
|
|
static void lineart_edge_cut(LineartRenderBuffer *rb,
|
|
LineartEdge *e,
|
|
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 = e->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_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_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_give_segment(rb);
|
|
}
|
|
if (ns2 == NULL) {
|
|
if (untouched) {
|
|
ns2 = ns;
|
|
cut_end_before = ns2;
|
|
}
|
|
else {
|
|
ns2 = lineart_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(&e->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 = e->segments.last;
|
|
ns->occlusion = irls->occlusion;
|
|
ns->transparency_mask = irls->transparency_mask;
|
|
BLI_addtail(&e->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(&e->segments, (void *)cut_end_before, (void *)ns2);
|
|
}
|
|
}
|
|
else {
|
|
irls = e->segments.last;
|
|
ns2->occlusion = irls->occlusion;
|
|
ns2->transparency_mask = irls->transparency_mask;
|
|
BLI_addtail(&e->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 = e->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(&e->segments, rls);
|
|
/* This puts the node back to the render buffer, if more cut happens, these unused nodes get
|
|
* picked first. */
|
|
lineart_discard_segment(rb, rls);
|
|
continue;
|
|
}
|
|
|
|
min_occ = MIN2(min_occ, rls->occlusion);
|
|
|
|
prev_rls = rls;
|
|
}
|
|
e->min_occ = min_occ;
|
|
}
|
|
|
|
/* To see if given line is connected to an adjacent intersection line. */
|
|
BLI_INLINE bool lineart_occlusion_is_adjacent_intersection(LineartEdge *e, LineartTriangle *rt)
|
|
{
|
|
LineartVertIntersection *v1 = (void *)e->v1;
|
|
LineartVertIntersection *v2 = (void *)e->v2;
|
|
return ((v1->base.flag && v1->intersecting_with == (void *)rt) ||
|
|
(v2->base.flag && v2->intersecting_with == (void *)rt));
|
|
}
|
|
|
|
static void lineart_occlusion_single_line(LineartRenderBuffer *rb, LineartEdge *e, int thread_id)
|
|
{
|
|
double x = e->v1->fbcoord[0], y = e->v1->fbcoord[1];
|
|
LineartBoundingArea *ba = lineart_edge_first_bounding_area(rb, e);
|
|
LineartBoundingArea *nba = ba;
|
|
LineartTriangleThread *rt;
|
|
|
|
/* These values are used for marching along the line. */
|
|
double l, r;
|
|
double k = (e->v2->fbcoord[1] - e->v1->fbcoord[1]) /
|
|
(e->v2->fbcoord[0] - e->v1->fbcoord[0] + 1e-30);
|
|
int positive_x = (e->v2->fbcoord[0] - e->v1->fbcoord[0]) > 0 ?
|
|
1 :
|
|
(e->v2->fbcoord[0] == e->v1->fbcoord[0] ? 0 : -1);
|
|
int positive_y = (e->v2->fbcoord[1] - e->v1->fbcoord[1]) > 0 ?
|
|
1 :
|
|
(e->v2->fbcoord[1] == e->v1->fbcoord[1] ? 0 : -1);
|
|
|
|
while (nba) {
|
|
|
|
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_e[thread_id] == e || (rt->base.flags & LRT_TRIANGLE_INTERSECTION_ONLY) ||
|
|
lineart_occlusion_is_adjacent_intersection(e, (LineartTriangle *)rt)) {
|
|
continue;
|
|
}
|
|
rt->testing_e[thread_id] = e;
|
|
if (lineart_triangle_edge_image_space_occlusion(&rb->lock_task,
|
|
(void *)rt,
|
|
e,
|
|
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_edge_cut(rb, e, l, r, rt->base.transparency_mask);
|
|
if (e->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 e->v1 to e->v2, searching each possible bounding areas it may touch. */
|
|
nba = lineart_bounding_area_next(nba, e, x, y, k, positive_x, positive_y, &x, &y);
|
|
}
|
|
}
|
|
|
|
static int lineart_occlusion_make_task_info(LineartRenderBuffer *rb, LineartRenderTaskInfo *rti)
|
|
{
|
|
LineartEdge *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_EDGE_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;
|
|
LineartEdge *eip;
|
|
|
|
while (lineart_occlusion_make_task_info(rb, rti)) {
|
|
|
|
for (eip = (void *)rti->contour; eip && eip != rti->contour_end; eip = eip->next) {
|
|
lineart_occlusion_single_line(rb, eip, rti->thread_id);
|
|
}
|
|
|
|
for (eip = (void *)rti->crease; eip && eip != rti->crease_end; eip = eip->next) {
|
|
lineart_occlusion_single_line(rb, eip, rti->thread_id);
|
|
}
|
|
|
|
for (eip = (void *)rti->intersection; eip && eip != rti->intersection_end; eip = eip->next) {
|
|
lineart_occlusion_single_line(rb, eip, rti->thread_id);
|
|
}
|
|
|
|
for (eip = (void *)rti->material; eip && eip != rti->material_end; eip = eip->next) {
|
|
lineart_occlusion_single_line(rb, eip, rti->thread_id);
|
|
}
|
|
|
|
for (eip = (void *)rti->edge_mark; eip && eip != rti->edge_mark_end; eip = eip->next) {
|
|
lineart_occlusion_single_line(rb, eip, rti->thread_id);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* All internal functions starting with lineart_main_ is called inside
|
|
* MOD_lineart_compute_feature_lines function.
|
|
* This function handles all occlusion calculation. */
|
|
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_edge_space(LineartRenderBuffer *rb)
|
|
{
|
|
LineartElementLinkNode *reln;
|
|
|
|
LineartEdge *render_edges = lineart_mem_aquire(&rb->render_data_pool, sizeof(LineartEdge) * 64);
|
|
|
|
reln = lineart_list_append_pointer_pool_sized(&rb->line_buffer_pointers,
|
|
&rb->render_data_pool,
|
|
render_edges,
|
|
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_edge_match(LineartTriangle *rt, LineartEdge *e, int v1, int v2)
|
|
{
|
|
return ((rt->v[v1] == e->v1 && rt->v[v2] == e->v2) ||
|
|
(rt->v[v2] == e->v1 && rt->v[v1] == e->v2));
|
|
}
|
|
|
|
/* 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_e_count,
|
|
int *r_t_count,
|
|
LineartElementLinkNode *v_eln,
|
|
LineartElementLinkNode *e_eln,
|
|
LineartElementLinkNode *t_eln)
|
|
{
|
|
double vv1[3], vv2[3], dot1, dot2;
|
|
double a;
|
|
int v_count = *r_v_count;
|
|
int e_count = *r_e_count;
|
|
int t_count = *r_t_count;
|
|
int v1_obi, v2_obi;
|
|
char new_flag = 0;
|
|
|
|
LineartEdge *new_e, *e, *old_e;
|
|
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 *)v_eln->pointer)[v_count];
|
|
LineartTriangle *rt1 = (void *)(((unsigned char *)t_eln->pointer) + rb->triangle_size * t_count);
|
|
LineartTriangle *rt2 = (void *)(((unsigned char *)t_eln->pointer) +
|
|
rb->triangle_size * (t_count + 1));
|
|
|
|
new_e = &((LineartEdge *)e_eln->pointer)[e_count];
|
|
/* Init rl to the last rl entry. */
|
|
e = new_e;
|
|
|
|
#define INCREASE_RL \
|
|
e_count++; \
|
|
v1_obi = e->v1_obindex; \
|
|
v2_obi = e->v2_obindex; \
|
|
new_e = &((LineartEdge *)e_eln->pointer)[e_count]; \
|
|
e = new_e; \
|
|
e->v1_obindex = v1_obi; \
|
|
e->v2_obindex = v2_obi; \
|
|
rls = lineart_mem_aquire(&rb->render_data_pool, sizeof(LineartLineSegment)); \
|
|
BLI_addtail(&e->segments, rls);
|
|
|
|
#define SELECT_RL(e_num, v1_link, v2_link, newrt) \
|
|
if (rta->e[e_num]) { \
|
|
old_e = rta->e[e_num]; \
|
|
new_flag = old_e->flags; \
|
|
old_e->flags = LRT_EDGE_FLAG_CHAIN_PICKED; \
|
|
INCREASE_RL \
|
|
e->v1 = (v1_link); \
|
|
e->v2 = (v2_link); \
|
|
e->flags = new_flag; \
|
|
e->object_ref = ob; \
|
|
e->t1 = ((old_e->t1 == rt) ? (newrt) : (old_e->t1)); \
|
|
e->t2 = ((old_e->t2 == rt) ? (newrt) : (old_e->t2)); \
|
|
lineart_add_edge_to_list(rb, e); \
|
|
}
|
|
|
|
#define RELINK_RL(e_num, newrt) \
|
|
if (rta->e[e_num]) { \
|
|
old_e = rta->e[e_num]; \
|
|
old_e->t1 = ((old_e->t1 == rt) ? (newrt) : (old_e->t1)); \
|
|
old_e->t2 = ((old_e->t2 == rt) ? (newrt) : (old_e->t2)); \
|
|
}
|
|
|
|
#define REMOVE_TRIANGLE_RL \
|
|
if (rta->e[0]) { \
|
|
rta->e[0]->flags = LRT_EDGE_FLAG_CHAIN_PICKED; \
|
|
} \
|
|
if (rta->e[1]) { \
|
|
rta->e[1]->flags = LRT_EDGE_FLAG_CHAIN_PICKED; \
|
|
} \
|
|
if (rta->e[2]) { \
|
|
rta->e[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) {
|
|
e->flags = LRT_EDGE_FLAG_CONTOUR;
|
|
lineart_prepend_edge_direct(&rb->contours, e);
|
|
}
|
|
/* Note: inverting e->v1/v2 (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.
|
|
*/
|
|
e->v1 = &rv[1];
|
|
e->v2 = &rv[0];
|
|
/* Only one adjacent triangle, because the other side is the near plane. */
|
|
/* Use tl or tr doesn't matter. */
|
|
e->t1 = rt1;
|
|
e->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) {
|
|
e->flags = LRT_EDGE_FLAG_CONTOUR;
|
|
lineart_prepend_edge_direct(&rb->contours, e);
|
|
}
|
|
e->v1 = &rv[0];
|
|
e->v2 = &rv[1];
|
|
e->t1 = rt1;
|
|
e->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) {
|
|
e->flags = LRT_EDGE_FLAG_CONTOUR;
|
|
lineart_prepend_edge_direct(&rb->contours, e);
|
|
}
|
|
e->v1 = &rv[1];
|
|
e->v2 = &rv[0];
|
|
e->t1 = rt1;
|
|
e->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) {
|
|
e->flags = LRT_EDGE_FLAG_CONTOUR;
|
|
lineart_prepend_edge_direct(&rb->contours, e);
|
|
}
|
|
e->v1 = &rv[1];
|
|
e->v2 = &rv[0];
|
|
e->t1 = rt1;
|
|
e->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) {
|
|
e->flags = LRT_EDGE_FLAG_CONTOUR;
|
|
lineart_prepend_edge_direct(&rb->contours, e);
|
|
}
|
|
e->v1 = &rv[1];
|
|
e->v2 = &rv[0];
|
|
e->t1 = rt1;
|
|
e->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) {
|
|
e->flags = LRT_EDGE_FLAG_CONTOUR;
|
|
lineart_prepend_edge_direct(&rb->contours, e);
|
|
}
|
|
e->v1 = &rv[1];
|
|
e->v2 = &rv[0];
|
|
e->t1 = rt1;
|
|
e->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_e_count = e_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 *v_eln, *t_eln, *e_eln;
|
|
double(*vp)[4] = rb->view_projection;
|
|
int i;
|
|
int v_count = 0, t_count = 0, e_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);
|
|
}
|
|
|
|
v_eln = lineart_memory_get_vert_space(rb);
|
|
t_eln = lineart_memory_get_triangle_space(rb);
|
|
e_eln = lineart_memory_get_edge_space(rb);
|
|
|
|
/* Additional memory space for storing generated points and triangles. */
|
|
#define LRT_CULL_ENSURE_MEMORY \
|
|
if (v_count > 60) { \
|
|
v_eln->element_count = v_count; \
|
|
v_eln = lineart_memory_get_vert_space(rb); \
|
|
v_count = 0; \
|
|
} \
|
|
if (t_count > 60) { \
|
|
t_eln->element_count = t_count; \
|
|
t_eln = lineart_memory_get_triangle_space(rb); \
|
|
t_count = 0; \
|
|
} \
|
|
if (e_count > 60) { \
|
|
e_eln->element_count = e_count; \
|
|
e_eln = lineart_memory_get_edge_space(rb); \
|
|
e_count = 0; \
|
|
}
|
|
|
|
#define LRT_CULL_DECIDE_INSIDE \
|
|
/* These three represents points that are in the clipping range or not*/ \
|
|
in0 = 0, in1 = 0, in2 = 0; \
|
|
if (clip_far) { \
|
|
/* Point outside far plane. */ \
|
|
if (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,
|
|
&e_count,
|
|
&t_count,
|
|
v_eln,
|
|
e_eln,
|
|
t_eln);
|
|
}
|
|
t_eln->element_count = t_count;
|
|
v_eln->element_count = v_count;
|
|
}
|
|
|
|
#undef LRT_CULL_ENSURE_MEMORY
|
|
#undef LRT_CULL_DECIDE_INSIDE
|
|
}
|
|
|
|
/* Adjacent data is only used during the initial stages of computing. So we can free it using this
|
|
* function when it is not needed anymore. */
|
|
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_edge_to_list(LineartRenderBuffer *rb, LineartEdge *e)
|
|
{
|
|
switch (e->flags) {
|
|
case LRT_EDGE_FLAG_CONTOUR:
|
|
lineart_prepend_edge_direct(&rb->contours, e);
|
|
break;
|
|
case LRT_EDGE_FLAG_CREASE:
|
|
lineart_prepend_edge_direct(&rb->crease_lines, e);
|
|
break;
|
|
case LRT_EDGE_FLAG_MATERIAL:
|
|
lineart_prepend_edge_direct(&rb->material_lines, e);
|
|
break;
|
|
case LRT_EDGE_FLAG_EDGE_MARK:
|
|
lineart_prepend_edge_direct(&rb->edge_marks, e);
|
|
break;
|
|
case LRT_EDGE_FLAG_INTERSECTION:
|
|
lineart_prepend_edge_direct(&rb->intersection_lines, e);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void lineart_triangle_adjacent_assign(LineartTriangle *rt,
|
|
LineartTriangleAdjacent *rta,
|
|
LineartEdge *e)
|
|
{
|
|
if (lineart_edge_match(rt, e, 0, 1)) {
|
|
rta->e[0] = e;
|
|
}
|
|
else if (lineart_edge_match(rt, e, 1, 2)) {
|
|
rta->e[1] = e;
|
|
}
|
|
else if (lineart_edge_match(rt, e, 2, 0)) {
|
|
rta->e[2] = e;
|
|
}
|
|
}
|
|
|
|
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;
|
|
LineartEdge *la_e;
|
|
LineartTriangle *rt;
|
|
LineartTriangleAdjacent *orta;
|
|
double new_mvp[4][4], new_mv[4][4], normal[4][4];
|
|
float imat[4][4];
|
|
LineartElementLinkNode *reln;
|
|
LineartVert *orv;
|
|
LineartEdge *o_la_e;
|
|
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_la_e = 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_la_e++;
|
|
}
|
|
/* 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;
|
|
}
|
|
|
|
o_la_e = lineart_mem_aquire(&rb->render_data_pool, sizeof(LineartEdge) * allocate_la_e);
|
|
reln = lineart_list_append_pointer_pool_sized(
|
|
&rb->line_buffer_pointers, &rb->render_data_pool, o_la_e, sizeof(LineartElementLinkNode));
|
|
reln->element_count = allocate_la_e;
|
|
reln->object_ref = orig_ob;
|
|
|
|
la_e = o_la_e;
|
|
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;
|
|
}
|
|
|
|
la_e->v1 = &orv[BM_elem_index_get(e->v1)];
|
|
la_e->v2 = &orv[BM_elem_index_get(e->v2)];
|
|
la_e->v1_obindex = la_e->v1->index - global_i;
|
|
la_e->v2_obindex = la_e->v2->index - global_i;
|
|
if (e->l) {
|
|
int findex = BM_elem_index_get(e->l->f);
|
|
la_e->t1 = lineart_triangle_from_index(rb, ort, findex);
|
|
lineart_triangle_adjacent_assign(la_e->t1, &orta[findex], la_e);
|
|
if (e->l->radial_next && e->l->radial_next != e->l) {
|
|
findex = BM_elem_index_get(e->l->radial_next->f);
|
|
la_e->t2 = lineart_triangle_from_index(rb, ort, findex);
|
|
lineart_triangle_adjacent_assign(la_e->t2, &orta[findex], la_e);
|
|
}
|
|
}
|
|
la_e->flags = e->head.hflag;
|
|
la_e->object_ref = orig_ob;
|
|
|
|
LineartLineSegment *rls = lineart_mem_aquire(&rb->render_data_pool,
|
|
sizeof(LineartLineSegment));
|
|
BLI_addtail(&la_e->segments, rls);
|
|
if (usage == OBJECT_LRT_INHERENT || usage == OBJECT_LRT_INCLUDE ||
|
|
usage == OBJECT_LRT_NO_INTERSECTION) {
|
|
lineart_add_edge_to_list(rb, la_e);
|
|
}
|
|
|
|
la_e++;
|
|
}
|
|
|
|
LRT_MESH_FINISH
|
|
}
|
|
|
|
#undef LRT_MESH_FINISH
|
|
}
|
|
|
|
static bool _lineart_object_not_in_source_collection(Collection *source, Object *ob)
|
|
{
|
|
CollectionChild *cc;
|
|
Collection *c = source->id.orig_id ? (Collection *)source->id.orig_id : source;
|
|
if (BKE_collection_has_object(c, (Object *)(ob->id.orig_id))) {
|
|
return false;
|
|
}
|
|
for (cc = source->children.first; cc; cc = cc->next) {
|
|
if (!_lineart_object_not_in_source_collection(cc->collection, ob)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* See if this object in such collection is used for generating line art,
|
|
* Disabling a collection for line art will diable all objects inside.
|
|
* "_rb" is used to provide source selection info. See the definition of rb->_source_type for
|
|
* details. */
|
|
static int lineart_usage_check(Collection *c, Object *ob, LineartRenderBuffer *_rb)
|
|
{
|
|
|
|
if (!c) {
|
|
return OBJECT_LRT_INHERENT;
|
|
}
|
|
|
|
int object_has_special_usage = (ob->lineart.usage != OBJECT_LRT_INHERENT);
|
|
|
|
if (object_has_special_usage) {
|
|
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, _rb);
|
|
if (result > OBJECT_LRT_INHERENT) {
|
|
return result;
|
|
}
|
|
}
|
|
|
|
/* Temp solution to speed up calculation in the modifier without cache. See the definition of
|
|
* rb->_source_type for details. */
|
|
if (_rb->_source_type == LRT_SOURCE_OBJECT) {
|
|
if (ob != _rb->_source_object) {
|
|
return OBJECT_LRT_OCCLUSION_ONLY;
|
|
}
|
|
}
|
|
else if (_rb->_source_type == LRT_SOURCE_COLLECTION) {
|
|
if (_lineart_object_not_in_source_collection(_rb->_source_collection, ob)) {
|
|
return OBJECT_LRT_OCCLUSION_ONLY;
|
|
}
|
|
}
|
|
|
|
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, rb);
|
|
|
|
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 LineartEdge *e,
|
|
bool allow_overlapping_edges)
|
|
{
|
|
/* Normally we just determine from the pointer address. */
|
|
if (e->t1 == rt || e->t2 == 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, e->v1) || LRT_TRI_SAME_POINT(rt, 1, e->v1) ||
|
|
LRT_TRI_SAME_POINT(rt, 2, e->v1)) &&
|
|
(LRT_TRI_SAME_POINT(rt, 0, e->v2) || LRT_TRI_SAME_POINT(rt, 1, e->v2) ||
|
|
LRT_TRI_SAME_POINT(rt, 2, e->v2))) {
|
|
return true;
|
|
}
|
|
#undef LRT_TRI_SAME_POINT
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* Sorting three intersection points from min to max,
|
|
* the order for each intersection is set in lst[0] to lst[2].*/
|
|
#define INTERSECT_SORT_MIN_TO_MAX_3(ia, ib, ic, lst) \
|
|
{ \
|
|
lst[0] = LRT_MIN3_INDEX(ia, ib, ic); \
|
|
lst[1] = (((ia <= ib && ib <= ic) || (ic <= ib && ib <= ia)) ? \
|
|
1 : \
|
|
(((ic <= ia && ia <= ib) || (ib < ia && ia <= ic)) ? 0 : 2)); \
|
|
lst[2] = LRT_MAX3_INDEX(ia, ib, ic); \
|
|
}
|
|
|
|
/* ia ib ic are ordered. */
|
|
#define INTERSECT_JUST_GREATER(is, order, num, index) \
|
|
{ \
|
|
index = (num < is[order[0]] ? \
|
|
order[0] : \
|
|
(num < is[order[1]] ? order[1] : (num < is[order[2]] ? order[2] : 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 e->v1 to e->v2. The line is later cut with these two values.
|
|
*/
|
|
static bool lineart_triangle_edge_image_space_occlusion(SpinLock *UNUSED(spl),
|
|
const LineartTriangle *rt,
|
|
const LineartEdge *e,
|
|
const double *override_camera_loc,
|
|
const bool override_cam_is_persp,
|
|
const bool allow_overlapping_edges,
|
|
const double vp[4][4],
|
|
const double *camera_dir,
|
|
const 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 = e->v1->fbcoord, *RFBC = e->v2->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, e, 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, e->v1->gloc, rt->v[0]->gloc);
|
|
sub_v3_v3v3_db(Rv, e->v2->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, e->v1->gloc, e->v2->gloc, cut);
|
|
mul_v4_m4v3_db(trans, vp, gloc);
|
|
mul_v3db_db(trans, (1 / trans[3]));
|
|
}
|
|
else {
|
|
interp_v3_v3v3_db(trans, e->v1->fbcoord, e->v2->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(e->v1->fbcoord[0] - e->v2->fbcoord[0]) > fabs(e->v1->fbcoord[1] - e->v2->fbcoord[1])) {
|
|
cut = ratiod(e->v1->fbcoord[0], e->v2->fbcoord[0], trans[0]);
|
|
}
|
|
else {
|
|
cut = ratiod(e->v1->fbcoord[1], e->v2->fbcoord[1], trans[1]);
|
|
}
|
|
|
|
/* 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 LineartEdge *lineart_triangle_intersect(LineartRenderBuffer *rb,
|
|
LineartTriangle *rt,
|
|
LineartTriangle *testing)
|
|
{
|
|
LineartVert *v1 = 0, *v2 = 0;
|
|
LineartVert **next = &v1;
|
|
LineartEdge *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);
|
|
|
|
v1 = 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);
|
|
|
|
v2 = lineart_triangle_2v_intersection_test(rb, sv1, sv2, rt, testing, 0);
|
|
|
|
if (v2 == NULL) {
|
|
lineart_triangle_get_other_verts(testing, share, &sv1, &sv2);
|
|
v2 = lineart_triangle_2v_intersection_test(rb, sv1, sv2, testing, rt, 0);
|
|
if (v2 == 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 = &v2;
|
|
}
|
|
E1T = lineart_triangle_2v_intersection_test(rb, rt->v[1], rt->v[2], rt, testing, v1);
|
|
if (E1T && (!(*next))) {
|
|
(*next) = E1T;
|
|
lineart_vert_set_intersection_2v((*next), rt->v[1], rt->v[2]);
|
|
next = &v2;
|
|
}
|
|
if (!(*next)) {
|
|
E2T = lineart_triangle_2v_intersection_test(rb, rt->v[2], rt->v[0], rt, testing, v1);
|
|
}
|
|
if (E2T && (!(*next))) {
|
|
(*next) = E2T;
|
|
lineart_vert_set_intersection_2v((*next), rt->v[2], rt->v[0]);
|
|
next = &v2;
|
|
}
|
|
|
|
if (!(*next)) {
|
|
TE0 = lineart_triangle_2v_intersection_test(
|
|
rb, testing->v[0], testing->v[1], testing, rt, v1);
|
|
}
|
|
if (TE0 && (!(*next))) {
|
|
(*next) = TE0;
|
|
lineart_vert_set_intersection_2v((*next), testing->v[0], testing->v[1]);
|
|
next = &v2;
|
|
}
|
|
if (!(*next)) {
|
|
TE1 = lineart_triangle_2v_intersection_test(
|
|
rb, testing->v[1], testing->v[2], testing, rt, v1);
|
|
}
|
|
if (TE1 && (!(*next))) {
|
|
(*next) = TE1;
|
|
lineart_vert_set_intersection_2v((*next), testing->v[1], testing->v[2]);
|
|
next = &v2;
|
|
}
|
|
if (!(*next)) {
|
|
TE2 = lineart_triangle_2v_intersection_test(
|
|
rb, testing->v[2], testing->v[0], testing, rt, v1);
|
|
}
|
|
if (TE2 && (!(*next))) {
|
|
(*next) = TE2;
|
|
lineart_vert_set_intersection_2v((*next), testing->v[2], testing->v[0]);
|
|
next = &v2;
|
|
}
|
|
|
|
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(v1->fbcoord, rb->view_projection, v1->gloc);
|
|
mul_v4_m4v3_db(v2->fbcoord, rb->view_projection, v2->gloc);
|
|
mul_v3db_db(v1->fbcoord, (1 / v1->fbcoord[3]));
|
|
mul_v3db_db(v2->fbcoord, (1 / v2->fbcoord[3]));
|
|
|
|
v1->fbcoord[0] -= rb->shift_x * 2;
|
|
v1->fbcoord[1] -= rb->shift_y * 2;
|
|
v2->fbcoord[0] -= rb->shift_x * 2;
|
|
v2->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.*/
|
|
v1->fbcoord[2] = ZMin * ZMax / (ZMax - fabs(v1->fbcoord[2]) * (ZMax - ZMin));
|
|
v2->fbcoord[2] = ZMin * ZMax / (ZMax - fabs(v2->fbcoord[2]) * (ZMax - ZMin));
|
|
|
|
((LineartVertIntersection *)v1)->intersecting_with = rt;
|
|
((LineartVertIntersection *)v2)->intersecting_with = testing;
|
|
|
|
result = lineart_mem_aquire(&rb->render_data_pool, sizeof(LineartEdge));
|
|
result->v1 = v1;
|
|
result->v2 = v2;
|
|
result->t1 = rt;
|
|
result->t2 = 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_edge_direct(&rb->intersection_lines, result);
|
|
int r1, r2, c1, c2, row, col;
|
|
if (lineart_get_edge_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_e[0] == (LineartEdge *)rt) {
|
|
continue;
|
|
}
|
|
rtt->testing_e[0] = (LineartEdge *)rt;
|
|
|
|
if ((testing_triangle->flags & LRT_TRIANGLE_NO_INTERSECTION) ||
|
|
((testing_triangle->flags & LRT_TRIANGLE_INTERSECTION_ONLY) &&
|
|
(rt->flags & LRT_TRIANGLE_INTERSECTION_ONLY))) {
|
|
continue;
|
|
}
|
|
|
|
double *RG0 = testing_triangle->v[0]->gloc, *RG1 = testing_triangle->v[1]->gloc,
|
|
*RG2 = testing_triangle->v[2]->gloc;
|
|
|
|
/* Bounding box not overlapping or triangles share edges, not potential of intersecting. */
|
|
if ((MIN3(G0[2], G1[2], G2[2]) > MAX3(RG0[2], RG1[2], RG2[2])) ||
|
|
(MAX3(G0[2], G1[2], G2[2]) < MIN3(RG0[2], RG1[2], RG2[2])) ||
|
|
(MIN3(G0[0], G1[0], G2[0]) > MAX3(RG0[0], RG1[0], RG2[0])) ||
|
|
(MAX3(G0[0], G1[0], G2[0]) < MIN3(RG0[0], RG1[0], RG2[0])) ||
|
|
(MIN3(G0[1], G1[1], G2[1]) > MAX3(RG0[1], RG1[1], RG2[1])) ||
|
|
(MAX3(G0[1], G1[1], G2[1]) < MIN3(RG0[1], RG1[1], RG2[1])) ||
|
|
lineart_triangle_share_edge(rt, testing_triangle)) {
|
|
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->edge_types & LRT_EDGE_FLAG_CONTOUR) != 0;
|
|
rb->use_crease = (lmd->edge_types & LRT_EDGE_FLAG_CREASE) != 0;
|
|
rb->use_material = (lmd->edge_types & LRT_EDGE_FLAG_MATERIAL) != 0;
|
|
rb->use_edge_marks = (lmd->edge_types & LRT_EDGE_FLAG_EDGE_MARK) != 0;
|
|
rb->use_intersections = (lmd->edge_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(LineartEdge *) * (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;
|
|
LineartEdge *e;
|
|
|
|
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 ((e = lineart_list_pop_pointer_no_free(&root->linked_lines)) != NULL) {
|
|
lineart_bounding_area_link_line(rb, root, e);
|
|
}
|
|
|
|
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,
|
|
LineartEdge *e)
|
|
{
|
|
if (root_ba->child == NULL) {
|
|
lineart_list_append_pointer_pool(&root_ba->linked_lines, &rb->render_data_pool, e);
|
|
}
|
|
else {
|
|
if (lineart_bounding_area_line_intersect(
|
|
rb, e->v1->fbcoord, e->v2->fbcoord, &root_ba->child[0])) {
|
|
lineart_bounding_area_link_line(rb, &root_ba->child[0], e);
|
|
}
|
|
if (lineart_bounding_area_line_intersect(
|
|
rb, e->v1->fbcoord, e->v2->fbcoord, &root_ba->child[1])) {
|
|
lineart_bounding_area_link_line(rb, &root_ba->child[1], e);
|
|
}
|
|
if (lineart_bounding_area_line_intersect(
|
|
rb, e->v1->fbcoord, e->v2->fbcoord, &root_ba->child[2])) {
|
|
lineart_bounding_area_link_line(rb, &root_ba->child[2], e);
|
|
}
|
|
if (lineart_bounding_area_line_intersect(
|
|
rb, e->v1->fbcoord, e->v2->fbcoord, &root_ba->child[3])) {
|
|
lineart_bounding_area_link_line(rb, &root_ba->child[3], e);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 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_edge_bounding_areas(rb, e, &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], e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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_edge_bounding_areas(LineartRenderBuffer *rb,
|
|
LineartEdge *e,
|
|
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 (!e->v1 || !e->v2) {
|
|
return false;
|
|
}
|
|
|
|
if (e->v1->fbcoord[0] != e->v1->fbcoord[0] || e->v2->fbcoord[0] != e->v2->fbcoord[0]) {
|
|
return false;
|
|
}
|
|
|
|
b[0] = MIN2(e->v1->fbcoord[0], e->v2->fbcoord[0]);
|
|
b[1] = MAX2(e->v1->fbcoord[0], e->v2->fbcoord[0]);
|
|
b[2] = MIN2(e->v1->fbcoord[1], e->v2->fbcoord[1]);
|
|
b[3] = MAX2(e->v1->fbcoord[1], e->v2->fbcoord[1]);
|
|
|
|
if (b[0] > 1 || b[1] < -1 || b[2] > 1 || b[3] < -1) {
|
|
return false;
|
|
}
|
|
|
|
(*colbegin) = (int)((b[0] + 1.0) / sp_w);
|
|
(*colend) = (int)((b[1] + 1.0) / sp_w);
|
|
(*rowend) = 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 e->v1, and later use lineart_bounding_area_next() to
|
|
* get next along the way. */
|
|
static LineartBoundingArea *lineart_edge_first_bounding_area(LineartRenderBuffer *rb,
|
|
LineartEdge *e)
|
|
{
|
|
double data[2] = {e->v1->fbcoord[0], e->v1->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(e->v1->fbcoord, e->v2->fbcoord, LU, RU, &sr) && sr < r &&
|
|
sr > 0) {
|
|
r = sr;
|
|
}
|
|
if (lineart_LineIntersectTest2d(e->v1->fbcoord, e->v2->fbcoord, LB, RB, &sr) && sr < r &&
|
|
sr > 0) {
|
|
r = sr;
|
|
}
|
|
if (lineart_LineIntersectTest2d(e->v1->fbcoord, e->v2->fbcoord, LB, LU, &sr) && sr < r &&
|
|
sr > 0) {
|
|
r = sr;
|
|
}
|
|
if (lineart_LineIntersectTest2d(e->v1->fbcoord, e->v2->fbcoord, RB, RU, &sr) && sr < r &&
|
|
sr > 0) {
|
|
r = sr;
|
|
}
|
|
interp_v2_v2v2_db(data, e->v1->fbcoord, e->v2->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,
|
|
LineartEdge *e,
|
|
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(e->v1->fbcoord[0], e->v2->fbcoord[0], rx);
|
|
r2 = ratiod(e->v1->fbcoord[0], e->v2->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(e->v1->fbcoord[0], e->v2->fbcoord[0], rx);
|
|
r2 = ratiod(e->v1->fbcoord[0], e->v2->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(e->v1->fbcoord[0], e->v2->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(e->v1->fbcoord[0], e->v2->fbcoord[0], lx);
|
|
r2 = ratiod(e->v1->fbcoord[0], e->v2->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(e->v1->fbcoord[0], e->v2->fbcoord[0], lx);
|
|
r2 = ratiod(e->v1->fbcoord[0], e->v2->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(e->v1->fbcoord[0], e->v2->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(e->v1->fbcoord[1], e->v2->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(e->v1->fbcoord[1], e->v2->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);
|
|
|
|
/* FIXME: (Yiming) See definition of int LineartRenderBuffer::_source_type for detailes. */
|
|
rb->_source_type = lmd->source_type;
|
|
rb->_source_collection = lmd->source_collection;
|
|
rb->_source_object = lmd->source_object;
|
|
|
|
/* 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 e->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_edge_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 resample_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_edge_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 (resample_length > 0.0001) {
|
|
BKE_gpencil_stroke_sample(gpencil_object->data, gps, resample_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 edge_types,
|
|
unsigned char transparency_flags,
|
|
unsigned char transparency_mask,
|
|
short thickness,
|
|
float opacity,
|
|
float resample_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 = edge_types & (~LRT_EDGE_FLAG_INTERSECTION);
|
|
}
|
|
else if (source_type == LRT_SOURCE_COLLECTION) {
|
|
if (!source_reference) {
|
|
return;
|
|
}
|
|
source_collection = (Collection *)source_reference;
|
|
use_types = edge_types;
|
|
}
|
|
else {
|
|
/* Whole scene. */
|
|
use_types = edge_types;
|
|
}
|
|
float gp_obmat_inverse[4][4];
|
|
invert_m4_m4(gp_obmat_inverse, ob->obmat);
|
|
lineart_gpencil_generate(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,
|
|
resample_length,
|
|
source_vgname,
|
|
vgname,
|
|
modifier_flags);
|
|
}
|