From 2a691dd339043cbc30214b820ab09f844b48e474 Mon Sep 17 00:00:00 2001 From: David Rose Date: Mon, 14 Mar 2011 20:09:33 +0000 Subject: [PATCH] move pfmfile from pandatool --- panda/src/grutil/Sources.pp | 3 + panda/src/grutil/config_grutil.cxx | 11 + panda/src/grutil/config_grutil.h | 3 + panda/src/grutil/grutil_composite2.cxx | 1 + panda/src/grutil/pfmFile.I | 182 +++++ panda/src/grutil/pfmFile.cxx | 962 +++++++++++++++++++++++++ panda/src/grutil/pfmFile.h | 109 +++ 7 files changed, 1271 insertions(+) create mode 100755 panda/src/grutil/pfmFile.I create mode 100755 panda/src/grutil/pfmFile.cxx create mode 100755 panda/src/grutil/pfmFile.h diff --git a/panda/src/grutil/Sources.pp b/panda/src/grutil/Sources.pp index bfaeb92b66..214429ef45 100644 --- a/panda/src/grutil/Sources.pp +++ b/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 diff --git a/panda/src/grutil/config_grutil.cxx b/panda/src/grutil/config_grutil.cxx index 9716e4c259..673a156466 100644 --- a/panda/src/grutil/config_grutil.cxx +++ b/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 diff --git a/panda/src/grutil/config_grutil.h b/panda/src/grutil/config_grutil.h index 508a29eb5d..6f2ebe27d3 100644 --- a/panda/src/grutil/config_grutil.h +++ b/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 diff --git a/panda/src/grutil/grutil_composite2.cxx b/panda/src/grutil/grutil_composite2.cxx index 466b02f268..cc86c78fcc 100644 --- a/panda/src/grutil/grutil_composite2.cxx +++ b/panda/src/grutil/grutil_composite2.cxx @@ -5,3 +5,4 @@ #include "rigidBodyCombiner.cxx" #include "meshDrawer.cxx" #include "meshDrawer2D.cxx" +#include "pfmFile.cxx" diff --git a/panda/src/grutil/pfmFile.I b/panda/src/grutil/pfmFile.I new file mode 100755 index 0000000000..7fbceefb9f --- /dev/null +++ b/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; +} diff --git a/panda/src/grutil/pfmFile.cxx b/panda/src/grutil/pfmFile.cxx new file mode 100755 index 0000000000..8807f101fd --- /dev/null +++ b/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 ©) : + _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 ©) { + _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 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); + } +} diff --git a/panda/src/grutil/pfmFile.h b/panda/src/grutil/pfmFile.h new file mode 100755 index 0000000000..e7279fb76f --- /dev/null +++ b/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 ©); + void operator = (const PfmFile ©); + + 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 Table; + Table _table; + + int _x_size; + int _y_size; + float _scale; + int _num_channels; + + bool _zero_special; + bool _vis_inverse; +}; + +#include "pfmFile.I" + +#endif