open_toontown_panda3d/panda/src/grutil/pfmVizzer.cxx

1231 lines
38 KiB
C++

/**
* 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."
*
* @file pfmVizzer.cxx
* @author drose
* @date 2012-09-30
*/
#include "pfmVizzer.h"
#include "geomNode.h"
#include "geom.h"
#include "geomVertexData.h"
#include "geomVertexFormat.h"
#include "geomPoints.h"
#include "geomTriangles.h"
#include "geomVertexWriter.h"
#include "lens.h"
#include "pnmImage.h"
#include "config_grutil.h"
using std::max;
using std::min;
/**
* The PfmVizzer constructor receives a reference to a PfmFile which it will
* operate on. It does not keep ownership of this reference; it is your
* responsibility to ensure the PfmFile does not destruct during the lifetime
* of the PfmVizzer.
*/
PfmVizzer::
PfmVizzer(PfmFile &pfm) : _pfm(pfm) {
_vis_inverse = false;
_vis_2d = false;
_keep_beyond_lens = false;
_vis_blend = nullptr;
_aux_pfm = nullptr;
}
/**
* Adjusts each (x, y, z) point of the Pfm file by projecting it through the
* indicated lens, converting each point to a (u, v, w) texture coordinate.
* The resulting file can be generated to a mesh (with set_vis_inverse(true)
* and generate_vis_mesh()) that will apply the lens distortion to an
* arbitrary texture image.
*/
void PfmVizzer::
project(const Lens *lens, const PfmFile *undist_lut) {
nassertv(_pfm.is_valid());
static LMatrix4 to_uv(0.5f, 0.0f, 0.0f, 0.0f,
0.0f, 0.5f, 0.0f, 0.0f,
0.0f, 0.0f, 0.5f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f);
for (int yi = 0; yi < _pfm.get_y_size(); ++yi) {
for (int xi = 0; xi < _pfm.get_x_size(); ++xi) {
if (!_pfm.has_point(xi, yi)) {
continue;
}
LPoint3f &p = _pfm.modify_point(xi, yi);
LPoint3 film;
if (!lens->project(LCAST(PN_stdfloat, p), film) && !_keep_beyond_lens) {
if (_pfm.has_no_data_value()) {
_pfm.set_point4(xi, yi, _pfm.get_no_data_value());
} else {
_pfm.set_point4(xi, yi, LVecBase4f(0, 0, 0, 0));
}
} else {
// Now the lens gives us coordinates in the range [-1, 1]. Rescale
// these to [0, 1].
LPoint3f uvw = LCAST(float, film * to_uv);
if (undist_lut != nullptr) {
// Apply the undistortion map, if given.
LPoint3f p2;
undist_lut->calc_bilinear_point(p2, uvw[0], 1.0 - uvw[1]);
uvw = p2;
uvw[1] = 1.0 - uvw[1];
}
p = uvw;
}
}
}
}
/**
* Converts each (u, v, depth) point of the Pfm file to an (x, y, z) point, by
* reversing project(). If the original file is only a 1-d file, assumes that
* it is a depth map with implicit (u, v) coordinates.
*
* This method is only valid for a linear lens (e.g. a PerspectiveLens or
* OrthographicLens). Non-linear lenses don't necessarily compute a sensible
* depth coordinate.
*/
void PfmVizzer::
extrude(const Lens *lens) {
nassertv(_pfm.is_valid());
static LMatrix4 from_uv(2.0, 0.0, 0.0, 0.0,
0.0, 2.0, 0.0, 0.0,
0.0, 0.0, 2.0, 0.0,
-1.0, -1.0, -1.0, 1.0);
PfmFile result;
result.clear(_pfm.get_x_size(), _pfm.get_y_size(), 3);
if (_pfm.has_no_data_value()) {
result.set_zero_special(true);
}
if (lens->is_linear()) {
// If the lens is linear (Perspective or Orthographic), we can take the
// slightly faster approach of extruding all the points via a transform
// matrix.
const LMatrix4 &proj_mat_inv = lens->get_projection_mat_inv();
if (_pfm.get_num_channels() == 1) {
// Create an implicit UV coordinate for each point.
LPoint2 uv_scale(1.0, 1.0);
if (_pfm.get_x_size() > 1) {
uv_scale[0] = 1.0 / PN_stdfloat(_pfm.get_x_size());
}
if (_pfm.get_y_size() > 1) {
uv_scale[1] = 1.0 / PN_stdfloat(_pfm.get_y_size());
}
for (int yi = 0; yi < _pfm.get_y_size(); ++yi) {
for (int xi = 0; xi < _pfm.get_x_size(); ++xi) {
if (!_pfm.has_point(xi, yi)) {
continue;
}
LPoint3 p, rp;
p.set(((PN_stdfloat)xi + 0.5) * uv_scale[0],
((PN_stdfloat)yi + 0.5) * uv_scale[1],
(PN_stdfloat)_pfm.get_point1(xi, yi));
from_uv.xform_point_in_place(p);
rp = proj_mat_inv.xform_point_general(p);
result.set_point(xi, yi, rp);
}
}
} else {
// Use the existing UV coordinate for each point.
for (int yi = 0; yi < _pfm.get_y_size(); ++yi) {
for (int xi = 0; xi < _pfm.get_x_size(); ++xi) {
if (!_pfm.has_point(xi, yi)) {
continue;
}
LPoint3 p, rp;
p = LCAST(PN_stdfloat, _pfm.get_point(xi, yi));
from_uv.xform_point_in_place(p);
rp = proj_mat_inv.xform_point_general(p);
result.set_point(xi, yi, rp);
}
}
}
} else {
// If the lens is some non-linear specialty lens, we have to call
// Lens::extrude_depth() to correctly extrude each point.
if (_pfm.get_num_channels() == 1) {
// Create an implicit UV coordinate for each point.
LPoint2 uv_scale(1.0, 1.0);
if (_pfm.get_x_size() > 1) {
uv_scale[0] = 1.0 / PN_stdfloat(_pfm.get_x_size());
}
if (_pfm.get_y_size() > 1) {
uv_scale[1] = 1.0 / PN_stdfloat(_pfm.get_y_size());
}
for (int yi = 0; yi < _pfm.get_y_size(); ++yi) {
for (int xi = 0; xi < _pfm.get_x_size(); ++xi) {
if (!_pfm.has_point(xi, yi)) {
continue;
}
LPoint3 p, rp;
p.set(((PN_stdfloat)xi + 0.5) * uv_scale[0],
((PN_stdfloat)yi + 0.5) * uv_scale[1],
(PN_stdfloat)_pfm.get_point1(xi, yi));
from_uv.xform_point_in_place(p);
lens->extrude_depth(p, rp);
result.set_point(xi, yi, rp);
}
}
} else {
// Use the existing UV coordinate for each point.
for (int yi = 0; yi < _pfm.get_y_size(); ++yi) {
for (int xi = 0; xi < _pfm.get_x_size(); ++xi) {
if (!_pfm.has_point(xi, yi)) {
continue;
}
LPoint3 p, rp;
p = LCAST(PN_stdfloat, _pfm.get_point(xi, yi));
from_uv.xform_point_in_place(p);
lens->extrude_depth(p, rp);
result.set_point(xi, yi, rp);
}
}
}
}
_pfm = result;
}
/**
* Removes all of the previously-added vis columns in preparation for building
* a new list. See add_vis_column().
*/
void PfmVizzer::
clear_vis_columns() {
_vis_columns.clear();
}
/**
* Adds a new vis column specification to the list of vertex data columns that
* will be generated at the next call to generate_vis_points() or
* generate_vis_mesh(). This advanced interface supercedes the higher-level
* set_vis_inverse(), set_flat_texcoord_name(), and set_vis_2d().
*
* If you use this advanced interface, you must specify explicitly the
* complete list of data columns to be created in the resulting
* GeomVertexData, by calling add_vis_column() each time. For each column,
* you specify the source of the column in the PFMFile, the target column and
* name in the GeomVertexData, and an optional transform matrix and/or lens to
* transform and project the point before generating it.
*/
void PfmVizzer::
add_vis_column(ColumnType source, ColumnType target,
InternalName *name, const TransformState *transform,
const Lens *lens, const PfmFile *undist_lut) {
add_vis_column(_vis_columns, source, target, name, transform, lens, undist_lut);
}
/**
* 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 PfmVizzer::
generate_vis_points() const {
nassertr(_pfm.is_valid(), NodePath());
bool check_aux_pfm = uses_aux_pfm();
nassertr(!check_aux_pfm || (_aux_pfm != nullptr && _aux_pfm->is_valid()), NodePath());
CPT(GeomVertexFormat) format;
if (_vis_inverse) {
if (_vis_2d) {
format = GeomVertexFormat::get_v3t2();
} else {
// We need a 3-d texture coordinate if we're inverting the vis and it's
// 3-d.
GeomVertexArrayFormat *v3t3 = new GeomVertexArrayFormat
(InternalName::get_vertex(), 3,
Geom::NT_stdfloat, Geom::C_point,
InternalName::get_texcoord(), 3,
Geom::NT_stdfloat, 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(_pfm.get_x_size() * _pfm.get_y_size());
GeomVertexWriter vertex(vdata, InternalName::get_vertex());
GeomVertexWriter texcoord(vdata, InternalName::get_texcoord());
LPoint2f uv_scale(1.0, 1.0);
if (_pfm.get_x_size() > 1) {
uv_scale[0] = 1.0f / PN_float32(_pfm.get_x_size());
}
if (_pfm.get_y_size() > 1) {
uv_scale[1] = 1.0f / PN_float32(_pfm.get_y_size());
}
int num_points = 0;
for (int yi = 0; yi < _pfm.get_y_size(); ++yi) {
for (int xi = 0; xi < _pfm.get_x_size(); ++xi) {
if (!_pfm.has_point(xi, yi)) {
continue;
}
if (check_aux_pfm && !_aux_pfm->has_point(xi, yi)) {
continue;
}
const LPoint3f &point = _pfm.get_point(xi, yi);
LPoint2f uv((PN_float32(xi) + 0.5) * uv_scale[0],
(PN_float32(yi) + 0.5) * uv_scale[1]);
if (_vis_inverse) {
vertex.add_data2f(uv);
texcoord.add_data3f(point);
} else if (_vis_2d) {
vertex.add_data2f(point[0], point[1]);
texcoord.add_data2f(uv);
} else {
vertex.add_data3f(point);
texcoord.add_data2f(uv);
}
++num_points;
}
}
PT(Geom) geom = new Geom(vdata);
PT(GeomPoints) points = new GeomPoints(Geom::UH_static);
points->add_next_vertices(num_points);
geom->add_primitive(points);
PT(GeomNode) gnode = new GeomNode("");
gnode->add_geom(geom);
return NodePath(gnode);
}
/**
* 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 PfmVizzer::
generate_vis_mesh(MeshFace face) const {
nassertr(_pfm.is_valid(), NodePath());
nassertr(!uses_aux_pfm() || (_aux_pfm != nullptr && _aux_pfm->is_valid()), NodePath());
nassertr(face != 0, NodePath());
if (_pfm.get_num_channels() == 1 && _vis_columns.empty()) {
// If we're generating a default mesh from a one-channel pfm file, expand
// it to a three-channel pfm file to make the visualization useful.
PfmFile expanded;
expanded.clear_to_texcoords(_pfm.get_x_size(), _pfm.get_y_size());
expanded.copy_channel(2, _pfm, 0);
PfmVizzer exvizzer(expanded);
return exvizzer.generate_vis_mesh(face);
}
if (_pfm.get_x_size() == 1 || _pfm.get_y_size() == 1) {
// Can't generate a 1-d mesh, so generate points in this case.
return generate_vis_points();
}
PT(GeomNode) gnode = new GeomNode("");
if (face & MF_front) {
make_vis_mesh_geom(gnode, false);
}
if (face & MF_back) {
make_vis_mesh_geom(gnode, true);
}
return NodePath(gnode);
}
/**
* Computes the maximum amount of shift, in pixels either left or right, of
* any pixel in the distortion map. This can be passed to
* make_displacement(); see that function for more information.
*/
double PfmVizzer::
calc_max_u_displacement() const {
int x_size = _pfm.get_x_size();
int y_size = _pfm.get_y_size();
double max_u = 0;
for (int yi = 0; yi < y_size; ++yi) {
for (int xi = 0; xi < x_size; ++xi) {
if (!_pfm.has_point(xi, yi)) {
continue;
}
const LPoint3f &point = _pfm.get_point(xi, yi);
double nxi = point[0] * (double)x_size - 0.5;
max_u = max(max_u, cabs(nxi - (double)xi));
}
}
return max_u;
}
/**
* Computes the maximum amount of shift, in pixels either up or down, of any
* pixel in the distortion map. This can be passed to make_displacement();
* see that function for more information.
*/
double PfmVizzer::
calc_max_v_displacement() const {
int x_size = _pfm.get_x_size();
int y_size = _pfm.get_y_size();
double max_v = 0;
for (int yi = 0; yi < y_size; ++yi) {
for (int xi = 0; xi < x_size; ++xi) {
if (!_pfm.has_point(xi, yi)) {
continue;
}
const LPoint3f &point = _pfm.get_point(xi, yi);
double nyi = point[1] * (double)y_size - 0.5;
max_v = max(max_v, cabs(nyi - (double)yi));
}
}
return max_v;
}
/**
* Assuming the underlying PfmFile is a 2-d distortion mesh, with the U and V
* in the first two components and the third component unused, this computes
* an AfterEffects-style displacement map that represents the same distortion.
* The indicated PNMImage will be filled in with a displacement map image,
* with horizontal shift in the red channel and vertical shift in the green
* channel, where a fully bright (or fully black) pixel indicates a shift of
* max_u or max_v pixels.
*
* Use calc_max_u_displacement() and calc_max_v_displacement() to compute
* suitable values for max_u and max_v.
*
* This generates an integer 16-bit displacement image. It is a good idea,
* though not necessarily essential, to check "Preserve RGB" in the interpret
* footage section for each displacement image. Set for_32bit true if this is
* meant to be used in a 32-bit project file, and false if it is meant to be
* used in a 16-bit project file.
*/
void PfmVizzer::
make_displacement(PNMImage &result, double max_u, double max_v, bool for_32bit) const {
int x_size = _pfm.get_x_size();
int y_size = _pfm.get_y_size();
result.clear(x_size, y_size, 3, PNM_MAXMAXVAL);
result.fill_val(0, 0, PNM_MAXMAXVAL);
// After Effects defines this as the zero (no-change) value. It's not
// exactly 0.5, because they round up.
static const int midval = (PNM_MAXMAXVAL + 1) / 2;
double scale_factor;
if (for_32bit) {
// There doesn't appear to be an undershift needed on 32-bit projects, but
// we have the factor here anyway in case it develops.
scale_factor = ae_undershift_factor_32;
} else {
// Empirically, After Effects seems to undershift by precisely this amount
// (but only in a 16-bit project, not in a 32-bit project). Curiously,
// this value is very close to, but not exactly, 256 255.
scale_factor = ae_undershift_factor_16;
}
double u_scale = scale_factor * 0.5 * PNM_MAXMAXVAL / max_u;
double v_scale = scale_factor * 0.5 * PNM_MAXMAXVAL / max_v;
for (int yi = 0; yi < y_size; ++yi) {
for (int xi = 0; xi < x_size; ++xi) {
if (!_pfm.has_point(xi, yi)) {
continue;
}
const LPoint3f &point = _pfm.get_point(xi, yi);
double nxi = point[0] * (double)x_size - 0.5;
double nyi = point[1] * (double)y_size - 0.5;
double x_shift = (nxi - (double)xi);
double y_shift = (nyi - (double)yi);
int u_val = midval + (int)cfloor(x_shift * u_scale + 0.5);
int v_val = midval + (int)cfloor(y_shift * v_scale + 0.5);
// We use the blue channel to mark holes, so we can fill them in later.
result.set_xel_val(xi, yi,
min(max(u_val, 0), PNM_MAXMAXVAL),
min(max(v_val, 0), PNM_MAXMAXVAL),
0);
}
}
// Now fill in holes.
for (int yi = 0; yi < y_size; ++yi) {
for (int xi = 0; xi < x_size; ++xi) {
if (!_pfm.has_point(xi, yi)) {
continue;
}
const LPoint3f &point = _pfm.get_point(xi, yi);
double nxi = point[0] * (double)x_size - 0.5;
double nyi = point[1] * (double)y_size - 0.5;
r_fill_displacement(result, xi - 1, yi, nxi, nyi, u_scale, v_scale, 1);
r_fill_displacement(result, xi + 1, yi, nxi, nyi, u_scale, v_scale, 1);
r_fill_displacement(result, xi, yi - 1, nxi, nyi, u_scale, v_scale, 1);
r_fill_displacement(result, xi, yi + 1, nxi, nyi, u_scale, v_scale, 1);
}
}
// Finally, reset the blue channel for cleanliness.
for (int yi = 0; yi < y_size; ++yi) {
for (int xi = 0; xi < x_size; ++xi) {
result.set_blue_val(xi, yi, midval);
}
}
}
/**
* Assuming the underlying PfmFile is a 2-d distortion mesh, with the U and V
* in the first two components and the third component unused, this computes
* an AfterEffects-style displacement map that represents the same distortion.
* The indicated PNMImage will be filled in with a displacement map image,
* with horizontal shift in the red channel and vertical shift in the green
* channel, where a fully bright (or fully black) pixel indicates a shift of
* max_u or max_v pixels.
*
* Use calc_max_u_displacement() and calc_max_v_displacement() to compute
* suitable values for max_u and max_v.
*
* This generates a 32-bit floating-point displacement image. It is essential
* to check "Preserve RGB" in the interpret footage section for each
* displacement image. Set for_32bit true if this is meant to be used in a
* 32-bit project file, and false if it is meant to be used in a 16-bit
* project file.
*/
void PfmVizzer::
make_displacement(PfmFile &result, double max_u, double max_v, bool for_32bit) const {
int x_size = _pfm.get_x_size();
int y_size = _pfm.get_y_size();
result.clear(x_size, y_size, 3);
double scale_factor;
if (for_32bit) {
// There doesn't appear to be an undershift needed on 32-bit projects, but
// we have the factor here anyway in case it develops.
scale_factor = ae_undershift_factor_32;
} else {
// Empirically, After Effects seems to undershift by precisely this amount
// (but only in a 16-bit project, not in a 32-bit project). Curiously,
// this value is very close to, but not exactly, 256 255.
scale_factor = ae_undershift_factor_16;
}
double u_scale = scale_factor * 0.5 / max_u;
double v_scale = scale_factor * 0.5 / max_v;
for (int yi = 0; yi < y_size; ++yi) {
for (int xi = 0; xi < x_size; ++xi) {
if (!_pfm.has_point(xi, yi)) {
continue;
}
const LPoint3f &point = _pfm.get_point(xi, yi);
double nxi = point[0] * (double)x_size - 0.5;
double nyi = point[1] * (double)y_size - 0.5;
double x_shift = (nxi - (double)xi);
double y_shift = (nyi - (double)yi);
float u_val = 0.5 + (float)(x_shift * u_scale);
float v_val = 0.5 + (float)(y_shift * v_scale);
// We use the blue channel to mark holes, so we can fill them in later.
result.set_point3(xi, yi, LVecBase3f(u_val, v_val, 0));
}
}
// Now fill in holes.
for (int yi = 0; yi < y_size; ++yi) {
for (int xi = 0; xi < x_size; ++xi) {
if (!_pfm.has_point(xi, yi)) {
continue;
}
const LPoint3f &point = _pfm.get_point(xi, yi);
double nxi = point[0] * (double)x_size - 0.5;
double nyi = point[1] * (double)y_size - 0.5;
r_fill_displacement(result, xi - 1, yi, nxi, nyi, u_scale, v_scale, 1);
r_fill_displacement(result, xi + 1, yi, nxi, nyi, u_scale, v_scale, 1);
r_fill_displacement(result, xi, yi - 1, nxi, nyi, u_scale, v_scale, 1);
r_fill_displacement(result, xi, yi + 1, nxi, nyi, u_scale, v_scale, 1);
}
}
// Finally, reset the blue channel for cleanliness.
for (int yi = 0; yi < y_size; ++yi) {
for (int xi = 0; xi < x_size; ++xi) {
result.set_channel(xi, yi, 2, 0.5);
}
}
}
/**
* Returns true if any of the vis_column tokens reference the aux_pfm file,
* false otherwise.
*/
bool PfmVizzer::
uses_aux_pfm() const {
for (VisColumns::const_iterator vci = _vis_columns.begin();
vci != _vis_columns.end();
++vci) {
const VisColumn &column = *vci;
switch (column._source) {
case CT_aux_vertex1:
case CT_aux_vertex2:
case CT_aux_vertex3:
return true;
default:
break;
}
}
return false;
}
/**
* Recursively fills in holes with the color of their nearest neighbor after
* processing the image. This avoids sudden discontinuities in the
* displacement map at the edge of the screen geometry.
*/
void PfmVizzer::
r_fill_displacement(PNMImage &result, int xi, int yi,
double nxi, double nyi, double u_scale, double v_scale,
int distance) const {
if (xi < 0 || yi < 0 ||
xi >= result.get_x_size() || yi >= result.get_y_size()) {
// Stop at the edge.
return;
}
if (distance > 1000) {
// Avoid runaway recursion.
return;
}
int val = result.get_blue_val(xi, yi);
if (val > distance) {
// We've found a point that's closer.
static const int midval = (PNM_MAXMAXVAL + 1) / 2;
double x_shift = (nxi - (double)xi);
double y_shift = (nyi - (double)yi);
int u_val = midval + (int)cfloor(x_shift * u_scale + 0.5);
int v_val = midval + (int)cfloor(y_shift * v_scale + 0.5);
result.set_xel_val(xi, yi,
min(max(u_val, 0), PNM_MAXMAXVAL),
min(max(v_val, 0), PNM_MAXMAXVAL),
min(distance, PNM_MAXMAXVAL));
r_fill_displacement(result, xi - 1, yi, nxi, nyi, u_scale, v_scale, distance + 1);
r_fill_displacement(result, xi + 1, yi, nxi, nyi, u_scale, v_scale, distance + 1);
r_fill_displacement(result, xi, yi - 1, nxi, nyi, u_scale, v_scale, distance + 1);
r_fill_displacement(result, xi, yi + 1, nxi, nyi, u_scale, v_scale, distance + 1);
}
}
/**
* Recursively fills in holes with the color of their nearest neighbor after
* processing the image. This avoids sudden discontinuities in the
* displacement map at the edge of the screen geometry.
*/
void PfmVizzer::
r_fill_displacement(PfmFile &result, int xi, int yi,
double nxi, double nyi, double u_scale, double v_scale,
int distance) const {
if (xi < 0 || yi < 0 ||
xi >= result.get_x_size() || yi >= result.get_y_size()) {
// Stop at the edge.
return;
}
if (distance > 1000) {
// Avoid runaway recursion.
return;
}
float val = result.get_channel(xi, yi, 2);
if (val > (float)distance) {
// We've found a point that's closer.
double x_shift = (nxi - (double)xi);
double y_shift = (nyi - (double)yi);
float u_val = 0.5 + (float)(x_shift * u_scale);
float v_val = 0.5 + (float)(y_shift * v_scale);
result.set_point3(xi, yi, LVecBase3f(u_val, v_val, distance));
r_fill_displacement(result, xi - 1, yi, nxi, nyi, u_scale, v_scale, distance + 1);
r_fill_displacement(result, xi + 1, yi, nxi, nyi, u_scale, v_scale, distance + 1);
r_fill_displacement(result, xi, yi - 1, nxi, nyi, u_scale, v_scale, distance + 1);
r_fill_displacement(result, xi, yi + 1, nxi, nyi, u_scale, v_scale, distance + 1);
}
}
/**
* Returns a triangle mesh for the pfm. If inverted is true, the mesh is
* facing the opposite direction.
*/
void PfmVizzer::
make_vis_mesh_geom(GeomNode *gnode, bool inverted) const {
static const bool keep_beyond_lens = true;
int num_x_cells = 1;
int num_y_cells = 1;
int x_size = _pfm.get_x_size();
int y_size = _pfm.get_y_size();
// This is the number of independent vertices we will require.
int num_vertices = x_size * y_size;
if (num_vertices == 0) {
// Trivial no-op.
return;
}
bool reverse_normals = inverted;
bool reverse_faces = inverted;
if (!is_right_handed(get_default_coordinate_system())) {
reverse_faces = !reverse_faces;
}
// This is the max number of vertex indices we might add to the
// GeomTriangles. (We might actually add fewer than this due to omitting
// the occasional missing data point.)
int max_indices = (x_size - 1) * (y_size - 1) * 6;
while (num_vertices > pfm_vis_max_vertices || max_indices > pfm_vis_max_indices) {
// Too many vertices in one mesh. Subdivide the mesh into smaller pieces.
if (num_x_cells > num_y_cells) {
++num_y_cells;
} else {
++num_x_cells;
}
x_size = (_pfm.get_x_size() + num_x_cells - 1) / num_x_cells + 1;
y_size = (_pfm.get_y_size() + num_y_cells - 1) / num_y_cells + 1;
num_vertices = x_size * y_size;
max_indices = (x_size - 1) * (y_size - 1) * 6;
}
// OK, now we know how many cells we need.
if (grutil_cat.is_debug()) {
grutil_cat.debug()
<< "Generating mesh with " << num_x_cells << " x " << num_y_cells
<< " pieces.\n";
}
VisColumns vis_columns = _vis_columns;
if (vis_columns.empty()) {
build_auto_vis_columns(vis_columns, true);
}
bool check_aux_pfm = uses_aux_pfm();
CPT(GeomVertexFormat) format = make_array_format(vis_columns);
for (int yci = 0; yci < num_y_cells; ++yci) {
int y_begin = (yci * _pfm.get_y_size()) / num_y_cells;
int y_end = ((yci + 1) * _pfm.get_y_size()) / num_y_cells;
// Include the first vertex from the next strip in this strip's vertices,
// so we are connected.
y_end = min(y_end + 1, _pfm.get_y_size());
y_size = y_end - y_begin;
if (y_size == 0) {
continue;
}
for (int xci = 0; xci < num_x_cells; ++xci) {
int x_begin = (xci * _pfm.get_x_size()) / num_x_cells;
int x_end = ((xci + 1) * _pfm.get_x_size()) / num_x_cells;
x_end = min(x_end + 1, _pfm.get_x_size());
x_size = x_end - x_begin;
if (x_size == 0) {
continue;
}
num_vertices = x_size * y_size;
max_indices = (x_size - 1) * (y_size - 1) * 6;
std::ostringstream mesh_name;
mesh_name << "mesh_" << xci << "_" << yci;
PT(GeomVertexData) vdata = new GeomVertexData
(mesh_name.str(), format, Geom::UH_static);
vdata->set_num_rows(num_vertices);
char *skip_points = new char[num_vertices];
memset(skip_points, 0, sizeof(char) * num_vertices);
// Fill in all of the vertices.
for (VisColumns::const_iterator vci = vis_columns.begin();
vci != vis_columns.end();
++vci) {
const VisColumn &column = *vci;
GeomVertexWriter vwriter(vdata, column._name);
vwriter.set_row(0);
for (int yi = y_begin; yi < y_end; ++yi) {
for (int xi = x_begin; xi < x_end; ++xi) {
if (!column.add_data(*this, vwriter, xi, yi, reverse_normals)) {
skip_points[(yi - y_begin) * x_size + (xi - x_begin)] = (char)true;
}
}
}
}
PT(Geom) geom = new Geom(vdata);
PT(GeomTriangles) tris = new GeomTriangles(Geom::UH_static);
tris->reserve_num_vertices(max_indices);
for (int yi = y_begin; yi < y_end - 1; ++yi) {
for (int xi = x_begin; xi < x_end - 1; ++xi) {
if (!_pfm.has_point(xi, yi) ||
!_pfm.has_point(xi, yi + 1) ||
!_pfm.has_point(xi + 1, yi + 1) ||
!_pfm.has_point(xi + 1, yi)) {
continue;
}
if (check_aux_pfm && (!_aux_pfm->has_point(xi, yi) ||
!_aux_pfm->has_point(xi, yi + 1) ||
!_aux_pfm->has_point(xi + 1, yi + 1) ||
!_aux_pfm->has_point(xi + 1, yi))) {
continue;
}
if (!keep_beyond_lens &&
(skip_points[(yi - y_begin) * x_size + (xi - x_begin)] ||
skip_points[(yi - y_begin + 1) * x_size + (xi - x_begin)] ||
skip_points[(yi - y_begin) * x_size + (xi - x_begin + 1)] ||
skip_points[(yi - y_begin + 1) * x_size + (xi - x_begin + 1)])) {
continue;
}
int xi0 = xi - x_begin;
int yi0 = yi - y_begin;
int vi0 = ((xi0) + (yi0) * x_size);
int vi1 = ((xi0) + (yi0 + 1) * x_size);
int vi2 = ((xi0 + 1) + (yi0 + 1) * x_size);
int vi3 = ((xi0 + 1) + (yi0) * x_size);
if (reverse_faces) {
tris->add_vertices(vi2, vi0, vi1);
tris->close_primitive();
tris->add_vertices(vi3, vi0, vi2);
tris->close_primitive();
} else {
tris->add_vertices(vi2, vi1, vi0);
tris->close_primitive();
tris->add_vertices(vi3, vi2, vi0);
tris->close_primitive();
}
}
}
geom->add_primitive(tris);
gnode->add_geom(geom);
delete[] skip_points;
}
}
}
/**
* The private implementation of the public add_vis_column(), this adds the
* column to the indicated specific vector.
*/
void PfmVizzer::
add_vis_column(VisColumns &vis_columns, ColumnType source, ColumnType target,
InternalName *name, const TransformState *transform,
const Lens *lens, const PfmFile *undist_lut) {
VisColumn column;
column._source = source;
column._target = target;
column._name = name;
column._transform = transform;
if (transform == nullptr) {
column._transform = TransformState::make_identity();
}
column._lens = lens;
if (undist_lut != nullptr && undist_lut->is_valid()) {
column._undist_lut = undist_lut;
}
vis_columns.push_back(column);
}
/**
* This function is called internally to construct the list of vis_columns
* automatically from the high-level interfaces such as set_vis_inverse(),
* set_flat_texcoord_name(), and set_vis_2d(). It's not called if the list
* has been build explicitly.
*/
void PfmVizzer::
build_auto_vis_columns(VisColumns &vis_columns, bool for_points) const {
vis_columns.clear();
if (_vis_2d) {
// No normals needed if we're just generating a 2-d mesh.
if (_vis_inverse) {
add_vis_column(vis_columns, CT_texcoord2, CT_vertex2, InternalName::get_vertex());
add_vis_column(vis_columns, CT_vertex2, CT_texcoord2, InternalName::get_texcoord());
} else {
add_vis_column(vis_columns, CT_vertex2, CT_vertex2, InternalName::get_vertex());
add_vis_column(vis_columns, CT_texcoord2, CT_texcoord2, InternalName::get_texcoord());
}
} else {
if (_vis_inverse) {
// We need a 3-d texture coordinate if we're inverting the vis and it's
// 3-d. But we still don't need normals in that case.
add_vis_column(vis_columns, CT_texcoord3, CT_vertex3, InternalName::get_vertex());
add_vis_column(vis_columns, CT_vertex3, CT_texcoord3, InternalName::get_texcoord());
} else {
// Otherwise, we only need a 2-d texture coordinate, and we do want
// normals.
add_vis_column(vis_columns, CT_vertex3, CT_vertex3, InternalName::get_vertex());
add_vis_column(vis_columns, CT_normal3, CT_normal3, InternalName::get_normal());
add_vis_column(vis_columns, CT_texcoord2, CT_texcoord2, InternalName::get_texcoord());
}
}
if (_flat_texcoord_name != nullptr) {
// We need an additional texcoord column for the flat texcoords.
add_vis_column(vis_columns, CT_texcoord2, CT_texcoord2, _flat_texcoord_name);
}
if (_vis_blend != nullptr) {
// The blend map, if specified, also gets applied to the vertices.
add_vis_column(vis_columns, CT_blend1, CT_blend1, InternalName::get_color());
}
}
/**
* Constructs a GeomVertexFormat that corresponds to the vis_columns list.
*/
CPT(GeomVertexFormat) PfmVizzer::
make_array_format(const VisColumns &vis_columns) const {
PT(GeomVertexArrayFormat) array_format = new GeomVertexArrayFormat;
for (VisColumns::const_iterator vci = vis_columns.begin();
vci != vis_columns.end();
++vci) {
const VisColumn &column = *vci;
InternalName *name = column._name;
int num_components = 0;
GeomEnums::NumericType numeric_type = GeomEnums::NT_float32;
GeomEnums::Contents contents = GeomEnums::C_point;
switch (column._target) {
case CT_texcoord2:
num_components = 2;
numeric_type = GeomEnums::NT_float32;
contents = GeomEnums::C_texcoord;
break;
case CT_texcoord3:
num_components = 3;
numeric_type = GeomEnums::NT_float32;
contents = GeomEnums::C_texcoord;
break;
case CT_vertex1:
case CT_aux_vertex1:
num_components = 1;
numeric_type = GeomEnums::NT_float32;
contents = GeomEnums::C_point;
break;
case CT_vertex2:
case CT_aux_vertex2:
num_components = 2;
numeric_type = GeomEnums::NT_float32;
contents = GeomEnums::C_point;
break;
case CT_vertex3:
case CT_aux_vertex3:
num_components = 3;
numeric_type = GeomEnums::NT_float32;
contents = GeomEnums::C_point;
break;
case CT_normal3:
num_components = 3;
numeric_type = GeomEnums::NT_float32;
contents = GeomEnums::C_normal;
break;
case CT_blend1:
num_components = 4;
numeric_type = GeomEnums::NT_uint8;
contents = GeomEnums::C_color;
break;
}
nassertr(num_components != 0, nullptr);
array_format->add_column(name, num_components, numeric_type, contents);
}
return GeomVertexFormat::register_format(array_format);
}
/**
* Adds the data for this column to the appropriate column of the
* GeomVertexWriter. Returns true if the point is valid, false otherwise.
*/
bool PfmVizzer::VisColumn::
add_data(const PfmVizzer &vizzer, GeomVertexWriter &vwriter, int xi, int yi, bool reverse_normals) const {
const PfmFile &pfm = vizzer.get_pfm();
bool success = true;
switch (_source) {
case CT_texcoord2:
{
LPoint2f uv((PN_float32(xi) + 0.5) / PN_float32(pfm.get_x_size()),
(PN_float32(yi) + 0.5) / PN_float32(pfm.get_y_size()));
if (!transform_point(uv)) {
success = false;
}
vwriter.set_data2f(uv);
}
break;
case CT_texcoord3:
{
LPoint3f uv((PN_float32(xi) + 0.5) / PN_float32(pfm.get_x_size()),
(PN_float32(yi) + 0.5) / PN_float32(pfm.get_y_size()),
0.0f);
if (!transform_point(uv)) {
success = false;
}
vwriter.set_data3f(uv);
}
break;
case CT_vertex1:
{
PN_float32 p = pfm.get_point1(xi, yi);
LPoint2f point(p, 0.0);
if (!transform_point(point)) {
success = false;
}
vwriter.set_data2f(point);
}
break;
case CT_aux_vertex1:
{
nassertr(vizzer.get_aux_pfm() != nullptr, false);
PN_float32 p = vizzer.get_aux_pfm()->get_point1(xi, yi);
LPoint2f point(p, 0.0);
if (!transform_point(point)) {
success = false;
}
vwriter.set_data2f(point);
}
break;
case CT_vertex2:
{
LPoint2f point = pfm.get_point2(xi, yi);
if (!transform_point(point)) {
success = false;
}
vwriter.set_data2f(point);
}
break;
case CT_aux_vertex2:
{
nassertr(vizzer.get_aux_pfm() != nullptr, false);
LPoint2f point = vizzer.get_aux_pfm()->get_point2(xi, yi);
if (!transform_point(point)) {
success = false;
}
vwriter.set_data2f(point);
}
break;
case CT_vertex3:
{
LPoint3f point = pfm.get_point(xi, yi);
if (!transform_point(point)) {
success = false;
}
vwriter.set_data3f(point);
}
break;
case CT_aux_vertex3:
{
LPoint3f point = vizzer.get_aux_pfm()->get_point(xi, yi);
if (!transform_point(point)) {
success = false;
}
vwriter.set_data3f(point);
}
break;
case CT_normal3:
{
// Calculate the normal based on two neighboring vertices.
bool flip = reverse_normals;
LPoint3f v[3];
v[0] = pfm.get_point(xi, yi);
v[1] = v[0];
v[2] = v[0];
if (pfm.has_point(xi + 1, yi)) {
v[1] = pfm.get_point(xi + 1, yi);
} else if (pfm.has_point(xi - 1, yi)) {
v[1] = pfm.get_point(xi - 1, yi);
flip = !flip;
}
if (pfm.has_point(xi, yi + 1)) {
v[2] = pfm.get_point(xi, yi + 1);
} else if (pfm.has_point(xi, yi - 1)) {
v[2] = pfm.get_point(xi, yi - 1);
flip = !flip;
}
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 (n.is_nan()) {
/*
cerr << "\nnan!\n"
<< " v[0] = " << v[0] << "\n"
<< " v[1] = " << v[1] << "\n"
<< " v[2] = " << v[2] << "\n";
*/
n.set(0, 0, 0);
success = false;
}
if (flip) {
n = -n;
}
if (!transform_vector(n)) {
success = false;
}
vwriter.set_data3f(n);
}
break;
case CT_blend1:
{
const PNMImage *vis_blend = vizzer.get_vis_blend();
if (vis_blend != nullptr) {
double gray = vis_blend->get_gray(xi, yi);
vwriter.set_data3d(gray, gray, gray);
}
}
break;
}
return success;
}
/**
* Transforms the indicated point as specified by the VisColumn.
*/
bool PfmVizzer::VisColumn::
transform_point(LPoint2f &point) const {
bool success = true;
if (!_transform->is_identity()) {
LCAST(PN_float32, _transform->get_mat3()).xform_point_in_place(point);
}
return success;
}
/**
* Transforms the indicated point as specified by the VisColumn.
*/
bool PfmVizzer::VisColumn::
transform_point(LPoint3f &point) const {
bool success = true;
if (!_transform->is_identity()) {
LCAST(PN_float32, _transform->get_mat()).xform_point_in_place(point);
}
if (_lens != nullptr) {
static LMatrix4f to_uv(0.5, 0.0, 0.0, 0.0,
0.0, 0.5, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.5, 0.5, 0.0, 1.0);
LPoint3 film;
if (!_lens->project(LCAST(PN_stdfloat, point), film)) {
success = false;
}
point = to_uv.xform_point(LCAST(PN_float32, film));
}
if (_undist_lut != nullptr) {
LPoint3f p;
if (!_undist_lut->calc_bilinear_point(p, point[0], 1.0 - point[1])) {
// Point is missing.
point.set(0, 0, 0);
success = false;
} else {
point = p;
point[1] = 1.0 - point[1];
}
}
return success;
}
/**
* Transforms the indicated vector as specified by the VisColumn.
*/
bool PfmVizzer::VisColumn::
transform_vector(LVector3f &vec) const {
if (!_transform->is_identity()) {
LCAST(PN_float32, _transform->get_mat()).xform_vec_in_place(vec);
}
return true;
}