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

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/source
diff options
context:
space:
mode:
authorYimingWu <xp8110@outlook.com>2021-03-16 21:35:53 +0300
committerSebastian Parborg <darkdefende@gmail.com>2021-03-16 21:59:09 +0300
commit3e87d8a4315d794efff659e40f0bb9e34e2aec8a (patch)
tree0c51740d7eef76e85736fbc2da34856c76405e94 /source
parent877238e2b7f5a1d8c7e705a0112f9d9b16df77d3 (diff)
Grease Pencil: Add LineArt modifier
This adds the LineArt grease pencil modifier. It takes objects or collections as input and generates various grease pencil lines from these objects with the help of the active scene camera. For example it can generate contour lines, intersection lines and crease lines to name a few. This is really useful as artists can then use 3D meshes to automatically generate grease pencil lines for characters, enviroments or other visualization purposes. These lines can then be baked and edited as regular grease pencil lines. Reviewed By: Sebastian Parborg, Antonio Vazquez, Matias Mendiola Differential Revision: http://developer.blender.org/D8758
Diffstat (limited to 'source')
-rw-r--r--source/blender/CMakeLists.txt1
-rw-r--r--source/blender/blenkernel/BKE_collection.h3
-rw-r--r--source/blender/blenkernel/BKE_global.h1
-rw-r--r--source/blender/blenkernel/BKE_gpencil.h5
-rw-r--r--source/blender/blenkernel/BKE_gpencil_modifier.h3
-rw-r--r--source/blender/blenkernel/intern/collection.c47
-rw-r--r--source/blender/blenkernel/intern/gpencil.c40
-rw-r--r--source/blender/blenkernel/intern/object_update.c10
-rw-r--r--source/blender/blenlib/BLI_math_base.h3
-rw-r--r--source/blender/blenlib/intern/math_base_inline.c12
-rw-r--r--source/blender/depsgraph/intern/builder/deg_builder_relations.cc2
-rw-r--r--source/blender/editors/gpencil/CMakeLists.txt1
-rw-r--r--source/blender/editors/gpencil/gpencil_add_lineart.c120
-rw-r--r--source/blender/editors/include/ED_gpencil.h1
-rw-r--r--source/blender/editors/mesh/editmesh_tools.c2
-rw-r--r--source/blender/editors/mesh/mesh_intern.h2
-rw-r--r--source/blender/editors/mesh/mesh_ops.c2
-rw-r--r--source/blender/editors/object/object_add.c85
-rw-r--r--source/blender/editors/object/object_ops.c5
-rw-r--r--source/blender/editors/space_buttons/buttons_context.c31
-rw-r--r--source/blender/editors/space_buttons/space_buttons.c10
-rw-r--r--source/blender/gpencil_modifiers/CMakeLists.txt12
-rw-r--r--source/blender/gpencil_modifiers/MOD_gpencil_lineart.h31
-rw-r--r--source/blender/gpencil_modifiers/MOD_gpencil_modifiertypes.h1
-rw-r--r--source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c1
-rw-r--r--source/blender/gpencil_modifiers/intern/MOD_gpencilarmature.c4
-rw-r--r--source/blender/gpencil_modifiers/intern/MOD_gpencilarray.c4
-rw-r--r--source/blender/gpencil_modifiers/intern/MOD_gpencilhook.c4
-rw-r--r--source/blender/gpencil_modifiers/intern/MOD_gpencillattice.c4
-rw-r--r--source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c477
-rw-r--r--source/blender/gpencil_modifiers/intern/MOD_gpencilmirror.c4
-rw-r--r--source/blender/gpencil_modifiers/intern/MOD_gpenciltint.c4
-rw-r--r--source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h552
-rw-r--r--source/blender/gpencil_modifiers/intern/lineart/lineart_chain.c980
-rw-r--r--source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c3931
-rw-r--r--source/blender/gpencil_modifiers/intern/lineart/lineart_intern.h113
-rw-r--r--source/blender/gpencil_modifiers/intern/lineart/lineart_ops.c439
-rw-r--r--source/blender/gpencil_modifiers/intern/lineart/lineart_util.c233
-rw-r--r--source/blender/makesdna/DNA_collection_types.h16
-rw-r--r--source/blender/makesdna/DNA_gpencil_modifier_defaults.h13
-rw-r--r--source/blender/makesdna/DNA_gpencil_modifier_types.h73
-rw-r--r--source/blender/makesdna/DNA_gpencil_types.h3
-rw-r--r--source/blender/makesdna/DNA_lineart_types.h70
-rw-r--r--source/blender/makesdna/DNA_material_types.h11
-rw-r--r--source/blender/makesdna/DNA_object_defaults.h1
-rw-r--r--source/blender/makesdna/DNA_object_types.h31
-rw-r--r--source/blender/makesdna/DNA_space_types.h1
-rw-r--r--source/blender/makesdna/intern/dna_defaults.c2
-rw-r--r--source/blender/makesdna/intern/makesdna.c2
-rw-r--r--source/blender/makesrna/RNA_access.h1
-rw-r--r--source/blender/makesrna/intern/CMakeLists.txt1
-rw-r--r--source/blender/makesrna/intern/rna_collection.c27
-rw-r--r--source/blender/makesrna/intern/rna_gpencil_modifier.c284
-rw-r--r--source/blender/makesrna/intern/rna_material.c78
-rw-r--r--source/blender/makesrna/intern/rna_object.c81
-rw-r--r--source/blender/makesrna/intern/rna_space.c1
-rw-r--r--source/blender/windowmanager/WM_api.h1
57 files changed, 7857 insertions, 20 deletions
diff --git a/source/blender/CMakeLists.txt b/source/blender/CMakeLists.txt
index f875a990d0a..8d18cf0ae9a 100644
--- a/source/blender/CMakeLists.txt
+++ b/source/blender/CMakeLists.txt
@@ -56,6 +56,7 @@ set(SRC_DNA_INC
${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_layer_types.h
${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_light_types.h
${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_lightprobe_types.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_lineart_types.h
${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_linestyle_types.h
${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_listBase.h
${CMAKE_CURRENT_SOURCE_DIR}/makesdna/DNA_mask_types.h
diff --git a/source/blender/blenkernel/BKE_collection.h b/source/blender/blenkernel/BKE_collection.h
index d15aebfe03d..d0fca5e3796 100644
--- a/source/blender/blenkernel/BKE_collection.h
+++ b/source/blender/blenkernel/BKE_collection.h
@@ -82,6 +82,8 @@ struct Collection *BKE_collection_master_add(void);
bool BKE_collection_has_object(struct Collection *collection, const struct Object *ob);
bool BKE_collection_has_object_recursive(struct Collection *collection, struct Object *ob);
+bool BKE_collection_has_object_recursive_instanced(struct Collection *collection,
+ struct Object *ob);
struct Collection *BKE_collection_object_find(struct Main *bmain,
struct Scene *scene,
struct Collection *collection,
@@ -123,6 +125,7 @@ bool BKE_collection_object_cyclic_check(struct Main *bmain,
/* Object list cache. */
struct ListBase BKE_collection_object_cache_get(struct Collection *collection);
+ListBase BKE_collection_object_cache_instanced_get(struct Collection *collection);
void BKE_collection_object_cache_free(struct Collection *collection);
struct Base *BKE_collection_or_layer_objects(const struct ViewLayer *view_layer,
diff --git a/source/blender/blenkernel/BKE_global.h b/source/blender/blenkernel/BKE_global.h
index b3d0e0c9f98..9e237679795 100644
--- a/source/blender/blenkernel/BKE_global.h
+++ b/source/blender/blenkernel/BKE_global.h
@@ -77,6 +77,7 @@ typedef struct Global {
* * 1112: Disable new Cloth internal springs handling (09/2014).
* * 1234: Disable new dyntopo code fixing skinny faces generation (04/2015).
* * 3001: Enable additional Fluid modifier (Mantaflow) options (02/2020).
+ * * 4000: Line Art state output and debugging logs (03/2021).
* * 16384 and above: Reserved for python (add-ons) usage.
*/
short debug_value;
diff --git a/source/blender/blenkernel/BKE_gpencil.h b/source/blender/blenkernel/BKE_gpencil.h
index 5cfdcf241d1..a0a3f30d6d8 100644
--- a/source/blender/blenkernel/BKE_gpencil.h
+++ b/source/blender/blenkernel/BKE_gpencil.h
@@ -212,6 +212,10 @@ void BKE_gpencil_layer_mask_sort(struct bGPdata *gpd, struct bGPDlayer *gpl);
void BKE_gpencil_layer_mask_sort_all(struct bGPdata *gpd);
void BKE_gpencil_layer_frames_sort(struct bGPDlayer *gpl, bool *r_has_duplicate_frames);
+struct bGPDlayer *BKE_gpencil_layer_get_by_name(struct bGPdata *gpd,
+ char *name,
+ int first_if_not_found);
+
/* Brush */
struct Material *BKE_gpencil_brush_material_get(struct Brush *brush);
void BKE_gpencil_brush_material_set(struct Brush *brush, struct Material *material);
@@ -231,6 +235,7 @@ struct Material *BKE_gpencil_object_material_new(struct Main *bmain,
int *r_index);
int BKE_gpencil_object_material_index_get(struct Object *ob, struct Material *ma);
+int BKE_gpencil_object_material_get_index_name(struct Object *ob, char *name);
struct Material *BKE_gpencil_object_material_from_brush_get(struct Object *ob,
struct Brush *brush);
diff --git a/source/blender/blenkernel/BKE_gpencil_modifier.h b/source/blender/blenkernel/BKE_gpencil_modifier.h
index c066c161f46..c6406c8478c 100644
--- a/source/blender/blenkernel/BKE_gpencil_modifier.h
+++ b/source/blender/blenkernel/BKE_gpencil_modifier.h
@@ -206,7 +206,8 @@ typedef struct GpencilModifierTypeInfo {
* This function is optional.
*/
void (*updateDepsgraph)(struct GpencilModifierData *md,
- const struct ModifierUpdateDepsgraphContext *ctx);
+ const struct ModifierUpdateDepsgraphContext *ctx,
+ const int mode);
/**
* Should return true if the modifier needs to be recalculated on time
diff --git a/source/blender/blenkernel/intern/collection.c b/source/blender/blenkernel/intern/collection.c
index 28b91dcc8ce..4e4134c7c8f 100644
--- a/source/blender/blenkernel/intern/collection.c
+++ b/source/blender/blenkernel/intern/collection.c
@@ -122,7 +122,9 @@ static void collection_copy_data(Main *bmain, ID *id_dst, const ID *id_src, cons
}
collection_dst->flag &= ~COLLECTION_HAS_OBJECT_CACHE;
+ collection_dst->flag &= ~COLLECTION_HAS_OBJECT_CACHE_INSTANCED;
BLI_listbase_clear(&collection_dst->object_cache);
+ BLI_listbase_clear(&collection_dst->object_cache_instanced);
BLI_listbase_clear(&collection_dst->gobject);
BLI_listbase_clear(&collection_dst->children);
@@ -214,8 +216,10 @@ static void collection_blend_write(BlendWriter *writer, ID *id, const void *id_a
if (collection->id.us > 0 || BLO_write_is_undo(writer)) {
/* Clean up, important in undo case to reduce false detection of changed data-blocks. */
collection->flag &= ~COLLECTION_HAS_OBJECT_CACHE;
+ collection->flag &= ~COLLECTION_HAS_OBJECT_CACHE_INSTANCED;
collection->tag = 0;
BLI_listbase_clear(&collection->object_cache);
+ BLI_listbase_clear(&collection->object_cache_instanced);
BLI_listbase_clear(&collection->parents);
/* write LibData */
@@ -246,8 +250,10 @@ void BKE_collection_blend_read_data(BlendDataReader *reader, Collection *collect
BKE_previewimg_blend_read(reader, collection->preview);
collection->flag &= ~COLLECTION_HAS_OBJECT_CACHE;
+ collection->flag &= ~COLLECTION_HAS_OBJECT_CACHE_INSTANCED;
collection->tag = 0;
BLI_listbase_clear(&collection->object_cache);
+ BLI_listbase_clear(&collection->object_cache_instanced);
BLI_listbase_clear(&collection->parents);
#ifdef USE_COLLECTION_COMPAT_28
@@ -773,7 +779,10 @@ const char *BKE_collection_ui_name_get(struct Collection *collection)
/** \name Object List Cache
* \{ */
-static void collection_object_cache_fill(ListBase *lb, Collection *collection, int parent_restrict)
+static void collection_object_cache_fill(ListBase *lb,
+ Collection *collection,
+ int parent_restrict,
+ bool with_instances)
{
int child_restrict = collection->flag | parent_restrict;
@@ -784,6 +793,10 @@ static void collection_object_cache_fill(ListBase *lb, Collection *collection, i
base = MEM_callocN(sizeof(Base), "Object Base");
base->object = cob->ob;
BLI_addtail(lb, base);
+ if (with_instances && cob->ob->instance_collection) {
+ collection_object_cache_fill(
+ lb, cob->ob->instance_collection, child_restrict, with_instances);
+ }
}
/* Only collection flags are checked here currently, object restrict flag is checked
@@ -798,7 +811,7 @@ static void collection_object_cache_fill(ListBase *lb, Collection *collection, i
}
LISTBASE_FOREACH (CollectionChild *, child, &collection->children) {
- collection_object_cache_fill(lb, child->collection, child_restrict);
+ collection_object_cache_fill(lb, child->collection, child_restrict, with_instances);
}
}
@@ -809,7 +822,7 @@ ListBase BKE_collection_object_cache_get(Collection *collection)
BLI_mutex_lock(&cache_lock);
if (!(collection->flag & COLLECTION_HAS_OBJECT_CACHE)) {
- collection_object_cache_fill(&collection->object_cache, collection, 0);
+ collection_object_cache_fill(&collection->object_cache, collection, 0, false);
collection->flag |= COLLECTION_HAS_OBJECT_CACHE;
}
BLI_mutex_unlock(&cache_lock);
@@ -818,11 +831,29 @@ ListBase BKE_collection_object_cache_get(Collection *collection)
return collection->object_cache;
}
+ListBase BKE_collection_object_cache_instanced_get(Collection *collection)
+{
+ if (!(collection->flag & COLLECTION_HAS_OBJECT_CACHE_INSTANCED)) {
+ static ThreadMutex cache_lock = BLI_MUTEX_INITIALIZER;
+
+ BLI_mutex_lock(&cache_lock);
+ if (!(collection->flag & COLLECTION_HAS_OBJECT_CACHE_INSTANCED)) {
+ collection_object_cache_fill(&collection->object_cache_instanced, collection, 0, true);
+ collection->flag |= COLLECTION_HAS_OBJECT_CACHE_INSTANCED;
+ }
+ BLI_mutex_unlock(&cache_lock);
+ }
+
+ return collection->object_cache_instanced;
+}
+
static void collection_object_cache_free(Collection *collection)
{
/* Clear own cache an for all parents, since those are affected by changes as well. */
collection->flag &= ~COLLECTION_HAS_OBJECT_CACHE;
+ collection->flag &= ~COLLECTION_HAS_OBJECT_CACHE_INSTANCED;
BLI_freelistN(&collection->object_cache);
+ BLI_freelistN(&collection->object_cache_instanced);
LISTBASE_FOREACH (CollectionParent *, parent, &collection->parents) {
collection_object_cache_free(parent->collection);
@@ -928,6 +959,16 @@ bool BKE_collection_has_object_recursive(Collection *collection, Object *ob)
return (BLI_findptr(&objects, ob, offsetof(Base, object)));
}
+bool BKE_collection_has_object_recursive_instanced(Collection *collection, Object *ob)
+{
+ if (ELEM(NULL, collection, ob)) {
+ return false;
+ }
+
+ const ListBase objects = BKE_collection_object_cache_instanced_get(collection);
+ return (BLI_findptr(&objects, ob, offsetof(Base, object)));
+}
+
static Collection *collection_next_find(Main *bmain, Scene *scene, Collection *collection)
{
if (scene && collection == scene->master_collection) {
diff --git a/source/blender/blenkernel/intern/gpencil.c b/source/blender/blenkernel/intern/gpencil.c
index 00dcaad83db..3b46672f9cd 100644
--- a/source/blender/blenkernel/intern/gpencil.c
+++ b/source/blender/blenkernel/intern/gpencil.c
@@ -1663,6 +1663,31 @@ bGPDlayer *BKE_gpencil_layer_active_get(bGPdata *gpd)
return NULL;
}
+bGPDlayer *BKE_gpencil_layer_get_by_name(bGPdata *gpd, char *name, int first_if_not_found)
+{
+ bGPDlayer *gpl;
+ int i = 0;
+
+ /* error checking */
+ if (ELEM(NULL, gpd, gpd->layers.first)) {
+ return NULL;
+ }
+
+ /* loop over layers until found (assume only one active) */
+ for (gpl = gpd->layers.first; gpl; gpl = gpl->next) {
+ if (STREQ(name, gpl->info)) {
+ return gpl;
+ }
+ i++;
+ }
+
+ /* no such layer */
+ if (first_if_not_found) {
+ return gpd->layers.first;
+ }
+ return NULL;
+}
+
/**
* Set active grease pencil layer.
* \param gpd: Grease pencil data-block
@@ -2421,6 +2446,21 @@ int BKE_gpencil_object_material_index_get(Object *ob, Material *ma)
return -1;
}
+int BKE_gpencil_object_material_get_index_name(Object *ob, char *name)
+{
+ short *totcol = BKE_object_material_len_p(ob);
+ Material *read_ma = NULL;
+ for (short i = 0; i < *totcol; i++) {
+ read_ma = BKE_object_material_get(ob, i + 1);
+ /* Material names are like "MAMaterial.001" */
+ if (STREQ(name, &read_ma->id.name[2])) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
/**
* Create a default palette.
* \param bmain: Main pointer
diff --git a/source/blender/blenkernel/intern/object_update.c b/source/blender/blenkernel/intern/object_update.c
index 69442b7646c..1e6a099040f 100644
--- a/source/blender/blenkernel/intern/object_update.c
+++ b/source/blender/blenkernel/intern/object_update.c
@@ -176,12 +176,14 @@ void BKE_object_handle_data_update(Depsgraph *depsgraph, Scene *scene, Object *o
CustomData_MeshMasks cddata_masks = scene->customdata_mask;
CustomData_MeshMasks_update(&cddata_masks, &CD_MASK_BAREMESH);
- if (DEG_get_mode(depsgraph) == DAG_EVAL_RENDER) {
- /* Make sure Freestyle edge/face marks appear in DM for render (see T40315). */
+ /* Make sure Freestyle edge/face marks appear in DM for render (see T40315). Due to Line Art
+ * impementation, edge marks should also be shown in viewport. */
#ifdef WITH_FREESTYLE
- cddata_masks.emask |= CD_MASK_FREESTYLE_EDGE;
- cddata_masks.pmask |= CD_MASK_FREESTYLE_FACE;
+ cddata_masks.emask |= CD_MASK_FREESTYLE_EDGE;
+ cddata_masks.pmask |= CD_MASK_FREESTYLE_FACE;
+ cddata_masks.vmask |= CD_MASK_MDEFORMVERT;
#endif
+ if (DEG_get_mode(depsgraph) == DAG_EVAL_RENDER) {
/* Always compute UVs, vertex colors as orcos for render. */
cddata_masks.lmask |= CD_MASK_MLOOPUV | CD_MASK_MLOOPCOL;
cddata_masks.vmask |= CD_MASK_ORCO | CD_MASK_PROP_COLOR;
diff --git a/source/blender/blenlib/BLI_math_base.h b/source/blender/blenlib/BLI_math_base.h
index c862290b262..028ca31a059 100644
--- a/source/blender/blenlib/BLI_math_base.h
+++ b/source/blender/blenlib/BLI_math_base.h
@@ -117,6 +117,9 @@ MINLINE float sasqrt(float fac);
MINLINE float interpf(float a, float b, float t);
MINLINE double interpd(double a, double b, double t);
+MINLINE float ratiof(float min, float max, float pos);
+MINLINE double ratiod(double min, double max, double pos);
+
/* NOTE: Compilers will upcast all types smaller than int to int when performing arithmetic
* operation. */
MINLINE int square_s(short a);
diff --git a/source/blender/blenlib/intern/math_base_inline.c b/source/blender/blenlib/intern/math_base_inline.c
index 39945960e68..6481fac5a14 100644
--- a/source/blender/blenlib/intern/math_base_inline.c
+++ b/source/blender/blenlib/intern/math_base_inline.c
@@ -180,6 +180,18 @@ MINLINE double interpd(double target, double origin, double fac)
return (fac * target) + (1.0f - fac) * origin;
}
+MINLINE float ratiof(float min, float max, float pos)
+{
+ float range = max - min;
+ return range == 0 ? 0 : ((pos - min) / range);
+}
+
+MINLINE double ratiod(double min, double max, double pos)
+{
+ double range = max - min;
+ return range == 0 ? 0 : ((pos - min) / range);
+}
+
/* used for zoom values*/
MINLINE float power_of_2(float val)
{
diff --git a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc
index 96b4da34347..3cc2ec02165 100644
--- a/source/blender/depsgraph/intern/builder/deg_builder_relations.cc
+++ b/source/blender/depsgraph/intern/builder/deg_builder_relations.cc
@@ -2085,7 +2085,7 @@ void DepsgraphRelationBuilder::build_object_data_geometry(Object *object)
if (mti->updateDepsgraph) {
DepsNodeHandle handle = create_node_handle(obdata_ubereval_key);
ctx.node = reinterpret_cast<::DepsNodeHandle *>(&handle);
- mti->updateDepsgraph(md, &ctx);
+ mti->updateDepsgraph(md, &ctx, graph_->mode);
}
if (BKE_object_modifier_gpencil_use_time(object, md)) {
TimeSourceKey time_src_key;
diff --git a/source/blender/editors/gpencil/CMakeLists.txt b/source/blender/editors/gpencil/CMakeLists.txt
index 1e91edbb3c0..47ae90acb74 100644
--- a/source/blender/editors/gpencil/CMakeLists.txt
+++ b/source/blender/editors/gpencil/CMakeLists.txt
@@ -36,6 +36,7 @@ set(SRC
annotate_paint.c
drawgpencil.c
editaction_gpencil.c
+ gpencil_add_lineart.c
gpencil_add_monkey.c
gpencil_add_stroke.c
gpencil_armature.c
diff --git a/source/blender/editors/gpencil/gpencil_add_lineart.c b/source/blender/editors/gpencil/gpencil_add_lineart.c
new file mode 100644
index 00000000000..71253635ea8
--- /dev/null
+++ b/source/blender/editors/gpencil/gpencil_add_lineart.c
@@ -0,0 +1,120 @@
+/*
+ * 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) 2017 Blender Foundation
+ * This is a new part of Blender
+ */
+
+/** \file
+ * \ingroup edgpencil
+ */
+
+#include "BLI_math.h"
+#include "BLI_utildefines.h"
+
+#include "DNA_gpencil_types.h"
+#include "DNA_material_types.h"
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+
+#include "BKE_brush.h"
+#include "BKE_context.h"
+#include "BKE_gpencil.h"
+#include "BKE_gpencil_geom.h"
+#include "BKE_lib_id.h"
+#include "BKE_main.h"
+#include "BKE_material.h"
+
+#include "DEG_depsgraph.h"
+#include "DEG_depsgraph_query.h"
+
+#include "ED_gpencil.h"
+
+/* Definition of the most important info from a color */
+typedef struct ColorTemplate {
+ const char *name;
+ float line[4];
+ float fill[4];
+} ColorTemplate;
+
+/* Add color an ensure duplications (matched by name) */
+static int gpencil_lineart_material(Main *bmain,
+ Object *ob,
+ const ColorTemplate *pct,
+ const bool fill)
+{
+ short *totcol = BKE_object_material_len_p(ob);
+ Material *ma = NULL;
+ for (short i = 0; i < *totcol; i++) {
+ ma = BKE_gpencil_material(ob, i + 1);
+ if (STREQ(ma->id.name, pct->name)) {
+ return i;
+ }
+ }
+
+ int idx;
+
+ /* create a new one */
+ ma = BKE_gpencil_object_material_new(bmain, ob, pct->name, &idx);
+
+ copy_v4_v4(ma->gp_style->stroke_rgba, pct->line);
+ srgb_to_linearrgb_v4(ma->gp_style->stroke_rgba, ma->gp_style->stroke_rgba);
+
+ copy_v4_v4(ma->gp_style->fill_rgba, pct->fill);
+ srgb_to_linearrgb_v4(ma->gp_style->fill_rgba, ma->gp_style->fill_rgba);
+
+ if (fill) {
+ ma->gp_style->flag |= GP_MATERIAL_FILL_SHOW;
+ }
+
+ return idx;
+}
+
+/* ***************************************************************** */
+/* Color Data */
+
+static const ColorTemplate gp_stroke_material_black = {
+ "Black",
+ {0.0f, 0.0f, 0.0f, 1.0f},
+ {0.0f, 0.0f, 0.0f, 0.0f},
+};
+
+/* ***************************************************************** */
+/* LineArt API */
+
+/* Add a Simple LineArt setup. */
+void ED_gpencil_create_lineart(bContext *C, Object *ob)
+{
+ Main *bmain = CTX_data_main(C);
+ Scene *scene = CTX_data_scene(C);
+ bGPdata *gpd = (bGPdata *)ob->data;
+
+ /* create colors */
+ int color_black = gpencil_lineart_material(bmain, ob, &gp_stroke_material_black, false);
+
+ /* set first color as active and in brushes */
+ ob->actcol = color_black + 1;
+
+ /* layers */
+ bGPDlayer *lines = BKE_gpencil_layer_addnew(gpd, "Lines", true);
+
+ /* frames */
+ BKE_gpencil_frame_addnew(lines, CFRA);
+
+ /* update depsgraph */
+ /* To trigger modifier update, this is still needed although we don't have any strokes. */
+ DEG_id_tag_update(&gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
+ gpd->flag |= GP_DATA_CACHE_IS_DIRTY;
+}
diff --git a/source/blender/editors/include/ED_gpencil.h b/source/blender/editors/include/ED_gpencil.h
index 2fbf280475b..f3b5abb1072 100644
--- a/source/blender/editors/include/ED_gpencil.h
+++ b/source/blender/editors/include/ED_gpencil.h
@@ -250,6 +250,7 @@ void ED_gpencil_brush_draw_eraser(struct Brush *brush, int x, int y);
void ED_gpencil_create_monkey(struct bContext *C, struct Object *ob, float mat[4][4]);
void ED_gpencil_create_stroke(struct bContext *C, struct Object *ob, float mat[4][4]);
+void ED_gpencil_create_lineart(struct bContext *C, struct Object *ob);
/* ------------ Object Utilities ------------ */
struct Object *ED_gpencil_add_object(struct bContext *C,
diff --git a/source/blender/editors/mesh/editmesh_tools.c b/source/blender/editors/mesh/editmesh_tools.c
index 68ac2842bab..8bb557509e0 100644
--- a/source/blender/editors/mesh/editmesh_tools.c
+++ b/source/blender/editors/mesh/editmesh_tools.c
@@ -7746,7 +7746,7 @@ void MESH_OT_symmetry_snap(struct wmOperatorType *ot)
/** \} */
-#ifdef WITH_FREESTYLE
+#if defined(WITH_FREESTYLE)
/* -------------------------------------------------------------------- */
/** \name Mark Edge (Freestyle) Operator
diff --git a/source/blender/editors/mesh/mesh_intern.h b/source/blender/editors/mesh/mesh_intern.h
index 21feddfb886..763bdf04d83 100644
--- a/source/blender/editors/mesh/mesh_intern.h
+++ b/source/blender/editors/mesh/mesh_intern.h
@@ -267,7 +267,7 @@ void MESH_OT_paint_mask_slice(struct wmOperatorType *ot);
struct wmKeyMap *point_normals_modal_keymap(wmKeyConfig *keyconf);
-#ifdef WITH_FREESTYLE
+#if defined(WITH_FREESTYLE)
void MESH_OT_mark_freestyle_edge(struct wmOperatorType *ot);
void MESH_OT_mark_freestyle_face(struct wmOperatorType *ot);
#endif
diff --git a/source/blender/editors/mesh/mesh_ops.c b/source/blender/editors/mesh/mesh_ops.c
index 27d73497b49..34e9a3f45a5 100644
--- a/source/blender/editors/mesh/mesh_ops.c
+++ b/source/blender/editors/mesh/mesh_ops.c
@@ -130,7 +130,7 @@ void ED_operatortypes_mesh(void)
WM_operatortype_append(MESH_OT_loop_multi_select);
WM_operatortype_append(MESH_OT_mark_seam);
WM_operatortype_append(MESH_OT_mark_sharp);
-#ifdef WITH_FREESTYLE
+#if defined(WITH_FREESTYLE)
WM_operatortype_append(MESH_OT_mark_freestyle_edge);
#endif
WM_operatortype_append(MESH_OT_vertices_smooth);
diff --git a/source/blender/editors/object/object_add.c b/source/blender/editors/object/object_add.c
index dc941f12eba..e4527740164 100644
--- a/source/blender/editors/object/object_add.c
+++ b/source/blender/editors/object/object_add.c
@@ -31,6 +31,7 @@
#include "DNA_camera_types.h"
#include "DNA_collection_types.h"
#include "DNA_curve_types.h"
+#include "DNA_gpencil_modifier_types.h"
#include "DNA_gpencil_types.h"
#include "DNA_key_types.h"
#include "DNA_light_types.h"
@@ -68,6 +69,7 @@
#include "BKE_geometry_set.h"
#include "BKE_gpencil_curve.h"
#include "BKE_gpencil_geom.h"
+#include "BKE_gpencil_modifier.h"
#include "BKE_hair.h"
#include "BKE_key.h"
#include "BKE_lattice.h"
@@ -1305,7 +1307,7 @@ static bool object_gpencil_add_poll(bContext *C)
static int object_gpencil_add_exec(bContext *C, wmOperator *op)
{
- Object *ob = CTX_data_active_object(C);
+ Object *ob = CTX_data_active_object(C), *ob_orig = ob;
bGPdata *gpd = (ob && (ob->type == OB_GPENCIL)) ? ob->data : NULL;
const int type = RNA_enum_get(op->ptr, "type");
@@ -1333,6 +1335,11 @@ static int object_gpencil_add_exec(bContext *C, wmOperator *op)
ob_name = "Stroke";
break;
}
+ case GP_LRT_OBJECT:
+ case GP_LRT_COLLECTION: {
+ ob_name = "Line Art";
+ break;
+ }
default: {
break;
}
@@ -1373,6 +1380,46 @@ static int object_gpencil_add_exec(bContext *C, wmOperator *op)
ED_gpencil_create_monkey(C, ob, mat);
break;
}
+ case GP_LRT_SCENE:
+ case GP_LRT_COLLECTION:
+ case GP_LRT_OBJECT: {
+ float radius = RNA_float_get(op->ptr, "radius");
+ float mat[4][4];
+
+ ED_object_new_primitive_matrix(C, ob, loc, rot, mat);
+ mul_v3_fl(mat[0], radius);
+ mul_v3_fl(mat[1], radius);
+ mul_v3_fl(mat[2], radius);
+
+ ED_gpencil_create_lineart(C, ob);
+
+ gpd = ob->data;
+
+ /* Add Line Art modifier */
+ LineartGpencilModifierData *md = (LineartGpencilModifierData *)BKE_gpencil_modifier_new(
+ eGpencilModifierType_Lineart);
+ BLI_addtail(&ob->greasepencil_modifiers, md);
+ BKE_gpencil_modifier_unique_name(&ob->greasepencil_modifiers, (GpencilModifierData *)md);
+
+ if (type == GP_LRT_COLLECTION) {
+ md->source_type = LRT_SOURCE_COLLECTION;
+ md->source_collection = CTX_data_collection(C);
+ }
+ else if (type == GP_LRT_OBJECT) {
+ md->source_type = LRT_SOURCE_OBJECT;
+ md->source_object = ob_orig;
+ }
+ else {
+ /* Whole scene. */
+ md->source_type = LRT_SOURCE_SCENE;
+ }
+ /* Only created one layer and one material. */
+ strcpy(md->target_layer, ((bGPDlayer *)gpd->layers.first)->info);
+ md->target_material = BKE_gpencil_material(ob, 1);
+
+ /* Stroke object is drawn in front of meshes by default. */
+ ob->dtx |= OB_DRAW_IN_FRONT;
+ }
case GP_EMPTY:
/* do nothing */
break;
@@ -1393,6 +1440,41 @@ static int object_gpencil_add_exec(bContext *C, wmOperator *op)
return OPERATOR_FINISHED;
}
+static const EnumPropertyItem *object_gpencil_add_options(bContext *C,
+ PointerRNA *UNUSED(ptr),
+ PropertyRNA *UNUSED(prop),
+ bool *r_free)
+{
+ EnumPropertyItem *item = NULL;
+ const EnumPropertyItem *item_ref = rna_enum_object_gpencil_type_items;
+ int totitem = 0;
+ int i = 0;
+ int orig_count = RNA_enum_items_count(item_ref);
+
+ /* Default types. */
+ for (i = 0; i < orig_count; i++) {
+ if (item_ref[i].value == GP_LRT_OBJECT || item_ref[i].value == GP_LRT_COLLECTION ||
+ item_ref[i].value == GP_LRT_SCENE) {
+ if (item_ref[i].value == GP_LRT_SCENE) {
+ /* separator before line art types */
+ RNA_enum_item_add_separator(&item, &totitem);
+ }
+ else if (item_ref[i].value == GP_LRT_OBJECT) {
+ Object *ob = CTX_data_active_object(C);
+ if (!ob || ob->type != OB_MESH) {
+ continue;
+ }
+ }
+ }
+ RNA_enum_item_add(&item, &totitem, &item_ref[i]);
+ }
+
+ RNA_enum_item_end(&item, &totitem);
+ *r_free = true;
+
+ return item;
+}
+
void OBJECT_OT_gpencil_add(wmOperatorType *ot)
{
/* identifiers */
@@ -1413,6 +1495,7 @@ void OBJECT_OT_gpencil_add(wmOperatorType *ot)
ED_object_add_generic_props(ot, false);
ot->prop = RNA_def_enum(ot->srna, "type", rna_enum_object_gpencil_type_items, 0, "Type", "");
+ RNA_def_enum_funcs(ot->prop, object_gpencil_add_options);
}
/** \} */
diff --git a/source/blender/editors/object/object_ops.c b/source/blender/editors/object/object_ops.c
index 57676cf5c76..71bea6bf9e3 100644
--- a/source/blender/editors/object/object_ops.c
+++ b/source/blender/editors/object/object_ops.c
@@ -37,6 +37,8 @@
#include "object_intern.h"
+#include "MOD_gpencil_lineart.h"
+
/* ************************** registration **********************************/
void ED_operatortypes_object(void)
@@ -154,6 +156,9 @@ void ED_operatortypes_object(void)
WM_operatortype_append(OBJECT_OT_gpencil_modifier_copy);
WM_operatortype_append(OBJECT_OT_gpencil_modifier_copy_to_selected);
+ /* grease pencil line art */
+ WM_operatortypes_lineart();
+
/* Shader FX. */
WM_operatortype_append(OBJECT_OT_shaderfx_add);
WM_operatortype_append(OBJECT_OT_shaderfx_remove);
diff --git a/source/blender/editors/space_buttons/buttons_context.c b/source/blender/editors/space_buttons/buttons_context.c
index 3efaff72637..13762b348c2 100644
--- a/source/blender/editors/space_buttons/buttons_context.c
+++ b/source/blender/editors/space_buttons/buttons_context.c
@@ -33,6 +33,7 @@
#include "DNA_armature_types.h"
#include "DNA_brush_types.h"
+#include "DNA_collection_types.h"
#include "DNA_linestyle_types.h"
#include "DNA_material_types.h"
#include "DNA_node_types.h"
@@ -153,6 +154,29 @@ static bool buttons_context_path_world(ButsContextPath *path)
return false;
}
+static bool buttons_context_path_collection(ButsContextPath *path, wmWindow *window)
+{
+ PointerRNA *ptr = &path->ptr[path->len - 1];
+
+ /* if we already have a (pinned) collection, we're done */
+ if (RNA_struct_is_a(ptr->type, &RNA_Collection)) {
+ return true;
+ }
+ /* if we have a view layer, use the view layer's active collection */
+ if (buttons_context_path_view_layer(path, window)) {
+ ViewLayer *view_layer = path->ptr[path->len - 1].data;
+ Collection *c = view_layer->active_collection->collection;
+ if (c) {
+ RNA_id_pointer_create(&c->id, &path->ptr[path->len]);
+ path->len++;
+ return true;
+ }
+ }
+
+ /* no path to a collection possible */
+ return false;
+}
+
static bool buttons_context_path_linestyle(ButsContextPath *path, wmWindow *window)
{
PointerRNA *ptr = &path->ptr[path->len - 1];
@@ -575,6 +599,9 @@ static bool buttons_context_path(
case BCONTEXT_WORLD:
found = buttons_context_path_world(path);
break;
+ case BCONTEXT_COLLECTION: /* This is for Line Art collection flags */
+ found = buttons_context_path_collection(path, window);
+ break;
case BCONTEXT_TOOL:
found = true;
break;
@@ -858,6 +885,10 @@ int /*eContextResult*/ buttons_context(const bContext *C,
set_pointer_type(path, result, &RNA_World);
return CTX_RESULT_OK;
}
+ if (CTX_data_equals(member, "collection")) {
+ set_pointer_type(path, result, &RNA_Collection);
+ return 1;
+ }
if (CTX_data_equals(member, "object")) {
set_pointer_type(path, result, &RNA_Object);
return CTX_RESULT_OK;
diff --git a/source/blender/editors/space_buttons/space_buttons.c b/source/blender/editors/space_buttons/space_buttons.c
index 07bc1d42c3f..43d45db38e2 100644
--- a/source/blender/editors/space_buttons/space_buttons.c
+++ b/source/blender/editors/space_buttons/space_buttons.c
@@ -206,6 +206,14 @@ int ED_buttons_tabs_list(SpaceProperties *sbuts, short *context_tabs_array)
context_tabs_array[length] = -1;
length++;
}
+ if (sbuts->pathflag & (1 << BCONTEXT_VIEW_LAYER)) {
+ context_tabs_array[length] = BCONTEXT_COLLECTION;
+ length++;
+ }
+ if (length != 0) {
+ context_tabs_array[length] = -1;
+ length++;
+ }
if (sbuts->pathflag & (1 << BCONTEXT_OBJECT)) {
context_tabs_array[length] = BCONTEXT_OBJECT;
length++;
@@ -271,6 +279,8 @@ static const char *buttons_main_region_context_string(const short mainb)
return "view_layer";
case BCONTEXT_WORLD:
return "world";
+ case BCONTEXT_COLLECTION:
+ return "collection";
case BCONTEXT_OBJECT:
return "object";
case BCONTEXT_DATA:
diff --git a/source/blender/gpencil_modifiers/CMakeLists.txt b/source/blender/gpencil_modifiers/CMakeLists.txt
index 9f1484b47c0..e3bb0d773a2 100644
--- a/source/blender/gpencil_modifiers/CMakeLists.txt
+++ b/source/blender/gpencil_modifiers/CMakeLists.txt
@@ -53,6 +53,7 @@ set(SRC
intern/MOD_gpencilbuild.c
intern/MOD_gpencilcolor.c
intern/MOD_gpencilhook.c
+ intern/MOD_gpencillineart.c
intern/MOD_gpencillattice.c
intern/MOD_gpencilmirror.c
intern/MOD_gpencilmultiply.c
@@ -70,6 +71,16 @@ set(SRC
MOD_gpencil_modifiertypes.h
intern/MOD_gpencil_ui_common.h
intern/MOD_gpencil_util.h
+
+ # Lineart code
+ intern/lineart/lineart_ops.c
+ intern/lineart/lineart_cpu.c
+ intern/lineart/lineart_chain.c
+ intern/lineart/lineart_util.c
+
+ intern/lineart/lineart_intern.h
+ intern/lineart/MOD_lineart.h
+
)
set(LIB
@@ -79,6 +90,7 @@ if(WITH_INTERNATIONAL)
add_definitions(-DWITH_INTERNATIONAL)
endif()
+add_definitions(${GL_DEFINITIONS})
blender_add_lib(bf_gpencil_modifiers "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
diff --git a/source/blender/gpencil_modifiers/MOD_gpencil_lineart.h b/source/blender/gpencil_modifiers/MOD_gpencil_lineart.h
new file mode 100644
index 00000000000..685f0cb36cb
--- /dev/null
+++ b/source/blender/gpencil_modifiers/MOD_gpencil_lineart.h
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup modifiers
+ */
+
+#pragma once
+
+#include "DNA_windowmanager_types.h"
+
+/* Operator types should be in exposed header. */
+void OBJECT_OT_lineart_bake_strokes(struct wmOperatorType *ot);
+void OBJECT_OT_lineart_bake_strokes_all(struct wmOperatorType *ot);
+void OBJECT_OT_lineart_clear(struct wmOperatorType *ot);
+void OBJECT_OT_lineart_clear_all(struct wmOperatorType *ot);
+
+void WM_operatortypes_lineart(void);
diff --git a/source/blender/gpencil_modifiers/MOD_gpencil_modifiertypes.h b/source/blender/gpencil_modifiers/MOD_gpencil_modifiertypes.h
index 3f167ac6785..e6ce7983a0f 100644
--- a/source/blender/gpencil_modifiers/MOD_gpencil_modifiertypes.h
+++ b/source/blender/gpencil_modifiers/MOD_gpencil_modifiertypes.h
@@ -43,6 +43,7 @@ extern GpencilModifierTypeInfo modifierType_Gpencil_Armature;
extern GpencilModifierTypeInfo modifierType_Gpencil_Time;
extern GpencilModifierTypeInfo modifierType_Gpencil_Multiply;
extern GpencilModifierTypeInfo modifierType_Gpencil_Texture;
+extern GpencilModifierTypeInfo modifierType_Gpencil_Lineart;
/* MOD_gpencil_util.c */
void gpencil_modifier_type_init(GpencilModifierTypeInfo *types[]);
diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c b/source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c
index f5bfe66562b..beb32c7a38e 100644
--- a/source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c
+++ b/source/blender/gpencil_modifiers/intern/MOD_gpencil_util.c
@@ -74,6 +74,7 @@ void gpencil_modifier_type_init(GpencilModifierTypeInfo *types[])
INIT_GP_TYPE(Time);
INIT_GP_TYPE(Multiply);
INIT_GP_TYPE(Texture);
+ INIT_GP_TYPE(Lineart);
#undef INIT_GP_TYPE
}
diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilarmature.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilarmature.c
index cc8eae64300..f021f71dbdc 100644
--- a/source/blender/gpencil_modifiers/intern/MOD_gpencilarmature.c
+++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilarmature.c
@@ -179,7 +179,9 @@ static bool isDisabled(GpencilModifierData *md, int UNUSED(userRenderParams))
return !mmd->object || mmd->object->type != OB_ARMATURE;
}
-static void updateDepsgraph(GpencilModifierData *md, const ModifierUpdateDepsgraphContext *ctx)
+static void updateDepsgraph(GpencilModifierData *md,
+ const ModifierUpdateDepsgraphContext *ctx,
+ const int UNUSED(mode))
{
ArmatureGpencilModifierData *lmd = (ArmatureGpencilModifierData *)md;
if (lmd->object != NULL) {
diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilarray.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilarray.c
index b8fa88327fc..7aa1320c4e3 100644
--- a/source/blender/gpencil_modifiers/intern/MOD_gpencilarray.c
+++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilarray.c
@@ -318,7 +318,9 @@ static void generateStrokes(GpencilModifierData *md, Depsgraph *depsgraph, Objec
generate_geometry(md, depsgraph, scene, ob);
}
-static void updateDepsgraph(GpencilModifierData *md, const ModifierUpdateDepsgraphContext *ctx)
+static void updateDepsgraph(GpencilModifierData *md,
+ const ModifierUpdateDepsgraphContext *ctx,
+ const int UNUSED(mode))
{
ArrayGpencilModifierData *lmd = (ArrayGpencilModifierData *)md;
if (lmd->object != NULL) {
diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilhook.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilhook.c
index 09cce3f1ab4..e0300eb498f 100644
--- a/source/blender/gpencil_modifiers/intern/MOD_gpencilhook.c
+++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilhook.c
@@ -330,7 +330,9 @@ static bool isDisabled(GpencilModifierData *md, int UNUSED(userRenderParams))
return !mmd->object;
}
-static void updateDepsgraph(GpencilModifierData *md, const ModifierUpdateDepsgraphContext *ctx)
+static void updateDepsgraph(GpencilModifierData *md,
+ const ModifierUpdateDepsgraphContext *ctx,
+ const int UNUSED(mode))
{
HookGpencilModifierData *lmd = (HookGpencilModifierData *)md;
if (lmd->object != NULL) {
diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencillattice.c b/source/blender/gpencil_modifiers/intern/MOD_gpencillattice.c
index ff493258b9b..7d78892aa6b 100644
--- a/source/blender/gpencil_modifiers/intern/MOD_gpencillattice.c
+++ b/source/blender/gpencil_modifiers/intern/MOD_gpencillattice.c
@@ -195,7 +195,9 @@ static bool isDisabled(GpencilModifierData *md, int UNUSED(userRenderParams))
return !mmd->object || mmd->object->type != OB_LATTICE;
}
-static void updateDepsgraph(GpencilModifierData *md, const ModifierUpdateDepsgraphContext *ctx)
+static void updateDepsgraph(GpencilModifierData *md,
+ const ModifierUpdateDepsgraphContext *ctx,
+ const int UNUSED(mode))
{
LatticeGpencilModifierData *lmd = (LatticeGpencilModifierData *)md;
if (lmd->object != NULL) {
diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c b/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c
new file mode 100644
index 00000000000..7fed771dabe
--- /dev/null
+++ b/source/blender/gpencil_modifiers/intern/MOD_gpencillineart.c
@@ -0,0 +1,477 @@
+/*
+ * 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) 2017, Blender Foundation
+ * This is a new part of Blender
+ */
+
+/** \file
+ * \ingroup modifiers
+ */
+
+#include <stdio.h>
+
+#include "BLI_utildefines.h"
+
+#include "BLI_blenlib.h"
+#include "BLI_math_vector.h"
+
+#include "BLT_translation.h"
+
+#include "DNA_collection_types.h"
+#include "DNA_defaults.h"
+#include "DNA_gpencil_modifier_types.h"
+#include "DNA_gpencil_types.h"
+#include "DNA_lineart_types.h"
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+#include "DNA_screen_types.h"
+
+#include "lineart/MOD_lineart.h"
+
+#include "BKE_collection.h"
+#include "BKE_context.h"
+#include "BKE_global.h"
+#include "BKE_gpencil.h"
+#include "BKE_gpencil_modifier.h"
+#include "BKE_lib_query.h"
+#include "BKE_main.h"
+#include "BKE_material.h"
+#include "BKE_screen.h"
+
+#include "UI_interface.h"
+#include "UI_resources.h"
+
+#include "BKE_modifier.h"
+#include "RNA_access.h"
+
+#include "DEG_depsgraph.h"
+#include "DEG_depsgraph_query.h"
+
+#include "MOD_gpencil_modifiertypes.h"
+#include "MOD_gpencil_ui_common.h"
+#include "MOD_gpencil_util.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+static void initData(GpencilModifierData *md)
+{
+ LineartGpencilModifierData *gpmd = (LineartGpencilModifierData *)md;
+
+ BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(gpmd, modifier));
+
+ MEMCPY_STRUCT_AFTER(gpmd, DNA_struct_default_get(LineartGpencilModifierData), modifier);
+}
+
+static void copyData(const GpencilModifierData *md, GpencilModifierData *target)
+{
+ BKE_gpencil_modifier_copydata_generic(md, target);
+}
+
+static void generate_strokes_actual(
+ GpencilModifierData *md, Depsgraph *depsgraph, Object *ob, bGPDlayer *gpl, bGPDframe *gpf)
+{
+ LineartGpencilModifierData *lmd = (LineartGpencilModifierData *)md;
+
+ if (G.debug_value == 4000) {
+ printf("LRT: Generating from modifier.\n");
+ }
+
+ MOD_lineart_gpencil_generate(
+ lmd->render_buffer,
+ depsgraph,
+ ob,
+ gpl,
+ gpf,
+ lmd->source_type,
+ lmd->source_type == LRT_SOURCE_OBJECT ? (void *)lmd->source_object :
+ (void *)lmd->source_collection,
+ lmd->level_start,
+ lmd->use_multiple_levels ? lmd->level_end : lmd->level_start,
+ lmd->target_material ? BKE_gpencil_object_material_index_get(ob, lmd->target_material) : 0,
+ lmd->line_types,
+ lmd->transparency_flags,
+ lmd->transparency_mask,
+ lmd->thickness,
+ lmd->opacity,
+ lmd->pre_sample_length,
+ lmd->source_vertex_group,
+ lmd->vgname,
+ lmd->flags);
+}
+
+static bool isModifierDisabled(GpencilModifierData *md)
+{
+ LineartGpencilModifierData *lmd = (LineartGpencilModifierData *)md;
+
+ if ((lmd->target_layer[0] == '\0') || (lmd->target_material == NULL)) {
+ return true;
+ }
+
+ if (lmd->source_type == LRT_SOURCE_OBJECT && !lmd->source_object) {
+ return true;
+ }
+
+ if (lmd->source_type == LRT_SOURCE_COLLECTION && !lmd->source_collection) {
+ return true;
+ }
+
+ /* Preventing calculation in depsgraph when baking frames. */
+ if (lmd->flags & LRT_GPENCIL_IS_BAKED) {
+ return true;
+ }
+
+ return false;
+}
+static void generateStrokes(GpencilModifierData *md, Depsgraph *depsgraph, Object *ob)
+{
+ LineartGpencilModifierData *lmd = (LineartGpencilModifierData *)md;
+ bGPdata *gpd = ob->data;
+
+ /* Guard early, don't trigger calculation when no gpencil frame is present. Probably should
+ * disable in the isModifierDisabled() function but we need addtional arg for depsgraph and
+ * gpd. */
+ bGPDlayer *gpl = BKE_gpencil_layer_get_by_name(gpd, lmd->target_layer, 1);
+ if (gpl == NULL) {
+ return;
+ }
+ /* Need to call this or we don't get active frame (user may haven't selected any one). */
+ BKE_gpencil_frame_active_set(depsgraph, gpd);
+ bGPDframe *gpf = gpl->actframe;
+ if (gpf == NULL) {
+ BKE_gpencil_frame_addnew(gpl, DEG_get_evaluated_scene(depsgraph)->r.cfra);
+ return;
+ }
+
+ /* Check all parameters required are filled. */
+ if (isModifierDisabled(md)) {
+ return;
+ }
+
+ MOD_lineart_compute_feature_lines(depsgraph, lmd);
+
+ generate_strokes_actual(md, depsgraph, ob, gpl, gpf);
+
+ MOD_lineart_destroy_render_data(lmd);
+
+ WM_main_add_notifier(NA_EDITED | NC_GPENCIL, NULL);
+}
+
+static void bakeModifier(Main *UNUSED(bmain),
+ Depsgraph *depsgraph,
+ GpencilModifierData *md,
+ Object *ob)
+{
+ bGPdata *gpd = ob->data;
+ LineartGpencilModifierData *lmd = (LineartGpencilModifierData *)md;
+
+ bGPDlayer *gpl = BKE_gpencil_layer_get_by_name(gpd, lmd->target_layer, 1);
+ if (gpl == NULL) {
+ return;
+ }
+ bGPDframe *gpf = gpl->actframe;
+ if (gpf == NULL) {
+ return;
+ }
+
+ MOD_lineart_compute_feature_lines(depsgraph, lmd);
+
+ generate_strokes_actual(md, depsgraph, ob, gpl, gpf);
+
+ MOD_lineart_destroy_render_data(lmd);
+}
+
+static bool isDisabled(GpencilModifierData *md, int UNUSED(userRenderParams))
+{
+ return isModifierDisabled(md);
+}
+
+static void updateDepsgraph(GpencilModifierData *md,
+ const ModifierUpdateDepsgraphContext *ctx,
+ const int mode)
+{
+ DEG_add_object_relation(ctx->node, ctx->object, DEG_OB_COMP_TRANSFORM, "Line Art Modifier");
+
+ LineartGpencilModifierData *lmd = (LineartGpencilModifierData *)md;
+ if (lmd->source_type == LRT_SOURCE_OBJECT && lmd->source_object) {
+ DEG_add_object_relation(
+ ctx->node, lmd->source_object, DEG_OB_COMP_GEOMETRY, "Line Art Modifier");
+ DEG_add_object_relation(
+ ctx->node, lmd->source_object, DEG_OB_COMP_TRANSFORM, "Line Art Modifier");
+ }
+ else {
+ FOREACH_COLLECTION_VISIBLE_OBJECT_RECURSIVE_BEGIN (ctx->scene->master_collection, ob, mode) {
+ if (ob->type == OB_MESH || ob->type == OB_MBALL || ob->type == OB_CURVE ||
+ ob->type == OB_SURF || ob->type == OB_FONT) {
+ if (!(ob->lineart.usage & COLLECTION_LRT_EXCLUDE)) {
+ DEG_add_object_relation(ctx->node, ob, DEG_OB_COMP_GEOMETRY, "Line Art Modifier");
+ DEG_add_object_relation(ctx->node, ob, DEG_OB_COMP_TRANSFORM, "Line Art Modifier");
+ }
+ }
+ }
+ FOREACH_COLLECTION_VISIBLE_OBJECT_RECURSIVE_END;
+ }
+ DEG_add_object_relation(
+ ctx->node, ctx->scene->camera, DEG_OB_COMP_TRANSFORM, "Line Art Modifier");
+}
+
+static void foreachIDLink(GpencilModifierData *md, Object *ob, IDWalkFunc walk, void *userData)
+{
+ LineartGpencilModifierData *lmd = (LineartGpencilModifierData *)md;
+
+ walk(userData, ob, (ID **)&lmd->target_material, IDWALK_CB_USER);
+ walk(userData, ob, (ID **)&lmd->source_collection, IDWALK_CB_NOP);
+
+ walk(userData, ob, (ID **)&lmd->source_object, IDWALK_CB_NOP);
+}
+
+static void panel_draw(const bContext *UNUSED(C), Panel *panel)
+{
+ uiLayout *layout = panel->layout;
+
+ PointerRNA ob_ptr;
+ PointerRNA *ptr = gpencil_modifier_panel_get_property_pointers(panel, &ob_ptr);
+
+ PointerRNA obj_data_ptr = RNA_pointer_get(&ob_ptr, "data");
+
+ const int source_type = RNA_enum_get(ptr, "source_type");
+ const bool is_baked = RNA_boolean_get(ptr, "is_baked");
+
+ uiLayoutSetPropSep(layout, true);
+ uiLayoutSetEnabled(layout, !is_baked);
+
+ uiItemR(layout, ptr, "source_type", 0, NULL, ICON_NONE);
+
+ if (source_type == LRT_SOURCE_OBJECT) {
+ uiItemR(layout, ptr, "source_object", 0, NULL, ICON_OBJECT_DATA);
+ }
+ else if (source_type == LRT_SOURCE_COLLECTION) {
+ uiItemR(layout, ptr, "source_collection", 0, NULL, ICON_OUTLINER_COLLECTION);
+ }
+ else {
+ /* Source is Scene. */
+ }
+
+ uiLayout *col = uiLayoutColumnWithHeading(layout, true, IFACE_("Edge Types"));
+
+ uiItemR(col, ptr, "use_contour", 0, "Contour", ICON_NONE);
+ uiItemR(col, ptr, "use_material", 0, "Material", ICON_NONE);
+ uiItemR(col, ptr, "use_edge_mark", 0, "Edge Marks", ICON_NONE);
+ uiItemR(col, ptr, "use_intersection", 0, "Intersection", ICON_NONE);
+
+ uiLayout *row = uiLayoutRow(col, true);
+ /* Don't automatically add the "animate property" button. */
+ uiLayoutSetPropDecorate(row, false);
+
+ const bool use_crease = RNA_boolean_get(ptr, "use_crease");
+ uiLayout *sub = uiLayoutRow(row, true);
+ uiItemR(sub, ptr, "use_crease", 0, "Crease", ICON_NONE);
+ sub = uiLayoutRow(sub, true);
+ uiLayoutSetEnabled(sub, use_crease);
+ uiItemR(sub, ptr, "crease_threshold", UI_ITEM_R_SLIDER, "", ICON_NONE);
+ uiItemDecoratorR(row, ptr, "crease_threshold", 0);
+
+ uiItemPointerR(layout, ptr, "target_layer", &obj_data_ptr, "layers", NULL, ICON_GREASEPENCIL);
+ uiItemPointerR(
+ layout, ptr, "target_material", &obj_data_ptr, "materials", NULL, ICON_SHADING_TEXTURE);
+
+ uiItemR(layout, ptr, "remove_doubles", 0, NULL, ICON_NONE);
+ uiItemR(layout, ptr, "allow_overlapping_edges", 0, "Overlapping Edges As Contour", ICON_NONE);
+
+ gpencil_modifier_panel_end(layout, ptr);
+}
+
+static void style_panel_draw(const bContext *UNUSED(C), Panel *panel)
+{
+ PointerRNA *ptr = gpencil_modifier_panel_get_property_pointers(panel, NULL);
+
+ uiLayout *layout = panel->layout;
+
+ const bool is_baked = RNA_boolean_get(ptr, "is_baked");
+
+ uiLayoutSetPropSep(layout, true);
+ uiLayoutSetEnabled(layout, !is_baked);
+
+ uiItemR(layout, ptr, "thickness", UI_ITEM_R_SLIDER, NULL, ICON_NONE);
+
+ uiItemR(layout, ptr, "opacity", UI_ITEM_R_SLIDER, NULL, ICON_NONE);
+}
+
+static void occlusion_panel_draw(const bContext *UNUSED(C), Panel *panel)
+{
+ PointerRNA *ptr = gpencil_modifier_panel_get_property_pointers(panel, NULL);
+
+ uiLayout *layout = panel->layout;
+
+ const bool is_baked = RNA_boolean_get(ptr, "is_baked");
+
+ uiLayoutSetPropSep(layout, true);
+ uiLayoutSetEnabled(layout, !is_baked);
+
+ const bool use_multiple_levels = RNA_boolean_get(ptr, "use_multiple_levels");
+ const bool use_transparency = RNA_boolean_get(ptr, "use_transparency");
+
+ uiItemR(layout, ptr, "use_multiple_levels", 0, "Multiple Levels", ICON_NONE);
+
+ if (use_multiple_levels) {
+ uiLayout *col = uiLayoutColumn(layout, true);
+ uiItemR(col, ptr, "level_start", 0, NULL, ICON_NONE);
+ uiItemR(col, ptr, "level_end", 0, NULL, ICON_NONE);
+ }
+ else {
+ uiItemR(layout, ptr, "level_start", 0, "Level", ICON_NONE);
+ }
+
+ uiItemR(layout, ptr, "use_transparency", 0, "Transparency", ICON_NONE);
+
+ uiLayout *col = uiLayoutColumn(layout, true);
+
+ if (use_transparency) {
+ uiItemR(col, ptr, "transparency_match", 0, "Match", ICON_NONE);
+ }
+
+ if (use_transparency) {
+ uiLayout *row = uiLayoutRow(col, true);
+ uiItemR(row, ptr, "transparency_mask_0", UI_ITEM_R_TOGGLE, "0", ICON_NONE);
+ uiItemR(row, ptr, "transparency_mask_1", UI_ITEM_R_TOGGLE, "1", ICON_NONE);
+ uiItemR(row, ptr, "transparency_mask_2", UI_ITEM_R_TOGGLE, "2", ICON_NONE);
+ uiItemR(row, ptr, "transparency_mask_3", UI_ITEM_R_TOGGLE, "3", ICON_NONE);
+ uiItemR(row, ptr, "transparency_mask_4", UI_ITEM_R_TOGGLE, "4", ICON_NONE);
+ uiItemR(row, ptr, "transparency_mask_5", UI_ITEM_R_TOGGLE, "5", ICON_NONE);
+ uiItemR(row, ptr, "transparency_mask_6", UI_ITEM_R_TOGGLE, "6", ICON_NONE);
+ uiItemR(row, ptr, "transparency_mask_7", UI_ITEM_R_TOGGLE, "7", ICON_NONE);
+ }
+}
+
+static void chaining_panel_draw(const bContext *UNUSED(C), Panel *panel)
+{
+ PointerRNA *ptr = gpencil_modifier_panel_get_property_pointers(panel, NULL);
+
+ uiLayout *layout = panel->layout;
+
+ const bool is_baked = RNA_boolean_get(ptr, "is_baked");
+
+ uiLayoutSetPropSep(layout, true);
+ uiLayoutSetEnabled(layout, !is_baked);
+
+ uiLayout *col = uiLayoutColumnWithHeading(layout, true, IFACE_("Chain"));
+ uiItemR(col, ptr, "fuzzy_intersections", 0, NULL, ICON_NONE);
+ uiItemR(col, ptr, "fuzzy_everything", 0, NULL, ICON_NONE);
+
+ col = uiLayoutColumn(layout, true);
+ uiItemR(col, ptr, "chaining_geometry_threshold", 0, NULL, ICON_NONE);
+ uiItemR(col, ptr, "chaining_image_threshold", 0, NULL, ICON_NONE);
+
+ uiItemR(layout, ptr, "pre_sample_length", UI_ITEM_R_SLIDER, NULL, ICON_NONE);
+
+ uiItemR(layout, ptr, "angle_splitting_threshold", UI_ITEM_R_SLIDER, NULL, ICON_NONE);
+}
+
+static void vgroup_panel_draw(const bContext *UNUSED(C), Panel *panel)
+{
+ PointerRNA ob_ptr;
+ PointerRNA *ptr = gpencil_modifier_panel_get_property_pointers(panel, &ob_ptr);
+
+ uiLayout *layout = panel->layout;
+
+ bool is_baked = RNA_boolean_get(ptr, "is_baked");
+
+ uiLayoutSetPropSep(layout, true);
+ uiLayoutSetEnabled(layout, !is_baked);
+
+ uiLayout *col = uiLayoutColumn(layout, true);
+
+ uiLayout *row = uiLayoutRow(col, true);
+ uiItemR(row, ptr, "source_vertex_group", 0, "Filter Source", ICON_GROUP_VERTEX);
+ uiItemR(row, ptr, "invert_source_vertex_group", UI_ITEM_R_TOGGLE, "", ICON_ARROW_LEFTRIGHT);
+
+ uiItemR(col, ptr, "match_output_vertex_group", 0, NULL, ICON_NONE);
+
+ bool match_output = RNA_boolean_get(ptr, "match_output_vertex_group");
+ if (!match_output) {
+ uiItemPointerR(col, ptr, "vertex_group", &ob_ptr, "vertex_groups", "Target", ICON_NONE);
+ }
+
+ uiItemR(layout, ptr, "soft_selection", 0, NULL, ICON_NONE);
+}
+
+static void baking_panel_draw(const bContext *UNUSED(C), Panel *panel)
+{
+ uiLayout *layout = panel->layout;
+ PointerRNA ob_ptr;
+ PointerRNA *ptr = gpencil_modifier_panel_get_property_pointers(panel, &ob_ptr);
+
+ bool is_baked = RNA_boolean_get(ptr, "is_baked");
+
+ uiLayoutSetPropSep(layout, true);
+
+ if (is_baked) {
+ uiLayout *col = uiLayoutColumn(layout, false);
+ uiLayoutSetPropSep(col, false);
+ uiItemL(col, "Modifier has baked data.", ICON_NONE);
+ uiItemR(col, ptr, "is_baked", UI_ITEM_R_TOGGLE, "Continue Without Clearing", ICON_NONE);
+ }
+
+ uiLayout *col = uiLayoutColumn(layout, false);
+ uiLayoutSetEnabled(col, !is_baked);
+ uiItemO(col, NULL, ICON_NONE, "OBJECT_OT_lineart_bake_strokes");
+ uiItemO(col, NULL, ICON_NONE, "OBJECT_OT_lineart_bake_strokes_all");
+
+ col = uiLayoutColumn(layout, false);
+ uiItemO(col, NULL, ICON_NONE, "OBJECT_OT_lineart_clear");
+ uiItemO(col, NULL, ICON_NONE, "OBJECT_OT_lineart_clear_all");
+}
+
+static void panelRegister(ARegionType *region_type)
+{
+ PanelType *panel_type = gpencil_modifier_panel_register(
+ region_type, eGpencilModifierType_Lineart, panel_draw);
+
+ gpencil_modifier_subpanel_register(
+ region_type, "style", "Style", NULL, style_panel_draw, panel_type);
+ gpencil_modifier_subpanel_register(
+ region_type, "occlusion", "Occlusion", NULL, occlusion_panel_draw, panel_type);
+ gpencil_modifier_subpanel_register(
+ region_type, "chaining", "Chaining", NULL, chaining_panel_draw, panel_type);
+ gpencil_modifier_subpanel_register(
+ region_type, "vgroup", "Vertex Weight Transfer", NULL, vgroup_panel_draw, panel_type);
+ gpencil_modifier_subpanel_register(
+ region_type, "baking", "Baking", NULL, baking_panel_draw, panel_type);
+}
+
+GpencilModifierTypeInfo modifierType_Gpencil_Lineart = {
+ /* name. */ "Line Art",
+ /* structName. */ "LineartGpencilModifierData",
+ /* structSize. */ sizeof(LineartGpencilModifierData),
+ /* type. */ eGpencilModifierTypeType_Gpencil,
+ /* flags. */ eGpencilModifierTypeFlag_SupportsEditmode,
+
+ /* copyData. */ copyData,
+
+ /* deformStroke. */ NULL,
+ /* generateStrokes. */ generateStrokes,
+ /* bakeModifier. */ bakeModifier,
+ /* remapTime. */ NULL,
+
+ /* initData. */ initData,
+ /* freeData. */ NULL,
+ /* isDisabled. */ isDisabled,
+ /* updateDepsgraph. */ updateDepsgraph,
+ /* dependsOnTime. */ NULL,
+ /* foreachIDLink. */ foreachIDLink,
+ /* foreachTexLink. */ NULL,
+ /* panelRegister. */ panelRegister,
+};
diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpencilmirror.c b/source/blender/gpencil_modifiers/intern/MOD_gpencilmirror.c
index 111c60436bf..eec4eab3441 100644
--- a/source/blender/gpencil_modifiers/intern/MOD_gpencilmirror.c
+++ b/source/blender/gpencil_modifiers/intern/MOD_gpencilmirror.c
@@ -207,7 +207,9 @@ static bool isDisabled(GpencilModifierData *UNUSED(md), int UNUSED(userRenderPar
return false;
}
-static void updateDepsgraph(GpencilModifierData *md, const ModifierUpdateDepsgraphContext *ctx)
+static void updateDepsgraph(GpencilModifierData *md,
+ const ModifierUpdateDepsgraphContext *ctx,
+ const int UNUSED(mode))
{
MirrorGpencilModifierData *lmd = (MirrorGpencilModifierData *)md;
if (lmd->object != NULL) {
diff --git a/source/blender/gpencil_modifiers/intern/MOD_gpenciltint.c b/source/blender/gpencil_modifiers/intern/MOD_gpenciltint.c
index 311d08238b9..fcc0a1f35eb 100644
--- a/source/blender/gpencil_modifiers/intern/MOD_gpenciltint.c
+++ b/source/blender/gpencil_modifiers/intern/MOD_gpenciltint.c
@@ -311,7 +311,9 @@ static bool isDisabled(GpencilModifierData *md, int UNUSED(userRenderParams))
return !mmd->object;
}
-static void updateDepsgraph(GpencilModifierData *md, const ModifierUpdateDepsgraphContext *ctx)
+static void updateDepsgraph(GpencilModifierData *md,
+ const ModifierUpdateDepsgraphContext *ctx,
+ const int UNUSED(mode))
{
TintGpencilModifierData *lmd = (TintGpencilModifierData *)md;
if (lmd->object != NULL) {
diff --git a/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h b/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h
new file mode 100644
index 00000000000..7519f3ef40f
--- /dev/null
+++ b/source/blender/gpencil_modifiers/intern/lineart/MOD_lineart.h
@@ -0,0 +1,552 @@
+/*
+ * 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) 2008 Blender Foundation.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup editors
+ */
+
+#pragma once
+
+#include "BLI_linklist.h"
+#include "BLI_listbase.h"
+#include "BLI_math.h"
+#include "BLI_threads.h"
+
+#include "DNA_lineart_types.h"
+
+#include <math.h>
+#include <string.h>
+
+typedef struct LineartStaticMemPoolNode {
+ Link item;
+ size_t size;
+ size_t used_byte;
+ /* User memory starts here */
+} LineartStaticMemPoolNode;
+
+typedef struct LineartStaticMemPool {
+ ListBase pools;
+ SpinLock lock_mem;
+} LineartStaticMemPool;
+
+typedef struct LineartRenderTriangleAdjacent {
+ struct LineartRenderLine *rl[3];
+} LineartRenderTriangleAdjacent;
+
+typedef struct LineartRenderTriangle {
+ struct LineartRenderVert *v[3];
+
+ /* first culled in line list to use adjacent triangle info, then go through triangle list. */
+ double gn[3];
+
+ /* Material flag is removed to save space. */
+ unsigned char transparency_mask;
+ unsigned char flags; /* eLineartTriangleFlags */
+
+ /* Only use single link list, because we don't need to go back in order.
+ * This variable is also reused to store the pointer to adjacent lines of this triangle before
+ * intersection statge */
+ struct LinkNode *intersecting_verts;
+} LineartRenderTriangle;
+
+typedef struct LineartRenderTriangleThread {
+ struct LineartRenderTriangle base;
+ /* This variable is used to store per-thread triangle-line testing pair,
+ * also re-used to store triangle-triangle pair for intersection testing stage.
+ * Do not directly use LineartRenderTriangleThread.
+ * The size of LineartRenderTriangle is dynamically allocated to contain set thread number of
+ * "testing" field. Worker threads will test lines against the "base" triangle.
+ * At least one thread is present, thus we always have at least testing[0]. */
+ struct LineartRenderLine *testing[1];
+} LineartRenderTriangleThread;
+
+typedef enum eLineArtElementNodeFlag {
+ LRT_ELEMENT_IS_ADDITIONAL = (1 << 0),
+ LRT_ELEMENT_BORDER_ONLY = (1 << 1),
+ LRT_ELEMENT_NO_INTERSECTION = (1 << 2),
+} eLineArtElementNodeFlag;
+
+typedef struct LineartRenderElementLinkNode {
+ struct LineartRenderElementLinkNode *next, *prev;
+ void *pointer;
+ int element_count;
+ void *object_ref;
+ eLineArtElementNodeFlag flags;
+
+ /* Per object value, always set, if not enabled by ObjectLineArt, then it's set to global. */
+ float crease_threshold;
+} LineartRenderElementLinkNode;
+
+typedef struct LineartRenderLineSegment {
+ struct LineartRenderLineSegment *next, *prev;
+ /** at==0: left at==1: right (this is in 2D projected space) */
+ double at;
+ /** Occlusion level after "at" point */
+ unsigned char occlusion;
+
+ /** For determining lines beind a glass window material.
+ * the size of this variable should also be dynamically decided, 1 byte to 8 byte,
+ * allows 8 to 64 materials for "transparent mask". 1 byte (8 materials) should be
+ * enought for most cases.
+ */
+ unsigned char transparency_mask;
+} LineartRenderLineSegment;
+
+typedef struct LineartRenderVert {
+ double gloc[3];
+ double fbcoord[4];
+
+ /* Scene global index. */
+ int index;
+
+ /** Intersection data flag is here, when LRT_VERT_HAS_INTERSECTION_DATA is set,
+ * size of the struct is extended to include intersection data.
+ * See eLineArtVertFlags.
+ */
+ char flag;
+
+} LineartRenderVert;
+
+typedef struct LineartRenderVertIntersection {
+ struct LineartRenderVert base;
+ /* Use vert index because we only use this to check vertex equal. This way we save 8 Bytes. */
+ int isec1, isec2;
+ struct LineartRenderTriangle *intersecting_with;
+} LineartRenderVertIntersection;
+
+typedef enum eLineArtVertFlags {
+ LRT_VERT_HAS_INTERSECTION_DATA = (1 << 0),
+ LRT_VERT_EDGE_USED = (1 << 1),
+} eLineArtVertFlags;
+
+typedef struct LineartRenderLine {
+ /* We only need link node kind of list here. */
+ struct LineartRenderLine *next;
+ struct LineartRenderVert *l, *r;
+ /* Local vertex index for two ends, not puting in RenderVert because all verts are loaded, so as
+ * long as fewer than half of the mesh edges are becoming a feature line, we save more memory. */
+ int l_obindex, r_obindex;
+ struct LineartRenderTriangle *tl, *tr;
+ ListBase segments;
+ char min_occ;
+
+ /** Also for line type determination on chainning */
+ unsigned char flags;
+
+ /** Still need this entry because culled lines will not add to object reln node,
+ * TODO: If really need more savings, we can allocate this in a "extended" way too, but we need
+ * another bit in flags to be able to show the difference.
+ */
+ struct Object *object_ref;
+} LineartRenderLine;
+
+typedef struct LineartRenderLineChain {
+ struct LineartRenderLineChain *next, *prev;
+ ListBase chain;
+
+ /** Calculated before draw cmd. */
+ float length;
+
+ /** Used when re-connecting and gp stroke generation */
+ char picked;
+ char level;
+
+ /** Chain now only contains one type of segments */
+ int type;
+ unsigned char transparency_mask;
+
+ struct Object *object_ref;
+} LineartRenderLineChain;
+
+typedef struct LineartRenderLineChainItem {
+ struct LineartRenderLineChainItem *next, *prev;
+ /** Need z value for fading */
+ float pos[3];
+ /** For restoring position to 3d space */
+ float gpos[3];
+ float normal[3];
+ char line_type;
+ char occlusion;
+ unsigned char transparency_mask;
+ size_t index;
+} LineartRenderLineChainItem;
+
+typedef struct LineartChainRegisterEntry {
+ struct LineartChainRegisterEntry *next, *prev;
+ LineartRenderLineChain *rlc;
+ LineartRenderLineChainItem *rlci;
+ char picked;
+
+ /** left/right mark.
+ * Because we revert list in chaining so we need the flag. */
+ char is_left;
+} LineartChainRegisterEntry;
+
+typedef struct LineartRenderBuffer {
+ struct LineartRenderBuffer *prev, *next;
+
+ int thread_count;
+
+ int w, h;
+ int tile_size_w, tile_size_h;
+ int tile_count_x, tile_count_y;
+ double width_per_tile, height_per_tile;
+ double view_projection[4][4];
+
+ struct LineartBoundingArea *initial_bounding_areas;
+ unsigned int bounding_area_count;
+
+ ListBase vertex_buffer_pointers;
+ ListBase line_buffer_pointers;
+ ListBase triangle_buffer_pointers;
+
+ /* This one's memory is not from main pool and is free()ed after culling stage. */
+ ListBase triangle_adjacent_pointers;
+
+ ListBase intersecting_vertex_buffer;
+ /** Use the one comes with Line Art. */
+ LineartStaticMemPool render_data_pool;
+ ListBase wasted_cuts;
+ SpinLock lock_cuts;
+
+ /* Render status */
+ double view_vector[3];
+
+ int triangle_size;
+
+ unsigned int contour_count;
+ unsigned int contour_processed;
+ LineartRenderLine *contour_managed;
+ /* Now changed to linknodes. */
+ LineartRenderLine *contours;
+
+ unsigned int intersection_count;
+ unsigned int intersection_processed;
+ LineartRenderLine *intersection_managed;
+ LineartRenderLine *intersection_lines;
+
+ unsigned int crease_count;
+ unsigned int crease_processed;
+ LineartRenderLine *crease_managed;
+ LineartRenderLine *crease_lines;
+
+ unsigned int material_line_count;
+ unsigned int material_processed;
+ LineartRenderLine *material_managed;
+ LineartRenderLine *material_lines;
+
+ unsigned int edge_mark_count;
+ unsigned int edge_mark_processed;
+ LineartRenderLine *edge_mark_managed;
+ LineartRenderLine *edge_marks;
+
+ ListBase chains;
+
+ /** For managing calculation tasks for multiple threads. */
+ SpinLock lock_task;
+
+ /* settings */
+
+ int max_occlusion_level;
+ double crease_angle;
+ double crease_cos;
+
+ int draw_material_preview;
+ double material_transparency;
+
+ bool use_contour;
+ bool use_crease;
+ bool use_material;
+ bool use_edge_marks;
+ bool use_intersections;
+ bool fuzzy_intersections;
+ bool fuzzy_everything;
+ bool allow_boundaries;
+ bool allow_overlapping_edges;
+ bool remove_doubles;
+
+ /** Keep an copy of these data so when line art is running it's self-contained. */
+ bool cam_is_persp;
+ float cam_obmat[4][4];
+ double camera_pos[3];
+ double near_clip, far_clip;
+ float shift_x, shift_y;
+ float crease_threshold;
+ float chaining_image_threshold;
+ float chaining_geometry_threshold;
+ float angle_splitting_threshold;
+} LineartRenderBuffer;
+
+#define DBL_TRIANGLE_LIM 1e-8
+#define DBL_EDGE_LIM 1e-9
+
+#define LRT_MEMORY_POOL_64MB (1 << 26)
+
+typedef enum eLineartTriangleFlags {
+ LRT_CULL_DONT_CARE = 0,
+ LRT_CULL_USED = (1 << 0),
+ LRT_CULL_DISCARD = (1 << 1),
+ LRT_CULL_GENERATED = (1 << 2),
+ LRT_TRIANGLE_INTERSECTION_ONLY = (1 << 3),
+ LRT_TRIANGLE_NO_INTERSECTION = (1 << 4),
+} eLineartTriangleFlags;
+
+/** Controls how many lines a worker thread is processing at one request.
+ * There's no significant performance impact on choosing different values.
+ * Don't make it too small so that the worker thread won't request too many times. */
+#define LRT_THREAD_LINE_COUNT 1000
+
+typedef struct LineartRenderTaskInfo {
+ struct LineartRenderBuffer *rb;
+
+ int thread_id;
+
+ LineartRenderLine *contour;
+ LineartRenderLine *contour_end;
+
+ LineartRenderLine *intersection;
+ LineartRenderLine *intersection_end;
+
+ LineartRenderLine *crease;
+ LineartRenderLine *crease_end;
+
+ LineartRenderLine *material;
+ LineartRenderLine *material_end;
+
+ LineartRenderLine *edge_mark;
+ LineartRenderLine *edge_mark_end;
+
+} LineartRenderTaskInfo;
+
+/** Bounding area diagram:
+ *
+ * +----+ <----U (Upper edge Y value)
+ * | |
+ * +----+ <----B (Bottom edge Y value)
+ * ^ ^
+ * L R (Left/Right edge X value)
+ *
+ * Example structure when subdividing 1 bounding areas:
+ * 1 area can be divided into 4 smaller children to
+ * accomodate image areas with denser triangle distribution.
+ * +--+--+-----+
+ * +--+--+ |
+ * +--+--+-----+
+ * | | |
+ * +-----+-----+
+ * lp/rp/up/bp is the list for
+ * storing pointers to adjacent bounding areas.
+ */
+typedef struct LineartBoundingArea {
+ double l, r, u, b;
+ double cx, cy;
+
+ /** 1,2,3,4 quadrant */
+ struct LineartBoundingArea *child;
+
+ ListBase lp;
+ ListBase rp;
+ ListBase up;
+ ListBase bp;
+
+ short triangle_count;
+
+ ListBase linked_triangles;
+ ListBase linked_lines;
+
+ /** Reserved for image space reduction && multithread chainning */
+ ListBase linked_chains;
+} LineartBoundingArea;
+
+#define LRT_TILE(tile, r, c, CCount) tile[r * CCount + c]
+
+#define LRT_CLAMP(a, Min, Max) a = a < Min ? Min : (a > Max ? Max : a)
+
+#define LRT_MAX3_INDEX(a, b, c) (a > b ? (a > c ? 0 : (b > c ? 1 : 2)) : (b > c ? 1 : 2))
+
+#define LRT_MIN3_INDEX(a, b, c) (a < b ? (a < c ? 0 : (b < c ? 1 : 2)) : (b < c ? 1 : 2))
+
+#define LRT_MAX3_INDEX_ABC(x, y, z) (x > y ? (x > z ? a : (y > z ? b : c)) : (y > z ? b : c))
+
+#define LRT_MIN3_INDEX_ABC(x, y, z) (x < y ? (x < z ? a : (y < z ? b : c)) : (y < z ? b : c))
+
+#define LRT_ABC(index) (index == 0 ? a : (index == 1 ? b : c))
+
+#define LRT_DOUBLE_CLOSE_ENOUGH(a, b) (((a) + DBL_EDGE_LIM) >= (b) && ((a)-DBL_EDGE_LIM) <= (b))
+
+BLI_INLINE int lineart_LineIntersectTest2d(
+ const double *a1, const double *a2, const double *b1, const double *b2, double *aRatio)
+{
+#define USE_VECTOR_LINE_INTERSECTION
+#ifdef USE_VECTOR_LINE_INTERSECTION
+
+ /* from isect_line_line_v2_point() */
+
+ double s10[2], s32[2];
+ double div;
+
+ sub_v2_v2v2_db(s10, a2, a1);
+ sub_v2_v2v2_db(s32, b2, b1);
+
+ div = cross_v2v2_db(s10, s32);
+ if (div != 0.0f) {
+ const double u = cross_v2v2_db(a2, a1);
+ const double v = cross_v2v2_db(b2, b1);
+
+ const double rx = ((s32[0] * u) - (s10[0] * v)) / div;
+ const double ry = ((s32[1] * u) - (s10[1] * v)) / div;
+ double rr;
+
+ if (fabs(a2[0] - a1[0]) > fabs(a2[1] - a1[1])) {
+ *aRatio = ratiod(a1[0], a2[0], rx);
+ if (fabs(b2[0] - b1[0]) > fabs(b2[1] - b1[1])) {
+ rr = ratiod(b1[0], b2[0], rx);
+ }
+ else {
+ rr = ratiod(b1[1], b2[1], ry);
+ }
+ if ((*aRatio) > 0 && (*aRatio) < 1 && rr > 0 && rr < 1) {
+ return 1;
+ }
+ return 0;
+ }
+
+ *aRatio = ratiod(a1[1], a2[1], ry);
+ if (fabs(b2[0] - b1[0]) > fabs(b2[1] - b1[1])) {
+ rr = ratiod(b1[0], b2[0], rx);
+ }
+ else {
+ rr = ratiod(b1[1], b2[1], ry);
+ }
+ if ((*aRatio) > 0 && (*aRatio) < 1 && rr > 0 && rr < 1) {
+ return 1;
+ }
+ return 0;
+ }
+ return 0;
+
+#else
+ double k1, k2;
+ double x;
+ double y;
+ double ratio;
+ double x_diff = (a2[0] - a1[0]);
+ double x_diff2 = (b2[0] - b1[0]);
+
+ if (LRT_DOUBLE_CLOSE_ENOUGH(x_diff, 0)) {
+ if (LRT_DOUBLE_CLOSE_ENOUGH(x_diff2, 0)) {
+ *aRatio = 0;
+ return 0;
+ }
+ double r2 = ratiod(b1[0], b2[0], a1[0]);
+ x = interpd(b2[0], b1[0], r2);
+ y = interpd(b2[1], b1[1], r2);
+ *aRatio = ratio = ratiod(a1[1], a2[1], y);
+ }
+ else {
+ if (LRT_DOUBLE_CLOSE_ENOUGH(x_diff2, 0)) {
+ ratio = ratiod(a1[0], a2[0], b1[0]);
+ x = interpd(a2[0], a1[0], ratio);
+ *aRatio = ratio;
+ }
+ else {
+ k1 = (a2[1] - a1[1]) / x_diff;
+ k2 = (b2[1] - b1[1]) / x_diff2;
+
+ if ((k1 == k2))
+ return 0;
+
+ x = (a1[1] - b1[1] - k1 * a1[0] + k2 * b1[0]) / (k2 - k1);
+
+ ratio = (x - a1[0]) / x_diff;
+
+ *aRatio = ratio;
+ }
+ }
+
+ if (LRT_DOUBLE_CLOSE_ENOUGH(b1[0], b2[0])) {
+ y = interpd(a2[1], a1[1], ratio);
+ if (y > MAX2(b1[1], b2[1]) || y < MIN2(b1[1], b2[1]))
+ return 0;
+ }
+ else if (ratio <= 0 || ratio > 1 || (b1[0] > b2[0] && x > b1[0]) ||
+ (b1[0] < b2[0] && x < b1[0]) || (b2[0] > b1[0] && x > b2[0]) ||
+ (b2[0] < b1[0] && x < b2[0]))
+ return 0;
+
+ return 1;
+#endif
+}
+
+struct Depsgraph;
+struct Scene;
+struct LineartRenderBuffer;
+struct LineartGpencilModifierData;
+
+void MOD_lineart_destroy_render_data(struct LineartGpencilModifierData *lmd);
+
+void MOD_lineart_chain_feature_lines(LineartRenderBuffer *rb);
+void MOD_lineart_chain_split_for_fixed_occlusion(LineartRenderBuffer *rb);
+void MOD_lineart_chain_connect(LineartRenderBuffer *rb, const bool do_geometry_space);
+void MOD_lineart_chain_discard_short(LineartRenderBuffer *rb, const float threshold);
+void MOD_lineart_chain_split_angle(LineartRenderBuffer *rb, float angle_threshold_rad);
+
+int MOD_lineart_chain_count(const LineartRenderLineChain *rlc);
+void MOD_lineart_chain_clear_picked_flag(struct LineartRenderBuffer *rb);
+
+int MOD_lineart_compute_feature_lines(struct Depsgraph *depsgraph,
+ struct LineartGpencilModifierData *lmd);
+
+struct Scene;
+
+LineartBoundingArea *MOD_lineart_get_parent_bounding_area(LineartRenderBuffer *rb,
+ double x,
+ double y);
+
+LineartBoundingArea *MOD_lineart_get_bounding_area(LineartRenderBuffer *rb, double x, double y);
+
+struct bGPDlayer;
+struct bGPDframe;
+struct GpencilModifierData;
+
+void MOD_lineart_gpencil_generate(LineartRenderBuffer *rb,
+ struct Depsgraph *depsgraph,
+ struct Object *ob,
+ struct bGPDlayer *gpl,
+ struct bGPDframe *gpf,
+ char source_type,
+ void *source_reference,
+ int level_start,
+ int level_end,
+ int mat_nr,
+ short line_types,
+ unsigned char transparency_flags,
+ unsigned char transparency_mask,
+ short thickness,
+ float opacity,
+ float pre_sample_length,
+ const char *source_vgname,
+ const char *vgname,
+ int modifier_flags);
+
+float MOD_lineart_chain_compute_length(LineartRenderLineChain *rlc);
+
+struct wmOperatorType;
+
+void ED_operatortypes_lineart(void);
diff --git a/source/blender/gpencil_modifiers/intern/lineart/lineart_chain.c b/source/blender/gpencil_modifiers/intern/lineart/lineart_chain.c
new file mode 100644
index 00000000000..36115fa1027
--- /dev/null
+++ b/source/blender/gpencil_modifiers/intern/lineart/lineart_chain.c
@@ -0,0 +1,980 @@
+/*
+ * 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 "BLI_linklist.h"
+#include "BLI_listbase.h"
+#include "BLI_math.h"
+
+#include "BKE_customdata.h"
+#include "BKE_object.h"
+
+#include "DEG_depsgraph_query.h"
+
+#include "DNA_camera_types.h"
+#include "DNA_lineart_types.h"
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+#include "DNA_scene_types.h"
+
+#include "MOD_lineart.h"
+
+#include "bmesh.h"
+
+#include "lineart_intern.h"
+
+#include <math.h>
+
+#define LRT_OTHER_RV(rl, rv) ((rv) == (rl)->l ? (rl)->r : ((rv) == (rl)->r ? (rl)->l : NULL))
+
+/* Get a connected line, only for lines who has the exact given vert, or (in the case of
+ * intersection lines) who has a vert that has the exact same position. */
+static LineartRenderLine *lineart_line_get_connected(LineartBoundingArea *ba,
+ LineartRenderVert *rv,
+ LineartRenderVert **new_rv,
+ int match_flag)
+{
+ LISTBASE_FOREACH (LinkData *, lip, &ba->linked_lines) {
+ LineartRenderLine *nrl = lip->data;
+
+ if ((!(nrl->flags & LRT_EDGE_FLAG_ALL_TYPE)) || (nrl->flags & LRT_EDGE_FLAG_CHAIN_PICKED)) {
+ continue;
+ }
+
+ if (match_flag && ((nrl->flags & LRT_EDGE_FLAG_ALL_TYPE) & match_flag) == 0) {
+ continue;
+ }
+
+ *new_rv = LRT_OTHER_RV(nrl, rv);
+ if (*new_rv) {
+ return nrl;
+ }
+
+ if (nrl->flags & LRT_EDGE_FLAG_INTERSECTION) {
+ if (rv->fbcoord[0] == nrl->l->fbcoord[0] && rv->fbcoord[1] == nrl->l->fbcoord[1]) {
+ *new_rv = LRT_OTHER_RV(nrl, nrl->l);
+ return nrl;
+ }
+ if (rv->fbcoord[0] == nrl->r->fbcoord[0] && rv->fbcoord[1] == nrl->r->fbcoord[1]) {
+ *new_rv = LRT_OTHER_RV(nrl, nrl->r);
+ return nrl;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+static LineartRenderLineChain *lineart_chain_create(LineartRenderBuffer *rb)
+{
+ LineartRenderLineChain *rlc;
+ rlc = lineart_mem_aquire(&rb->render_data_pool, sizeof(LineartRenderLineChain));
+
+ BLI_addtail(&rb->chains, rlc);
+
+ return rlc;
+}
+
+static bool lineart_point_overlapping(LineartRenderLineChainItem *rlci,
+ float x,
+ float y,
+ double threshold)
+{
+ if (!rlci) {
+ return false;
+ }
+ if (((rlci->pos[0] + threshold) >= x) && ((rlci->pos[0] - threshold) <= x) &&
+ ((rlci->pos[1] + threshold) >= y) && ((rlci->pos[1] - threshold) <= y)) {
+ return true;
+ }
+ return false;
+}
+
+static LineartRenderLineChainItem *lineart_chain_append_point(LineartRenderBuffer *rb,
+ LineartRenderLineChain *rlc,
+ float *fbcoord,
+ float *gpos,
+ float *normal,
+ char type,
+ int level,
+ unsigned char transparency_mask,
+ size_t index)
+{
+ LineartRenderLineChainItem *rlci;
+
+ if (lineart_point_overlapping(rlc->chain.last, fbcoord[0], fbcoord[1], 1e-5)) {
+ /* Because the new chain point is overlapping, just replace the type and occlusion level of the
+ * current point. This makes it so that the line to the point after this one has the correct
+ * type and level. */
+ LineartRenderLineChainItem *old_rlci = rlc->chain.last;
+ old_rlci->line_type = type;
+ old_rlci->occlusion = level;
+ return old_rlci;
+ }
+
+ rlci = lineart_mem_aquire(&rb->render_data_pool, sizeof(LineartRenderLineChainItem));
+
+ copy_v2_v2(rlci->pos, fbcoord);
+ copy_v3_v3(rlci->gpos, gpos);
+ rlci->index = index;
+ copy_v3_v3(rlci->normal, normal);
+ rlci->line_type = type & LRT_EDGE_FLAG_ALL_TYPE;
+ rlci->occlusion = level;
+ rlci->transparency_mask = transparency_mask;
+ BLI_addtail(&rlc->chain, rlci);
+
+ return rlci;
+}
+
+static LineartRenderLineChainItem *lineart_chain_prepend_point(LineartRenderBuffer *rb,
+ LineartRenderLineChain *rlc,
+ float *fbcoord,
+ float *gpos,
+ float *normal,
+ char type,
+ int level,
+ unsigned char transparency_mask,
+ size_t index)
+{
+ LineartRenderLineChainItem *rlci;
+
+ if (lineart_point_overlapping(rlc->chain.first, fbcoord[0], fbcoord[1], 1e-5)) {
+ return rlc->chain.first;
+ }
+
+ rlci = lineart_mem_aquire(&rb->render_data_pool, sizeof(LineartRenderLineChainItem));
+
+ copy_v2_v2(rlci->pos, fbcoord);
+ copy_v3_v3(rlci->gpos, gpos);
+ rlci->index = index;
+ copy_v3_v3(rlci->normal, normal);
+ rlci->line_type = type & LRT_EDGE_FLAG_ALL_TYPE;
+ rlci->occlusion = level;
+ rlci->transparency_mask = transparency_mask;
+ BLI_addhead(&rlc->chain, rlci);
+
+ return rlci;
+}
+
+void MOD_lineart_chain_feature_lines(LineartRenderBuffer *rb)
+{
+ LineartRenderLineChain *rlc;
+ LineartRenderLineChainItem *rlci;
+ LineartBoundingArea *ba;
+ LineartRenderLineSegment *rls;
+ int last_occlusion;
+ unsigned char last_transparency;
+ /* Used when converting from double. */
+ float use_fbcoord[2];
+ float use_gpos[3];
+
+#define VERT_COORD_TO_FLOAT(a) \
+ copy_v2fl_v2db(use_fbcoord, (a)->fbcoord); \
+ copy_v3fl_v3db(use_gpos, (a)->gloc);
+
+#define POS_TO_FLOAT(lpos, gpos) \
+ copy_v2fl_v2db(use_fbcoord, lpos); \
+ copy_v3fl_v3db(use_gpos, gpos);
+
+ LRT_ITER_ALL_LINES_BEGIN
+ {
+ if ((!(rl->flags & LRT_EDGE_FLAG_ALL_TYPE)) || (rl->flags & LRT_EDGE_FLAG_CHAIN_PICKED)) {
+ LRT_ITER_ALL_LINES_NEXT
+ continue;
+ }
+
+ rl->flags |= LRT_EDGE_FLAG_CHAIN_PICKED;
+
+ rlc = lineart_chain_create(rb);
+
+ /* One chain can only have one object_ref,
+ * so we assign it based on the first segment we found. */
+ rlc->object_ref = rl->object_ref;
+
+ LineartRenderLine *new_rl = rl;
+ LineartRenderVert *new_rv;
+ float N[3] = {0};
+
+ if (rl->tl) {
+ N[0] += rl->tl->gn[0];
+ N[1] += rl->tl->gn[1];
+ N[2] += rl->tl->gn[2];
+ }
+ if (rl->tr) {
+ N[0] += rl->tr->gn[0];
+ N[1] += rl->tr->gn[1];
+ N[2] += rl->tr->gn[2];
+ }
+ if (rl->tl || rl->tr) {
+ normalize_v3(N);
+ }
+
+ /* Step 1: grow left. */
+ ba = MOD_lineart_get_bounding_area(rb, rl->l->fbcoord[0], rl->l->fbcoord[1]);
+ new_rv = rl->l;
+ rls = rl->segments.first;
+ VERT_COORD_TO_FLOAT(new_rv);
+ lineart_chain_prepend_point(rb,
+ rlc,
+ use_fbcoord,
+ use_gpos,
+ N,
+ rl->flags,
+ rls->occlusion,
+ rls->transparency_mask,
+ rl->l_obindex);
+ while (ba && (new_rl = lineart_line_get_connected(ba, new_rv, &new_rv, rl->flags))) {
+ new_rl->flags |= LRT_EDGE_FLAG_CHAIN_PICKED;
+
+ if (new_rl->tl || new_rl->tr) {
+ zero_v3(N);
+ if (new_rl->tl) {
+ N[0] += new_rl->tl->gn[0];
+ N[1] += new_rl->tl->gn[1];
+ N[2] += new_rl->tl->gn[2];
+ }
+ if (new_rl->tr) {
+ N[0] += new_rl->tr->gn[0];
+ N[1] += new_rl->tr->gn[1];
+ N[2] += new_rl->tr->gn[2];
+ }
+ normalize_v3(N);
+ }
+
+ if (new_rv == new_rl->l) {
+ for (rls = new_rl->segments.last; rls; rls = rls->prev) {
+ double gpos[3], lpos[3];
+ double *lfb = new_rl->l->fbcoord, *rfb = new_rl->r->fbcoord;
+ double global_at = lfb[3] * rls->at / (rls->at * lfb[3] + (1 - rls->at) * rfb[3]);
+ interp_v3_v3v3_db(lpos, new_rl->l->fbcoord, new_rl->r->fbcoord, rls->at);
+ interp_v3_v3v3_db(gpos, new_rl->l->gloc, new_rl->r->gloc, global_at);
+ POS_TO_FLOAT(lpos, gpos)
+ lineart_chain_prepend_point(rb,
+ rlc,
+ use_fbcoord,
+ use_gpos,
+ N,
+ new_rl->flags,
+ rls->occlusion,
+ rls->transparency_mask,
+ new_rl->l_obindex);
+ last_occlusion = rls->occlusion;
+ last_transparency = rls->transparency_mask;
+ }
+ }
+ else if (new_rv == new_rl->r) {
+ rls = new_rl->segments.first;
+ last_occlusion = rls->occlusion;
+ last_transparency = rls->transparency_mask;
+ rls = rls->next;
+ for (; rls; rls = rls->next) {
+ double gpos[3], lpos[3];
+ double *lfb = new_rl->l->fbcoord, *rfb = new_rl->r->fbcoord;
+ double global_at = lfb[3] * rls->at / (rls->at * lfb[3] + (1 - rls->at) * rfb[3]);
+ interp_v3_v3v3_db(lpos, new_rl->l->fbcoord, new_rl->r->fbcoord, rls->at);
+ interp_v3_v3v3_db(gpos, new_rl->l->gloc, new_rl->r->gloc, global_at);
+ POS_TO_FLOAT(lpos, gpos)
+ lineart_chain_prepend_point(rb,
+ rlc,
+ use_fbcoord,
+ use_gpos,
+ N,
+ new_rl->flags,
+ last_occlusion,
+ last_transparency,
+ new_rl->r_obindex);
+ last_occlusion = rls->occlusion;
+ last_transparency = rls->transparency_mask;
+ }
+ VERT_COORD_TO_FLOAT(new_rl->r);
+ lineart_chain_prepend_point(rb,
+ rlc,
+ use_fbcoord,
+ use_gpos,
+ N,
+ new_rl->flags,
+ last_occlusion,
+ last_transparency,
+ new_rl->r_obindex);
+ }
+ ba = MOD_lineart_get_bounding_area(rb, new_rv->fbcoord[0], new_rv->fbcoord[1]);
+ }
+
+ /* Restore normal value. */
+ if (rl->tl || rl->tr) {
+ zero_v3(N);
+ if (rl->tl) {
+ N[0] += rl->tl->gn[0];
+ N[1] += rl->tl->gn[1];
+ N[2] += rl->tl->gn[2];
+ }
+ if (rl->tr) {
+ N[0] += rl->tr->gn[0];
+ N[1] += rl->tr->gn[1];
+ N[2] += rl->tr->gn[2];
+ }
+ normalize_v3(N);
+ }
+ /* Step 2: Adding all cuts from the given line, so we can continue connecting the right side
+ * of the line. */
+ rls = rl->segments.first;
+ last_occlusion = ((LineartRenderLineSegment *)rls)->occlusion;
+ last_transparency = ((LineartRenderLineSegment *)rls)->transparency_mask;
+ for (rls = rls->next; rls; rls = rls->next) {
+ double gpos[3], lpos[3];
+ double *lfb = rl->l->fbcoord, *rfb = rl->r->fbcoord;
+ double global_at = lfb[3] * rls->at / (rls->at * lfb[3] + (1 - rls->at) * rfb[3]);
+ interp_v3_v3v3_db(lpos, rl->l->fbcoord, rl->r->fbcoord, rls->at);
+ interp_v3_v3v3_db(gpos, rl->l->gloc, rl->r->gloc, global_at);
+ POS_TO_FLOAT(lpos, gpos)
+ lineart_chain_append_point(rb,
+ rlc,
+ use_fbcoord,
+ use_gpos,
+ N,
+ rl->flags,
+ rls->occlusion,
+ rls->transparency_mask,
+ rl->l_obindex);
+ last_occlusion = rls->occlusion;
+ }
+ VERT_COORD_TO_FLOAT(rl->r)
+ lineart_chain_append_point(rb,
+ rlc,
+ use_fbcoord,
+ use_gpos,
+ N,
+ rl->flags,
+ last_occlusion,
+ last_transparency,
+ rl->r_obindex);
+
+ /* Step 3: grow right. */
+ ba = MOD_lineart_get_bounding_area(rb, rl->r->fbcoord[0], rl->r->fbcoord[1]);
+ new_rv = rl->r;
+ while (ba && (new_rl = lineart_line_get_connected(ba, new_rv, &new_rv, rl->flags))) {
+ new_rl->flags |= LRT_EDGE_FLAG_CHAIN_PICKED;
+
+ if (new_rl->tl || new_rl->tr) {
+ zero_v3(N);
+ if (new_rl->tl) {
+ N[0] += new_rl->tl->gn[0];
+ N[1] += new_rl->tl->gn[1];
+ N[2] += new_rl->tl->gn[2];
+ }
+ if (new_rl->tr) {
+ N[0] += new_rl->tr->gn[0];
+ N[1] += new_rl->tr->gn[1];
+ N[2] += new_rl->tr->gn[2];
+ }
+ normalize_v3(N);
+ }
+
+ /* Fix leading vertex type. */
+ rlci = rlc->chain.last;
+ rlci->line_type = new_rl->flags & LRT_EDGE_FLAG_ALL_TYPE;
+
+ if (new_rv == new_rl->l) {
+ rls = new_rl->segments.last;
+ last_occlusion = rls->occlusion;
+ last_transparency = rls->transparency_mask;
+ /* Fix leading vertex occlusion. */
+ rlci->occlusion = last_occlusion;
+ for (rls = new_rl->segments.last; rls; rls = rls->prev) {
+ double gpos[3], lpos[3];
+ double *lfb = new_rl->l->fbcoord, *rfb = new_rl->r->fbcoord;
+ double global_at = lfb[3] * rls->at / (rls->at * lfb[3] + (1 - rls->at) * rfb[3]);
+ interp_v3_v3v3_db(lpos, new_rl->l->fbcoord, new_rl->r->fbcoord, rls->at);
+ interp_v3_v3v3_db(gpos, new_rl->l->gloc, new_rl->r->gloc, global_at);
+ last_occlusion = rls->prev ? rls->prev->occlusion : last_occlusion;
+ POS_TO_FLOAT(lpos, gpos)
+ lineart_chain_append_point(rb,
+ rlc,
+ use_fbcoord,
+ use_gpos,
+ N,
+ new_rl->flags,
+ last_occlusion,
+ last_transparency,
+ new_rl->l_obindex);
+ }
+ }
+ else if (new_rv == new_rl->r) {
+ rls = new_rl->segments.first;
+ last_occlusion = rls->occlusion;
+ last_transparency = rls->transparency_mask;
+ rlci->occlusion = last_occlusion;
+ rls = rls->next;
+ for (; rls; rls = rls->next) {
+ double gpos[3], lpos[3];
+ double *lfb = new_rl->l->fbcoord, *rfb = new_rl->r->fbcoord;
+ double global_at = lfb[3] * rls->at / (rls->at * lfb[3] + (1 - rls->at) * rfb[3]);
+ interp_v3_v3v3_db(lpos, new_rl->l->fbcoord, new_rl->r->fbcoord, rls->at);
+ interp_v3_v3v3_db(gpos, new_rl->l->gloc, new_rl->r->gloc, global_at);
+ POS_TO_FLOAT(lpos, gpos)
+ lineart_chain_append_point(rb,
+ rlc,
+ use_fbcoord,
+ use_gpos,
+ N,
+ new_rl->flags,
+ rls->occlusion,
+ rls->transparency_mask,
+ new_rl->r_obindex);
+ last_occlusion = rls->occlusion;
+ last_transparency = rls->transparency_mask;
+ }
+ VERT_COORD_TO_FLOAT(new_rl->r)
+ lineart_chain_append_point(rb,
+ rlc,
+ use_fbcoord,
+ use_gpos,
+ N,
+ new_rl->flags,
+ last_occlusion,
+ last_transparency,
+ new_rl->r_obindex);
+ }
+ ba = MOD_lineart_get_bounding_area(rb, new_rv->fbcoord[0], new_rv->fbcoord[1]);
+ }
+ if (rb->fuzzy_everything) {
+ rlc->type = LRT_EDGE_FLAG_CONTOUR;
+ }
+ else {
+ rlc->type = (rl->flags & LRT_EDGE_FLAG_ALL_TYPE);
+ }
+ }
+ LRT_ITER_ALL_LINES_END
+}
+
+static LineartBoundingArea *lineart_bounding_area_get_rlci_recursive(
+ LineartRenderBuffer *rb, LineartBoundingArea *root, LineartRenderLineChainItem *rlci)
+{
+ if (root->child == NULL) {
+ return root;
+ }
+
+ LineartBoundingArea *ch = root->child;
+#define IN_BOUND(ba, rlci) \
+ ba.l <= rlci->pos[0] && ba.r >= rlci->pos[0] && ba.b <= rlci->pos[1] && ba.u >= rlci->pos[1]
+
+ if (IN_BOUND(ch[0], rlci)) {
+ return lineart_bounding_area_get_rlci_recursive(rb, &ch[0], rlci);
+ }
+ if (IN_BOUND(ch[1], rlci)) {
+ return lineart_bounding_area_get_rlci_recursive(rb, &ch[1], rlci);
+ }
+ if (IN_BOUND(ch[2], rlci)) {
+ return lineart_bounding_area_get_rlci_recursive(rb, &ch[2], rlci);
+ }
+ if (IN_BOUND(ch[3], rlci)) {
+ return lineart_bounding_area_get_rlci_recursive(rb, &ch[3], rlci);
+ }
+#undef IN_BOUND
+ return NULL;
+}
+
+static LineartBoundingArea *lineart_bounding_area_get_end_point(LineartRenderBuffer *rb,
+ LineartRenderLineChainItem *rlci)
+{
+ if (!rlci) {
+ return NULL;
+ }
+ LineartBoundingArea *root = MOD_lineart_get_parent_bounding_area(rb, rlci->pos[0], rlci->pos[1]);
+ if (root == NULL) {
+ return NULL;
+ }
+ return lineart_bounding_area_get_rlci_recursive(rb, root, rlci);
+}
+
+/* Here we will try to connect geometry space chains together in image space. However we can't
+ * chain two chains together if their end and start points lie on the border between two bounding
+ * areas, this happens either when 1) the geometry is way too dense, or 2) the chaining threshold
+ * is too big that it covers multiple small bounding areas. */
+static void lineart_bounding_area_link_point_recursive(LineartRenderBuffer *rb,
+ LineartBoundingArea *root,
+ LineartRenderLineChain *rlc,
+ LineartRenderLineChainItem *rlci)
+{
+ if (root->child == NULL) {
+ LineartChainRegisterEntry *cre = lineart_list_append_pointer_pool_sized(
+ &root->linked_chains, &rb->render_data_pool, rlc, sizeof(LineartChainRegisterEntry));
+
+ cre->rlci = rlci;
+
+ if (rlci == rlc->chain.first) {
+ cre->is_left = 1;
+ }
+ }
+ else {
+ LineartBoundingArea *ch = root->child;
+
+#define IN_BOUND(ba, rlci) \
+ ba.l <= rlci->pos[0] && ba.r >= rlci->pos[0] && ba.b <= rlci->pos[1] && ba.u >= rlci->pos[1]
+
+ if (IN_BOUND(ch[0], rlci)) {
+ lineart_bounding_area_link_point_recursive(rb, &ch[0], rlc, rlci);
+ }
+ else if (IN_BOUND(ch[1], rlci)) {
+ lineart_bounding_area_link_point_recursive(rb, &ch[1], rlc, rlci);
+ }
+ else if (IN_BOUND(ch[2], rlci)) {
+ lineart_bounding_area_link_point_recursive(rb, &ch[2], rlc, rlci);
+ }
+ else if (IN_BOUND(ch[3], rlci)) {
+ lineart_bounding_area_link_point_recursive(rb, &ch[3], rlc, rlci);
+ }
+
+#undef IN_BOUND
+ }
+}
+
+static void lineart_bounding_area_link_chain(LineartRenderBuffer *rb, LineartRenderLineChain *rlc)
+{
+ LineartRenderLineChainItem *pl = rlc->chain.first;
+ LineartRenderLineChainItem *pr = rlc->chain.last;
+ LineartBoundingArea *ba1 = MOD_lineart_get_parent_bounding_area(rb, pl->pos[0], pl->pos[1]);
+ LineartBoundingArea *ba2 = MOD_lineart_get_parent_bounding_area(rb, pr->pos[0], pr->pos[1]);
+
+ if (ba1) {
+ lineart_bounding_area_link_point_recursive(rb, ba1, rlc, pl);
+ }
+ if (ba2) {
+ lineart_bounding_area_link_point_recursive(rb, ba2, rlc, pr);
+ }
+}
+
+void MOD_lineart_chain_split_for_fixed_occlusion(LineartRenderBuffer *rb)
+{
+ LineartRenderLineChain *rlc, *new_rlc;
+ LineartRenderLineChainItem *rlci, *next_rlci;
+ ListBase swap = {0};
+
+ swap.first = rb->chains.first;
+ swap.last = rb->chains.last;
+
+ rb->chains.last = rb->chains.first = NULL;
+
+ while ((rlc = BLI_pophead(&swap)) != NULL) {
+ rlc->next = rlc->prev = NULL;
+ BLI_addtail(&rb->chains, rlc);
+ LineartRenderLineChainItem *first_rlci = (LineartRenderLineChainItem *)rlc->chain.first;
+ int fixed_occ = first_rlci->occlusion;
+ unsigned char fixed_mask = first_rlci->transparency_mask;
+ rlc->level = fixed_occ;
+ rlc->transparency_mask = fixed_mask;
+ for (rlci = first_rlci->next; rlci; rlci = next_rlci) {
+ next_rlci = rlci->next;
+ if (rlci->occlusion != fixed_occ || rlci->transparency_mask != fixed_mask) {
+ if (next_rlci) {
+ if (lineart_point_overlapping(next_rlci, rlci->pos[0], rlci->pos[1], 1e-5)) {
+ continue;
+ }
+ }
+ else {
+ /* Set the same occlusion level for the end vertex, so when further connection is needed
+ * the backwards occlusion info is also correct. */
+ rlci->occlusion = fixed_occ;
+ /* No need to split at the last point anyway. */
+ break;
+ }
+ new_rlc = lineart_chain_create(rb);
+ new_rlc->chain.first = rlci;
+ new_rlc->chain.last = rlc->chain.last;
+ rlc->chain.last = rlci->prev;
+ ((LineartRenderLineChainItem *)rlc->chain.last)->next = 0;
+ rlci->prev = 0;
+
+ /* End the previous one. */
+ lineart_chain_append_point(rb,
+ rlc,
+ rlci->pos,
+ rlci->gpos,
+ rlci->normal,
+ rlci->line_type,
+ fixed_occ,
+ fixed_mask,
+ rlci->index);
+ new_rlc->object_ref = rlc->object_ref;
+ new_rlc->type = rlc->type;
+ rlc = new_rlc;
+ fixed_occ = rlci->occlusion;
+ fixed_mask = rlci->transparency_mask;
+ rlc->level = fixed_occ;
+ rlc->transparency_mask = fixed_mask;
+ }
+ }
+ }
+ LISTBASE_FOREACH (LineartRenderLineChain *, irlc, &rb->chains) {
+ lineart_bounding_area_link_chain(rb, irlc);
+ }
+}
+
+/* Note: segment type (crease/material/contour...) is ambiguous after this. */
+static void lineart_chain_connect(LineartRenderBuffer *UNUSED(rb),
+ LineartRenderLineChain *onto,
+ LineartRenderLineChain *sub,
+ int reverse_1,
+ int reverse_2)
+{
+ LineartRenderLineChainItem *rlci;
+ if (onto->type == LRT_EDGE_FLAG_INTERSECTION) {
+ if (sub->object_ref) {
+ onto->object_ref = sub->object_ref;
+ onto->type = LRT_EDGE_FLAG_CONTOUR;
+ }
+ }
+ else if (sub->type == LRT_EDGE_FLAG_INTERSECTION) {
+ if (onto->type != LRT_EDGE_FLAG_INTERSECTION) {
+ onto->type = LRT_EDGE_FLAG_CONTOUR;
+ }
+ }
+ if (!reverse_1) { /* L--R L-R. */
+ if (reverse_2) { /* L--R R-L. */
+ BLI_listbase_reverse(&sub->chain);
+ }
+ rlci = sub->chain.first;
+ if (lineart_point_overlapping(onto->chain.last, rlci->pos[0], rlci->pos[1], 1e-5)) {
+ BLI_pophead(&sub->chain);
+ if (sub->chain.first == NULL) {
+ return;
+ }
+ }
+ ((LineartRenderLineChainItem *)onto->chain.last)->next = sub->chain.first;
+ ((LineartRenderLineChainItem *)sub->chain.first)->prev = onto->chain.last;
+ onto->chain.last = sub->chain.last;
+ }
+ else { /* L-R L--R. */
+ if (!reverse_2) { /* R-L L--R. */
+ BLI_listbase_reverse(&sub->chain);
+ }
+ rlci = onto->chain.first;
+ if (lineart_point_overlapping(sub->chain.last, rlci->pos[0], rlci->pos[1], 1e-5)) {
+ BLI_pophead(&onto->chain);
+ if (onto->chain.first == NULL) {
+ return;
+ }
+ }
+ ((LineartRenderLineChainItem *)sub->chain.last)->next = onto->chain.first;
+ ((LineartRenderLineChainItem *)onto->chain.first)->prev = sub->chain.last;
+ onto->chain.first = sub->chain.first;
+ }
+}
+
+static LineartChainRegisterEntry *lineart_chain_get_closest_cre(LineartRenderBuffer *rb,
+ LineartBoundingArea *ba,
+ LineartRenderLineChain *rlc,
+ LineartRenderLineChainItem *rlci,
+ int occlusion,
+ unsigned char transparency_mask,
+ float dist,
+ int do_geometry_space,
+ float *result_new_len,
+ LineartBoundingArea *caller_ba)
+{
+
+ LineartChainRegisterEntry *closest_cre = NULL;
+
+ /* Keep using for loop because cre could be removed from the iteration before getting to the
+ * next one. */
+ LISTBASE_FOREACH_MUTABLE (LineartChainRegisterEntry *, cre, &ba->linked_chains) {
+ if (cre->rlc->object_ref != rlc->object_ref) {
+ if (!rb->fuzzy_everything) {
+ if (rb->fuzzy_intersections) {
+ /* If none of those are intersection lines... */
+ if ((!(cre->rlc->type & LRT_EDGE_FLAG_INTERSECTION)) &&
+ (!(rlc->type & LRT_EDGE_FLAG_INTERSECTION))) {
+ continue; /* We don't want to chain along different objects at the moment. */
+ }
+ }
+ else {
+ continue;
+ }
+ }
+ }
+ if (cre->rlc->picked || cre->picked) {
+ continue;
+ }
+ if (cre->rlc == rlc || (!cre->rlc->chain.first) || (cre->rlc->level != occlusion) ||
+ (cre->rlc->transparency_mask != transparency_mask)) {
+ continue;
+ }
+ if (!rb->fuzzy_everything) {
+ if (cre->rlc->type != rlc->type) {
+ if (rb->fuzzy_intersections) {
+ if (!(cre->rlc->type == LRT_EDGE_FLAG_INTERSECTION ||
+ rlc->type == LRT_EDGE_FLAG_INTERSECTION)) {
+ continue; /* Fuzzy intersetions but no intersection line found. */
+ }
+ }
+ else { /* Line type different but no fuzzy. */
+ continue;
+ }
+ }
+ }
+
+ float new_len = do_geometry_space ? len_v3v3(cre->rlci->gpos, rlci->gpos) :
+ len_v2v2(cre->rlci->pos, rlci->pos);
+ if (new_len < dist) {
+ closest_cre = cre;
+ dist = new_len;
+ if (result_new_len) {
+ (*result_new_len) = new_len;
+ }
+ }
+ }
+
+ /* We want a closer point anyway. So using modified dist is fine. */
+ float adjacent_new_len = dist;
+ LineartChainRegisterEntry *adjacent_closest;
+
+#define LRT_TEST_ADJACENT_AREAS(dist_to, list) \
+ if (dist_to < dist && dist_to > 0) { \
+ LISTBASE_FOREACH (LinkData *, ld, list) { \
+ LineartBoundingArea *sba = (LineartBoundingArea *)ld->data; \
+ adjacent_closest = lineart_chain_get_closest_cre(rb, \
+ sba, \
+ rlc, \
+ rlci, \
+ occlusion, \
+ transparency_mask, \
+ dist, \
+ do_geometry_space, \
+ &adjacent_new_len, \
+ ba); \
+ if (adjacent_new_len < dist) { \
+ dist = adjacent_new_len; \
+ closest_cre = adjacent_closest; \
+ } \
+ } \
+ }
+ if (!do_geometry_space && !caller_ba) {
+ LRT_TEST_ADJACENT_AREAS(rlci->pos[0] - ba->l, &ba->lp);
+ LRT_TEST_ADJACENT_AREAS(ba->r - rlci->pos[0], &ba->rp);
+ LRT_TEST_ADJACENT_AREAS(ba->u - rlci->pos[1], &ba->up);
+ LRT_TEST_ADJACENT_AREAS(rlci->pos[1] - ba->b, &ba->bp);
+ }
+ if (result_new_len) {
+ (*result_new_len) = dist;
+ }
+ return closest_cre;
+}
+
+/* This function only connects two different chains. It will not do any clean up or smart chaining.
+ * So no: removing overlapping chains, removal of short isolated segments, and no loop reduction is
+ * implemented yet. */
+void MOD_lineart_chain_connect(LineartRenderBuffer *rb, const bool do_geometry_space)
+{
+ LineartRenderLineChain *rlc;
+ LineartRenderLineChainItem *rlci_l, *rlci_r;
+ LineartBoundingArea *ba_l, *ba_r;
+ LineartChainRegisterEntry *closest_cre_l, *closest_cre_r, *closest_cre;
+ float dist = do_geometry_space ? rb->chaining_geometry_threshold : rb->chaining_image_threshold;
+ float dist_l, dist_r;
+ int occlusion, reverse_main;
+ unsigned char transparency_mask;
+ ListBase swap = {0};
+
+ if ((!do_geometry_space && rb->chaining_image_threshold < 0.0001) ||
+ (do_geometry_space && rb->chaining_geometry_threshold < 0.0001)) {
+ return;
+ }
+
+ swap.first = rb->chains.first;
+ swap.last = rb->chains.last;
+
+ rb->chains.last = rb->chains.first = NULL;
+
+ while ((rlc = BLI_pophead(&swap)) != NULL) {
+ rlc->next = rlc->prev = NULL;
+ if (rlc->picked) {
+ continue;
+ }
+ BLI_addtail(&rb->chains, rlc);
+
+ occlusion = rlc->level;
+ transparency_mask = rlc->transparency_mask;
+
+ rlci_l = rlc->chain.first;
+ rlci_r = rlc->chain.last;
+ while ((ba_l = lineart_bounding_area_get_end_point(rb, rlci_l)) &&
+ (ba_r = lineart_bounding_area_get_end_point(rb, rlci_r))) {
+ closest_cre_l = lineart_chain_get_closest_cre(rb,
+ ba_l,
+ rlc,
+ rlci_l,
+ occlusion,
+ transparency_mask,
+ dist,
+ do_geometry_space,
+ &dist_l,
+ NULL);
+ closest_cre_r = lineart_chain_get_closest_cre(rb,
+ ba_r,
+ rlc,
+ rlci_r,
+ occlusion,
+ transparency_mask,
+ dist,
+ do_geometry_space,
+ &dist_r,
+ NULL);
+ if (closest_cre_l && closest_cre_r) {
+ if (dist_l < dist_r) {
+ closest_cre = closest_cre_l;
+ reverse_main = 1;
+ }
+ else {
+ closest_cre = closest_cre_r;
+ reverse_main = 0;
+ }
+ }
+ else if (closest_cre_l) {
+ closest_cre = closest_cre_l;
+ reverse_main = 1;
+ }
+ else if (closest_cre_r) {
+ closest_cre = closest_cre_r;
+ BLI_remlink(&ba_r->linked_chains, closest_cre_r);
+ reverse_main = 0;
+ }
+ else {
+ break;
+ }
+ closest_cre->picked = 1;
+ closest_cre->rlc->picked = 1;
+ if (closest_cre->is_left) {
+ lineart_chain_connect(rb, rlc, closest_cre->rlc, reverse_main, 0);
+ }
+ else {
+ lineart_chain_connect(rb, rlc, closest_cre->rlc, reverse_main, 1);
+ }
+ BLI_remlink(&swap, closest_cre->rlc);
+ rlci_l = rlc->chain.first;
+ rlci_r = rlc->chain.last;
+ }
+ rlc->picked = 1;
+ }
+}
+
+/* Length is in image space. */
+float MOD_lineart_chain_compute_length(LineartRenderLineChain *rlc)
+{
+ LineartRenderLineChainItem *rlci;
+ float offset_accum = 0;
+ float dist;
+ float last_point[2];
+
+ rlci = rlc->chain.first;
+ copy_v2_v2(last_point, rlci->pos);
+ for (rlci = rlc->chain.first; rlci; rlci = rlci->next) {
+ dist = len_v2v2(rlci->pos, last_point);
+ offset_accum += dist;
+ copy_v2_v2(last_point, rlci->pos);
+ }
+ return offset_accum;
+}
+
+void MOD_lineart_chain_discard_short(LineartRenderBuffer *rb, const float threshold)
+{
+ LineartRenderLineChain *rlc, *next_rlc;
+ for (rlc = rb->chains.first; rlc; rlc = next_rlc) {
+ next_rlc = rlc->next;
+ if (MOD_lineart_chain_compute_length(rlc) < threshold) {
+ BLI_remlink(&rb->chains, rlc);
+ }
+ }
+}
+
+int MOD_lineart_chain_count(const LineartRenderLineChain *rlc)
+{
+ int count = 0;
+ LISTBASE_FOREACH (LineartRenderLineChainItem *, rlci, &rlc->chain) {
+ count++;
+ }
+ return count;
+}
+
+void MOD_lineart_chain_clear_picked_flag(LineartRenderBuffer *rb)
+{
+ if (rb == NULL) {
+ return;
+ }
+ LISTBASE_FOREACH (LineartRenderLineChain *, rlc, &rb->chains) {
+ rlc->picked = 0;
+ }
+}
+
+/* This should always be the last stage!, see the end of
+ * MOD_lineart_chain_split_for_fixed_occlusion().*/
+void MOD_lineart_chain_split_angle(LineartRenderBuffer *rb, float angle_threshold_rad)
+{
+ LineartRenderLineChain *rlc, *new_rlc;
+ LineartRenderLineChainItem *rlci, *next_rlci, *prev_rlci;
+ ListBase swap = {0};
+
+ swap.first = rb->chains.first;
+ swap.last = rb->chains.last;
+
+ rb->chains.last = rb->chains.first = NULL;
+
+ while ((rlc = BLI_pophead(&swap)) != NULL) {
+ rlc->next = rlc->prev = NULL;
+ BLI_addtail(&rb->chains, rlc);
+ LineartRenderLineChainItem *first_rlci = (LineartRenderLineChainItem *)rlc->chain.first;
+ for (rlci = first_rlci->next; rlci; rlci = next_rlci) {
+ next_rlci = rlci->next;
+ prev_rlci = rlci->prev;
+ float angle = M_PI;
+ if (next_rlci && prev_rlci) {
+ angle = angle_v2v2v2(prev_rlci->pos, rlci->pos, next_rlci->pos);
+ }
+ else {
+ break; /* No need to split at the last point anyway.*/
+ }
+ if (angle < angle_threshold_rad) {
+ new_rlc = lineart_chain_create(rb);
+ new_rlc->chain.first = rlci;
+ new_rlc->chain.last = rlc->chain.last;
+ rlc->chain.last = rlci->prev;
+ ((LineartRenderLineChainItem *)rlc->chain.last)->next = 0;
+ rlci->prev = 0;
+
+ /* End the previous one. */
+ lineart_chain_append_point(rb,
+ rlc,
+ rlci->pos,
+ rlci->gpos,
+ rlci->normal,
+ rlci->line_type,
+ rlc->level,
+ rlci->transparency_mask,
+ rlci->index);
+ new_rlc->object_ref = rlc->object_ref;
+ new_rlc->type = rlc->type;
+ new_rlc->level = rlc->level;
+ new_rlc->transparency_mask = rlc->transparency_mask;
+ rlc = new_rlc;
+ }
+ }
+ }
+}
diff --git a/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c b/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c
new file mode 100644
index 00000000000..195f0088114
--- /dev/null
+++ b/source/blender/gpencil_modifiers/intern/lineart/lineart_cpu.c
@@ -0,0 +1,3931 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2019 Blender Foundation.
+ * All rights reserved.
+ */
+
+/* \file
+ * \ingroup editors
+ */
+
+#include "MOD_lineart.h"
+
+#include "BLI_alloca.h"
+#include "BLI_linklist.h"
+#include "BLI_listbase.h"
+#include "BLI_math_matrix.h"
+#include "BLI_task.h"
+#include "BLI_utildefines.h"
+
+#include "BKE_callbacks.h"
+#include "BKE_camera.h"
+#include "BKE_collection.h"
+#include "BKE_context.h"
+#include "BKE_curve.h"
+#include "BKE_customdata.h"
+#include "BKE_deform.h"
+#include "BKE_editmesh.h"
+#include "BKE_global.h"
+#include "BKE_gpencil.h"
+#include "BKE_gpencil_geom.h"
+#include "BKE_gpencil_modifier.h"
+#include "BKE_material.h"
+#include "BKE_mesh.h"
+#include "BKE_object.h"
+#include "BKE_scene.h"
+#include "BKE_screen.h"
+#include "BKE_text.h"
+#include "DEG_depsgraph_query.h"
+#include "DNA_camera_types.h"
+#include "DNA_collection_types.h"
+#include "DNA_gpencil_types.h"
+#include "DNA_lineart_types.h"
+#include "DNA_material_types.h"
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+#include "DNA_modifier_types.h"
+#include "DNA_scene_types.h"
+#include "DNA_text_types.h"
+#include "MEM_guardedalloc.h"
+
+#include "RNA_access.h"
+#include "RNA_define.h"
+
+#include "BLI_math.h"
+#include "BLI_string_utils.h"
+
+#include "bmesh.h"
+#include "bmesh_class.h"
+#include "bmesh_tools.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "MOD_gpencil_modifiertypes.h"
+
+#include "lineart_intern.h"
+
+static LineartBoundingArea *lineart_line_first_bounding_area(LineartRenderBuffer *rb,
+ LineartRenderLine *rl);
+
+static void lineart_bounding_area_link_line(LineartRenderBuffer *rb,
+ LineartBoundingArea *root_ba,
+ LineartRenderLine *rl);
+
+static LineartBoundingArea *lineart_bounding_area_next(LineartBoundingArea *This,
+ LineartRenderLine *rl,
+ double x,
+ double y,
+ double k,
+ int positive_x,
+ int positive_y,
+ double *next_x,
+ double *next_y);
+
+static bool lineart_get_line_bounding_areas(LineartRenderBuffer *rb,
+ LineartRenderLine *rl,
+ int *rowbegin,
+ int *rowend,
+ int *colbegin,
+ int *colend);
+
+static void lineart_bounding_area_link_triangle(LineartRenderBuffer *rb,
+ LineartBoundingArea *root_ba,
+ LineartRenderTriangle *rt,
+ double *LRUB,
+ int recursive,
+ int recursive_level,
+ bool do_intersection);
+
+static bool lineart_triangle_line_image_space_occlusion(SpinLock *spl,
+ const LineartRenderTriangle *rt,
+ const LineartRenderLine *rl,
+ const double *override_camera_loc,
+ const bool override_cam_is_persp,
+ const bool allow_overlapping_edges,
+ const double vp[4][4],
+ const double *camera_dir,
+ const float cam_shift_x,
+ const float cam_shift_y,
+ double *from,
+ double *to);
+
+static void lineart_add_line_to_list(LineartRenderBuffer *rb, LineartRenderLine *rl);
+
+static void lineart_line_discard_segment(LineartRenderBuffer *rb, LineartRenderLineSegment *rls)
+{
+ BLI_spin_lock(&rb->lock_cuts);
+
+ memset(rls, 0, sizeof(LineartRenderLineSegment));
+
+ /* 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 LineartRenderLineSegment *lineart_line_give_segment(LineartRenderBuffer *rb)
+{
+ BLI_spin_lock(&rb->lock_cuts);
+
+ /* See if there is any already allocated memory we can reuse. */
+ if (rb->wasted_cuts.first) {
+ LineartRenderLineSegment *rls = (LineartRenderLineSegment *)BLI_pophead(&rb->wasted_cuts);
+ BLI_spin_unlock(&rb->lock_cuts);
+ memset(rls, 0, sizeof(LineartRenderLineSegment));
+ return rls;
+ }
+ BLI_spin_unlock(&rb->lock_cuts);
+
+ /* Otherwise allocate some new memory. */
+ return (LineartRenderLineSegment *)lineart_mem_aquire_thread(&rb->render_data_pool,
+ sizeof(LineartRenderLineSegment));
+}
+
+/* Cuts the line in image space and mark occlusion level for each segment. */
+static void lineart_line_cut(LineartRenderBuffer *rb,
+ LineartRenderLine *rl,
+ double start,
+ double end,
+ unsigned char transparency_mask)
+{
+ LineartRenderLineSegment *rls, *irls, *next_rls, *prev_rls;
+ LineartRenderLineSegment *start_segment = 0, *end_segment = 0;
+ LineartRenderLineSegment *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, so we take care of them here. */
+ if (LRT_DOUBLE_CLOSE_ENOUGH(start, end)) {
+ return;
+ }
+ if (LRT_DOUBLE_CLOSE_ENOUGH(start, 1) || LRT_DOUBLE_CLOSE_ENOUGH(end, 0)) {
+ return;
+ }
+ if (UNLIKELY(start != start)) {
+ start = 0;
+ }
+ if (UNLIKELY(end != end)) {
+ end = 0;
+ }
+
+ if (start > end) {
+ double t = start;
+ start = end;
+ end = t;
+ }
+
+ /* Begin looking for starting position of the segment. */
+ /* Not using a list iteration macro because of it more clear when using for loops to iterate
+ * through the segments. */
+ for (rls = rl->segments.first; rls; rls = rls->next) {
+ if (LRT_DOUBLE_CLOSE_ENOUGH(rls->at, start)) {
+ start_segment = rls;
+ ns = start_segment;
+ break;
+ }
+ if (rls->next == NULL) {
+ break;
+ }
+ irls = rls->next;
+ if (irls->at > start + 1e-09 && start > rls->at) {
+ start_segment = irls;
+ ns = lineart_mem_aquire_thread(&rb->render_data_pool, sizeof(LineartRenderLineSegment));
+ break;
+ }
+ }
+ if (!start_segment && LRT_DOUBLE_CLOSE_ENOUGH(1, end)) {
+ untouched = 1;
+ }
+ for (rls = start_segment; 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)) {
+ end_segment = rls;
+ ns2 = end_segment;
+ 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)) {
+ end_segment = rls;
+ ns2 = end_segment;
+ untouched = 1;
+ break;
+ }
+ /* When an actual cut is needed in the line. */
+ if (rls->at > end) {
+ end_segment = rls;
+ ns2 = lineart_line_give_segment(rb);
+ break;
+ }
+ }
+
+ /* When an actual cut is needed in the line. */
+ if (ns == NULL) {
+ ns = lineart_line_give_segment(rb);
+ }
+ if (ns2 == NULL) {
+ if (untouched) {
+ ns2 = ns;
+ end_segment = ns2;
+ }
+ else {
+ ns2 = lineart_line_give_segment(rb);
+ }
+ }
+
+ /* Insert cutting points. */
+ if (start_segment) {
+ if (start_segment != ns) {
+ ns->occlusion = start_segment->prev ? (irls = start_segment->prev)->occlusion : 0;
+ BLI_insertlinkbefore(&rl->segments, (void *)start_segment, (void *)ns);
+ }
+ }
+ else {
+ ns->occlusion = (irls = rl->segments.last)->occlusion;
+ BLI_addtail(&rl->segments, ns);
+ }
+ if (end_segment) {
+ if (end_segment != ns2) {
+ ns2->occlusion = end_segment->prev ? (irls = end_segment->prev)->occlusion : 0;
+ BLI_insertlinkbefore(&rl->segments, (void *)end_segment, (void *)ns2);
+ }
+ }
+ else {
+ ns2->occlusion = (irls = rl->segments.last)->occlusion;
+ BLI_addtail(&rl->segments, ns2);
+ }
+
+ ns->at = start;
+ if (!untouched) {
+ ns2->at = end;
+ }
+ else {
+ ns2 = ns2->next;
+ }
+
+ /* Register 1 level of occlusion. */
+ for (rls = ns; rls && rls != ns2; rls = rls->next) {
+ rls->occlusion++;
+ rls->transparency_mask |= transparency_mask;
+ }
+
+ /* Reduce adjacent cutting points of the same level, which saves memory. */
+ char min_occ = 127;
+ prev_rls = NULL;
+ for (rls = rl->segments.first; rls; rls = next_rls) {
+ next_rls = rls->next;
+
+ if (prev_rls && prev_rls->occlusion == rls->occlusion &&
+ prev_rls->transparency_mask == rls->transparency_mask) {
+ BLI_remlink(&rl->segments, rls);
+ /* This put the node back to the render buffer, if more cut happens, these unused nodes get
+ * picked first. */
+ lineart_line_discard_segment(rb, rls);
+ continue;
+ }
+
+ min_occ = MIN2(min_occ, rls->occlusion);
+
+ prev_rls = rls;
+ }
+ rl->min_occ = min_occ;
+}
+
+/* To see if given line is connected to an adjacent intersection line. */
+BLI_INLINE bool lineart_occlusion_is_adjacent_intersection(LineartRenderLine *rl,
+ LineartRenderTriangle *rt)
+{
+ LineartRenderVertIntersection *l = (void *)rl->l;
+ LineartRenderVertIntersection *r = (void *)rl->r;
+ return ((l->base.flag && l->intersecting_with == (void *)rt) ||
+ (r->base.flag && r->intersecting_with == (void *)rt));
+}
+
+static void lineart_occlusion_single_line(LineartRenderBuffer *rb,
+ LineartRenderLine *rl,
+ int thread_id)
+{
+ double x = rl->l->fbcoord[0], y = rl->l->fbcoord[1];
+ LineartBoundingArea *ba = lineart_line_first_bounding_area(rb, rl);
+ LineartBoundingArea *nba = ba;
+ LineartRenderTriangleThread *rt;
+
+ /* These values are used for marching along the line. */
+ double l, r;
+ double k = (rl->r->fbcoord[1] - rl->l->fbcoord[1]) /
+ (rl->r->fbcoord[0] - rl->l->fbcoord[0] + 1e-30);
+ int positive_x = (rl->r->fbcoord[0] - rl->l->fbcoord[0]) > 0 ?
+ 1 :
+ (rl->r->fbcoord[0] == rl->l->fbcoord[0] ? 0 : -1);
+ int positive_y = (rl->r->fbcoord[1] - rl->l->fbcoord[1]) > 0 ?
+ 1 :
+ (rl->r->fbcoord[1] == rl->l->fbcoord[1] ? 0 : -1);
+
+ while (nba) {
+
+ LISTBASE_FOREACH (LinkData *, lip, &nba->linked_triangles) {
+ rt = lip->data;
+ /* If we are already testing the line in this thread, then don't do it. */
+ if (rt->testing[thread_id] == rl || (rt->base.flags & LRT_TRIANGLE_INTERSECTION_ONLY) ||
+ lineart_occlusion_is_adjacent_intersection(rl, (LineartRenderTriangle *)rt)) {
+ continue;
+ }
+ rt->testing[thread_id] = rl;
+ if (lineart_triangle_line_image_space_occlusion(&rb->lock_task,
+ (void *)rt,
+ rl,
+ rb->camera_pos,
+ rb->cam_is_persp,
+ rb->allow_overlapping_edges,
+ rb->view_projection,
+ rb->view_vector,
+ rb->shift_x,
+ rb->shift_y,
+ &l,
+ &r)) {
+ lineart_line_cut(rb, rl, l, r, rt->base.transparency_mask);
+ if (rl->min_occ > rb->max_occlusion_level) {
+ /* No need to caluclate any longer on this line because no level more than set value is
+ * going to show up in the rendered result. */
+ return;
+ }
+ }
+ }
+ /* Marching along rl->l to rl->r, searching each possible bounding areas it may touch. */
+ nba = lineart_bounding_area_next(nba, rl, x, y, k, positive_x, positive_y, &x, &y);
+ }
+}
+
+static int lineart_occlusion_make_task_info(LineartRenderBuffer *rb, LineartRenderTaskInfo *rti)
+{
+ LineartRenderLine *data;
+ int i;
+ int res = 0;
+
+ BLI_spin_lock(&rb->lock_task);
+
+#define LRT_ASSIGN_OCCLUSION_TASK(name) \
+ if (rb->name##_managed) { \
+ data = rb->name##_managed; \
+ rti->name = (void *)data; \
+ for (i = 0; i < LRT_THREAD_LINE_COUNT && data; i++) { \
+ data = data->next; \
+ } \
+ rti->name##_end = data; \
+ rb->name##_managed = data; \
+ res = 1; \
+ } \
+ else { \
+ rti->name = NULL; \
+ }
+
+ LRT_ASSIGN_OCCLUSION_TASK(contour);
+ LRT_ASSIGN_OCCLUSION_TASK(intersection);
+ LRT_ASSIGN_OCCLUSION_TASK(crease);
+ LRT_ASSIGN_OCCLUSION_TASK(material);
+ LRT_ASSIGN_OCCLUSION_TASK(edge_mark);
+
+#undef LRT_ASSIGN_OCCLUSION_TASK
+
+ BLI_spin_unlock(&rb->lock_task);
+
+ return res;
+}
+
+static void lineart_occlusion_worker(TaskPool *__restrict UNUSED(pool), LineartRenderTaskInfo *rti)
+{
+ LineartRenderBuffer *rb = rti->rb;
+ LineartRenderLine *lip;
+
+ while (lineart_occlusion_make_task_info(rb, rti)) {
+
+ for (lip = (void *)rti->contour; lip && lip != rti->contour_end; lip = lip->next) {
+ lineart_occlusion_single_line(rb, lip, rti->thread_id);
+ }
+
+ for (lip = (void *)rti->crease; lip && lip != rti->crease_end; lip = lip->next) {
+ lineart_occlusion_single_line(rb, lip, rti->thread_id);
+ }
+
+ for (lip = (void *)rti->intersection; lip && lip != rti->intersection_end; lip = lip->next) {
+ lineart_occlusion_single_line(rb, lip, rti->thread_id);
+ }
+
+ for (lip = (void *)rti->material; lip && lip != rti->material_end; lip = lip->next) {
+ lineart_occlusion_single_line(rb, lip, rti->thread_id);
+ }
+
+ for (lip = (void *)rti->edge_mark; lip && lip != rti->edge_mark_end; lip = lip->next) {
+ lineart_occlusion_single_line(rb, lip, rti->thread_id);
+ }
+ }
+}
+
+/* All internal functions starting with lineart_main_ is called inside
+ * MOD_lineart_compute_feature_lines function.
+ * This function handles all occlusion calculation. */
+static void lineart_main_occlusion_begin(LineartRenderBuffer *rb)
+{
+ int thread_count = rb->thread_count;
+ LineartRenderTaskInfo *rti = MEM_callocN(sizeof(LineartRenderTaskInfo) * thread_count,
+ "Task Pool");
+ int i;
+
+ rb->contour_managed = rb->contours;
+ rb->crease_managed = rb->crease_lines;
+ rb->intersection_managed = rb->intersection_lines;
+ rb->material_managed = rb->material_lines;
+ rb->edge_mark_managed = rb->edge_marks;
+
+ TaskPool *tp = BLI_task_pool_create(NULL, TASK_PRIORITY_HIGH);
+
+ for (i = 0; i < thread_count; i++) {
+ rti[i].thread_id = i;
+ rti[i].rb = rb;
+ BLI_task_pool_push(tp, (TaskRunFunction)lineart_occlusion_worker, &rti[i], 0, NULL);
+ }
+ BLI_task_pool_work_and_wait(tp);
+ BLI_task_pool_free(tp);
+
+ MEM_freeN(rti);
+}
+
+/* Test if v lies with in the triangle formed by v0, v1, and v2. Returns false when v is exactly on
+ * the edge.
+ * For v to be inside the triangle, it needs to be at the same side of v0->v1, v1->v2, and
+ * v2->v0, where the "side" is determined by checking the sign of cross(v1-v0, v1-v) and so on.
+ */
+static bool lineart_point_inside_triangle(const double v[2],
+ const double v0[2],
+ const double v1[2],
+ const double v2[2])
+{
+ double cl, c;
+
+ cl = (v0[0] - v[0]) * (v1[1] - v[1]) - (v0[1] - v[1]) * (v1[0] - v[0]);
+ c = cl;
+
+ cl = (v1[0] - v[0]) * (v2[1] - v[1]) - (v1[1] - v[1]) * (v2[0] - v[0]);
+ if (c * cl <= 0) {
+ return false;
+ }
+
+ c = cl;
+
+ cl = (v2[0] - v[0]) * (v0[1] - v[1]) - (v2[1] - v[1]) * (v0[0] - v[0]);
+ if (c * cl <= 0) {
+ return false;
+ }
+
+ c = cl;
+
+ cl = (v0[0] - v[0]) * (v1[1] - v[1]) - (v0[1] - v[1]) * (v1[0] - v[0]);
+ if (c * cl <= 0) {
+ return false;
+ }
+
+ return true;
+}
+
+static int lineart_point_on_line_segment(double v[2], double v0[2], double v1[2])
+{
+ /* c1!=c2 by default. */
+ double c1 = 1, c2 = 0;
+ double l0[2], l1[2];
+
+ sub_v2_v2v2_db(l0, v, v0);
+ sub_v2_v2v2_db(l1, v, v1);
+
+ if (v1[0] == v0[0] && v1[1] == v0[1]) {
+ return 0;
+ }
+
+ if (v1[0] - v0[0]) {
+ c1 = ratiod(v0[0], v1[0], v[0]);
+ }
+ else if (v[0] == v1[0]) {
+ c2 = ratiod(v0[1], v1[1], v[1]);
+ return (c2 >= 0 && c2 <= 1);
+ }
+
+ if (v1[1] - v0[1]) {
+ c2 = ratiod(v0[1], v1[1], v[1]);
+ }
+ else if (v[1] == v1[1]) {
+ c1 = ratiod(v0[0], v1[0], v[0]);
+ return (c1 >= 0 && c1 <= 1);
+ }
+
+ if (LRT_DOUBLE_CLOSE_ENOUGH(c1, c2) && c1 >= 0 && c1 <= 1) {
+ return 1;
+ }
+
+ return 0;
+}
+
+/* Same algorithm as lineart_point_inside_triangle(), but returns differently:
+ * 0-outside 1-on the edge 2-inside. */
+static int lineart_point_triangle_relation(double v[2], double v0[2], double v1[2], double v2[2])
+{
+ double cl, c;
+ double r;
+ if (lineart_point_on_line_segment(v, v0, v1) || lineart_point_on_line_segment(v, v1, v2) ||
+ lineart_point_on_line_segment(v, v2, v0)) {
+ return 1;
+ }
+
+ cl = (v0[0] - v[0]) * (v1[1] - v[1]) - (v0[1] - v[1]) * (v1[0] - v[0]);
+ c = cl;
+
+ cl = (v1[0] - v[0]) * (v2[1] - v[1]) - (v1[1] - v[1]) * (v2[0] - v[0]);
+ if ((r = c * cl) < 0) {
+ return 0;
+ }
+
+ c = cl;
+
+ cl = (v2[0] - v[0]) * (v0[1] - v[1]) - (v2[1] - v[1]) * (v0[0] - v[0]);
+ if ((r = c * cl) < 0) {
+ return 0;
+ }
+
+ c = cl;
+
+ cl = (v0[0] - v[0]) * (v1[1] - v[1]) - (v0[1] - v[1]) * (v1[0] - v[0]);
+ if ((r = c * cl) < 0) {
+ return 0;
+ }
+
+ if (r == 0) {
+ return 1;
+ }
+
+ return 2;
+}
+
+/* Similar with lineart_point_inside_triangle, but in 3d.
+ * Returns false when not co-plannar. */
+static bool lineart_point_inside_triangle3d(double v[3], double v0[3], double v1[3], double v2[3])
+{
+ double l[3], r[3];
+ double N1[3], N2[3];
+ double d;
+
+ sub_v3_v3v3_db(l, v1, v0);
+ sub_v3_v3v3_db(r, v, v1);
+ cross_v3_v3v3_db(N1, l, r);
+
+ sub_v3_v3v3_db(l, v2, v1);
+ sub_v3_v3v3_db(r, v, v2);
+ cross_v3_v3v3_db(N2, l, r);
+
+ if ((d = dot_v3v3_db(N1, N2)) < 0) {
+ return false;
+ }
+
+ sub_v3_v3v3_db(l, v0, v2);
+ sub_v3_v3v3_db(r, v, v0);
+ cross_v3_v3v3_db(N1, l, r);
+
+ if ((d = dot_v3v3_db(N1, N2)) < 0) {
+ return false;
+ }
+
+ sub_v3_v3v3_db(l, v1, v0);
+ sub_v3_v3v3_db(r, v, v1);
+ cross_v3_v3v3_db(N2, l, r);
+
+ if ((d = dot_v3v3_db(N1, N2)) < 0) {
+ return false;
+ }
+
+ return true;
+}
+
+/* The following lineart_memory_get_XXX_space functions are for allocating new memory for some
+ * modified geometries in the culling stage. */
+static LineartRenderElementLinkNode *lineart_memory_get_triangle_space(LineartRenderBuffer *rb)
+{
+ LineartRenderElementLinkNode *reln;
+
+ /* We don't need to allocate a whole bunch of triangles because the amount of clipped triangles
+ * are relatively small. */
+ LineartRenderTriangle *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(LineartRenderElementLinkNode));
+ reln->element_count = 64;
+ reln->flags |= LRT_ELEMENT_IS_ADDITIONAL;
+
+ return reln;
+}
+
+static LineartRenderElementLinkNode *lineart_memory_get_vert_space(LineartRenderBuffer *rb)
+{
+ LineartRenderElementLinkNode *reln;
+
+ LineartRenderVert *render_vertices = lineart_mem_aquire(&rb->render_data_pool,
+ sizeof(LineartRenderVert) * 64);
+
+ reln = lineart_list_append_pointer_pool_sized(&rb->vertex_buffer_pointers,
+ &rb->render_data_pool,
+ render_vertices,
+ sizeof(LineartRenderElementLinkNode));
+ reln->element_count = 64;
+ reln->flags |= LRT_ELEMENT_IS_ADDITIONAL;
+
+ return reln;
+}
+
+static LineartRenderElementLinkNode *lineart_memory_get_line_space(LineartRenderBuffer *rb)
+{
+ LineartRenderElementLinkNode *reln;
+
+ LineartRenderLine *render_lines = lineart_mem_aquire(&rb->render_data_pool,
+ sizeof(LineartRenderLine) * 64);
+
+ reln = lineart_list_append_pointer_pool_sized(&rb->line_buffer_pointers,
+ &rb->render_data_pool,
+ render_lines,
+ sizeof(LineartRenderElementLinkNode));
+ reln->element_count = 64;
+ reln->crease_threshold = rb->crease_threshold;
+ reln->flags |= LRT_ELEMENT_IS_ADDITIONAL;
+
+ return reln;
+}
+
+static void lineart_triangle_post(LineartRenderTriangle *rt, LineartRenderTriangle *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(LineartRenderTriangle *rt, unsigned char flag)
+{
+ unsigned char intersection_only = (rt->flags & LRT_TRIANGLE_INTERSECTION_ONLY);
+ rt->flags = flag;
+ rt->flags |= intersection_only;
+}
+
+static bool lineart_line_match(LineartRenderTriangle *rt, LineartRenderLine *rl, int v1, int v2)
+{
+ return ((rt->v[v1] == rl->l && rt->v[v2] == rl->r) ||
+ (rt->v[v2] == rl->l && rt->v[v1] == rl->r));
+}
+
+/* Does near-plane cut on 1 triangle only. When cutting with far-plane, the camera vectors gets
+ * reversed by the caller so don't need to implement one in a different direction. */
+static void lineart_triangle_cull_single(LineartRenderBuffer *rb,
+ LineartRenderTriangle *rt,
+ int in0,
+ int in1,
+ int in2,
+ double *cam_pos,
+ double *view_dir,
+ bool allow_boundaries,
+ double (*vp)[4],
+ Object *ob,
+ int *r_v_count,
+ int *r_l_count,
+ int *r_t_count,
+ LineartRenderElementLinkNode *veln,
+ LineartRenderElementLinkNode *leln,
+ LineartRenderElementLinkNode *teln)
+{
+ double vv1[3], vv2[3], dot1, dot2;
+ double a;
+ int v_count = *r_v_count;
+ int l_count = *r_l_count;
+ int t_count = *r_t_count;
+ int l_obi, r_obi;
+ char new_flag = 0;
+
+ LineartRenderLine *new_rl, *rl, *old_rl;
+ LineartRenderLineSegment *rls;
+ LineartRenderTriangleAdjacent *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;
+
+ LineartRenderVert *rv = &((LineartRenderVert *)veln->pointer)[v_count];
+ LineartRenderTriangle *rt1 = (void *)(((unsigned char *)teln->pointer) +
+ rb->triangle_size * t_count);
+ LineartRenderTriangle *rt2 = (void *)(((unsigned char *)teln->pointer) +
+ rb->triangle_size * (t_count + 1));
+
+ new_rl = &((LineartRenderLine *)leln->pointer)[l_count];
+ /* Init rl to the last rl entry. */
+ rl = new_rl;
+
+#define INCREASE_RL \
+ l_count++; \
+ l_obi = rl->l_obindex; \
+ r_obi = rl->r_obindex; \
+ new_rl = &((LineartRenderLine *)leln->pointer)[l_count]; \
+ rl = new_rl; \
+ rl->l_obindex = l_obi; \
+ rl->r_obindex = r_obi; \
+ rls = lineart_mem_aquire(&rb->render_data_pool, sizeof(LineartRenderLineSegment)); \
+ BLI_addtail(&rl->segments, rls);
+
+#define SELECT_RL(rl_num, llink, rlink, newrt) \
+ if (rta->rl[rl_num]) { \
+ old_rl = rta->rl[rl_num]; \
+ new_flag = old_rl->flags; \
+ old_rl->flags = LRT_EDGE_FLAG_CHAIN_PICKED; \
+ INCREASE_RL \
+ rl->l = (llink); \
+ rl->r = (rlink); \
+ rl->flags = new_flag; \
+ rl->object_ref = ob; \
+ rl->tl = ((old_rl->tl == rt) ? (newrt) : (old_rl->tl)); \
+ rl->tr = ((old_rl->tr == rt) ? (newrt) : (old_rl->tr)); \
+ lineart_add_line_to_list(rb, rl); \
+ }
+
+#define RELINK_RL(rl_num, newrt) \
+ if (rta->rl[rl_num]) { \
+ old_rl = rta->rl[rl_num]; \
+ old_rl->tl = ((old_rl->tl == rt) ? (newrt) : (old_rl->tl)); \
+ old_rl->tr = ((old_rl->tr == rt) ? (newrt) : (old_rl->tr)); \
+ }
+
+#define REMOVE_TRIANGLE_RL \
+ if (rta->rl[0]) { \
+ rta->rl[0]->flags = LRT_EDGE_FLAG_CHAIN_PICKED; \
+ } \
+ if (rta->rl[1]) { \
+ rta->rl[1]->flags = LRT_EDGE_FLAG_CHAIN_PICKED; \
+ } \
+ if (rta->rl[2]) { \
+ rta->rl[2]->flags = LRT_EDGE_FLAG_CHAIN_PICKED; \
+ }
+
+ switch (in0 + in1 + in2) {
+ case 0: /* Triangle is visible. Ignore this triangle. */
+ return;
+ case 3:
+ /* Triangle completely behind near plane, throw it away
+ * also remove render lines form being computed. */
+ lineart_triangle_set_cull_flag(rt, LRT_CULL_DISCARD);
+ REMOVE_TRIANGLE_RL
+ return;
+ case 2:
+ /* Two points behind near plane, cut those and
+ * generate 2 new points, 3 lines and 1 triangle. */
+ lineart_triangle_set_cull_flag(rt, LRT_CULL_USED);
+
+ /* (!in0) means "when point 0 is visible".
+ * conditons for point 1, 2 are the same idea.
+ * 1-----|-------0
+ * | | ---
+ * | |---
+ * | ---|
+ * 2-- |
+ * (near)---------->(far)
+ * Will become:
+ * |N******0
+ * |* ***
+ * |N**
+ * |
+ * |
+ * (near)---------->(far)
+ */
+ if (!in0) {
+
+ /* Cut point for line 2---|-----0. */
+ sub_v3_v3v3_db(vv1, rt->v[0]->gloc, cam_pos);
+ sub_v3_v3v3_db(vv2, cam_pos, rt->v[2]->gloc);
+ dot1 = dot_v3v3_db(vv1, view_dir);
+ dot2 = dot_v3v3_db(vv2, view_dir);
+ a = dot1 / (dot1 + dot2);
+ /* Assign it to a new point. */
+ interp_v3_v3v3_db(rv[0].gloc, rt->v[0]->gloc, rt->v[2]->gloc, a);
+ mul_v4_m4v3_db(rv[0].fbcoord, vp, rv[0].gloc);
+ rv[0].index = rt->v[2]->index;
+
+ /* Cut point for line 1---|-----0. */
+ sub_v3_v3v3_db(vv1, rt->v[0]->gloc, cam_pos);
+ sub_v3_v3v3_db(vv2, cam_pos, rt->v[1]->gloc);
+ dot1 = dot_v3v3_db(vv1, view_dir);
+ dot2 = dot_v3v3_db(vv2, view_dir);
+ a = dot1 / (dot1 + dot2);
+ /* Assign it to another new point. */
+ interp_v3_v3v3_db(rv[1].gloc, rt->v[0]->gloc, rt->v[1]->gloc, a);
+ mul_v4_m4v3_db(rv[1].fbcoord, vp, rv[1].gloc);
+ rv[1].index = rt->v[1]->index;
+
+ /* New line connecting two new points. */
+ INCREASE_RL
+ if (allow_boundaries) {
+ rl->flags = LRT_EDGE_FLAG_CONTOUR;
+ lineart_prepend_line_direct(&rb->contours, rl);
+ }
+ /* Note: inverting rl->l/r (left/right point) doesn't matter as long as
+ * rt->rl and rt->v has the same sequence. and the winding direction
+ * can be either CW or CCW but needs to be consistent throughout the calculation.
+ */
+ rl->l = &rv[1];
+ rl->r = &rv[0];
+ /* Only one adjacent triangle, because the other side is the near plane. */
+ /* Use tl or tr doesn't matter. */
+ rl->tl = rt1;
+ rl->object_ref = ob;
+
+ /* New line connecting original point 0 and a new point, only when it's a selected line. */
+ SELECT_RL(2, rt->v[0], &rv[0], rt1)
+ /* New line connecting original point 0 and another new point. */
+ SELECT_RL(0, rt->v[0], &rv[1], rt1)
+
+ /* Re-assign triangle point array to two new points. */
+ rt1->v[0] = rt->v[0];
+ rt1->v[1] = &rv[1];
+ rt1->v[2] = &rv[0];
+
+ lineart_triangle_post(rt1, rt);
+
+ v_count += 2;
+ t_count += 1;
+ }
+ else if (!in2) {
+ sub_v3_v3v3_db(vv1, rt->v[2]->gloc, cam_pos);
+ sub_v3_v3v3_db(vv2, cam_pos, rt->v[0]->gloc);
+ dot1 = dot_v3v3_db(vv1, view_dir);
+ dot2 = dot_v3v3_db(vv2, view_dir);
+ a = dot1 / (dot1 + dot2);
+ interp_v3_v3v3_db(rv[0].gloc, rt->v[2]->gloc, rt->v[0]->gloc, a);
+ mul_v4_m4v3_db(rv[0].fbcoord, vp, rv[0].gloc);
+ rv[0].index = rt->v[0]->index;
+
+ sub_v3_v3v3_db(vv1, rt->v[2]->gloc, cam_pos);
+ sub_v3_v3v3_db(vv2, cam_pos, rt->v[1]->gloc);
+ dot1 = dot_v3v3_db(vv1, view_dir);
+ dot2 = dot_v3v3_db(vv2, view_dir);
+ a = dot1 / (dot1 + dot2);
+ interp_v3_v3v3_db(rv[1].gloc, rt->v[2]->gloc, rt->v[1]->gloc, a);
+ mul_v4_m4v3_db(rv[1].fbcoord, vp, rv[1].gloc);
+ rv[1].index = rt->v[1]->index;
+
+ INCREASE_RL
+ if (allow_boundaries) {
+ rl->flags = LRT_EDGE_FLAG_CONTOUR;
+ lineart_prepend_line_direct(&rb->contours, rl);
+ }
+ rl->l = &rv[0];
+ rl->r = &rv[1];
+ rl->tl = rt1;
+ rl->object_ref = ob;
+
+ SELECT_RL(2, rt->v[2], &rv[0], rt1)
+ SELECT_RL(1, rt->v[2], &rv[1], rt1)
+
+ rt1->v[0] = &rv[0];
+ rt1->v[1] = &rv[1];
+ rt1->v[2] = rt->v[2];
+
+ lineart_triangle_post(rt1, rt);
+
+ v_count += 2;
+ t_count += 1;
+ }
+ else if (!in1) {
+ sub_v3_v3v3_db(vv1, rt->v[1]->gloc, cam_pos);
+ sub_v3_v3v3_db(vv2, cam_pos, rt->v[2]->gloc);
+ dot1 = dot_v3v3_db(vv1, view_dir);
+ dot2 = dot_v3v3_db(vv2, view_dir);
+ a = dot1 / (dot1 + dot2);
+ interp_v3_v3v3_db(rv[0].gloc, rt->v[1]->gloc, rt->v[2]->gloc, a);
+ mul_v4_m4v3_db(rv[0].fbcoord, vp, rv[0].gloc);
+ rv[0].index = rt->v[2]->index;
+
+ sub_v3_v3v3_db(vv1, rt->v[1]->gloc, cam_pos);
+ sub_v3_v3v3_db(vv2, cam_pos, rt->v[0]->gloc);
+ dot1 = dot_v3v3_db(vv1, view_dir);
+ dot2 = dot_v3v3_db(vv2, view_dir);
+ a = dot1 / (dot1 + dot2);
+ interp_v3_v3v3_db(rv[1].gloc, rt->v[1]->gloc, rt->v[0]->gloc, a);
+ mul_v4_m4v3_db(rv[1].fbcoord, vp, rv[1].gloc);
+ rv[1].index = rt->v[0]->index;
+
+ INCREASE_RL
+ if (allow_boundaries) {
+ rl->flags = LRT_EDGE_FLAG_CONTOUR;
+ lineart_prepend_line_direct(&rb->contours, rl);
+ }
+ rl->l = &rv[1];
+ rl->r = &rv[0];
+ rl->tl = rt1;
+ rl->object_ref = ob;
+
+ SELECT_RL(1, rt->v[1], &rv[0], rt1)
+ SELECT_RL(0, rt->v[1], &rv[1], rt1)
+
+ rt1->v[0] = &rv[0];
+ rt1->v[1] = rt->v[1];
+ rt1->v[2] = &rv[1];
+
+ lineart_triangle_post(rt1, rt);
+
+ v_count += 2;
+ t_count += 1;
+ }
+ break;
+ case 1:
+ /* One point behind near plane, cut those and
+ * generate 2 new points, 4 lines and 2 triangles. */
+ lineart_triangle_set_cull_flag(rt, LRT_CULL_USED);
+
+ /* (in0) means "when point 0 is invisible".
+ * conditons for point 1, 2 are the same idea.
+ * 0------|----------1
+ * -- | |
+ * ---| |
+ * |-- |
+ * | --- |
+ * | --- |
+ * | --2
+ * (near)---------->(far)
+ * Will become:
+ * |N*********1
+ * |* *** |
+ * |* *** |
+ * |N** |
+ * | *** |
+ * | *** |
+ * | **2
+ * (near)---------->(far)
+ */
+ if (in0) {
+ /* Cut point for line 0---|------1. */
+ sub_v3_v3v3_db(vv1, rt->v[1]->gloc, cam_pos);
+ sub_v3_v3v3_db(vv2, cam_pos, rt->v[0]->gloc);
+ dot1 = dot_v3v3_db(vv1, view_dir);
+ dot2 = dot_v3v3_db(vv2, view_dir);
+ a = dot2 / (dot1 + dot2);
+ /* Assign to a new point. */
+ interp_v3_v3v3_db(rv[0].gloc, rt->v[0]->gloc, rt->v[1]->gloc, a);
+ mul_v4_m4v3_db(rv[0].fbcoord, vp, rv[0].gloc);
+ rv[0].index = rt->v[0]->index;
+
+ /* Cut point for line 0---|------2. */
+ sub_v3_v3v3_db(vv1, rt->v[2]->gloc, cam_pos);
+ sub_v3_v3v3_db(vv2, cam_pos, rt->v[0]->gloc);
+ dot1 = dot_v3v3_db(vv1, view_dir);
+ dot2 = dot_v3v3_db(vv2, view_dir);
+ a = dot2 / (dot1 + dot2);
+ /* Assign to aother new point. */
+ interp_v3_v3v3_db(rv[1].gloc, rt->v[0]->gloc, rt->v[2]->gloc, a);
+ mul_v4_m4v3_db(rv[1].fbcoord, vp, rv[1].gloc);
+ rv[1].index = rt->v[0]->index;
+
+ /* New line connects two new points. */
+ INCREASE_RL
+ if (allow_boundaries) {
+ rl->flags = LRT_EDGE_FLAG_CONTOUR;
+ lineart_prepend_line_direct(&rb->contours, rl);
+ }
+ rl->l = &rv[1];
+ rl->r = &rv[0];
+ rl->tl = rt1;
+ rl->object_ref = ob;
+
+ /* New line connects new point 0 and old point 1,
+ * this is a border line.
+ */
+
+ SELECT_RL(0, rt->v[1], &rv[0], rt1)
+ SELECT_RL(2, rt->v[2], &rv[1], rt2)
+ RELINK_RL(1, rt2)
+
+ /* We now have one triangle closed. */
+ rt1->v[0] = rt->v[1];
+ rt1->v[1] = &rv[1];
+ rt1->v[2] = &rv[0];
+ /* Close the second triangle. */
+ rt2->v[0] = &rv[1];
+ rt2->v[1] = rt->v[1];
+ rt2->v[2] = rt->v[2];
+
+ lineart_triangle_post(rt1, rt);
+ lineart_triangle_post(rt2, rt);
+
+ v_count += 2;
+ t_count += 2;
+ }
+ else if (in1) {
+
+ sub_v3_v3v3_db(vv1, rt->v[1]->gloc, cam_pos);
+ sub_v3_v3v3_db(vv2, cam_pos, rt->v[2]->gloc);
+ dot1 = dot_v3v3_db(vv1, view_dir);
+ dot2 = dot_v3v3_db(vv2, view_dir);
+ a = dot1 / (dot1 + dot2);
+ interp_v3_v3v3_db(rv[0].gloc, rt->v[1]->gloc, rt->v[2]->gloc, a);
+ mul_v4_m4v3_db(rv[0].fbcoord, vp, rv[0].gloc);
+ rv[0].index = rt->v[1]->index;
+
+ sub_v3_v3v3_db(vv1, rt->v[1]->gloc, cam_pos);
+ sub_v3_v3v3_db(vv2, cam_pos, rt->v[0]->gloc);
+ dot1 = dot_v3v3_db(vv1, view_dir);
+ dot2 = dot_v3v3_db(vv2, view_dir);
+ a = dot1 / (dot1 + dot2);
+ interp_v3_v3v3_db(rv[1].gloc, rt->v[1]->gloc, rt->v[0]->gloc, a);
+ mul_v4_m4v3_db(rv[1].fbcoord, vp, rv[1].gloc);
+ rv[1].index = rt->v[1]->index;
+
+ INCREASE_RL
+ if (allow_boundaries) {
+ rl->flags = LRT_EDGE_FLAG_CONTOUR;
+ lineart_prepend_line_direct(&rb->contours, rl);
+ }
+ rl->l = &rv[1];
+ rl->r = &rv[0];
+ rl->tl = rt1;
+ rl->object_ref = ob;
+
+ SELECT_RL(1, rt->v[2], &rv[0], rt1)
+ SELECT_RL(0, rt->v[0], &rv[1], rt2)
+ RELINK_RL(2, rt2)
+
+ rt1->v[0] = rt->v[2];
+ rt1->v[1] = &rv[1];
+ rt1->v[2] = &rv[0];
+
+ rt2->v[0] = &rv[1];
+ rt2->v[1] = rt->v[2];
+ rt2->v[2] = rt->v[0];
+
+ lineart_triangle_post(rt1, rt);
+ lineart_triangle_post(rt2, rt);
+
+ v_count += 2;
+ t_count += 2;
+ }
+ else if (in2) {
+
+ sub_v3_v3v3_db(vv1, rt->v[2]->gloc, cam_pos);
+ sub_v3_v3v3_db(vv2, cam_pos, rt->v[0]->gloc);
+ dot1 = dot_v3v3_db(vv1, view_dir);
+ dot2 = dot_v3v3_db(vv2, view_dir);
+ a = dot1 / (dot1 + dot2);
+ interp_v3_v3v3_db(rv[0].gloc, rt->v[2]->gloc, rt->v[0]->gloc, a);
+ mul_v4_m4v3_db(rv[0].fbcoord, vp, rv[0].gloc);
+ rv[0].index = rt->v[2]->index;
+
+ sub_v3_v3v3_db(vv1, rt->v[2]->gloc, cam_pos);
+ sub_v3_v3v3_db(vv2, cam_pos, rt->v[1]->gloc);
+ dot1 = dot_v3v3_db(vv1, view_dir);
+ dot2 = dot_v3v3_db(vv2, view_dir);
+ a = dot1 / (dot1 + dot2);
+ interp_v3_v3v3_db(rv[1].gloc, rt->v[2]->gloc, rt->v[1]->gloc, a);
+ mul_v4_m4v3_db(rv[1].fbcoord, vp, rv[1].gloc);
+ rv[1].index = rt->v[2]->index;
+
+ INCREASE_RL
+ if (allow_boundaries) {
+ rl->flags = LRT_EDGE_FLAG_CONTOUR;
+ lineart_prepend_line_direct(&rb->contours, rl);
+ }
+ rl->l = &rv[1];
+ rl->r = &rv[0];
+ rl->tl = rt1;
+ rl->object_ref = ob;
+
+ SELECT_RL(2, rt->v[0], &rv[0], rt1)
+ SELECT_RL(1, rt->v[1], &rv[1], rt2)
+ RELINK_RL(0, rt2)
+
+ rt1->v[0] = rt->v[0];
+ rt1->v[1] = &rv[1];
+ rt1->v[2] = &rv[0];
+
+ rt2->v[0] = &rv[1];
+ rt2->v[1] = rt->v[0];
+ rt2->v[2] = rt->v[1];
+
+ lineart_triangle_post(rt1, rt);
+ lineart_triangle_post(rt2, rt);
+
+ v_count += 2;
+ t_count += 2;
+ }
+ break;
+ }
+ *r_v_count = v_count;
+ *r_l_count = l_count;
+ *r_t_count = t_count;
+
+#undef INCREASE_RL
+#undef SELECT_RL
+#undef RELINK_RL
+#undef REMOVE_TRIANGLE_RL
+}
+
+/* This function cuts triangles with near- or far-plane. Setting clip_far = true for cutting with
+ * far-plane. For triangles that's crossing the plane, it will generate new 1 or 2 triangles with
+ * new topology that represents the trimmed triangle. (which then became a triangle or a square
+ * formed by two triangles)
+ */
+static void lineart_main_cull_triangles(LineartRenderBuffer *rb, bool clip_far)
+{
+ LineartRenderTriangle *rt;
+ LineartRenderElementLinkNode *veln, *teln, *leln;
+ double(*vp)[4] = rb->view_projection;
+ int i;
+ int v_count = 0, t_count = 0, l_count = 0;
+ Object *ob;
+ bool allow_boundaries = rb->allow_boundaries;
+ double cam_pos[3];
+ double clip_start = rb->near_clip, clip_end = rb->far_clip;
+ double view_dir[3], clip_advance[3];
+
+ copy_v3_v3_db(view_dir, rb->view_vector);
+ copy_v3_v3_db(clip_advance, rb->view_vector);
+ copy_v3_v3_db(cam_pos, rb->camera_pos);
+
+ if (clip_far) {
+ /* Move starting point to end plane. */
+ mul_v3db_db(clip_advance, -clip_end);
+ add_v3_v3_db(cam_pos, clip_advance);
+
+ /* "reverse looking". */
+ mul_v3db_db(view_dir, -1.0f);
+ }
+ else {
+ /* Clip Near. */
+ mul_v3db_db(clip_advance, -clip_start);
+ add_v3_v3_db(cam_pos, clip_advance);
+ }
+
+ veln = lineart_memory_get_vert_space(rb);
+ teln = lineart_memory_get_triangle_space(rb);
+ leln = lineart_memory_get_line_space(rb);
+
+ /* Additional memory space for storing generated points and triangles. */
+#define LRT_CULL_ENSURE_MEMORY \
+ if (v_count > 60) { \
+ veln->element_count = v_count; \
+ veln = lineart_memory_get_vert_space(rb); \
+ v_count = 0; \
+ } \
+ if (t_count > 60) { \
+ teln->element_count = t_count; \
+ teln = lineart_memory_get_triangle_space(rb); \
+ t_count = 0; \
+ } \
+ if (l_count > 60) { \
+ leln->element_count = l_count; \
+ leln = lineart_memory_get_line_space(rb); \
+ l_count = 0; \
+ }
+
+#define LRT_CULL_DECIDE_INSIDE \
+ /* These three represents points that are in the clipping range or not*/ \
+ in0 = 0, in1 = 0, in2 = 0; \
+ if (clip_far) { \
+ /* Point outside far plane. */ \
+ if (rt->v[0]->fbcoord[use_w] > clip_end) { \
+ in0 = 1; \
+ } \
+ if (rt->v[1]->fbcoord[use_w] > clip_end) { \
+ in1 = 1; \
+ } \
+ if (rt->v[2]->fbcoord[use_w] > clip_end) { \
+ in2 = 1; \
+ } \
+ } \
+ else { \
+ /* Point inside near plane. */ \
+ if (rt->v[0]->fbcoord[use_w] < clip_start) { \
+ in0 = 1; \
+ } \
+ if (rt->v[1]->fbcoord[use_w] < clip_start) { \
+ in1 = 1; \
+ } \
+ if (rt->v[2]->fbcoord[use_w] < clip_start) { \
+ in2 = 1; \
+ } \
+ }
+
+ int use_w = 3;
+ int in0 = 0, in1 = 0, in2 = 0;
+
+ if (!rb->cam_is_persp) {
+ clip_start = -1;
+ clip_end = 1;
+ use_w = 2;
+ }
+
+ /* Then go through all the other triangles. */
+ LISTBASE_FOREACH (LineartRenderElementLinkNode *, reln, &rb->triangle_buffer_pointers) {
+ if (reln->flags & LRT_ELEMENT_IS_ADDITIONAL) {
+ continue;
+ }
+ ob = reln->object_ref;
+ for (i = 0; i < reln->element_count; i++) {
+ /* Select the triangle in the array. */
+ rt = (void *)(((unsigned char *)reln->pointer) + rb->triangle_size * i);
+
+ LRT_CULL_DECIDE_INSIDE
+ LRT_CULL_ENSURE_MEMORY
+ lineart_triangle_cull_single(rb,
+ rt,
+ in0,
+ in1,
+ in2,
+ cam_pos,
+ view_dir,
+ allow_boundaries,
+ vp,
+ ob,
+ &v_count,
+ &l_count,
+ &t_count,
+ veln,
+ leln,
+ teln);
+ }
+ teln->element_count = t_count;
+ veln->element_count = v_count;
+ }
+
+#undef LRT_CULL_ENSURE_MEMORY
+#undef LRT_CULL_DECIDE_INSIDE
+}
+
+/* Adjacent data is only used during the initial stages of computing. So we can free it using this
+ * function when it is not needed anymore. */
+static void lineart_main_free_adjacent_data(LineartRenderBuffer *rb)
+{
+ LinkData *ld;
+ while ((ld = BLI_pophead(&rb->triangle_adjacent_pointers)) != NULL) {
+ MEM_freeN(ld->data);
+ }
+ LISTBASE_FOREACH (LineartRenderElementLinkNode *, reln, &rb->triangle_buffer_pointers) {
+ LineartRenderTriangle *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 = (LineartRenderTriangle *)(((unsigned char *)rt) + rb->triangle_size);
+ }
+ }
+}
+
+static void lineart_main_perspective_division(LineartRenderBuffer *rb)
+{
+ LineartRenderVert *rv;
+ int i;
+
+ if (!rb->cam_is_persp) {
+ return;
+ }
+
+ LISTBASE_FOREACH (LineartRenderElementLinkNode *, 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, LineartRenderVert *RvBuf, double (*mv_mat)[4], double (*mvp_mat)[4])
+{
+ double co[4];
+ LineartRenderVert *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 LineartRenderTriangle, we need an access helper. See
+ * LineartRenderTriangleThread for more info. */
+static LineartRenderTriangle *lineart_triangle_from_index(LineartRenderBuffer *rb,
+ LineartRenderTriangle *rt_array,
+ int index)
+{
+ char *b = (char *)rt_array;
+ b += (index * rb->triangle_size);
+ return (LineartRenderTriangle *)b;
+}
+
+static char lineart_identify_feature_line(LineartRenderBuffer *rb,
+ BMEdge *e,
+ LineartRenderTriangle *rt_array,
+ LineartRenderVert *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;
+ }
+
+ LineartRenderTriangle *rt1, *rt2;
+ LineartRenderVert *l;
+
+ /* The mesh should already be triangulated now, so we can assume each face is a triangle. */
+ rt1 = lineart_triangle_from_index(rb, rt_array, BM_elem_index_get(ll->f));
+ rt2 = lineart_triangle_from_index(rb, rt_array, BM_elem_index_get(lr->f));
+
+ l = &rv_array[BM_elem_index_get(e->v1)];
+
+ double vv[3];
+ double *view_vector = vv;
+ double dot_1 = 0, dot_2 = 0;
+ double result;
+ FreestyleEdge *fe;
+
+ if (rb->cam_is_persp) {
+ sub_v3_v3v3_db(view_vector, l->gloc, rb->camera_pos);
+ }
+ else {
+ view_vector = rb->view_vector;
+ }
+
+ dot_1 = dot_v3v3_db(view_vector, rt1->gn);
+ dot_2 = dot_v3v3_db(view_vector, rt2->gn);
+
+ if ((result = dot_1 * dot_2) < 0 && (dot_1 + dot_2)) {
+ return LRT_EDGE_FLAG_CONTOUR;
+ }
+
+ if (rb->use_crease && (dot_v3v3_db(rt1->gn, rt2->gn) < crease_threshold)) {
+ if (!no_crease) {
+ return LRT_EDGE_FLAG_CREASE;
+ }
+ }
+ else if (rb->use_material && (ll->f->mat_nr != lr->f->mat_nr)) {
+ return LRT_EDGE_FLAG_MATERIAL;
+ }
+ else if (count_freestyle && rb->use_edge_marks) {
+ fe = CustomData_bmesh_get(&bm_if_freestyle->edata, e->head.data, CD_FREESTYLE_EDGE);
+ if (fe->flag & FREESTYLE_EDGE_MARK) {
+ return LRT_EDGE_FLAG_EDGE_MARK;
+ }
+ }
+ return 0;
+}
+
+static void lineart_add_line_to_list(LineartRenderBuffer *rb, LineartRenderLine *rl)
+{
+ switch (rl->flags) {
+ case LRT_EDGE_FLAG_CONTOUR:
+ lineart_prepend_line_direct(&rb->contours, rl);
+ break;
+ case LRT_EDGE_FLAG_CREASE:
+ lineart_prepend_line_direct(&rb->crease_lines, rl);
+ break;
+ case LRT_EDGE_FLAG_MATERIAL:
+ lineart_prepend_line_direct(&rb->material_lines, rl);
+ break;
+ case LRT_EDGE_FLAG_EDGE_MARK:
+ lineart_prepend_line_direct(&rb->edge_marks, rl);
+ break;
+ case LRT_EDGE_FLAG_INTERSECTION:
+ lineart_prepend_line_direct(&rb->intersection_lines, rl);
+ break;
+ }
+}
+
+static void lineart_triangle_adjacent_assign(LineartRenderTriangle *rt,
+ LineartRenderTriangleAdjacent *rta,
+ LineartRenderLine *rl)
+{
+ if (lineart_line_match(rt, rl, 0, 1)) {
+ rta->rl[0] = rl;
+ }
+ else if (lineart_line_match(rt, rl, 1, 2)) {
+ rta->rl[1] = rl;
+ }
+ else if (lineart_line_match(rt, rl, 2, 0)) {
+ rta->rl[2] = rl;
+ }
+}
+
+static void lineart_geometry_object_load(Depsgraph *dg,
+ Object *ob,
+ double (*mv_mat)[4],
+ double (*mvp_mat)[4],
+ LineartRenderBuffer *rb,
+ int override_usage,
+ int *global_vindex)
+{
+ BMesh *bm;
+ BMVert *v;
+ BMFace *f;
+ BMEdge *e;
+ BMLoop *loop;
+ LineartRenderLine *rl;
+ LineartRenderTriangle *rt;
+ LineartRenderTriangleAdjacent *orta;
+ double new_mvp[4][4], new_mv[4][4], normal[4][4];
+ float imat[4][4];
+ LineartRenderElementLinkNode *reln;
+ LineartRenderVert *orv;
+ LineartRenderLine *orl;
+ LineartRenderTriangle *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(LineartRenderVert) * 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(LineartRenderElementLinkNode));
+ 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(LineartRenderElementLinkNode));
+ 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(LineartRenderTriangleAdjacent) * bm->totface,
+ "LineartRenderTriangleAdjacent");
+ /* 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 = (LineartRenderTriangle *)(((unsigned char *)rt) + rb->triangle_size);
+ }
+
+ /* Use BM_ELEM_TAG in f->head.hflag to store needed faces in the first iteration. */
+
+ int allocate_rl = 0;
+ for (i = 0; i < bm->totedge; i++) {
+ e = BM_edge_at_index(bm, i);
+
+ /* Because e->head.hflag is char, so line type flags should not exceed positive 7 bits. */
+ char eflag = lineart_identify_feature_line(
+ rb, e, ort, orv, use_crease, ob->type == OB_FONT, CanFindFreestyle, bm);
+ if (eflag) {
+ /* Only allocate for feature lines (instead of all lines) to save memory. */
+ allocate_rl++;
+ }
+ /* Here we just use bm's flag for when loading actual lines, then we don't need to call
+ * lineart_identify_feature_line() again, e->head.hflag deleted after loading anyway. Always
+ * set the flag, so hflag stays 0 for lines that are not feature lines. */
+ e->head.hflag = eflag;
+ }
+
+ orl = lineart_mem_aquire(&rb->render_data_pool, sizeof(LineartRenderLine) * allocate_rl);
+ reln = lineart_list_append_pointer_pool_sized(&rb->line_buffer_pointers,
+ &rb->render_data_pool,
+ orl,
+ sizeof(LineartRenderElementLinkNode));
+ reln->element_count = allocate_rl;
+ reln->object_ref = orig_ob;
+
+ rl = orl;
+ for (i = 0; i < bm->totedge; i++) {
+ e = BM_edge_at_index(bm, i);
+
+ /* Not a feature line, so we skip. */
+ if (!e->head.hflag) {
+ continue;
+ }
+
+ rl->l = &orv[BM_elem_index_get(e->v1)];
+ rl->r = &orv[BM_elem_index_get(e->v2)];
+ rl->l_obindex = rl->l->index - global_i;
+ rl->r_obindex = rl->r->index - global_i;
+ if (e->l) {
+ int findex = BM_elem_index_get(e->l->f);
+ rl->tl = lineart_triangle_from_index(rb, ort, findex);
+ lineart_triangle_adjacent_assign(rl->tl, &orta[findex], rl);
+ if (e->l->radial_next && e->l->radial_next != e->l) {
+ findex = BM_elem_index_get(e->l->radial_next->f);
+ rl->tr = lineart_triangle_from_index(rb, ort, findex);
+ lineart_triangle_adjacent_assign(rl->tr, &orta[findex], rl);
+ }
+ }
+ rl->flags = e->head.hflag;
+ rl->object_ref = orig_ob;
+
+ LineartRenderLineSegment *rls = lineart_mem_aquire(&rb->render_data_pool,
+ sizeof(LineartRenderLineSegment));
+ BLI_addtail(&rl->segments, rls);
+ if (usage == OBJECT_LRT_INHERENT || usage == OBJECT_LRT_INCLUDE ||
+ usage == OBJECT_LRT_NO_INTERSECTION) {
+ lineart_add_line_to_list(rb, rl);
+ }
+
+ rl++;
+ }
+
+ LRT_MESH_FINISH
+ }
+
+#undef LRT_MESH_FINISH
+}
+
+/* See if this object in such collection is used for generating line art,
+ * Disabling a collection for line art will diable all objects inside. */
+static int lineart_usage_check(Collection *c, Object *ob)
+{
+
+ if (!c) {
+ return OBJECT_LRT_INHERENT;
+ }
+
+ int object_is_used = (ob->lineart.usage != OBJECT_LRT_INHERENT);
+
+ if (object_is_used) {
+ return ob->lineart.usage;
+ }
+
+ if (c->children.first == NULL) {
+ if (BKE_collection_has_object(c, (Object *)(ob->id.orig_id))) {
+ if (ob->lineart.usage == OBJECT_LRT_INHERENT) {
+ switch (c->lineart_usage) {
+ case COLLECTION_LRT_OCCLUSION_ONLY:
+ return OBJECT_LRT_OCCLUSION_ONLY;
+ case COLLECTION_LRT_EXCLUDE:
+ return OBJECT_LRT_EXCLUDE;
+ case COLLECTION_LRT_INTERSECTION_ONLY:
+ return OBJECT_LRT_INTERSECTION_ONLY;
+ case COLLECTION_LRT_NO_INTERSECTION:
+ return OBJECT_LRT_NO_INTERSECTION;
+ }
+ return OBJECT_LRT_INHERENT;
+ }
+ return ob->lineart.usage;
+ }
+ return OBJECT_LRT_INHERENT;
+ }
+
+ LISTBASE_FOREACH (CollectionChild *, cc, &c->children) {
+ int result = lineart_usage_check(cc->collection, ob);
+ if (result > OBJECT_LRT_INHERENT) {
+ return result;
+ }
+ }
+
+ return OBJECT_LRT_INHERENT;
+}
+
+static void lineart_main_load_geometries(
+ Depsgraph *depsgraph,
+ Scene *scene,
+ Object *camera /* Still use camera arg for convenience. */,
+ LineartRenderBuffer *rb,
+ bool allow_duplicates)
+{
+ double proj[4][4], view[4][4], result[4][4];
+ float inv[4][4];
+
+ Camera *cam = camera->data;
+ float sensor = BKE_camera_sensor_size(cam->sensor_fit, cam->sensor_x, cam->sensor_y);
+ double fov = focallength_to_fov(cam->lens, sensor);
+
+ double asp = ((double)rb->w / (double)rb->h);
+
+ if (cam->type == CAM_PERSP) {
+ if (asp < 1) {
+ fov /= asp;
+ }
+ lineart_matrix_perspective_44d(proj, fov, asp, cam->clip_start, cam->clip_end);
+ }
+ else if (cam->type == CAM_ORTHO) {
+ double w = cam->ortho_scale / 2;
+ lineart_matrix_ortho_44d(proj, -w, w, -w / asp, w / asp, cam->clip_start, cam->clip_end);
+ }
+ invert_m4_m4(inv, rb->cam_obmat);
+ mul_m4db_m4db_m4fl_uniq(result, proj, inv);
+ copy_m4_m4_db(proj, result);
+ copy_m4_m4_db(rb->view_projection, proj);
+
+ unit_m4_db(view);
+
+ BLI_listbase_clear(&rb->triangle_buffer_pointers);
+ BLI_listbase_clear(&rb->vertex_buffer_pointers);
+
+ int flags = DEG_ITER_OBJECT_FLAG_LINKED_DIRECTLY | DEG_ITER_OBJECT_FLAG_LINKED_VIA_SET |
+ DEG_ITER_OBJECT_FLAG_VISIBLE;
+
+ /* Instance duplicated & particles. */
+ if (allow_duplicates) {
+ flags |= DEG_ITER_OBJECT_FLAG_DUPLI;
+ }
+
+ /* This is to serialize vertex index in the whole scene, so lineart_triangle_share_edge() can
+ * work properly from the lack of triangle adjacent info. */
+ int global_i = 0;
+
+ DEG_OBJECT_ITER_BEGIN (depsgraph, ob, flags) {
+ int usage = lineart_usage_check(scene->master_collection, ob);
+
+ lineart_geometry_object_load(depsgraph, ob, view, proj, rb, usage, &global_i);
+ }
+ DEG_OBJECT_ITER_END;
+}
+
+/* Returns the two other verts of the triangle given a vertex. Returns false if the given vertex
+ * doesn't belong to this triangle. */
+static bool lineart_triangle_get_other_verts(const LineartRenderTriangle *rt,
+ const LineartRenderVert *rv,
+ LineartRenderVert **l,
+ LineartRenderVert **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 LineartRenderTriangle *rt,
+ const LineartRenderLine *rl,
+ bool allow_overlapping_edges)
+{
+ /* Normally we just determine from the pointer address. */
+ if (rl->tl == rt || rl->tr == rt) {
+ return true;
+ }
+ /* If allows overlapping, then we compare the vertex coordinates one by one to determine if one
+ * edge is from specific triangle. This is slower but can handle edge split cases very well. */
+ if (allow_overlapping_edges) {
+#define LRT_TRI_SAME_POINT(rt, i, pt) \
+ ((LRT_DOUBLE_CLOSE_ENOUGH(rt->v[i]->gloc[0], pt->gloc[0]) && \
+ LRT_DOUBLE_CLOSE_ENOUGH(rt->v[i]->gloc[1], pt->gloc[1]) && \
+ LRT_DOUBLE_CLOSE_ENOUGH(rt->v[i]->gloc[2], pt->gloc[2])) || \
+ (LRT_DOUBLE_CLOSE_ENOUGH(rt->v[i]->gloc[0], pt->gloc[0]) && \
+ LRT_DOUBLE_CLOSE_ENOUGH(rt->v[i]->gloc[1], pt->gloc[1]) && \
+ LRT_DOUBLE_CLOSE_ENOUGH(rt->v[i]->gloc[2], pt->gloc[2])))
+ if ((LRT_TRI_SAME_POINT(rt, 0, rl->l) || LRT_TRI_SAME_POINT(rt, 1, rl->l) ||
+ LRT_TRI_SAME_POINT(rt, 2, rl->l)) &&
+ (LRT_TRI_SAME_POINT(rt, 0, rl->r) || LRT_TRI_SAME_POINT(rt, 1, rl->r) ||
+ LRT_TRI_SAME_POINT(rt, 2, rl->r))) {
+ return true;
+ }
+#undef LRT_TRI_SAME_POINT
+ }
+ return false;
+}
+
+/* Sorting three intersection points from min to max,
+ * the order for each intersection is set in lst[0] to lst[2].*/
+#define INTERSECT_SORT_MIN_TO_MAX_3(ia, ib, ic, lst) \
+ { \
+ lst[0] = LRT_MIN3_INDEX(ia, ib, ic); \
+ lst[1] = (((ia <= ib && ib <= ic) || (ic <= ib && ib <= ia)) ? \
+ 1 : \
+ (((ic <= ia && ia <= ib) || (ib < ia && ia <= ic)) ? 0 : 2)); \
+ lst[2] = LRT_MAX3_INDEX(ia, ib, ic); \
+ }
+
+/* ia ib ic are ordered. */
+#define INTERSECT_JUST_GREATER(is, order, num, index) \
+ { \
+ index = (num < is[order[0]] ? \
+ order[0] : \
+ (num < is[order[1]] ? order[1] : (num < is[order[2]] ? order[2] : order[2]))); \
+ }
+
+/* ia ib ic are ordered. */
+#define INTERSECT_JUST_SMALLER(is, order, num, index) \
+ { \
+ index = (num > is[order[2]] ? \
+ order[2] : \
+ (num > is[order[1]] ? order[1] : (num > is[order[0]] ? order[0] : order[0]))); \
+ }
+
+/* This is the main function to calculate
+ * the occlusion status between 1(one) triangle and 1(one) line.
+ * if returns true, then from/to will carry the occludded segments
+ * in ratio from rl->l to rl->r. The line is later cut with these two values.
+ */
+static bool lineart_triangle_line_image_space_occlusion(SpinLock *UNUSED(spl),
+ const LineartRenderTriangle *rt,
+ const LineartRenderLine *rl,
+ const double *override_camera_loc,
+ const bool override_cam_is_persp,
+ const bool allow_overlapping_edges,
+ const double vp[4][4],
+ const double *camera_dir,
+ const float cam_shift_x,
+ const float cam_shift_y,
+ double *from,
+ double *to)
+{
+ double is[3] = {0};
+ int order[3];
+ int LCross = -1, RCross = -1;
+ int a, b, c;
+ int st_l = 0, st_r = 0;
+
+ double Lv[3];
+ double Rv[3];
+ double vd4[4];
+ double Cv[3];
+ double dot_l, dot_r, dot_la, dot_ra;
+ double dot_f;
+ double gloc[4], trans[4];
+ double cut = -1;
+
+ double *LFBC = rl->l->fbcoord, *RFBC = rl->r->fbcoord, *FBC0 = rt->v[0]->fbcoord,
+ *FBC1 = rt->v[1]->fbcoord, *FBC2 = rt->v[2]->fbcoord;
+
+ /* Overlapping not possible, return early. */
+ if ((MAX3(FBC0[0], FBC1[0], FBC2[0]) < MIN2(LFBC[0], RFBC[0])) ||
+ (MIN3(FBC0[0], FBC1[0], FBC2[0]) > MAX2(LFBC[0], RFBC[0])) ||
+ (MAX3(FBC0[1], FBC1[1], FBC2[1]) < MIN2(LFBC[1], RFBC[1])) ||
+ (MIN3(FBC0[1], FBC1[1], FBC2[1]) > MAX2(LFBC[1], RFBC[1])) ||
+ (MIN3(FBC0[3], FBC1[3], FBC2[3]) > MAX2(LFBC[3], RFBC[3]))) {
+ return false;
+ }
+
+ /* If the the line is one of the edge in the triangle, then it's not occludded. */
+ if (lineart_edge_from_triangle(rt, rl, allow_overlapping_edges)) {
+ return false;
+ }
+
+ /* Check if the line visually crosses one of the edge in the triangle. */
+ a = lineart_LineIntersectTest2d(LFBC, RFBC, FBC0, FBC1, &is[0]);
+ b = lineart_LineIntersectTest2d(LFBC, RFBC, FBC1, FBC2, &is[1]);
+ c = lineart_LineIntersectTest2d(LFBC, RFBC, FBC2, FBC0, &is[2]);
+
+ /* Sort the intersection distance. */
+ INTERSECT_SORT_MIN_TO_MAX_3(is[0], is[1], is[2], order);
+
+ sub_v3_v3v3_db(Lv, rl->l->gloc, rt->v[0]->gloc);
+ sub_v3_v3v3_db(Rv, rl->r->gloc, rt->v[0]->gloc);
+
+ copy_v3_v3_db(Cv, camera_dir);
+
+ if (override_cam_is_persp) {
+ copy_v3_v3_db(vd4, override_camera_loc);
+ }
+ else {
+ copy_v4_v4_db(vd4, override_camera_loc);
+ }
+ if (override_cam_is_persp) {
+ sub_v3_v3v3_db(Cv, vd4, rt->v[0]->gloc);
+ }
+
+ dot_l = dot_v3v3_db(Lv, rt->gn);
+ dot_r = dot_v3v3_db(Rv, rt->gn);
+ dot_f = dot_v3v3_db(Cv, rt->gn);
+
+ if (!dot_f) {
+ return false;
+ }
+
+ if (!a && !b && !c) {
+ if (!(st_l = lineart_point_triangle_relation(LFBC, FBC0, FBC1, FBC2)) &&
+ !(st_r = lineart_point_triangle_relation(RFBC, FBC0, FBC1, FBC2))) {
+ return 0; /* Intersection point is not inside triangle. */
+ }
+ }
+
+ st_l = lineart_point_triangle_relation(LFBC, FBC0, FBC1, FBC2);
+ st_r = lineart_point_triangle_relation(RFBC, FBC0, FBC1, FBC2);
+
+ /* Determine the cut position. */
+
+ dot_la = fabs(dot_l);
+ if (dot_la < DBL_EPSILON) {
+ dot_la = 0;
+ dot_l = 0;
+ }
+ dot_ra = fabs(dot_r);
+ if (dot_ra < DBL_EPSILON) {
+ dot_ra = 0;
+ dot_r = 0;
+ }
+ if (dot_l - dot_r == 0) {
+ cut = 100000;
+ }
+ else if (dot_l * dot_r <= 0) {
+ cut = dot_la / fabs(dot_l - dot_r);
+ }
+ else {
+ cut = fabs(dot_r + dot_l) / fabs(dot_l - dot_r);
+ cut = dot_ra > dot_la ? 1 - cut : cut;
+ }
+
+ /* Transform the cut from geometry space to image space. */
+ if (override_cam_is_persp) {
+ interp_v3_v3v3_db(gloc, rl->l->gloc, rl->r->gloc, cut);
+ mul_v4_m4v3_db(trans, vp, gloc);
+ mul_v3db_db(trans, (1 / trans[3]));
+ }
+ else {
+ interp_v3_v3v3_db(trans, rl->l->fbcoord, rl->r->fbcoord, cut);
+ }
+ trans[0] -= cam_shift_x * 2;
+ trans[1] -= cam_shift_y * 2;
+
+ /* To accomodate k=0 and k=inf (vertical) lines. here the cut is in image space. */
+ if (fabs(rl->l->fbcoord[0] - rl->r->fbcoord[0]) > fabs(rl->l->fbcoord[1] - rl->r->fbcoord[1])) {
+ cut = ratiod(rl->l->fbcoord[0], rl->r->fbcoord[0], trans[0]);
+ }
+ else {
+ cut = ratiod(rl->l->fbcoord[1], rl->r->fbcoord[1], trans[1]);
+ }
+
+ /* Determine the pair of edges that the line has crossed. */
+
+ if (st_l == 2) {
+ if (st_r == 2) {
+ INTERSECT_JUST_SMALLER(is, order, DBL_TRIANGLE_LIM, LCross);
+ INTERSECT_JUST_GREATER(is, order, 1 - DBL_TRIANGLE_LIM, RCross);
+ }
+ else if (st_r == 1) {
+ INTERSECT_JUST_SMALLER(is, order, DBL_TRIANGLE_LIM, LCross);
+ INTERSECT_JUST_GREATER(is, order, 1 - DBL_TRIANGLE_LIM, RCross);
+ }
+ else if (st_r == 0) {
+ INTERSECT_JUST_SMALLER(is, order, DBL_TRIANGLE_LIM, LCross);
+ INTERSECT_JUST_GREATER(is, order, 0, RCross);
+ }
+ }
+ else if (st_l == 1) {
+ if (st_r == 2) {
+ INTERSECT_JUST_SMALLER(is, order, DBL_TRIANGLE_LIM, LCross);
+ INTERSECT_JUST_GREATER(is, order, 1 - DBL_TRIANGLE_LIM, RCross);
+ }
+ else if (st_r == 1) {
+ INTERSECT_JUST_SMALLER(is, order, DBL_TRIANGLE_LIM, LCross);
+ INTERSECT_JUST_GREATER(is, order, 1 - DBL_TRIANGLE_LIM, RCross);
+ }
+ else if (st_r == 0) {
+ INTERSECT_JUST_GREATER(is, order, DBL_TRIANGLE_LIM, RCross);
+ if (LRT_ABC(RCross) && is[RCross] > (DBL_TRIANGLE_LIM)) {
+ INTERSECT_JUST_SMALLER(is, order, DBL_TRIANGLE_LIM, LCross);
+ }
+ else {
+ INTERSECT_JUST_SMALLER(is, order, -DBL_TRIANGLE_LIM, LCross);
+ INTERSECT_JUST_GREATER(is, order, -DBL_TRIANGLE_LIM, RCross);
+ }
+ }
+ }
+ else if (st_l == 0) {
+ if (st_r == 2) {
+ INTERSECT_JUST_SMALLER(is, order, 1 - DBL_TRIANGLE_LIM, LCross);
+ INTERSECT_JUST_GREATER(is, order, 1 - DBL_TRIANGLE_LIM, RCross);
+ }
+ else if (st_r == 1) {
+ INTERSECT_JUST_SMALLER(is, order, 1 - DBL_TRIANGLE_LIM, LCross);
+ if (LRT_ABC(LCross) && is[LCross] < (1 - DBL_TRIANGLE_LIM)) {
+ INTERSECT_JUST_GREATER(is, order, 1 - DBL_TRIANGLE_LIM, RCross);
+ }
+ else {
+ INTERSECT_JUST_SMALLER(is, order, 1 + DBL_TRIANGLE_LIM, LCross);
+ INTERSECT_JUST_GREATER(is, order, 1 + DBL_TRIANGLE_LIM, RCross);
+ }
+ }
+ else if (st_r == 0) {
+ INTERSECT_JUST_GREATER(is, order, 0, LCross);
+ if (LRT_ABC(LCross) && is[LCross] > 0) {
+ INTERSECT_JUST_GREATER(is, order, is[LCross], RCross);
+ }
+ else {
+ INTERSECT_JUST_GREATER(is, order, is[LCross], LCross);
+ INTERSECT_JUST_GREATER(is, order, is[LCross], RCross);
+ }
+ }
+ }
+
+ double LF = dot_l * dot_f, RF = dot_r * dot_f;
+
+ /* Determine the start and end point of image space cut on a line. */
+ if (LF <= 0 && RF <= 0 && (dot_l || dot_r)) {
+ *from = MAX2(0, is[LCross]);
+ *to = MIN2(1, is[RCross]);
+ if (*from >= *to) {
+ return false;
+ }
+ return true;
+ }
+ if (LF >= 0 && RF <= 0 && (dot_l || dot_r)) {
+ *from = MAX2(cut, is[LCross]);
+ *to = MIN2(1, is[RCross]);
+ if (*from >= *to) {
+ return false;
+ }
+ return true;
+ }
+ if (LF <= 0 && RF >= 0 && (dot_l || dot_r)) {
+ *from = MAX2(0, is[LCross]);
+ *to = MIN2(cut, is[RCross]);
+ if (*from >= *to) {
+ return false;
+ }
+ return true;
+ }
+
+ /* Unlikely, but here's the default failed value if anything fall through. */
+ return false;
+}
+
+#undef INTERSECT_SORT_MIN_TO_MAX_3
+#undef INTERSECT_JUST_GREATER
+#undef INTERSECT_JUST_SMALLER
+
+/* At this stage of the computation we don't have triangle adjacent info anymore, so we can only
+ * compare the global vert index. */
+static bool lineart_triangle_share_edge(const LineartRenderTriangle *l,
+ const LineartRenderTriangle *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 LineartRenderVert *lineart_triangle_share_point(const LineartRenderTriangle *l,
+ const LineartRenderTriangle *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(LineartRenderVertIntersection *rv,
+ LineartRenderVertIntersection *v1,
+ LineartRenderVertIntersection *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(LineartRenderVert *rv,
+ LineartRenderVert *v1,
+ LineartRenderVert *v2)
+{
+ LineartRenderVertIntersection *irv = (LineartRenderVertIntersection *)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 LineartRenderVert *lineart_triangle_2v_intersection_test(LineartRenderBuffer *rb,
+ LineartRenderVert *v1,
+ LineartRenderVert *v2,
+ LineartRenderTriangle *rt,
+ LineartRenderTriangle *testing,
+ LineartRenderVert *last)
+{
+ double Lv[3];
+ double Rv[3];
+ double dot_l, dot_r;
+ LineartRenderVert *result;
+ double gloc[3];
+ LineartRenderVert *l = v1, *r = v2;
+
+ for (LinkNode *ln = (void *)testing->intersecting_verts; ln; ln = ln->next) {
+ LineartRenderVertIntersection *rv = ln->link;
+ if (rv->intersecting_with == rt &&
+ lineart_vert_already_intersected_2v(
+ rv, (LineartRenderVertIntersection *)l, (LineartRenderVertIntersection *)r)) {
+ return (LineartRenderVert *)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 LineartRenderVert,
+ * allocated separately. */
+ result = lineart_mem_aquire(&rb->render_data_pool, sizeof(LineartRenderVertIntersection));
+
+ /* 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 LineartRenderLine *lineart_triangle_intersect(LineartRenderBuffer *rb,
+ LineartRenderTriangle *rt,
+ LineartRenderTriangle *testing)
+{
+ LineartRenderVert *l = 0, *r = 0;
+ LineartRenderVert **next = &l;
+ LineartRenderLine *result;
+ LineartRenderVert *E0T = 0;
+ LineartRenderVert *E1T = 0;
+ LineartRenderVert *E2T = 0;
+ LineartRenderVert *TE0 = 0;
+ LineartRenderVert *TE1 = 0;
+ LineartRenderVert *TE2 = 0;
+ LineartRenderVert *sv1, *sv2;
+ double cl[3];
+
+ double ZMin, ZMax;
+ ZMax = rb->far_clip;
+ ZMin = rb->near_clip;
+ copy_v3_v3_db(cl, rb->camera_pos);
+ LineartRenderVert *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.*/
+
+ LineartRenderVert *new_share;
+ lineart_triangle_get_other_verts(rt, share, &sv1, &sv2);
+
+ l = new_share = lineart_mem_aquire(&rb->render_data_pool,
+ (sizeof(LineartRenderVertIntersection)));
+
+ new_share->flag = LRT_VERT_HAS_INTERSECTION_DATA;
+
+ copy_v3_v3_db(new_share->gloc, share->gloc);
+
+ r = lineart_triangle_2v_intersection_test(rb, sv1, sv2, rt, testing, 0);
+
+ if (r == NULL) {
+ lineart_triangle_get_other_verts(testing, share, &sv1, &sv2);
+ r = lineart_triangle_2v_intersection_test(rb, sv1, sv2, testing, rt, 0);
+ if (r == NULL) {
+ return 0;
+ }
+ lineart_prepend_pool(&testing->intersecting_verts, &rb->render_data_pool, new_share);
+ }
+ else {
+ lineart_prepend_pool(&rt->intersecting_verts, &rb->render_data_pool, new_share);
+ }
+ }
+ else {
+ /* If not sharing any points, then we need to try all the possibilities. */
+
+ E0T = lineart_triangle_2v_intersection_test(rb, rt->v[0], rt->v[1], rt, testing, 0);
+ if (E0T && (!(*next))) {
+ (*next) = E0T;
+ lineart_vert_set_intersection_2v((*next), rt->v[0], rt->v[1]);
+ next = &r;
+ }
+ E1T = lineart_triangle_2v_intersection_test(rb, rt->v[1], rt->v[2], rt, testing, l);
+ if (E1T && (!(*next))) {
+ (*next) = E1T;
+ lineart_vert_set_intersection_2v((*next), rt->v[1], rt->v[2]);
+ next = &r;
+ }
+ if (!(*next)) {
+ E2T = lineart_triangle_2v_intersection_test(rb, rt->v[2], rt->v[0], rt, testing, l);
+ }
+ if (E2T && (!(*next))) {
+ (*next) = E2T;
+ lineart_vert_set_intersection_2v((*next), rt->v[2], rt->v[0]);
+ next = &r;
+ }
+
+ if (!(*next)) {
+ TE0 = lineart_triangle_2v_intersection_test(
+ rb, testing->v[0], testing->v[1], testing, rt, l);
+ }
+ if (TE0 && (!(*next))) {
+ (*next) = TE0;
+ lineart_vert_set_intersection_2v((*next), testing->v[0], testing->v[1]);
+ next = &r;
+ }
+ if (!(*next)) {
+ TE1 = lineart_triangle_2v_intersection_test(
+ rb, testing->v[1], testing->v[2], testing, rt, l);
+ }
+ if (TE1 && (!(*next))) {
+ (*next) = TE1;
+ lineart_vert_set_intersection_2v((*next), testing->v[1], testing->v[2]);
+ next = &r;
+ }
+ if (!(*next)) {
+ TE2 = lineart_triangle_2v_intersection_test(
+ rb, testing->v[2], testing->v[0], testing, rt, l);
+ }
+ if (TE2 && (!(*next))) {
+ (*next) = TE2;
+ lineart_vert_set_intersection_2v((*next), testing->v[2], testing->v[0]);
+ next = &r;
+ }
+
+ if (!(*next)) {
+ return 0;
+ }
+ }
+
+ /* The intersection line has been generated only in geometry space, so we need to transform them
+ * as well. */
+ mul_v4_m4v3_db(l->fbcoord, rb->view_projection, l->gloc);
+ mul_v4_m4v3_db(r->fbcoord, rb->view_projection, r->gloc);
+ mul_v3db_db(l->fbcoord, (1 / l->fbcoord[3]));
+ mul_v3db_db(r->fbcoord, (1 / r->fbcoord[3]));
+
+ l->fbcoord[0] -= rb->shift_x * 2;
+ l->fbcoord[1] -= rb->shift_y * 2;
+ r->fbcoord[0] -= rb->shift_x * 2;
+ r->fbcoord[1] -= rb->shift_y * 2;
+
+ /* This z transformation is not the same as the rest of the part, because the data don't go
+ * through normal perspective division calls in the pipeline, but this way the 3D result and
+ * occlution on the generated line is correct, and we don't really use 2D for viewport stroke
+ * generation anyway.*/
+ l->fbcoord[2] = ZMin * ZMax / (ZMax - fabs(l->fbcoord[2]) * (ZMax - ZMin));
+ r->fbcoord[2] = ZMin * ZMax / (ZMax - fabs(r->fbcoord[2]) * (ZMax - ZMin));
+
+ ((LineartRenderVertIntersection *)l)->intersecting_with = rt;
+ ((LineartRenderVertIntersection *)r)->intersecting_with = testing;
+
+ result = lineart_mem_aquire(&rb->render_data_pool, sizeof(LineartRenderLine));
+ result->l = l;
+ result->r = r;
+ result->tl = rt;
+ result->tr = testing;
+
+ LineartRenderLineSegment *rls = lineart_mem_aquire(&rb->render_data_pool,
+ sizeof(LineartRenderLineSegment));
+ BLI_addtail(&result->segments, rls);
+ /* Don't need to OR flags right now, just a type mark. */
+ result->flags = LRT_EDGE_FLAG_INTERSECTION;
+ lineart_prepend_line_direct(&rb->intersection_lines, result);
+ int r1, r2, c1, c2, row, col;
+ if (lineart_get_line_bounding_areas(rb, result, &r1, &r2, &c1, &c2)) {
+ for (row = r1; row != r2 + 1; row++) {
+ for (col = c1; col != c2 + 1; col++) {
+ lineart_bounding_area_link_line(
+ rb, &rb->initial_bounding_areas[row * LRT_BA_ROWS + col], result);
+ }
+ }
+ }
+
+ rb->intersection_count++;
+
+ return result;
+}
+
+static void lineart_triangle_intersect_in_bounding_area(LineartRenderBuffer *rb,
+ LineartRenderTriangle *rt,
+ LineartBoundingArea *ba)
+{
+ /* Testing_triangle->testing[0] is used to store pairing triangle reference.
+ * See definition of LineartRenderTriangleThread for more info. */
+ LineartRenderTriangle *testing_triangle;
+ LineartRenderTriangleThread *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 = (LineartRenderTriangleThread *)testing_triangle;
+
+ if (testing_triangle == rt || rtt->testing[0] == (LineartRenderLine *)rt ||
+ (testing_triangle->flags & LRT_TRIANGLE_NO_INTERSECTION) ||
+ ((testing_triangle->flags & LRT_TRIANGLE_INTERSECTION_ONLY) &&
+ (rt->flags & LRT_TRIANGLE_INTERSECTION_ONLY)) ||
+ lineart_triangle_share_edge(rt, testing_triangle)) {
+ continue;
+ }
+
+ rtt->testing[0] = (LineartRenderLine *)rt;
+ double *RG0 = testing_triangle->v[0]->gloc, *RG1 = testing_triangle->v[1]->gloc,
+ *RG2 = testing_triangle->v[2]->gloc;
+
+ /* Bounding box not overlapping, not potential of intersecting. */
+ if ((MIN3(G0[2], G1[2], G2[2]) > MAX3(RG0[2], RG1[2], RG2[2])) ||
+ (MAX3(G0[2], G1[2], G2[2]) < MIN3(RG0[2], RG1[2], RG2[2])) ||
+ (MIN3(G0[0], G1[0], G2[0]) > MAX3(RG0[0], RG1[0], RG2[0])) ||
+ (MAX3(G0[0], G1[0], G2[0]) < MIN3(RG0[0], RG1[0], RG2[0])) ||
+ (MIN3(G0[1], G1[1], G2[1]) > MAX3(RG0[1], RG1[1], RG2[1])) ||
+ (MAX3(G0[1], G1[1], G2[1]) < MIN3(RG0[1], RG1[1], RG2[1]))) {
+ continue;
+ }
+
+ /* If we do need to compute intersection, then finally do it. */
+ lineart_triangle_intersect(rb, rt, testing_triangle);
+ }
+}
+
+/* The calculated view vector will point towards the far-plane from the camera position. */
+static void lineart_main_get_view_vector(LineartRenderBuffer *rb)
+{
+ float direction[3] = {0, 0, 1};
+ float trans[3];
+ float inv[4][4];
+ float obmat_no_scale[4][4];
+
+ copy_m4_m4(obmat_no_scale, rb->cam_obmat);
+
+ normalize_v3(obmat_no_scale[0]);
+ normalize_v3(obmat_no_scale[1]);
+ normalize_v3(obmat_no_scale[2]);
+ invert_m4_m4(inv, obmat_no_scale);
+ transpose_m4(inv);
+ mul_v3_mat3_m4v3(trans, inv, direction);
+ copy_m4_m4(rb->cam_obmat, obmat_no_scale);
+ copy_v3db_v3fl(rb->view_vector, trans);
+}
+
+static void lineart_destroy_render_data(LineartRenderBuffer *rb)
+{
+ if (rb == NULL) {
+ return;
+ }
+
+ rb->contour_count = 0;
+ rb->contour_managed = NULL;
+ rb->intersection_count = 0;
+ rb->intersection_managed = NULL;
+ rb->material_line_count = 0;
+ rb->material_managed = NULL;
+ rb->crease_count = 0;
+ rb->crease_managed = NULL;
+ rb->edge_mark_count = 0;
+ rb->edge_mark_managed = NULL;
+
+ rb->contours = NULL;
+ rb->intersection_lines = NULL;
+ rb->crease_lines = NULL;
+ rb->material_lines = NULL;
+ rb->edge_marks = NULL;
+
+ BLI_listbase_clear(&rb->chains);
+ BLI_listbase_clear(&rb->wasted_cuts);
+
+ BLI_listbase_clear(&rb->vertex_buffer_pointers);
+ BLI_listbase_clear(&rb->line_buffer_pointers);
+ BLI_listbase_clear(&rb->triangle_buffer_pointers);
+
+ BLI_spin_end(&rb->lock_task);
+ BLI_spin_end(&rb->lock_cuts);
+ BLI_spin_end(&rb->render_data_pool.lock_mem);
+
+ lineart_mem_destroy(&rb->render_data_pool);
+}
+
+void MOD_lineart_destroy_render_data(LineartGpencilModifierData *lmd)
+{
+ LineartRenderBuffer *rb = lmd->render_buffer;
+
+ lineart_destroy_render_data(rb);
+
+ if (rb) {
+ MEM_freeN(rb);
+ lmd->render_buffer = NULL;
+ }
+
+ if (G.debug_value == 4000) {
+ printf("LRT: Destroyed render data.\n");
+ }
+}
+
+static LineartRenderBuffer *lineart_create_render_buffer(Scene *scene,
+ LineartGpencilModifierData *lmd)
+{
+ LineartRenderBuffer *rb = MEM_callocN(sizeof(LineartRenderBuffer), "Line Art render buffer");
+
+ lmd->render_buffer = rb;
+
+ if (!scene || !scene->camera) {
+ return NULL;
+ }
+ Camera *c = scene->camera->data;
+ double clipping_offset = 0;
+
+ if (lmd->calculation_flags & LRT_ALLOW_CLIPPING_BOUNDARIES) {
+ /* This way the clipped lines are "stablely visible" by prevents depth buffer artefacts. */
+ clipping_offset = 0.0001;
+ }
+
+ copy_v3db_v3fl(rb->camera_pos, scene->camera->obmat[3]);
+ copy_m4_m4(rb->cam_obmat, scene->camera->obmat);
+ rb->cam_is_persp = (c->type == CAM_PERSP);
+ rb->near_clip = c->clip_start + clipping_offset;
+ rb->far_clip = c->clip_end - clipping_offset;
+ rb->w = scene->r.xsch;
+ rb->h = scene->r.ysch;
+
+ double asp = ((double)rb->w / (double)rb->h);
+ rb->shift_x = (asp >= 1) ? c->shiftx : c->shiftx * asp;
+ rb->shift_y = (asp <= 1) ? c->shifty : c->shifty * asp;
+
+ rb->crease_threshold = cos(M_PI - lmd->crease_threshold);
+ rb->angle_splitting_threshold = lmd->angle_splitting_threshold;
+ rb->chaining_image_threshold = lmd->chaining_image_threshold;
+ rb->chaining_geometry_threshold = lmd->chaining_geometry_threshold;
+
+ rb->fuzzy_intersections = (lmd->calculation_flags & LRT_INTERSECTION_AS_CONTOUR) != 0;
+ rb->fuzzy_everything = (lmd->calculation_flags & LRT_EVERYTHING_AS_CONTOUR) != 0;
+ rb->allow_boundaries = (lmd->calculation_flags & LRT_ALLOW_CLIPPING_BOUNDARIES) != 0;
+ rb->remove_doubles = (lmd->calculation_flags & LRT_REMOVE_DOUBLES) != 0;
+
+ /* See lineart_edge_from_triangle() for how this option may impact performance. */
+ rb->allow_overlapping_edges = (lmd->calculation_flags & LRT_ALLOW_OVERLAPPING_EDGES) != 0;
+
+ rb->use_contour = (lmd->line_types & LRT_EDGE_FLAG_CONTOUR) != 0;
+ rb->use_crease = (lmd->line_types & LRT_EDGE_FLAG_CREASE) != 0;
+ rb->use_material = (lmd->line_types & LRT_EDGE_FLAG_MATERIAL) != 0;
+ rb->use_edge_marks = (lmd->line_types & LRT_EDGE_FLAG_EDGE_MARK) != 0;
+ rb->use_intersections = (lmd->line_types & LRT_EDGE_FLAG_INTERSECTION) != 0;
+
+ BLI_spin_init(&rb->lock_task);
+ BLI_spin_init(&rb->lock_cuts);
+ BLI_spin_init(&rb->render_data_pool.lock_mem);
+
+ return rb;
+}
+
+static int lineart_triangle_size_get(const Scene *scene, LineartRenderBuffer *rb)
+{
+ if (rb->thread_count == 0) {
+ rb->thread_count = BKE_render_num_threads(&scene->r);
+ }
+ return sizeof(LineartRenderTriangle) + (sizeof(LineartRenderLine *) * (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);
+ LineartRenderTriangle *rt;
+ LineartRenderLine *rl;
+
+ ba[0].l = root->cx;
+ ba[0].r = root->r;
+ ba[0].u = root->u;
+ ba[0].b = root->cy;
+ ba[0].cx = (ba[0].l + ba[0].r) / 2;
+ ba[0].cy = (ba[0].u + ba[0].b) / 2;
+
+ ba[1].l = root->l;
+ ba[1].r = root->cx;
+ ba[1].u = root->u;
+ ba[1].b = root->cy;
+ ba[1].cx = (ba[1].l + ba[1].r) / 2;
+ ba[1].cy = (ba[1].u + ba[1].b) / 2;
+
+ ba[2].l = root->l;
+ ba[2].r = root->cx;
+ ba[2].u = root->cy;
+ ba[2].b = root->b;
+ ba[2].cx = (ba[2].l + ba[2].r) / 2;
+ ba[2].cy = (ba[2].u + ba[2].b) / 2;
+
+ ba[3].l = root->cx;
+ ba[3].r = root->r;
+ ba[3].u = root->cy;
+ ba[3].b = root->b;
+ ba[3].cx = (ba[3].l + ba[3].r) / 2;
+ ba[3].cy = (ba[3].u + ba[3].b) / 2;
+
+ root->child = ba;
+
+ lineart_bounding_areas_connect_new(rb, root);
+
+ while ((rt = lineart_list_pop_pointer_no_free(&root->linked_triangles)) != NULL) {
+ LineartBoundingArea *cba = root->child;
+ double b[4];
+ b[0] = MIN3(rt->v[0]->fbcoord[0], rt->v[1]->fbcoord[0], rt->v[2]->fbcoord[0]);
+ b[1] = MAX3(rt->v[0]->fbcoord[0], rt->v[1]->fbcoord[0], rt->v[2]->fbcoord[0]);
+ b[2] = MAX3(rt->v[0]->fbcoord[1], rt->v[1]->fbcoord[1], rt->v[2]->fbcoord[1]);
+ b[3] = MIN3(rt->v[0]->fbcoord[1], rt->v[1]->fbcoord[1], rt->v[2]->fbcoord[1]);
+ if (LRT_BOUND_AREA_CROSSES(b, &cba[0].l)) {
+ lineart_bounding_area_link_triangle(rb, &cba[0], rt, b, 0, recursive_level + 1, false);
+ }
+ if (LRT_BOUND_AREA_CROSSES(b, &cba[1].l)) {
+ lineart_bounding_area_link_triangle(rb, &cba[1], rt, b, 0, recursive_level + 1, false);
+ }
+ if (LRT_BOUND_AREA_CROSSES(b, &cba[2].l)) {
+ lineart_bounding_area_link_triangle(rb, &cba[2], rt, b, 0, recursive_level + 1, false);
+ }
+ if (LRT_BOUND_AREA_CROSSES(b, &cba[3].l)) {
+ lineart_bounding_area_link_triangle(rb, &cba[3], rt, b, 0, recursive_level + 1, false);
+ }
+ }
+
+ while ((rl = lineart_list_pop_pointer_no_free(&root->linked_lines)) != NULL) {
+ lineart_bounding_area_link_line(rb, root, rl);
+ }
+
+ rb->bounding_area_count += 3;
+}
+
+static bool lineart_bounding_area_line_intersect(LineartRenderBuffer *UNUSED(fb),
+ const double l[2],
+ const double r[2],
+ LineartBoundingArea *ba)
+{
+ double vx, vy;
+ double converted[4];
+ double c1, c;
+
+ if (((converted[0] = (double)ba->l) > MAX2(l[0], r[0])) ||
+ ((converted[1] = (double)ba->r) < MIN2(l[0], r[0])) ||
+ ((converted[2] = (double)ba->b) > MAX2(l[1], r[1])) ||
+ ((converted[3] = (double)ba->u) < MIN2(l[1], r[1]))) {
+ return false;
+ }
+
+ vx = l[0] - r[0];
+ vy = l[1] - r[1];
+
+ c1 = vx * (converted[2] - l[1]) - vy * (converted[0] - l[0]);
+ c = c1;
+
+ c1 = vx * (converted[2] - l[1]) - vy * (converted[1] - l[0]);
+ if (c1 * c <= 0) {
+ return true;
+ }
+ c = c1;
+
+ c1 = vx * (converted[3] - l[1]) - vy * (converted[0] - l[0]);
+ if (c1 * c <= 0) {
+ return true;
+ }
+ c = c1;
+
+ c1 = vx * (converted[3] - l[1]) - vy * (converted[1] - l[0]);
+ if (c1 * c <= 0) {
+ return true;
+ }
+ c = c1;
+
+ return false;
+}
+
+static bool lineart_bounding_area_triangle_intersect(LineartRenderBuffer *fb,
+ LineartRenderTriangle *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,
+ LineartRenderTriangle *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,
+ LineartRenderLine *rl)
+{
+ if (root_ba->child == NULL) {
+ lineart_list_append_pointer_pool(&root_ba->linked_lines, &rb->render_data_pool, rl);
+ }
+ else {
+ if (lineart_bounding_area_line_intersect(
+ rb, rl->l->fbcoord, rl->r->fbcoord, &root_ba->child[0])) {
+ lineart_bounding_area_link_line(rb, &root_ba->child[0], rl);
+ }
+ if (lineart_bounding_area_line_intersect(
+ rb, rl->l->fbcoord, rl->r->fbcoord, &root_ba->child[1])) {
+ lineart_bounding_area_link_line(rb, &root_ba->child[1], rl);
+ }
+ if (lineart_bounding_area_line_intersect(
+ rb, rl->l->fbcoord, rl->r->fbcoord, &root_ba->child[2])) {
+ lineart_bounding_area_link_line(rb, &root_ba->child[2], rl);
+ }
+ if (lineart_bounding_area_line_intersect(
+ rb, rl->l->fbcoord, rl->r->fbcoord, &root_ba->child[3])) {
+ lineart_bounding_area_link_line(rb, &root_ba->child[3], rl);
+ }
+ }
+}
+
+/* Link lines to their respective bounding areas. */
+static void lineart_main_link_lines(LineartRenderBuffer *rb)
+{
+ LRT_ITER_ALL_LINES_BEGIN
+ {
+ int r1, r2, c1, c2, row, col;
+ if (lineart_get_line_bounding_areas(rb, rl, &r1, &r2, &c1, &c2)) {
+ for (row = r1; row != r2 + 1; row++) {
+ for (col = c1; col != c2 + 1; col++) {
+ lineart_bounding_area_link_line(
+ rb, &rb->initial_bounding_areas[row * LRT_BA_ROWS + col], rl);
+ }
+ }
+ }
+ }
+ LRT_ITER_ALL_LINES_END
+}
+
+static bool lineart_get_triangle_bounding_areas(LineartRenderBuffer *rb,
+ LineartRenderTriangle *rt,
+ int *rowbegin,
+ int *rowend,
+ int *colbegin,
+ int *colend)
+{
+ double sp_w = rb->width_per_tile, sp_h = rb->height_per_tile;
+ double b[4];
+
+ if (!rt->v[0] || !rt->v[1] || !rt->v[2]) {
+ return false;
+ }
+
+ b[0] = MIN3(rt->v[0]->fbcoord[0], rt->v[1]->fbcoord[0], rt->v[2]->fbcoord[0]);
+ b[1] = MAX3(rt->v[0]->fbcoord[0], rt->v[1]->fbcoord[0], rt->v[2]->fbcoord[0]);
+ b[2] = MIN3(rt->v[0]->fbcoord[1], rt->v[1]->fbcoord[1], rt->v[2]->fbcoord[1]);
+ b[3] = MAX3(rt->v[0]->fbcoord[1], rt->v[1]->fbcoord[1], rt->v[2]->fbcoord[1]);
+
+ if (b[0] > 1 || b[1] < -1 || b[2] > 1 || b[3] < -1) {
+ return false;
+ }
+
+ (*colbegin) = (int)((b[0] + 1.0) / sp_w);
+ (*colend) = (int)((b[1] + 1.0) / sp_w);
+ (*rowend) = rb->tile_count_y - (int)((b[2] + 1.0) / sp_h) - 1;
+ (*rowbegin) = rb->tile_count_y - (int)((b[3] + 1.0) / sp_h) - 1;
+
+ if ((*colend) >= rb->tile_count_x) {
+ (*colend) = rb->tile_count_x - 1;
+ }
+ if ((*rowend) >= rb->tile_count_y) {
+ (*rowend) = rb->tile_count_y - 1;
+ }
+ if ((*colbegin) < 0) {
+ (*colbegin) = 0;
+ }
+ if ((*rowbegin) < 0) {
+ (*rowbegin) = 0;
+ }
+
+ return true;
+}
+
+static bool lineart_get_line_bounding_areas(LineartRenderBuffer *rb,
+ LineartRenderLine *rl,
+ int *rowbegin,
+ int *rowend,
+ int *colbegin,
+ int *colend)
+{
+ double sp_w = rb->width_per_tile, sp_h = rb->height_per_tile;
+ double b[4];
+
+ if (!rl->l || !rl->r) {
+ return false;
+ }
+
+ if (rl->l->fbcoord[0] != rl->l->fbcoord[0] || rl->r->fbcoord[0] != rl->r->fbcoord[0]) {
+ return false;
+ }
+
+ b[0] = MIN2(rl->l->fbcoord[0], rl->r->fbcoord[0]);
+ b[1] = MAX2(rl->l->fbcoord[0], rl->r->fbcoord[0]);
+ b[2] = MIN2(rl->l->fbcoord[1], rl->r->fbcoord[1]);
+ b[3] = MAX2(rl->l->fbcoord[1], rl->r->fbcoord[1]);
+
+ if (b[0] > 1 || b[1] < -1 || b[2] > 1 || b[3] < -1) {
+ return false;
+ }
+
+ (*colbegin) = (int)((b[0] + 1.0) / sp_w);
+ (*colend) = (int)((b[1] + 1.0) / sp_w);
+ (*rowend) = rb->tile_count_y - (int)((b[2] + 1.0) / sp_h) - 1;
+ (*rowbegin) = rb->tile_count_y - (int)((b[3] + 1.0) / sp_h) - 1;
+
+ /* It'scene possible that the line stretches too much out to the side, resulting negative value
+ . */
+ if ((*rowend) < (*rowbegin)) {
+ (*rowend) = rb->tile_count_y - 1;
+ }
+
+ if ((*colend) < (*colbegin)) {
+ (*colend) = rb->tile_count_x - 1;
+ }
+
+ CLAMP((*colbegin), 0, rb->tile_count_x - 1);
+ CLAMP((*rowbegin), 0, rb->tile_count_y - 1);
+ CLAMP((*colend), 0, rb->tile_count_x - 1);
+ CLAMP((*rowend), 0, rb->tile_count_y - 1);
+
+ return true;
+}
+
+/* This only gets initial "biggest" tile. */
+LineartBoundingArea *MOD_lineart_get_parent_bounding_area(LineartRenderBuffer *rb,
+ double x,
+ double y)
+{
+ double sp_w = rb->width_per_tile, sp_h = rb->height_per_tile;
+ int col, row;
+
+ if (x > 1 || x < -1 || y > 1 || y < -1) {
+ return 0;
+ }
+
+ col = (int)((x + 1.0) / sp_w);
+ row = rb->tile_count_y - (int)((y + 1.0) / sp_h) - 1;
+
+ if (col >= rb->tile_count_x) {
+ col = rb->tile_count_x - 1;
+ }
+ if (row >= rb->tile_count_y) {
+ row = rb->tile_count_y - 1;
+ }
+ if (col < 0) {
+ col = 0;
+ }
+ if (row < 0) {
+ row = 0;
+ }
+
+ return &rb->initial_bounding_areas[row * LRT_BA_ROWS + col];
+}
+
+static LineartBoundingArea *lineart_get_bounding_area(LineartRenderBuffer *rb, double x, double y)
+{
+ LineartBoundingArea *iba;
+ double sp_w = rb->width_per_tile, sp_h = rb->height_per_tile;
+ int c = (int)((x + 1.0) / sp_w);
+ int r = rb->tile_count_y - (int)((y + 1.0) / sp_h) - 1;
+ if (r < 0) {
+ r = 0;
+ }
+ if (c < 0) {
+ c = 0;
+ }
+ if (r >= rb->tile_count_y) {
+ r = rb->tile_count_y - 1;
+ }
+ if (c >= rb->tile_count_x) {
+ c = rb->tile_count_x - 1;
+ }
+
+ iba = &rb->initial_bounding_areas[r * LRT_BA_ROWS + c];
+ while (iba->child) {
+ if (x > iba->cx) {
+ if (y > iba->cy) {
+ iba = &iba->child[0];
+ }
+ else {
+ iba = &iba->child[3];
+ }
+ }
+ else {
+ if (y > iba->cy) {
+ iba = &iba->child[1];
+ }
+ else {
+ iba = &iba->child[2];
+ }
+ }
+ }
+ return iba;
+}
+
+/* Wrapper for more convenience. */
+LineartBoundingArea *MOD_lineart_get_bounding_area(LineartRenderBuffer *rb, double x, double y)
+{
+ LineartBoundingArea *ba;
+ if ((ba = MOD_lineart_get_parent_bounding_area(rb, x, y)) != NULL) {
+ return lineart_get_bounding_area(rb, x, y);
+ }
+ return NULL;
+}
+
+/* Sequentially add triangles into render buffer. This also does intersection along the way. */
+static void lineart_main_add_triangles(LineartRenderBuffer *rb)
+{
+ LineartRenderTriangle *rt;
+ int i, lim;
+ int x1, x2, y1, y2;
+ int r, co;
+
+ LISTBASE_FOREACH (LineartRenderElementLinkNode *, reln, &rb->triangle_buffer_pointers) {
+ rt = reln->pointer;
+ lim = reln->element_count;
+ for (i = 0; i < lim; i++) {
+ if ((rt->flags & LRT_CULL_USED) || (rt->flags & LRT_CULL_DISCARD)) {
+ rt = (void *)(((unsigned char *)rt) + rb->triangle_size);
+ continue;
+ }
+ if (lineart_get_triangle_bounding_areas(rb, rt, &y1, &y2, &x1, &x2)) {
+ for (co = x1; co <= x2; co++) {
+ for (r = y1; r <= y2; r++) {
+ lineart_bounding_area_link_triangle(rb,
+ &rb->initial_bounding_areas[r * LRT_BA_ROWS + co],
+ rt,
+ 0,
+ 1,
+ 0,
+ (!(rt->flags & LRT_TRIANGLE_NO_INTERSECTION)));
+ }
+ }
+ } /* Else throw away. */
+ rt = (void *)(((unsigned char *)rt) + rb->triangle_size);
+ }
+ }
+}
+
+/* This function gets the tile for the point rl->l, and later use lineart_bounding_area_next() to
+ * get next along the way. */
+static LineartBoundingArea *lineart_line_first_bounding_area(LineartRenderBuffer *rb,
+ LineartRenderLine *rl)
+{
+ double data[2] = {rl->l->fbcoord[0], rl->l->fbcoord[1]};
+ double LU[2] = {-1, 1}, RU[2] = {1, 1}, LB[2] = {-1, -1}, RB[2] = {1, -1};
+ double r = 1, sr = 1;
+
+ if (data[0] > -1 && data[0] < 1 && data[1] > -1 && data[1] < 1) {
+ return lineart_get_bounding_area(rb, data[0], data[1]);
+ }
+
+ if (lineart_LineIntersectTest2d(rl->l->fbcoord, rl->r->fbcoord, LU, RU, &sr) && sr < r &&
+ sr > 0) {
+ r = sr;
+ }
+ if (lineart_LineIntersectTest2d(rl->l->fbcoord, rl->r->fbcoord, LB, RB, &sr) && sr < r &&
+ sr > 0) {
+ r = sr;
+ }
+ if (lineart_LineIntersectTest2d(rl->l->fbcoord, rl->r->fbcoord, LB, LU, &sr) && sr < r &&
+ sr > 0) {
+ r = sr;
+ }
+ if (lineart_LineIntersectTest2d(rl->l->fbcoord, rl->r->fbcoord, RB, RU, &sr) && sr < r &&
+ sr > 0) {
+ r = sr;
+ }
+ interp_v2_v2v2_db(data, rl->l->fbcoord, rl->r->fbcoord, r);
+
+ return lineart_get_bounding_area(rb, data[0], data[1]);
+}
+
+/* This march along one render line in image space and
+ * get the next bounding area the line is crossing. */
+static LineartBoundingArea *lineart_bounding_area_next(LineartBoundingArea *this,
+ LineartRenderLine *rl,
+ double x,
+ double y,
+ double k,
+ int positive_x,
+ int positive_y,
+ double *next_x,
+ double *next_y)
+{
+ double rx, ry, ux, uy, lx, ly, bx, by;
+ double r1, r2;
+ LineartBoundingArea *ba;
+
+ /* If we are marching towards the right. */
+ if (positive_x > 0) {
+ rx = this->r;
+ ry = y + k * (rx - x);
+
+ /* If we are marching towards the top. */
+ if (positive_y > 0) {
+ uy = this->u;
+ ux = x + (uy - y) / k;
+ r1 = ratiod(rl->l->fbcoord[0], rl->r->fbcoord[0], rx);
+ r2 = ratiod(rl->l->fbcoord[0], rl->r->fbcoord[0], ux);
+ if (MIN2(r1, r2) > 1) {
+ return 0;
+ }
+
+ /* We reached the right side before the top side. */
+ if (r1 <= r2) {
+ LISTBASE_FOREACH (LinkData *, lip, &this->rp) {
+ ba = lip->data;
+ if (ba->u >= ry && ba->b < ry) {
+ *next_x = rx;
+ *next_y = ry;
+ return ba;
+ }
+ }
+ }
+ /* We reached the top side before the right side. */
+ else {
+ LISTBASE_FOREACH (LinkData *, lip, &this->up) {
+ ba = lip->data;
+ if (ba->r >= ux && ba->l < ux) {
+ *next_x = ux;
+ *next_y = uy;
+ return ba;
+ }
+ }
+ }
+ }
+ /* If we are marching towards the bottom. */
+ else if (positive_y < 0) {
+ by = this->b;
+ bx = x + (by - y) / k;
+ r1 = ratiod(rl->l->fbcoord[0], rl->r->fbcoord[0], rx);
+ r2 = ratiod(rl->l->fbcoord[0], rl->r->fbcoord[0], bx);
+ if (MIN2(r1, r2) > 1) {
+ return 0;
+ }
+ if (r1 <= r2) {
+ LISTBASE_FOREACH (LinkData *, lip, &this->rp) {
+ ba = lip->data;
+ if (ba->u >= ry && ba->b < ry) {
+ *next_x = rx;
+ *next_y = ry;
+ return ba;
+ }
+ }
+ }
+ else {
+ LISTBASE_FOREACH (LinkData *, lip, &this->bp) {
+ ba = lip->data;
+ if (ba->r >= bx && ba->l < bx) {
+ *next_x = bx;
+ *next_y = by;
+ return ba;
+ }
+ }
+ }
+ }
+ /* If the line is compeletely horizontal, in which Y diffence == 0. */
+ else {
+ r1 = ratiod(rl->l->fbcoord[0], rl->r->fbcoord[0], this->r);
+ if (r1 > 1) {
+ return 0;
+ }
+ LISTBASE_FOREACH (LinkData *, lip, &this->rp) {
+ ba = lip->data;
+ if (ba->u >= y && ba->b < y) {
+ *next_x = this->r;
+ *next_y = y;
+ return ba;
+ }
+ }
+ }
+ }
+
+ /* If we are marching towards the left. */
+ else if (positive_x < 0) {
+ lx = this->l;
+ ly = y + k * (lx - x);
+
+ /* If we are marching towards the top. */
+ if (positive_y > 0) {
+ uy = this->u;
+ ux = x + (uy - y) / k;
+ r1 = ratiod(rl->l->fbcoord[0], rl->r->fbcoord[0], lx);
+ r2 = ratiod(rl->l->fbcoord[0], rl->r->fbcoord[0], ux);
+ if (MIN2(r1, r2) > 1) {
+ return 0;
+ }
+ if (r1 <= r2) {
+ LISTBASE_FOREACH (LinkData *, lip, &this->lp) {
+ ba = lip->data;
+ if (ba->u >= ly && ba->b < ly) {
+ *next_x = lx;
+ *next_y = ly;
+ return ba;
+ }
+ }
+ }
+ else {
+ LISTBASE_FOREACH (LinkData *, lip, &this->up) {
+ ba = lip->data;
+ if (ba->r >= ux && ba->l < ux) {
+ *next_x = ux;
+ *next_y = uy;
+ return ba;
+ }
+ }
+ }
+ }
+
+ /* If we are marching towards the bottom. */
+ else if (positive_y < 0) {
+ by = this->b;
+ bx = x + (by - y) / k;
+ r1 = ratiod(rl->l->fbcoord[0], rl->r->fbcoord[0], lx);
+ r2 = ratiod(rl->l->fbcoord[0], rl->r->fbcoord[0], bx);
+ if (MIN2(r1, r2) > 1) {
+ return 0;
+ }
+ if (r1 <= r2) {
+ LISTBASE_FOREACH (LinkData *, lip, &this->lp) {
+ ba = lip->data;
+ if (ba->u >= ly && ba->b < ly) {
+ *next_x = lx;
+ *next_y = ly;
+ return ba;
+ }
+ }
+ }
+ else {
+ LISTBASE_FOREACH (LinkData *, lip, &this->bp) {
+ ba = lip->data;
+ if (ba->r >= bx && ba->l < bx) {
+ *next_x = bx;
+ *next_y = by;
+ return ba;
+ }
+ }
+ }
+ }
+ /* Again, horizontal. */
+ else {
+ r1 = ratiod(rl->l->fbcoord[0], rl->r->fbcoord[0], this->l);
+ if (r1 > 1) {
+ return 0;
+ }
+ LISTBASE_FOREACH (LinkData *, lip, &this->lp) {
+ ba = lip->data;
+ if (ba->u >= y && ba->b < y) {
+ *next_x = this->l;
+ *next_y = y;
+ return ba;
+ }
+ }
+ }
+ }
+ /* If the line is completely vertical, hence X difference == 0. */
+ else {
+ if (positive_y > 0) {
+ r1 = ratiod(rl->l->fbcoord[1], rl->r->fbcoord[1], this->u);
+ if (r1 > 1) {
+ return 0;
+ }
+ LISTBASE_FOREACH (LinkData *, lip, &this->up) {
+ ba = lip->data;
+ if (ba->r > x && ba->l <= x) {
+ *next_x = x;
+ *next_y = this->u;
+ return ba;
+ }
+ }
+ }
+ else if (positive_y < 0) {
+ r1 = ratiod(rl->l->fbcoord[1], rl->r->fbcoord[1], this->b);
+ if (r1 > 1) {
+ return 0;
+ }
+ LISTBASE_FOREACH (LinkData *, lip, &this->bp) {
+ ba = lip->data;
+ if (ba->r > x && ba->l <= x) {
+ *next_x = x;
+ *next_y = this->b;
+ return ba;
+ }
+ }
+ }
+ else {
+ /* egment has no length. */
+ return 0;
+ }
+ }
+ return 0;
+}
+
+/* This is the entry point of all line art calculations. */
+int MOD_lineart_compute_feature_lines(Depsgraph *depsgraph, LineartGpencilModifierData *lmd)
+{
+ LineartRenderBuffer *rb;
+ Scene *scene = DEG_get_evaluated_scene(depsgraph);
+ int intersections_only = 0; /* Not used right now, but preserve for future. */
+
+ if (!scene->camera) {
+ return OPERATOR_CANCELLED;
+ }
+
+ rb = lineart_create_render_buffer(scene, lmd);
+
+ /* Triangle thread testing data size varies depending on the thread count.
+ * See definition of LineartRenderTriangleThread for details. */
+ rb->triangle_size = lineart_triangle_size_get(scene, rb);
+
+ /* This is used to limit calculation to a certain level to save time, lines who have higher
+ * occlusion levels will get ignored. */
+ rb->max_occlusion_level = MAX2(lmd->level_start, lmd->level_end);
+
+ /* Get view vector before loading geometries, because we detect feature lines there. */
+ lineart_main_get_view_vector(rb);
+ lineart_main_load_geometries(
+ depsgraph, scene, scene->camera, rb, lmd->calculation_flags & LRT_ALLOW_DUPLI_OBJECTS);
+
+ if (!rb->vertex_buffer_pointers.first) {
+ /* No geometry loaded, return early. */
+ return OPERATOR_FINISHED;
+ }
+
+ /* Initialize the bounding box acceleration structure, it's a lot like BVH in 3D. */
+ lineart_main_bounding_area_make_initial(rb);
+
+ /* We need to get cut into triangles that are crossing near/far plans, only this way can we get
+ * correct coordinates of those clipped lines. Done in two steps,
+ * setting clip_far==false for near plane. */
+ lineart_main_cull_triangles(rb, false);
+ /* clip_far==true for far plane. */
+ lineart_main_cull_triangles(rb, true);
+
+ /* At this point triangle adjacent info pointers is no longer needed, free them. */
+ lineart_main_free_adjacent_data(rb);
+
+ /* Do the perspective division after clipping is done. */
+ lineart_main_perspective_division(rb);
+
+ /* Triangle intersections are done here during sequential adding of them. Only after this,
+ * triangles and lines are all linked with acceleration structure, and the 2D occlusion stage can
+ * do its job. */
+ lineart_main_add_triangles(rb);
+
+ /* Link lines to acceleration structure, this can only be done after perspective division, if we
+ * do it after triangles being added, the acceleration structure has already been subdivided,
+ * this way we do less list manipulations. */
+ lineart_main_link_lines(rb);
+
+ /* "intersection_only" is preserved for being called in a standalone fashion.
+ * If so the data will already be available at the stage. Otherwise we do the occlusion and
+ * chaining etc.*/
+
+ if (!intersections_only) {
+
+ /* Occlusion is work-and-wait. This call will not return before work is completed. */
+ lineart_main_occlusion_begin(rb);
+
+ /* Chaining is all single threaded. See lineart_chain.c
+ * In this particular call, only lines that are geometrically connected (share the _exact_ same
+ * end point) will be chained together. */
+ MOD_lineart_chain_feature_lines(rb);
+
+ /* We are unable to take care of occlusion if we only connect end points, so here we do a spit,
+ * where the splitting point could be any cut in rl->segments. */
+ MOD_lineart_chain_split_for_fixed_occlusion(rb);
+
+ /* Then we connect chains based on the _proximity_ of their end points in geometry or image
+ * space, here's the place threashold value gets involved. */
+
+ /* If both chaining thresholds are zero, then we allow at least image space chaining to do a
+ * little bit of work so we don't end up in fragmented strokes. */
+ float *t_image = &lmd->chaining_image_threshold;
+ float *t_geom = &lmd->chaining_geometry_threshold;
+ if (*t_image < FLT_EPSILON && *t_geom < FLT_EPSILON) {
+ *t_geom = 0.0f;
+ *t_image = 0.001f;
+ }
+
+ /* do_geometry_space = true. */
+ MOD_lineart_chain_connect(rb, true);
+
+ /* After chaining, we need to clear flags so we can do another round in image space. */
+ MOD_lineart_chain_clear_picked_flag(rb);
+
+ /* do_geometry_space = false (it's image_space). */
+ MOD_lineart_chain_connect(rb, false);
+
+ /* Clear again so we don't confuse GPencil generation calls. */
+ MOD_lineart_chain_clear_picked_flag(rb);
+
+ /* This configuration ensures there won't be accidental lost of short unchained segments. */
+ MOD_lineart_chain_discard_short(rb, MIN3(*t_image, *t_geom, 0.001f) - FLT_EPSILON);
+
+ if (rb->angle_splitting_threshold > FLT_EPSILON) {
+ MOD_lineart_chain_split_angle(rb, rb->angle_splitting_threshold);
+ }
+ }
+
+ if (G.debug_value == 4000) {
+ lineart_count_and_print_render_buffer_memory(rb);
+ }
+
+ return OPERATOR_FINISHED;
+}
+
+static int lineart_rb_line_types(LineartRenderBuffer *rb)
+{
+ int types = 0;
+ types |= rb->use_contour ? LRT_EDGE_FLAG_CONTOUR : 0;
+ types |= rb->use_crease ? LRT_EDGE_FLAG_CREASE : 0;
+ types |= rb->use_material ? LRT_EDGE_FLAG_MATERIAL : 0;
+ types |= rb->use_edge_marks ? LRT_EDGE_FLAG_EDGE_MARK : 0;
+ types |= rb->use_intersections ? LRT_EDGE_FLAG_INTERSECTION : 0;
+ return types;
+}
+
+static void lineart_gpencil_generate(LineartRenderBuffer *rb,
+ Depsgraph *depsgraph,
+ Object *gpencil_object,
+ float (*gp_obmat_inverse)[4],
+ bGPDlayer *UNUSED(gpl),
+ bGPDframe *gpf,
+ int level_start,
+ int level_end,
+ int material_nr,
+ Object *source_object,
+ Collection *source_collection,
+ int types,
+ unsigned char transparency_flags,
+ unsigned char transparency_mask,
+ short thickness,
+ float opacity,
+ float pre_sample_length,
+ const char *source_vgname,
+ const char *vgname,
+ int modifier_flags)
+{
+ if (rb == NULL) {
+ if (G.debug_value == 4000) {
+ printf("NULL Lineart rb!\n");
+ }
+ return;
+ }
+
+ int stroke_count = 0;
+ int color_idx = 0;
+
+ Object *orig_ob = NULL;
+ if (source_object) {
+ orig_ob = source_object->id.orig_id ? (Object *)source_object->id.orig_id : source_object;
+ }
+
+ Collection *orig_col = NULL;
+ if (source_collection) {
+ orig_col = source_collection->id.orig_id ? (Collection *)source_collection->id.orig_id :
+ source_collection;
+ }
+
+ /* (!orig_col && !orig_ob) means the whole scene is selected. */
+
+ float mat[4][4];
+ unit_m4(mat);
+
+ int enabled_types = lineart_rb_line_types(rb);
+ bool invert_input = modifier_flags & LRT_GPENCIL_INVERT_SOURCE_VGROUP;
+ bool match_output = modifier_flags & LRT_GPENCIL_MATCH_OUTPUT_VGROUP;
+ bool preserve_weight = modifier_flags & LRT_GPENCIL_SOFT_SELECTION;
+
+ LISTBASE_FOREACH (LineartRenderLineChain *, 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 (LineartRenderLineChainItem *, 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 (LineartRenderLineChainItem *, rlci, &rlc->chain) {
+ vindex = rlci->index;
+ if (vindex >= me->totvert) {
+ break;
+ }
+ MDeformWeight *mdw = BKE_defvert_ensure_index(&me->dvert[vindex], dindex);
+ MDeformWeight *gdw = BKE_defvert_ensure_index(&gps->dvert[sindex], gpdg);
+ if (preserve_weight) {
+ float use_weight = mdw->weight;
+ if (invert_input) {
+ use_weight = 1 - use_weight;
+ }
+ gdw->weight = MAX2(use_weight, gdw->weight);
+ }
+ else {
+ if (mdw->weight > 0.999f) {
+ gdw->weight = 1.0f;
+ }
+ }
+ sindex++;
+ }
+ }
+ dindex++;
+ }
+ }
+ }
+ }
+ }
+
+ if (pre_sample_length > 0.0001) {
+ BKE_gpencil_stroke_sample(gpencil_object->data, gps, pre_sample_length, false);
+ }
+ if (G.debug_value == 4000) {
+ BKE_gpencil_stroke_set_random_color(gps);
+ }
+ BKE_gpencil_stroke_geometry_update(gpencil_object->data, gps);
+ stroke_count++;
+ }
+
+ if (G.debug_value == 4000) {
+ printf("LRT: Generated %d strokes.\n", stroke_count);
+ }
+}
+
+/* Wrapper for external calls. */
+void MOD_lineart_gpencil_generate(LineartRenderBuffer *rb,
+ Depsgraph *depsgraph,
+ Object *ob,
+ bGPDlayer *gpl,
+ bGPDframe *gpf,
+ char source_type,
+ void *source_reference,
+ int level_start,
+ int level_end,
+ int mat_nr,
+ short line_types,
+ unsigned char transparency_flags,
+ unsigned char transparency_mask,
+ short thickness,
+ float opacity,
+ float pre_sample_length,
+ const char *source_vgname,
+ const char *vgname,
+ int modifier_flags)
+{
+
+ if (!gpl || !gpf || !ob) {
+ return;
+ }
+
+ Object *source_object = NULL;
+ Collection *source_collection = NULL;
+ short use_types = 0;
+ if (source_type == LRT_SOURCE_OBJECT) {
+ if (!source_reference) {
+ return;
+ }
+ source_object = (Object *)source_reference;
+ /* Note that intersection lines will only be in collection. */
+ use_types = line_types & (~LRT_EDGE_FLAG_INTERSECTION);
+ }
+ else if (source_type == LRT_SOURCE_COLLECTION) {
+ if (!source_reference) {
+ return;
+ }
+ source_collection = (Collection *)source_reference;
+ use_types = line_types;
+ }
+ else {
+ /* Whole scene. */
+ use_types = line_types;
+ }
+ float gp_obmat_inverse[4][4];
+ invert_m4_m4(gp_obmat_inverse, ob->obmat);
+ lineart_gpencil_generate(rb,
+ depsgraph,
+ ob,
+ gp_obmat_inverse,
+ gpl,
+ gpf,
+ level_start,
+ level_end,
+ mat_nr,
+ source_object,
+ source_collection,
+ use_types,
+ transparency_flags,
+ transparency_mask,
+ thickness,
+ opacity,
+ pre_sample_length,
+ source_vgname,
+ vgname,
+ modifier_flags);
+}
diff --git a/source/blender/gpencil_modifiers/intern/lineart/lineart_intern.h b/source/blender/gpencil_modifiers/intern/lineart/lineart_intern.h
new file mode 100644
index 00000000000..99cfa06b2fe
--- /dev/null
+++ b/source/blender/gpencil_modifiers/intern/lineart/lineart_intern.h
@@ -0,0 +1,113 @@
+/*
+ * 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
+ */
+
+#pragma once
+
+#include "BLI_linklist.h"
+#include "BLI_listbase.h"
+#include "BLI_math.h"
+#include "BLI_threads.h"
+
+#include "DNA_lineart_types.h"
+
+#include <math.h>
+#include <string.h>
+
+struct LineartStaticMemPool;
+struct LineartStaticMemPoolNode;
+struct LineartRenderLine;
+struct LineartRenderBuffer;
+
+void *lineart_list_append_pointer_pool(ListBase *h, struct LineartStaticMemPool *smp, void *data);
+void *lineart_list_append_pointer_pool_sized(ListBase *h,
+ struct LineartStaticMemPool *smp,
+ void *data,
+ int size);
+void *list_push_pointer_static(ListBase *h, struct LineartStaticMemPool *smp, void *p);
+void *list_push_pointer_static_sized(ListBase *h,
+ struct LineartStaticMemPool *smp,
+ void *p,
+ int size);
+
+void *lineart_list_pop_pointer_no_free(ListBase *h);
+void lineart_list_remove_pointer_item_no_free(ListBase *h, LinkData *lip);
+
+struct LineartStaticMemPoolNode *lineart_mem_new_static_pool(struct LineartStaticMemPool *smp,
+ size_t size);
+void *lineart_mem_aquire(struct LineartStaticMemPool *smp, size_t size);
+void *lineart_mem_aquire_thread(struct LineartStaticMemPool *smp, size_t size);
+void lineart_mem_destroy(struct LineartStaticMemPool *smp);
+
+void lineart_prepend_line_direct(struct LineartRenderLine **first, void *node);
+void lineart_prepend_pool(LinkNode **first, struct LineartStaticMemPool *smp, void *link);
+
+void lineart_matrix_ortho_44d(double (*mProjection)[4],
+ double xMin,
+ double xMax,
+ double yMin,
+ double yMax,
+ double zMin,
+ double zMax);
+void lineart_matrix_perspective_44d(
+ double (*mProjection)[4], double fFov_rad, double fAspect, double zMin, double zMax);
+
+int lineart_count_intersection_segment_count(struct LineartRenderBuffer *rb);
+
+void lineart_count_and_print_render_buffer_memory(struct LineartRenderBuffer *rb);
+
+#define LRT_ITER_ALL_LINES_BEGIN \
+ LineartRenderLine *rl, *next_rl, **current_list; \
+ rl = rb->contours; \
+ for (current_list = &rb->contours; rl; rl = next_rl) { \
+ next_rl = rl->next;
+
+#define LRT_ITER_ALL_LINES_NEXT \
+ while (!next_rl) { \
+ if (current_list == &rb->contours) { \
+ current_list = &rb->crease_lines; \
+ } \
+ else if (current_list == &rb->crease_lines) { \
+ current_list = &rb->material_lines; \
+ } \
+ else if (current_list == &rb->material_lines) { \
+ current_list = &rb->edge_marks; \
+ } \
+ else if (current_list == &rb->edge_marks) { \
+ current_list = &rb->intersection_lines; \
+ } \
+ else { \
+ break; \
+ } \
+ next_rl = *current_list; \
+ }
+
+#define LRT_ITER_ALL_LINES_END \
+ LRT_ITER_ALL_LINES_NEXT \
+ }
+
+#define LRT_BOUND_AREA_CROSSES(b1, b2) \
+ ((b1)[0] < (b2)[1] && (b1)[1] > (b2)[0] && (b1)[3] < (b2)[2] && (b1)[2] > (b2)[3])
+
+/* Initial bounding area row/column count, setting 4 is the simplest way algorithm could function
+ * efficiently. */
+#define LRT_BA_ROWS 4
diff --git a/source/blender/gpencil_modifiers/intern/lineart/lineart_ops.c b/source/blender/gpencil_modifiers/intern/lineart/lineart_ops.c
new file mode 100644
index 00000000000..d5bd232cc9c
--- /dev/null
+++ b/source/blender/gpencil_modifiers/intern/lineart/lineart_ops.c
@@ -0,0 +1,439 @@
+/*
+ * 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 <stdlib.h>
+
+#include "MEM_guardedalloc.h"
+
+#include "BKE_collection.h"
+#include "BKE_context.h"
+#include "BKE_global.h"
+#include "BKE_gpencil.h"
+#include "BKE_report.h"
+#include "BKE_scene.h"
+
+#include "DEG_depsgraph_query.h"
+
+#include "BLI_utildefines.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "DNA_gpencil_modifier_types.h"
+#include "DNA_gpencil_types.h"
+#include "DNA_scene_types.h"
+
+#include "UI_resources.h"
+
+#include "MOD_gpencil_lineart.h"
+#include "MOD_lineart.h"
+
+#include "lineart_intern.h"
+
+static void clear_strokes(Object *ob, GpencilModifierData *md, int frame)
+{
+ if (md->type != eGpencilModifierType_Lineart) {
+ return;
+ }
+ LineartGpencilModifierData *lmd = (LineartGpencilModifierData *)md;
+ bGPdata *gpd = ob->data;
+
+ bGPDlayer *gpl = BKE_gpencil_layer_get_by_name(gpd, lmd->target_layer, 1);
+ if (!gpl) {
+ return;
+ }
+ bGPDframe *gpf = BKE_gpencil_layer_frame_find(gpl, frame);
+
+ if (!gpf) {
+ /* No greasepencil frame found. */
+ return;
+ }
+
+ BKE_gpencil_layer_frame_delete(gpl, gpf);
+}
+
+static bool bake_strokes(Object *ob, Depsgraph *dg, GpencilModifierData *md, int frame)
+{
+ LineartGpencilModifierData *lmd = (LineartGpencilModifierData *)md;
+ bGPdata *gpd = ob->data;
+
+ bGPDlayer *gpl = BKE_gpencil_layer_get_by_name(gpd, lmd->target_layer, 1);
+ if (!gpl) {
+ return false;
+ }
+ bool only_use_existing_gp_frames = false;
+ bGPDframe *gpf = (only_use_existing_gp_frames ?
+ BKE_gpencil_layer_frame_find(gpl, frame) :
+ BKE_gpencil_layer_frame_get(gpl, frame, GP_GETFRAME_ADD_NEW));
+
+ if (!gpf) {
+ /* No greasepencil frame created or found. */
+ return false;
+ }
+
+ MOD_lineart_compute_feature_lines(dg, lmd);
+
+ MOD_lineart_gpencil_generate(
+ lmd->render_buffer,
+ dg,
+ ob,
+ gpl,
+ gpf,
+ lmd->source_type,
+ lmd->source_type == LRT_SOURCE_OBJECT ? (void *)lmd->source_object :
+ (void *)lmd->source_collection,
+ lmd->level_start,
+ lmd->use_multiple_levels ? lmd->level_end : lmd->level_start,
+ lmd->target_material ? BKE_gpencil_object_material_index_get(ob, lmd->target_material) : 0,
+ lmd->line_types,
+ lmd->transparency_flags,
+ lmd->transparency_mask,
+ lmd->thickness,
+ lmd->opacity,
+ lmd->pre_sample_length,
+ lmd->source_vertex_group,
+ lmd->vgname,
+ lmd->flags);
+
+ MOD_lineart_destroy_render_data(lmd);
+
+ return true;
+}
+
+typedef struct LineartBakeJob {
+ wmWindowManager *wm;
+ void *owner;
+ short *stop, *do_update;
+ float *progress;
+
+ /* C or ob must have one != NULL. */
+ bContext *C;
+ LinkNode *objects;
+ Scene *scene;
+ Depsgraph *dg;
+ int frame;
+ int frame_begin;
+ int frame_end;
+ int frame_orig;
+ int frame_increment;
+ bool overwrite_frames;
+} LineartBakeJob;
+
+static bool lineart_gpencil_bake_single_target(LineartBakeJob *bj, Object *ob, int frame)
+{
+ bool touched = false;
+ if (ob->type != OB_GPENCIL || G.is_break) {
+ return false;
+ }
+
+ if (bj->overwrite_frames) {
+ LISTBASE_FOREACH (GpencilModifierData *, md, &ob->greasepencil_modifiers) {
+ clear_strokes(ob, md, frame);
+ }
+ }
+
+ LISTBASE_FOREACH (GpencilModifierData *, md, &ob->greasepencil_modifiers) {
+ if (bake_strokes(ob, bj->dg, md, frame)) {
+ touched = true;
+ }
+ }
+
+ return touched;
+}
+
+static void lineart_gpencil_guard_modifiers(LineartBakeJob *bj)
+{
+ for (LinkNode *l = bj->objects; l; l = l->next) {
+ Object *ob = l->link;
+ LISTBASE_FOREACH (GpencilModifierData *, md, &ob->greasepencil_modifiers) {
+ if (md->type == eGpencilModifierType_Lineart) {
+ LineartGpencilModifierData *lmd = (LineartGpencilModifierData *)md;
+ lmd->flags |= LRT_GPENCIL_IS_BAKED;
+ }
+ }
+ }
+}
+
+static void lineart_gpencil_bake_startjob(void *customdata,
+ short *stop,
+ short *do_update,
+ float *progress)
+{
+ LineartBakeJob *bj = (LineartBakeJob *)customdata;
+ bj->stop = stop;
+ bj->do_update = do_update;
+ bj->progress = progress;
+
+ lineart_gpencil_guard_modifiers(bj);
+
+ for (int frame = bj->frame_begin; frame <= bj->frame_end; frame += bj->frame_increment) {
+
+ if (G.is_break) {
+ G.is_break = false;
+ break;
+ }
+
+ BKE_scene_frame_set(bj->scene, frame);
+ BKE_scene_graph_update_for_newframe(bj->dg);
+
+ for (LinkNode *l = bj->objects; l; l = l->next) {
+ Object *ob = l->link;
+ if (lineart_gpencil_bake_single_target(bj, ob, frame)) {
+ DEG_id_tag_update((struct ID *)ob->data, ID_RECALC_GEOMETRY);
+ WM_event_add_notifier(bj->C, NC_GPENCIL | ND_DATA | NA_EDITED, ob);
+ }
+ }
+
+ /* Update and refresh the progress bar. */
+ *bj->progress = (float)(frame - bj->frame_begin) / (bj->frame_end - bj->frame_begin);
+ *bj->do_update = true;
+ }
+
+ /* This need to be reset manually. */
+ G.is_break = false;
+
+ /* Restore original frame. */
+ BKE_scene_frame_set(bj->scene, bj->frame_orig);
+ BKE_scene_graph_update_for_newframe(bj->dg);
+}
+
+static void lineart_gpencil_bake_endjob(void *customdata)
+{
+ LineartBakeJob *bj = customdata;
+
+ WM_set_locked_interface(CTX_wm_manager(bj->C), false);
+
+ WM_main_add_notifier(NC_SCENE | ND_FRAME, bj->scene);
+
+ for (LinkNode *l = bj->objects; l; l = l->next) {
+ WM_main_add_notifier(NC_GPENCIL | ND_DATA | NA_EDITED, (Object *)l->link);
+ }
+
+ BLI_linklist_free(bj->objects, NULL);
+}
+
+static int lineart_gpencil_bake_common(bContext *C,
+ wmOperator *op,
+ bool bake_all_targets,
+ bool do_background)
+{
+ LineartBakeJob *bj = MEM_callocN(sizeof(LineartBakeJob), "LineartBakeJob");
+
+ if (!bake_all_targets) {
+ Object *ob = CTX_data_active_object(C);
+ if (!ob || ob->type != OB_GPENCIL) {
+ WM_report(RPT_ERROR, "No active object or active object isn't a GPencil object.");
+ return OPERATOR_FINISHED;
+ }
+ BLI_linklist_prepend(&bj->objects, ob);
+ }
+ else {
+ /* CTX_DATA_BEGIN is not available for interating in objects while using the Job system. */
+ CTX_DATA_BEGIN (C, Object *, ob, visible_objects) {
+ if (ob->type == OB_GPENCIL) {
+ LISTBASE_FOREACH (GpencilModifierData *, md, &ob->greasepencil_modifiers) {
+ if (md->type == eGpencilModifierType_Lineart) {
+ BLI_linklist_prepend(&bj->objects, ob);
+ }
+ }
+ }
+ }
+ CTX_DATA_END;
+ }
+ bj->C = C;
+ Scene *scene = CTX_data_scene(C);
+ bj->scene = scene;
+ bj->dg = CTX_data_depsgraph_pointer(C);
+ bj->frame_begin = scene->r.sfra;
+ bj->frame_end = scene->r.efra;
+ bj->frame_orig = scene->r.cfra;
+ bj->frame_increment = scene->r.frame_step;
+ bj->overwrite_frames = true;
+
+ if (do_background) {
+ wmJob *wm_job = WM_jobs_get(CTX_wm_manager(C),
+ CTX_wm_window(C),
+ scene,
+ "Line Art",
+ WM_JOB_PROGRESS,
+ WM_JOB_TYPE_LINEART);
+
+ WM_jobs_customdata_set(wm_job, bj, MEM_freeN);
+ WM_jobs_timer(wm_job, 0.1, NC_GPENCIL | ND_DATA | NA_EDITED, NC_GPENCIL | ND_DATA | NA_EDITED);
+ WM_jobs_callbacks(
+ wm_job, lineart_gpencil_bake_startjob, NULL, NULL, lineart_gpencil_bake_endjob);
+
+ WM_set_locked_interface(CTX_wm_manager(C), true);
+
+ WM_jobs_start(CTX_wm_manager(C), wm_job);
+
+ WM_event_add_modal_handler(C, op);
+
+ return OPERATOR_RUNNING_MODAL;
+ }
+
+ float pseduo_progress;
+ short pseduo_do_update;
+ lineart_gpencil_bake_startjob(bj, NULL, &pseduo_do_update, &pseduo_progress);
+
+ BLI_linklist_free(bj->objects, NULL);
+ MEM_freeN(bj);
+
+ return OPERATOR_FINISHED;
+}
+
+static int lineart_gpencil_bake_strokes_all_invoke(bContext *C,
+ wmOperator *op,
+ const wmEvent *UNUSED(event))
+{
+ return lineart_gpencil_bake_common(C, op, true, true);
+}
+static int lineart_gpencil_bake_strokes_all_exec(bContext *C, wmOperator *op)
+{
+ return lineart_gpencil_bake_common(C, op, true, false);
+}
+static int lineart_gpencil_bake_strokes_invoke(bContext *C,
+ wmOperator *op,
+ const wmEvent *UNUSED(event))
+{
+ return lineart_gpencil_bake_common(C, op, false, true);
+}
+static int lineart_gpencil_bake_strokes_exec(bContext *C, wmOperator *op)
+{
+ return lineart_gpencil_bake_common(C, op, false, false);
+
+ return OPERATOR_FINISHED;
+}
+static int lineart_gpencil_bake_strokes_commom_modal(bContext *C,
+ wmOperator *op,
+ const wmEvent *UNUSED(event))
+{
+ Scene *scene = (Scene *)op->customdata;
+
+ /* no running blender, remove handler and pass through. */
+ if (0 == WM_jobs_test(CTX_wm_manager(C), scene, WM_JOB_TYPE_LINEART)) {
+ return OPERATOR_FINISHED | OPERATOR_PASS_THROUGH;
+ }
+
+ return OPERATOR_PASS_THROUGH;
+}
+
+static void lineart_gpencil_clear_strokes_exec_common(Object *ob)
+{
+ if (ob->type != OB_GPENCIL) {
+ return;
+ }
+ LISTBASE_FOREACH (GpencilModifierData *, md, &ob->greasepencil_modifiers) {
+ if (md->type != eGpencilModifierType_Lineart) {
+ continue;
+ }
+ LineartGpencilModifierData *lmd = (LineartGpencilModifierData *)md;
+ bGPdata *gpd = ob->data;
+
+ bGPDlayer *gpl = BKE_gpencil_layer_get_by_name(gpd, lmd->target_layer, 1);
+ if (!gpl) {
+ continue;
+ }
+ BKE_gpencil_free_frames(gpl);
+
+ md->mode |= eGpencilModifierMode_Realtime | eGpencilModifierMode_Render;
+
+ lmd->flags &= (~LRT_GPENCIL_IS_BAKED);
+ }
+ DEG_id_tag_update((struct ID *)ob->data, ID_RECALC_GEOMETRY);
+}
+
+static int lineart_gpencil_clear_strokes_exec(bContext *C, wmOperator *UNUSED(op))
+{
+ Object *ob = CTX_data_active_object(C);
+
+ lineart_gpencil_clear_strokes_exec_common(ob);
+
+ WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, ob);
+
+ return OPERATOR_FINISHED;
+}
+static int lineart_gpencil_clear_strokes_all_exec(bContext *C, wmOperator *op)
+{
+ CTX_DATA_BEGIN (C, Object *, ob, visible_objects) {
+ lineart_gpencil_clear_strokes_exec_common(ob);
+ WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, ob);
+ }
+ CTX_DATA_END;
+
+ BKE_report(op->reports, RPT_INFO, "All line art objects are now cleared.");
+
+ return OPERATOR_FINISHED;
+}
+
+/* Bake all line art modifiers on the current object. */
+void OBJECT_OT_lineart_bake_strokes(wmOperatorType *ot)
+{
+ ot->name = "Bake Line Art";
+ ot->description = "Bake Line Art for current GPencil object";
+ ot->idname = "OBJECT_OT_lineart_bake_strokes";
+
+ ot->invoke = lineart_gpencil_bake_strokes_invoke;
+ ot->exec = lineart_gpencil_bake_strokes_exec;
+ ot->modal = lineart_gpencil_bake_strokes_commom_modal;
+}
+
+/* Bake all lineart objects in the scene. */
+void OBJECT_OT_lineart_bake_strokes_all(wmOperatorType *ot)
+{
+ ot->name = "Bake Line Art (All)";
+ ot->description = "Bake all GPencil objects who has at least one Line Art modifier";
+ ot->idname = "OBJECT_OT_lineart_bake_strokes_all";
+
+ ot->invoke = lineart_gpencil_bake_strokes_all_invoke;
+ ot->exec = lineart_gpencil_bake_strokes_all_exec;
+ ot->modal = lineart_gpencil_bake_strokes_commom_modal;
+}
+
+/* clear all line art modifiers on the current object. */
+void OBJECT_OT_lineart_clear(wmOperatorType *ot)
+{
+ ot->name = "Clear Baked Line Art";
+ ot->description = "Clear all strokes in current GPencil obejct";
+ ot->idname = "OBJECT_OT_lineart_clear";
+
+ ot->exec = lineart_gpencil_clear_strokes_exec;
+}
+
+/* clear all lineart objects in the scene. */
+void OBJECT_OT_lineart_clear_all(wmOperatorType *ot)
+{
+ ot->name = "Clear Baked Line Art (All)";
+ ot->description = "Clear all strokes in all GPencil obejcts who has a Line Art modifier";
+ ot->idname = "OBJECT_OT_lineart_clear_all";
+
+ ot->exec = lineart_gpencil_clear_strokes_all_exec;
+}
+
+void WM_operatortypes_lineart(void)
+{
+ WM_operatortype_append(OBJECT_OT_lineart_bake_strokes);
+ WM_operatortype_append(OBJECT_OT_lineart_bake_strokes_all);
+ WM_operatortype_append(OBJECT_OT_lineart_clear);
+ WM_operatortype_append(OBJECT_OT_lineart_clear_all);
+}
diff --git a/source/blender/gpencil_modifiers/intern/lineart/lineart_util.c b/source/blender/gpencil_modifiers/intern/lineart/lineart_util.c
new file mode 100644
index 00000000000..0d145042e08
--- /dev/null
+++ b/source/blender/gpencil_modifiers/intern/lineart/lineart_util.c
@@ -0,0 +1,233 @@
+/*
+ * 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 <stdio.h>
+#include <stdlib.h>
+/* #include <time.h> */
+#include "MEM_guardedalloc.h"
+#include "MOD_lineart.h"
+#include <math.h>
+
+#include "lineart_intern.h"
+
+/* Line art memory and list helper */
+
+void *lineart_list_append_pointer_pool(ListBase *h, LineartStaticMemPool *smp, void *data)
+{
+ LinkData *lip;
+ if (h == NULL) {
+ return 0;
+ }
+ lip = lineart_mem_aquire(smp, sizeof(LinkData));
+ lip->data = data;
+ BLI_addtail(h, lip);
+ return lip;
+}
+void *lineart_list_append_pointer_pool_sized(ListBase *h,
+ LineartStaticMemPool *smp,
+ void *data,
+ int size)
+{
+ LinkData *lip;
+ if (h == NULL) {
+ return 0;
+ }
+ lip = lineart_mem_aquire(smp, size);
+ lip->data = data;
+ BLI_addtail(h, lip);
+ return lip;
+}
+
+void *lineart_list_pop_pointer_no_free(ListBase *h)
+{
+ LinkData *lip;
+ void *rev = 0;
+ if (h == NULL) {
+ return 0;
+ }
+ lip = BLI_pophead(h);
+ rev = lip ? lip->data : 0;
+ return rev;
+}
+void lineart_list_remove_pointer_item_no_free(ListBase *h, LinkData *lip)
+{
+ BLI_remlink(h, (void *)lip);
+}
+
+LineartStaticMemPoolNode *lineart_mem_new_static_pool(LineartStaticMemPool *smp, size_t size)
+{
+ size_t set_size = size;
+ if (set_size < LRT_MEMORY_POOL_64MB) {
+ set_size = LRT_MEMORY_POOL_64MB; /* Prevent too many small allocations. */
+ }
+ size_t total_size = size + sizeof(LineartStaticMemPoolNode);
+ LineartStaticMemPoolNode *smpn = MEM_callocN(total_size, "mempool");
+ smpn->size = total_size;
+ smpn->used_byte = sizeof(LineartStaticMemPoolNode);
+ BLI_addhead(&smp->pools, smpn);
+ return smpn;
+}
+void *lineart_mem_aquire(LineartStaticMemPool *smp, size_t size)
+{
+ LineartStaticMemPoolNode *smpn = smp->pools.first;
+ void *ret;
+
+ if (!smpn || (smpn->used_byte + size) > smpn->size) {
+ smpn = lineart_mem_new_static_pool(smp, size);
+ }
+
+ ret = ((unsigned char *)smpn) + smpn->used_byte;
+
+ smpn->used_byte += size;
+
+ return ret;
+}
+void *lineart_mem_aquire_thread(LineartStaticMemPool *smp, size_t size)
+{
+ void *ret;
+
+ BLI_spin_lock(&smp->lock_mem);
+
+ LineartStaticMemPoolNode *smpn = smp->pools.first;
+
+ if (!smpn || (smpn->used_byte + size) > smpn->size) {
+ smpn = lineart_mem_new_static_pool(smp, size);
+ }
+
+ ret = ((unsigned char *)smpn) + smpn->used_byte;
+
+ smpn->used_byte += size;
+
+ BLI_spin_unlock(&smp->lock_mem);
+
+ return ret;
+}
+void lineart_mem_destroy(LineartStaticMemPool *smp)
+{
+ LineartStaticMemPoolNode *smpn;
+ while ((smpn = BLI_pophead(&smp->pools)) != NULL) {
+ MEM_freeN(smpn);
+ }
+}
+
+void lineart_prepend_line_direct(LineartRenderLine **first, void *node)
+{
+ LineartRenderLine *ln = (LineartRenderLine *)node;
+ ln->next = (*first);
+ (*first) = ln;
+}
+
+void lineart_prepend_pool(LinkNode **first, LineartStaticMemPool *smp, void *link)
+{
+ LinkNode *ln = lineart_mem_aquire_thread(smp, sizeof(LinkNode));
+ ln->next = (*first);
+ ln->link = link;
+ (*first) = ln;
+}
+
+/* =======================================================================[str] */
+
+void lineart_matrix_perspective_44d(
+ double (*mProjection)[4], double fFov_rad, double fAspect, double zMin, double zMax)
+{
+ double yMax;
+ double yMin;
+ double xMin;
+ double xMax;
+
+ if (fAspect < 1) {
+ yMax = zMin * tan(fFov_rad * 0.5f);
+ yMin = -yMax;
+ xMin = yMin * fAspect;
+ xMax = -xMin;
+ }
+ else {
+ xMax = zMin * tan(fFov_rad * 0.5f);
+ xMin = -xMax;
+ yMin = xMin / fAspect;
+ yMax = -yMin;
+ }
+
+ unit_m4_db(mProjection);
+
+ mProjection[0][0] = (2.0f * zMin) / (xMax - xMin);
+ mProjection[1][1] = (2.0f * zMin) / (yMax - yMin);
+ mProjection[2][0] = (xMax + xMin) / (xMax - xMin);
+ mProjection[2][1] = (yMax + yMin) / (yMax - yMin);
+ mProjection[2][2] = -((zMax + zMin) / (zMax - zMin));
+ mProjection[2][3] = -1.0f;
+ mProjection[3][2] = -((2.0f * (zMax * zMin)) / (zMax - zMin));
+ mProjection[3][3] = 0.0f;
+}
+void lineart_matrix_ortho_44d(double (*mProjection)[4],
+ double xMin,
+ double xMax,
+ double yMin,
+ double yMax,
+ double zMin,
+ double zMax)
+{
+ unit_m4_db(mProjection);
+
+ mProjection[0][0] = 2.0f / (xMax - xMin);
+ mProjection[1][1] = 2.0f / (yMax - yMin);
+ mProjection[2][2] = -2.0f / (zMax - zMin);
+ mProjection[3][0] = -((xMax + xMin) / (xMax - xMin));
+ mProjection[3][1] = -((yMax + yMin) / (yMax - yMin));
+ mProjection[3][2] = -((zMax + zMin) / (zMax - zMin));
+ mProjection[3][3] = 1.0f;
+}
+
+void lineart_count_and_print_render_buffer_memory(LineartRenderBuffer *rb)
+{
+ size_t total = 0;
+ size_t sum_this = 0;
+ size_t count_this = 0;
+
+ LISTBASE_FOREACH (LineartStaticMemPoolNode *, smpn, &rb->render_data_pool.pools) {
+ count_this++;
+ sum_this += LRT_MEMORY_POOL_64MB;
+ }
+ printf("LANPR Memory allocated %lu Standalone nodes, total %lu Bytes.\n", count_this, sum_this);
+ total += sum_this;
+ sum_this = 0;
+ count_this = 0;
+
+ LISTBASE_FOREACH (LineartRenderElementLinkNode *, reln, &rb->line_buffer_pointers) {
+ count_this++;
+ sum_this += reln->element_count * sizeof(LineartRenderLine);
+ }
+ printf(" allocated %lu edge blocks, total %lu Bytes.\n", count_this, sum_this);
+ total += sum_this;
+ sum_this = 0;
+ count_this = 0;
+
+ LISTBASE_FOREACH (LineartRenderElementLinkNode *, reln, &rb->triangle_buffer_pointers) {
+ count_this++;
+ sum_this += reln->element_count * rb->triangle_size;
+ }
+ printf(" allocated %lu triangle blocks, total %lu Bytes.\n", count_this, sum_this);
+ total += sum_this;
+ sum_this = 0;
+ count_this = 0;
+}
diff --git a/source/blender/makesdna/DNA_collection_types.h b/source/blender/makesdna/DNA_collection_types.h
index 0a80e00d456..f5fcb0b190e 100644
--- a/source/blender/makesdna/DNA_collection_types.h
+++ b/source/blender/makesdna/DNA_collection_types.h
@@ -46,6 +46,14 @@ typedef struct CollectionChild {
struct Collection *collection;
} CollectionChild;
+enum CollectionFeatureLine_Usage {
+ COLLECTION_LRT_INCLUDE = 0,
+ COLLECTION_LRT_OCCLUSION_ONLY = (1 << 0),
+ COLLECTION_LRT_EXCLUDE = (1 << 1),
+ COLLECTION_LRT_INTERSECTION_ONLY = (1 << 2),
+ COLLECTION_LRT_NO_INTERSECTION = (1 << 3),
+};
+
typedef struct Collection {
ID id;
@@ -63,8 +71,10 @@ typedef struct Collection {
/* Runtime-only, always cleared on file load. */
short tag;
+ /** Line Art engine specific */
+ short lineart_usage;
+
int16_t color_tag;
- char _pad[2];
/* Runtime. Cache of objects in this collection and all its
* children. This is created on demand when e.g. some physics
@@ -72,6 +82,9 @@ typedef struct Collection {
* collections due to memory usage reasons. */
ListBase object_cache;
+ /* Need this for line art sub-collection selections. */
+ ListBase object_cache_instanced;
+
/* Runtime. List of collections that are a parent of this
* datablock. */
ListBase parents;
@@ -89,6 +102,7 @@ enum {
COLLECTION_RESTRICT_RENDER = (1 << 3), /* Disable in renders. */
COLLECTION_HAS_OBJECT_CACHE = (1 << 4), /* Runtime: object_cache is populated. */
COLLECTION_IS_MASTER = (1 << 5), /* Is master collection embedded in the scene. */
+ COLLECTION_HAS_OBJECT_CACHE_INSTANCED = (1 << 6), /* for object_cache_instanced. */
};
/* Collection->tag */
diff --git a/source/blender/makesdna/DNA_gpencil_modifier_defaults.h b/source/blender/makesdna/DNA_gpencil_modifier_defaults.h
index 11d4ee1b2cd..baf2b2414fa 100644
--- a/source/blender/makesdna/DNA_gpencil_modifier_defaults.h
+++ b/source/blender/makesdna/DNA_gpencil_modifier_defaults.h
@@ -283,4 +283,17 @@
.colorband = NULL, \
}
+#define _DNA_DEFAULT_LineartGpencilModifierData \
+ { \
+ .line_types = LRT_EDGE_FLAG_ALL_TYPE, \
+ .thickness = 25, \
+ .opacity = 1.0f, \
+ .flags = LRT_GPENCIL_MATCH_OUTPUT_VGROUP | LRT_GPENCIL_SOFT_SELECTION, \
+ .crease_threshold = DEG2RAD(140.0f), \
+ .calculation_flags = LRT_ALLOW_DUPLI_OBJECTS | LRT_REMOVE_DOUBLES | LRT_ALLOW_OVERLAPPING_EDGES | LRT_ALLOW_CLIPPING_BOUNDARIES, \
+ .angle_splitting_threshold = DEG2RAD(60.0f), \
+ .chaining_geometry_threshold = 0.001f, \
+ .chaining_image_threshold = 0.001f, \
+ }
+
/* clang-format off */
diff --git a/source/blender/makesdna/DNA_gpencil_modifier_types.h b/source/blender/makesdna/DNA_gpencil_modifier_types.h
index b2d62e0a5f6..a9262bf7ca4 100644
--- a/source/blender/makesdna/DNA_gpencil_modifier_types.h
+++ b/source/blender/makesdna/DNA_gpencil_modifier_types.h
@@ -53,6 +53,7 @@ typedef enum GpencilModifierType {
eGpencilModifierType_Time = 16,
eGpencilModifierType_Multiply = 17,
eGpencilModifierType_Texture = 18,
+ eGpencilModifierType_Lineart = 19,
/* Keep last. */
NUM_GREASEPENCIL_MODIFIER_TYPES,
} GpencilModifierType;
@@ -809,6 +810,78 @@ typedef enum eTextureGpencil_Mode {
STROKE_AND_FILL = 2,
} eTextureGpencil_Mode;
+typedef enum eLineartGpencilModifierSource {
+ LRT_SOURCE_COLLECTION = 0,
+ LRT_SOURCE_OBJECT = 1,
+ LRT_SOURCE_SCENE = 2,
+} eLineartGpencilModifierSource;
+
+typedef enum eLineArtGPencilModifierFlags {
+ LRT_GPENCIL_INVERT_SOURCE_VGROUP = (1 << 0),
+ LRT_GPENCIL_MATCH_OUTPUT_VGROUP = (1 << 1),
+ LRT_GPENCIL_SOFT_SELECTION = (1 << 2),
+ LRT_GPENCIL_IS_BAKED = (1 << 3),
+} eLineArtGPencilModifierFlags;
+
+typedef enum eLineartGpencilTransparencyFlags {
+ LRT_GPENCIL_TRANSPARENCY_ENABLE = (1 << 0),
+ /** Set to true means using "and" instead of "or" logic on mask bits. */
+ LRT_GPENCIL_TRANSPARENCY_MATCH = (1 << 1),
+} eLineartGpencilTransparencyFlags;
+
+typedef struct LineartGpencilModifierData {
+ GpencilModifierData modifier;
+
+ short line_types; /* line type enable flags, bits in eLineartEdgeFlag */
+
+ char source_type; /* Object or Collection, from eLineartGpencilModifierSource */
+
+ char use_multiple_levels;
+ short level_start;
+ short level_end;
+
+ struct Object *source_object;
+ struct Collection *source_collection;
+
+ struct Material *target_material;
+ char target_layer[64];
+
+ /** These two variables are to pass on vertex group information from mesh to strokes.
+ * vgname specifies which vertex groups our strokes from source_vertex_group will go to. */
+ char source_vertex_group[64];
+ char vgname[64];
+
+ float opacity;
+ short thickness;
+
+ unsigned char transparency_flags; /* eLineartGpencilTransparencyFlags */
+ unsigned char transparency_mask;
+
+ /** 0-1 range for cosine angle */
+ float crease_threshold;
+
+ /** 0-PI angle, for splitting strokes at sharp points */
+ float angle_splitting_threshold;
+
+ /* CPU mode */
+ float chaining_geometry_threshold;
+ float chaining_image_threshold;
+
+ float pre_sample_length;
+
+ /* Ported from SceneLineArt flags. */
+ int calculation_flags;
+
+ /* Additional Switches. */
+ int flags;
+
+ int _pad;
+
+ /* Runtime only. */
+ void *render_buffer;
+
+} LineartGpencilModifierData;
+
#ifdef __cplusplus
}
#endif
diff --git a/source/blender/makesdna/DNA_gpencil_types.h b/source/blender/makesdna/DNA_gpencil_types.h
index 9d969a29add..8facdca2f9c 100644
--- a/source/blender/makesdna/DNA_gpencil_types.h
+++ b/source/blender/makesdna/DNA_gpencil_types.h
@@ -34,6 +34,7 @@ extern "C" {
struct AnimData;
struct Curve;
struct MDeformVert;
+struct Curve;
#define GP_DEFAULT_PIX_FACTOR 1.0f
#define GP_DEFAULT_GRID_LINES 4
@@ -412,6 +413,8 @@ typedef enum eGPDframe_Flag {
GP_FRAME_PAINT = (1 << 0),
/* for editing in Action Editor */
GP_FRAME_SELECT = (1 << 1),
+ /* Line Art generation */
+ GP_FRAME_LRT_CLEARED = (1 << 2),
} eGPDframe_Flag;
/* ***************************************** */
diff --git a/source/blender/makesdna/DNA_lineart_types.h b/source/blender/makesdna/DNA_lineart_types.h
new file mode 100644
index 00000000000..2eb36cfb9d3
--- /dev/null
+++ b/source/blender/makesdna/DNA_lineart_types.h
@@ -0,0 +1,70 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * 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) 2010 Blender Foundation.
+ * All rights reserved.
+ *
+ * The Original Code is: all of this file.
+ *
+ * Contributor(s): none yet.
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+#ifndef __DNA_LRT_TYPES_H__
+#define __DNA_LRT_TYPES_H__
+
+/** \file DNA_lineart_types.h
+ * \ingroup DNA
+ */
+
+#include "DNA_ID.h"
+#include "DNA_listBase.h"
+
+struct Object;
+struct Material;
+
+/* Notice that we need to have this file although no struct defines.
+ * Edge flags and usage flags are used by with scene/object/gpencil modifier bits, and those values
+ * needs to stay consistent throughout. */
+
+typedef enum eLineartMainFlags {
+ LRT_INTERSECTION_AS_CONTOUR = (1 << 0),
+ LRT_EVERYTHING_AS_CONTOUR = (1 << 1),
+ LRT_ALLOW_DUPLI_OBJECTS = (1 << 2),
+ LRT_ALLOW_OVERLAPPING_EDGES = (1 << 3),
+ LRT_ALLOW_CLIPPING_BOUNDARIES = (1 << 4),
+ LRT_REMOVE_DOUBLES = (1 << 5),
+} eLineartMainFlags;
+
+typedef enum eLineartEdgeFlag {
+ LRT_EDGE_FLAG_EDGE_MARK = (1 << 0),
+ LRT_EDGE_FLAG_CONTOUR = (1 << 1),
+ LRT_EDGE_FLAG_CREASE = (1 << 2),
+ LRT_EDGE_FLAG_MATERIAL = (1 << 3),
+ LRT_EDGE_FLAG_INTERSECTION = (1 << 4),
+ /** floating edge, unimplemented yet */
+ LRT_EDGE_FLAG_FLOATING = (1 << 5),
+ /** also used as discarded line mark */
+ LRT_EDGE_FLAG_CHAIN_PICKED = (1 << 6),
+ LRT_EDGE_FLAG_CLIPPED = (1 << 7),
+ /* Maxed out for 8 bits, DON'T ADD ANYMORE until improvements on the data structure. */
+} eLineartEdgeFlag;
+
+#define LRT_EDGE_FLAG_ALL_TYPE 0x3f
+
+#endif
diff --git a/source/blender/makesdna/DNA_material_types.h b/source/blender/makesdna/DNA_material_types.h
index 884c2df6480..55d5ea202f7 100644
--- a/source/blender/makesdna/DNA_material_types.h
+++ b/source/blender/makesdna/DNA_material_types.h
@@ -145,6 +145,16 @@ typedef enum eMaterialGPencilStyle_Mode {
GP_MATERIAL_MODE_SQUARE = 2,
} eMaterialGPencilStyle_Mode;
+typedef struct MaterialLineArt {
+ int flags; /* eMaterialLineArtFlags */
+ unsigned char transparency_mask;
+ unsigned char _pad[3];
+} MaterialLineArt;
+
+typedef enum eMaterialLineArtFlags {
+ LRT_MATERIAL_TRANSPARENCY_ENABLED = (1 << 0),
+} eMaterialLineArtFlags;
+
typedef struct Material {
ID id;
/** Animation data (must be immediately after id for utilities to use it). */
@@ -210,6 +220,7 @@ typedef struct Material {
/** Grease pencil color. */
struct MaterialGPencilStyle *gp_style;
+ struct MaterialLineArt lineart;
} Material;
/* **************** MATERIAL ********************* */
diff --git a/source/blender/makesdna/DNA_object_defaults.h b/source/blender/makesdna/DNA_object_defaults.h
index 1bca572b963..d545b7912c0 100644
--- a/source/blender/makesdna/DNA_object_defaults.h
+++ b/source/blender/makesdna/DNA_object_defaults.h
@@ -66,6 +66,7 @@
.preview = NULL, \
.duplicator_visibility_flag = OB_DUPLI_FLAG_VIEWPORT | OB_DUPLI_FLAG_RENDER, \
.pc_ids = {NULL, NULL}, \
+ .lineart = { .crease_threshold = DEG2RAD(140.0f) }, \
}
/** \} */
diff --git a/source/blender/makesdna/DNA_object_types.h b/source/blender/makesdna/DNA_object_types.h
index f6372a0c240..fa4e3953f5b 100644
--- a/source/blender/makesdna/DNA_object_types.h
+++ b/source/blender/makesdna/DNA_object_types.h
@@ -26,6 +26,11 @@
#include "DNA_object_enums.h"
+#include "DNA_customdata_types.h"
+#include "DNA_defs.h"
+#include "DNA_lineart_types.h"
+#include "DNA_listBase.h"
+
#include "DNA_ID.h"
#include "DNA_action_types.h" /* bAnimVizSettings */
#include "DNA_customdata_types.h"
@@ -200,6 +205,27 @@ typedef struct Object_Runtime {
short _pad2[3];
} Object_Runtime;
+typedef struct ObjectLineArt {
+ short usage;
+ short flags;
+
+ /** if OBJECT_LRT_OWN_CREASE is set */
+ float crease_threshold;
+} ObjectLineArt;
+
+enum ObjectFeatureLine_Usage {
+ OBJECT_LRT_INHERENT = 0,
+ OBJECT_LRT_INCLUDE = (1 << 0),
+ OBJECT_LRT_OCCLUSION_ONLY = (1 << 1),
+ OBJECT_LRT_EXCLUDE = (1 << 2),
+ OBJECT_LRT_INTERSECTION_ONLY = (1 << 3),
+ OBJECT_LRT_NO_INTERSECTION = (1 << 4),
+};
+
+enum ObjectFeatureLine_Flags {
+ OBJECT_LRT_OWN_CREASE = (1 << 0),
+};
+
typedef struct Object {
ID id;
/** Animation data (must be immediately after id for utilities to use it). */
@@ -405,6 +431,8 @@ typedef struct Object {
struct PreviewImage *preview;
+ ObjectLineArt lineart;
+
/** Runtime evaluation data (keep last). */
Object_Runtime runtime;
} Object;
@@ -595,6 +623,9 @@ enum {
GP_EMPTY = 0,
GP_STROKE = 1,
GP_MONKEY = 2,
+ GP_LRT_SCENE = 3,
+ GP_LRT_OBJECT = 4,
+ GP_LRT_COLLECTION = 5,
};
/* boundtype */
diff --git a/source/blender/makesdna/DNA_space_types.h b/source/blender/makesdna/DNA_space_types.h
index de9b842312c..755010cd8a5 100644
--- a/source/blender/makesdna/DNA_space_types.h
+++ b/source/blender/makesdna/DNA_space_types.h
@@ -228,6 +228,7 @@ typedef enum eSpaceButtons_Context {
BCONTEXT_TOOL = 14,
BCONTEXT_SHADERFX = 15,
BCONTEXT_OUTPUT = 16,
+ BCONTEXT_COLLECTION = 17,
/* Keep last. */
BCONTEXT_TOT,
diff --git a/source/blender/makesdna/intern/dna_defaults.c b/source/blender/makesdna/intern/dna_defaults.c
index 7aca742a8e6..95272fb7804 100644
--- a/source/blender/makesdna/intern/dna_defaults.c
+++ b/source/blender/makesdna/intern/dna_defaults.c
@@ -315,6 +315,7 @@ SDNA_DEFAULT_DECL_STRUCT(TextureGpencilModifierData);
SDNA_DEFAULT_DECL_STRUCT(ThickGpencilModifierData);
SDNA_DEFAULT_DECL_STRUCT(TimeGpencilModifierData);
SDNA_DEFAULT_DECL_STRUCT(TintGpencilModifierData);
+SDNA_DEFAULT_DECL_STRUCT(LineartGpencilModifierData);
#undef SDNA_DEFAULT_DECL_STRUCT
@@ -539,6 +540,7 @@ const void *DNA_default_table[SDNA_TYPE_MAX] = {
SDNA_DEFAULT_DECL(ThickGpencilModifierData),
SDNA_DEFAULT_DECL(TimeGpencilModifierData),
SDNA_DEFAULT_DECL(TintGpencilModifierData),
+ SDNA_DEFAULT_DECL(LineartGpencilModifierData),
};
#undef SDNA_DEFAULT_DECL
#undef SDNA_DEFAULT_DECL_EX
diff --git a/source/blender/makesdna/intern/makesdna.c b/source/blender/makesdna/intern/makesdna.c
index 7624649bf78..26fc56cfa1d 100644
--- a/source/blender/makesdna/intern/makesdna.c
+++ b/source/blender/makesdna/intern/makesdna.c
@@ -84,6 +84,7 @@ static const char *includefiles[] = {
"DNA_mesh_types.h",
"DNA_meshdata_types.h",
"DNA_modifier_types.h",
+ "DNA_lineart_types.h",
"DNA_lattice_types.h",
"DNA_object_types.h",
"DNA_object_force_types.h",
@@ -1558,6 +1559,7 @@ int main(int argc, char **argv)
#include "DNA_layer_types.h"
#include "DNA_light_types.h"
#include "DNA_lightprobe_types.h"
+#include "DNA_lineart_types.h"
#include "DNA_linestyle_types.h"
#include "DNA_listBase.h"
#include "DNA_mask_types.h"
diff --git a/source/blender/makesrna/RNA_access.h b/source/blender/makesrna/RNA_access.h
index a9125c78229..ba67cedfdbe 100644
--- a/source/blender/makesrna/RNA_access.h
+++ b/source/blender/makesrna/RNA_access.h
@@ -456,6 +456,7 @@ extern StructRNA RNA_NormalEditModifier;
extern StructRNA RNA_Object;
extern StructRNA RNA_ObjectBase;
extern StructRNA RNA_ObjectDisplay;
+extern StructRNA RNA_ObjectLineArt;
extern StructRNA RNA_OceanModifier;
extern StructRNA RNA_OceanTexData;
extern StructRNA RNA_OffsetGpencilModifier;
diff --git a/source/blender/makesrna/intern/CMakeLists.txt b/source/blender/makesrna/intern/CMakeLists.txt
index 94cfd8464ae..c04ccf59327 100644
--- a/source/blender/makesrna/intern/CMakeLists.txt
+++ b/source/blender/makesrna/intern/CMakeLists.txt
@@ -443,6 +443,7 @@ set(LIB
bf_editor_undo
)
+add_definitions(${GL_DEFINITIONS})
blender_add_lib(bf_rna "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
diff --git a/source/blender/makesrna/intern/rna_collection.c b/source/blender/makesrna/intern/rna_collection.c
index 20a455f5312..90e969560b7 100644
--- a/source/blender/makesrna/intern/rna_collection.c
+++ b/source/blender/makesrna/intern/rna_collection.c
@@ -22,6 +22,8 @@
#include "DNA_collection_types.h"
+#include "DNA_lineart_types.h"
+
#include "BLI_utildefines.h"
#include "RNA_define.h"
@@ -517,6 +519,31 @@ void RNA_def_collections(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Disable in Renders", "Globally disable in renders");
RNA_def_property_update(prop, NC_SCENE | ND_LAYER_CONTENT, "rna_Collection_flag_update");
+ static const EnumPropertyItem rna_collection_lineart_usage[] = {
+ {COLLECTION_LRT_INCLUDE, "INCLUDE", 0, "Include", "Collection will produce feature lines"},
+ {COLLECTION_LRT_OCCLUSION_ONLY,
+ "OCCLUSION_ONLY",
+ 0,
+ "Occlusion Only",
+ "Only use the collection to produce occlusion"},
+ {COLLECTION_LRT_EXCLUDE, "EXCLUDE", 0, "Exclude", "Don't use this collection in LRT"},
+ {COLLECTION_LRT_INTERSECTION_ONLY,
+ "INTERSECTION_ONLY",
+ 0,
+ "Intersection Only",
+ "Only generate intersection lines with this collection"},
+ {COLLECTION_LRT_NO_INTERSECTION,
+ "NO_INTERSECTION",
+ 0,
+ "No Intersection",
+ "Do not generate intersection lines for this collection"},
+ {0, NULL, 0, NULL, NULL}};
+
+ prop = RNA_def_property(srna, "lineart_usage", PROP_ENUM, PROP_NONE);
+ RNA_def_property_enum_items(prop, rna_collection_lineart_usage);
+ RNA_def_property_ui_text(prop, "Usage", "How to use this collection in LRT");
+ RNA_def_property_update(prop, NC_SCENE, NULL);
+
prop = RNA_def_property(srna, "color_tag", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, NULL, "color_tag");
RNA_def_property_enum_funcs(
diff --git a/source/blender/makesrna/intern/rna_gpencil_modifier.c b/source/blender/makesrna/intern/rna_gpencil_modifier.c
index 71d5a53adb2..b84fcf4cf7c 100644
--- a/source/blender/makesrna/intern/rna_gpencil_modifier.c
+++ b/source/blender/makesrna/intern/rna_gpencil_modifier.c
@@ -88,6 +88,11 @@ const EnumPropertyItem rna_enum_object_greasepencil_modifier_type_items[] = {
ICON_MOD_SUBSURF,
"Subdivide",
"Subdivide stroke adding more control points"},
+ {eGpencilModifierType_Lineart,
+ "GP_LINEART",
+ ICON_MOD_EDGESPLIT,
+ "Line Art",
+ "Generate Line Art strokes from selected source"},
{0, "", 0, N_("Deform"), ""},
{eGpencilModifierType_Armature,
"GP_ARMATURE",
@@ -241,6 +246,8 @@ static StructRNA *rna_GpencilModifier_refine(struct PointerRNA *ptr)
return &RNA_MultiplyGpencilModifier;
case eGpencilModifierType_Texture:
return &RNA_TextureGpencilModifier;
+ case eGpencilModifierType_Lineart:
+ return &RNA_LineartGpencilModifier;
/* Default */
case eGpencilModifierType_None:
case NUM_GREASEPENCIL_MODIFIER_TYPES:
@@ -311,6 +318,7 @@ RNA_GP_MOD_VGROUP_NAME_SET(Offset, vgname);
RNA_GP_MOD_VGROUP_NAME_SET(Armature, vgname);
RNA_GP_MOD_VGROUP_NAME_SET(Texture, vgname);
RNA_GP_MOD_VGROUP_NAME_SET(Tint, vgname);
+RNA_GP_MOD_VGROUP_NAME_SET(Lineart, vgname);
# undef RNA_GP_MOD_VGROUP_NAME_SET
@@ -2305,6 +2313,281 @@ static void rna_def_modifier_gpenciltexture(BlenderRNA *brna)
RNA_def_property_update(prop, 0, "rna_GpencilModifier_dependency_update");
}
+static void rna_def_modifier_gpencillineart(BlenderRNA *brna)
+{
+ StructRNA *srna;
+ PropertyRNA *prop;
+
+ static const EnumPropertyItem modifier_lineart_source_type[] = {
+ {LRT_SOURCE_COLLECTION, "COLLECTION", 0, "Collection", ""},
+ {LRT_SOURCE_OBJECT, "OBJECT", 0, "Object", ""},
+ {LRT_SOURCE_SCENE, "SCENE", 0, "Scene", ""},
+ {0, NULL, 0, NULL, NULL},
+ };
+
+ srna = RNA_def_struct(brna, "LineartGpencilModifier", "GpencilModifier");
+ RNA_def_struct_ui_text(
+ srna, "Line Art Modifier", "Generate Line Art strokes from selected source");
+ RNA_def_struct_sdna(srna, "LineartGpencilModifierData");
+ RNA_def_struct_ui_icon(srna, ICON_MOD_EDGESPLIT);
+
+ prop = RNA_def_property(srna, "fuzzy_intersections", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "calculation_flags", LRT_INTERSECTION_AS_CONTOUR);
+ RNA_def_property_ui_text(prop,
+ "Intersection With Contour",
+ "Treat intersection and contour lines as if they were the same type so "
+ "they can be chained together");
+ RNA_def_property_update(prop, NC_SCENE, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "fuzzy_everything", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "calculation_flags", LRT_EVERYTHING_AS_CONTOUR);
+ RNA_def_property_ui_text(
+ prop, "All Lines", "Treat all lines as the same line type so they can be chained together");
+ RNA_def_property_update(prop, NC_SCENE, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "allow_duplication", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "calculation_flags", LRT_ALLOW_DUPLI_OBJECTS);
+ RNA_def_property_ui_text(
+ prop,
+ "Instanced Objects",
+ "Allow particle objects and face/vertiex duplication to show in line art");
+ RNA_def_property_update(prop, NC_SCENE, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "allow_overlapping_edges", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "calculation_flags", LRT_ALLOW_OVERLAPPING_EDGES);
+ RNA_def_property_ui_text(prop,
+ "Handle Overlapping Edges",
+ "Allow lines from edge split to show properly, may run slower");
+ RNA_def_property_update(prop, NC_SCENE, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "allow_clipping_boundaries", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "calculation_flags", LRT_ALLOW_CLIPPING_BOUNDARIES);
+ RNA_def_property_ui_text(
+ prop, "Clipping Boundaries", "Allow lines on near/far clipping plane to be shown");
+ RNA_def_property_update(prop, NC_SCENE, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "crease_threshold", PROP_FLOAT, PROP_ANGLE);
+ RNA_def_property_range(prop, 0, DEG2RAD(180.0f));
+ RNA_def_property_ui_range(prop, 0.0f, DEG2RAD(180.0f), 0.01f, 1);
+ RNA_def_property_ui_text(
+ prop, "Crease Threshold", "Angles smaller than this will be treated as creases");
+ RNA_def_property_update(prop, NC_SCENE, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "angle_splitting_threshold", PROP_FLOAT, PROP_ANGLE);
+ RNA_def_property_ui_text(
+ prop, "Angle Splitting", "Angle in screen space below which a stroke is split in two");
+ /* Don't allow value very close to PI, or we get a lot of small segments.*/
+ RNA_def_property_ui_range(prop, 0.0f, DEG2RAD(179.5f), 0.01f, 1);
+ RNA_def_property_range(prop, 0.0f, DEG2RAD(180.0f));
+ RNA_def_property_update(prop, NC_SCENE, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "remove_doubles", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "calculation_flags", LRT_REMOVE_DOUBLES);
+ RNA_def_property_ui_text(
+ prop, "Remove Doubles", "Remove doubles when line art is loading geometries");
+ RNA_def_property_update(prop, NC_SCENE, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "chaining_geometry_threshold", PROP_FLOAT, PROP_DISTANCE);
+ RNA_def_property_ui_text(prop,
+ "Geometry Threshold",
+ "Segments where their geometric distance between them lower than this "
+ "will be chained together");
+ RNA_def_property_ui_range(prop, 0.0f, 0.5f, 0.001f, 3);
+ RNA_def_property_range(prop, 0.0f, 0.5f);
+ RNA_def_property_update(prop, NC_SCENE, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "chaining_image_threshold", PROP_FLOAT, PROP_DISTANCE);
+ RNA_def_property_ui_text(
+ prop,
+ "Image Threshold",
+ "Segments with an image distance smaller than this will be chained together");
+ RNA_def_property_ui_range(prop, 0.0f, 0.3f, 0.001f, 4);
+ RNA_def_property_range(prop, 0.0f, 0.3f);
+ RNA_def_property_update(prop, NC_SCENE, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "source_type", PROP_ENUM, PROP_NONE);
+ RNA_def_property_enum_items(prop, modifier_lineart_source_type);
+ RNA_def_property_ui_text(prop, "Source Type", "Lineart stroke source type");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_dependency_update");
+
+ prop = RNA_def_property(srna, "source_object", PROP_POINTER, PROP_NONE);
+ RNA_def_property_flag(prop, PROP_EDITABLE | PROP_ID_SELF_CHECK);
+ RNA_def_property_struct_type(prop, "Object");
+ RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
+ RNA_def_property_ui_text(
+ prop, "Source Object", "Source object that this modifier grabs data from");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_dependency_update");
+
+ prop = RNA_def_property(srna, "source_collection", PROP_POINTER, PROP_NONE);
+ RNA_def_property_flag(prop, PROP_EDITABLE | PROP_ID_SELF_CHECK);
+ RNA_def_property_struct_type(prop, "Collection");
+ RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
+ RNA_def_property_ui_text(
+ prop, "Source Collection", "Source collection that this modifier uses data from");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_dependency_update");
+
+ /* types */
+ prop = RNA_def_property(srna, "use_contour", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "line_types", LRT_EDGE_FLAG_CONTOUR);
+ RNA_def_property_ui_text(prop, "Use Contour", "Include contour lines in the result");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "use_crease", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "line_types", LRT_EDGE_FLAG_CREASE);
+ RNA_def_property_ui_text(prop, "Use Crease", "Include sharp edges in the result");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "use_material", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "line_types", LRT_EDGE_FLAG_MATERIAL);
+ RNA_def_property_ui_text(
+ prop, "Use Material", "Include material separation lines in the result");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "use_edge_mark", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "line_types", LRT_EDGE_FLAG_EDGE_MARK);
+ RNA_def_property_ui_text(prop, "Use Edge Mark", "Include freestyle edge marks in the result");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "use_intersection", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "line_types", LRT_EDGE_FLAG_INTERSECTION);
+ RNA_def_property_ui_text(prop, "Use Intersection", "Include intersection lines in the result");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "use_multiple_levels", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "use_multiple_levels", 0);
+ RNA_def_property_ui_text(
+ prop, "Use Multiple Levels", "Select lines from multiple occlusion levels");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "level_start", PROP_INT, PROP_NONE);
+ RNA_def_property_ui_text(
+ prop, "Level Start", "Minimum level of occlusion level that gets selected");
+ RNA_def_property_range(prop, 0, 128);
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "level_end", PROP_INT, PROP_NONE);
+ RNA_def_property_ui_text(
+ prop, "Level End", "Maximum level of occlusion level that gets selected");
+ RNA_def_property_range(prop, 0, 128);
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "target_material", PROP_POINTER, PROP_NONE);
+ RNA_def_property_flag(prop, PROP_EDITABLE | PROP_ID_SELF_CHECK);
+ RNA_def_property_struct_type(prop, "Material");
+ RNA_def_property_override_flag(prop, PROPOVERRIDE_OVERRIDABLE_LIBRARY);
+ RNA_def_property_ui_text(
+ prop, "Target Material", "Grease Pencil material assigned to the generated strokes");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "target_layer", PROP_STRING, PROP_NONE);
+ RNA_def_property_ui_text(
+ prop, "Target Layer", "Grease Pencil layer assigned to the generated strokes");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "source_vertex_group", PROP_STRING, PROP_NONE);
+ RNA_def_property_ui_text(
+ prop,
+ "Source Vertex Group",
+ "Matches the beginning of vertex group names from mesh objects, match all when left empty");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "vertex_group", PROP_STRING, PROP_NONE);
+ RNA_def_property_string_sdna(prop, NULL, "vgname");
+ RNA_def_property_string_funcs(prop, NULL, NULL, "rna_LineartGpencilModifier_vgname_set");
+ RNA_def_property_ui_text(prop, "Vertex Group", "Vertex group name for selected strokes");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "invert_source_vertex_group", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "flags", LRT_GPENCIL_INVERT_SOURCE_VGROUP);
+ RNA_def_property_ui_text(prop, "Invert Source", "Invert source vertex group values");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "match_output_vertex_group", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "flags", LRT_GPENCIL_MATCH_OUTPUT_VGROUP);
+ RNA_def_property_ui_text(prop, "Match Output", "Match output vertex group");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "soft_selection", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "flags", LRT_GPENCIL_SOFT_SELECTION);
+ RNA_def_property_ui_text(
+ prop, "Soft selection", "Preserve original vertex weight instead of clipping to 0/1");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "is_baked", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "flags", LRT_GPENCIL_IS_BAKED);
+ RNA_def_property_ui_text(prop, "Is Baked", "This modifier has baked data");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "thickness", PROP_INT, PROP_NONE);
+ RNA_def_property_ui_text(prop, "Thickness", "The thickness that used to generate strokes");
+ RNA_def_property_ui_range(prop, 1, 100, 1, 1);
+ RNA_def_property_range(prop, 1, 200);
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "opacity", PROP_FLOAT, PROP_FACTOR);
+ RNA_def_property_ui_text(prop, "Opacity", "The strength value used to generate strokes");
+ RNA_def_property_ui_range(prop, 0.0f, 1.0f, 0.01f, 2);
+ RNA_def_property_range(prop, 0.0f, 1.0f);
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "pre_sample_length", PROP_FLOAT, PROP_NONE);
+ RNA_def_property_ui_text(prop, "Pre Sample Length", "Sample strokes before sending out");
+ RNA_def_property_ui_range(prop, 0.0f, 1.0f, 0.01f, 2);
+ RNA_def_property_range(prop, 0.0f, 1.0f);
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "use_transparency", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "transparency_flags", LRT_GPENCIL_TRANSPARENCY_ENABLE);
+ RNA_def_property_ui_text(
+ prop, "Use Transparency", "Use transparency mask from this material in Line Art");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "transparency_match", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "transparency_flags", LRT_GPENCIL_TRANSPARENCY_MATCH);
+ RNA_def_property_ui_text(prop, "Match Transparency", "Match all transparency bits");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "transparency_mask_0", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "transparency_mask", 1 << 0);
+ RNA_def_property_ui_text(prop, "Mask 0", "Mask bit 0");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "transparency_mask_1", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "transparency_mask", 1 << 1);
+ RNA_def_property_ui_text(prop, "Mask 1", "Mask bit 1");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "transparency_mask_2", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "transparency_mask", 1 << 2);
+ RNA_def_property_ui_text(prop, "Mask 2", "Mask bit 2");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "transparency_mask_3", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "transparency_mask", 1 << 3);
+ RNA_def_property_ui_text(prop, "Mask 3", "Mask bit 3");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "transparency_mask_4", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "transparency_mask", 1 << 4);
+ RNA_def_property_ui_text(prop, "Mask 4", "Mask bit 4");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "transparency_mask_5", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "transparency_mask", 1 << 5);
+ RNA_def_property_ui_text(prop, "Mask 5", "Mask bit 5");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "transparency_mask_6", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "transparency_mask", 1 << 6);
+ RNA_def_property_ui_text(prop, "mask 6", "Mask bit 6");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+
+ prop = RNA_def_property(srna, "transparency_mask_7", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "transparency_mask", 1 << 7);
+ RNA_def_property_ui_text(prop, "Mask 7", "Mask bit 7");
+ RNA_def_property_update(prop, 0, "rna_GpencilModifier_update");
+}
+
void RNA_def_greasepencil_modifier(BlenderRNA *brna)
{
StructRNA *srna;
@@ -2379,6 +2662,7 @@ void RNA_def_greasepencil_modifier(BlenderRNA *brna)
rna_def_modifier_gpencilarmature(brna);
rna_def_modifier_gpencilmultiply(brna);
rna_def_modifier_gpenciltexture(brna);
+ rna_def_modifier_gpencillineart(brna);
}
#endif
diff --git a/source/blender/makesrna/intern/rna_material.c b/source/blender/makesrna/intern/rna_material.c
index 84c831a178e..39dd3059f3d 100644
--- a/source/blender/makesrna/intern/rna_material.c
+++ b/source/blender/makesrna/intern/rna_material.c
@@ -127,6 +127,14 @@ static void rna_MaterialGpencil_update(Main *bmain, Scene *scene, PointerRNA *pt
WM_main_add_notifier(NC_GPENCIL | ND_DATA, ma);
}
+static void rna_MaterialLineArt_update(Main *UNUSED(bmain), Scene *UNUSED(scene), PointerRNA *ptr)
+{
+ Material *ma = (Material *)ptr->owner_id;
+ /* Need to tag geometry for line art modifier updates. */
+ DEG_id_tag_update(&ma->id, ID_RECALC_GEOMETRY);
+ WM_main_add_notifier(NC_MATERIAL | ND_SHADING_DRAW, ma);
+}
+
static void rna_Material_draw_update(Main *UNUSED(bmain), Scene *UNUSED(scene), PointerRNA *ptr)
{
Material *ma = (Material *)ptr->owner_id;
@@ -671,6 +679,70 @@ static void rna_def_material_greasepencil(BlenderRNA *brna)
RNA_def_property_ui_text(
prop, "Is Fill Visible", "True when opacity of fill is set high enough to be visible");
}
+static void rna_def_material_lineart(BlenderRNA *brna)
+{
+ StructRNA *srna;
+ PropertyRNA *prop;
+
+ srna = RNA_def_struct(brna, "MaterialLineArt", NULL);
+ RNA_def_struct_sdna(srna, "MaterialLineArt");
+ RNA_def_struct_ui_text(srna, "Material Line Art", "");
+
+ prop = RNA_def_property(srna, "use_transparency", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_default(prop, 0);
+ RNA_def_property_boolean_sdna(prop, NULL, "flags", LRT_MATERIAL_TRANSPARENCY_ENABLED);
+ RNA_def_property_ui_text(
+ prop, "Use Transparency", "Use transparency mask from this material in Line Art");
+ RNA_def_property_update(prop, NC_GPENCIL | ND_SHADING, "rna_MaterialLineArt_update");
+
+ prop = RNA_def_property(srna, "transparency_mask_0", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_default(prop, 0);
+ RNA_def_property_boolean_sdna(prop, NULL, "transparency_mask", 1 << 0);
+ RNA_def_property_ui_text(prop, "Mask 0", "Mask bit 0");
+ RNA_def_property_update(prop, NC_GPENCIL | ND_SHADING, "rna_MaterialLineArt_update");
+
+ prop = RNA_def_property(srna, "transparency_mask_1", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_default(prop, 0);
+ RNA_def_property_boolean_sdna(prop, NULL, "transparency_mask", 1 << 1);
+ RNA_def_property_ui_text(prop, "Mask 1", "Mask bit 1");
+ RNA_def_property_update(prop, NC_GPENCIL | ND_SHADING, "rna_MaterialLineArt_update");
+
+ prop = RNA_def_property(srna, "transparency_mask_2", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_default(prop, 0);
+ RNA_def_property_boolean_sdna(prop, NULL, "transparency_mask", 1 << 2);
+ RNA_def_property_ui_text(prop, "Mask 2", "Mask bit 2");
+ RNA_def_property_update(prop, NC_GPENCIL | ND_SHADING, "rna_MaterialLineArt_update");
+
+ prop = RNA_def_property(srna, "transparency_mask_3", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_default(prop, 0);
+ RNA_def_property_boolean_sdna(prop, NULL, "transparency_mask", 1 << 3);
+ RNA_def_property_ui_text(prop, "Mask 3", "Mask bit 3");
+ RNA_def_property_update(prop, NC_GPENCIL | ND_SHADING, "rna_MaterialLineArt_update");
+
+ prop = RNA_def_property(srna, "transparency_mask_4", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_default(prop, 0);
+ RNA_def_property_boolean_sdna(prop, NULL, "transparency_mask", 1 << 4);
+ RNA_def_property_ui_text(prop, "Mask 4", "Mask bit 4");
+ RNA_def_property_update(prop, NC_GPENCIL | ND_SHADING, "rna_MaterialLineArt_update");
+
+ prop = RNA_def_property(srna, "transparency_mask_5", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_default(prop, 0);
+ RNA_def_property_boolean_sdna(prop, NULL, "transparency_mask", 1 << 5);
+ RNA_def_property_ui_text(prop, "Mask 5", "Mask bit 5");
+ RNA_def_property_update(prop, NC_GPENCIL | ND_SHADING, "rna_MaterialLineArt_update");
+
+ prop = RNA_def_property(srna, "transparency_mask_6", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_default(prop, 0);
+ RNA_def_property_boolean_sdna(prop, NULL, "transparency_mask", 1 << 6);
+ RNA_def_property_ui_text(prop, "mask 6", "Mask bit 6");
+ RNA_def_property_update(prop, NC_GPENCIL | ND_SHADING, "rna_MaterialLineArt_update");
+
+ prop = RNA_def_property(srna, "transparency_mask_7", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_default(prop, 0);
+ RNA_def_property_boolean_sdna(prop, NULL, "transparency_mask", 1 << 7);
+ RNA_def_property_ui_text(prop, "Mask 7", "Mask bit 7");
+ RNA_def_property_update(prop, NC_GPENCIL | ND_SHADING, "rna_MaterialLineArt_update");
+}
void RNA_def_material(BlenderRNA *brna)
{
@@ -836,7 +908,13 @@ void RNA_def_material(BlenderRNA *brna)
RNA_def_property_ui_text(
prop, "Is Grease Pencil", "True if this material has grease pencil data");
+ /* line art */
+ prop = RNA_def_property(srna, "lineart", PROP_POINTER, PROP_NONE);
+ RNA_def_property_pointer_sdna(prop, NULL, "lineart");
+ RNA_def_property_ui_text(prop, "Line Art Settings", "Line Art settings for material");
+
rna_def_material_greasepencil(brna);
+ rna_def_material_lineart(brna);
RNA_api_material(srna);
}
diff --git a/source/blender/makesrna/intern/rna_object.c b/source/blender/makesrna/intern/rna_object.c
index a70b776b07a..8c12ccceb5d 100644
--- a/source/blender/makesrna/intern/rna_object.c
+++ b/source/blender/makesrna/intern/rna_object.c
@@ -36,6 +36,7 @@
#include "DNA_shader_fx_types.h"
#include "DNA_workspace_types.h"
+#include "BLI_math.h"
#include "BLI_utildefines.h"
#include "BLT_translation.h"
@@ -159,6 +160,21 @@ const EnumPropertyItem rna_enum_object_gpencil_type_items[] = {
{GP_EMPTY, "EMPTY", ICON_EMPTY_AXIS, "Blank", "Create an empty grease pencil object"},
{GP_STROKE, "STROKE", ICON_STROKE, "Stroke", "Create a simple stroke with basic colors"},
{GP_MONKEY, "MONKEY", ICON_MONKEY, "Monkey", "Construct a Suzanne grease pencil object"},
+ {GP_LRT_SCENE,
+ "LRT_SCENE",
+ ICON_SCENE_DATA,
+ "Scene Line Art",
+ "Quickly set up Line Art for the whole scene"},
+ {GP_LRT_COLLECTION,
+ "LRT_COLLECTION",
+ ICON_OUTLINER_COLLECTION,
+ "Collection Line Art",
+ "Quickly set up Line Art for active collection"},
+ {GP_LRT_OBJECT,
+ "LRT_OBJECT",
+ ICON_CUBE,
+ "Object Line Art",
+ "Quickly set up Line Art for active collection"},
{0, NULL, 0, NULL, NULL}};
static const EnumPropertyItem parent_type_items[] = {
@@ -2083,6 +2099,12 @@ int rna_Object_use_dynamic_topology_sculpting_get(PointerRNA *ptr)
return (ss && ss->bm);
}
+static void rna_object_lineart_update(Main *UNUSED(bmain), Scene *UNUSED(scene), PointerRNA *ptr)
+{
+ DEG_id_tag_update(ptr->owner_id, ID_RECALC_GEOMETRY);
+ WM_main_add_notifier(NC_OBJECT | ND_MODIFIER, ptr->owner_id);
+}
+
#else
static void rna_def_vertex_group(BlenderRNA *brna)
@@ -2645,6 +2667,59 @@ static void rna_def_object_display(BlenderRNA *brna)
RNA_define_lib_overridable(false);
}
+static void rna_def_object_lineart(BlenderRNA *brna)
+{
+ StructRNA *srna;
+ PropertyRNA *prop;
+
+ static EnumPropertyItem prop_feature_line_usage_items[] = {
+ {OBJECT_LRT_INHERENT,
+ "INHEREIT",
+ 0,
+ "Inhereit",
+ "Follow settings from the parent collection"},
+ {OBJECT_LRT_INCLUDE, "INCLUDE", 0, "Include", "Include this object into LRT calculation"},
+ {OBJECT_LRT_OCCLUSION_ONLY,
+ "OCCLUSION_ONLY",
+ 0,
+ "Occlusion Only",
+ "Don't produce lines, only used as occlusion object"},
+ {OBJECT_LRT_EXCLUDE, "EXCLUDE", 0, "Exclude", "Don't use this object for LRT rendering"},
+ {OBJECT_LRT_INTERSECTION_ONLY,
+ "INTERSECTION_ONLY",
+ 0,
+ "Intersection Only",
+ "Only to generate intersection lines with this object"},
+ {OBJECT_LRT_NO_INTERSECTION,
+ "NO_INTERSECTION",
+ 0,
+ "No Intersection",
+ "Include this object but do not generate intersection lines"},
+ {0, NULL, 0, NULL, NULL},
+ };
+
+ srna = RNA_def_struct(brna, "ObjectLineArt", NULL);
+ RNA_def_struct_ui_text(srna, "Object Line Art", "Object lineart settings");
+ RNA_def_struct_sdna(srna, "ObjectLineArt");
+
+ prop = RNA_def_property(srna, "usage", PROP_ENUM, PROP_NONE);
+ RNA_def_property_enum_items(prop, prop_feature_line_usage_items);
+ RNA_def_property_ui_text(prop, "Usage", "How to use this object in line art calculation");
+ RNA_def_property_update(prop, 0, "rna_object_lineart_update");
+
+ prop = RNA_def_property(srna, "use_crease_override", PROP_BOOLEAN, PROP_NONE);
+ RNA_def_property_boolean_sdna(prop, NULL, "flags", OBJECT_LRT_OWN_CREASE);
+ RNA_def_property_ui_text(prop, "Own Crease", "Use own crease setting to overwrite scene global");
+ RNA_def_property_update(prop, 0, "rna_object_lineart_update");
+
+ prop = RNA_def_property(srna, "crease_threshold", PROP_FLOAT, PROP_ANGLE);
+ RNA_def_property_range(prop, 0, DEG2RAD(180.0f));
+ RNA_def_property_ui_range(prop, 0.0f, DEG2RAD(180.0f), 0.01f, 1);
+ RNA_def_property_ui_text(
+ prop, "Own Crease", "Angles smaller than this will be treated as creases");
+ RNA_def_property_update(prop, 0, "rna_object_lineart_update");
+}
+
static void rna_def_object(BlenderRNA *brna)
{
StructRNA *srna;
@@ -3414,6 +3489,11 @@ static void rna_def_object(BlenderRNA *brna)
RNA_def_property_pointer_funcs(prop, "rna_Object_display_get", NULL, NULL, NULL);
RNA_def_property_ui_text(prop, "Object Display", "Object display settings for 3D viewport");
+ /* Line Art */
+ prop = RNA_def_property(srna, "lineart", PROP_POINTER, PROP_NONE);
+ RNA_def_property_struct_type(prop, "ObjectLineArt");
+ RNA_def_property_ui_text(prop, "Line Art", "Line Art settings for the object");
+
RNA_define_lib_overridable(false);
/* anim */
@@ -3434,6 +3514,7 @@ void RNA_def_object(BlenderRNA *brna)
rna_def_face_map(brna);
rna_def_material_slot(brna);
rna_def_object_display(brna);
+ rna_def_object_lineart(brna);
RNA_define_animate_sdna(true);
}
diff --git a/source/blender/makesrna/intern/rna_space.c b/source/blender/makesrna/intern/rna_space.c
index 0ddc9a61cad..8377a8fbf3f 100644
--- a/source/blender/makesrna/intern/rna_space.c
+++ b/source/blender/makesrna/intern/rna_space.c
@@ -458,6 +458,7 @@ static const EnumPropertyItem buttons_context_items[] = {
{BCONTEXT_OUTPUT, "OUTPUT", ICON_OUTPUT, "Output", "Output Properties"},
{BCONTEXT_VIEW_LAYER, "VIEW_LAYER", ICON_RENDER_RESULT, "View Layer", "View Layer Properties"},
{BCONTEXT_WORLD, "WORLD", ICON_WORLD, "World", "World Properties"},
+ {BCONTEXT_COLLECTION, "COLLECTION", ICON_GROUP, "Collection", "Collection Properties"},
{BCONTEXT_OBJECT, "OBJECT", ICON_OBJECT_DATA, "Object", "Object Properties"},
{BCONTEXT_CONSTRAINT,
"CONSTRAINT",
diff --git a/source/blender/windowmanager/WM_api.h b/source/blender/windowmanager/WM_api.h
index 545371f4540..9f70d05cc8a 100644
--- a/source/blender/windowmanager/WM_api.h
+++ b/source/blender/windowmanager/WM_api.h
@@ -755,6 +755,7 @@ enum {
WM_JOB_TYPE_FSMENU_BOOKMARK_VALIDATE,
WM_JOB_TYPE_QUADRIFLOW_REMESH,
WM_JOB_TYPE_TRACE_IMAGE,
+ WM_JOB_TYPE_LINEART,
/* add as needed, bake, seq proxy build
* if having hard coded values is a problem */
};