cLwoSurface.cxx

00001 // Filename: cLwoSurface.cxx
00002 // Created by:  drose (25Apr01)
00003 //
00004 ////////////////////////////////////////////////////////////////////
00005 //
00006 // PANDA 3D SOFTWARE
00007 // Copyright (c) Carnegie Mellon University.  All rights reserved.
00008 //
00009 // All use of this software is subject to the terms of the revised BSD
00010 // license.  You should have received a copy of this license along
00011 // with this source code in a file named "LICENSE."
00012 //
00013 ////////////////////////////////////////////////////////////////////
00014 
00015 #include "cLwoSurface.h"
00016 #include "cLwoSurfaceBlock.h"
00017 #include "cLwoClip.h"
00018 #include "lwoToEggConverter.h"
00019 
00020 #include "lwoSurfaceColor.h"
00021 #include "lwoSurfaceParameter.h"
00022 #include "lwoSurfaceSmoothingAngle.h"
00023 #include "lwoSurfaceSidedness.h"
00024 #include "lwoSurfaceBlock.h"
00025 #include "eggPrimitive.h"
00026 #include "string_utils.h"
00027 #include "mathNumbers.h"
00028 #include "dcast.h"
00029 
00030 
00031 ////////////////////////////////////////////////////////////////////
00032 //     Function: CLwoSurface::Constructor
00033 //       Access: Public
00034 //  Description:
00035 ////////////////////////////////////////////////////////////////////
00036 CLwoSurface::
00037 CLwoSurface(LwoToEggConverter *converter, const LwoSurface *surface) :
00038   _converter(converter),
00039   _surface(surface)
00040 {
00041   _flags = 0;
00042   _rgb.set(1.0, 1.0, 1.0);
00043   _checked_material = false;
00044   _checked_texture = false;
00045   _map_uvs = NULL;
00046   _block = (CLwoSurfaceBlock *)NULL;
00047 
00048   // Walk through the chunk list, looking for some basic properties.
00049   int num_chunks = _surface->get_num_chunks();
00050   for (int i = 0; i < num_chunks; i++) {
00051     const IffChunk *chunk = _surface->get_chunk(i);
00052 
00053     if (chunk->is_of_type(LwoSurfaceColor::get_class_type())) {
00054       const LwoSurfaceColor *color = DCAST(LwoSurfaceColor, chunk);
00055       _flags |= F_rgb;
00056       _rgb = color->_color;
00057 
00058     } else if (chunk->is_of_type(LwoSurfaceParameter::get_class_type())) {
00059       const LwoSurfaceParameter *param = DCAST(LwoSurfaceParameter, chunk);
00060       IffId type = param->get_id();
00061 
00062       if (type == IffId("DIFF")) {
00063         _flags |= F_diffuse;
00064         _diffuse = param->_value;
00065 
00066       } else if (type == IffId("LUMI")) {
00067         _flags |= F_luminosity;
00068         _luminosity = param->_value;
00069 
00070       } else if (type == IffId("SPEC")) {
00071         _flags |= F_specular;
00072         _specular = param->_value;
00073 
00074       } else if (type == IffId("REFL")) {
00075         _flags |= F_reflection;
00076         _reflection = param->_value;
00077 
00078       } else if (type == IffId("TRAN")) {
00079         _flags |= F_transparency;
00080         _transparency = param->_value;
00081 
00082       } else if (type == IffId("GLOS")) {
00083         _flags |= F_gloss;
00084         _gloss = param->_value;
00085 
00086       } else if (type == IffId("TRNL")) {
00087         _flags |= F_translucency;
00088         _translucency = param->_value;
00089       }
00090 
00091     } else if (chunk->is_of_type(LwoSurfaceSmoothingAngle::get_class_type())) {
00092       const LwoSurfaceSmoothingAngle *sa = DCAST(LwoSurfaceSmoothingAngle, chunk);
00093       _flags |= F_smooth_angle;
00094       _smooth_angle = sa->_angle;
00095 
00096     } else if (chunk->is_of_type(LwoSurfaceSidedness::get_class_type())) {
00097       const LwoSurfaceSidedness *sn = DCAST(LwoSurfaceSidedness, chunk);
00098       _flags |= F_backface;
00099       _backface = (sn->_sidedness == LwoSurfaceSidedness::S_front_and_back);
00100 
00101     } else if (chunk->is_of_type(LwoSurfaceBlock::get_class_type())) {
00102       const LwoSurfaceBlock *lwo_block = DCAST(LwoSurfaceBlock, chunk);
00103       // One of possibly several blocks in the texture that define
00104       // additional fancy rendering properties.
00105 
00106       CLwoSurfaceBlock *block = new CLwoSurfaceBlock(_converter, lwo_block);
00107 
00108       // We only consider enabled "IMAP" type blocks that affect "COLR".
00109       if (block->_block_type == IffId("IMAP") &&
00110           block->_channel_id == IffId("COLR") &&
00111           block->_enabled) {
00112         // Now save the block with the lowest ordinal.
00113         if (_block == (CLwoSurfaceBlock *)NULL) {
00114           _block = block;
00115 
00116         } else if (block->_ordinal < _block->_ordinal) {
00117           delete _block;
00118           _block = block;
00119 
00120         } else {
00121           delete block;
00122         }
00123 
00124       } else {
00125         delete block;
00126       }
00127     }
00128   }
00129 
00130   // Now get the four-component color, based on combining the RGB and
00131   // the transparency.
00132   _color.set(1.0, 1.0, 1.0, 1.0);
00133 
00134   if ((_flags & F_rgb) != 0) {
00135     _color[0] = _rgb[0];
00136     _color[1] = _rgb[1];
00137     _color[2] = _rgb[2];
00138   }
00139 
00140   if ((_flags & F_transparency) != 0) {
00141     _color[3] = 1.0 - _transparency;
00142   }
00143 
00144   _diffuse_color = _color;
00145 }
00146 
00147 ////////////////////////////////////////////////////////////////////
00148 //     Function: CLwoSurface::Destructor
00149 //       Access: Public
00150 //  Description:
00151 ////////////////////////////////////////////////////////////////////
00152 CLwoSurface::
00153 ~CLwoSurface() {
00154   if (_block != (CLwoSurfaceBlock *)NULL) {
00155     delete _block;
00156   }
00157 }
00158 
00159 ////////////////////////////////////////////////////////////////////
00160 //     Function: CLwoSurface::apply_properties
00161 //       Access: Public
00162 //  Description: Applies the color, texture, etc. described by the
00163 //               surface to the indicated egg primitive.
00164 //
00165 //               If the surface defines a smoothing angle,
00166 //               smooth_angle may be updated to reflect it if the
00167 //               angle is greater than that specified.
00168 ////////////////////////////////////////////////////////////////////
00169 void CLwoSurface::
00170 apply_properties(EggPrimitive *egg_prim, vector_PT_EggVertex &egg_vertices,
00171                  PN_stdfloat &smooth_angle) {
00172   if (!_surface->_source.empty()) {
00173     // This surface is derived from another surface; apply that one
00174     // first.
00175     CLwoSurface *parent = _converter->get_surface(_surface->_source);
00176     if (parent != (CLwoSurface *)NULL && parent != this) {
00177       parent->apply_properties(egg_prim, egg_vertices, smooth_angle);
00178     }
00179   }
00180 
00181   bool has_texture = check_texture();
00182   bool has_material = check_material();
00183 
00184   egg_prim->set_color(_diffuse_color);
00185 
00186   if (has_material) {
00187     egg_prim->set_material(_egg_material);
00188   }
00189 
00190   if (has_texture) {
00191     egg_prim->set_texture(_egg_texture);
00192 
00193     // Assign UV's to the vertices.
00194     generate_uvs(egg_vertices);
00195   }
00196 
00197   if ((_flags & F_backface) != 0) {
00198     egg_prim->set_bface_flag(_backface);
00199   }
00200 
00201   if ((_flags & F_smooth_angle) != 0) {
00202     smooth_angle = max(smooth_angle, _smooth_angle);
00203   }
00204 }
00205 
00206 ////////////////////////////////////////////////////////////////////
00207 //     Function: CLwoSurface::check_texture
00208 //       Access: Public
00209 //  Description: Checks whether the surface demands a texture or not.
00210 //               Returns true if so, false otherwise.
00211 //
00212 //               If the surface demands a texture, this also sets up
00213 //               _egg_texture and _compute_uvs as appropriate for the
00214 //               texture.
00215 ////////////////////////////////////////////////////////////////////
00216 bool CLwoSurface::
00217 check_texture() {
00218   if (_checked_texture) {
00219     return (_egg_texture != (EggTexture *)NULL);
00220   }
00221   _checked_texture = true;
00222   _egg_texture = (EggTexture *)NULL;
00223   _map_uvs = NULL;
00224 
00225   if (_block == (CLwoSurfaceBlock *)NULL) {
00226     // No texture.  Not even a shader block.
00227     return false;
00228   }
00229 
00230   int clip_index = _block->_clip_index;
00231   if (clip_index < 0) {
00232     // No image file associated with the texture.
00233     return false;
00234   }
00235 
00236   CLwoClip *clip = _converter->get_clip(clip_index);
00237   if (clip == (CLwoClip *)NULL) {
00238     nout << "No clip image with index " << clip_index << "\n";
00239     return false;
00240   }
00241 
00242   if (!clip->is_still_image()) {
00243     // Can't do anything with an animated image right now.
00244     return false;
00245   }
00246 
00247   Filename pathname = _converter->convert_model_path(clip->_filename);
00248 
00249   _egg_texture = new EggTexture("clip" + format_string(clip_index), pathname);
00250 
00251   // Do we need to generate UV's?
00252   switch (_block->_projection_mode) {
00253   case LwoSurfaceBlockProjection::M_planar:
00254     _map_uvs = &CLwoSurface::map_planar;
00255     break;
00256 
00257   case LwoSurfaceBlockProjection::M_cylindrical:
00258     _map_uvs = &CLwoSurface::map_cylindrical;
00259     break;
00260 
00261   case LwoSurfaceBlockProjection::M_spherical:
00262     _map_uvs = &CLwoSurface::map_spherical;
00263     break;
00264 
00265   case LwoSurfaceBlockProjection::M_cubic:
00266     _map_uvs = &CLwoSurface::map_cubic;
00267     break;
00268 
00269   case LwoSurfaceBlockProjection::M_front:
00270     // Cannot generate "front" UV's, since this depends on a camera.
00271     // Is it supposed to be updated in real time, like a projected
00272     // texture?
00273     break;
00274 
00275   case LwoSurfaceBlockProjection::M_uv:
00276     // "uv" projection means to use the existing UV's already defined
00277     // for the vertex.  This case was already handled in the code that
00278     // created the EggVertex pointers.
00279     break;
00280   };
00281 
00282   // Texture overrides the primitive's natural color.
00283   _color[0] = 1.0;
00284   _color[1] = 1.0;
00285   _color[2] = 1.0;
00286 
00287   return true;
00288 }
00289 
00290 ////////////////////////////////////////////////////////////////////
00291 //     Function: CLwoSurface::check_material
00292 //       Access: Public
00293 //  Description: Checks whether the surface demands a material or not.
00294 //               Returns true if so, false otherwise.
00295 ////////////////////////////////////////////////////////////////////
00296 bool CLwoSurface::
00297 check_material() {
00298   if (_checked_material) {
00299     return (_egg_material != (EggMaterial *)NULL);
00300   }
00301   _checked_material = true;
00302   _egg_material = (EggMaterial *)NULL;
00303 
00304   if (!_converter->_make_materials) {
00305     // If we aren't making materials, then don't make a material.
00306     return false;
00307   }
00308 
00309   _egg_material = new EggMaterial(get_name());
00310 
00311   if ((_flags & F_diffuse) != 0) {
00312     _diffuse_color.set(_color[0] * _diffuse,
00313                        _color[1] * _diffuse,
00314                        _color[2] * _diffuse,
00315                        _color[3]);
00316     // We want to avoid setting the diffuse color on the material.
00317     // We're already setting the color explicitly on the object, so
00318     // there's no need to also set a diffuse color on the material,
00319     // and doing so prevents nice features like set_color() and
00320     // set_color_scale() from working in Panda.
00321 
00322     //_egg_material->set_diff(_diffuse_color);
00323   }
00324 
00325   if ((_flags & F_luminosity) != 0) {
00326     LColor luminosity(_color[0] * _luminosity,
00327                       _color[1] * _luminosity,
00328                       _color[2] * _luminosity,
00329                       1.0);
00330     _egg_material->set_emit(luminosity);
00331   }
00332 
00333   if ((_flags & F_specular) != 0) {
00334     LColor specular(_color[0] * _specular,
00335                     _color[1] * _specular,
00336                     _color[2] * _specular,
00337                     1.0);
00338     _egg_material->set_spec(specular);
00339   }
00340 
00341   if ((_flags & F_gloss) != 0) {
00342     _egg_material->set_shininess(_gloss * 128.0);
00343   }
00344 
00345   return true;
00346 }
00347 
00348 
00349 ////////////////////////////////////////////////////////////////////
00350 //     Function: CLwoSurface::generate_uvs
00351 //       Access: Private
00352 //  Description: Computes all the UV's for the polygon's vertices,
00353 //               according to the _projection_mode defined in the
00354 //               block.
00355 ////////////////////////////////////////////////////////////////////
00356 void CLwoSurface::
00357 generate_uvs(vector_PT_EggVertex &egg_vertices) {
00358   if (_map_uvs == NULL) {
00359     return;
00360   }
00361 
00362   // To do this properly near seams and singularities (for instance,
00363   // the back seam and the poles of the spherical map), we will need
00364   // to know the polygon's centroid.
00365   LPoint3d centroid(0.0, 0.0, 0.0);
00366 
00367   vector_PT_EggVertex::const_iterator vi;
00368   for (vi = egg_vertices.begin(); vi != egg_vertices.end(); ++vi) {
00369     EggVertex *egg_vertex = (*vi);
00370     centroid += egg_vertex->get_pos3();
00371   }
00372 
00373   centroid /= (double)egg_vertices.size();
00374   centroid = centroid * _block->_inv_transform;
00375 
00376   // Now go back through and actually compute the UV's.
00377   for (vi = egg_vertices.begin(); vi != egg_vertices.end(); ++vi) {
00378     EggVertex *egg_vertex = (*vi);
00379     LPoint3d pos = egg_vertex->get_pos3() * _block->_inv_transform;
00380     LPoint2d uv = (this->*_map_uvs)(pos, centroid);
00381     egg_vertex->set_uv(uv);
00382   }
00383 }
00384 
00385 ////////////////////////////////////////////////////////////////////
00386 //     Function: CLwoSurface::map_planar
00387 //       Access: Private
00388 //  Description: Computes a UV based on the given point in space,
00389 //               using a planar projection.
00390 ////////////////////////////////////////////////////////////////////
00391 LPoint2d CLwoSurface::
00392 map_planar(const LPoint3d &pos, const LPoint3d &) const {
00393   // A planar projection is about as easy as can be.  We ignore the Y
00394   // axis, and project the point into the XZ plane.  Done.
00395   double u = (pos[0] + 0.5);
00396   double v = (pos[2] + 0.5);
00397 
00398   return LPoint2d(u, v);
00399 }
00400 
00401 ////////////////////////////////////////////////////////////////////
00402 //     Function: CLwoSurface::map_spherical
00403 //       Access: Private
00404 //  Description: Computes a UV based on the given point in space,
00405 //               using a spherical projection.
00406 ////////////////////////////////////////////////////////////////////
00407 LPoint2d CLwoSurface::
00408 map_spherical(const LPoint3d &pos, const LPoint3d &centroid) const {
00409   // To compute the x position on the frame, we only need to consider
00410   // the angle of the vector about the Y axis.  Project the vector
00411   // into the XZ plane to do this.
00412 
00413   LVector2d xz_orig(pos[0], pos[2]);
00414   LVector2d xz = xz_orig;
00415   double u_offset = 0.0;
00416 
00417   if (xz == LVector2d::zero()) {
00418     // If we have a point on either pole, we've got problems.  This
00419     // point maps to the entire bottom edge of the image, so which U
00420     // value should we choose?  It does make a difference, especially
00421     // if we have a number of polygons around the south pole that all
00422     // share the common vertex.
00423 
00424     // We choose the U value based on the polygon's centroid.
00425     xz.set(centroid[0], centroid[2]);
00426 
00427   } else if (xz[1] >= 0.0 && ((xz[0] < 0.0) != (centroid[0] < 0.))) {
00428     // Now, if our polygon crosses the seam along the back of the
00429     // sphere--that is, the point is on the back of the sphere (xz[1]
00430     // >= 0.0) and not on the same side of the XZ plane as the
00431     // centroid, we've got problems too.  We need to add an offset to
00432     // the computed U value, either 1 or -1, to keep all the vertices
00433     // of the polygon on the same side of the seam.
00434 
00435     u_offset = (xz[0] < 0.0) ? 1.0 : -1.0;
00436   }
00437 
00438   // The U value is based on the longitude: the angle about the Y
00439   // axis.
00440   double u =
00441     (atan2(xz[0], -xz[1]) / (2.0 * MathNumbers::pi) + 0.5 + u_offset) * _block->_w_repeat;
00442 
00443   // Now rotate the vector into the YZ plane, and the V value is based
00444   // on the latitude: the angle about the X axis.
00445   LVector2d yz(pos[1], xz_orig.length());
00446   double v =
00447     (atan2(yz[0], yz[1]) / MathNumbers::pi + 0.5) * _block->_h_repeat;
00448 
00449   return LPoint2d(u, v);
00450 }
00451 
00452 ////////////////////////////////////////////////////////////////////
00453 //     Function: CLwoSurface::map_cylindrical
00454 //       Access: Private
00455 //  Description: Computes a UV based on the given point in space,
00456 //               using a cylindrical projection.
00457 ////////////////////////////////////////////////////////////////////
00458 LPoint2d CLwoSurface::
00459 map_cylindrical(const LPoint3d &pos, const LPoint3d &centroid) const {
00460   // This is almost identical to the spherical projection, except for
00461   // the computation of V.
00462 
00463   LVector2d xz(pos[0], pos[2]);
00464   double u_offset = 0.0;
00465 
00466   if (xz == LVector2d::zero()) {
00467     // Although a cylindrical mapping does not really have a
00468     // singularity at the pole, it's still possible to put a point
00469     // there, and we'd like to do the right thing with the polygon
00470     // that shares that point.  So the singularity logic remains.
00471     xz.set(centroid[0], centroid[2]);
00472 
00473   } else if (xz[1] >= 0.0 && ((xz[0] < 0.0) != (centroid[0] < 0.))) {
00474     // And cylinders do still have a seam at the back.
00475     u_offset = (xz[0] < 0.0) ? 1.0 : -1.0;
00476   }
00477 
00478   double u =
00479     (atan2(xz[0], -xz[1]) / (2.0 * MathNumbers::pi) + 0.5 + u_offset) * _block->_w_repeat;
00480 
00481   // For a cylindrical mapping, the V value comes almost directly from
00482   // Y.  Easy.
00483   double v = (pos[1] + 0.5);
00484 
00485   return LPoint2d(u, v);
00486 }
00487 
00488 ////////////////////////////////////////////////////////////////////
00489 //     Function: CLwoSurface::map_cubic
00490 //       Access: Private
00491 //  Description: Computes a UV based on the given point in space,
00492 //               using a cubic projection.
00493 ////////////////////////////////////////////////////////////////////
00494 LPoint2d CLwoSurface::
00495 map_cubic(const LPoint3d &pos, const LPoint3d &centroid) const {
00496   // A cubic projection is a planar projection, but we eliminate the
00497   // dominant axis (based on the polygon's centroid) instead of
00498   // arbitrarily eliminating Y.
00499 
00500   double x = fabs(centroid[0]);
00501   double y = fabs(centroid[1]);
00502   double z = fabs(centroid[2]);
00503 
00504   double u, v;
00505 
00506   if (x > y) {
00507     if (x > z) {
00508       // X is dominant.
00509       u = (pos[2] + 0.5);
00510       v = (pos[1] + 0.5);
00511     } else {
00512       // Z is dominant.
00513       u = (pos[0] + 0.5);
00514       v = (pos[1] + 0.5);
00515     }
00516   } else {
00517     if (y > z) {
00518       // Y is dominant.
00519       u = (pos[0] + 0.5);
00520       v = (pos[2] + 0.5);
00521     } else {
00522       // Z is dominant.
00523       u = (pos[0] + 0.5);
00524       v = (pos[1] + 0.5);
00525     }
00526   }
00527 
00528   return LPoint2d(u, v);
00529 }