Curves: support transforming Bezier handles in edit mode
Allows user to transform Bezier handles. Pull Request: https://projects.blender.org/blender/blender/pulls/120222
This commit is contained in:
committed by
Jacques Lucke
parent
c42eeb0c38
commit
12df5a68ba
@@ -90,7 +90,8 @@ struct CurvesTransformData {
|
||||
|
||||
/**
|
||||
* The offsets of every grease pencil layer into `positions` array.
|
||||
* For curves only one layer is used.
|
||||
* For curves layers are used to store: positions, handle_positions_left and
|
||||
* handle_positions_right.
|
||||
*/
|
||||
blender::Vector<int> layer_offsets;
|
||||
|
||||
@@ -171,15 +172,15 @@ void animrecord_check_state(TransInfo *t, ID *id);
|
||||
/**
|
||||
* Used for both curves and grease pencil objects.
|
||||
*/
|
||||
void curve_populate_trans_data_structs(TransDataContainer &tc,
|
||||
blender::bke::CurvesGeometry &curves,
|
||||
const blender::float4x4 &matrix,
|
||||
std::optional<blender::MutableSpan<float>> value_attribute,
|
||||
const blender::IndexMask &selected_indices,
|
||||
bool use_proportional_edit,
|
||||
const blender::IndexMask &affected_curves,
|
||||
bool use_connected_only,
|
||||
int trans_data_offset);
|
||||
void curve_populate_trans_data_structs(
|
||||
TransDataContainer &tc,
|
||||
blender::bke::CurvesGeometry &curves,
|
||||
const blender::float4x4 &transform,
|
||||
std::optional<blender::MutableSpan<float>> value_attribute,
|
||||
const blender::Span<blender::IndexMask> points_to_transform_indices,
|
||||
const blender::IndexMask &affected_curves,
|
||||
bool use_connected_only,
|
||||
const blender::IndexMask &bezier_curves);
|
||||
|
||||
CurvesTransformData *create_curves_transform_custom_data(TransCustomData &custom_data);
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
#include "BKE_attribute.hh"
|
||||
#include "BKE_curves.hh"
|
||||
#include "BKE_curves_utils.hh"
|
||||
|
||||
#include "ED_curves.hh"
|
||||
|
||||
@@ -63,9 +64,9 @@ static void calculate_curve_point_distances_for_proportional_editing(
|
||||
}
|
||||
}
|
||||
|
||||
static void append_positions_to_custom_data(const IndexMask selection,
|
||||
Span<float3> positions,
|
||||
TransCustomData &custom_data)
|
||||
static MutableSpan<float3> append_positions_to_custom_data(const IndexMask selection,
|
||||
Span<float3> positions,
|
||||
TransCustomData &custom_data)
|
||||
{
|
||||
CurvesTransformData &transform_data = *static_cast<CurvesTransformData *>(custom_data.data);
|
||||
transform_data.selection_by_layer.append(selection);
|
||||
@@ -75,32 +76,101 @@ static void append_positions_to_custom_data(const IndexMask selection,
|
||||
positions,
|
||||
selection,
|
||||
transform_data.positions.as_mutable_span().slice(data_offset, selection.size()));
|
||||
return transform_data.positions.as_mutable_span().slice(transform_data.layer_offsets.last(1),
|
||||
selection.size());
|
||||
}
|
||||
|
||||
static void createTransCurvesVerts(bContext * /*C*/, TransInfo *t)
|
||||
{
|
||||
MutableSpan<TransDataContainer> trans_data_contrainers(t->data_container, t->data_container_len);
|
||||
Array<IndexMask> selection_per_object(t->data_container_len);
|
||||
Array<Vector<IndexMask>> points_to_transform_per_attribute(t->data_container_len);
|
||||
Array<IndexMask> bezier_curves(t->data_container_len);
|
||||
const bool use_proportional_edit = (t->flag & T_PROP_EDIT_ALL) != 0;
|
||||
const bool use_connected_only = (t->flag & T_PROP_CONNECTED) != 0;
|
||||
|
||||
Vector<int> must_be_selected;
|
||||
|
||||
/* Count selected elements per object and create TransData structs. */
|
||||
for (const int i : trans_data_contrainers.index_range()) {
|
||||
TransDataContainer &tc = trans_data_contrainers[i];
|
||||
Curves *curves_id = static_cast<Curves *>(tc.obedit->data);
|
||||
bke::CurvesGeometry &curves = curves_id->geometry.wrap();
|
||||
|
||||
CurvesTransformData *curves_transform_data = create_curves_transform_custom_data(
|
||||
tc.custom.type);
|
||||
Span<StringRef> selection_attribute_names = ed::curves::get_curves_selection_attribute_names(
|
||||
curves);
|
||||
std::array<IndexMask, 3> selection_per_attribute;
|
||||
|
||||
for (const int attribute_i : selection_attribute_names.index_range()) {
|
||||
const StringRef &selection_name = selection_attribute_names[attribute_i];
|
||||
selection_per_attribute[attribute_i] = ed::curves::retrieve_selected_points(
|
||||
curves, selection_name, curves_transform_data->memory);
|
||||
}
|
||||
|
||||
bezier_curves[i] = bke::curves::indices_for_type(curves.curve_types(),
|
||||
curves.curve_type_counts(),
|
||||
CURVE_TYPE_BEZIER,
|
||||
curves.curves_range(),
|
||||
curves_transform_data->memory);
|
||||
/* Alter selection as in legacy curves bezt_select_to_transform_triple_flag(). */
|
||||
if (!bezier_curves[i].is_empty()) {
|
||||
const OffsetIndices<int> points_by_curve = curves.points_by_curve();
|
||||
const VArray<int8_t> handle_types_left = curves.handle_types_left();
|
||||
const VArray<int8_t> handle_types_right = curves.handle_types_right();
|
||||
|
||||
must_be_selected.clear();
|
||||
bezier_curves[i].foreach_index([&](const int bezier_index) {
|
||||
for (const int point_i : points_by_curve[bezier_index]) {
|
||||
if (selection_per_attribute[0].contains(point_i)) {
|
||||
const HandleType type_left = HandleType(handle_types_left[point_i]);
|
||||
const HandleType type_right = HandleType(handle_types_right[point_i]);
|
||||
if (ELEM(type_left, BEZIER_HANDLE_AUTO, BEZIER_HANDLE_ALIGN) &&
|
||||
ELEM(type_right, BEZIER_HANDLE_AUTO, BEZIER_HANDLE_ALIGN))
|
||||
{
|
||||
must_be_selected.append(point_i);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/* Select bezier handles that must be transformed if the main control point is selected. */
|
||||
IndexMask must_be_selected_mask = IndexMask::from_indices(must_be_selected.as_span(),
|
||||
curves_transform_data->memory);
|
||||
if (must_be_selected.size()) {
|
||||
selection_per_attribute[1] = IndexMask::from_union(
|
||||
selection_per_attribute[1], must_be_selected_mask, curves_transform_data->memory);
|
||||
selection_per_attribute[2] = IndexMask::from_union(
|
||||
selection_per_attribute[2], must_be_selected_mask, curves_transform_data->memory);
|
||||
}
|
||||
}
|
||||
|
||||
if (use_proportional_edit) {
|
||||
selection_per_object[i] = curves.points_range();
|
||||
tc.data_len = curves.point_num;
|
||||
Array<int> bezier_point_offset_data(bezier_curves[i].size() + 1);
|
||||
OffsetIndices<int> bezier_offsets = offset_indices::gather_selected_offsets(
|
||||
curves.points_by_curve(), bezier_curves[i], bezier_point_offset_data);
|
||||
|
||||
const int bezier_point_count = bezier_offsets.total_size();
|
||||
tc.data_len = curves.points_num() + 2 * bezier_point_count;
|
||||
points_to_transform_per_attribute[i].append(curves.points_range());
|
||||
|
||||
if (bezier_point_count > 0) {
|
||||
Vector<index_mask::IndexMask::Initializer> bezier_point_ranges;
|
||||
OffsetIndices<int> points_by_curve = curves.points_by_curve();
|
||||
bezier_curves[i].foreach_index(GrainSize(512), [&](const int bezier_curve_i) {
|
||||
bezier_point_ranges.append(points_by_curve[bezier_curve_i]);
|
||||
});
|
||||
IndexMask bezier_points = IndexMask::from_initializers(bezier_point_ranges,
|
||||
curves_transform_data->memory);
|
||||
points_to_transform_per_attribute[i].append(bezier_points);
|
||||
points_to_transform_per_attribute[i].append(bezier_points);
|
||||
}
|
||||
}
|
||||
else {
|
||||
selection_per_object[i] = ed::curves::retrieve_selected_points(
|
||||
curves, curves_transform_data->memory);
|
||||
tc.data_len = selection_per_object[i].size();
|
||||
tc.data_len = 0;
|
||||
for (const int selection_i : selection_attribute_names.index_range()) {
|
||||
points_to_transform_per_attribute[i].append(selection_per_attribute[selection_i]);
|
||||
tc.data_len += points_to_transform_per_attribute[i][selection_i].size();
|
||||
}
|
||||
}
|
||||
|
||||
if (tc.data_len > 0) {
|
||||
@@ -140,11 +210,10 @@ static void createTransCurvesVerts(bContext * /*C*/, TransInfo *t)
|
||||
curves,
|
||||
object->object_to_world(),
|
||||
value_attribute,
|
||||
selection_per_object[i],
|
||||
use_proportional_edit,
|
||||
points_to_transform_per_attribute[i],
|
||||
curves.curves_range(),
|
||||
use_connected_only,
|
||||
0 /* No data offset for curves. */);
|
||||
bezier_curves[i]);
|
||||
|
||||
/* TODO: This is wrong. The attribute writer should live at least as long as the span. */
|
||||
attribute_writer.finish();
|
||||
@@ -164,8 +233,16 @@ static void recalcData_curves(TransInfo *t)
|
||||
curves.tag_normals_changed();
|
||||
}
|
||||
else {
|
||||
copy_positions_from_curves_transform_custom_data(
|
||||
tc.custom.type, 0, curves.positions_for_write());
|
||||
const std::array<MutableSpan<float3>, 3> positions_per_selection_attr = {
|
||||
curves.positions_for_write(),
|
||||
curves.handle_positions_left_for_write(),
|
||||
curves.handle_positions_right_for_write()};
|
||||
for (const int selection_i :
|
||||
ed::curves::get_curves_selection_attribute_names(curves).index_range())
|
||||
{
|
||||
copy_positions_from_curves_transform_custom_data(
|
||||
tc.custom.type, selection_i, positions_per_selection_attr[selection_i]);
|
||||
}
|
||||
curves.tag_positions_changed();
|
||||
curves.calculate_bezier_auto_handles();
|
||||
}
|
||||
@@ -173,6 +250,45 @@ static void recalcData_curves(TransInfo *t)
|
||||
}
|
||||
}
|
||||
|
||||
static OffsetIndices<int> recent_position_offsets(TransCustomData &custom_data, int num)
|
||||
{
|
||||
const CurvesTransformData &transform_data = *static_cast<CurvesTransformData *>(
|
||||
custom_data.data);
|
||||
return OffsetIndices(transform_data.layer_offsets.as_span().slice(
|
||||
transform_data.layer_offsets.size() - num - 1, num + 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates map of indices to `tc.data` representing curve in layout
|
||||
* [L0, P0, R0, L1, P1, R1, L2,P2, R2], where [P0, P1, P2], [L0, L1, L2] and [R0, R1, R2] are
|
||||
* positions, left handles and right handles respectively.
|
||||
*/
|
||||
static void fill_map(const CurveType curve_type,
|
||||
const IndexRange curve_points,
|
||||
const OffsetIndices<int> position_offsets_in_td,
|
||||
const int handles_offset,
|
||||
MutableSpan<int> map)
|
||||
{
|
||||
const int attr_num = (curve_type == CURVE_TYPE_BEZIER) ? 3 : 1;
|
||||
const int left_handle_index = handles_offset + position_offsets_in_td[1].start();
|
||||
const int position_index = curve_points.start() + position_offsets_in_td[0].start();
|
||||
const int right_handle_index = handles_offset + position_offsets_in_td[2].start();
|
||||
|
||||
std::array<int, 3> first_per_attr = {curve_type == CURVE_TYPE_BEZIER ? left_handle_index :
|
||||
position_index,
|
||||
/* Next two unused for non Bezier curves. */
|
||||
position_index,
|
||||
right_handle_index};
|
||||
|
||||
threading::parallel_for(curve_points.index_range(), 4096, [&](const IndexRange range) {
|
||||
for (const int i : range) {
|
||||
for (const int attr : IndexRange(attr_num)) {
|
||||
map[i * attr_num + attr] = first_per_attr[attr] + i;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace blender::ed::transform::curves
|
||||
|
||||
CurvesTransformData *create_curves_transform_custom_data(TransCustomData &custom_data)
|
||||
@@ -203,108 +319,136 @@ void copy_positions_from_curves_transform_custom_data(
|
||||
array_utils::scatter(positions, selection, positions_dst);
|
||||
}
|
||||
|
||||
void curve_populate_trans_data_structs(TransDataContainer &tc,
|
||||
blender::bke::CurvesGeometry &curves,
|
||||
const blender::float4x4 &transform,
|
||||
std::optional<blender::MutableSpan<float>> value_attribute,
|
||||
const blender::IndexMask &selected_indices,
|
||||
const bool use_proportional_edit,
|
||||
const blender::IndexMask &affected_curves,
|
||||
bool use_connected_only,
|
||||
int trans_data_offset)
|
||||
void curve_populate_trans_data_structs(
|
||||
TransDataContainer &tc,
|
||||
blender::bke::CurvesGeometry &curves,
|
||||
const blender::float4x4 &transform,
|
||||
std::optional<blender::MutableSpan<float>> value_attribute,
|
||||
const blender::Span<blender::IndexMask> points_to_transform_per_attr,
|
||||
const blender::IndexMask &affected_curves,
|
||||
bool use_connected_only,
|
||||
const blender::IndexMask &bezier_curves)
|
||||
{
|
||||
using namespace blender;
|
||||
const std::array<Span<float3>, 3> src_positions_per_selection_attr = {
|
||||
curves.positions(), curves.handle_positions_left(), curves.handle_positions_right()};
|
||||
std::array<MutableSpan<float3>, 3> positions_per_selection_attr;
|
||||
|
||||
for (const int selection_i : points_to_transform_per_attr.index_range()) {
|
||||
positions_per_selection_attr[selection_i] =
|
||||
ed::transform::curves::append_positions_to_custom_data(
|
||||
points_to_transform_per_attr[selection_i],
|
||||
src_positions_per_selection_attr[selection_i],
|
||||
tc.custom.type);
|
||||
}
|
||||
|
||||
float mtx[3][3], smtx[3][3];
|
||||
copy_m3_m4(mtx, transform.ptr());
|
||||
pseudoinverse_m3_m3(smtx, mtx, PSEUDOINVERSE_EPSILON);
|
||||
|
||||
ed::transform::curves::append_positions_to_custom_data(
|
||||
selected_indices, curves.positions(), tc.custom.type);
|
||||
MutableSpan<float3> positions = static_cast<CurvesTransformData *>(tc.custom.type.data)
|
||||
->positions.as_mutable_span()
|
||||
.slice(trans_data_offset, selected_indices.size());
|
||||
MutableSpan<TransData> all_tc_data = MutableSpan(tc.data, tc.data_len);
|
||||
OffsetIndices<int> position_offsets_in_td = ed::transform::curves::recent_position_offsets(
|
||||
tc.custom.type, points_to_transform_per_attr.size());
|
||||
|
||||
if (use_proportional_edit) {
|
||||
Vector<VArray<bool>> selection_attrs;
|
||||
Span<StringRef> selection_attribute_names = ed::curves::get_curves_selection_attribute_names(
|
||||
curves);
|
||||
for (const StringRef selection_name : selection_attribute_names) {
|
||||
const VArray<bool> selection_attr = *curves.attributes().lookup_or_default<bool>(
|
||||
selection_name, bke::AttrDomain::Point, true);
|
||||
selection_attrs.append(selection_attr);
|
||||
}
|
||||
|
||||
for (const int selection_i : position_offsets_in_td.index_range()) {
|
||||
if (position_offsets_in_td[selection_i].is_empty()) {
|
||||
continue;
|
||||
}
|
||||
MutableSpan<TransData> tc_data = all_tc_data.slice(position_offsets_in_td[selection_i]);
|
||||
MutableSpan<float3> positions = positions_per_selection_attr[selection_i];
|
||||
IndexMask points_to_transform = points_to_transform_per_attr[selection_i];
|
||||
VArray<bool> selection = selection_attrs[selection_i];
|
||||
|
||||
threading::parallel_for(points_to_transform.index_range(), 1024, [&](const IndexRange range) {
|
||||
for (const int tranform_point_i : range) {
|
||||
const int point_in_domain_i = points_to_transform[tranform_point_i];
|
||||
TransData &td = tc_data[tranform_point_i];
|
||||
float3 *elem = &positions[tranform_point_i];
|
||||
|
||||
copy_v3_v3(td.iloc, *elem);
|
||||
copy_v3_v3(td.center, td.iloc);
|
||||
td.loc = *elem;
|
||||
|
||||
td.flag = 0;
|
||||
if (selection[point_in_domain_i]) {
|
||||
td.flag = TD_SELECTED;
|
||||
}
|
||||
|
||||
if (value_attribute) {
|
||||
float *value = &((*value_attribute)[point_in_domain_i]);
|
||||
td.val = value;
|
||||
td.ival = *value;
|
||||
}
|
||||
td.ext = nullptr;
|
||||
|
||||
copy_m3_m3(td.smtx, smtx);
|
||||
copy_m3_m3(td.mtx, mtx);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (use_connected_only) {
|
||||
const VArray<int8_t> curve_types = curves.curve_types();
|
||||
const OffsetIndices<int> points_by_curve = curves.points_by_curve();
|
||||
const VArray<bool> selection = *curves.attributes().lookup_or_default<bool>(
|
||||
".selection", bke::AttrDomain::Point, true);
|
||||
Array<int> bezier_offsets_in_td(curves.curves_num() + 1, 0);
|
||||
offset_indices::copy_group_sizes(points_by_curve, bezier_curves, bezier_offsets_in_td);
|
||||
offset_indices::accumulate_counts_to_offsets(bezier_offsets_in_td);
|
||||
|
||||
affected_curves.foreach_segment(GrainSize(512), [&](const IndexMaskSegment segment) {
|
||||
Vector<float> closest_distances;
|
||||
Array<int> map;
|
||||
Array<float> closest_distances;
|
||||
Array<float3> mapped_curve_positions;
|
||||
|
||||
for (const int curve_i : segment) {
|
||||
const IndexRange points = points_by_curve[curve_i];
|
||||
const bool has_any_selected = ed::curves::has_anything_selected(selection, points);
|
||||
if (!has_any_selected && use_connected_only) {
|
||||
for (const int point_i : points) {
|
||||
TransData &td = tc.data[point_i + trans_data_offset];
|
||||
const int selection_attrs_num = curve_types[curve_i] == CURVE_TYPE_BEZIER ? 3 : 1;
|
||||
const IndexRange curve_points = points_by_curve[curve_i];
|
||||
const int total_curve_points = selection_attrs_num * curve_points.size();
|
||||
map.reinitialize(total_curve_points);
|
||||
closest_distances.reinitialize(total_curve_points);
|
||||
closest_distances.fill(std::numeric_limits<float>::max());
|
||||
mapped_curve_positions.reinitialize(total_curve_points);
|
||||
|
||||
ed::transform::curves::fill_map(CurveType(curve_types[curve_i]),
|
||||
curve_points,
|
||||
position_offsets_in_td,
|
||||
bezier_offsets_in_td[curve_i],
|
||||
map);
|
||||
|
||||
bool has_any_selected = false;
|
||||
for (const int selection_attr_i : IndexRange(selection_attrs_num)) {
|
||||
has_any_selected = has_any_selected ||
|
||||
ed::curves::has_anything_selected(selection_attrs[selection_attr_i],
|
||||
curve_points);
|
||||
}
|
||||
if (!has_any_selected) {
|
||||
for (const int i : map) {
|
||||
TransData &td = all_tc_data[i];
|
||||
td.flag |= TD_SKIP;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
closest_distances.reinitialize(points.size());
|
||||
closest_distances.fill(std::numeric_limits<float>::max());
|
||||
|
||||
for (const int i : IndexRange(points.size())) {
|
||||
const int point_i = points[i];
|
||||
TransData &td = tc.data[point_i + trans_data_offset];
|
||||
float3 *elem = &positions[point_i];
|
||||
|
||||
copy_v3_v3(td.iloc, *elem);
|
||||
copy_v3_v3(td.center, td.iloc);
|
||||
td.loc = *elem;
|
||||
|
||||
td.flag = 0;
|
||||
if (selection[point_i]) {
|
||||
for (const int i : closest_distances.index_range()) {
|
||||
TransData &td = all_tc_data[map[i]];
|
||||
mapped_curve_positions[i] = td.loc;
|
||||
if (td.flag & TD_SELECTED) {
|
||||
closest_distances[i] = 0.0f;
|
||||
td.flag = TD_SELECTED;
|
||||
}
|
||||
|
||||
if (value_attribute) {
|
||||
float *value = &((*value_attribute)[point_i]);
|
||||
td.val = value;
|
||||
td.ival = *value;
|
||||
}
|
||||
|
||||
td.ext = nullptr;
|
||||
|
||||
copy_m3_m3(td.smtx, smtx);
|
||||
copy_m3_m3(td.mtx, mtx);
|
||||
}
|
||||
|
||||
if (use_connected_only) {
|
||||
blender::ed::transform::curves::calculate_curve_point_distances_for_proportional_editing(
|
||||
positions.slice(points), closest_distances.as_mutable_span());
|
||||
for (const int i : IndexRange(points.size())) {
|
||||
TransData &td = tc.data[points[i] + trans_data_offset];
|
||||
td.dist = closest_distances[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
threading::parallel_for(selected_indices.index_range(), 1024, [&](const IndexRange range) {
|
||||
for (const int selection_i : range) {
|
||||
TransData *td = &tc.data[selection_i + trans_data_offset];
|
||||
const int point_i = selected_indices[selection_i];
|
||||
float3 *elem = &positions[selection_i];
|
||||
|
||||
copy_v3_v3(td->iloc, *elem);
|
||||
copy_v3_v3(td->center, td->iloc);
|
||||
td->loc = *elem;
|
||||
|
||||
if (value_attribute) {
|
||||
float *value = &((*value_attribute)[point_i]);
|
||||
td->val = value;
|
||||
td->ival = *value;
|
||||
blender::ed::transform::curves::calculate_curve_point_distances_for_proportional_editing(
|
||||
mapped_curve_positions.as_span(), closest_distances.as_mutable_span());
|
||||
for (const int i : closest_distances.index_range()) {
|
||||
TransData &td = all_tc_data[map[i]];
|
||||
td.dist = closest_distances[i];
|
||||
}
|
||||
|
||||
td->flag = TD_SELECTED;
|
||||
td->ext = nullptr;
|
||||
|
||||
copy_m3_m3(td->smtx, smtx);
|
||||
copy_m3_m3(td->mtx, mtx);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -89,7 +89,6 @@ static void createTransGreasePencilVerts(bContext *C, TransInfo *t)
|
||||
GreasePencil &grease_pencil = *static_cast<GreasePencil *>(tc.obedit->data);
|
||||
Span<const bke::greasepencil::Layer *> layers = grease_pencil.layers();
|
||||
|
||||
int layer_points_offset = 0;
|
||||
const Vector<ed::greasepencil::MutableDrawingInfo> drawings = all_drawings[i];
|
||||
for (ed::greasepencil::MutableDrawingInfo info : drawings) {
|
||||
const bke::greasepencil::Layer &layer = *layers[info.layer_index];
|
||||
@@ -115,13 +114,10 @@ static void createTransGreasePencilVerts(bContext *C, TransInfo *t)
|
||||
curves,
|
||||
layer_space_to_world_space,
|
||||
value_attribute,
|
||||
points,
|
||||
use_proportional_edit,
|
||||
{points},
|
||||
affected_strokes,
|
||||
use_connected_only,
|
||||
layer_points_offset);
|
||||
|
||||
layer_points_offset += points.size();
|
||||
IndexMask());
|
||||
layer_offset++;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user