Modeling: Implements adaptive Bezier curve tessellation #114628

Closed
Laurynas Duburas wants to merge 1 commits from laurynas/blender:bezier-adaptive-tess into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
7 changed files with 274 additions and 19 deletions

View File

@ -311,7 +311,12 @@ class DATA_PT_active_spline(CurveButtonsPanelActive, Panel):
sub.prop(act_spline, "order_v", text="V")
sub = col.column(align=True)
sub.prop(act_spline, "resolution_u", text="Resolution U")
if (act_spline.type == 'BEZIER'):
sub.prop(act_spline, "tess_mode")
res_text = "Angle" if (act_spline.type == 'BEZIER') and (
act_spline.tess_mode == "TESS_MODE_ADAPTIVE") else "Resolution U"
sub.prop(act_spline, "resolution_u", text=res_text)
if is_surf:
sub.prop(act_spline, "resolution_v", text="V")

View File

@ -105,6 +105,7 @@ set(SRC
intern/curve.cc
intern/curve_bevel.cc
intern/curve_bezier.cc
intern/curve_bezier_tess.cc
intern/curve_catmull_rom.cc
intern/curve_convert.cc
intern/curve_decimate.cc
@ -522,6 +523,7 @@ set(SRC
intern/CCGSubSurf_inline.h
intern/CCGSubSurf_intern.h
intern/attribute_access_intern.hh
intern/curve_bezier_tess.hh
intern/data_transfer_intern.h
intern/lib_intern.h
intern/multires_inline.hh

View File

@ -57,9 +57,13 @@
#include "CLG_log.h"
#include "BLO_read_write.hh"
#include "curve_bezier_tess.hh"
using blender::float3;
using blender::IndexRange;
using blender::bke::curves::bezier::LegacyBezierTess;
using blender::bke::curves::bezier::LegacyDeCasteljauTess;
using blender::bke::curves::bezier::LegacyForwardTess;
/* globals */
@ -2599,6 +2603,10 @@ void BKE_curve_bevelList_make(Object *ob, const ListBase *nurbs, const bool for_
if (nu->hide && is_editmode) {
continue;
}
LegacyBezierTess *tess = (nu->tess_mode == CU_TESS_MODE_ADAPTIVE) ?
static_cast<LegacyBezierTess *>(
MEM_new<LegacyDeCasteljauTess>(__func__)) :
MEM_new<LegacyForwardTess>(__func__);
/* check we are a single point? also check we are not a surface and that the orderu is sane,
* enforced in the UI but can go wrong possibly */
@ -2680,8 +2688,8 @@ void BKE_curve_bevelList_make(Object *ob, const ListBase *nurbs, const bool for_
}
}
else if (nu->type == CU_BEZIER) {
/* in case last point is not cyclic */
len = segcount * resolu + 1;
tess->init(nu);
len = tess->position_count();
BevList *bl = MEM_cnew<BevList>(__func__);
bl->bevpoints = (BevPoint *)MEM_calloc_arrayN(len, sizeof(BevPoint), __func__);
@ -2743,19 +2751,8 @@ void BKE_curve_bevelList_make(Object *ob, const ListBase *nurbs, const bool for_
}
}
else {
/* Always do all three, to prevent data hanging around. */
int j;
/* #BevPoint must stay aligned to 4 so `sizeof(BevPoint) / sizeof(float)` works. */
for (j = 0; j < 3; j++) {
BKE_curve_forward_diff_bezier(prevbezt->vec[1][j],
prevbezt->vec[2][j],
bezt->vec[0][j],
bezt->vec[1][j],
&(bevp->vec[j]),
resolu,
sizeof(BevPoint));
}
tess->get_positions(
bezt, prevbezt, segcount - a - 1, sizeof(BevPoint), &(bevp->vec[0]), resolu);
/* If both arrays are `nullptr` do nothing. */
tilt_bezpart(prevbezt,
@ -2767,7 +2764,7 @@ void BKE_curve_bevelList_make(Object *ob, const ListBase *nurbs, const bool for_
resolu,
sizeof(BevPoint));
if (cu->twist_mode == CU_TWIST_TANGENT) {
if (cu->twist_mode == CU_TWIST_TANGENT && nu->tess_mode == CU_TESS_MODE_FORWARD) {
forward_diff_bezier_cotangent(prevbezt->vec[1],
prevbezt->vec[2],
bezt->vec[0],
@ -2781,7 +2778,7 @@ void BKE_curve_bevelList_make(Object *ob, const ListBase *nurbs, const bool for_
if (seglen != nullptr) {
*seglen = 0;
*segbevcount = 0;
for (j = 0; j < resolu; j++) {
for (int j = 0; j < resolu; j++) {
bevp0 = bevp;
bevp++;
bevp->offset = len_v3v3(bevp0->vec, bevp->vec);
@ -2873,6 +2870,7 @@ void BKE_curve_bevelList_make(Object *ob, const ListBase *nurbs, const bool for_
}
}
}
MEM_delete(tess);
}
/* STEP 2: DOUBLE POINTS AND AUTOMATIC RESOLUTION, REDUCE DATABLOCKS */

View File

@ -0,0 +1,172 @@
#include "curve_bezier_tess.hh"
#include "BKE_curve.h"
namespace blender::bke::curves::bezier {
/**
* Calculates and caches points for all segments of given Nurb.
* Precalculation needed to know final number of tess points in advance.
*/
void LegacyDeCasteljauTess::init(const struct Nurb *nurb)
{
const bool cyclic = nurb->flagu & CU_NURB_CYCLIC;
int segments = nurb->pntsu - 1;
positions.clear();
segment_offsets.clear();
BezTriple *bezt = nurb->bezt;
BezTriple *prevbezt = nurb->bezt;
if (cyclic) {
segments++;
prevbezt = nurb->bezt + (nurb->pntsu - 1);
}
else {
prevbezt = bezt;
bezt++;
}
for (int s : IndexRange(segments)) {
UNUSED_VARS(s);
const float(&prev)[3][3] = prevbezt->vec;
const float(&curr)[3][3] = bezt->vec;
segment_offsets.append(positions.size());
decasteljau_evaluate_segment(prev[1], prev[2], curr[0], curr[1], nurb->resolu, positions);
prevbezt = bezt;
bezt++;
}
segment_offsets.append(positions.size());
positions.append((nurb->bezt + (nurb->pntsu - 1))->vec[1]);
}
int LegacyDeCasteljauTess::position_count()
{
return positions.size();
}
void LegacyDeCasteljauTess::get_positions(const BezTriple * /*bezt*/,
const BezTriple * /*prevbezt*/,
const int segment,
const int stride,
float *dest,
int &pnts)
{
const int start = segment_offsets[segment];
const int count = segment_offsets[segment + 1] - start;
pnts = count;
for (const float3 &p : positions.as_span().slice(start, count)) {
copy_v3_v3(dest, p);
dest = (float *)POINTER_OFFSET(dest, stride);
}
}
void LegacyForwardTess::init(const struct Nurb *nurb)
{
this->nurb = nurb;
}
int LegacyForwardTess::position_count()
{
const int resolution = nurb->resolu;
const int segment_count = SEGMENTSU(nurb);
/* in case last point is not cyclic */
return segment_count * resolution + 1;
}
void LegacyForwardTess::get_positions(const BezTriple *bezt,
const BezTriple *prevbezt,
const int /* segment */,
const int /* stride */,
float *dest,
int &pnts)
{
pnts = nurb->resolu;
for (int j = 0; j < 3; j++) {
BKE_curve_forward_diff_bezier(prevbezt->vec[1][j],
prevbezt->vec[2][j],
bezt->vec[0][j],
bezt->vec[1][j],
dest + j,
pnts,
sizeof(BevPoint));
}
}
/**
* Checks if angle between vecotors ab and cd is less than angle given as squared cosine.
*/
bool angle_test(float3 a, float3 b, float3 c, float3 d, float angle_cos_sqr)
{
const float3 start = b - a;
const float3 end = d - c;
const float dot_prod = dot_v3v3(start, end);
if (dot_prod < 0) {
return true;
}
const float lengths_squared = len_squared_v3(start) * len_squared_v3(end);
return dot_prod * dot_prod < angle_cos_sqr * lengths_squared;
}
bool subdivision_needed(const float3 points[], const int degree, float angle_cos_sqr)
{
bool ends = angle_test(points[0], points[1], points[degree - 1], points[degree], angle_cos_sqr);
if (ends) {
return true;
}
for (int i : IndexRange(0, degree - 2)) {
if (angle_test(points[i], points[i + 1], points[i + 1], points[i + 2], angle_cos_sqr)) {
return true;
}
}
return false;
}
/**
* Subdivides Bezier curve segment.
* \param degree: Degree of curve.
* \param depth: Current recursion level.
* \param angle_cos_sqr: Stop angle's cosine squared.
*/
void subdivide(
const float3 points[], int degree, Vector<float3> &dest, const int depth, float angle_cos_sqr)
{
if (depth < 15 && subdivision_needed(points, degree, angle_cos_sqr)) {
float3 new_points[2 * degree + 1];
for (int i : IndexRange(degree + 1)) {
const int j = i << 1;
new_points[j] = points[i];
}
for (int k : IndexRange(1, degree + 1)) {
for (int i = k - 1; i < 2 * degree - k; i += 2) {
new_points[i + 1] = (new_points[i] + new_points[i + 2]) * 0.5;
}
}
subdivide(new_points, degree, dest, depth + 1, angle_cos_sqr);
dest.append(new_points[degree]);
subdivide(new_points + degree, degree, dest, depth + 1, angle_cos_sqr);
}
}
/**
* Calculates cubic Bezier curves points using De Casteljau's algorithm.
* \param angle: Subdivision stops if angles between control point leg pairs is less than given.
*/
void decasteljau_evaluate_segment(const float3 &point_0,
const float3 &point_1,
const float3 &point_2,
const float3 &point_3,
const short angle,
Vector<float3> &dest)
{
const float angle_cos = cos(M_PI * angle / 180);
const float3 points[] = {point_0, point_1, point_2, point_3};
dest.append(point_0);
subdivide(points, 3, dest, 0, angle_cos * angle_cos);
}
} // namespace blender::bke::curves::bezier

View File

@ -0,0 +1,59 @@
#pragma once
#include "BKE_curves.hh"
#include "DNA_curve_types.h"
using blender::Vector;
namespace blender::bke::curves::bezier {
class LegacyBezierTess {
public:
virtual void init(const struct Nurb *nurb) = 0;
virtual int position_count() = 0;
virtual void get_positions(const BezTriple *bezt,
const BezTriple *prevbezt,
const int segment,
const int stride,
float *dest,
int &pnts) = 0;
virtual ~LegacyBezierTess(){};
};
class LegacyForwardTess : public LegacyBezierTess {
const Nurb *nurb;
public:
void init(const struct Nurb *nurb);
int position_count();
void get_positions(const BezTriple *bezt,
const BezTriple *prevbezt,
const int segment,
const int stride,
float *dest,
int &pnts);
};
class LegacyDeCasteljauTess : public LegacyBezierTess {
Vector<float3> positions;
Vector<int> segment_offsets;
public:
void init(const struct Nurb *nurb);
int position_count();
void get_positions(const BezTriple *bezt,
const BezTriple *prevbezt,
const int segment,
const int stride,
float *dest,
int &pnts);
};
void decasteljau_evaluate_segment(const float3 &point_0,
const float3 &point_1,
const float3 &point_2,
const float3 &point_3,
const short angle,
Vector<float3> &dest);
} // namespace blender::bke::curves::bezier

View File

@ -137,7 +137,8 @@ typedef struct Nurb {
short hide, flag;
/** Number of points in the U or V directions. */
int pntsu, pntsv;
char _pad[4];
char _pad[2];
short tess_mode;
/** Tessellation resolution in the U or V directions. */
short resolu, resolv;
short orderu, orderv;
@ -403,6 +404,12 @@ enum {
CU_SMOOTH = 1 << 0,
};
/** #Nurb.tess_mode*/
enum {
CU_TESS_MODE_FORWARD = 0,
CU_TESS_MODE_ADAPTIVE = 1,
};
/** #Nurb.type */
enum {
CU_POLY = 0,

View File

@ -1947,6 +1947,12 @@ static void rna_def_curve(BlenderRNA *brna)
static void rna_def_curve_nurb(BlenderRNA *brna)
{
static const EnumPropertyItem spline_tess_mode_items[] = {
{CU_TESS_MODE_FORWARD, "TESS_MODE_FORWARD", 0, "Forward Diff", "Evenly spaced"},
{CU_TESS_MODE_ADAPTIVE, "TESS_MODE_ADAPTIVE", 0, "De Casteljau", "Adaptive"},
{0, nullptr, 0, nullptr, nullptr},
};
static const EnumPropertyItem spline_interpolation_items[] = {
{KEY_LINEAR, "LINEAR", 0, "Linear", ""},
{KEY_CARDINAL, "CARDINAL", 0, "Cardinal", ""},
@ -2044,6 +2050,12 @@ static void rna_def_curve_nurb(BlenderRNA *brna)
"influence a greater area, but have worse performance");
RNA_def_property_update(prop, 0, "rna_Nurb_update_knot_v");
prop = RNA_def_property(srna, "tess_mode", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, nullptr, "tess_mode");
RNA_def_property_enum_items(prop, spline_tess_mode_items);
RNA_def_property_ui_text(prop, "Tessellation", "Tessellation algorithm for Bezier Curves");
RNA_def_property_update(prop, 0, "rna_Curve_update_data");
prop = RNA_def_property(srna, "resolution_u", PROP_INT, PROP_NONE);
RNA_def_property_int_sdna(prop, nullptr, "resolu");
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);