Modeling: Implements adaptive Bezier curve tessellation #114628
@ -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")
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 */
|
||||
|
172
source/blender/blenkernel/intern/curve_bezier_tess.cc
Normal file
172
source/blender/blenkernel/intern/curve_bezier_tess.cc
Normal 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
|
59
source/blender/blenkernel/intern/curve_bezier_tess.hh
Normal file
59
source/blender/blenkernel/intern/curve_bezier_tess.hh
Normal 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
|
@ -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,
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user