Browse Source

move pfmfile from pandatool

David Rose 14 years ago
parent
commit
2a691dd339

+ 3 - 0
panda/src/grutil/Sources.pp

@@ -26,6 +26,7 @@
     lineSegs.I lineSegs.h \
     multitexReducer.I multitexReducer.h multitexReducer.cxx \
     nodeVertexTransform.I nodeVertexTransform.h \
+    pfmFile.I pfmFile.h \
     rigidBodyCombiner.I rigidBodyCombiner.h
     
   #define INCLUDED_SOURCES \
@@ -41,6 +42,7 @@
     sceneGraphAnalyzerMeter.cxx \
     heightfieldTesselator.cxx \
     nodeVertexTransform.cxx \    
+    pfmFile.cxx \
     pipeOcclusionCullTraverser.cxx \
     lineSegs.cxx \
     rigidBodyCombiner.cxx
@@ -59,6 +61,7 @@
     lineSegs.I lineSegs.h \
     multitexReducer.I multitexReducer.h \
     nodeVertexTransform.I nodeVertexTransform.h \
+    pfmFile.I pfmFile.h \
     rigidBodyCombiner.I rigidBodyCombiner.h
 
   #define IGATESCAN all

+ 11 - 0
panda/src/grutil/config_grutil.cxx

@@ -62,6 +62,17 @@ ConfigVariableDouble scene_graph_analyzer_meter_scale
 ConfigVariableDouble scene_graph_analyzer_meter_side_margins
 ("scene-graph-analyzer-meter-side-margins", 0.5);
 
+ConfigVariableBool pfm_force_littleendian
+("pfm-force-littleendian", false,
+ PRC_DESC("This forces a pfm file to be read as a sequence of little-endian "
+          "floats, even if its scale factor is given as a positive number."));
+
+ConfigVariableBool pfm_reverse_dimensions
+("pfm-reverse-dimensions", false,
+ PRC_DESC("Understands that the width and height of a pfm file are given "
+          "backwards, in the form height width instead of width height, "
+          "on input.  Does not affect output, which is always written width height."));
+
 ////////////////////////////////////////////////////////////////////
 //     Function: init_libgrutil
 //  Description: Initializes the library.  This must be called at

+ 3 - 0
panda/src/grutil/config_grutil.h

@@ -35,6 +35,9 @@ extern ConfigVariableInt scene_graph_analyzer_meter_layer_sort;
 extern ConfigVariableDouble scene_graph_analyzer_meter_scale;
 extern ConfigVariableDouble scene_graph_analyzer_meter_side_margins;
 
+extern ConfigVariableBool pfm_force_littleendian;
+extern ConfigVariableBool pfm_reverse_dimensions;
+
 extern EXPCL_PANDA_GRUTIL void init_libgrutil();
 
 #endif

+ 1 - 0
panda/src/grutil/grutil_composite2.cxx

@@ -5,3 +5,4 @@
 #include "rigidBodyCombiner.cxx"
 #include "meshDrawer.cxx"
 #include "meshDrawer2D.cxx"
+#include "pfmFile.cxx"

+ 182 - 0
panda/src/grutil/pfmFile.I

