From d026d9d928af3b7593e54162abf93a2cdc80107a Mon Sep 17 00:00:00 2001 From: David Rose Date: Fri, 14 Mar 2003 23:36:50 +0000 Subject: [PATCH] implement spherical projections --- pandatool/src/maya/mayaShader.cxx | 166 +++++++++++++++---- pandatool/src/maya/mayaShader.h | 11 +- pandatool/src/mayaegg/mayaToEggConverter.cxx | 27 +-- 3 files changed, 160 insertions(+), 44 deletions(-) diff --git a/pandatool/src/maya/mayaShader.cxx b/pandatool/src/maya/mayaShader.cxx index 9fc3f8d73b..7df6de923c 100644 --- a/pandatool/src/maya/mayaShader.cxx +++ b/pandatool/src/maya/mayaShader.cxx @@ -45,6 +45,7 @@ MayaShader(MObject engine) { _has_texture = false; _projection_type = PT_off; + _map_uvs = NULL; _coverage.set(1.0, 1.0); _translate_frame.set(0.0, 0.0); @@ -137,38 +138,9 @@ has_projection() const { // quadrant as the indicated reference point. //////////////////////////////////////////////////////////////////// TexCoordd MayaShader:: -project_uv(const LPoint3d &point, const LPoint3d &ref_point) const { - LPoint3d p = point * _projection_matrix; - - switch (_projection_type) { - case PT_planar: - return TexCoordd(p[0], p[1]); - - case PT_cylindrical: - { - LPoint3d rp = ref_point * _projection_matrix; - - TexCoordd uv - (// The u position is the angle about the Y axis, scaled to 0 .. 1. - catan2(p[0], p[2]) / (2.0 * MathNumbers::pi) + 0.5, - // The v position is the Y height. - p[1]); - - // Also convert the reference point, so we can adjust the - // quadrant if necessary; each single polygon should only go the - // short way around the cylinder. - double ref_u = catan2(rp[0], rp[1]) / (2.0 * MathNumbers::pi) + 0.5; - if (uv[0] - ref_u > 0.5) { - uv[0] -= 1.0; - } else if (uv[0] - ref_u < -0.5) { - uv[0] += 1.0; - } - return uv; - } - - default: - return TexCoordd(0.0, 0.0); - } +project_uv(const LPoint3d &pos, const LPoint3d ¢roid) const { + nassertr(_map_uvs != NULL, TexCoordd::zero()); + return (this->*_map_uvs)(pos * _projection_matrix, centroid * _projection_matrix); } //////////////////////////////////////////////////////////////////// @@ -334,6 +306,15 @@ read_surface_color(MObject color) { _projection_matrix = LMatrix4d::ident_mat(); } + // The uAngle and vAngle might be used for certain kinds of + // projections. + if (!get_angle_attribute(color, "uAngle", _u_angle)) { + _u_angle = 360.0; + } + if (!get_angle_attribute(color, "vAngle", _v_angle)) { + _v_angle = 180.0; + } + string type; if (get_enum_attribute(color, "projType", type)) { set_projection_type(type); @@ -369,6 +350,7 @@ void MayaShader:: set_projection_type(const string &type) { if (cmp_nocase(type, "planar") == 0) { _projection_type = PT_planar; + _map_uvs = &MayaShader::map_planar; // The Planar projection normally projects to a range (-1, 1) in // both axes. Scale this into our UV range of (0, 1). @@ -379,6 +361,12 @@ set_projection_type(const string &type) { } else if (cmp_nocase(type, "cylindrical") == 0) { _projection_type = PT_cylindrical; + _map_uvs = &MayaShader::map_cylindrical; + + // We always want at least u wrapping with a cylindrical + // projection--this will help with the seams. Plus, if the + // _u_wrap value is less than 360, we want wrapping anyway. + _wrap_u = true; // The cylindrical projection is orthographic in the Y axis; scale // the range (-1, 1) in this axis into our UV range (0, 1). @@ -387,10 +375,124 @@ set_projection_type(const string &type) { 0.0, 0.0, 1.0, 0.0, 0.0, 0.5, 0.0, 1.0); + } else if (cmp_nocase(type, "spherical") == 0) { + _projection_type = PT_spherical; + _map_uvs = &MayaShader::map_spherical; + } else { // Other projection types are currently unimplemented by the // converter. maya_cat.error() << "Don't know how to handle type " << type << " projections.\n"; + _projection_type = PT_off; + _map_uvs = NULL; } } + +//////////////////////////////////////////////////////////////////// +// Function: MayaShader::map_planar +// Access: Private +// Description: Computes a UV based on the given point in space, +// using a planar projection. +//////////////////////////////////////////////////////////////////// +LPoint2d MayaShader:: +map_planar(const LPoint3d &pos, const LPoint3d &) const { + // A planar projection is about as easy as can be. We ignore the Z + // axis, and project the point into the XY plane. Done. + return LPoint2d(pos[0], pos[1]); +} + +//////////////////////////////////////////////////////////////////// +// Function: MayaShader::map_spherical +// Access: Private +// Description: Computes a UV based on the given point in space, +// using a spherical projection. +//////////////////////////////////////////////////////////////////// +LPoint2d MayaShader:: +map_spherical(const LPoint3d &pos, const LPoint3d ¢roid) const { + // To compute the x position on the frame, we only need to consider + // the angle of the vector about the Y axis. Project the vector + // into the XZ plane to do this. + + LVector2d xz(pos[0], pos[2]); + double xz_length = xz.length(); + + if (xz_length < 0.01) { + // If we have a point on or near either pole, we've got problems. + // This point maps to the entire bottom edge of the image, so + // which U value should we choose? It does make a difference, + // especially if we have a number of polygons around the south + // pole that all share the common vertex. + + // We choose the U value based on the polygon's centroid. + xz.set(centroid[0], centroid[2]); + } + + // Now, if the polygon crosses the seam, we also have problems. + // Make sure that the u value is in the same half of the texture as + // the centroid's u value. + double u = rad_2_deg(atan2(xz[0], xz[1])) / (2.0 * _u_angle); + double c = rad_2_deg(atan2(centroid[0], centroid[2])) / (2.0 * _u_angle); + + if (u - c > 0.5) { + u -= floor(u - c + 0.5); + } else if (u - c < -0.5) { + u += floor(c - u + 0.5); + } + + // Now rotate the vector into the YZ plane, and the V value is based + // on the latitude: the angle about the X axis. + LVector2d yz(pos[1], xz_length); + double v = rad_2_deg(atan2(yz[0], yz[1])) / (2.0 * _v_angle); + + LPoint2d uv(u - 0.5, v - 0.5); + + nassertr(fabs(u - c) <= 0.5, uv); + return uv; +} + +//////////////////////////////////////////////////////////////////// +// Function: MayaShader::map_cylindrical +// Access: Private +// Description: Computes a UV based on the given point in space, +// using a cylindrical projection. +//////////////////////////////////////////////////////////////////// +LPoint2d MayaShader:: +map_cylindrical(const LPoint3d &pos, const LPoint3d ¢roid) const { + // This is almost identical to the spherical projection, except for + // the computation of V. + + LVector2d xz(pos[0], pos[2]); + double xz_length = xz.length(); + + if (xz_length < 0.01) { + // A cylindrical mapping has the same singularity problem at the + // pole as a spherical mapping does: points at the pole do not map + // to a single point on the texture. (It's technically a slightly + // different problem: in a cylindrical mapping, points at the pole + // do not map to any point on the texture, while in a spherical + // mapping, points at the pole map to the top or bottom edge of + // the texture. But this is a technicality that doesn't really + // apply to us.) We still solve it the same way: if our point is + // at or near the pole, compute the angle based on the centroid of + // the polygon (which we assume is further from the pole). + xz.set(centroid[0], centroid[2]); + } + + // And cylinders do still have a seam at the back. + double u = rad_2_deg(atan2(xz[0], xz[1])) / _u_angle; + double c = rad_2_deg(atan2(centroid[0], centroid[2])) / _u_angle; + + if (u - c > 0.5) { + u -= floor(u - c + 0.5); + } else if (u - c < -0.5) { + u += floor(c - u + 0.5); + } + + // For a cylindrical mapping, the V value comes directly from Y. + // Easy. + LPoint2d uv(u - 0.5, pos[1]); + + nassertr(fabs(u - c) <= 0.5, uv); + return uv; +} diff --git a/pandatool/src/maya/mayaShader.h b/pandatool/src/maya/mayaShader.h index a663192225..2a226b3d70 100644 --- a/pandatool/src/maya/mayaShader.h +++ b/pandatool/src/maya/mayaShader.h @@ -41,7 +41,7 @@ public: LMatrix3d compute_texture_matrix() const; bool has_projection() const; - TexCoordd project_uv(const LPoint3d &point, const LPoint3d &ref_point) const; + TexCoordd project_uv(const LPoint3d &pos, const LPoint3d &ref_point) const; void output(ostream &out) const; bool reset_maya_texture(const Filename &texture); @@ -68,6 +68,8 @@ public: }; ProjectionType _projection_type; LMatrix4d _projection_matrix; + double _u_angle; + double _v_angle; LVector2f _coverage; LVector2f _translate_frame; @@ -88,6 +90,13 @@ private: bool read_surface_shader(MObject shader); void read_surface_color(MObject color); void set_projection_type(const string &type); + + LPoint2d map_planar(const LPoint3d &pos, const LPoint3d ¢roid) const; + LPoint2d map_spherical(const LPoint3d &pos, const LPoint3d ¢roid) const; + LPoint2d map_cylindrical(const LPoint3d &pos, const LPoint3d ¢roid) const; + + // Define a pointer to one of the above member functions. + LPoint2d (MayaShader::*_map_uvs)(const LPoint3d &pos, const LPoint3d ¢roid) const; }; INLINE ostream &operator << (ostream &out, const MayaShader &shader) { diff --git a/pandatool/src/mayaegg/mayaToEggConverter.cxx b/pandatool/src/mayaegg/mayaToEggConverter.cxx index 26c13203a9..5aad4cc10a 100644 --- a/pandatool/src/mayaegg/mayaToEggConverter.cxx +++ b/pandatool/src/mayaegg/mayaToEggConverter.cxx @@ -1309,20 +1309,25 @@ make_polyset(const MDagPath &dag_path, const MFnMesh &mesh, // Get the vertices for the polygon. long num_verts = pi.polygonVertexCount(); - LPoint3d ref_p3d; - for (long i = 0; i < num_verts; i++) { + long i; + LPoint3d centroid(0.0, 0.0, 0.0); + + if (shader != (MayaShader *)NULL && shader->has_projection()) { + // If the shader has a projection, we may need to compute the + // polygon's centroid to avoid seams at the edges. + for (i = 0; i < num_verts; i++) { + MPoint p = pi.point(i, MSpace::kWorld); + LPoint3d p3d(p[0], p[1], p[2]); + centroid += p3d; + } + centroid /= (double)num_verts; + } + + for (i = 0; i < num_verts; i++) { EggVertex vert; MPoint p = pi.point(i, MSpace::kWorld); LPoint3d p3d(p[0], p[1], p[2]); - if (i == 0) { - // Save the first vertex of the polygon as a reference point - // for sealing up seams that might be introduced by a UV - // projection, so we can ensure that all the vertices are - // projected into the same quadrant. - ref_p3d = p3d; - } - vert.set_pos(p3d); MVector n; @@ -1336,7 +1341,7 @@ make_polyset(const MDagPath &dag_path, const MFnMesh &mesh, if (shader != (MayaShader *)NULL && shader->has_projection()) { // If the shader has a projection, use it instead of the // polygon's built-in UV's. - vert.set_uv(shader->project_uv(p3d, ref_p3d)); + vert.set_uv(shader->project_uv(p3d, centroid)); } else if (pi.hasUVs()) { // Get the UV's from the polygon.