Panda3D
Loading...
Searching...
No Matches
qtessSurface.cxx
Go to the documentation of this file.
1/**
2 * PANDA 3D SOFTWARE
3 * Copyright (c) Carnegie Mellon University. All rights reserved.
4 *
5 * All use of this software is subject to the terms of the revised BSD
6 * license. You should have received a copy of this license along
7 * with this source code in a file named "LICENSE."
8 *
9 * @file qtessSurface.cxx
10 * @author drose
11 * @date 2003-10-13
12 */
13
14#include "qtessSurface.h"
15#include "qtessGlobals.h"
16#include "qtessInputEntry.h"
17#include "config_egg_qtess.h"
18#include "eggPolygon.h"
19#include "eggVertexPool.h"
20#include "eggVertex.h"
21#include "eggComment.h"
22#include "egg_parametrics.h"
23#include "pset.h"
24#include "pmap.h"
25
26using std::max;
27using std::string;
28
29/**
30 *
31 */
32QtessSurface::
33QtessSurface(EggNurbsSurface *egg_surface) :
34 _egg_surface(egg_surface)
35{
36 _nurbs = make_nurbs_surface(_egg_surface, LMatrix4d::ident_mat());
37 _has_vertex_color = _egg_surface->has_vertex_color();
38
39 // The first four slots are reserved for vertex color.
40 _next_d = 4;
41
42 _importance = 1.0;
43 _importance2 = 1.0;
44 _match_u = _match_v = nullptr;
45 _tess_u = _tess_v = 0;
46 _got_scores = false;
47
48 // If the surface is closed in either dimension, the mininum tesselation in
49 // that dimension is by default 3, so we don't ribbonize the surface.
50 // Otherwise the minimum is 1.
51 _min_u = _min_v = 1;
52 if (egg_surface->is_closed_u()) {
53 _min_u = 3;
54 }
55 if (egg_surface->is_closed_v()) {
56 _min_v = 3;
57 }
58
59 if (_nurbs == nullptr) {
60 _num_u = _num_v = 0;
61
62 } else {
63 record_vertex_extras();
64
65 _nurbs->normalize_u_knots();
66 _nurbs->normalize_v_knots();
67 _nurbs_result = _nurbs->evaluate();
68
69 _num_u = _nurbs->get_num_u_segments();
70 _num_v = _nurbs->get_num_v_segments();
71
72 if (QtessGlobals::_respect_egg) {
73 if (egg_surface->get_u_subdiv() != 0) {
74 _num_u = egg_surface->get_u_subdiv();
75 }
76 if (egg_surface->get_v_subdiv() != 0) {
77 _num_v = egg_surface->get_v_subdiv();
78 }
79 }
80 }
81}
82
83/**
84 * Computes the curvature/stretch score for the surface, if it has not been
85 * already computed, and returns the net surface score. This is used both for
86 * automatically distributing isoparams among the surfaces by curvature, as
87 * well as for automatically placing the isoparams within each surface by
88 * curvature.
89 */
91get_score(double ratio) {
92 if (_nurbs == nullptr) {
93 return 0.0;
94 }
95
96 if (!_got_scores) {
97 _u_placer.get_scores(_nurbs->get_num_u_segments() * 100,
98 _nurbs->get_num_v_segments() * 2,
99 ratio, _nurbs_result, true);
100 _v_placer.get_scores(_nurbs->get_num_v_segments() * 100,
101 _nurbs->get_num_u_segments() * 2,
102 ratio, _nurbs_result, false);
103 _got_scores = true;
104 }
105
106 return _u_placer.get_total_score() * _v_placer.get_total_score() * _importance2;
107}
108
109/**
110 * Applies the appropriate tesselation to the surface, and replaces its node
111 * in the tree with an EggGroup containing both the new vertex pool and all of
112 * the polygons.
113 */
115tesselate() {
116 apply_match();
117 int tris = 0;
118
119 PT(EggGroup) group = do_uniform_tesselate(tris);
120 PT(EggNode) new_node = group.p();
121 if (new_node == nullptr) {
122 new_node = new EggComment(_egg_surface->get_name(),
123 "Omitted NURBS surface.");
124 tris = 0;
125 }
126 EggGroupNode *parent = _egg_surface->get_parent();
127 nassertr(parent != nullptr, 0);
128 parent->remove_child(_egg_surface);
129 parent->add_child(new_node);
130
131 return tris;
132}
133
134/**
135 * Writes a line to the given output file telling qtess how this surface
136 * should be tesselated uniformly. Returns the number of tris.
137 */
139write_qtess_parameter(std::ostream &out) {
140 apply_match();
141
142 if (_tess_u == 0 || _tess_v == 0) {
143 out << get_name() << " : omit\n";
144
145 } else if (_iso_u.empty() || _iso_v.empty()) {
146 out << get_name() << " : " << _tess_u << " " << _tess_v << "\n";
147
148 } else {
149 out << get_name() << " : " << _iso_u.back() << " " << _iso_v.back();
150 QtessInputEntry::output_extra(out, _iso_u, 'u');
151 QtessInputEntry::output_extra(out, _iso_v, 'v');
152 out << "\n";
153 }
154
155 return count_tris();
156}
157
158
159/**
160 * Sets up the surface to omit itself from the output.
161 */
163omit() {
164 _tess_u = 0;
165 _tess_v = 0;
166}
167
168/**
169 * Sets the surface up to tesselate itself uniformly at u x v, or if autoplace
170 * is true, automatically with u x v quads.
171 */
173tesselate_uv(int u, int v, bool autoplace, double ratio) {
174 _tess_u = u;
175 _tess_v = v;
176 _iso_u.clear();
177 _iso_v.clear();
178 if (autoplace) {
179 tesselate_auto(_tess_u, _tess_v, ratio);
180 }
181}
182
183/**
184 * Sets the surface up to tesselate itself at specific isoparams only.
185 */
188 const pvector<double> &v_list) {
189 _iso_u = u_list;
190 _iso_v = v_list;
191 _tess_u = (int)_iso_u.size() - 1;
192 _tess_v = (int)_iso_v.size() - 1;
193}
194
195/**
196 * Sets the surface up to tesselate itself to a uniform amount per isoparam.
197 */
199tesselate_per_isoparam(double pi, bool autoplace, double ratio) {
200 if (_num_u == 0 || _num_v == 0) {
201 omit();
202
203 } else {
204 _tess_u = max(_min_u, (int)floor(_num_u * _importance * pi + 0.5));
205 _tess_v = max(_min_v, (int)floor(_num_v * _importance * pi + 0.5));
206 _iso_u.clear();
207 _iso_v.clear();
208 if (autoplace) {
209 tesselate_auto(_tess_u, _tess_v, ratio);
210 }
211 }
212}
213
214
215/**
216 * Sets the surface up to tesselate itself according to its computed curvature
217 * score in both dimensions.
218 */
220tesselate_per_score(double pi, bool autoplace, double ratio) {
221 if (get_score(ratio) <= 0.0) {
222 omit();
223
224 } else {
225 _tess_u = max(_min_u, (int)floor(_u_placer.get_total_score() * _importance * pi + 0.5));
226 _tess_v = max(_min_v, (int)floor(_v_placer.get_total_score() * _importance * pi + 0.5));
227 _iso_u.clear();
228 _iso_v.clear();
229 if (autoplace) {
230 tesselate_auto(_tess_u, _tess_v, ratio);
231 }
232 }
233}
234
235/**
236 * Sets the surface up to tesselate itself by automatically determining the
237 * best place to put the indicated u x v isoparams.
238 */
240tesselate_auto(int u, int v, double ratio) {
241 if (get_score(ratio) <= 0.0) {
242 omit();
243
244 } else {
245 _u_placer.place(u, _iso_u);
246 _v_placer.place(v, _iso_v);
247 _tess_u = (int)_iso_u.size() - 1;
248 _tess_v = (int)_iso_v.size() - 1;
249 }
250}
251
252/**
253 * Records the joint membership and morph offsets of each control vertex in
254 * the extra-dimensional space of the NURBS, so that we can extract this data
255 * out again later to apply to the polygon vertices.
256 */
257void QtessSurface::
258record_vertex_extras() {
259 int num_u_vertices = _egg_surface->get_num_u_cvs();
260 int num_v_vertices = _egg_surface->get_num_v_cvs();
261
262 for (int ui = 0; ui < num_u_vertices; ui++) {
263 for (int vi = 0; vi < num_v_vertices; vi++) {
264 int i = _egg_surface->get_vertex_index(ui, vi);
265 EggVertex *egg_vertex = _egg_surface->get_vertex(i);
266
267 // The joint membership.
268 EggVertex::GroupRef::const_iterator gi;
269 for (gi = egg_vertex->gref_begin(); gi != egg_vertex->gref_end(); ++gi) {
270 EggGroup *joint = (*gi);
271 int d = get_joint_membership_index(joint);
272 double membership = joint->get_vertex_membership(egg_vertex);
273 _nurbs->set_extended_vertex(ui, vi, d, membership);
274 }
275
276 // The xyz morphs.
277 EggMorphVertexList::const_iterator dxi;
278 for (dxi = egg_vertex->_dxyzs.begin();
279 dxi != egg_vertex->_dxyzs.end();
280 ++dxi) {
281 const string &morph_name = (*dxi).get_name();
282 LVector3 delta = LCAST(PN_stdfloat, (*dxi).get_offset());
283 int d = get_dxyz_index(morph_name);
284 _nurbs->set_extended_vertices(ui, vi, d, delta.get_data(), 3);
285 }
286
287 // The rgba morphs.
288 EggMorphColorList::const_iterator dri;
289 for (dri = egg_vertex->_drgbas.begin();
290 dri != egg_vertex->_drgbas.end();
291 ++dri) {
292 const string &morph_name = (*dri).get_name();
293 const LVector4 &delta = (*dri).get_offset();
294 int d = get_drgba_index(morph_name);
295 _nurbs->set_extended_vertices(ui, vi, d, delta.get_data(), 4);
296 }
297 }
298 }
299}
300
301/**
302 * If the surface was set up to copy its tesselation in either axis from
303 * another surface, makes this copy now.
304 */
305void QtessSurface::
306apply_match() {
307 if (_match_u != nullptr) {
308 QtessSurface *m = *_match_u;
309 if (m == nullptr) {
310 qtess_cat.warning()
311 << "No surface to match " << get_name() << " to in U.\n";
312 } else {
313 if (qtess_cat.is_debug()) {
314 qtess_cat.debug()
315 << "Matching " << get_name() << " in U to " << m->get_name()
316 << " in " << (_match_u_to_u?'U':'V') << ".\n";
317 }
318 if (_match_u_to_u) {
319 _tess_u = m->_tess_u;
320 _iso_u = m->_iso_u;
321 } else {
322 _tess_u = m->_tess_v;
323 _iso_u = m->_iso_v;
324 }
325 }
326 }
327
328 if (_match_v != nullptr) {
329 QtessSurface *m = *_match_v;
330 if (m == nullptr) {
331 qtess_cat.warning()
332 << "No surface to match " << get_name() << " in V.\n";
333 } else {
334 if (qtess_cat.is_debug()) {
335 qtess_cat.debug()
336 << "Matching " << get_name() << " in V to " << m->get_name()
337 << " in " << (_match_v_to_v?'V':'U') << ".\n";
338 }
339 if (_match_v_to_v) {
340 _tess_v = m->_tess_v;
341 _iso_v = m->_iso_v;
342 } else {
343 _tess_v = m->_tess_u;
344 _iso_v = m->_iso_u;
345 }
346 }
347 }
348}
349
350/**
351 * Subdivide the surface uniformly according to the parameters specified by an
352 * earlier call to omit(), teseselate_uv(), or tesselate_per_isoparam().
353 */
354PT(EggGroup) QtessSurface::
355do_uniform_tesselate(int &tris) const {
356 tris = 0;
357
358 if (_tess_u == 0 || _tess_v == 0) {
359 // No tesselation!
360 if (qtess_cat.is_debug()) {
361 qtess_cat.debug()
362 << get_name() << " : omit\n";
363 }
364 return nullptr;
365 }
366
367 PT(EggGroup) group = new EggGroup(_egg_surface->get_name());
368
369 // _tess_u and _tess_v are the number of patches to create. Convert that to
370 // the number of vertices.
371
372 int num_u = _tess_u + 1;
373 int num_v = _tess_v + 1;
374
375 if (qtess_cat.is_debug()) {
376 qtess_cat.debug() << get_name() << " : " << tris << "\n";
377 }
378
379 assert(_iso_u.empty() || (int)_iso_u.size() == num_u);
380 assert(_iso_v.empty() || (int)_iso_v.size() == num_v);
381
382 // Now how many vertices is that total, and how many vertices per strip?
383 int num_verts = num_u * num_v;
384
385 // Create a vertex pool.
386 PT(EggVertexPool) vpool = new EggVertexPool(_egg_surface->get_name());
387 group->add_child(vpool);
388
389 // Create all the vertices.
390 int ui, vi;
391 double u, v;
392
393 typedef pvector<EggVertex *> VertexList;
394 VertexList new_verts;
395 new_verts.reserve(num_verts);
396
397 // Also collect the vertices into this set to group them by spatial position
398 // only. This is relevant for calculating normals.
399 typedef pset<EggVertex *> NVertexGroup;
400 typedef pmap<LVertexd, NVertexGroup> NVertexCollection;
401 NVertexCollection n_collection;
402
403 for (vi = 0; vi < num_v; vi++) {
404 if (_iso_v.empty()) {
405 v = (double)vi / (double)(num_v-1);
406 } else {
407 v = _iso_v[vi] / _iso_v.back();
408 }
409 for (ui = 0; ui < num_u; ui++) {
410 if (_iso_u.empty()) {
411 u = (double)ui / (double)(num_u-1);
412 } else {
413 u = _iso_u[ui] / _iso_u.back();
414 }
415
416 PT(EggVertex) egg_vertex = evaluate_vertex(u, v);
417 vpool->add_vertex(egg_vertex);
418 new_verts.push_back(egg_vertex);
419 n_collection[egg_vertex->get_pos3()].insert(egg_vertex);
420 }
421 }
422 nassertr((int)new_verts.size() == num_verts, nullptr);
423
424 // Now create a bunch of quads.
425 for (vi = 1; vi < num_v; vi++) {
426 for (ui = 1; ui < num_u; ui++) {
427 PT(EggPolygon) poly = new EggPolygon;
428 poly->add_vertex(new_verts[vi*num_u + (ui-1)]);
429 poly->add_vertex(new_verts[(vi-1)*num_u + (ui-1)]);
430 poly->add_vertex(new_verts[(vi-1)*num_u + ui]);
431 poly->add_vertex(new_verts[vi*num_u + ui]);
432
433 poly->copy_attributes(*_egg_surface);
434
435 // We compute a polygon normal just so we can verify the calculated
436 // vertex normals. It's also helpful for identifying degenerate
437 // polygons.
438 if (poly->recompute_polygon_normal()) {
439 tris += 2;
440 group->add_child(poly);
441 }
442 }
443 }
444
445 // Now check all the vertex normals by comparing them to the polygon
446 // normals. Some might have not been computed at all; others might be
447 // facing in the wrong direction.
448
449 // Now go back through and normalize the computed normals.
450 NVertexCollection::const_iterator nci;
451 for (nci = n_collection.begin(); nci != n_collection.end(); ++nci) {
452 const NVertexGroup &group = (*nci).second;
453
454 // Calculate the normal these vertices should have based on the polygons
455 // that share it.
456 LNormald normal = LNormald::zero();
457 int num_polys = 0;
458 NVertexGroup::const_iterator ngi;
459 for (ngi = group.begin(); ngi != group.end(); ++ngi) {
460 EggVertex *egg_vertex = (*ngi);
461 EggVertex::PrimitiveRef::const_iterator pri;
462 for (pri = egg_vertex->pref_begin();
463 pri != egg_vertex->pref_end();
464 ++pri) {
465 EggPrimitive *egg_primitive = (*pri);
466 nassertr(egg_primitive->has_normal(), nullptr);
467 normal += egg_primitive->get_normal();
468 num_polys++;
469 }
470 }
471
472 if (num_polys > 0) {
473 normal /= (double)num_polys;
474
475 // Now compare this normal with what the NURBS representation
476 // calculated. It should be facing in at least vaguely the same
477 // direction.
478 for (ngi = group.begin(); ngi != group.end(); ++ngi) {
479 EggVertex *egg_vertex = (*ngi);
480 if (egg_vertex->has_normal()) {
481 if (normal.dot(egg_vertex->get_normal()) < 0.0) {
482 // This one is backwards.
483 egg_vertex->set_normal(-egg_vertex->get_normal());
484 }
485 } else {
486 // This vertex doesn't have a normal; it gets the computed normal.
487 egg_vertex->set_normal(normal);
488 }
489 }
490 }
491 }
492
493 return group;
494}
495
496/**
497 * Evaluates the surface at the given u, v position and sets the vertex to the
498 * appropriate values. Also sets the joint membership of the vertex.
499 */
500PT(EggVertex) QtessSurface::
501evaluate_vertex(double u, double v) const {
502 PT(EggVertex) egg_vertex = new EggVertex;
503
504 LVertex point;
505 LNormal normal;
506 _nurbs_result->eval_point(u, v, point);
507 _nurbs_result->eval_normal(u, v, normal);
508
509 // If the normal is too short, don't consider it--it's probably inaccurate
510 // due to numerical limitations. We'll recompute it later based on the
511 // polygon normals.
512 PN_stdfloat length = normal.length();
513 if (length > 0.0001f) {
514 normal /= length;
515 egg_vertex->set_normal(LCAST(double, normal));
516 }
517
518 egg_vertex->set_pos(LCAST(double, point));
519 egg_vertex->set_uv(LVecBase2d(u, v));
520
521 // The color is stored, by convention, in slots 0-4 of the surface.
522 if (_has_vertex_color) {
523 LColor rgba;
524 _nurbs_result->eval_extended_points(u, v, 0, &rgba[0], 4);
525 egg_vertex->set_color(rgba);
526 }
527
528 // Also fill in the joint membership.
529 JointTable::const_iterator jti;
530 for (jti = _joint_table.begin(); jti != _joint_table.end(); ++jti) {
531 EggGroup *joint = (*jti).first;
532 int d = (*jti).second;
533
534 double membership = _nurbs_result->eval_extended_point(u, v, d);
535 if (membership > 0.0) {
536 joint->ref_vertex(egg_vertex, membership);
537 }
538 }
539
540 // And the morphs.
541 MorphTable::const_iterator mti;
542 for (mti = _dxyz_table.begin(); mti != _dxyz_table.end(); ++mti) {
543 const string &morph_name = (*mti).first;
544 int d = (*mti).second;
545
546 LVector3 delta;
547 _nurbs_result->eval_extended_points(u, v, d, &delta[0], 3);
548 if (!delta.almost_equal(LVector3::zero())) {
549 egg_vertex->_dxyzs.insert(EggMorphVertex(morph_name, LCAST(double, delta)));
550 }
551 }
552
553 for (mti = _drgba_table.begin(); mti != _drgba_table.end(); ++mti) {
554 const string &morph_name = (*mti).first;
555 int d = (*mti).second;
556
557 LVector4 delta;
558 _nurbs_result->eval_extended_points(u, v, d, &delta[0], 4);
559 if (!delta.almost_equal(LVector4::zero())) {
560 egg_vertex->_drgbas.insert(EggMorphColor(morph_name, delta));
561 }
562 }
563
564 return egg_vertex;
565}
A comment that appears in an egg file within a <Comment> entry.
Definition eggComment.h:24
A base class for nodes in the hierarchy that are not leaf nodes.
EggNode * add_child(EggNode *node)
Adds the indicated child to the group and returns it.
The main glue of the egg hierarchy, this corresponds to the <Group>, <Instance>, and <Joint> type nod...
Definition eggGroup.h:34
void ref_vertex(EggVertex *vert, double membership=1.0)
Adds the vertex to the set of those referenced by the group, at the indicated membership level.
Definition eggGroup.cxx:608
double get_vertex_membership(const EggVertex *vert) const
Returns the amount of membership of the indicated vertex in this group.
Definition eggGroup.cxx:677
std::pair< iterator, bool > insert(const MorphType &value)
This is similar to the insert() interface for sets, except it does not guarantee that the resulting l...
A single <Dxyz> or <Duv> or some such entry.
Definition eggMorph.h:30
A base class for things that may be directly added into the egg hierarchy.
Definition eggNode.h:36
A parametric NURBS surface.
bool is_closed_u() const
Returns true if the surface appears to be closed in the U direction.
bool is_closed_v() const
Returns true if the surface appears to be closed in the V direction.
A single polygon.
Definition eggPolygon.h:24
A base class for any of a number of kinds of geometry primitives: polygons, point lights,...
EggVertex * add_vertex(EggVertex *vertex)
Adds the indicated vertex to the end of the primitive's list of vertices, and returns it.
int get_v_subdiv() const
Returns the requested number of subdivisions in the U direction, or 0 if no particular subdivisions h...
Definition eggSurface.I:82
int get_u_subdiv() const
Returns the requested number of subdivisions in the U direction, or 0 if no particular subdivisions h...
Definition eggSurface.I:62
A collection of vertices.
Any one-, two-, three-, or four-component vertex, possibly with attributes such as a normal.
Definition eggVertex.h:39
PrimitiveRef::const_iterator pref_begin() const
Returns an iterator that can, in conjunction with pref_end(), be used to traverse the entire set of p...
PrimitiveRef::const_iterator pref_end() const
Returns an iterator that can, in conjunction with pref_begin(), be used to traverse the entire set of...
GroupRef::const_iterator gref_end() const
Returns an iterator that can, in conjunction with gref_begin(), be used to traverse the entire set of...
void set_pos(double pos)
Sets the vertex position.
Definition eggVertex.I:42
GroupRef::const_iterator gref_begin() const
Returns an iterator that can, in conjunction with gref_end(), be used to traverse the entire set of g...
LVertexd get_pos3() const
Valid if get_num_dimensions() returns 3 or 4.
Definition eggVertex.I:131
void set_uv(const LTexCoordd &texCoord)
Replaces the unnamed UV coordinate pair on the vertex with the indicated value.
Definition eggVertex.I:193
static void output_extra(std::ostream &out, const pvector< double > &iso, char axis)
This function is used to identify the extra isoparams in the list added by user control.
A reference to an EggNurbsSurface in the egg file, and its parameters as set by the user input file a...
int count_tris() const
Returns the number of triangles that will be generated by the current tesselation parameters.
double get_score(double ratio)
Computes the curvature/stretch score for the surface, if it has not been already computed,...
void tesselate_per_isoparam(double pi, bool autoplace, double ratio)
Sets the surface up to tesselate itself to a uniform amount per isoparam.
void omit()
Sets up the surface to omit itself from the output.
int write_qtess_parameter(std::ostream &out)
Writes a line to the given output file telling qtess how this surface should be tesselated uniformly.
void tesselate_auto(int u, int v, double ratio)
Sets the surface up to tesselate itself by automatically determining the best place to put the indica...
int tesselate()
Applies the appropriate tesselation to the surface, and replaces its node in the tree with an EggGrou...
void tesselate_uv(int u, int v, bool autoplace, double ratio)
Sets the surface up to tesselate itself uniformly at u x v, or if autoplace is true,...
void tesselate_per_score(double pi, bool autoplace, double ratio)
Sets the surface up to tesselate itself according to its computed curvature score in both dimensions.
void tesselate_specific(const pvector< double > &u_list, const pvector< double > &v_list)
Sets the surface up to tesselate itself at specific isoparams only.
This is our own Panda specialization on the default STL map.
Definition pmap.h:49
This is our own Panda specialization on the default STL set.
Definition pset.h:49
This is our own Panda specialization on the default STL vector.
Definition pvector.h:42
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.