@@ -0,0 +1,182 @@
+// Filename: pfmFile.I
+// Created by:  drose (23Dec10)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: PfmFile::is_valid
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE bool PfmFile::
+is_valid() const {
+  return _num_channels != 0 && (_x_size * _y_size == (int)_table.size());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PfmFile::get_x_size
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE int PfmFile::
+get_x_size() const {
+  return _x_size;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PfmFile::get_y_size
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE int PfmFile::
+get_y_size() const {
+  return _y_size;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PfmFile::get_scale
+//       Access: Published
+//  Description: The "scale" is reported in the pfm header and is
+//               probably meaningless.
+////////////////////////////////////////////////////////////////////
+INLINE float PfmFile::
+get_scale() const {
+  return _scale;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PfmFile::get_num_channels
+//       Access: Published
+//  Description: A pfm file can be either 1-channel
+//               (get_num_channels() == 1) or 3-channel
+//               (get_num_channels() == 3).  In the case of a
+//               1-channel file, the values can be found in the x
+//               component of each point, and the y and z components
+//               are unused.
+////////////////////////////////////////////////////////////////////
+INLINE int PfmFile::
+get_num_channels() const {
+  return _num_channels;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PfmFile::has_point
+//       Access: Published
+//  Description: Returns true if there is a valid point at x, y.  This
+//               always returns true unless the zero_special flag is
+//               enabled, in which case it returns false if the point
+//               at x, y is zero.
+////////////////////////////////////////////////////////////////////
+INLINE bool PfmFile::
+has_point(int x, int y) const {
+  if ((x >= 0 && x < _x_size) && 
+      (y >= 0 && y < _y_size)) {
+    return (!_zero_special || _table[y * _x_size + x] != LPoint3f::zero());
+  }
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PfmFile::get_point
+//       Access: Published
+//  Description: Returns the 3-component point value at the indicated
+//               point.  In a 1-channel image, the channel value is in
+//               the x component.
+////////////////////////////////////////////////////////////////////
+INLINE const LPoint3f &PfmFile::
+get_point(int x, int y) const {
+  nassertr(x >= 0 && x < _x_size, LPoint3f::zero());
+  nassertr(y >= 0 && y < _y_size, LPoint3f::zero());
+  return _table[y * _x_size + x];
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PfmFile::set_point
+//       Access: Published
+//  Description: Replaces the 3-component point value at the indicated
+//               point.  In a 1-channel image, the channel value is in
+//               the x component.
+////////////////////////////////////////////////////////////////////
+INLINE void PfmFile::
+set_point(int x, int y, const LVecBase3f &point) {
+  nassertv(x >= 0 && x < _x_size);
+  nassertv(y >= 0 && y < _y_size);
+  _table[y * _x_size + x] = point;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PfmFile::modify_point
+//       Access: Published
+//  Description: Returns a modifiable 3-component point value at the
+//               indicated point.
+////////////////////////////////////////////////////////////////////
+INLINE LPoint3f &PfmFile::
+modify_point(int x, int y) {
+#ifndef NDEBUG
+  static LPoint3f dummy_value = LPoint3f::zero();
+  nassertr(x >= 0 && x < _x_size, dummy_value);
+  nassertr(y >= 0 && y < _y_size, dummy_value);
+#endif
+
+  return _table[y * _x_size + x];
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PfmFile::set_zero_special
+//       Access: Published
+//  Description: Sets the zero_special flag.  When this flag is true,
+//               values of (0, 0, 0) in the pfm file are treated as a
+//               special case, and are not processed.
+////////////////////////////////////////////////////////////////////
+INLINE void PfmFile::
+set_zero_special(bool zero_special) {
+  _zero_special = zero_special;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PfmFile::get_zero_special
+//       Access: Published
+//  Description: Returns the zero_special flag.  When this flag is true,
+//               values of (0, 0, 0) in the pfm file are treated as a
+//               special case, and are not processed.
+////////////////////////////////////////////////////////////////////
+INLINE bool PfmFile::
+get_zero_special() const {
+  return _zero_special;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PfmFile::set_vis_inverse
+//       Access: Published
+//  Description: Sets the vis_inverse flag.  When this flag is true,
+//               vis meshes and point clouds are generated with the
+//               3-d depth value in the texture coordinates, and the
+//               2-d index value in the vertex position.  When it is
+//               false, meshes are generated normally, with the 3-d
+//               depth value in the vertex position and the 2-d index
+//               value in the texture coordinates.
+////////////////////////////////////////////////////////////////////
+INLINE void PfmFile::
+set_vis_inverse(bool vis_inverse) {
+  _vis_inverse = vis_inverse;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PfmFile::get_vis_inverse
+//       Access: Published
+//  Description: Returns the vis_inverse flag.  See set_vis_inverse().
+////////////////////////////////////////////////////////////////////
+INLINE bool PfmFile::
+get_vis_inverse() const {
+  return _vis_inverse;
+}

+ 962 - 0
panda/src/grutil/pfmFile.cxx

@@ -0,0 +1,962 @@
+// Filename: pfmFile.cxx
+// Created by:  drose (23Dec10)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+#include "config_grutil.h"
+#include "pfmFile.h"
+#include "virtualFileSystem.h"
+#include "pandaFileStream.h"
+#include "littleEndian.h"
+#include "bigEndian.h"
+#include "cmath.h"
+#include "geomNode.h"
+#include "geom.h"
+#include "geomVertexData.h"
+#include "geomVertexFormat.h"
+#include "geomPoints.h"
+#include "geomTriangles.h"
+#include "geomVertexWriter.h"
+#include "look_at.h"
+
+////////////////////////////////////////////////////////////////////
+//     Function: PfmFile::Constructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+PfmFile::
+PfmFile() {
+  _zero_special = false;
+  _vis_inverse = false;
+  clear();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PfmFile::Copy Constructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+PfmFile::
+PfmFile(const PfmFile &copy) :
+  _table(copy._table),
+  _x_size(copy._x_size),
+  _y_size(copy._y_size),
+  _scale(copy._scale),
+  _num_channels(copy._num_channels),
+  _zero_special(copy._zero_special)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PfmFile::Copy Assignment
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+void PfmFile::
+operator = (const PfmFile &copy) {
+  _table = copy._table;
+  _x_size = copy._x_size;
+  _y_size = copy._y_size;
+  _scale = copy._scale;
+  _num_channels = copy._num_channels;
+  _zero_special = copy._zero_special;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PfmFile::clear
+//       Access: Published
+//  Description: Eliminates all data in the file.
+////////////////////////////////////////////////////////////////////
+void PfmFile::
+clear() {
+  _x_size = 0;
+  _y_size = 0;
+  _num_channels = 3;
+  _table.clear();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PfmFile::clear
+//       Access: Published
+//  Description: Resets to an empty table with a specific size.
+////////////////////////////////////////////////////////////////////
+void PfmFile::
+clear(int x_size, int y_size, int num_channels) {
+  nassertv(num_channels == 1 || num_channels == 3);
+  nassertv(x_size >= 0 && y_size >= 0);
+  _x_size = x_size;
+  _y_size = y_size;
+  _num_channels = _num_channels;
+
+  _table.clear();
+  int size = _x_size * _y_size;
+  _table.insert(_table.end(), size, LPoint3f::zero());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PfmFile::read
+//       Access: Published
+//  Description: Reads the PFM data from the indicated file, returning
+//               true on success, false on failure.
+////////////////////////////////////////////////////////////////////
+bool PfmFile::
+read(const Filename &fullpath) {
+  VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
+
+  Filename filename = Filename::binary_filename(fullpath);
+  PT(VirtualFile) file = vfs->get_file(filename);
+  if (file == (VirtualFile *)NULL) {
+    // No such file.
+    grutil_cat.error()
+      << "Could not find " << fullpath << "\n";
+    return false;
+  }
+
+  if (grutil_cat.is_debug()) {
+    grutil_cat.debug()
+      << "Reading PFM file " << filename << "\n";
+  }
+
+  istream *in = file->open_read_file(true);
+  bool success = read(*in);
+  vfs->close_read_file(in);
+
+  return success;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PfmFile::read
+//       Access: Published
+//  Description: Reads the PFM data from the indicated stream,
+//               returning true on success, false on failure.
+////////////////////////////////////////////////////////////////////
+bool PfmFile::
+read(istream &in) {
+  clear();
+
+  string identifier;
+  in >> identifier;
+
+  if (identifier == "PF") {
+    _num_channels = 3;
+  } else if (identifier == "Pf") {
+    _num_channels = 1;
+  } else {
+    grutil_cat.error()
+      << "Not a pfm file.\n";
+    return false;
+  }
+
+  int width, height;
+  float scale;
+  in >> width >> height >> scale;
+  if (!in) {
+    grutil_cat.error()
+      << "Error parsing pfm header.\n";
+    return false;
+  }
+
+  // Skip the last newline/whitespace character before the raw data
+  // begins.
+  in.get();
+
+  bool little_endian = false;
+  if (scale < 0) {
+    scale = -scale;
+    little_endian = true;
+  }
+  if (pfm_force_littleendian) {
+    little_endian = true;
+  }
+  if (pfm_reverse_dimensions) {
+    int t = width;
+    width = height;
+    height = t;
+  }
+
+  _x_size = width;
+  _y_size = height;
+  _scale = scale;
+
+  // So far, so good.  Now read the data.
+  int size = _x_size * _y_size;
+  _table.reserve(size);
+
+  if (little_endian) {
+    for (int i = 0; i < size; ++i) {
+      LPoint3f point = LPoint3f::zero();
+      for (int ci = 0; ci < _num_channels; ++ci) {
+        float data;
+        in.read((char *)&data, sizeof(data));
+        LittleEndian value(&data, sizeof(data));
+        value.store_value(&(point[ci]), sizeof(point[ci]));
+      }
+      _table.push_back(point);
+    }
+  } else {
+    for (int i = 0; i < size; ++i) {
+      LPoint3f point = LPoint3f::zero();
+      for (int ci = 0; ci < _num_channels; ++ci) {
+        float data;
+        in.read((char *)&data, sizeof(data));
+        BigEndian value(&data, sizeof(data));
+        value.store_value(&(point[ci]), sizeof(point[ci]));
+      }
+      _table.push_back(point);
+    }
+  }
+
+  if (in.fail() && !in.eof()) {
+    return false;
+  }
+  
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PfmFile::write
+//       Access: Published
+//  Description: Writes the PFM data to the indicated file, returning
+//               true on success, false on failure.
+////////////////////////////////////////////////////////////////////
+bool PfmFile::
+write(const Filename &fullpath) {
+  Filename filename = Filename::binary_filename(fullpath);
+  pofstream out;
+  if (!filename.open_write(out)) {
+    grutil_cat.error()
+      << "Unable to open " << filename << "\n";
+    return false;
+  }
+
+  if (grutil_cat.is_debug()) {
+    grutil_cat.debug()
+      << "Writing PFM file " << filename << "\n";
+  }
+
+  return write(out);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PfmFile::write
+//       Access: Published
+//  Description: Writes the PFM data to the indicated stream,
+//               returning true on success, false on failure.
+////////////////////////////////////////////////////////////////////
+bool PfmFile::
+write(ostream &out) {
+  nassertr(is_valid(), false);
+
+  if (_num_channels == 1) {
+    out << "Pf\n";
+  } else {
+    out << "PF\n";
+  }
+  out << _x_size << " " << _y_size << "\n";
+
+  float scale = cabs(_scale);
+  if (scale == 0.0f) {
+    scale = 1.0f;
+  }
+#ifndef WORDS_BIGENDIAN
+  // Little-endian computers must write a negative scale to indicate
+  // the little-endian nature of the output.
+  scale = -scale;
+#endif
+  out << scale << "\n";
+
+  int size = _x_size * _y_size;
+  for (int i = 0; i < size; ++i) {
+    const LPoint3f &point = _table[i];
+    for (int ci = 0; ci < _num_channels; ++ci) {
+      float data = point[ci];
+      out.write((const char *)&data, sizeof(data));
+    }
+  }
+
+  if (out.fail()) {
+    return false;
+  }
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PfmFile::calc_average_point
+//       Access: Published
+//  Description: Computes the unweighted average point of all points
+//               within the box centered at (x, y) with the indicated
+//               Manhattan-distance radius.  Missing points are
+//               assigned the value of their nearest neighbor.
+//               Returns true if successful, or false if the point
+//               value cannot be determined.
+////////////////////////////////////////////////////////////////////
+bool PfmFile::
+calc_average_point(LPoint3f &result, double x, double y, double radius) const {
+  result = LPoint3f::zero();
+
+  int min_x = int(ceil(x - radius));
+  int min_y = int(ceil(y - radius));
+  int max_x = int(floor(x + radius));
+  int max_y = int(floor(y + radius));
+
+  // We first construct a mini-grid of x_size by y_size integer values
+  // to index into the main table.  This indirection allows us to fill
+  // in the holes in the mini-grid with the nearest known values
+  // before we compute the average.
+  int x_size = max_x - min_x + 1;
+  int y_size = max_y - min_y + 1;
+  int size = x_size * y_size;
+  if (size == 0) {
+    return false;
+  }
+
+  pvector<MiniGridCell> mini_grid;
+  mini_grid.insert(mini_grid.end(), size, MiniGridCell());
+
+  // Now collect the known data points and apply them to the
+  // mini-grid.
+  min_x = max(min_x, 0);
+  min_y = max(min_y, 0);
+  max_x = min(max_x, _x_size - 1);
+  max_y = min(max_y, _y_size - 1);
+
+  bool got_any = false;
+  int xi, yi;
+  for (yi = min_y; yi <= max_y; ++yi) {
+    for (xi = min_x; xi <= max_x; ++xi) {
+      const LPoint3f &p = _table[yi * _x_size + xi];
+      if (_zero_special && p == LPoint3f::zero()) {
+        continue;
+      }
+
+      int gi = (yi - min_y) * y_size + (xi - min_x);
+      nassertr(gi >= 0 && gi < size, false);
+      mini_grid[gi]._ti = yi * _x_size + xi;
+      mini_grid[gi]._dist = 0;
+      got_any = true;
+    }
+  }
+
+  if (!got_any) {
+    return false;
+  }
+
+  // Now recursively fill in any missing holes in the mini-grid.
+  for (yi = 0; yi < y_size; ++yi) {
+    for (xi = 0; xi < x_size; ++xi) {
+      int gi = yi * x_size + xi;
+      if (mini_grid[gi]._dist == 0) {
+        int ti = mini_grid[gi]._ti;
+        fill_mini_grid(&mini_grid[0], x_size, y_size, xi + 1, yi, 1, ti);
+        fill_mini_grid(&mini_grid[0], x_size, y_size, xi - 1, yi, 1, ti);
+        fill_mini_grid(&mini_grid[0], x_size, y_size, xi, yi + 1, 1, ti);
+        fill_mini_grid(&mini_grid[0], x_size, y_size, xi, yi - 1, 1, ti);
+      }
+    }
+  }
+
+  // Now the mini-grid is completely filled, so we can compute the
+  // average.
+  for (int gi = 0; gi < size; ++gi) {
+    int ti = mini_grid[gi]._ti;
+    nassertr(ti >= 0 && ti < (int)_table.size(), false);
+    result += _table[ti];
+  }
+
+  result /= float(size);
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PfmFile::calc_min_max
+//       Access: Published
+//  Description: Calculates the minimum and maximum x, y, and z depth
+//               component values, representing the bounding box of
+//               depth values, and places them in the indicated
+//               vectors.  Returns true if successful, false if the
+//               mesh contains no points.
+////////////////////////////////////////////////////////////////////
+bool PfmFile::
+calc_min_max(LVecBase3f &min_depth, LVecBase3f &max_depth) const {
+  bool any_points = false;
+
+  min_depth = LVecBase3f::zero();
+  max_depth = LVecBase3f::zero();
+
+  Table::const_iterator ti;
+  for (ti = _table.begin(); ti != _table.end(); ++ti) {
+    const LPoint3f &p = (*ti);
+    if (_zero_special && p == LPoint3f::zero()) {
+      continue;
+    }
+    
+    if (!any_points) {
+      min_depth = p;
+      max_depth = p;
+      any_points = true;
+    } else {
+      min_depth[0] = min(min_depth[0], p[0]);
+      min_depth[1] = min(min_depth[1], p[1]);
+      min_depth[2] = min(min_depth[2], p[2]);
+      max_depth[0] = max(max_depth[0], p[0]);
+      max_depth[1] = max(max_depth[1], p[1]);
+      max_depth[2] = max(max_depth[2], p[2]);
+    }
+  }
+
+  return any_points;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PfmFile::resize
+//       Access: Published
+//  Description: Applies a simple filter to resample the pfm file
+//               in-place to the indicated size.  Don't confuse this
+//               with applying a scale to all of the points via
+//               xform().
+////////////////////////////////////////////////////////////////////
+void PfmFile::
+resize(int new_x_size, int new_y_size) {
+  if (_x_size == 0 || _y_size == 0 || new_x_size == 0 || new_y_size == 0) {
+    clear(new_x_size, new_y_size, _num_channels);
+    return;
+  }
+
+  if (new_x_size == _x_size && new_y_size == _y_size) {
+    return;
+  }
+
+  Table new_data;
+  new_data.reserve(new_x_size * new_y_size);
+
+  double from_x0, from_x1, from_y0, from_y1;
+
+  double x_scale = (double)(_x_size - 1) / (double)(new_x_size - 1);
+  double y_scale = (double)(_y_size - 1) / (double)(new_y_size - 1);
+
+  from_y0 = 0.0;
+  for (int to_y = 0; to_y < new_y_size; ++to_y) {
+    from_y1 = (to_y + 0.5) * y_scale;
+    from_y1 = min(from_y1, (double) _y_size);
+
+    from_x0 = 0.0;
+    for (int to_x = 0; to_x < new_x_size; ++to_x) {
+      from_x1 = (to_x + 0.5) * x_scale;
+      from_x1 = min(from_x1, (double) _x_size);
+
+      // Now the box from (from_x0, from_y0) - (from_x1, from_y1)
+      // but not including (from_x1, from_y1) maps to the pixel (to_x, to_y).
+      LPoint3f result;
+      box_filter_region(result, from_x0, from_y0, from_x1, from_y1);
+      new_data.push_back(result);
+
+      from_x0 = from_x1;
+    }
+    from_y0 = from_y1;
+  }
+
+  _table.swap(new_data);
+  _x_size = new_x_size;
+  _y_size = new_y_size;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PfmFile::reverse_rows
+//       Access: Published
+//  Description: Performs an in-place reversal of the row (y) data.
+////////////////////////////////////////////////////////////////////
+void PfmFile::
+reverse_rows() {
+  nassertv(is_valid());
+
+  Table reversed;
+  reversed.reserve(_table.size());
+  for (int yi = 0; yi < _y_size; ++yi) {
+    int source_yi = _y_size - 1 - yi;
+    int start = source_yi * _x_size;
+    reversed.insert(reversed.end(), 
+                    _table.begin() + start, _table.begin() + start + _x_size);
+  }
+
+  nassertv(reversed.size() == _table.size());
+  _table.swap(reversed);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PfmFile::xform
+//       Access: Published
+//  Description: Applies the indicated transform matrix to all points
+//               in-place.
+////////////////////////////////////////////////////////////////////
+void PfmFile::
+xform(const LMatrix4f &transform) {
+  nassertv(is_valid());
+
+  Table::iterator ti;
+  for (ti = _table.begin(); ti != _table.end(); ++ti) {
+    if (_zero_special && (*ti) == LPoint3f::zero()) {
+      continue;
+    }
+
+    (*ti) = (*ti) * transform;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PfmFile::compute_planar_bounds
+//       Access: Published
+//  Description: Computes the minmax bounding volume of the points in
+//               3-D space, assuming the points represent a
+//               mostly-planar surface.
+//
+//               This algorithm works by sampling the (square)
+//               sample_radius pixels at three of the four point_dist
+//               corners around the center (cx - pd, cx + pd) and so
+//               on, to determine the plane of the surface.  Then all
+//               of the points are projected into that plane and the
+//               bounding volume within that plane is determined.
+//
+//               point_dist and sample_radius are in UV space, i.e. in
+//               the range 0..1.
+////////////////////////////////////////////////////////////////////
+PT(BoundingHexahedron) PfmFile::
+compute_planar_bounds(double point_dist, double sample_radius) const {
+  LPoint3f p0, p1, p2;
+  compute_sample_point(p0, 0.5 + point_dist, 0.5 - point_dist, sample_radius);
+  compute_sample_point(p1, 0.5 + point_dist, 0.5 + point_dist, sample_radius);
+  compute_sample_point(p2, 0.5 - point_dist, 0.5 + point_dist, sample_radius);
+
+  LPoint3f normal;
+
+  normal[0] = p0[1] * p1[2] - p0[2] * p1[1];
+  normal[1] = p0[2] * p1[0] - p0[0] * p1[2];
+  normal[2] = p0[0] * p1[1] - p0[1] * p1[0];
+
+  normal[0] += p1[1] * p2[2] - p1[2] * p2[1];
+  normal[1] += p1[2] * p2[0] - p1[0] * p2[2];
+  normal[2] += p1[0] * p2[1] - p1[1] * p2[0];
+
+  normal[0] += p2[1] * p0[2] - p2[2] * p0[1];
+  normal[1] += p2[2] * p0[0] - p2[0] * p0[2];
+  normal[2] += p2[0] * p0[1] - p2[1] * p0[0];
+
+  normal.normalize();
+
+  // Compute the transform necessary to rotate all of the points into
+  // the Y = 0 plane.
+  LMatrix4f rotate;
+  look_at(rotate, normal, p1 - p0);
+
+  LMatrix4f rinv;
+  rinv.invert_from(rotate);
+
+  LPoint3f trans = p0 * rinv;
+  rinv.set_row(3, -trans);
+  rotate.invert_from(rinv);
+
+  // Now determine the minmax in the XZ plane.
+  float min_x, min_z, max_x, max_z;
+  bool got_point = false;
+  Table::const_iterator ti;
+  for (ti = _table.begin(); ti != _table.end(); ++ti) {
+    if (_zero_special && (*ti) == LPoint3f::zero()) {
+      continue;
+    }
+    LPoint3f point = (*ti) * rinv;
+    if (!got_point) {
+      min_x = point[0];
+      min_z = point[2];
+      max_x = point[0];
+      max_z = point[2];
+      got_point = true;
+    } else {
+      min_x = min(min_x, point[0]);
+      min_z = min(min_z, point[2]);
+      max_x = max(max_x, point[0]);
+      max_z = max(max_z, point[2]);
+    }
+  }
+
+  PT(BoundingHexahedron) bounds = new BoundingHexahedron
+    (LPoint3f(min_x, 0, min_z), LPoint3f(max_x, 0, min_z),
+     LPoint3f(min_x, 0, max_z), LPoint3f(max_x, 0, max_z),
+     LPoint3f(min_x, 0, min_z), LPoint3f(max_x, 0, min_z),
+     LPoint3f(min_x, 0, max_z), LPoint3f(max_x, 0, max_z));
+
+  // Rotate the bounding volume back into the original space of the
+  // screen.
+  bounds->xform(rotate);
+
+  return bounds;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PfmFile::generate_vis_points
+//       Access: Published
+//  Description: Creates a point cloud with the points of the pfm as
+//               3-d coordinates in space, and texture coordinates
+//               ranging from 0 .. 1 based on the position within the
+//               pfm grid.
+////////////////////////////////////////////////////////////////////
+NodePath PfmFile::
+generate_vis_points() const {
+  nassertr(is_valid(), NodePath());
+
+  CPT(GeomVertexFormat) format;
+  if (_vis_inverse) {
+    // We need a 3-d texture coordinate if we're inverted the vis.
+    GeomVertexArrayFormat *v3t3 = new GeomVertexArrayFormat
+      (InternalName::get_vertex(), 3, 
+       Geom::NT_float32, Geom::C_point,
+       InternalName::get_texcoord(), 3, 
+       Geom::NT_float32, Geom::C_texcoord);
+    format = GeomVertexFormat::register_format(v3t3);
+  } else {
+    format = GeomVertexFormat::get_v3t2();
+  }
+
+  PT(GeomVertexData) vdata = new GeomVertexData
+    ("points", format, Geom::UH_static);
+  vdata->set_num_rows(_x_size * _y_size);
+  GeomVertexWriter vertex(vdata, InternalName::get_vertex());
+  GeomVertexWriter texcoord(vdata, InternalName::get_texcoord());
+  
+  for (int yi = 0; yi < _y_size; ++yi) {
+    for (int xi = 0; xi < _x_size; ++xi) {
+      const LPoint3f &point = get_point(xi, yi);
+      LPoint2f uv(float(xi) / float(_x_size - 1),
+                  float(yi) / float(_y_size - 1));
+      if (_vis_inverse) {
+        vertex.add_data2f(uv);
+        texcoord.add_data3f(point);
+      } else {
+        vertex.add_data3f(point);
+        texcoord.add_data2f(uv);
+      }
+    }
+  }
+  
+  PT(Geom) geom = new Geom(vdata);
+  PT(GeomPoints) points = new GeomPoints(Geom::UH_static);
+  points->add_next_vertices(_x_size * _y_size);
+  geom->add_primitive(points);
+  
+  PT(GeomNode) gnode = new GeomNode("");
+  gnode->add_geom(geom);
+  return NodePath(gnode);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PfmFile::generate_vis_mesh
+//       Access: Published
+//  Description: Creates a triangle mesh with the points of the pfm as
+//               3-d coordinates in space, and texture coordinates
+//               ranging from 0 .. 1 based on the position within the
+//               pfm grid.
+////////////////////////////////////////////////////////////////////
+NodePath PfmFile::
+generate_vis_mesh(bool double_sided) const {
+  nassertr(is_valid(), NodePath());
+  
+  PT(GeomNode) gnode = new GeomNode("");
+
+  PT(Geom) geom1 = make_vis_mesh_geom(false);
+  gnode->add_geom(geom1);
+
+  if (double_sided) {
+    PT(Geom) geom2 = make_vis_mesh_geom(true);
+    gnode->add_geom(geom2);
+  }
+
+  return NodePath(gnode);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PfmFile::make_vis_mesh_geom
+//       Access: Private
+//  Description: Returns a triangle mesh for the pfm.  If inverted is
+//               true, the mesh is facing the opposite direction.
+////////////////////////////////////////////////////////////////////
+PT(Geom) PfmFile::
+make_vis_mesh_geom(bool inverted) const {
+
+  CPT(GeomVertexFormat) format;
+  if (_vis_inverse) {
+    // We need a 3-d texture coordinate if we're inverted the vis.
+    // But we don't need normals in that case.
+    GeomVertexArrayFormat *v3t3 = new GeomVertexArrayFormat
+      (InternalName::get_vertex(), 3, 
+       Geom::NT_float32, Geom::C_point,
+       InternalName::get_texcoord(), 3, 
+       Geom::NT_float32, Geom::C_texcoord);
+    format = GeomVertexFormat::register_format(v3t3);
+  } else {
+    // Otherwise, we only need a 2-d texture coordinate, and we do
+    // want normals.
+    format = GeomVertexFormat::get_v3n3t2();
+  }
+
+  PT(GeomVertexData) vdata = new GeomVertexData
+    ("mesh", format, Geom::UH_static);
+  int num_vertices = _x_size * _y_size;
+  vdata->set_num_rows(num_vertices);
+  GeomVertexWriter vertex(vdata, InternalName::get_vertex());
+  GeomVertexWriter normal(vdata, InternalName::get_normal());
+  GeomVertexWriter texcoord(vdata, InternalName::get_texcoord());
+
+  for (int yi = 0; yi < _y_size; ++yi) {
+    for (int xi = 0; xi < _x_size; ++xi) {
+      const LPoint3f &point = get_point(xi, yi);
+      LPoint2f uv(float(xi) / float(_x_size - 1),
+                  float(yi) / float(_y_size - 1));
+
+      if (_vis_inverse) {
+        vertex.add_data2f(uv);
+        texcoord.add_data3f(point);
+      } else {
+        vertex.add_data3f(point);
+        texcoord.add_data2f(uv);
+
+        // Calculate the normal based on two neighboring vertices.
+        LPoint3f v[3];
+        v[0] = get_point(xi, yi);
+        if (xi + 1 < _x_size) {
+          v[1] = get_point(xi + 1, yi);
+        } else {
+          v[1] = v[0];
+          v[0] = get_point(xi - 1, yi);
+        }
+        
+        if (yi + 1 < _y_size) {
+          v[2] = get_point(xi, yi + 1);
+        } else {
+          v[2] = v[0];
+          v[0] = get_point(xi, yi - 1);
+        }
+        
+        LVector3f n = LVector3f::zero();
+        for (int i = 0; i < 3; ++i) {
+          const LPoint3f &v0 = v[i];
+          const LPoint3f &v1 = v[(i + 1) % 3];
+          n[0] += v0[1] * v1[2] - v0[2] * v1[1];
+          n[1] += v0[2] * v1[0] - v0[0] * v1[2];
+          n[2] += v0[0] * v1[1] - v0[1] * v1[0];
+        }
+        n.normalize();
+        if (inverted) {
+          n = -n;
+        }
+        normal.add_data3f(n);
+      }
+    }
+  }
+  
+  PT(Geom) geom = new Geom(vdata);
+  PT(GeomTriangles) tris = new GeomTriangles(Geom::UH_static);
+
+  if (num_vertices > 0xffff) {
+    // We need 32-bit indices.
+    tris->set_index_type(Geom::NT_uint32);
+  }
+
+  // We get direct access to the vertices data so we can speed things
+  // up by pre-specifying the number of vertices.  Need a better
+  // interface to do this same thing using the high-level access
+  // methods.
+  int num_indices = (_x_size - 1) * (_y_size - 1) * 6;
+
+  PT(GeomVertexArrayData) indices = tris->modify_vertices();
+  indices->set_num_rows(num_indices);
+  GeomVertexWriter index(indices, 0);
+
+  int actual_num_indices = 0;
+  for (int yi = 0; yi < _y_size - 1; ++yi) {
+    for (int xi = 0; xi < _x_size - 1; ++xi) {
+
+      if (_zero_special) {
+        if (get_point(xi, yi) == LPoint3f::zero() ||
+            get_point(xi, yi + 1) == LPoint3f::zero() ||
+            get_point(xi + 1, yi + 1) == LPoint3f::zero() ||
+            get_point(xi + 1, yi) == LPoint3f::zero()) {
+          continue;
+        }
+      }
+
+      int vi0 = ((xi) + (yi) * _x_size);
+      int vi1 = ((xi) + (yi + 1) * _x_size);
+      int vi2 = ((xi + 1) + (yi + 1) * _x_size);
+      int vi3 = ((xi + 1) + (yi) * _x_size);
+
+      if (inverted) {
+        index.add_data1i(vi2);
+        index.add_data1i(vi0);
+        index.add_data1i(vi1);
+        
+        index.add_data1i(vi3);
+        index.add_data1i(vi0);
+        index.add_data1i(vi2);
+      } else {
+        index.add_data1i(vi2);
+        index.add_data1i(vi1);
+        index.add_data1i(vi0);
+        
+        index.add_data1i(vi3);
+        index.add_data1i(vi2);
+        index.add_data1i(vi0);
+      }
+
+      actual_num_indices += 6;
+    }
+  }
+  indices->set_num_rows(actual_num_indices);
+  geom->add_primitive(tris);
+
+  return geom;
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: PfmFile::compute_sample_point
+//       Access: Private
+//  Description: Computes the average of all the point within
+//               sample_radius (manhattan distance) and the indicated
+//               point.
+//
+//               Unlike box_filter_*(), these point values are given
+//               in UV space, in the range 0..1.
+////////////////////////////////////////////////////////////////////
+void PfmFile::
+compute_sample_point(LPoint3f &result,
+                     double x, double y, double sample_radius) const {
+  x *= _x_size;
+  y *= _y_size;
+  double xr = sample_radius * _x_size;
+  double yr = sample_radius * _y_size;
+  box_filter_region(result, x - xr, y - yr, x + xr, y + yr);
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: PfmFile::box_filter_region
+//       Access: Private
+//  Description: Averages all the points in the rectangle from x0
+//               .. y0 to x1 .. y1 into result.  The region may be
+//               defined by floating-point boundaries; the result will
+//               be weighted by the degree of coverage of each
+//               included point.
+////////////////////////////////////////////////////////////////////
+void PfmFile::
+box_filter_region(LPoint3f &result,
+                  double x0, double y0, double x1, double y1) const {
+  result = LPoint3f::zero();
+  double coverage = 0.0;
+
+  assert(y0 >= 0.0 && y1 >= 0.0);
+
+  int y = (int)y0;
+  // Get the first (partial) row
+  box_filter_line(result, coverage, x0, y, x1, (double)(y+1)-y0);
+
+  int y_last = (int)y1;
+  if (y < y_last) {
+    y++;
+    while (y < y_last) {
+      // Get each consecutive (complete) row
+      box_filter_line(result, coverage, x0, y, x1, 1.0);
+      y++;
+    }
+
+    // Get the final (partial) row
+    double y_contrib = y1 - (double)y_last;
+    if (y_contrib > 0.0001) {
+      box_filter_line(result, coverage, x0, y, x1, y_contrib);
+    }
+  }
+
+  if (coverage != 0.0) {
+    result /= coverage;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PfmFile::box_filter_line
+//       Access: Private
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void PfmFile::
+box_filter_line(LPoint3f &result, double &coverage,
+                double x0, int y, double x1, double y_contrib) const {
+  int x = (int)x0;
+  // Get the first (partial) xel
+  box_filter_point(result, coverage, x, y, (double)(x+1)-x0, y_contrib);
+
+  int x_last = (int)x1;
+  if (x < x_last) {
+    x++;
+    while (x < x_last) {
+      // Get each consecutive (complete) xel
+      box_filter_point(result, coverage, x, y, 1.0, y_contrib);
+      x++;
+    }
+
+    // Get the final (partial) xel
+    double x_contrib = x1 - (double)x_last;
+    if (x_contrib > 0.0001) {
+      box_filter_point(result, coverage, x, y, x_contrib, y_contrib);
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PfmFile::box_filter_point
+//       Access: Private
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void PfmFile::
+box_filter_point(LPoint3f &result, double &coverage,
+                 int x, int y, double x_contrib, double y_contrib) const {
+  const LPoint3f &point = get_point(x, y);
+  if (_zero_special && point == LPoint3f::zero()) {
+    return;
+  }
+
+  double contrib = x_contrib * y_contrib;
+  result += point * contrib;
+  coverage += contrib;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PfmFile::fill_mini_grid
+//       Access: Private
+//  Description: A support function for calc_average_point(), this
+//               recursively fills in the holes in the mini_grid data
+//               with the index to the nearest value.
+////////////////////////////////////////////////////////////////////
+void PfmFile::
+fill_mini_grid(MiniGridCell *mini_grid, int x_size, int y_size, 
+               int xi, int yi, int dist, int ti) const {
+  if (xi < 0 || xi >= x_size || yi < 0 || yi >= y_size) {
+    // Out of bounds.
+    return;
+  }
+
+  int gi = yi * x_size + xi;
+  if (mini_grid[gi]._dist == -1 || mini_grid[gi]._dist > dist) {
+    // Here's an undefined value that we need to populate.
+    mini_grid[gi]._dist = dist;
+    mini_grid[gi]._ti = ti;
+    fill_mini_grid(mini_grid, x_size, y_size, xi + 1, yi, dist + 1, ti);
+    fill_mini_grid(mini_grid, x_size, y_size, xi - 1, yi, dist + 1, ti);
+    fill_mini_grid(mini_grid, x_size, y_size, xi, yi + 1, dist + 1, ti);
+    fill_mini_grid(mini_grid, x_size, y_size, xi, yi - 1, dist + 1, ti);
+  }
+}

+ 109 - 0
panda/src/grutil/pfmFile.h

@@ -0,0 +1,109 @@
+// Filename: pfmFile.h
+// Created by:  drose (23Dec10)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) Carnegie Mellon University.  All rights reserved.
+//
+// All use of this software is subject to the terms of the revised BSD
+// license.  You should have received a copy of this license along
+// with this source code in a file named "LICENSE."
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef PFMFILE_H
+#define PFMFILE_H
+
+#include "pandabase.h"
+#include "luse.h"
+#include "nodePath.h"
+#include "boundingHexahedron.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : PfmFile
+// Description : Defines a pfm file, a 2-d table of floating-point
+//               numbers, either 3-component or 1-component.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA_GRUTIL PfmFile {
+PUBLISHED:
+  PfmFile();
+  PfmFile(const PfmFile &copy);
+  void operator = (const PfmFile &copy);
+
+  void clear();
+  void clear(int x_size, int y_size, int num_channels);
+
+  bool read(const Filename &fullpath);
+  bool read(istream &in);
+  bool write(const Filename &fullpath);
+  bool write(ostream &out);
+
+  INLINE bool is_valid() const;
+
+  INLINE int get_x_size() const;
+  INLINE int get_y_size() const;
+  INLINE float get_scale() const;
+  INLINE int get_num_channels() const;
+
+  INLINE bool has_point(int x, int y) const;
+  INLINE const LPoint3f &get_point(int x, int y) const;
+  INLINE void set_point(int x, int y, const LVecBase3f &point);
+  INLINE LPoint3f &modify_point(int x, int y);
+
+  bool calc_average_point(LPoint3f &result, double x, double y, double radius) const;
+  bool calc_min_max(LVecBase3f &min_points, LVecBase3f &max_points) const;
+
+  INLINE void set_zero_special(bool zero_special);
+  INLINE bool get_zero_special() const;
+
+  void resize(int new_x_size, int new_y_size);
+  void reverse_rows();
+  void xform(const LMatrix4f &transform);
+
+  PT(BoundingHexahedron) compute_planar_bounds(double point_dist, double sample_radius) const;
+
+  INLINE void set_vis_inverse(bool vis_inverse);
+  INLINE bool get_vis_inverse() const;
+
+  NodePath generate_vis_points() const;
+  NodePath generate_vis_mesh(bool double_sided) const;
+
+private:
+  PT(Geom) make_vis_mesh_geom(bool inverted) const;
+
+  void compute_sample_point(LPoint3f &result,
+                            double x, double y, double sample_radius) const;
+  void box_filter_region(LPoint3f &result,
+                         double x0, double y0, double x1, double y1) const;
+  void box_filter_line(LPoint3f &result, double &coverage,
+                       double x0, int y, double x1, double y_contrib) const;
+  void box_filter_point(LPoint3f &result, double &coverage,
+                        int x, int y, double x_contrib, double y_contrib) const;
+
+  class MiniGridCell {
+  public:
+    MiniGridCell() : _ti(-1), _dist(-1) { }
+    int _ti;
+    int _dist;
+  };
+
+  void fill_mini_grid(MiniGridCell *mini_grid, int x_size, int y_size, 
+                      int xi, int yi, int dist, int ti) const;
+
+private:
+  typedef pvector<LPoint3f> Table;
+  Table _table;
+
+  int _x_size;
+  int _y_size;
+  float _scale;
+  int _num_channels;
+
+  bool _zero_special;
+  bool _vis_inverse;
+};
+
+#include "pfmFile.I"
+
+#endif