Explorar o código

port old qtess utility as egg-qtess

David Rose %!s(int64=22) %!d(string=hai) anos
pai
achega
0f5e761eb8

+ 27 - 0
pandatool/src/egg-qtess/config_egg_qtess.cxx

@@ -0,0 +1,27 @@
+// Filename: config_egg_qtess.cxx
+// Created by:  drose (13Oct03)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "config_egg_qtess.h"
+
+#include "dconfig.h"
+
+Configure(config_egg_qtess);
+NotifyCategoryDef(qtess, "");
+
+ConfigureFn(config_egg_qtess) {
+}

+ 29 - 0
pandatool/src/egg-qtess/config_egg_qtess.h

@@ -0,0 +1,29 @@
+// Filename: config_egg_qtess.h
+// Created by:  drose (13Oct03)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef CONFIG_EGG_QTESS_H
+#define CONFIG_EGG_QTESS_H
+
+#include "pandatoolbase.h"
+#include "notifyCategoryProxy.h"
+
+NotifyCategoryDeclNoExport(qtess);
+
+// No variables to declare here.
+
+#endif

+ 355 - 0
pandatool/src/egg-qtess/eggQtess.cxx

@@ -0,0 +1,355 @@
+// Filename: eggQtess.cxx
+// Created by:  drose (13Oct03)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "eggQtess.h"
+#include "qtessGlobals.h"
+#include "dcast.h"
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggQtess::Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+EggQtess::
+EggQtess() {
+  add_normals_options();
+
+  set_program_description
+    ("egg-qtess reads an egg file, tesselates all of its NURBS surfaces "
+     "using a simple uniform tesselation, and outputs a polygonal "
+     "egg file.\n\n"
+
+     "Characters are supported, soft-skinned and otherwise; joint "
+     "ownership is computed correctly for each new polygon vertex.  "
+     "Primitives other than NURBS surfaces appearing in the egg file "
+     "are unaffected.");
+
+  add_option
+    ("f", "filename", 0,
+     "Read the indicated qtess-style parameter file.  Type qtess -H "
+     "to print a description of the qtess-style format.",
+     &EggQtess::dispatch_filename, NULL, &_qtess_filename);
+
+  add_option
+    ("up", "subdiv", 0,
+     "Specify a uniform subdivision per patch (isoparm).  Each NURBS "
+     "surface is made up of N x M patches, each of which is divided "
+     "into subdiv x subdiv quads.  A fractional number is allowed.",
+     &EggQtess::dispatch_double, NULL, &_uniform_per_isoparm);
+
+  add_option
+    ("us", "subdiv", 0,
+     "Specify a uniform subdivision per surface.  Each NURBS "
+     "surface is subdivided into subdiv x subdiv quads, regardless "
+     "of the number of isoparms it has.  A fractional number is "
+     "meaningless.",
+     &EggQtess::dispatch_int, NULL, &_uniform_per_surface);
+
+  add_option
+    ("t", "tris", 0,
+     "Specify an approximate number of triangles to produce.  This "
+     "is the total number of triangles for the entire egg file, "
+     "including those surfaces that have already been given an "
+     "explicit tesselation by a parameter file.",
+     &EggQtess::dispatch_int, NULL, &_total_tris);
+
+  add_option
+    ("ap", "", 0,
+     "Attempt to automatically place tesselation lines where they'll "
+     "do the most good on each surface (once the number of polygons "
+     "for the surface has already been determined).",
+     &EggQtess::dispatch_none, &QtessGlobals::_auto_place);
+
+  add_option
+    ("ad", "", 0,
+     "Attempt to automatically distribute polygons among the surfaces "
+     "where they are most needed according to curvature and size, "
+     "instead of according to the number of isoparms.  This only has "
+     "meaning when used in conjunction with -t.",
+     &EggQtess::dispatch_none, &QtessGlobals::_auto_distribute);
+
+  add_option
+    ("ar", "ratio", 0,
+     "Specify the ratio of dominance of size to curvature for -ap and "
+     "-ad.  A value of 0 forces placement by curvature only; a very "
+     "large value (like 1000) forces placement by size only.  The "
+     "default is 5.0.",
+     &EggQtess::dispatch_double, NULL, &QtessGlobals::_curvature_ratio);
+
+  add_option
+    ("e", "", 0,
+     "Respect subdivision parameters given in the egg file.  If this "
+     "is specified, the egg file may define the effective number of "
+     "patches of each NURBS entry.  This can be used alone or in "
+     "conjunction with -u or -t to fine-tune the uniform tesselation "
+     "on a per-surface basis.  (This is ignored if -ad is in effect.)",
+     &EggQtess::dispatch_none, &QtessGlobals::_respect_egg);
+
+  add_option
+    ("q", "", 0,
+     "Instead of writing an egg file, generate a qtess-style input file "
+     "for output.",
+     &EggQtess::dispatch_none, &_qtess_output);
+
+  add_option
+    ("H", "", 0,
+     "Describe the format of the qtess parameter file specified with -f.",
+     &EggQtess::dispatch_none, &_describe_qtess);
+
+  _uniform_per_isoparm = 0.0;
+  _uniform_per_surface = 0;
+  _total_tris = 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggQtess::handle_args
+//       Access: Protected, Virtual
+//  Description: Does something with the additional arguments on the
+//               command line (after all the -options have been
+//               parsed).  Returns true if the arguments are good,
+//               false otherwise.
+////////////////////////////////////////////////////////////////////
+bool EggQtess::
+handle_args(ProgramBase::Args &args) {
+  if (_describe_qtess) {
+    describe_qtess_format();
+    exit(0);
+  }
+
+  return EggFilter::handle_args(args);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggQtess::run
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+void EggQtess::
+run() {
+  bool read_qtess = false;
+  if (!_qtess_filename.empty()) {
+    if (!_qtess_file.read(_qtess_filename)) {
+      exit(1);
+    }
+    read_qtess = true;
+  }
+
+  find_surfaces(&_data);
+
+  QtessInputEntry &default_entry = _qtess_file.get_default_entry();
+  if (!read_qtess || default_entry.get_num_surfaces() == 0) {
+    nout << _surfaces.size() << " NURBS surfaces found.\n";
+
+  } else {
+    nout << _surfaces.size() << " NURBS surfaces found; "
+	 << default_entry.get_num_surfaces()
+         << " unaccounted for by input file.\n";
+  }
+
+  int num_tris = _qtess_file.count_tris();
+
+  if (_total_tris != 0) {
+    // Whatever number of triangles we have unaccounted for, assign to
+    // the default bucket.
+    int extra_tris = max(0, _total_tris - num_tris);
+    if (read_qtess && default_entry.get_num_surfaces() != 0) {
+      cerr << extra_tris << " triangles unaccounted for.\n";
+    }
+
+    default_entry.set_num_tris(extra_tris);
+
+  } else if (_uniform_per_isoparm!=0.0) {
+    default_entry.set_per_isoparm(_uniform_per_isoparm);
+
+  } else if (_uniform_per_surface!=0.0) {
+    default_entry.set_uv(_uniform_per_surface, _uniform_per_surface);
+
+  } else {
+    default_entry.set_per_isoparm(1.0);
+  }
+
+  default_entry.count_tris();
+
+  if (_qtess_output) {
+    // Sort the names into alphabetical order for aesthetics.
+    //sort(_surfaces.begin(), _surfaces.end(), compare_surfaces());
+
+    int tris = 0;
+
+    ostream &out = get_output();
+    Surfaces::const_iterator si;
+    for (si = _surfaces.begin(); si != _surfaces.end(); ++si) {
+      tris += (*si)->write_qtess_parameter(out);
+    }
+    
+    cerr << tris << " tris generated.\n";
+
+  } else {
+
+    int tris = 0;
+
+    Surfaces::const_iterator si;
+    for (si = _surfaces.begin(); si != _surfaces.end(); ++si) {
+      tris += (*si)->tesselate();
+    }
+    
+    cerr << tris << " tris generated.\n";
+
+    // Clear out the surfaces list before removing the vertices, since
+    // each surface is holding reference counts to the previously-used
+    // vertices.
+    _surfaces.clear();
+
+    _data.remove_unused_vertices();
+    write_egg_file();
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggQtess::describe_qtess_format
+//       Access: Private
+//  Description:
+////////////////////////////////////////////////////////////////////
+void EggQtess::
+describe_qtess_format() {
+  nout <<
+    "A qtess-style parameter file consists of lines of the form:\n\n"
+
+    "name [name...] : parameters\n\n"
+
+    "Where name is a string (possibly including wildcard characters "
+    "such as * and ?) that matches one or more surface "
+    "names, and parameters is a tesselation specification, described below. "
+    "The colon must be followed by at least one space to differentiate it "
+    "from a colon character in the name(s).  Multiple names "
+    "may be combined on one line.\n\n\n"
+
+
+    "The parameters may be any of the following.  Lowercase letters are "
+    "literal.  NUM is any number.\n\n";
+
+  show_text("  omit", 10,
+            "Remove the surface from the output.\n\n");
+
+  show_text("  NUM", 10,
+            "Try to achieve the indicated number of triangles over all the "
+            "surfaces matched by this line.\n\n");
+  
+  show_text("  NUM NUM [[!]u# [!]u# ...] [[!]v# [!]v# ...]", 10,
+            "Tesselate to NUM x NUM quads.  If u# or v# appear, they indicate "
+            "additional isoparms to insert (or remove if preceded by an "
+            "exclamation point).  The range is [0, 1].\n\n");
+
+  show_text("  iNUM", 10,
+            "Subdivision amount per isoparm.  Equivalent to the command-line "
+            "option -u NUM.\n\n");
+  
+  show_text("  NUM%", 10,
+            "This is a special parameter.  This does not request any specific "
+            "tesselation for the named surfaces, but instead gives a relative "
+            "importance for them when they appear with other surfaces in a "
+            "later entry (or are tesselated via -t on the command line).  In "
+            "general, a surface with a weight of 25% will be given a quarter "
+            "of the share of the polygons it otherwise would have received; "
+            "a weight of 150% will give the surface 50% more than its fair "
+            "share.\n\n");
+
+  show_text("  matchvu", 10,
+            "This is a special parameter that indicates that two or more "
+            "surfaces share a common edge, and must be tesselated the "
+            "same way "
+            "along that edge.  Specifically, matchvu means that the V "
+            "tesselation of the first named surface will be applied to the U "
+            "tesselation of the second (and later) named surface(s).  Similar "
+            "definitions exist for matchuv, matchuu, and matchvv.\n\n");
+
+  show_text("  minu NUM", 10,
+            "This is another special parameter that specifies a "
+            "minimum tesselation for all these surfaces in "
+            "the U direction.  This is "
+            "the number of quads across the dimension the surface will be "
+            "broken into.  The default is 1 for an open surface, and 3 for "
+            "a closed surface.\n\n");
+
+  show_text("  minv NUM", 10,
+            "Similar to minv, in the V direction.\n\n");
+
+  nout <<
+    "In addition, the following optional parameters may appear.  If they appear, "
+    "they override similar parameters given on the command line; if they do not "
+    "appear, the defaults are taken from the command line:\n\n";
+
+  show_text("  ap", 10,
+            "Automatically place tesselation lines on each surface where they "
+            "seem to be needed most.\n\n");
+
+  show_text("  !ap", 10,
+            "Do not move lines automatically; use a strict uniform "
+            "tesselation.\n\n");
+
+  show_text("  ad", 10,
+            "Automatically distribute polygons to the surfaces that seem to "
+            "need them the most.\n\n");
+
+  show_text("  !ad", 10,
+            "Do not automatically distribute polygons; distribute "
+            "them according to the number of isoparms of each surface.\n\n");
+
+  show_text("  arNUM", 10,
+            "Specify the ratio of dominance of size to curvature.\n\n");
+
+  nout <<
+    "The hash symbol '#' begins a comment if it is preceded by whitespace or at the "
+    "beginning of a line.  The backslash character at the end of a line can be used "
+    "to indicate a continuation.\n\n";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggQtess::find_surfaces
+//       Access: Private
+//  Description: Recursively walks the egg graph, collecting all the
+//               NURBS surfaces found.
+////////////////////////////////////////////////////////////////////
+void EggQtess::
+find_surfaces(EggNode *egg_node) {
+  if (egg_node->is_of_type(EggNurbsSurface::get_class_type())) {
+    PT(QtessSurface) surface = 
+      new QtessSurface(DCAST(EggNurbsSurface, egg_node));
+    if (surface->is_valid()) {
+      _surfaces.push_back(surface);
+      QtessInputEntry::Type match_type = _qtess_file.match(surface);
+      nassertv(match_type != QtessInputEntry::T_undefined);
+    }
+  }
+
+  if (egg_node->is_of_type(EggGroupNode::get_class_type())) {
+    EggGroupNode *egg_group = DCAST(EggGroupNode, egg_node);
+    EggGroupNode::const_iterator ci;
+    for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) {
+      find_surfaces(*ci);
+    }
+  }
+}
+
+int main(int argc, char *argv[]) {
+  EggQtess prog;
+  prog.parse_command_line(argc, argv);
+  prog.run();
+  return 0;
+}
+

+ 63 - 0
pandatool/src/egg-qtess/eggQtess.h

@@ -0,0 +1,63 @@
+// Filename: eggQtess.h
+// Created by:  drose (13Oct03)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef EGGQTESS_H
+#define EGGQTESS_H
+
+#include "pandatoolbase.h"
+#include "eggFilter.h"
+#include "qtessInputFile.h"
+#include "qtessSurface.h"
+#include "pointerTo.h"
+#include "pvector.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : EggQtess
+// Description : A program to tesselate NURBS surfaces appearing
+//               within an egg file into polygons, using variations on
+//               a quick uniform tesselation.
+////////////////////////////////////////////////////////////////////
+class EggQtess : public EggFilter {
+public:
+  EggQtess();
+
+  void run();
+
+protected:
+  virtual bool handle_args(ProgramBase::Args &args);
+
+private:
+  void describe_qtess_format();
+  void find_surfaces(EggNode *egg_node);
+
+  Filename _qtess_filename;
+  double _uniform_per_isoparm;
+  int _uniform_per_surface;
+  int _total_tris;
+  bool _qtess_output;
+  bool _describe_qtess;
+
+  QtessInputFile _qtess_file;
+  
+  typedef pvector< PT(QtessSurface) > Surfaces;
+  Surfaces _surfaces;
+};
+
+#endif
+
+

+ 38 - 0
pandatool/src/egg-qtess/isoPlacer.I

@@ -0,0 +1,38 @@
+// Filename: isoPlacer.I
+// Created by:  drose (13Oct03)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: IsoPlacer::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE IsoPlacer::
+IsoPlacer() {
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: IsoPlacer::get_total_score
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE double IsoPlacer::
+get_total_score() const {
+  return _cint[_maxi];
+}

+ 242 - 0
pandatool/src/egg-qtess/isoPlacer.cxx

@@ -0,0 +1,242 @@
+// Filename: isoPlacer.cxx
+// Created by:  drose (13Oct03)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "isoPlacer.h"
+#include "qtessSurface.h"
+#include "subdivSegment.h"
+#include "nurbsSurfaceResult.h"
+#include "pvector.h"
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: IsoPlacer::get_scores
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void IsoPlacer::
+get_scores(int subdiv, int across, double ratio,
+           NurbsSurfaceResult *surf, bool s) {
+  _maxi = subdiv - 1;
+
+  _cscore.clear();
+  _sscore.clear();
+
+  _cscore.reserve(_maxi);
+  _sscore.reserve(_maxi);
+
+  // First, tally up the curvature and stretch scores across the
+  // surface.
+  int i = 0;
+  for (i = 0; i < _maxi; i++) {
+    _cscore.push_back(0.0);
+    _sscore.push_back(0.0);
+  }
+
+  int a;
+  for (a = 0; a <= across; a++) {
+    double v = (double)a / (double)across;
+
+    LVecBase3f p1, p2, p3, pnext;
+    LVecBase3f v1, v2;
+    if (s) {
+      surf->eval_point(0.0, v, p3);
+    } else {
+      surf->eval_point(v, 0.0, p3);
+    }
+    int num_points = 1;
+
+    for (i = -1; i < _maxi; i++) {
+      double u = (double)(i+1) / (double)(_maxi+1);
+      if (s) {
+	surf->eval_point(u, v, pnext);
+      } else {
+	surf->eval_point(v, u, pnext);
+      }
+
+      // We'll ignore consecutive equal points.  They don't contribute
+      // to curvature or size.
+      if (!pnext.almost_equal(p3)) {
+	num_points++;
+	p1 = p2;
+	p2 = p3;
+	p3 = pnext;
+
+	v1 = v2;
+	v2 = p3 - p2;
+	double vlength = length(v2);
+	v2 /= vlength;
+
+	if (i >= 0) {
+	  _sscore[i] += vlength;
+	}
+
+	if (num_points >= 3) {
+	  // We only have a meaningful v1, v2 when we've read at least
+	  // three non-equal points.
+	  double d = v1.dot(v2);
+	  
+	  _cscore[i] += acos(max(min(d, 1.0), -1.0));
+	}
+      }
+    }
+  }
+
+  // Now integrate.
+  _cint.clear();
+  _cint.reserve(_maxi + 1);
+
+  double net = 0.0;
+  double ad = (double)(across+1);
+  _cint.push_back(0.0);
+  for (i = 0; i < _maxi; i++) {
+    net += _cscore[i]/ad + ratio * _sscore[i]/ad;
+    _cint.push_back(net);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: IsoPlacer::place
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void IsoPlacer::
+place(int count, pvector<double> &iso_points) {
+  int i;
+
+  // Count up the average curvature.
+  double avg_curve = 0.0;
+  for (i = 0; i < _maxi; i++) {
+    avg_curve += _cscore[i];
+  }
+  avg_curve /= (double)_maxi;
+
+  // Find all the local maxima in the curvature table.  These are bend
+  // points.
+  typedef pvector<int> BendPoints;
+  BendPoints bpoints;
+  BendPoints::iterator bi, bnext;
+
+  typedef pvector<SubdivSegment> Segments;
+  Segments segments;
+  Segments::iterator si;
+
+  /*
+  // Having problems with bend points right now.  Maybe this is just a
+  // bad idea.  It seems to work pretty well without them, anyway.
+  for (i = 1; i < _maxi-1; i++) {
+    // A point must be measurably higher than both its neighbors, as
+    // well as at least 50% more curvy than the average curvature, to
+    // qualify as a bend point.
+    if (_cscore[i] > _cscore[i-1]+0.001 && 
+	_cscore[i] > _cscore[i+1]+0.001 &&
+	_cscore[i] > 1.5 * avg_curve) {
+      bpoints.push_back(i);
+    }
+  }
+  */
+
+  // Now make sure there aren't any two bend points closer together
+  // than maxi/count.  If there are, remove the smaller of the two.
+  bi = bpoints.begin();
+  int min_separation = _maxi/count;
+  while (bi != bpoints.end()) {
+    bnext = bi;
+    ++bnext;
+
+    if (bnext != bpoints.end() && (*bnext) - (*bi) < min_separation) {
+      // Too close.  Remove one.
+      if (_cscore[*bnext] > _cscore[*bi]) {
+	*bi = *bnext;
+      }
+      bpoints.erase(bnext);
+    } else {
+      // Not too close; keep going;
+      bi = bnext;
+    }
+  }
+
+  // Now, if we have fewer total subdivisions than bend points, then
+  // remove the smallest bend points.
+  while (count - 1 < (int)bpoints.size()) {
+    bi = bpoints.begin();
+    BendPoints::iterator mi = bi;
+    for (++bi; bi != bpoints.end(); ++bi) {
+      if (_cscore[*bi] < _cscore[*mi]) {
+	mi = bi;
+      }
+    }
+    bpoints.erase(mi);
+  }
+
+  // Now all the remaining bend points are valid.
+  bi = bpoints.begin();
+  int last = 0;
+  for (bi = bpoints.begin(); bi != bpoints.end(); ++bi) {
+    segments.push_back(SubdivSegment(&_cint[0], last, *bi));
+    last = *bi;
+  }
+  segments.push_back(SubdivSegment(&_cint[0], last, _maxi));
+
+  int nr = count - segments.size();
+
+  // Now we have subdivided the curve into a number of smaller curves
+  // at the bend points.  We still have nr remaining cuts to make;
+  // distribute these cuts among the curves evenly according to
+  // score.
+
+  // Divvy out the extra cuts.  First, each segment gets an amount
+  // proportional to its score.
+  double net_score = _cint[_maxi];
+  nassertv(net_score > 0.0);
+  int ns = 0;
+  for (si = segments.begin(); si != segments.end(); ++si) {
+    (*si)._num_cuts = (int)floor(nr * (*si).get_score() / net_score);
+    nassertv((*si)._num_cuts <= nr);  // This fails if net_score is nan.
+    ns += (*si)._num_cuts;
+  }
+
+  // Then, assign the remaining cuts to the neediest segments.
+  nr -= ns;
+  while (nr > 0) {
+    si = min_element(segments.begin(), segments.end());
+    (*si)._num_cuts++;
+    nr--;
+  }
+
+  // Now cut up the segments as indicated.
+  for (si = segments.begin(); si != segments.end(); ++si) {
+    (*si).cut();
+  }
+
+  // Finally, return the result.
+  iso_points.erase(iso_points.begin(), iso_points.end());
+
+  iso_points.push_back(0.0);
+  for (si = segments.begin(); si != segments.end(); ++si) {
+    pvector<int>::iterator ci;
+    for (ci = (*si)._cuts.begin(); ci != (*si)._cuts.end(); ++ci) {
+      iso_points.push_back((*ci+1) / (double)(_maxi+1));
+    }
+    iso_points.push_back(((*si)._t+1) / (double)(_maxi+1));
+  }
+
+  // Oh, wait.  The last segment is actually drawn all the way to 1.
+  iso_points.back() = 1.0;
+}
+    
+

+ 50 - 0
pandatool/src/egg-qtess/isoPlacer.h

@@ -0,0 +1,50 @@
+// Filename: isoPlacer.h
+// Created by:  drose (13Oct03)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef ISOPLACER_H
+#define ISOPLACER_H
+
+#include "pandatoolbase.h"
+#include "pvector.h"
+#include "vector_double.h"
+
+class NurbsSurfaceResult;
+
+////////////////////////////////////////////////////////////////////
+//       Class : IsoPlacer
+// Description : Contains the logic used to place isoparms where
+//               they'll do the most good on a surface.
+////////////////////////////////////////////////////////////////////
+class IsoPlacer {
+public:
+  INLINE IsoPlacer();
+
+  void get_scores(int subdiv, int across, double ratio,
+                  NurbsSurfaceResult *surf, bool s);
+  void place(int count, pvector<double> &iso_points);
+
+  INLINE double get_total_score() const;
+
+  vector_double _cscore, _sscore, _cint;
+  int _maxi;
+};
+
+#include "isoPlacer.I"
+
+#endif
+

+ 24 - 0
pandatool/src/egg-qtess/qtessGlobals.cxx

@@ -0,0 +1,24 @@
+// Filename: qtessGlobals.cxx
+// Created by:  drose (13Oct03)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "qtessGlobals.h"
+
+bool QtessGlobals::_auto_place = false;
+bool QtessGlobals::_auto_distribute = false;
+double QtessGlobals::_curvature_ratio = 5.0;
+bool QtessGlobals::_respect_egg = false;

+ 39 - 0
pandatool/src/egg-qtess/qtessGlobals.h

@@ -0,0 +1,39 @@
+// Filename: qtessGlobals.h
+// Created by:  drose (13Oct03)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef QTESS_GLOBALS_H
+#define QTESS_GLOBALS_H
+
+#include "pandatoolbase.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : QtessGlobals
+// Description : Simply used as a namespace to scope some global
+//               variables for this program, set from the command
+//               line.
+////////////////////////////////////////////////////////////////////
+class QtessGlobals {
+public:
+  static bool _auto_place;
+  static bool _auto_distribute;
+  static double _curvature_ratio;
+  static bool _respect_egg;
+};
+
+#endif
+

+ 195 - 0
pandatool/src/egg-qtess/qtessInputEntry.I

@@ -0,0 +1,195 @@
+// Filename: qtessInputEntry.I
+// Created by:  drose (13Oct03)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessInputEntry::Copy Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE QtessInputEntry::
+QtessInputEntry(const QtessInputEntry &copy) {
+  (*this) = copy;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessInputEntry::add_node_name
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void QtessInputEntry::
+add_node_name(const string &name) {
+  _node_names.push_back(GlobPattern(name));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessInputEntry::set_importance
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void QtessInputEntry::
+set_importance(double i) {
+  _importance = i;
+  _type = T_importance;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessInputEntry::set_match_uu
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void QtessInputEntry::
+set_match_uu() {
+  _type = T_match_uu;
+  _constrain_u = NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessInputEntry::set_match_vv
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void QtessInputEntry::
+set_match_vv() {
+  _type = T_match_vv;
+  _constrain_v = NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessInputEntry::set_match_uv
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void QtessInputEntry::
+set_match_uv() {
+  _type = T_match_uv;
+  _constrain_u = NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessInputEntry::set_match_vu
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void QtessInputEntry::
+set_match_vu() {
+  _type = T_match_vu;
+  _constrain_v = NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessInputEntry::set_min_u
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void QtessInputEntry::
+set_min_u(int min_u) {
+  _type = T_min_u;
+  _num_u = min_u;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessInputEntry::set_min_v
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void QtessInputEntry::
+set_min_v(int min_v) {
+  _type = T_min_v;
+  _num_v = min_v;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessInputEntry::set_undefined
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void QtessInputEntry::
+set_undefined() {
+  _type = T_undefined;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessInputEntry::set_omit
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void QtessInputEntry::
+set_omit() {
+  _type = T_omit;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessInputEntry::set_num_tris
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void QtessInputEntry::
+set_num_tris(int nt) {
+  _num_tris = nt;
+  _type = T_num_tris;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessInputEntry::set_uv
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void QtessInputEntry::
+set_uv(int u, int v) {
+  set_uv(u, v, NULL, 0);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessInputEntry::set_per_isoparm
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void QtessInputEntry::
+set_per_isoparm(double pi) {
+  _per_isoparm = pi;
+  _type = T_per_isoparm;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessInputEntry::set_per_score
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void QtessInputEntry::
+set_per_score(double pi) {
+  _per_isoparm = pi;
+  _type = T_per_score;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessInputEntry::get_num_surfaces
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE int QtessInputEntry::
+get_num_surfaces() const {
+  return _surfaces.size();
+}
+
+
+INLINE ostream &operator << (ostream &out, const QtessInputEntry &entry) {
+  entry.output(out);
+  return out;
+}
+

+ 497 - 0
pandatool/src/egg-qtess/qtessInputEntry.cxx

@@ -0,0 +1,497 @@
+// Filename: qtessInputEntry.cxx
+// Created by:  drose (13Oct03)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "qtessInputEntry.h"
+#include "qtessSurface.h"
+#include "qtessGlobals.h"
+#include "config_egg_qtess.h"
+#include "ctype.h"
+#include "indent.h"
+#include "string_utils.h"
+
+#include <algorithm>
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessInputEntry::Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+QtessInputEntry::
+QtessInputEntry(const string &name) {
+  _type = T_undefined;
+  _num_patches = 0.0;
+  _auto_place = QtessGlobals::_auto_place;
+  _auto_distribute = QtessGlobals::_auto_distribute;
+  _curvature_ratio = QtessGlobals::_curvature_ratio;
+  if (!name.empty()) {
+    add_node_name(name);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessInputEntry::Copy Assignment Operator
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+void QtessInputEntry::
+operator = (const QtessInputEntry &copy) {
+  _node_names = copy._node_names;
+  _type = copy._type;
+  _num_tris = copy._num_tris;
+  _num_u = copy._num_u;
+  _num_v = copy._num_v;
+  _per_isoparm = copy._per_isoparm;
+  _iso_u = copy._iso_u;
+  _iso_v = copy._iso_v;
+  _surfaces = copy._surfaces;
+  _num_patches = copy._num_patches;
+  _auto_place = copy._auto_place;
+  _auto_distribute = copy._auto_distribute;
+  _curvature_ratio = copy._curvature_ratio;
+  _importance = copy._importance;
+  _constrain_u = copy._constrain_u;
+  _constrain_v = copy._constrain_v;
+}
+
+////////////////////////////////////////////////////////////////////
+//       Class : DoublesAlmostEqual
+// Description : An STL function object to determine if two doubles
+//               are very nearly equal.  Used in set_uv(), below.
+////////////////////////////////////////////////////////////////////
+class DoublesAlmostEqual {
+public:
+  int operator ()(double a, double b) const {
+    return fabs(a - b) < 0.00001;
+  }
+};
+
+////////////////////////////////////////////////////////////////////
+//       Class : DoubleAlmostMatches
+// Description : An STL function object to determine if a double
+//               is vert nearly equal the supplied value .  Used in
+//               set_uv(), below.
+////////////////////////////////////////////////////////////////////
+class DoubleAlmostMatches {
+public:
+  DoubleAlmostMatches(double v) : _v(v) {}
+  int operator ()(double a) const {
+    return fabs(a - _v) < 0.00001;
+  }
+  double _v;
+};
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessInputEntry::set_uv
+//       Access: Public
+//  Description: Sets specific tesselation.  The tesselation will be u
+//               by v quads, with the addition of any isoparms
+//               described in the list of parms.
+////////////////////////////////////////////////////////////////////
+void QtessInputEntry::
+set_uv(int u, int v, const string parms[], int num_parms) {
+  _num_u = u;
+  _num_v = v;
+
+  // First, fill up the arrays with the defaults.
+  int i;
+  for (i = 0; i <= _num_u; i++) {
+    _iso_u.push_back(i);
+  }
+  for (i = 0; i <= _num_v; i++) {
+    _iso_v.push_back(i);
+  }
+
+  // Then get out all the additional entries.
+  for (i = 0; i < num_parms; i++) {
+    const string &parm = parms[i];
+
+    if (parm[0] == '!' && parm.size() > 2) {
+      double value;
+      if (!string_to_double(parm.substr(2), value)) {
+        qtess_cat.warning()
+          << "Ignoring invalid parameter: " << parm << "\n";
+      } else {
+        switch (tolower(parm[1])) {
+        case 'u':
+          _auto_place = false;
+          _iso_u.erase(remove_if(_iso_u.begin(), _iso_u.end(), 
+                                 DoubleAlmostMatches(value)),
+                       _iso_u.end());
+          break;
+          
+        case 'v':
+          _auto_place = false;
+          _iso_v.erase(remove_if(_iso_v.begin(), _iso_v.end(),
+                                 DoubleAlmostMatches(value)),
+                       _iso_v.end());
+          break;
+	
+        default:
+          qtess_cat.warning()
+            << "Ignoring invalid parameter: " << parms[i] << "\n";
+        }
+      }
+    } else {
+      double value;
+      if (!string_to_double(parm.substr(1), value)) {
+        qtess_cat.warning()
+          << "Ignoring invalid parameter: " << parm << "\n";
+      } else {
+        switch (tolower(parm[0])) {
+        case 'u':
+          _auto_place = false;
+          _iso_u.push_back(value);
+          break;
+          
+        case 'v':
+          _auto_place = false;
+          _iso_v.push_back(value);
+          break;
+          
+        default:
+          qtess_cat.warning()
+            << "Ignoring invalid parameter: " << parms[i] << "\n";
+        }
+      }
+    }
+  }
+
+  // Now sort them into ascending order and remove duplicates.
+  sort(_iso_u.begin(), _iso_u.end());
+  sort(_iso_v.begin(), _iso_v.end());
+  _iso_u.erase(unique(_iso_u.begin(), _iso_u.end(), DoublesAlmostEqual()), _iso_u.end());
+  _iso_v.erase(unique(_iso_v.begin(), _iso_v.end(), DoublesAlmostEqual()), _iso_v.end());
+
+  _type = T_uv;
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessInputEntry::add_extra_u_isoparm
+//       Access: Public
+//  Description: May be called a number of times before set_uv() to add
+//               specific additional isoparms to the tesselation.
+////////////////////////////////////////////////////////////////////
+void QtessInputEntry::
+add_extra_u_isoparm(double u) {
+  _iso_u.push_back(u);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessInputEntry::add_extra_v_isoparm
+//       Access: Public
+//  Description: May be called a number of times before set_uv() to add
+//               specific additional isoparms to the tesselation.
+////////////////////////////////////////////////////////////////////
+void QtessInputEntry::
+add_extra_v_isoparm(double v) {
+  _iso_v.push_back(v);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessInputEntry::match
+//       Access: Public
+//  Description: Tests the surface to see if it matches any of the
+//               regular expressions that define this node entry.  If
+//               so, adds it to the set of matched surfaces and
+//               returns the type of the matching entry.  If no match
+//               is found, returns T_undefined.
+////////////////////////////////////////////////////////////////////
+QtessInputEntry::Type QtessInputEntry::
+match(QtessSurface *surface) {
+  const string &name = surface->get_name();
+
+  NodeNames::const_iterator nni;
+  for (nni = _node_names.begin();
+       nni != _node_names.end();
+       ++nni) {
+    const GlobPattern &pattern = (*nni);
+    if (pattern.matches(name)) {
+      // We have a winner!
+      switch (_type) {
+      case T_importance:
+	// A type of "Importance" is a special case.  This entry
+	// doesn't specify any kind of tesselation on the surface, and
+	// in fact doesn't preclude the surface from matching anything
+	// later.  It just specifies the relative importance of the
+	// surface to all the other surfaces.
+	if (qtess_cat.is_debug()) {
+	  qtess_cat.debug() 
+            << "Assigning importance of " << _importance*100.0 
+            << "% to " << name << "\n";
+	}
+	surface->set_importance(_importance);
+	return T_undefined;
+
+      case T_match_uu:
+      case T_match_uv:
+	// Similarly for type "matchUU".  This indicates that all the
+	// surfaces that match this one must all share the
+	// U-tesselation with whichever surface first matched against
+	// the first node name.
+	if (nni == _node_names.begin() && _constrain_u==NULL) {
+	  // This is the lucky surface that dominates!
+	  _constrain_u = surface;
+	} else {
+	  if (_type == T_match_uu) {
+	    surface->set_match_u(&_constrain_u, true);
+	  } else {
+	    surface->set_match_v(&_constrain_u, false);
+	  }
+	}
+	return T_undefined;
+
+      case T_match_vv:
+      case T_match_vu:
+	// Ditto for "matchVV".
+	if (nni == _node_names.begin() && _constrain_v==NULL) {
+	  // This is the lucky surface that dominates!
+	  _constrain_v = surface;
+	} else {
+	  if (_type == T_match_vv) {
+	    surface->set_match_v(&_constrain_v, true);
+	  } else {
+	    surface->set_match_u(&_constrain_v, false);
+	  }
+	}
+	return T_undefined;
+
+      case T_min_u:
+	// And for min U and V.
+	if (qtess_cat.is_debug()) {
+	  qtess_cat.debug()
+            << "Assigning minimum of " << _num_u << " in U to "
+            << name << "\n";
+	}
+	surface->set_min_u(_num_u);
+	return T_undefined;
+
+      case T_min_v:
+	if (qtess_cat.is_debug()) {
+	  qtess_cat.debug()
+            << "Assigning minimum of " << _num_v << " in V to "
+            << name << "\n";
+	}
+	surface->set_min_v(_num_v);
+	return T_undefined;
+
+      default:
+	_surfaces.push_back(surface);
+	if (_auto_distribute) {
+	  _num_patches += surface->get_score(_curvature_ratio);
+	} else {
+	  _num_patches += surface->count_patches();
+	}
+	return _type;
+      }
+    }
+  }
+
+  return T_undefined;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessInputEntry::count_tris
+//       Access: Public
+//  Description: Determines the tesselation u,v amounts of each
+//               attached surface, and stores this information in the
+//               surface pointer.  Returns the total number of tris
+//               that will be produced.
+////////////////////////////////////////////////////////////////////
+int QtessInputEntry::
+count_tris(double tri_factor, int attempts) {
+  int total_tris = 0;
+  bool aim_for_tris = false;
+
+  if (_type == T_num_tris && _num_patches > 0.0) {
+    // If we wanted to aim for a particular number of triangles for
+    // the group, choose a per-isoparm setting that will approximately
+    // achieve this.
+    if (_auto_distribute) {
+      set_per_score(sqrt(0.5 * (double)_num_tris / _num_patches / tri_factor));
+    } else {
+      set_per_isoparm(sqrt(0.5 * (double)_num_tris / _num_patches / tri_factor));
+    }
+    aim_for_tris = true;
+  }
+
+  Surfaces::iterator si;
+  for (si = _surfaces.begin(); si != _surfaces.end(); ++si) {
+    QtessSurface *surface = (*si);
+
+    switch (_type) {
+    case T_undefined:
+    case T_omit:
+      surface->omit();
+      break;
+      
+    case T_uv:
+      if (!_iso_u.empty() && !_iso_v.empty() && !_auto_place) {
+	surface->tesselate_specific(_iso_u, _iso_v);
+      } else {
+	surface->tesselate_uv(_num_u, _num_v, _auto_place, _curvature_ratio);
+      }
+      break;
+      
+    case T_per_isoparm:
+      surface->tesselate_per_isoparm(_per_isoparm, _auto_place, _curvature_ratio);
+      break;
+      
+    case T_per_score:
+      surface->tesselate_per_score(_per_isoparm, _auto_place, _curvature_ratio);
+      break;
+
+    default:
+      break;
+    }
+
+    total_tris += surface->count_tris();
+  }
+
+  if (aim_for_tris && attempts < 10 && 
+      (double)total_tris / (double)_num_tris > 1.1) {
+    // We'd like to get within 10% of the requested number of
+    // triangles, if possible.  Keep trying until we do, or until we
+    // just need to give up.
+    set_num_tris(_num_tris);
+    return count_tris(tri_factor * total_tris / _num_tris, attempts + 1);
+  }
+
+  return total_tris;
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessInputEntry::output_extra
+//       Access: Public, Static
+//  Description: This function is used to identify the extra isoparms
+//               in the list added by user control.
+////////////////////////////////////////////////////////////////////
+void QtessInputEntry::
+output_extra(ostream &out, const pvector<double> &iso, char axis) {
+  pvector<double>::const_iterator di;
+  int expect = 0;
+  for (di = iso.begin(); di != iso.end(); ++di) {
+    while ((*di) > (double)expect) {
+      // Didn't find one we were expecting.  Omit it.
+      out << " !" << axis << expect;
+    }
+    if ((*di)==(double)expect) {
+      // Here's one we were expecting; ignore it.
+      expect++;
+    } else {
+      // Here's a new one.  Write it.
+      out << " " << axis << *di;
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessInputEntry::output
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+void QtessInputEntry::
+output(ostream &out) const {
+  NodeNames::const_iterator nni;
+  for (nni = _node_names.begin();
+       nni != _node_names.end();
+       ++nni) {
+    out << (*nni) << " ";
+  }
+  out << ": ";
+
+  bool show_auto = false;
+
+  switch (_type) {
+  case T_undefined:
+    break;
+
+  case T_omit:
+    out << "omit";
+    break;
+
+  case T_num_tris:
+    out << _num_tris;
+    show_auto = true;
+    break;
+    
+  case T_uv:
+    out << _num_u << " " << _num_v;
+    output_extra(out, _iso_u, 'u');
+    output_extra(out, _iso_v, 'v');
+    show_auto = true;
+    break;
+
+  case T_per_isoparm:
+  case T_per_score:
+    out << "i" << _per_isoparm;
+    show_auto = true;
+    break;
+
+  case T_importance:
+    out << _importance * 100.0 << "%";
+    break;
+
+  case T_match_uu:
+    out << "matchuu";
+    break;
+
+  case T_match_vv:
+    out << "matchvv";
+    break;
+
+  case T_match_uv:
+    out << "matchuv";
+    break;
+
+  case T_match_vu:
+    out << "matchvu";
+    break;
+
+  case T_min_u:
+    out << "minu " << _num_u;
+    break;
+
+  case T_min_v:
+    out << "minv " << _num_v;
+    break;
+
+  default:
+    out << "Invalid!";
+  }
+
+  if (show_auto) {
+    out << " " << (_auto_place?"":"!") << "ap"
+	<< " " << (_auto_distribute?"":"!") << "ad";
+    if (_auto_place || _auto_distribute) {
+      out << " ar" << _curvature_ratio;
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessInputEntry::output
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+void QtessInputEntry::
+write(ostream &out, int indent_level) const {
+  indent(out, indent_level) << (*this) << "\n";
+}

+ 98 - 0
pandatool/src/egg-qtess/qtessInputEntry.h

@@ -0,0 +1,98 @@
+// Filename: qtessInputEntry.h
+// Created by:  drose (13Oct03)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef QTESSINPUTENTRY_H
+#define QTESSINPUTENTRY_H
+
+#include "pandatoolbase.h"
+#include "globPattern.h"
+#include "pvector.h"
+
+class QtessSurface;
+
+////////////////////////////////////////////////////////////////////
+//       Class : QtessInputEntry
+// Description : Stores one entry in the qtess input file.  This
+//               consists of a list of name patterns and a
+//               set of tesselation parameters.
+////////////////////////////////////////////////////////////////////
+class QtessInputEntry {
+public:
+  enum Type {
+    T_undefined, T_omit, T_num_tris, T_uv, T_per_isoparm, T_per_score,
+    T_importance, T_match_uu, T_match_vv, T_match_uv, T_match_vu,
+    T_min_u, T_min_v
+  };
+
+  QtessInputEntry(const string &name = string());
+  INLINE QtessInputEntry(const QtessInputEntry &copy);
+  void operator = (const QtessInputEntry &copy);
+
+  INLINE void add_node_name(const string &name);
+  INLINE void set_importance(double i);
+  INLINE void set_match_uu();
+  INLINE void set_match_vv();
+  INLINE void set_match_uv();
+  INLINE void set_match_vu();
+  INLINE void set_min_u(int min_u);
+  INLINE void set_min_v(int min_v);
+  INLINE void set_undefined();
+  INLINE void set_omit();
+  INLINE void set_num_tris(int nt);
+  INLINE void set_uv(int u, int v);
+  void set_uv(int u, int v, const string parms[], int num_parms);
+  INLINE void set_per_isoparm(double pi);
+  INLINE void set_per_score(double pi);
+  void add_extra_u_isoparm(double u);
+  void add_extra_v_isoparm(double u);
+
+  Type match(QtessSurface *surface);
+  INLINE int get_num_surfaces() const;
+  int count_tris(double tri_factor = 1.0, int attempts = 0);
+
+  static void output_extra(ostream &out, const pvector<double> &iso, char axis);
+  void output(ostream &out) const;
+  void write(ostream &out, int indent_level) const;
+
+  bool _auto_place, _auto_distribute;
+  double _curvature_ratio;
+  double _importance;
+  QtessSurface *_constrain_u, *_constrain_v;
+
+private:
+  typedef pvector<GlobPattern> NodeNames;
+  NodeNames _node_names;
+
+  int _num_tris;
+  int _num_u, _num_v;
+  double _per_isoparm;
+  pvector<double> _iso_u, _iso_v;
+  Type _type;
+
+  typedef pvector<QtessSurface *> Surfaces;
+  Surfaces _surfaces;
+
+  double _num_patches;
+};
+
+INLINE ostream &operator << (ostream &out, const QtessInputEntry &entry);
+
+#include "qtessInputEntry.I"
+
+#endif
+

+ 39 - 0
pandatool/src/egg-qtess/qtessInputFile.I

@@ -0,0 +1,39 @@
+// Filename: qtessInputFile.I
+// Created by:  drose (13Oct03)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessInputFile::Copy Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE QtessInputFile::
+QtessInputFile(const QtessInputFile &copy) : 
+  _entries(copy._entries) 
+{ 
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessInputFile::Copy Assignment Operator
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE void QtessInputFile::
+operator = (const QtessInputFile &copy) {
+  _entries = copy._entries;
+}

+ 349 - 0
pandatool/src/egg-qtess/qtessInputFile.cxx

@@ -0,0 +1,349 @@
+// Filename: qtessInputFile.cxx
+// Created by:  drose (13Oct03)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "qtessInputFile.h"
+#include "config_egg_qtess.h"
+#include "string_utils.h"
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessInputFile::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+QtessInputFile::
+QtessInputFile() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessInputFile::read
+//       Access: Public
+//  Description: reads the input file.
+////////////////////////////////////////////////////////////////////
+bool QtessInputFile::
+read(const Filename &filename) {
+  _filename = Filename::text_filename(filename);
+  _entries.clear();
+
+  ifstream input;
+  if (!_filename.open_read(input)) {
+    qtess_cat.error()
+      << "Unable to open input file " << _filename << "\n";
+    return false;
+  }
+
+  string complete_line;
+
+  int line_number = 0;
+  string line;
+  while (getline(input, line)) {
+    line_number++;
+
+    // Eliminate comments.  We have to scan the line repeatedly until
+    // we find the first hash mark that's preceded by whitespace.
+    size_t comment = line.find('#');
+    while (comment != string::npos) {
+      if (comment == 0 || isspace(line[comment - 1])) {
+        line = line.substr(0, comment);
+        comment = string::npos;
+
+      } else {
+        comment = line.find('#', comment + 1);
+      }
+    }
+
+    // Check for a trailing backslash: continuation character.
+    line = trim_right(line);
+    if (!line.empty() && line[line.size() - 1] == '\\') {
+      // We have a continuation character; go back and read some more.
+      complete_line += line.substr(0, line.size() - 1);
+
+    } else {
+      // It's a complete line.  Begin parsing.
+      line = trim(complete_line + line);
+      complete_line = string();
+
+      if (!line.empty()) {
+	QtessInputEntry entry;
+	
+	// Scan for the first colon followed by whitespace.
+	size_t colon = line.find(": ");
+	if (colon == string::npos) {
+	  qtess_cat.error()
+            << _filename << ": line " << line_number
+            << " has no colon followed by whitespace.\n";
+	  return false;
+	}
+	if (colon == 0) {
+	  qtess_cat.error()
+            << _filename << ": line " << line_number 
+            << " has no nodes.\n";
+	  return false;
+	}
+	
+        // Split the line into two groups of words at the colon: names
+        // before the colon, and parms following it.
+        vector_string names, parms;
+        extract_words(line.substr(0, colon), names);
+        extract_words(line.substr(colon + 1), parms);
+        
+        vector_string::const_iterator ni;
+        for (ni = names.begin(); ni != names.end(); ++ni) {
+          entry.add_node_name(*ni);
+        }
+
+	// Scan for things like ap, ad, ar, and pull them out of the
+	// stream.
+	vector_string::iterator ci, cnext;
+	ci = parms.begin();
+	while (ci != parms.end()) {
+	  cnext = ci;
+	  ++cnext;
+	  
+	  string parm = *ci;
+	  bool invert = false;
+	  if (parm[0] == '!' && parm.size() > 1) {
+	    invert = true;
+	    parm = parm.substr(1);
+	  }
+	  if (tolower(parm[0]) == 'a' && parm.size() > 1) {
+	    switch (tolower(parm[1])) {
+	    case 'p':
+	      entry._auto_place = !invert;
+	      break;
+
+	    case 'd':
+	      entry._auto_distribute = !invert;
+	      break;
+
+	    case 'r':
+              if (!string_to_double(parm.substr(2), entry._curvature_ratio)) {
+		qtess_cat.error()
+                  << _filename << ": line " << line_number 
+                  << " - invalid field " << parm << "\n";
+		return false;
+	      }
+	      break;
+
+	    default:
+	      qtess_cat.error()
+                << _filename << ": invalid parameters at line " 
+                << line_number << ".\n";
+	      return false;
+	    }
+	    parms.erase(ci);
+	  } else {
+	    ci = cnext;
+	  }
+	}
+	
+	if (!parms.empty()) {
+	  bool okflag = true;
+	  if (cmp_nocase(parms[0], "omit")==0) {
+	    entry.set_omit();
+
+	  } else if (cmp_nocase(parms[0], "matchuu")==0) {
+	    entry.set_match_uu();
+	    if (parms.size() > 1 && cmp_nocase(parms[1], "matchvv")==0) {
+	      entry.set_match_vv();
+	    }
+
+	  } else if (cmp_nocase(parms[0], "matchvv")==0) {
+	    entry.set_match_vv();
+	    if (parms.size() > 1 && cmp_nocase(parms[1], "matchuu")==0) {
+	      entry.set_match_uu();
+	    }
+
+	  } else if (cmp_nocase(parms[0], "matchuv")==0) {
+	    entry.set_match_uv();
+	    if (parms.size() > 1 && cmp_nocase(parms[1], "matchvu")==0) {
+	      entry.set_match_vu();
+	    }
+
+	  } else if (cmp_nocase(parms[0], "matchvu")==0) {
+	    entry.set_match_vu();
+	    if (parms.size() > 1 && cmp_nocase(parms[1], "matchuv")==0) {
+	      entry.set_match_uv();
+	    }
+
+	  } else if (cmp_nocase(parms[0], "minu")==0) {
+	    // minu #: minimum tesselation in U.
+	    if (parms.size() < 2) {
+	      okflag = false;
+	    } else {
+              int value = 0;
+              okflag = string_to_int(parms[1], value);
+	      entry.set_min_u(value);
+	    }
+
+	  } else if (cmp_nocase(parms[0], "minv")==0) {
+	    // minu #: minimum tesselation in V.
+	    if (parms.size() < 2) {
+	      okflag = false;
+	    } else {
+              int value = 0;
+              okflag = string_to_int(parms[1], value);
+	      entry.set_min_v(value);
+	    }
+
+	  } else if (tolower(parms[0][0]) == 'i') {
+	    // "i#": per-isoparm tesselation.
+            int value = 0;
+            okflag = string_to_int(parms[0].substr(1), value);
+            entry.set_per_isoparm(value);
+
+	  } else if (parms[0][parms[0].length() - 1] == '%') {
+            double value = 0.0;
+            okflag = string_to_double(parms[0].substr(0, parms[0].length() - 1), value);
+	    entry.set_importance(value / 100.0);
+
+	  } else if (parms.size() == 1) {
+	    // One numeric parameter: the number of triangles.
+            int value = 0;
+            okflag = string_to_int(parms[0], value);
+            entry.set_num_tris(value);
+
+	  } else if (parms.size() >= 2) {
+	    // Two or more numeric parameters: the number of u by v quads,
+	    // followed by an optional list of specific isoparms.
+            int u = 0, v = 0;
+            okflag = string_to_int(parms[0], u) && string_to_int(parms[1], v);
+	    entry.set_uv(u, v, &parms[2], parms.size() - 2);
+
+	  } else {
+	    okflag = false;
+	  }
+
+	  if (!okflag) {
+	    qtess_cat.error()
+              << _filename << ": invalid parameters at line " 
+              << line_number << ".\n";
+	    return false;
+	  }
+	}
+	_entries.push_back(entry);
+      }
+    }
+  }
+
+  if (qtess_cat.is_info()) {
+    qtess_cat.info()
+      << "read qtess parameter file " << _filename << ".\n";
+    if (qtess_cat.is_debug()) {
+      write(qtess_cat.debug(false));
+    }
+  }
+
+  add_default_entry();
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessInputFile::get_default_entry
+//       Access: Public
+//  Description: Returns a reference to the last entry on the list,
+//               which is the "default" entry that will match any
+//               surface that does not get explicitly named in the
+//               input file.
+////////////////////////////////////////////////////////////////////
+QtessInputEntry &QtessInputFile::
+get_default_entry() {
+  if (_entries.empty()) {
+    // No entries; create one.
+    add_default_entry();
+  }
+  return _entries.back();
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessInputFile::match
+//       Access: Public
+//  Description: Attempts to find a match for the given surface in the
+//               user input entries.  Searches in the order in which
+//               the entries were defined, and chooses the first
+//               match.
+//
+//               When a match is found, the surface is added to the
+//               entry's set of matched surfaces.  Returns the type of
+//               the matching node if a match is found, or T_undefined
+//               otherwise.
+////////////////////////////////////////////////////////////////////
+QtessInputEntry::Type QtessInputFile::
+match(QtessSurface *surface) {
+  QtessInputEntry::Type type;
+
+  if (_entries.empty()) {
+    // No entries; create one.
+    add_default_entry();
+  }
+
+  Entries::iterator ei;
+  for (ei = _entries.begin(); ei != _entries.end(); ++ei) {
+    type = (*ei).match(surface);
+    if (type != QtessInputEntry::T_undefined) {
+      return type;
+    }
+  }
+  return QtessInputEntry::T_undefined;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessInputFile::count_tris
+//       Access: Public
+//  Description: Determines the tesselation u,v amounts of each
+//               attached surface, and stores this information in the
+//               surface pointer.  Returns the total number of tris
+//               that will be produced.
+////////////////////////////////////////////////////////////////////
+int QtessInputFile::
+count_tris() {
+  int total_tris = 0;
+
+  Entries::iterator ei;
+  for (ei = _entries.begin(); ei != _entries.end(); ++ei) {
+    total_tris += (*ei).count_tris();
+  }
+  return total_tris;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessInputFile::write
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void QtessInputFile::
+write(ostream &out, int indent_level) const {
+  Entries::const_iterator ei;
+  for (ei = _entries.begin(); ei != _entries.end(); ++ei) {
+    (*ei).write(out, indent_level);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessInputFile::add_default_entry
+//       Access: Private
+//  Description: Adds one more entry to the end of the list, to catch
+//               all of the surfaces that didn't get explicitly named.
+////////////////////////////////////////////////////////////////////
+void QtessInputFile::
+add_default_entry() {
+  QtessInputEntry entry("*");
+  entry.set_omit();
+  _entries.push_back(entry);
+}

+ 59 - 0
pandatool/src/egg-qtess/qtessInputFile.h

@@ -0,0 +1,59 @@
+// Filename: qtessInputFile.h
+// Created by:  drose (13Oct03)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef QTESSINPUTFILE_H
+#define QTESSINPUTFILE_H
+
+#include "pandatoolbase.h"
+#include "qtessInputEntry.h"
+#include "filename.h"
+#include "pvector.h"
+
+class QtessSurface;
+
+////////////////////////////////////////////////////////////////////
+//       Class : QtessInputFile
+// Description : Stores all the information read from a tesselation
+//               input file: a list of QtessInputEntry's.
+////////////////////////////////////////////////////////////////////
+class QtessInputFile {
+public:
+  QtessInputFile();
+  INLINE QtessInputFile(const QtessInputFile &copy);
+  INLINE void operator = (const QtessInputFile &copy);
+
+  bool read(const Filename &filename);
+  QtessInputEntry &get_default_entry();
+
+  QtessInputEntry::Type match(QtessSurface *surface);
+  int count_tris();
+
+  void write(ostream &out, int indent_level = 0) const;
+
+private:
+  void add_default_entry();
+
+  Filename _filename;
+
+  typedef pvector<QtessInputEntry> Entries;
+  Entries _entries;
+};
+
+#include "qtessInputFile.I"
+
+#endif

+ 193 - 0
pandatool/src/egg-qtess/qtessSurface.I

@@ -0,0 +1,193 @@
+// Filename: qtessSurface.I
+// Created by:  drose (13Oct03)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessSurface::get_name
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE const string &QtessSurface::
+get_name() const {
+  return _egg_surface->get_name();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessSurface::is_valid
+//       Access: Public
+//  Description: Returns true if the defined surface is valid, false
+//               otherwise.
+////////////////////////////////////////////////////////////////////
+INLINE bool QtessSurface::
+is_valid() const {
+  return (_nurbs != (NurbsSurfaceEvaluator *)NULL);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessSurface::set_importance
+//       Access: Public
+//  Description: Sets the importance of the surface, as a ratio in
+//               proportion to the square of its size.
+////////////////////////////////////////////////////////////////////
+INLINE void QtessSurface::
+set_importance(double importance2) {
+  _importance = sqrt(importance2);
+  _importance2 = importance2;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessSurface::set_match_u
+//       Access: Public
+//  Description: Indicates the surface to which this surface must
+//               match in its U direction.  If u_to_u is true, it
+//               matches to the other surface's U direction;
+//               otherwise, it matches to the other surface's V
+//               direction.
+//
+//               Note that the surface pointer is an indirect pointer.
+//               The value passed in is the address of the pointer to
+//               the actual surface (which may or may not be filled in
+//               yet).  The actual pointer may be filled in later.
+////////////////////////////////////////////////////////////////////
+INLINE void QtessSurface::
+set_match_u(QtessSurface **match_u, bool match_u_to_u) {
+  _match_u = match_u;
+  _match_u_to_u = match_u_to_u;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessSurface::set_match_v
+//       Access: Public
+//  Description: Indicates the surface to which this surface must
+//               match in its V direction.  If v_to_v is true, it
+//               matches to the other surface's V direction;
+//               otherwise, it matches to the other surface's U
+//               direction.
+//
+//               Note that the surface pointer is an indirect pointer.
+//               The value passed in is the address of the pointer to
+//               the actual surface (which may or may not be filled in
+//               yet).  The actual pointer may be filled in later.
+////////////////////////////////////////////////////////////////////
+INLINE void QtessSurface::
+set_match_v(QtessSurface **match_v, bool match_v_to_v) {
+  _match_v = match_v;
+  _match_v_to_v = match_v_to_v;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessSurface::set_min_u
+//       Access: Public
+//  Description: Specifies the absolute minimum number of segments
+//               allowed in the U direction.
+////////////////////////////////////////////////////////////////////
+INLINE void QtessSurface::
+set_min_u(int min_u) {
+  _min_u = min_u;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessSurface::set_min_v
+//       Access: Public
+//  Description: Specifies the absolute minimum number of segments
+//               allowed in the V direction.
+////////////////////////////////////////////////////////////////////
+INLINE void QtessSurface::
+set_min_v(int min_v) {
+  _min_v = min_v;
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessSurface::count_patches
+//       Access: Public
+//  Description: Returns the number of patches the NURBS contains.
+//               Each patch is a square area bounded by isoparms.
+//               This actually scales by the importance of the
+//               surface, if it is not 1.
+////////////////////////////////////////////////////////////////////
+INLINE double QtessSurface::
+count_patches() const {
+  return _num_u * _num_v * _importance2;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessSurface::count_tris
+//       Access: Public
+//  Description: Returns the number of triangles that will be
+//               generated by the current tesselation parameters.
+////////////////////////////////////////////////////////////////////
+INLINE int QtessSurface::
+count_tris() const {
+  return _tess_u * _tess_v * 2;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessSurface::get_joint_membership_index
+//       Access: Public
+//  Description: Returns the extra dimension number within the surface
+//               where the vertex membership in the indicated joint
+//               should be stored.
+////////////////////////////////////////////////////////////////////
+INLINE int QtessSurface::
+get_joint_membership_index(EggGroup *joint) {
+  JointTable::iterator jti = _joint_table.find(joint);
+  if (jti != _joint_table.end()) {
+    return (*jti).second;
+  }
+  int d = _next_d;
+  _next_d++;
+  _joint_table[joint] = d;
+  return d;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessSurface::get_dxyz_index
+//       Access: Public
+//  Description: Returns the extra dimension number within the surface
+//               where the indicated Dxyz morph offset should be stored.
+////////////////////////////////////////////////////////////////////
+INLINE int QtessSurface::
+get_dxyz_index(const string &morph_name) {
+  MorphTable::iterator mti = _dxyz_table.find(morph_name);
+  if (mti != _dxyz_table.end()) {
+    return (*mti).second;
+  }
+  int d = _next_d;
+  _next_d += 3;
+  _dxyz_table[morph_name] = d;
+  return d;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessSurface::get_drgba_index
+//       Access: Public
+//  Description: Returns the extra dimension number within the surface
+//               where the indicated Drgba morph offset should be stored.
+////////////////////////////////////////////////////////////////////
+INLINE int QtessSurface::
+get_drgba_index(const string &morph_name) {
+  MorphTable::iterator mti = _drgba_table.find(morph_name);
+  if (mti != _drgba_table.end()) {
+    return (*mti).second;
+  }
+  int d = _next_d;
+  _next_d += 4;
+  _drgba_table[morph_name] = d;
+  return d;
+}

+ 609 - 0
pandatool/src/egg-qtess/qtessSurface.cxx

@@ -0,0 +1,609 @@
+// Filename: qtessSurface.cxx
+// Created by:  drose (13Oct03)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "qtessSurface.h"
+#include "qtessGlobals.h"
+#include "qtessInputEntry.h"
+#include "config_egg_qtess.h"
+#include "eggPolygon.h"
+#include "eggVertexPool.h"
+#include "eggVertex.h"
+#include "eggComment.h"
+#include "egg_parametrics.h"
+#include "pset.h"
+#include "pmap.h"
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessSurface::Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+QtessSurface::
+QtessSurface(EggNurbsSurface *egg_surface) :
+  _egg_surface(egg_surface)
+{
+  _nurbs = make_nurbs_surface(_egg_surface, LMatrix4d::ident_mat());
+  _has_vertex_color = _egg_surface->has_vertex_color();
+
+  // The first four slots are reserved for vertex color.
+  _next_d = 4;
+
+  _importance = 1.0;
+  _importance2 = 1.0;
+  _match_u = _match_v = NULL;
+  _tess_u = _tess_v = 0;
+  _got_scores = false;
+
+  // If the surface is closed in either dimension, the mininum
+  // tesselation in that dimension is by default 3, so we don't
+  // ribbonize the surface.  Otherwise the minimum is 1.
+  _min_u = _min_v = 1;
+  if (egg_surface->is_closed_u()) {
+    _min_u = 3;
+  }
+  if (egg_surface->is_closed_v()) {
+    _min_v = 3;
+  }
+
+  if (_nurbs == (NurbsSurfaceEvaluator *)NULL) {
+    _num_u = _num_v = 0;
+
+  } else {
+    record_vertex_extras();
+
+    _nurbs->normalize_u_knots();
+    _nurbs->normalize_v_knots();
+    _nurbs_result = _nurbs->evaluate();
+
+    _num_u = _nurbs->get_num_u_segments();
+    _num_v = _nurbs->get_num_v_segments();
+
+    if (QtessGlobals::_respect_egg) {
+      if (egg_surface->get_u_subdiv() != 0) {
+	_num_u = egg_surface->get_u_subdiv();
+      }
+      if (egg_surface->get_v_subdiv() != 0) {
+	_num_v = egg_surface->get_v_subdiv();
+      }
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: get_score
+//       Access: Public
+//  Description: Computes the curvature/stretch score for the surface,
+//               if it has not been already computed, and returns the
+//               net surface score.  This is used both for
+//               automatically distributing isoparms among the
+//               surfaces by curvature, as well as for automatically
+//               placing the isoparms within each surface by
+//               curvature.
+////////////////////////////////////////////////////////////////////
+double QtessSurface::
+get_score(double ratio) {
+  if (_nurbs == (NurbsSurfaceEvaluator *)NULL) {
+    return 0.0;
+  }
+
+  if (!_got_scores) {
+    _u_placer.get_scores(_nurbs->get_num_u_segments() * 100, 
+                         _nurbs->get_num_v_segments() * 2,
+                         ratio, _nurbs_result, true);
+    _v_placer.get_scores(_nurbs->get_num_v_segments() * 100, 
+                         _nurbs->get_num_u_segments() * 2,
+                         ratio, _nurbs_result, false);
+    _got_scores = true;
+  }
+
+  return _u_placer.get_total_score() * _v_placer.get_total_score() * _importance2;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessSurface::tesselate
+//       Access: Public
+//  Description: Applies the appropriate tesselation to the surface,
+//               and replaces its node in the tree with an EggGroup
+//               containing both the new vertex pool and all of the
+//               polygons.
+////////////////////////////////////////////////////////////////////
+int QtessSurface::
+tesselate() {
+  apply_match();
+  int tris = 0;
+
+  PT(EggGroup) group = do_uniform_tesselate(tris);
+  PT(EggNode) new_node = group.p();
+  if (new_node == (EggNode *)NULL) {
+    new_node = new EggComment(_egg_surface->get_name(),
+                              "Omitted NURBS surface.");
+    tris = 0;
+  }
+  EggGroupNode *parent = _egg_surface->get_parent();
+  nassertr(parent != (EggGroupNode *)NULL, 0);
+  parent->remove_child(_egg_surface);
+  parent->add_child(new_node);
+
+  return tris;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessSurface::write_qtess_parameter
+//       Access: Public
+//  Description: Writes a line to the given output file telling qtess
+//               how this surface should be tesselated uniformly.
+//               Returns the number of tris.
+////////////////////////////////////////////////////////////////////
+int QtessSurface::
+write_qtess_parameter(ostream &out) {
+  apply_match();
+
+  if (_tess_u == 0 || _tess_v == 0) {
+    out << get_name() << " : omit\n";
+
+  } else if (_iso_u.empty() || _iso_v.empty()) {
+    out << get_name() << " : " << _tess_u << " " << _tess_v << "\n";
+
+  } else {
+    out << get_name() << " : " << _iso_u.back() << " " << _iso_v.back();
+    QtessInputEntry::output_extra(out, _iso_u, 'u');
+    QtessInputEntry::output_extra(out, _iso_v, 'v');
+    out << "\n";
+  }
+
+  return count_tris();
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessSurface::omit
+//       Access: Public
+//  Description: Sets up the surface to omit itself from the output.
+////////////////////////////////////////////////////////////////////
+void QtessSurface::
+omit() {
+  _tess_u = 0;
+  _tess_v = 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessSurface::tesselate_uv
+//       Access: Public
+//  Description: Sets the surface up to tesselate itself uniformly at
+//               u x v, or if autoplace is true, automatically with u
+//               x v quads.
+////////////////////////////////////////////////////////////////////
+void QtessSurface::
+tesselate_uv(int u, int v, bool autoplace, double ratio) {
+  _tess_u = u;
+  _tess_v = v;
+  _iso_u.clear();
+  _iso_v.clear();
+  if (autoplace) {
+    tesselate_auto(_tess_u, _tess_v, ratio);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessSurface::tesselate_specific
+//       Access: Public
+//  Description: Sets the surface up to tesselate itself at specific
+//               isoparms only.
+////////////////////////////////////////////////////////////////////
+void QtessSurface::
+tesselate_specific(const pvector<double> &u_list,
+                   const pvector<double> &v_list) {
+  _iso_u = u_list;
+  _iso_v = v_list;
+  _tess_u = (int)_iso_u.size() - 1;
+  _tess_v = (int)_iso_v.size() - 1;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessSurface::tesselate_per_isoparm
+//       Access: Public
+//  Description: Sets the surface up to tesselate itself to a uniform
+//               amount per isoparm.
+////////////////////////////////////////////////////////////////////
+void QtessSurface::
+tesselate_per_isoparm(double pi, bool autoplace, double ratio) {
+  if (_num_u == 0 || _num_v == 0) {
+    omit();
+
+  } else {
+    _tess_u = max(_min_u, (int)floor(_num_u * _importance * pi + 0.5));
+    _tess_v = max(_min_v, (int)floor(_num_v * _importance * pi + 0.5));
+    _iso_u.clear();
+    _iso_v.clear();
+    if (autoplace) {
+      tesselate_auto(_tess_u, _tess_v, ratio);
+    }
+  }
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessSurface::TesselatePerScore
+//       Access: Public
+//  Description: Sets the surface up to tesselate itself according to
+//               its computed curvature score in both dimensions.
+////////////////////////////////////////////////////////////////////
+void QtessSurface::
+tesselate_per_score(double pi, bool autoplace, double ratio) {
+  if (get_score(ratio) <= 0.0) {
+    omit();
+
+  } else {
+    _tess_u = max(_min_u, (int)floor(_u_placer.get_total_score() * _importance * pi + 0.5));
+    _tess_v = max(_min_v, (int)floor(_v_placer.get_total_score() * _importance * pi + 0.5));
+    _iso_u.clear();
+    _iso_v.clear();
+    if (autoplace) {
+      tesselate_auto(_tess_u, _tess_v, ratio);
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessSurface::tesselate_auto
+//       Access: Public
+//  Description: Sets the surface up to tesselate itself by
+//               automatically determining the best place to put the
+//               indicated u x v isoparms.
+////////////////////////////////////////////////////////////////////
+void QtessSurface::
+tesselate_auto(int u, int v, double ratio) {
+  if (get_score(ratio) <= 0.0) {
+    omit();
+
+  } else {
+    _u_placer.place(u, _iso_u);
+    _v_placer.place(v, _iso_v);
+    _tess_u = (int)_iso_u.size() - 1;
+    _tess_v = (int)_iso_v.size() - 1;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessSurface::record_vertex_extras
+//       Access: Private
+//  Description: Records the joint membership and morph offsets of
+//               each control vertex in the extra-dimensional space of
+//               the NURBS, so that we can extract this data out again
+//               later to apply to the polygon vertices.
+////////////////////////////////////////////////////////////////////
+void QtessSurface::
+record_vertex_extras() {
+  int num_u_vertices = _egg_surface->get_num_u_cvs();
+  int num_v_vertices = _egg_surface->get_num_v_cvs();
+
+  for (int ui = 0; ui < num_u_vertices; ui++) {
+    for (int vi = 0; vi < num_v_vertices; vi++) {
+      int i = _egg_surface->get_vertex_index(ui, vi);
+      EggVertex *egg_vertex = _egg_surface->get_vertex(i);
+
+      // The joint membership.
+      EggVertex::GroupRef::const_iterator gi;
+      for (gi = egg_vertex->gref_begin(); gi != egg_vertex->gref_end(); ++gi) {
+        EggGroup *joint = (*gi);
+        int d = get_joint_membership_index(joint);
+        double membership = joint->get_vertex_membership(egg_vertex);
+        _nurbs->set_extended_vertex(ui, vi, d, membership);
+      }
+
+      // The xyz morphs.
+      EggMorphVertexList::const_iterator dxi;
+      for (dxi = egg_vertex->_dxyzs.begin(); 
+           dxi != egg_vertex->_dxyzs.end(); 
+           ++dxi) {
+        const string &morph_name = (*dxi).get_name();
+        LVector3f delta = LCAST(float, (*dxi).get_offset());
+        int d = get_dxyz_index(morph_name);
+        _nurbs->set_extended_vertices(ui, vi, d, delta.get_data(), 3);
+      }
+
+      // The rgba morphs.
+      EggMorphColorList::const_iterator dri;
+      for (dri = egg_vertex->_drgbas.begin(); 
+           dri != egg_vertex->_drgbas.end(); 
+           ++dri) {
+        const string &morph_name = (*dri).get_name();
+        const LVector4f &delta = (*dri).get_offset();
+        int d = get_drgba_index(morph_name);
+        _nurbs->set_extended_vertices(ui, vi, d, delta.get_data(), 4);
+      }
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: QtessSurface::apply_match
+//       Access: Private
+//  Description: If the surface was set up to copy its tesselation in
+//               either axis from another surface, makes this copy
+//               now.
+////////////////////////////////////////////////////////////////////
+void QtessSurface::
+apply_match() {
+  if (_match_u != NULL) {
+    QtessSurface *m = *_match_u;
+    if (m == NULL) {
+      qtess_cat.warning()
+        << "No surface to match " << get_name() << " to in U.\n";
+    } else {
+      if (qtess_cat.is_debug()) {
+	qtess_cat.debug()
+          << "Matching " << get_name() << " in U to " << m->get_name()
+          << " in " << (_match_u_to_u?'U':'V') << ".\n";
+      }
+      if (_match_u_to_u) {
+	_tess_u = m->_tess_u;
+	_iso_u = m->_iso_u;
+      } else {
+	_tess_u = m->_tess_v;
+	_iso_u = m->_iso_v;
+      }
+    }
+  }
+
+  if (_match_v != NULL) {
+    QtessSurface *m = *_match_v;
+    if (m == NULL) {
+      qtess_cat.warning()
+        << "No surface to match " << get_name() << " in V.\n";
+    } else {
+      if (qtess_cat.is_debug()) {
+	qtess_cat.debug()
+          << "Matching " << get_name() << " in V to " << m->get_name()
+          << " in " << (_match_v_to_v?'V':'U') << ".\n";
+      }
+      if (_match_v_to_v) {
+	_tess_v = m->_tess_v;
+	_iso_v = m->_iso_v;
+      } else {
+	_tess_v = m->_tess_u;
+	_iso_v = m->_iso_u;
+      }
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: do_uniform_tesselate
+//       Access: Private
+//  Description: Subdivide the surface uniformly according to the
+//               parameters specified by an earlier call to omit(),
+//               teseselate_uv(), or tesselate_per_isoparm().
+////////////////////////////////////////////////////////////////////
+PT(EggGroup) QtessSurface::
+do_uniform_tesselate(int &tris) const {
+  tris = 0;
+
+  if (_tess_u == 0 || _tess_v == 0) {
+    // No tesselation!
+    if (qtess_cat.is_debug()) {
+      qtess_cat.debug()
+        << get_name() << " : omit\n";
+    }
+    return NULL;
+  }
+
+  PT(EggGroup) group = new EggGroup(_egg_surface->get_name());
+
+  // _tess_u and _tess_v are the number of patches to create.  Convert
+  // that to the number of vertices.
+
+  int num_u = _tess_u + 1;
+  int num_v = _tess_v + 1;
+
+  if (qtess_cat.is_debug()) {
+    qtess_cat.debug() << get_name() << " : " << tris << "\n";
+  }
+
+  assert(_iso_u.empty() || (int)_iso_u.size() == num_u);
+  assert(_iso_v.empty() || (int)_iso_v.size() == num_v);
+
+  // Now how many vertices is that total, and how many vertices per
+  // strip?
+  int num_verts = num_u * num_v;
+
+  // Create a vertex pool.
+  PT(EggVertexPool) vpool = new EggVertexPool(_egg_surface->get_name());
+  group->add_child(vpool);
+
+  // Create all the vertices.
+  int ui, vi;
+  double u, v;
+
+  typedef pvector<EggVertex *> VertexList;
+  VertexList new_verts;
+  new_verts.reserve(num_verts);
+
+  // Also collect the vertices into this set to group them by spatial
+  // position only.  This is relevant for calculating normals.
+  typedef pset<EggVertex *> NVertexGroup;
+  typedef pmap<Vertexd, NVertexGroup> NVertexCollection;
+  NVertexCollection n_collection;
+  
+  for (vi = 0; vi < num_v; vi++) {
+    if (_iso_v.empty()) {
+      v = (double)vi / (double)(num_v-1);
+    } else {
+      v = _iso_v[vi] / _iso_v.back();
+    }
+    for (ui = 0; ui < num_u; ui++) {
+      if (_iso_u.empty()) {
+	u = (double)ui / (double)(num_u-1);
+      } else {
+	u = _iso_u[ui] / _iso_u.back();
+      }
+
+      PT(EggVertex) egg_vertex = evaluate_vertex(u, v);
+      vpool->add_vertex(egg_vertex);
+      new_verts.push_back(egg_vertex);
+      n_collection[egg_vertex->get_pos3()].insert(egg_vertex);
+    }
+  }
+  nassertr((int)new_verts.size() == num_verts, NULL);
+
+  // Now create a bunch of quads.
+  for (vi = 1; vi < num_v; vi++) {
+    for (ui = 1; ui < num_u; ui++) {
+      PT(EggPolygon) poly = new EggPolygon;
+      poly->add_vertex(new_verts[vi*num_u + (ui-1)]);
+      poly->add_vertex(new_verts[(vi-1)*num_u + (ui-1)]);
+      poly->add_vertex(new_verts[(vi-1)*num_u + ui]); 
+      poly->add_vertex(new_verts[vi*num_u + ui]);
+
+      poly->copy_attributes(*_egg_surface);
+
+      // We compute a polygon normal just so we can verify the
+      // calculated vertex normals.  It's also helpful for identifying
+      // degenerate polygons.
+      if (poly->recompute_polygon_normal()) {
+        tris += 2;
+        group->add_child(poly);
+      }
+    }
+  }
+
+  // Now check all the vertex normals by comparing them to the polygon
+  // normals.  Some might have not been computed at all; others might
+  // be facing in the wrong direction.
+
+  // Now go back through and normalize the computed normals.
+  NVertexCollection::const_iterator nci;
+  for (nci = n_collection.begin(); nci != n_collection.end(); ++nci) {
+    const NVertexGroup &group = (*nci).second;
+
+    // Calculate the normal these vertices should have based on the
+    // polygons that share it.
+    Normald normal = Normald::zero();
+    int num_polys = 0;
+    NVertexGroup::const_iterator ngi;
+    for (ngi = group.begin(); ngi != group.end(); ++ngi) {
+      EggVertex *egg_vertex = (*ngi);
+      EggVertex::PrimitiveRef::const_iterator pri;
+      for (pri = egg_vertex->pref_begin(); 
+           pri != egg_vertex->pref_end(); 
+           ++pri) {
+        EggPrimitive *egg_primitive = (*pri);
+        nassertr(egg_primitive->has_normal(), NULL);
+        normal += egg_primitive->get_normal();
+        num_polys++;
+      }
+    }
+
+    if (num_polys > 0) {
+      normal /= (double)num_polys;
+
+      // Now compare this normal with what the NURBS representation
+      // calculated.  It should be facing in at least vaguely the same
+      // direction.
+      for (ngi = group.begin(); ngi != group.end(); ++ngi) {
+        EggVertex *egg_vertex = (*ngi);
+        if (egg_vertex->has_normal()) {
+          if (normal.dot(egg_vertex->get_normal()) < 0.0) {
+            // This one is backwards.
+            egg_vertex->set_normal(-egg_vertex->get_normal());
+          }
+        } else {
+          // This vertex doesn't have a normal; it gets the computed
+          // normal.
+          egg_vertex->set_normal(normal);
+        }
+      }
+    }
+  }
+
+  return group;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: evaluate_vertex
+//       Access: Private
+//  Description: Evaluates the surface at the given u, v position and
+//               sets the vertex to the appropriate values.  Also sets
+//               the joint membership of the vertex.
+////////////////////////////////////////////////////////////////////
+PT(EggVertex) QtessSurface::
+evaluate_vertex(double u, double v) const {
+  PT(EggVertex) egg_vertex = new EggVertex;
+
+  Vertexf point;
+  Normalf normal;
+  _nurbs_result->eval_point(u, v, point);
+  _nurbs_result->eval_normal(u, v, normal);
+
+  // If the normal is too short, don't consider it--it's probably
+  // inaccurate due to numerical limitations.  We'll recompute it
+  // later based on the polygon normals.
+  float length = normal.length();
+  if (length > 0.0001f) {
+    normal /= length;
+    egg_vertex->set_normal(LCAST(double, normal));
+  }
+
+  egg_vertex->set_pos(LCAST(double, point));
+  egg_vertex->set_uv(LVecBase2d(u, v));
+
+  // The color is stored, by convention, in slots 0-4 of the surface.
+  if (_has_vertex_color) {
+    Colorf rgba;
+    _nurbs_result->eval_extended_points(u, v, 0, &rgba[0], 4);
+    egg_vertex->set_color(rgba);
+  }
+
+  // Also fill in the joint membership.
+  JointTable::const_iterator jti;
+  for (jti = _joint_table.begin(); jti != _joint_table.end(); ++jti) {
+    EggGroup *joint = (*jti).first;
+    int d = (*jti).second;
+
+    double membership = _nurbs_result->eval_extended_point(u, v, d);
+    if (membership > 0.0) {
+      joint->ref_vertex(egg_vertex, membership);
+    }
+  }
+
+  // And the morphs.
+  MorphTable::const_iterator mti;
+  for (mti = _dxyz_table.begin(); mti != _dxyz_table.end(); ++mti) {
+    const string &morph_name = (*mti).first;
+    int d = (*mti).second;
+
+    LVector3f delta;
+    _nurbs_result->eval_extended_points(u, v, d, &delta[0], 3);
+    if (!delta.almost_equal(LVector3f::zero())) {
+      egg_vertex->_dxyzs.insert(EggMorphVertex(morph_name, LCAST(double, delta)));
+    }
+  }
+
+  for (mti = _drgba_table.begin(); mti != _drgba_table.end(); ++mti) {
+    const string &morph_name = (*mti).first;
+    int d = (*mti).second;
+
+    LVector4f delta;
+    _nurbs_result->eval_extended_points(u, v, d, &delta[0], 4);
+    if (!delta.almost_equal(LVector4f::zero())) {
+      egg_vertex->_drgbas.insert(EggMorphColor(morph_name, delta));
+    }
+  }
+  
+  return egg_vertex;
+}

+ 122 - 0
pandatool/src/egg-qtess/qtessSurface.h

@@ -0,0 +1,122 @@
+// Filename: qtessSurface.h
+// Created by:  drose (13Oct03)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef QTESSSURFACE_H
+#define QTESSSURFACE_H
+
+#include "pandatoolbase.h"
+#include "isoPlacer.h"
+#include "eggNurbsSurface.h"
+#include "eggGroup.h"
+#include "eggVertex.h"
+#include "nurbsSurfaceEvaluator.h"
+#include "nurbsSurfaceResult.h"
+#include "referenceCount.h"
+#include "pointerTo.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : QtessSurface
+// Description : A reference to an EggNurbsSurface in the egg file,
+//               and its parameters as set by the user input file and
+//               as computed in relation to the other surfaces.
+////////////////////////////////////////////////////////////////////
+class QtessSurface : public ReferenceCount {
+public:
+  QtessSurface(EggNurbsSurface *egg_surface);
+
+  INLINE const string &get_name() const;
+  INLINE bool is_valid() const;
+
+  INLINE void set_importance(double importance2);
+  INLINE void set_match_u(QtessSurface **match_u, bool match_u_to_u);
+  INLINE void set_match_v(QtessSurface **match_v, bool match_v_to_v);
+  INLINE void set_min_u(int min_u);
+  INLINE void set_min_v(int min_v);
+
+  INLINE double count_patches() const;
+  INLINE int count_tris() const;
+
+  double get_score(double ratio);
+  
+  int tesselate();
+  int write_qtess_parameter(ostream &out);
+  void omit();
+  void tesselate_uv(int u, int v, bool autoplace, double ratio);
+  void tesselate_specific(const pvector<double> &u_list,
+                          const pvector<double> &v_list);
+  void tesselate_per_isoparm(double pi, bool autoplace, double ratio);
+  void tesselate_per_score(double pi, bool autoplace, double ratio);
+  void tesselate_auto(int u, int v, double ratio);
+
+private:
+  void record_vertex_extras();
+  INLINE int get_joint_membership_index(EggGroup *joint);
+  INLINE int get_dxyz_index(const string &morph_name);
+  INLINE int get_drgba_index(const string &morph_name);
+
+  void apply_match();
+  PT(EggGroup) do_uniform_tesselate(int &tris) const;
+  PT(EggVertex) evaluate_vertex(double u, double v) const;
+
+  PT(EggNurbsSurface) _egg_surface;
+  PT(NurbsSurfaceEvaluator) _nurbs;
+  PT(NurbsSurfaceResult) _nurbs_result;
+  bool _has_vertex_color;
+
+  // Mapping arbitrary attributes to integer extended dimension
+  // values, so we can hang arbitrary data in the extra dimensional
+  // space of the surface.
+  int _next_d;
+  typedef map<EggGroup *, int> JointTable;
+  JointTable _joint_table;
+  typedef map<string, int> MorphTable;
+  MorphTable _dxyz_table;
+  MorphTable _drgba_table;
+
+  int _num_u, _num_v;
+  int _tess_u, _tess_v;
+  pvector<double> _iso_u, _iso_v;  // If nonempty, isoparms at which to tess.
+
+  // _importance is the relative importance of the surface along either
+  // axis; _importance2 is this number squared, which is the value set by
+  // set_importance().
+  double _importance;
+  double _importance2;
+
+  // _match_u and _match_v indicate which surface we must match
+  // exactly for tesselation in U or V.  This helps get edges to line
+  // up properly.  They are indirect pointers because we go through
+  // the surfaces in one pass, and might need to fill in the correct
+  // value later.
+  QtessSurface **_match_u, **_match_v;
+  bool _match_u_to_u, _match_v_to_v;
+
+  // _min_u and _min_v specify a mininum number of quads below which
+  // we should not attempt to subdivide the surface in either
+  // dimension.  This is intended to prevent degenerate cases like
+  // knife-fingers.
+  int _min_u, _min_v;
+
+  IsoPlacer _u_placer, _v_placer;
+  bool _got_scores;
+};
+
+#include "qtessSurface.I"
+
+#endif
+

+ 63 - 0
pandatool/src/egg-qtess/subdivSegment.I

@@ -0,0 +1,63 @@
+// Filename: subdivSegment.I
+// Created by:  drose (14Oct03)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: SubdivSegment::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE SubdivSegment::
+SubdivSegment(const double *cint, int f, int t) : 
+  _cint(cint), 
+  _f(f),
+  _t(t) 
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SubdivSegment::get_score
+//       Access: Public
+//  Description: Returns the net score of the segment.
+////////////////////////////////////////////////////////////////////
+INLINE double SubdivSegment::
+get_score() const {
+  return _cint[_t] - _cint[_f];
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SubdivSegment::get_need
+//       Access: Public
+//  Description: Returns a score that indicates how badly the segment
+//               needs to be further subdivided.  The greater the
+//               number, the greater the need.
+////////////////////////////////////////////////////////////////////
+INLINE double SubdivSegment::
+get_need() const {
+  return get_score() / (double)(_num_cuts+1);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SubdivSegment::operator <
+//       Access: Public
+//  Description: Sorts the segments in descending order of need.
+////////////////////////////////////////////////////////////////////
+INLINE bool SubdivSegment::
+operator < (const SubdivSegment &other) const {
+  return get_need() > other.get_need();
+}

+ 89 - 0
pandatool/src/egg-qtess/subdivSegment.cxx

@@ -0,0 +1,89 @@
+// Filename: subdivSegment.cxx
+// Created by:  drose (14Oct03)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "subdivSegment.h"
+
+
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: binary_search
+//  Description: Performs a standard binary search.  This utility
+//               function is used below.
+////////////////////////////////////////////////////////////////////
+static int
+binary_search(double val, const double *array, int bot, int top) {
+  if (top < bot) {
+    return bot;
+  }
+  int mid = (bot + top)/2;
+
+  if (array[mid] < val) {
+    return binary_search(val, array, mid+1, top);
+  } else {
+    return binary_search(val, array, bot, mid-1);
+  }
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: SubdivSegment::cut
+//       Access: Public
+//  Description: Applies _num_cuts cuts to the segment.
+////////////////////////////////////////////////////////////////////
+void SubdivSegment::
+cut() {
+  int c;
+  double ct = get_score();
+
+  _cuts.erase(_cuts.begin(), _cuts.end());
+  int last = _f;
+  for (c = 1; c < _num_cuts+1; c++) {
+    double val = (double)c * ct / (double)(_num_cuts+1) + _cint[_f];
+    int i = binary_search(val, _cint, _f, _t);
+    if (i != last && i < _t) {
+      _cuts.push_back(i);
+    }
+    last = i;
+  }
+
+  while ((int)_cuts.size() < _num_cuts) {
+    // Do we have any extra?  Assign them into likely places.
+    int last = _f;
+    int mc = -1;
+    int mv = 0;
+    for (c = 0; c < (int)_cuts.size(); c++) {
+      if (mc == -1 || _cuts[c] - last > mv) {
+	mc = c;
+	mv = _cuts[c] - last;
+      }
+      last = _cuts[c];
+    }
+
+    if (mc==-1) {
+      // Surrender.
+      return;
+    }
+    if (mc==0) {
+      _cuts.insert(_cuts.begin() + mc, (_cuts[mc] + _f) / 2);
+    } else {
+      _cuts.insert(_cuts.begin() + mc, (_cuts[mc] + _cuts[mc-1]) / 2);
+    }
+  }
+}
+

+ 50 - 0
pandatool/src/egg-qtess/subdivSegment.h

@@ -0,0 +1,50 @@
+// Filename: subdivSegment.h
+// Created by:  drose (14Oct03)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef SUBDIVSEGMENT_H
+#define SUBDIVSEGMENT_H
+
+#include "pandatoolbase.h"
+#include "pvector.h"
+#include "vector_int.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : SubdivSegment
+// Description : Represents a single hypothetical subdivided segment,
+//               under consideration by the IsoPlacer.
+////////////////////////////////////////////////////////////////////
+class SubdivSegment {
+public:
+  INLINE SubdivSegment(const double *cint, int f, int t);
+
+  INLINE double get_score() const;
+  INLINE double get_need() const;
+  INLINE bool operator < (const SubdivSegment &other) const;
+
+  void cut();
+
+  const double *_cint;
+  int _f, _t;
+  int _num_cuts;
+  vector_int _cuts;
+};
+
+#include "subdivSegment.I"
+
+#endif
+