forked from blender/blender
GPv3: Add Compound Shapes Rendering (i.e. Hole rendering) #12
@ -54,7 +54,7 @@ class DrawingRuntime {
|
|||||||
/**
|
/**
|
||||||
* Triangle cache for all the strokes in the drawing.
|
* Triangle cache for all the strokes in the drawing.
|
||||||
*/
|
*/
|
||||||
mutable SharedCache<Vector<uint3>> triangles_cache;
|
mutable SharedCache<Vector<Vector<uint3>>> triangles_cache;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normal vector cache for every stroke. Computed using Newell's method.
|
* Normal vector cache for every stroke. Computed using Newell's method.
|
||||||
@ -86,10 +86,14 @@ class Drawing : public ::GreasePencilDrawing {
|
|||||||
|
|
||||||
const bke::CurvesGeometry &strokes() const;
|
const bke::CurvesGeometry &strokes() const;
|
||||||
bke::CurvesGeometry &strokes_for_write();
|
bke::CurvesGeometry &strokes_for_write();
|
||||||
|
|
||||||
|
Vector<IndexMask> shapes(IndexMaskMemory &memory) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The triangles for all the fills in the geometry.
|
* The triangles for all the fills in the geometry.
|
||||||
|
* Stored per shape and indexing into points.
|
||||||
*/
|
*/
|
||||||
Span<uint3> triangles() const;
|
Span<Vector<uint3>> triangles() const;
|
||||||
/**
|
/**
|
||||||
* Normal vectors for a plane that fits the stroke.
|
* Normal vectors for a plane that fits the stroke.
|
||||||
*/
|
*/
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
#include "BKE_object_types.hh"
|
#include "BKE_object_types.hh"
|
||||||
|
|
||||||
#include "BLI_bounds.hh"
|
#include "BLI_bounds.hh"
|
||||||
|
#include "BLI_delaunay_2d.hh"
|
||||||
#include "BLI_enumerable_thread_specific.hh"
|
#include "BLI_enumerable_thread_specific.hh"
|
||||||
#include "BLI_map.hh"
|
#include "BLI_map.hh"
|
||||||
#include "BLI_math_euler_types.hh"
|
#include "BLI_math_euler_types.hh"
|
||||||
@ -366,7 +367,143 @@ Drawing::~Drawing()
|
|||||||
this->runtime = nullptr;
|
this->runtime = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
Span<uint3> Drawing::triangles() const
|
Vector<IndexMask> Drawing::shapes(IndexMaskMemory &memory) const
|
||||||
|
{
|
||||||
|
const CurvesGeometry &curves = this->strokes();
|
||||||
|
const bke::AttributeAccessor attributes = curves.attributes();
|
||||||
|
|
||||||
|
const VArray<int> shape_ids = *attributes.lookup<int>("shape_id", bke::AttrDomain::Curve);
|
||||||
|
|
||||||
|
if (!shape_ids) {
|
||||||
|
/* If the attribute does not exist then the default is each shape containing one curve. */
|
||||||
|
Vector<IndexMask> shapes;
|
||||||
|
for (const int i : curves.curves_range()) {
|
||||||
|
shapes.append(IndexRange::from_single(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
return shapes;
|
||||||
|
}
|
||||||
|
|
||||||
|
VectorSet<int> shape_indexing;
|
||||||
|
const Vector<IndexMask> shapes = IndexMask::from_group_ids(shape_ids, memory, shape_indexing);
|
||||||
|
|
||||||
|
return shapes;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool check_self_intersections(Span<float2> projverts)
|
||||||
|
{
|
||||||
|
std::atomic<bool> intersect = false;
|
||||||
|
threading::parallel_for(projverts.index_range(), 512, [&](const IndexRange range) {
|
||||||
|
for (const int e2_id : range) {
|
||||||
|
if (intersect) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const int e1_id : projverts.index_range().drop_front(e2_id)) {
|
||||||
|
const int p1 = e1_id;
|
||||||
|
const int p2 = (e1_id + 1) % projverts.size();
|
||||||
|
const int p3 = e2_id;
|
||||||
|
const int p4 = (e2_id + 1) % projverts.size();
|
||||||
|
if (p1 == p4) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (p2 == p3) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isect_seg_seg_v2_simple(projverts[p1], projverts[p2], projverts[p3], projverts[p4])) {
|
||||||
|
intersect.store(true, std::memory_order_relaxed);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return intersect;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool check_other_intersections(Span<float2> projverts1, Span<float2> projverts2)
|
||||||
|
{
|
||||||
|
std::atomic<bool> intersect = false;
|
||||||
|
|
||||||
|
threading::parallel_for(projverts1.index_range(), 512, [&](const IndexRange range) {
|
||||||
|
for (const int e1_id : range) {
|
||||||
|
for (const int e2_id : projverts2.index_range()) {
|
||||||
|
const int p11 = e1_id;
|
||||||
|
const int p12 = (e1_id + 1) % projverts1.size();
|
||||||
|
const int p21 = e2_id;
|
||||||
|
const int p22 = (e2_id + 1) % projverts2.size();
|
||||||
|
|
||||||
|
if (isect_seg_seg_v2_simple(
|
||||||
|
projverts1[p11], projverts1[p12], projverts2[p21], projverts2[p22]))
|
||||||
|
{
|
||||||
|
intersect.store(true, std::memory_order_relaxed);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return intersect;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool check_valid_curves(Span<float2> projverts, const OffsetIndices<int> points_by_group)
|
||||||
|
{
|
||||||
|
std::atomic<bool> intersect = false;
|
||||||
|
/* Check for self intersections. */
|
||||||
|
threading::parallel_for(points_by_group.index_range(), 512, [&](const IndexRange range) {
|
||||||
|
for (const int pos : range) {
|
||||||
|
if (intersect) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const IndexRange point_group = points_by_group[pos];
|
||||||
|
if (check_self_intersections(projverts.slice(point_group))) {
|
||||||
|
intersect.store(true, std::memory_order_relaxed);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (intersect) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check if other intersect. */
|
||||||
|
threading::parallel_for(points_by_group.index_range(), 512, [&](const IndexRange range1) {
|
||||||
|
for (const int pos1 : range1) {
|
||||||
|
if (intersect) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const IndexRange point_group1 = points_by_group[pos1];
|
||||||
|
|
||||||
|
threading::parallel_for(points_by_group.index_range(), 512, [&](const IndexRange range2) {
|
||||||
|
for (const int pos2 : range2) {
|
||||||
|
if (intersect) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const IndexRange point_group2 = points_by_group[pos2];
|
||||||
|
|
||||||
|
if (pos2 >= pos1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (check_other_intersections(projverts.slice(point_group1),
|
||||||
|
projverts.slice(point_group2)))
|
||||||
|
{
|
||||||
|
intersect.store(true, std::memory_order_relaxed);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (intersect) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Span<Vector<uint3>> Drawing::triangles() const
|
||||||
{
|
{
|
||||||
struct LocalMemArena {
|
struct LocalMemArena {
|
||||||
MemArena *pf_arena = nullptr;
|
MemArena *pf_arena = nullptr;
|
||||||
@ -379,52 +516,112 @@ Span<uint3> Drawing::triangles() const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
this->runtime->triangles_cache.ensure([&](Vector<uint3> &r_data) {
|
|
||||||
|
this->runtime->triangles_cache.ensure([&](Vector<Vector<uint3>> &r_data) {
|
||||||
const CurvesGeometry &curves = this->strokes();
|
const CurvesGeometry &curves = this->strokes();
|
||||||
const Span<float3> positions = curves.evaluated_positions();
|
const Span<float3> positions = curves.evaluated_positions();
|
||||||
const Span<float3> normals = this->curve_plane_normals();
|
const Span<float3> normals = this->curve_plane_normals();
|
||||||
const OffsetIndices<int> points_by_curve = curves.evaluated_points_by_curve();
|
const OffsetIndices<int> points_by_curve = curves.evaluated_points_by_curve();
|
||||||
|
const Array<int> point_to_curve_map = curves.point_to_curve_map();
|
||||||
|
|
||||||
int total_triangles = 0;
|
IndexMaskMemory memory;
|
||||||
Array<int> tris_offests(curves.curves_num());
|
const Vector<IndexMask> shapes = this->shapes(memory);
|
||||||
for (int curve_i : curves.curves_range()) {
|
|
||||||
IndexRange points = points_by_curve[curve_i];
|
r_data.resize(shapes.size() + 1);
|
||||||
if (points.size() > 2) {
|
MutableSpan<Vector<uint3>> strokes_triangles = r_data.as_mutable_span();
|
||||||
tris_offests[curve_i] = total_triangles;
|
|
||||||
total_triangles += points.size() - 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
r_data.resize(total_triangles);
|
|
||||||
MutableSpan<uint3> triangles = r_data.as_mutable_span();
|
|
||||||
threading::EnumerableThreadSpecific<LocalMemArena> all_local_mem_arenas;
|
threading::EnumerableThreadSpecific<LocalMemArena> all_local_mem_arenas;
|
||||||
threading::parallel_for(curves.curves_range(), 32, [&](const IndexRange range) {
|
threading::parallel_for(shapes.index_range(), 32, [&](const IndexRange shape_range) {
|
||||||
MemArena *pf_arena = all_local_mem_arenas.local().pf_arena;
|
MemArena *pf_arena = all_local_mem_arenas.local().pf_arena;
|
||||||
for (const int curve_i : range) {
|
for (const int shape_index : shape_range) {
|
||||||
const IndexRange points = points_by_curve[curve_i];
|
const IndexMask &shape = shapes[shape_index];
|
||||||
if (points.size() < 3) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int num_triangles = points.size() - 2;
|
|
||||||
MutableSpan<uint3> r_tris = triangles.slice(tris_offests[curve_i], num_triangles);
|
|
||||||
|
|
||||||
float(*projverts)[2] = static_cast<float(*)[2]>(
|
|
||||||
BLI_memarena_alloc(pf_arena, sizeof(*projverts) * size_t(points.size())));
|
|
||||||
|
|
||||||
float3x3 axis_mat;
|
float3x3 axis_mat;
|
||||||
axis_dominant_v3_to_m3(axis_mat.ptr(), normals[curve_i]);
|
axis_dominant_v3_to_m3(axis_mat.ptr(), normals[shape.first()]);
|
||||||
|
|
||||||
for (const int i : IndexRange(points.size())) {
|
Array<int> offsets_data(shape.size() + 1);
|
||||||
mul_v2_m3v3(projverts[i], axis_mat.ptr(), positions[points[i]]);
|
offset_indices::gather_group_sizes(
|
||||||
|
points_by_curve, shape, offsets_data.as_mutable_span().drop_back(1));
|
||||||
|
offset_indices::accumulate_counts_to_offsets(offsets_data);
|
||||||
|
const OffsetIndices<int> points_by_group = OffsetIndices<int>(offsets_data);
|
||||||
|
|
||||||
|
const int num_points = points_by_group.total_size();
|
||||||
|
|
||||||
|
float(*projverts)[2] = static_cast<float(*)[2]>(
|
||||||
|
BLI_memarena_alloc(pf_arena, sizeof(*projverts) * size_t(num_points)));
|
||||||
|
|
||||||
|
shape.foreach_index(GrainSize(256), [&](const int64_t curve_i, const int64_t pos) {
|
||||||
|
const IndexRange point_group = points_by_group[pos];
|
||||||
|
const IndexRange points = points_by_curve[curve_i];
|
||||||
|
threading::parallel_for(points.index_range(), 512, [&](const IndexRange range) {
|
||||||
|
for (const int p_id : range) {
|
||||||
|
mul_v2_m3v3(projverts[point_group[p_id]], axis_mat.ptr(), positions[points[p_id]]);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/* If there is only on stroke or the geometry can not meshed then use simple poly fill
|
||||||
|
* using the first curve in the group. */
|
||||||
|
if (shape.size() == 1 ||
|
||||||
|
!check_valid_curves({reinterpret_cast<float2 *>(projverts), num_points},
|
||||||
|
points_by_group))
|
||||||
|
{
|
||||||
|
const IndexRange points = points_by_curve[shape.first()];
|
||||||
|
|
||||||
|
strokes_triangles[shape_index].resize(points.size() - 2);
|
||||||
|
MutableSpan<uint3> r_tris = strokes_triangles[shape_index];
|
||||||
BLI_polyfill_calc_arena(projverts,
|
BLI_polyfill_calc_arena(projverts,
|
||||||
points.size(),
|
points.size(),
|
||||||
0,
|
0,
|
||||||
reinterpret_cast<uint32_t(*)[3]>(r_tris.data()),
|
reinterpret_cast<uint32_t(*)[3]>(r_tris.data()),
|
||||||
pf_arena);
|
pf_arena);
|
||||||
|
for (const int i : r_tris.index_range()) {
|
||||||
|
r_tris[i] += points.first();
|
||||||
|
}
|
||||||
BLI_memarena_clear(pf_arena);
|
BLI_memarena_clear(pf_arena);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Array<double2> verts(num_points);
|
||||||
|
Array<std::pair<int, int>> edges(num_points);
|
||||||
|
Array<Vector<int>> faces(shape.size());
|
||||||
|
|
||||||
|
Array<int> vert_to_point_map(num_points);
|
||||||
|
|
||||||
|
shape.foreach_index(GrainSize(256), [&](const int64_t curve_i, const int64_t pos) {
|
||||||
|
const IndexRange point_group = points_by_group[pos];
|
||||||
|
const IndexRange points = points_by_curve[curve_i];
|
||||||
|
faces[pos].resize(points.size());
|
||||||
|
threading::parallel_for(points.index_range(), 512, [&](const IndexRange range) {
|
||||||
|
for (const int p_id : range) {
|
||||||
|
vert_to_point_map[point_group[p_id]] = points[p_id];
|
||||||
|
verts[point_group[p_id]] = double2(projverts[point_group[p_id]]);
|
||||||
|
edges[point_group[p_id]] = std::pair<int, int>(
|
||||||
|
point_group[p_id], point_group[(p_id + 1) % points.size()]);
|
||||||
|
faces[pos][p_id] = point_group[p_id];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
meshintersect::CDT_input<double> input;
|
||||||
|
input.vert = verts;
|
||||||
|
input.edge = edges;
|
||||||
|
input.face = faces;
|
||||||
|
input.need_ids = false;
|
||||||
|
|
||||||
|
meshintersect::CDT_result<double> result = delaunay_2d_calc(input, CDT_INSIDE_WITH_HOLES);
|
||||||
|
|
||||||
|
strokes_triangles[shape_index].resize(result.face.size());
|
||||||
|
MutableSpan<uint3> r_tris = strokes_triangles[shape_index];
|
||||||
|
|
||||||
|
threading::parallel_for(result.face.index_range(), 512, [&](const IndexRange range) {
|
||||||
|
for (const int i : range) {
|
||||||
|
BLI_assert(result.face[i].size() == 3);
|
||||||
|
r_tris[i] = uint3(vert_to_point_map[result.face[i][0]],
|
||||||
|
vert_to_point_map[result.face[i][1]],
|
||||||
|
vert_to_point_map[result.face[i][2]]);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -714,6 +714,8 @@ static GPENCIL_tObject *grease_pencil_object_cache_populate(GPENCIL_PrivateData
|
|||||||
for (const DrawingInfo info : drawings) {
|
for (const DrawingInfo info : drawings) {
|
||||||
const Layer &layer = *layers[info.layer_index];
|
const Layer &layer = *layers[info.layer_index];
|
||||||
|
|
||||||
|
const Span<Vector<uint3>> triangles = info.drawing.triangles();
|
||||||
|
|
||||||
const bke::CurvesGeometry &curves = info.drawing.strokes();
|
const bke::CurvesGeometry &curves = info.drawing.strokes();
|
||||||
const OffsetIndices<int> points_by_curve = curves.evaluated_points_by_curve();
|
const OffsetIndices<int> points_by_curve = curves.evaluated_points_by_curve();
|
||||||
const bke::AttributeAccessor attributes = curves.attributes();
|
const bke::AttributeAccessor attributes = curves.attributes();
|
||||||
@ -723,24 +725,32 @@ static GPENCIL_tObject *grease_pencil_object_cache_populate(GPENCIL_PrivateData
|
|||||||
IndexMaskMemory memory;
|
IndexMaskMemory memory;
|
||||||
const IndexMask visible_strokes = ed::greasepencil::retrieve_visible_strokes(
|
const IndexMask visible_strokes = ed::greasepencil::retrieve_visible_strokes(
|
||||||
*ob, info.drawing, memory);
|
*ob, info.drawing, memory);
|
||||||
|
const Vector<IndexMask> shapes = info.drawing.shapes(memory);
|
||||||
|
|
||||||
/* Precompute all the triangle and vertex counts.
|
/* Precompute all the triangle and vertex counts.
|
||||||
* In case the drawing should not be rendered, we need to compute the offset where the next
|
* In case the drawing should not be rendered, we need to compute the offset where the next
|
||||||
* drawing begins. */
|
* drawing begins. */
|
||||||
Array<int> num_triangles_per_stroke(visible_strokes.size());
|
Array<int> num_triangles_per_stroke(shapes.size());
|
||||||
Array<int> num_vertices_per_stroke(visible_strokes.size());
|
Array<int> num_vertices_per_stroke(visible_strokes.size());
|
||||||
int total_num_triangles = 0;
|
int total_num_triangles = 0;
|
||||||
int total_num_vertices = 0;
|
int total_num_vertices = 0;
|
||||||
visible_strokes.foreach_index([&](const int stroke_i, const int pos) {
|
int current_curve = 0;
|
||||||
const IndexRange points = points_by_curve[stroke_i];
|
for (const int shape_index : shapes.index_range()) {
|
||||||
const int num_stroke_triangles = (points.size() >= 3) ? (points.size() - 2) : 0;
|
const IndexMask &shape = shapes[shape_index];
|
||||||
const int num_stroke_vertices = (points.size() +
|
|
||||||
int(cyclic[stroke_i] && (points.size() >= 3)));
|
const int num_stroke_triangles = triangles[shape_index].size();
|
||||||
num_triangles_per_stroke[pos] = num_stroke_triangles;
|
num_triangles_per_stroke[shape_index] = num_stroke_triangles;
|
||||||
num_vertices_per_stroke[pos] = num_stroke_vertices;
|
|
||||||
total_num_triangles += num_stroke_triangles;
|
total_num_triangles += num_stroke_triangles;
|
||||||
|
|
||||||
|
shape.foreach_index([&](const int curve_i) {
|
||||||
|
const IndexRange points = points_by_curve[curve_i];
|
||||||
|
const int num_stroke_vertices = (points.size() +
|
||||||
|
int(cyclic[curve_i] && (points.size() >= 3)));
|
||||||
|
num_vertices_per_stroke[current_curve] = num_stroke_vertices;
|
||||||
total_num_vertices += num_stroke_vertices;
|
total_num_vertices += num_stroke_vertices;
|
||||||
|
current_curve++;
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
bool is_layer_used_as_mask = false;
|
bool is_layer_used_as_mask = false;
|
||||||
const bool show_drawing_in_render = use_layer_in_render(
|
const bool show_drawing_in_render = use_layer_in_render(
|
||||||
@ -787,17 +797,21 @@ static GPENCIL_tObject *grease_pencil_object_cache_populate(GPENCIL_PrivateData
|
|||||||
info.frame_number != pd->cfra && pd->use_multiedit_lines_only;
|
info.frame_number != pd->cfra && pd->use_multiedit_lines_only;
|
||||||
const bool is_onion = info.onion_id != 0;
|
const bool is_onion = info.onion_id != 0;
|
||||||
|
|
||||||
visible_strokes.foreach_index([&](const int stroke_i, const int pos) {
|
current_curve = 0;
|
||||||
const IndexRange points = points_by_curve[stroke_i];
|
for (const int shape_index : shapes.index_range()) {
|
||||||
|
const IndexMask &shape = shapes[shape_index];
|
||||||
|
|
||||||
|
const int curve_i = shape.first();
|
||||||
|
|
||||||
/* The material index is allowed to be negative as it's stored as a generic attribute. We
|
/* The material index is allowed to be negative as it's stored as a generic attribute. We
|
||||||
* clamp it here to avoid crashing in the rendering code. Any stroke with a material < 0 will
|
* clamp it here to avoid crashing in the rendering code. Any stroke with a material < 0
|
||||||
* use the first material in the first material slot.*/
|
* will use the first material in the first material slot.*/
|
||||||
const int material_index = std::max(stroke_materials[stroke_i], 0);
|
const int material_index = std::max(stroke_materials[curve_i], 0);
|
||||||
const MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob, material_index + 1);
|
const MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob, material_index + 1);
|
||||||
|
|
||||||
const bool hide_material = (gp_style->flag & GP_MATERIAL_HIDE) != 0;
|
const bool hide_material = (gp_style->flag & GP_MATERIAL_HIDE) != 0;
|
||||||
const bool show_stroke = ((gp_style->flag & GP_MATERIAL_STROKE_SHOW) != 0);
|
const bool show_stroke = ((gp_style->flag & GP_MATERIAL_STROKE_SHOW) != 0);
|
||||||
const bool show_fill = (points.size() >= 3) &&
|
const bool show_fill = (!triangles[shape_index].is_empty()) &&
|
||||||
((gp_style->flag & GP_MATERIAL_FILL_SHOW) != 0) &&
|
((gp_style->flag & GP_MATERIAL_FILL_SHOW) != 0) &&
|
||||||
(!pd->simplify_fill);
|
(!pd->simplify_fill);
|
||||||
const bool hide_onion = is_onion && ((gp_style->flag & GP_MATERIAL_HIDE_ONIONSKIN) != 0 ||
|
const bool hide_onion = is_onion && ((gp_style->flag & GP_MATERIAL_HIDE_ONIONSKIN) != 0 ||
|
||||||
@ -806,9 +820,12 @@ static GPENCIL_tObject *grease_pencil_object_cache_populate(GPENCIL_PrivateData
|
|||||||
(only_lines && !is_onion) || hide_onion;
|
(only_lines && !is_onion) || hide_onion;
|
||||||
|
|
||||||
if (skip_stroke) {
|
if (skip_stroke) {
|
||||||
t_offset += num_triangles_per_stroke[pos];
|
t_offset += num_triangles_per_stroke[shape_index];
|
||||||
t_offset += num_vertices_per_stroke[pos] * 2;
|
for (const int i : shape.index_range()) {
|
||||||
return;
|
t_offset += num_vertices_per_stroke[current_curve + i] * 2;
|
||||||
|
};
|
||||||
|
current_curve += shape.size();
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
GPUUniformBuf *new_ubo_mat;
|
GPUUniformBuf *new_ubo_mat;
|
||||||
@ -853,21 +870,26 @@ static GPENCIL_tObject *grease_pencil_object_cache_populate(GPENCIL_PrivateData
|
|||||||
|
|
||||||
if (show_fill) {
|
if (show_fill) {
|
||||||
const int v_first = t_offset * 3;
|
const int v_first = t_offset * 3;
|
||||||
const int v_count = num_triangles_per_stroke[pos] * 3;
|
const int v_count = num_triangles_per_stroke[shape_index] * 3;
|
||||||
drawcall_add(geom, v_first, v_count);
|
drawcall_add(geom, v_first, v_count);
|
||||||
}
|
}
|
||||||
|
|
||||||
t_offset += num_triangles_per_stroke[pos];
|
t_offset += num_triangles_per_stroke[shape_index];
|
||||||
|
|
||||||
|
shape.foreach_index([&](const int curve_i) {
|
||||||
|
const IndexRange points = points_by_curve[curve_i];
|
||||||
|
|
||||||
if (show_stroke) {
|
if (show_stroke) {
|
||||||
const int v_first = t_offset * 3;
|
const int v_first = t_offset * 3;
|
||||||
const int v_count = num_vertices_per_stroke[pos] * 2 * 3;
|
const int v_count = num_vertices_per_stroke[current_curve] * 2 * 3;
|
||||||
drawcall_add(geom, v_first, v_count);
|
drawcall_add(geom, v_first, v_count);
|
||||||
}
|
}
|
||||||
|
|
||||||
t_offset += num_vertices_per_stroke[pos] * 2;
|
t_offset += num_vertices_per_stroke[current_curve] * 2;
|
||||||
|
current_curve++;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
drawcall_flush();
|
drawcall_flush();
|
||||||
|
|
||||||
|
@ -89,31 +89,32 @@ class GreasePencil {
|
|||||||
IndexMaskMemory memory;
|
IndexMaskMemory memory;
|
||||||
const IndexMask visible_strokes = ed::greasepencil::retrieve_visible_strokes(
|
const IndexMask visible_strokes = ed::greasepencil::retrieve_visible_strokes(
|
||||||
*ob, info.drawing, memory);
|
*ob, info.drawing, memory);
|
||||||
|
const Vector<IndexMask> shapes = info.drawing.shapes(memory);
|
||||||
|
|
||||||
visible_strokes.foreach_index([&](const int stroke_i) {
|
const Span<Vector<uint3>> triangles = info.drawing.triangles();
|
||||||
const IndexRange points = points_by_curve[stroke_i];
|
|
||||||
const int material_index = stroke_materials[stroke_i];
|
|
||||||
MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob, material_index + 1);
|
|
||||||
|
|
||||||
const bool hide_onion = info.onion_id != 0;
|
const bool hide_onion = info.onion_id != 0;
|
||||||
|
|
||||||
|
for (const int shape_index : shapes.index_range()) {
|
||||||
|
const IndexMask &shape = shapes[shape_index];
|
||||||
|
|
||||||
|
const int material_index = stroke_materials[shape.first()];
|
||||||
|
MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob, material_index + 1);
|
||||||
|
|
||||||
const bool hide_material = (gp_style->flag & GP_MATERIAL_HIDE) != 0;
|
const bool hide_material = (gp_style->flag & GP_MATERIAL_HIDE) != 0;
|
||||||
|
|
||||||
const int num_stroke_triangles = (points.size() >= 3) ? (points.size() - 2) : 0;
|
const int num_stroke_triangles = triangles[shape_index].size();
|
||||||
const int num_stroke_vertices = (points.size() +
|
|
||||||
int(cyclic[stroke_i] && (points.size() >= 3)));
|
|
||||||
|
|
||||||
if (hide_material || hide_onion) {
|
if (hide_material || hide_onion) {
|
||||||
t_offset += num_stroke_triangles;
|
t_offset += num_stroke_triangles;
|
||||||
t_offset += num_stroke_vertices * 2;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
blender::gpu::Batch *geom = draw::DRW_cache_grease_pencil_get(scene, ob);
|
|
||||||
|
|
||||||
const bool show_stroke = (gp_style->flag & GP_MATERIAL_STROKE_SHOW) != 0;
|
const bool show_stroke = (gp_style->flag & GP_MATERIAL_STROKE_SHOW) != 0;
|
||||||
const bool show_fill = (points.size() >= 3) &&
|
const bool show_fill = (num_stroke_triangles != 0) &&
|
||||||
(gp_style->flag & GP_MATERIAL_FILL_SHOW) != 0;
|
(gp_style->flag & GP_MATERIAL_FILL_SHOW) != 0;
|
||||||
|
|
||||||
|
blender::gpu::Batch *geom = draw::DRW_cache_grease_pencil_get(scene, ob);
|
||||||
|
|
||||||
if (show_fill) {
|
if (show_fill) {
|
||||||
int v_first = t_offset * 3;
|
int v_first = t_offset * 3;
|
||||||
int v_count = num_stroke_triangles * 3;
|
int v_count = num_stroke_triangles * 3;
|
||||||
@ -122,6 +123,17 @@ class GreasePencil {
|
|||||||
|
|
||||||
t_offset += num_stroke_triangles;
|
t_offset += num_stroke_triangles;
|
||||||
|
|
||||||
|
shape.foreach_index([&](const int curve_i) {
|
||||||
|
const IndexRange points = points_by_curve[curve_i];
|
||||||
|
|
||||||
|
const int num_stroke_vertices = (points.size() +
|
||||||
|
int(cyclic[curve_i] && (points.size() >= 3)));
|
||||||
|
|
||||||
|
if (hide_material || hide_onion) {
|
||||||
|
t_offset += num_stroke_vertices * 2;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (show_stroke) {
|
if (show_stroke) {
|
||||||
int v_first = t_offset * 3;
|
int v_first = t_offset * 3;
|
||||||
int v_count = num_stroke_vertices * 2 * 3;
|
int v_count = num_stroke_vertices * 2 * 3;
|
||||||
@ -131,6 +143,7 @@ class GreasePencil {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/* Returns the normal plane in NDC space. */
|
/* Returns the normal plane in NDC space. */
|
||||||
|
@ -323,29 +323,26 @@ static void OVERLAY_outline_grease_pencil(OVERLAY_PrivateData *pd, Scene *scene,
|
|||||||
IndexMaskMemory memory;
|
IndexMaskMemory memory;
|
||||||
const IndexMask visible_strokes = ed::greasepencil::retrieve_visible_strokes(
|
const IndexMask visible_strokes = ed::greasepencil::retrieve_visible_strokes(
|
||||||
*ob, info.drawing, memory);
|
*ob, info.drawing, memory);
|
||||||
|
const Vector<IndexMask> shapes = info.drawing.shapes(memory);
|
||||||
|
|
||||||
visible_strokes.foreach_index([&](const int stroke_i) {
|
const Span<Vector<uint3>> triangles = info.drawing.triangles();
|
||||||
const IndexRange points = points_by_curve[stroke_i];
|
|
||||||
const int material_index = stroke_materials[stroke_i];
|
for (const int shape_index : shapes.index_range()) {
|
||||||
|
const IndexMask &shape = shapes[shape_index];
|
||||||
|
|
||||||
|
const int material_index = stroke_materials[shape.first()];
|
||||||
MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob, material_index + 1);
|
MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob, material_index + 1);
|
||||||
|
|
||||||
const bool hide_onion = info.onion_id != 0;
|
const bool hide_onion = info.onion_id != 0;
|
||||||
const bool hide_material = (gp_style->flag & GP_MATERIAL_HIDE) != 0;
|
const bool hide_material = (gp_style->flag & GP_MATERIAL_HIDE) != 0;
|
||||||
|
|
||||||
const int num_stroke_triangles = (points.size() >= 3) ? (points.size() - 2) : 0;
|
const int num_stroke_triangles = triangles[shape_index].size();
|
||||||
const int num_stroke_vertices = (points.size() +
|
|
||||||
int(cyclic[stroke_i] && (points.size() >= 3)));
|
|
||||||
|
|
||||||
if (hide_material || hide_onion) {
|
|
||||||
t_offset += num_stroke_triangles;
|
|
||||||
t_offset += num_stroke_vertices * 2;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
blender::gpu::Batch *geom = draw::DRW_cache_grease_pencil_get(scene, ob);
|
|
||||||
|
|
||||||
const bool show_stroke = (gp_style->flag & GP_MATERIAL_STROKE_SHOW) != 0;
|
const bool show_stroke = (gp_style->flag & GP_MATERIAL_STROKE_SHOW) != 0;
|
||||||
const bool show_fill = (points.size() >= 3) && (gp_style->flag & GP_MATERIAL_FILL_SHOW) != 0;
|
const bool show_fill = (num_stroke_triangles != 0) &&
|
||||||
|
(gp_style->flag & GP_MATERIAL_FILL_SHOW) != 0;
|
||||||
|
|
||||||
|
blender::gpu::Batch *geom = draw::DRW_cache_grease_pencil_get(scene, ob);
|
||||||
|
|
||||||
if (show_fill) {
|
if (show_fill) {
|
||||||
int v_first = t_offset * 3;
|
int v_first = t_offset * 3;
|
||||||
@ -355,6 +352,16 @@ static void OVERLAY_outline_grease_pencil(OVERLAY_PrivateData *pd, Scene *scene,
|
|||||||
|
|
||||||
t_offset += num_stroke_triangles;
|
t_offset += num_stroke_triangles;
|
||||||
|
|
||||||
|
shape.foreach_index([&](const int curve_i) {
|
||||||
|
const IndexRange points = points_by_curve[curve_i];
|
||||||
|
const int num_stroke_vertices = (points.size() +
|
||||||
|
int(cyclic[curve_i] && (points.size() >= 3)));
|
||||||
|
|
||||||
|
if (hide_material || hide_onion) {
|
||||||
|
t_offset += num_stroke_vertices * 2;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (show_stroke) {
|
if (show_stroke) {
|
||||||
int v_first = t_offset * 3;
|
int v_first = t_offset * 3;
|
||||||
int v_count = num_stroke_vertices * 2 * 3;
|
int v_count = num_stroke_vertices * 2 * 3;
|
||||||
@ -363,6 +370,7 @@ static void OVERLAY_outline_grease_pencil(OVERLAY_PrivateData *pd, Scene *scene,
|
|||||||
t_offset += num_stroke_vertices * 2;
|
t_offset += num_stroke_vertices * 2;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void OVERLAY_outline_volume(OVERLAY_PrivateData *pd, Object *ob)
|
static void OVERLAY_outline_volume(OVERLAY_PrivateData *pd, Object *ob)
|
||||||
|
@ -1038,12 +1038,10 @@ static void grease_pencil_geom_batch_ensure(Object &object,
|
|||||||
ed::greasepencil::retrieve_visible_drawings(scene, grease_pencil, true);
|
ed::greasepencil::retrieve_visible_drawings(scene, grease_pencil, true);
|
||||||
|
|
||||||
/* First, count how many vertices and triangles are needed for the whole object. Also record the
|
/* First, count how many vertices and triangles are needed for the whole object. Also record the
|
||||||
* offsets into the curves for the vertices and triangles. */
|
* offsets into the curves for the vertices. */
|
||||||
int total_verts_num = 0;
|
int total_verts_num = 0;
|
||||||
int total_triangles_num = 0;
|
int total_triangles_num = 0;
|
||||||
int v_offset = 0;
|
|
||||||
Vector<Array<int>> verts_start_offsets_per_visible_drawing;
|
Vector<Array<int>> verts_start_offsets_per_visible_drawing;
|
||||||
Vector<Array<int>> tris_start_offsets_per_visible_drawing;
|
|
||||||
for (const ed::greasepencil::DrawingInfo &info : drawings) {
|
for (const ed::greasepencil::DrawingInfo &info : drawings) {
|
||||||
const bke::CurvesGeometry &curves = info.drawing.strokes();
|
const bke::CurvesGeometry &curves = info.drawing.strokes();
|
||||||
const OffsetIndices<int> points_by_curve = curves.evaluated_points_by_curve();
|
const OffsetIndices<int> points_by_curve = curves.evaluated_points_by_curve();
|
||||||
@ -1051,31 +1049,20 @@ static void grease_pencil_geom_batch_ensure(Object &object,
|
|||||||
IndexMaskMemory memory;
|
IndexMaskMemory memory;
|
||||||
const IndexMask visible_strokes = ed::greasepencil::retrieve_visible_strokes(
|
const IndexMask visible_strokes = ed::greasepencil::retrieve_visible_strokes(
|
||||||
object, info.drawing, memory);
|
object, info.drawing, memory);
|
||||||
|
const Span<Vector<uint3>> triangles = info.drawing.triangles();
|
||||||
|
const Vector<IndexMask> shapes = info.drawing.shapes(memory);
|
||||||
|
|
||||||
const int num_curves = visible_strokes.size();
|
Array<int> verts_start_offsets(curves.curves_num(), 0);
|
||||||
const int verts_start_offsets_size = num_curves;
|
|
||||||
const int tris_start_offsets_size = num_curves;
|
|
||||||
Array<int> verts_start_offsets(verts_start_offsets_size);
|
|
||||||
Array<int> tris_start_offsets(tris_start_offsets_size);
|
|
||||||
|
|
||||||
/* Calculate the triangle offsets for all the visible curves. */
|
|
||||||
int t_offset = 0;
|
|
||||||
int pos = 0;
|
|
||||||
for (const int curve_i : curves.curves_range()) {
|
|
||||||
IndexRange points = points_by_curve[curve_i];
|
|
||||||
if (visible_strokes.contains(curve_i)) {
|
|
||||||
tris_start_offsets[pos] = t_offset;
|
|
||||||
pos++;
|
|
||||||
}
|
|
||||||
if (points.size() >= 3) {
|
|
||||||
t_offset += points.size() - 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Calculate the vertex offsets for all the visible curves. */
|
/* Calculate the vertex offsets for all the visible curves. */
|
||||||
int num_cyclic = 0;
|
int num_cyclic = 0;
|
||||||
int num_points = 0;
|
int num_points = 0;
|
||||||
visible_strokes.foreach_index([&](const int curve_i, const int pos) {
|
for (const int shape_index : shapes.index_range()) {
|
||||||
|
const IndexMask &shape = shapes[shape_index];
|
||||||
|
|
||||||
|
total_triangles_num += triangles[shape_index].size();
|
||||||
|
|
||||||
|
shape.foreach_index([&](const int curve_i) {
|
||||||
IndexRange points = points_by_curve[curve_i];
|
IndexRange points = points_by_curve[curve_i];
|
||||||
const bool is_cyclic = cyclic[curve_i] && (points.size() > 2);
|
const bool is_cyclic = cyclic[curve_i] && (points.size() > 2);
|
||||||
|
|
||||||
@ -1083,18 +1070,17 @@ static void grease_pencil_geom_batch_ensure(Object &object,
|
|||||||
num_cyclic++;
|
num_cyclic++;
|
||||||
}
|
}
|
||||||
|
|
||||||
verts_start_offsets[pos] = v_offset;
|
verts_start_offsets[curve_i] = total_verts_num;
|
||||||
v_offset += 1 + points.size() + (is_cyclic ? 1 : 0) + 1;
|
/* One vertex is stored before and after as padding. Cyclic strokes have one extra vertex.
|
||||||
|
*/
|
||||||
|
total_verts_num += 1 + points.size() + (is_cyclic ? 1 : 0) + 1;
|
||||||
num_points += points.size();
|
num_points += points.size();
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/* One vertex is stored before and after as padding. Cyclic strokes have one extra vertex. */
|
|
||||||
total_verts_num += num_points + num_cyclic + num_curves * 2;
|
|
||||||
total_triangles_num += (num_points + num_cyclic) * 2;
|
total_triangles_num += (num_points + num_cyclic) * 2;
|
||||||
total_triangles_num += info.drawing.triangles().size();
|
|
||||||
|
|
||||||
verts_start_offsets_per_visible_drawing.append(std::move(verts_start_offsets));
|
verts_start_offsets_per_visible_drawing.append(std::move(verts_start_offsets));
|
||||||
tris_start_offsets_per_visible_drawing.append(std::move(tris_start_offsets));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GPUUsageType vbo_flag = GPU_USAGE_STATIC | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY;
|
GPUUsageType vbo_flag = GPU_USAGE_STATIC | GPU_USAGE_FLAG_BUFFER_TEXTURE_ONLY;
|
||||||
@ -1161,10 +1147,9 @@ static void grease_pencil_geom_batch_ensure(Object &object,
|
|||||||
const VArray<float> fill_opacities = *attributes.lookup_or_default<float>(
|
const VArray<float> fill_opacities = *attributes.lookup_or_default<float>(
|
||||||
"fill_opacity", bke::AttrDomain::Curve, 1.0f);
|
"fill_opacity", bke::AttrDomain::Curve, 1.0f);
|
||||||
|
|
||||||
const Span<uint3> triangles = info.drawing.triangles();
|
const Span<Vector<uint3>> triangles = info.drawing.triangles();
|
||||||
const Span<float4x2> texture_matrices = info.drawing.texture_matrices();
|
const Span<float4x2> texture_matrices = info.drawing.texture_matrices();
|
||||||
const Span<int> verts_start_offsets = verts_start_offsets_per_visible_drawing[drawing_i];
|
const Span<int> verts_start_offsets = verts_start_offsets_per_visible_drawing[drawing_i];
|
||||||
const Span<int> tris_start_offsets = tris_start_offsets_per_visible_drawing[drawing_i];
|
|
||||||
IndexMaskMemory memory;
|
IndexMaskMemory memory;
|
||||||
const IndexMask visible_strokes = ed::greasepencil::retrieve_visible_strokes(
|
const IndexMask visible_strokes = ed::greasepencil::retrieve_visible_strokes(
|
||||||
object, info.drawing, memory);
|
object, info.drawing, memory);
|
||||||
@ -1210,33 +1195,42 @@ static void grease_pencil_geom_batch_ensure(Object &object,
|
|||||||
GPU_indexbuf_add_tri_verts(&ibo, v_mat + 2, v_mat + 1, v_mat + 3);
|
GPU_indexbuf_add_tri_verts(&ibo, v_mat + 2, v_mat + 1, v_mat + 3);
|
||||||
};
|
};
|
||||||
|
|
||||||
visible_strokes.foreach_index([&](const int curve_i, const int pos) {
|
const Vector<IndexMask> shapes = info.drawing.shapes(memory);
|
||||||
|
const Array<int> point_to_curve_map = curves.point_to_curve_map();
|
||||||
|
|
||||||
|
auto point_to_id = [&](uint32_t p) {
|
||||||
|
const int curve_ = point_to_curve_map[p];
|
||||||
|
const IndexRange points_ = points_by_curve[curve_];
|
||||||
|
return (1 + (p - points_.first()) + verts_start_offsets[curve_]) << GP_VERTEX_ID_SHIFT;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const int shape_index : shapes.index_range()) {
|
||||||
|
const IndexMask &shape = shapes[shape_index];
|
||||||
|
const Span<uint3> tris_slice = triangles[shape_index];
|
||||||
|
|
||||||
|
/* Add the triangle indices to the index buffer. */
|
||||||
|
for (const uint3 tri : tris_slice) {
|
||||||
|
const uint3 tri_verts = uint3(point_to_id(tri.x), point_to_id(tri.y), point_to_id(tri.z));
|
||||||
|
GPU_indexbuf_add_tri_verts(&ibo, tri_verts.x, tri_verts.y, tri_verts.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
const float4x2 texture_matrix = texture_matrices[shape.first()] *
|
||||||
|
object_space_to_layer_space;
|
||||||
|
|
||||||
|
shape.foreach_index([&](const int curve_i) {
|
||||||
const IndexRange points = points_by_curve[curve_i];
|
const IndexRange points = points_by_curve[curve_i];
|
||||||
const bool is_cyclic = cyclic[curve_i] && (points.size() > 2);
|
const bool is_cyclic = cyclic[curve_i] && (points.size() > 2);
|
||||||
const int verts_start_offset = verts_start_offsets[pos];
|
const int verts_start_offset = verts_start_offsets[curve_i];
|
||||||
const int tris_start_offset = tris_start_offsets[pos];
|
|
||||||
const int num_verts = 1 + points.size() + (is_cyclic ? 1 : 0) + 1;
|
const int num_verts = 1 + points.size() + (is_cyclic ? 1 : 0) + 1;
|
||||||
const IndexRange verts_range = IndexRange(verts_start_offset, num_verts);
|
const IndexRange verts_range = IndexRange(verts_start_offset, num_verts);
|
||||||
MutableSpan<GreasePencilStrokeVert> verts_slice = verts.slice(verts_range);
|
MutableSpan<GreasePencilStrokeVert> verts_slice = verts.slice(verts_range);
|
||||||
MutableSpan<GreasePencilColorVert> cols_slice = cols.slice(verts_range);
|
MutableSpan<GreasePencilColorVert> cols_slice = cols.slice(verts_range);
|
||||||
const float4x2 texture_matrix = texture_matrices[curve_i] * object_space_to_layer_space;
|
|
||||||
|
|
||||||
const Span<float> lengths = curves.evaluated_lengths_for_curve(curve_i, cyclic[curve_i]);
|
const Span<float> lengths = curves.evaluated_lengths_for_curve(curve_i, cyclic[curve_i]);
|
||||||
|
|
||||||
/* First vertex is not drawn. */
|
/* First vertex is not drawn. */
|
||||||
verts_slice.first().mat = -1;
|
verts_slice.first().mat = -1;
|
||||||
|
|
||||||
/* If the stroke has more than 2 points, add the triangle indices to the index buffer. */
|
|
||||||
if (points.size() >= 3) {
|
|
||||||
const Span<uint3> tris_slice = triangles.slice(tris_start_offset, points.size() - 2);
|
|
||||||
for (const uint3 tri : tris_slice) {
|
|
||||||
GPU_indexbuf_add_tri_verts(&ibo,
|
|
||||||
(verts_range[1] + tri.x) << GP_VERTEX_ID_SHIFT,
|
|
||||||
(verts_range[1] + tri.y) << GP_VERTEX_ID_SHIFT,
|
|
||||||
(verts_range[1] + tri.z) << GP_VERTEX_ID_SHIFT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Write all the point attributes to the vertex buffers. Create a quad for each point. */
|
/* Write all the point attributes to the vertex buffers. Create a quad for each point. */
|
||||||
const float u_scale = u_scales[curve_i];
|
const float u_scale = u_scales[curve_i];
|
||||||
const float u_translation = u_translations[curve_i];
|
const float u_translation = u_translations[curve_i];
|
||||||
@ -1275,6 +1269,7 @@ static void grease_pencil_geom_batch_ensure(Object &object,
|
|||||||
verts_slice.last().mat = -1;
|
verts_slice.last().mat = -1;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Mark last 2 verts as invalid. */
|
/* Mark last 2 verts as invalid. */
|
||||||
verts[total_verts_num + 0].mat = -1;
|
verts[total_verts_num + 0].mat = -1;
|
||||||
|
Loading…
Reference in New Issue
Block a user