Blender V4.5
obj_import_nurbs.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include "BKE_curves.hh"
10#include "BKE_lib_id.hh"
11#include "BKE_object.hh"
12
13#include "BLI_array_utils.hh"
14#include "BLI_listbase.h"
15#include "BLI_math_vector.h"
16
17#include "DNA_curve_types.h"
18
19#include "IO_wavefront_obj.hh"
21#include "obj_import_nurbs.hh"
22#include "obj_import_objects.hh"
23
24namespace blender::io::obj {
25
27{
28 BLI_assert(!curve_geometry_.nurbs_element_.curv_indices.is_empty());
29
30 Curve *curve = BKE_id_new_nomain<Curve>(nullptr);
31
33
34 curve->flag = CU_3D;
35 curve->resolu = curve->resolv = 12;
36 /* Only one NURBS spline will be created in the curve object. */
37 curve->actnu = 0;
38
39 Nurb *nurb = MEM_callocN<Nurb>(__func__);
40 BLI_addtail(BKE_curve_nurbs_get(curve), nurb);
41 this->create_nurbs(curve, import_params);
42
43 return curve;
44}
45
47{
48 if (curve_geometry_.nurbs_element_.curv_indices.is_empty()) {
49 return nullptr;
50 }
51 std::string ob_name = get_geometry_name(curve_geometry_.geometry_name_,
52 import_params.collection_separator);
53 if (ob_name.empty() && !curve_geometry_.nurbs_element_.group_.empty()) {
54 ob_name = curve_geometry_.nurbs_element_.group_;
55 }
56 if (ob_name.empty()) {
57 ob_name = "Untitled";
58 }
59
60 Curve *curve = BKE_curve_add(bmain, ob_name.c_str(), OB_CURVES_LEGACY);
61 Object *obj = BKE_object_add_only_object(bmain, OB_CURVES_LEGACY, ob_name.c_str());
62
63 curve->flag = CU_3D;
64 curve->resolu = curve->resolv = 12;
65 /* Only one NURBS spline will be created in the curve object. */
66 curve->actnu = 0;
67
68 Nurb *nurb = MEM_callocN<Nurb>(__func__);
69 BLI_addtail(BKE_curve_nurbs_get(curve), nurb);
70 this->create_nurbs(curve, import_params);
71
72 obj->data = curve;
73 transform_object(obj, import_params);
74
75 return obj;
76}
77
79{
80 /* Use max(1, min()) to avoid undefined clamp behavior when curve_indices.size() == 0 */
81 const int degree = std::max(1, std::min<int>(element.degree, element.curv_indices.size() - 1));
82 return degree + 1 > std::numeric_limits<int8_t>::max() ? std::numeric_limits<int8_t>::max() - 1 :
83 int8_t(degree);
84}
85
90static int cyclic_repeated_points(const int8_t order, const int end_multiplicity)
91{
92 /* Since multiplicity == order is considered 'cyclic' even if it is only C0 continuous in
93 * principle (it is technically discontinuous!), it needs to be clamped to 1.
94 */
95 return std::max<int>(order - end_multiplicity, 1);
96}
97
98void CurveFromGeometry::create_nurbs(Curve *curve, const OBJImportParams &import_params)
99{
100 const NurbsElement &nurbs_geometry = curve_geometry_.nurbs_element_;
101 const int8_t degree = get_valid_nurbs_degree(nurbs_geometry);
102 Nurb *nurb = static_cast<Nurb *>(curve->nurb.first);
103
104 nurb->type = CU_NURBS;
105 nurb->flag = CU_3D;
106 nurb->next = nurb->prev = nullptr;
107 /* BKE_nurb_points_add later on will update pntsu. If this were set to total curve points,
108 * we get double the total points in viewport. */
109 nurb->pntsu = 0;
110 /* Total points = pntsu * pntsv. */
111 nurb->pntsv = 1;
112 nurb->orderu = nurb->orderv = degree + 1;
113 nurb->resolu = nurb->resolv = curve->resolu;
114
116 nurbs_geometry.parm);
117 nurb->flagu = this->detect_knot_mode(
118 import_params, degree, nurbs_geometry.curv_indices, nurbs_geometry.parm, multiplicity);
119
121 /* TODO: If mode is CU_NURB_CUSTOM, but not CU_NURB_CYCLIC and CU_NURB_ENDPOINT, then make
122 * curve clamped instead of removing CU_NURB_CUSTOM. */
123 nurb->flagu &= ~CU_NURB_CUSTOM;
124 }
125
126 const int repeated_points = nurb->flagu & CU_NURB_CYCLIC ?
127 cyclic_repeated_points(nurb->orderu, multiplicity.first()) :
128 0;
129 const Span<int> indices = nurbs_geometry.curv_indices.as_span().slice(
130 nurbs_geometry.curv_indices.index_range().drop_back(repeated_points));
131
132 BKE_nurb_points_add(nurb, indices.size());
133 for (const int i : indices.index_range()) {
134 BPoint &bpoint = nurb->bp[i];
135 copy_v3_v3(bpoint.vec, global_vertices_.vertices[indices[i]]);
136 bpoint.vec[3] = (global_vertices_.vertex_weights.size() > indices[i]) ?
137 global_vertices_.vertex_weights[indices[i]] :
138 1.0f;
139 bpoint.weight = 1.0f;
140 }
141
142 if (nurb->flagu & CU_NURB_CUSTOM) {
144 array_utils::copy<float>(nurbs_geometry.parm, MutableSpan<float>{nurb->knotsu, KNOTSU(nurb)});
145 }
146 else {
148 }
149}
150
151static bool detect_clamped_endpoint(const int8_t degree, const Span<int> multiplicity)
152{
153 const int8_t order = degree + 1;
154 return multiplicity.first() == order && multiplicity.last() == order;
155}
156
157static bool detect_knot_mode_cyclic(const int8_t degree,
158 const Span<int> indices,
159 const Span<float> knots,
160 const Span<int> multiplicity)
161{
162 constexpr float epsilon = 1e-7;
163 const int8_t order = degree + 1;
164
165 /* This is a good distinction between the 'cyclic' property and a true periodic NURBS curve. A
166 * periodic curve should be smooth to the degree - 1 derivative (which is the maximum possible).
167 * Allowing matching `multiplicity > 1` is not a periodic NURBS but can be considered cyclic.
168 */
169 if (multiplicity.first() != multiplicity.last()) {
170 return false;
171 }
172
173 /* Multiplicity m is continuous to the `degree - m` derivative and as such
174 * `multiplicity == order` is discontinuous.
175 * By allowing it, clamped or Bezier curves can still be considered cyclic but
176 * ensure [here] that illogical `multiplicities > order` is not considered cyclic.
177 */
178 if (multiplicity.first() > order || multiplicity.last() > order) {
179
180 return false;
181 }
182
183 const int repeated_points = cyclic_repeated_points(order, multiplicity.first());
184 const Span<int> indices_tail = indices.take_back(repeated_points);
185 for (const int64_t i : indices_tail.index_range()) {
186 if (indices[i] != indices_tail[i]) {
187 return false;
188 }
189 }
190
191 if (multiplicity.first() >= degree) {
192 /* There is no overlap in the knot spans. */
193 return true;
194 }
195
196 /* Ensure it matches on both of the knot spans adjacent to the start/end of the parameter range.
197 */
198 const Span<float> knots_tail = knots.take_back(order + degree);
199 for (const int64_t i : knots_tail.index_range().drop_back(1)) {
200 const float head_span = knots[i + 1] - knots[i];
201 const float tail_span = knots_tail[i + 1] - knots_tail[i];
202 if (abs(head_span - tail_span) > epsilon) {
203 return false;
204 }
205 }
206 return true;
207}
208
209static bool detect_knot_mode_bezier(const int8_t degree, const Span<int> multiplicity)
210{
211 const int8_t order = degree + 1;
212 if (multiplicity.first() != order || multiplicity.last() != order) {
213 return false;
214 }
215
216 for (const int m : multiplicity.drop_front(1).drop_back(1)) {
217 if (m != degree) {
218 return false;
219 }
220 }
221 return true;
222}
223
224static bool detect_knot_mode_uniform(const int8_t degree,
225 const Span<float> knots,
226 const Span<int> multiplicity,
227 bool clamped)
228{
229 constexpr float epsilon = 1e-7;
230
231 /* Check if knot count matches multiplicity adjusted for clamped ends. For a uniform non-clamped
232 * curve, all multiplicity entries equals 1 and the array size should match.
233 */
234 const int clamped_offset = clamped * degree;
235 if (knots.size() != multiplicity.size() - 2 * clamped_offset) {
236 return false;
237 }
238
239 /* Ensure it's not a single segment with clamped ends (it would be a Bezier segment). */
240 const Span<float> unclamped_knots = knots.drop_front(clamped_offset).drop_back(clamped_offset);
241 if (!unclamped_knots.size()) {
242 return false;
243 }
244
245 /* Verify spacing is uniform (excluding clamped ends). */
246 const float uniform_delta = unclamped_knots[1] - unclamped_knots[0];
247 for (const int64_t i : knots.index_range().drop_front(2)) {
248 if (abs((knots[i] - knots[i - 1]) - uniform_delta) < epsilon) {
249 return false;
250 }
251 }
252 return true;
253}
254
255short CurveFromGeometry::detect_knot_mode(const OBJImportParams &import_params,
256 const int8_t degree,
257 const Span<int> indices,
258 const Span<float> knots,
259 const Span<int> multiplicity)
260{
261 short knot_mode = 0;
262
263 if (import_params.close_spline_loops && indices.size() > degree) {
265 knot_mode, detect_knot_mode_cyclic(degree, indices, knots, multiplicity), CU_NURB_CYCLIC);
266 }
267
268 if (detect_knot_mode_bezier(degree, multiplicity)) {
269 /* Currently endpoint flag is not parsed for Bezier, mainly because a clamped Bezier curve in
270 * Blender is either:
271 * a) A valid Bezier curve for given degree/order with correct number of points to form one.
272 * b) Not a valid Bezier curve for given degree, last span/segment is of a lower degree Bezier.
273 *
274 * Set ENDPOINT to true since legacy Bezier NURBS only validates and compute knots if it
275 * contains order + 1 control points unless endpoint is set...?
276 */
277 SET_FLAG_FROM_TEST(knot_mode, true, CU_NURB_ENDPOINT);
278 SET_FLAG_FROM_TEST(knot_mode, true, CU_NURB_BEZIER);
279 }
280 else {
281 const bool clamped = detect_clamped_endpoint(degree, multiplicity);
282 SET_FLAG_FROM_TEST(knot_mode, clamped, CU_NURB_ENDPOINT);
283 SET_FLAG_FROM_TEST(knot_mode,
284 !detect_knot_mode_uniform(degree, knots, multiplicity, clamped),
286 }
287 return knot_mode;
288}
289
290} // namespace blender::io::obj
void BKE_curve_init(Curve *cu, short curve_type)
Definition curve.cc:372
ListBase * BKE_curve_nurbs_get(Curve *cu)
Definition curve.cc:4962
void BKE_nurb_knot_calc_u(Nurb *nu)
Definition curve.cc:1193
Curve * BKE_curve_add(Main *bmain, const char *name, int type)
Definition curve.cc:411
void BKE_nurb_knot_alloc_u(Nurb *nu)
Definition curve.cc:1188
void BKE_nurb_points_add(Nurb *nu, int number)
Definition curve.cc:867
Low-level operations for curves.
void * BKE_id_new_nomain(short type, const char *name)
Definition lib_id.cc:1500
General operations, lookup, etc. for blender objects.
Object * BKE_object_add_only_object(Main *bmain, int type, const char *name) ATTR_RETURNS_NONNULL
#define BLI_assert(a)
Definition BLI_assert.h:46
void BLI_addtail(ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:111
MINLINE void copy_v3_v3(float r[3], const float a[3])
#define SET_FLAG_FROM_TEST(value, test, flag)
@ CU_NURB_CYCLIC
@ CU_NURB_CUSTOM
@ CU_NURB_ENDPOINT
@ CU_NURB_BEZIER
@ CU_NURBS
@ CU_3D
@ OB_CURVES_LEGACY
ATTR_WARN_UNUSED_RESULT const void * element
ATTR_WARN_UNUSED_RESULT const BMVert const BMEdge * e
long long int int64_t
int64_t size() const
constexpr IndexRange drop_back(int64_t n) const
constexpr IndexRange drop_front(int64_t n) const
constexpr Span drop_front(int64_t n) const
Definition BLI_span.hh:171
constexpr Span take_back(int64_t n) const
Definition BLI_span.hh:204
constexpr Span drop_back(int64_t n) const
Definition BLI_span.hh:182
constexpr const T & first() const
Definition BLI_span.hh:315
constexpr int64_t size() const
Definition BLI_span.hh:252
constexpr const T & last(const int64_t n=0) const
Definition BLI_span.hh:325
constexpr IndexRange index_range() const
Definition BLI_span.hh:401
const T & first() const
Curve * create_curve(const OBJImportParams &import_params)
Object * create_curve_object(Main *bmain, const OBJImportParams &import_params)
static ushort indices[]
#define abs
void * MEM_callocN(size_t len, const char *str)
Definition mallocn.cc:118
void copy(const GVArray &src, GMutableSpan dst, int64_t grain_size=4096)
Vector< int > calculate_multiplicity_sequence(Span< float > knots)
static int8_t get_valid_nurbs_degree(const NurbsElement &element)
static bool detect_clamped_endpoint(const int8_t degree, const Span< int > multiplicity)
static int cyclic_repeated_points(const int8_t order, const int end_multiplicity)
static bool detect_knot_mode_cyclic(const int8_t degree, const Span< int > indices, const Span< float > knots, const Span< int > multiplicity)
void transform_object(Object *object, const OBJImportParams &import_params)
static bool detect_knot_mode_uniform(const int8_t degree, const Span< float > knots, const Span< int > multiplicity, bool clamped)
static bool detect_knot_mode_bezier(const int8_t degree, const Span< int > multiplicity)
std::string get_geometry_name(const std::string &full_name, char separator)
float vec[4]
short resolv
short resolu
ListBase nurb
void * first
short flagu
short orderu
struct Nurb * next
short orderv
short flag
short type
BPoint * bp
short resolu
struct Nurb * prev
short resolv
i
Definition text_draw.cc:230