Blender V5.0
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
10#include "BKE_curves.hh"
11#include "BKE_lib_id.hh"
12#include "BKE_object.hh"
13
14#include "BLI_array_utils.hh"
15#include "BLI_listbase.h"
16#include "BLI_math_vector.h"
17
18#include "DNA_curve_types.h"
19
20#include "IO_wavefront_obj.hh"
21
23#include "obj_import_nurbs.hh"
24#include "obj_import_objects.hh"
25
26namespace blender::io::obj {
27
29{
30 BLI_assert(!curve_geometry_.nurbs_element_.curv_indices.is_empty());
31
32 Curves *curves_id = bke::curves_new_nomain(0, 0);
33 bke::CurvesGeometry &curves = curves_id->geometry.wrap();
34 this->create_nurbs(curves, import_params);
35 return curves_id;
36}
37
39{
40 if (curve_geometry_.nurbs_element_.curv_indices.is_empty()) {
41 return nullptr;
42 }
43 std::string ob_name = get_geometry_name(curve_geometry_.geometry_name_,
44 import_params.collection_separator);
45 if (ob_name.empty() && !curve_geometry_.nurbs_element_.group_.empty()) {
46 ob_name = curve_geometry_.nurbs_element_.group_;
47 }
48 if (ob_name.empty()) {
49 ob_name = "Untitled";
50 }
51
52 Curve *curve = BKE_curve_add(bmain, ob_name.c_str(), OB_CURVES_LEGACY);
53 Object *obj = BKE_object_add_only_object(bmain, OB_CURVES_LEGACY, ob_name.c_str());
54
55 curve->flag = CU_3D;
56 curve->resolu = curve->resolv = 12;
57 /* Only one NURBS spline will be created in the curve object. */
58 curve->actnu = 0;
59
60 Nurb *nurb = MEM_callocN<Nurb>(__func__);
61 BLI_addtail(BKE_curve_nurbs_get(curve), nurb);
62 this->create_nurbs(curve, import_params);
63
64 obj->data = curve;
65 transform_object(obj, import_params);
66
67 return obj;
68}
69
71{
72 /* Use max(1, min()) to avoid undefined clamp behavior when curve_indices.size() == 0 */
73 const int degree = std::max(1, std::min<int>(element.degree, element.curv_indices.size() - 1));
74 return degree + 1 > std::numeric_limits<int8_t>::max() ? std::numeric_limits<int8_t>::max() - 1 :
75 int8_t(degree);
76}
77
82static int repeating_cyclic_point_num(const int8_t order, const Span<float> knots)
83{
84 /* Due to the additional start knot, drop first.
85 */
87 knots.slice(1, order - 1));
88
89 BLI_assert(order > multiplicity.first());
90 return order - multiplicity.first();
91}
92
93void CurveFromGeometry::create_nurbs(Curve *curve, const OBJImportParams &import_params)
94{
95 const NurbsElement &nurbs_geometry = curve_geometry_.nurbs_element_;
96 const int8_t degree = get_valid_nurbs_degree(nurbs_geometry);
97 Nurb *nurb = static_cast<Nurb *>(curve->nurb.first);
98
99 nurb->type = CU_NURBS;
100 nurb->flag = CU_3D;
101 nurb->next = nurb->prev = nullptr;
102 /* BKE_nurb_points_add later on will update pntsu. If this were set to total curve points,
103 * we get double the total points in viewport. */
104 nurb->pntsu = 0;
105 /* Total points = pntsu * pntsv. */
106 nurb->pntsv = 1;
107 nurb->orderu = nurb->orderv = degree + 1;
108 nurb->resolu = nurb->resolv = curve->resolu;
109
111 nurbs_geometry.parm);
112 nurb->flagu = this->detect_knot_mode(
113 import_params, degree, nurbs_geometry.curv_indices, nurbs_geometry.parm, multiplicity);
114
115 const int repeated_points = nurb->flagu & CU_NURB_CYCLIC ?
116 repeating_cyclic_point_num(nurb->orderu, nurbs_geometry.parm) :
117 0;
118 const Span<int> indices = nurbs_geometry.curv_indices.as_span().slice(
119 nurbs_geometry.curv_indices.index_range().drop_back(repeated_points));
120
121 BKE_nurb_points_add(nurb, indices.size());
122 for (const int i : indices.index_range()) {
123 BPoint &bpoint = nurb->bp[i];
124 copy_v3_v3(bpoint.vec, global_vertices_.vertices[indices[i]]);
125 bpoint.vec[3] = (global_vertices_.vertex_weights.size() > indices[i]) ?
126 global_vertices_.vertex_weights[indices[i]] :
127 1.0f;
128 bpoint.weight = 1.0f;
129 }
130
131 if (nurb->flagu & CU_NURB_CUSTOM) {
133 MutableSpan<float> knots_dst_u{nurb->knotsu, KNOTSU(nurb)};
134 array_utils::copy<float>(nurbs_geometry.parm, knots_dst_u);
135 }
136 else {
138 }
139}
140
141void CurveFromGeometry::create_nurbs(bke::CurvesGeometry &curves,
142 const OBJImportParams &import_params)
143{
144 const NurbsElement &nurbs_geometry = curve_geometry_.nurbs_element_;
145 const int8_t degree = get_valid_nurbs_degree(nurbs_geometry);
146 const int8_t order = degree + 1;
147
148 const Vector<int> multiplicity = bke::curves::nurbs::calculate_multiplicity_sequence(
149 nurbs_geometry.parm);
150 const short knot_flag = this->detect_knot_mode(
151 import_params, degree, nurbs_geometry.curv_indices, nurbs_geometry.parm, multiplicity);
152
153 const bool is_cyclic = knot_flag & CU_NURB_CYCLIC;
154 const int repeated_points = is_cyclic ? repeating_cyclic_point_num(order, nurbs_geometry.parm) :
155 0;
156 const Span<int> indices = nurbs_geometry.curv_indices.as_span().slice(
157 nurbs_geometry.curv_indices.index_range().drop_back(repeated_points));
158
159 const int points_num = indices.size();
160 const int curve_index = 0;
161 curves.resize(points_num, 1);
162
163 MutableSpan<int8_t> types = curves.curve_types_for_write();
164 MutableSpan<bool> cyclic = curves.cyclic_for_write();
165 MutableSpan<int8_t> orders = curves.nurbs_orders_for_write();
166 MutableSpan<int8_t> modes = curves.nurbs_knots_modes_for_write();
167 types.first() = CURVE_TYPE_NURBS;
168 cyclic.first() = is_cyclic;
169 orders.first() = order;
170 modes.first() = bke::knots_mode_from_legacy(knot_flag);
171 curves.update_curve_types();
172
173 const OffsetIndices points_by_curve = curves.points_by_curve();
174 const IndexRange point_range = points_by_curve[curve_index];
175
176 MutableSpan<float3> positions = curves.positions_for_write().slice(point_range);
177 MutableSpan<float> weights = curves.nurbs_weights_for_write().slice(point_range);
178 for (const int i : indices.index_range()) {
179 positions[i] = global_vertices_.vertices[indices[i]];
180 weights[i] = (global_vertices_.vertex_weights.size() > indices[i]) ?
181 global_vertices_.vertex_weights[indices[i]] :
182 1.0f;
183 }
184
185 if (modes.first() == NURBS_KNOT_MODE_CUSTOM) {
186 OffsetIndices<int> knot_offsets = curves.nurbs_custom_knots_by_curve();
187 curves.nurbs_custom_knots_update_size();
188 MutableSpan<float> knots = curves.nurbs_custom_knots_for_write().slice(
189 knot_offsets[curve_index]);
190
191 array_utils::copy<float>(nurbs_geometry.parm, knots);
192 }
193}
194
195static bool detect_clamped_endpoint(const int8_t degree, const Span<int> multiplicity)
196{
197 const int8_t order = degree + 1;
198 /* Consider any combination of following patterns as clamped:
199 *
200 * O ..
201 * 1 d ..
202 */
203 const bool begin_clamped = multiplicity.first() == order ||
204 (multiplicity.first() == 1 && multiplicity[1] == degree);
205 const bool end_clamped = multiplicity.last() == order ||
206 (multiplicity.last() == 1 && multiplicity.last(1) == degree);
207 return begin_clamped && end_clamped;
208}
209
210static bool almost_equal_relative(const float a, const float b, const float epsilon)
211{
212 const float abs_diff = std::abs(b - a);
213 return abs_diff < a * epsilon;
214}
215
216static bool detect_knot_mode_cyclic(const int8_t degree,
217 const Span<int> indices,
218 const Span<float> knots,
219 const Span<int> multiplicity,
220 const bool is_clamped)
221{
222 constexpr float epsilon = 1e-4;
223 const int8_t order = degree + 1;
224
225 const int repeated_points = repeating_cyclic_point_num(order, knots);
226 BLI_assert(repeated_points > 0);
227 const Span<int> indices_tail = indices.take_back(repeated_points);
228 for (const int64_t i : indices_tail.index_range()) {
229 if (indices[i] != indices_tail[i]) {
230 return false;
231 }
232 }
233
234 /* Multiplicity m is continuous to the `degree - m` derivative and as such
235 * `multiplicity == degree` is discontinuous. Due to the superfluous knots
236 * the first/last entry can be up to `order`, remaining up to `degree`.
237 */
238 if (multiplicity.first() > order || multiplicity.last() > order) {
239 return false;
240 }
241 for (const int m : multiplicity.drop_front(1).drop_back(1)) {
242 if (m > degree) {
243 return false;
244 }
245 }
246
247 if (is_clamped) {
248 /* Clamped curves are discontinuous at the ends and have no overlapping spans. */
249 return true;
250 }
251
252 /* Ensure it matches on both of the knot spans adjacent to the start/end of the parameter range.
253 */
254 const Span<float> knots_tail = knots.take_back(2 * degree + 1);
255 for (const int64_t i : knots_tail.index_range().drop_back(1)) {
256 const float head_span = knots[i + 1] - knots[i];
257 const float tail_span = knots_tail[i + 1] - knots_tail[i];
258 if (!almost_equal_relative(head_span, tail_span, epsilon)) {
259 return false;
260 }
261 }
262 return true;
263}
264
265static bool detect_knot_mode_bezier_clamped(const int8_t degree,
266 const int num_points,
267 const Span<int> multiplicity)
268{
269 const int8_t order = degree + 1;
270 /* Don't treat polylines as Beziers. */
271 if (order == 2) {
272 return false;
273 }
274
275 /* Allow patterns:
276 O d ..
277 1 d d ..
278 */
279 if (multiplicity[0] < order && (multiplicity[0] != 1 || multiplicity[1] < degree)) {
280 return false;
281 }
282
283 Span<int> mdegree_span = multiplicity.drop_front(1);
284 if (multiplicity.size() == 2) {
285 /* Single segment, allow patterns:
286 * O a
287 * where a > 0
288 */
289 if (multiplicity.first() != order) {
290 return false;
291 }
292 }
293 else {
294 /* Allow patterns:
295 .. d O+
296 .. d d 1
297 */
298 if (multiplicity.last() != order &&
299 (multiplicity.last() == 1 && multiplicity.last(1) != degree))
300 {
301 /* No match to the valid patterns. */
302 return false;
303 }
304
305 const int remainder = (num_points - 1) % degree;
306 if (multiplicity.last() != order + remainder &&
307 (multiplicity.last() != 1 || multiplicity.last(1) < degree))
308 {
309 return false;
310 }
311 }
312 mdegree_span = mdegree_span.drop_back(1);
313
314 /* Verify all other knots are of degree multiplicity */
315 for (const int m : mdegree_span) {
316 if (m != degree) {
317 return false;
318 }
319 }
320 return true;
321}
322
323static bool detect_knot_mode_uniform(const int8_t degree,
324 const Span<float> knots,
325 const Span<int> multiplicity,
326 const bool clamped)
327{
328 constexpr float epsilon = 1e-4;
329
330 /* Check if knot count matches multiplicity adjusted for clamped ends. For a uniform non-clamped
331 * curve, all multiplicity entries equals 1 and the array size should match.
332 */
333 const int O1_clamps = int(multiplicity.first() == 1) + int(multiplicity.last() == 1);
334 const int clamped_offset = clamped ? 2 * degree - O1_clamps : 0;
335 if (knots.size() != multiplicity.size() + clamped_offset) {
336 return false;
337 }
338
339 /* Ensure it's not a single segment with clamped ends (it would be a Bezier segment). */
340 const Span<float> unclamped_knots = knots.drop_front(clamped_offset).drop_back(clamped_offset);
341 if (unclamped_knots.size() == 2) {
342 return false;
343 }
344 if (unclamped_knots.size() < 2) {
345 /* Classify single point as uniform? */
346 return true;
347 }
348
349 /* Verify spacing is uniform (excluding clamped ends). */
350 const float uniform_delta = unclamped_knots[1] - unclamped_knots[0];
351 for (const int64_t i : unclamped_knots.index_range().drop_front(2)) {
352 const float delta = unclamped_knots[i] - unclamped_knots[i - 1];
353 if (!almost_equal_relative(delta, uniform_delta, epsilon)) {
354 return false;
355 }
356 }
357 return true;
358}
359
360short CurveFromGeometry::detect_knot_mode(const OBJImportParams &import_params,
361 const int8_t degree,
362 const Span<int> indices,
363 const Span<float> knots,
364 const Span<int> multiplicity)
365{
366 short knot_mode = 0;
367
368 const bool is_clamped = detect_clamped_endpoint(degree, multiplicity);
369
370 const bool is_bezier = detect_knot_mode_bezier_clamped(degree, indices.size(), multiplicity);
371 if (is_bezier) {
372 SET_FLAG_FROM_TEST(knot_mode, true, CU_NURB_ENDPOINT);
373 SET_FLAG_FROM_TEST(knot_mode, true, CU_NURB_BEZIER);
374 }
375 else {
376 const bool is_uniform = detect_knot_mode_uniform(degree, knots, multiplicity, is_clamped);
377 SET_FLAG_FROM_TEST(knot_mode, is_clamped, CU_NURB_ENDPOINT);
378 SET_FLAG_FROM_TEST(knot_mode, !is_uniform, CU_NURB_CUSTOM);
379 }
380
381 const bool check_cyclic = import_params.close_spline_loops && indices.size() > degree;
382 const bool no_custom_cyclic = knot_mode & CU_NURB_CUSTOM;
383 if (check_cyclic && !no_custom_cyclic) {
385 degree, indices, knots, multiplicity, is_clamped);
387 }
388
389 return knot_mode;
390}
391
392} // namespace blender::io::obj
#define KNOTSU(nu)
Definition BKE_curve.hh:74
ListBase * BKE_curve_nurbs_get(Curve *cu)
Definition curve.cc:4958
void BKE_nurb_knot_calc_u(Nurb *nu)
Definition curve.cc:1189
Curve * BKE_curve_add(Main *bmain, const char *name, int type)
Definition curve.cc:407
void BKE_nurb_knot_alloc_u(Nurb *nu)
Definition curve.cc:1184
void BKE_nurb_points_add(Nurb *nu, int number)
Definition curve.cc:863
Low-level operations for curves.
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_3D
@ CU_NURBS
@ NURBS_KNOT_MODE_CUSTOM
@ OB_CURVES_LEGACY
ATTR_WARN_UNUSED_RESULT const void * element
ATTR_WARN_UNUSED_RESULT const BMVert const BMEdge * e
long long int int64_t
constexpr T & first() const
Definition BLI_span.hh:679
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 slice(int64_t start, int64_t size) const
Definition BLI_span.hh:137
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
Object * create_curve_object(Main *bmain, const OBJImportParams &import_params)
Curves * create_curve(const OBJImportParams &import_params)
static bool is_cyclic(const Nurb *nu)
static ushort indices[]
static char ** types
Definition makesdna.cc:71
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)
KnotsMode knots_mode_from_legacy(short flag)
Curves * curves_new_nomain(int points_num, int curves_num)
static bool detect_knot_mode_bezier_clamped(const int8_t degree, const int num_points, const Span< int > multiplicity)
static int repeating_cyclic_point_num(const int8_t order, const Span< float > knots)
static int8_t get_valid_nurbs_degree(const NurbsElement &element)
static bool almost_equal_relative(const float a, const float b, const float epsilon)
static bool detect_clamped_endpoint(const int8_t degree, const Span< int > multiplicity)
static bool detect_knot_mode_uniform(const int8_t degree, const Span< float > knots, const Span< int > multiplicity, const bool clamped)
static bool detect_knot_mode_cyclic(const int8_t degree, const Span< int > indices, const Span< float > knots, const Span< int > multiplicity, const bool is_clamped)
void transform_object(Object *object, const OBJImportParams &import_params)
std::string get_geometry_name(const std::string &full_name, char separator)
float vec[4]
short resolv
short resolu
ListBase nurb
CurvesGeometry geometry
void * first
short flagu
short orderu
struct Nurb * next
short orderv
float * knotsu
short flag
short type
BPoint * bp
short resolu
struct Nurb * prev
short resolv
i
Definition text_draw.cc:230