Panda3D

txaLine.cxx

00001 // Filename: txaLine.cxx
00002 // Created by:  drose (30Nov00)
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 "txaLine.h"
00016 #include "pal_string_utils.h"
00017 #include "eggFile.h"
00018 #include "palettizer.h"
00019 #include "textureImage.h"
00020 #include "sourceTextureImage.h"
00021 #include "paletteGroup.h"
00022 
00023 #include "pnotify.h"
00024 #include "pnmFileType.h"
00025 
00026 ////////////////////////////////////////////////////////////////////
00027 //     Function: TxaLine::Constructor
00028 //       Access: Public
00029 //  Description:
00030 ////////////////////////////////////////////////////////////////////
00031 TxaLine::
00032 TxaLine() {
00033   _size_type = ST_none;
00034   _scale = 0.0;
00035   _x_size = 0;
00036   _y_size = 0;
00037   _aniso_degree = 0;
00038   _num_channels = 0;
00039   _format = EggTexture::F_unspecified;
00040   _force_format = false;
00041   _generic_format = false;
00042   _keep_format = false;
00043   _alpha_mode = EggRenderMode::AM_unspecified;
00044   _wrap_u = EggTexture::WM_unspecified;
00045   _wrap_v = EggTexture::WM_unspecified;
00046   _quality_level = EggTexture::QL_unspecified;
00047   _got_margin = false;
00048   _margin = 0;
00049   _got_coverage_threshold = false;
00050   _coverage_threshold = 0.0;
00051   _color_type = (PNMFileType *)NULL;
00052   _alpha_type = (PNMFileType *)NULL;
00053 }
00054 
00055 ////////////////////////////////////////////////////////////////////
00056 //     Function: TxaLine::parse
00057 //       Access: Public
00058 //  Description: Accepts a string that defines a line of the .txa file
00059 //               and parses it into its constinuent parts.  Returns
00060 //               true if successful, false on error.
00061 ////////////////////////////////////////////////////////////////////
00062 bool TxaLine::
00063 parse(const string &line) {
00064   size_t colon = line.find(':');
00065   if (colon == string::npos) {
00066     nout << "Colon required.\n";
00067     return false;
00068   }
00069 
00070   // Chop up the first part of the string (preceding the colon) into
00071   // its individual words.  These are patterns to match.
00072   vector_string words;
00073   extract_words(line.substr(0, colon), words);
00074 
00075   vector_string::iterator wi;
00076   for (wi = words.begin(); wi != words.end(); ++wi) {
00077     string word = (*wi);
00078 
00079     // If the pattern ends in the string ".egg", and only if it ends
00080     // in this string, it is deemed an egg pattern and will only be
00081     // tested against egg files.  If it ends in anything else, it is
00082     // deemed a texture pattern and will only be tested against
00083     // textures.
00084     if (word.length() > 4 && word.substr(word.length() - 4) == ".egg") {
00085       GlobPattern pattern(word);
00086       pattern.set_case_sensitive(false);
00087       _egg_patterns.push_back(pattern);
00088 
00089     } else {
00090       // However, the filename extension, if any, is stripped off
00091       // because the texture key names nowadays don't include them.
00092       size_t dot = word.rfind('.');
00093       if (dot != string::npos) {
00094         word = word.substr(0, dot);
00095       }
00096       GlobPattern pattern(word);
00097       pattern.set_case_sensitive(false);
00098       _texture_patterns.push_back(pattern);
00099     }
00100   }
00101 
00102   if (_egg_patterns.empty() && _texture_patterns.empty()) {
00103     nout << "No texture or egg filenames given.\n";
00104     return false;
00105   }
00106 
00107   // Now chop up the rest of the string (following the colon) into its
00108   // individual words.  These are keywords and size indications.
00109   words.clear();
00110   extract_words(line.substr(colon + 1), words);
00111 
00112   wi = words.begin();
00113   while (wi != words.end()) {
00114     const string &word = *wi;
00115     nassertr(!word.empty(), false);
00116 
00117     if (isdigit(word[0])) {
00118       // This is either a new size or a scale percentage.
00119       if (_size_type != ST_none) {
00120         nout << "Invalid repeated size request: " << word << "\n";
00121         return false;
00122       }
00123       if (word[word.length() - 1] == '%') {
00124         // It's a scale percentage!
00125         _size_type = ST_scale;
00126 
00127         string tail;
00128         _scale = string_to_double(word, tail);
00129         if (!(tail == "%")) {
00130           // This is an invalid number.
00131           return false;
00132         }
00133         ++wi;
00134 
00135       } else {
00136         // Collect a number of consecutive numeric fields.
00137         pvector<int> numbers;
00138         while (wi != words.end() && isdigit((*wi)[0])) {
00139           const string &word = *wi;
00140           int num;
00141           if (!string_to_int(word, num)) {
00142             nout << "Invalid size: " << word << "\n";
00143             return false;
00144           }
00145           numbers.push_back(num);
00146           ++wi;
00147         }
00148         if (numbers.size() < 2) {
00149           nout << "At least two size numbers must be given, or a percent sign used to indicate scaling.\n";
00150           return false;
00151 
00152         } else if (numbers.size() == 2) {
00153           _size_type = ST_explicit_2;
00154           _x_size = numbers[0];
00155           _y_size = numbers[1];
00156 
00157         } else if (numbers.size() == 3) {
00158           _size_type = ST_explicit_3;
00159           _x_size = numbers[0];
00160           _y_size = numbers[1];
00161           _num_channels = numbers[2];
00162 
00163         } else {
00164           nout << "Too many size numbers given.\n";
00165           return false;
00166         }
00167       }
00168 
00169     } else {
00170       // The word does not begin with a digit; therefore it's either a
00171       // keyword or an image file type request.
00172       if (word == "omit") {
00173         _keywords.push_back(KW_omit);
00174 
00175       } else if (word == "nearest") {
00176         _keywords.push_back(KW_nearest);
00177 
00178       } else if (word == "linear") {
00179         _keywords.push_back(KW_linear);
00180 
00181       } else if (word == "mipmap") {
00182         _keywords.push_back(KW_mipmap);
00183 
00184       } else if (word == "cont") {
00185         _keywords.push_back(KW_cont);
00186 
00187       } else if (word == "margin") {
00188         ++wi;
00189         if (wi == words.end()) {
00190           nout << "Argument required for 'margin'.\n";
00191           return false;
00192         }
00193 
00194         const string &arg = (*wi);
00195         if (!string_to_int(arg, _margin)) {
00196           nout << "Not an integer: " << arg << "\n";
00197           return false;
00198         }
00199         if (_margin < 0) {
00200           nout << "Invalid margin: " << _margin << "\n";
00201           return false;
00202         }
00203         _got_margin = true;
00204 
00205       } else if (word == "aniso") {
00206         ++wi;
00207         if (wi == words.end()) {
00208           nout << "Integer argument required for 'aniso'.\n";
00209           return false;
00210         }
00211 
00212         const string &arg = (*wi);
00213         if (!string_to_int(arg, _aniso_degree)) {
00214           nout << "Not an integer: " << arg << "\n";
00215           return false;
00216         }
00217         if ((_aniso_degree < 2) || (_aniso_degree > 16)) {
00218           // make it an error to specific degree 0 or 1, which means no anisotropy so it's probably an input mistake
00219           nout << "Invalid anistropic degree (range is 2-16): " << _aniso_degree << "\n";
00220           return false;
00221         }
00222 
00223         _keywords.push_back(KW_anisotropic);
00224 
00225       } else if (word == "coverage") {
00226         ++wi;
00227         if (wi == words.end()) {
00228           nout << "Argument required for 'coverage'.\n";
00229           return false;
00230         }
00231 
00232         const string &arg = (*wi);
00233         if (!string_to_double(arg, _coverage_threshold)) {
00234           nout << "Not a number: " << arg << "\n";
00235           return false;
00236         }
00237         if (_coverage_threshold <= 0.0) {
00238           nout << "Invalid coverage threshold: " << _coverage_threshold << "\n";
00239           return false;
00240         }
00241         _got_coverage_threshold = true;
00242 
00243       } else if (word.substr(0, 6) == "force-") {
00244         // Force a particular format, despite the number of channels
00245         // in the image.
00246         string format_name = word.substr(6);
00247         EggTexture::Format format = EggTexture::string_format(format_name);
00248         if (format != EggTexture::F_unspecified) {
00249           _format = format;
00250           _force_format = true;
00251         } else {
00252           nout << "Unknown image format: " << format_name << "\n";
00253           return false;
00254         }
00255 
00256       } else if (word == "generic") {
00257         // Genericize the image format by replacing bitcount-specific
00258         // formats with their generic equivalents, e.g. rgba8 becomes
00259         // rgba.
00260         _generic_format = true;
00261 
00262       } else if (word == "keep-format") {
00263         // Keep whatever image format was specified.
00264         _keep_format = true;
00265 
00266       } else {
00267         // Maybe it's a group name.
00268         PaletteGroup *group = pal->test_palette_group(word);
00269         if (group != (PaletteGroup *)NULL) {
00270           _palette_groups.insert(group);
00271 
00272         } else {
00273           // Maybe it's a format name.  This suggests an image format,
00274           // but may be overridden to reflect the number of channels in
00275           // the image.
00276           EggTexture::Format format = EggTexture::string_format(word);
00277           if (format != EggTexture::F_unspecified) {
00278             if (!_force_format) {
00279               _format = format;
00280             }
00281           } else {
00282             // Maybe it's an alpha mode.
00283             EggRenderMode::AlphaMode am = EggRenderMode::string_alpha_mode(word);
00284             if (am != EggRenderMode::AM_unspecified) {
00285               _alpha_mode = am;
00286 
00287             } else {
00288               // Maybe it's a quality level.
00289               EggTexture::QualityLevel ql = EggTexture::string_quality_level(word);
00290               if (ql != EggTexture::QL_unspecified) {
00291                 _quality_level = ql;
00292 
00293               } else if (word.length() > 2 && word[word.length() - 2] == '_' &&
00294                          strchr("uv", word[word.length() - 1]) != NULL) {
00295                 // It must be a wrap mode for u or v.
00296                 string prefix = word.substr(0, word.length() - 2);
00297                 EggTexture::WrapMode wm = EggTexture::string_wrap_mode(prefix);
00298                 if (wm == EggTexture::WM_unspecified) {
00299                   return false;
00300                 }
00301                 switch (word[word.length() - 1]) {
00302                 case 'u':
00303                   _wrap_u = wm;
00304                   break;
00305                   
00306                 case 'v':
00307                   _wrap_v = wm;
00308                   break;
00309                 }
00310                 
00311               } else {
00312                 // Maybe it's an image file request.
00313                 if (!parse_image_type_request(word, _color_type, _alpha_type)) {
00314                   return false;
00315                 }
00316               }
00317             }
00318           }
00319         }
00320       }
00321       ++wi;
00322     }
00323   }
00324 
00325   return true;
00326 }
00327 
00328 ////////////////////////////////////////////////////////////////////
00329 //     Function: TxaLine::match_egg
00330 //       Access: Public
00331 //  Description: Compares the patterns on the line to the indicated
00332 //               EggFile.  If they match, updates the egg with the
00333 //               appropriate information.  Returns true if a match is
00334 //               detected and the search for another line should stop,
00335 //               or false if a match is not detected (or if the
00336 //               keyword "cont" is present, which means the search
00337 //               should continue regardless).
00338 ////////////////////////////////////////////////////////////////////
00339 bool TxaLine::
00340 match_egg(EggFile *egg_file) const {
00341   string name = egg_file->get_name();
00342 
00343   bool matched_any = false;
00344   Patterns::const_iterator pi;
00345   for (pi = _egg_patterns.begin();
00346        pi != _egg_patterns.end() && !matched_any;
00347        ++pi) {
00348     matched_any = (*pi).matches(name);
00349   }
00350 
00351   if (!matched_any) {
00352     // No match this line; continue.
00353     return false;
00354   }
00355 
00356   bool got_cont = false;
00357   Keywords::const_iterator ki;
00358   for (ki = _keywords.begin(); ki != _keywords.end(); ++ki) {
00359     switch (*ki) {
00360     case KW_omit:
00361       break;
00362 
00363     case KW_nearest:
00364     case KW_linear:
00365     case KW_mipmap:
00366     case KW_anisotropic:
00367       // These mean nothing to an egg file.
00368       break;
00369 
00370     case KW_cont:
00371       got_cont = true;
00372       break;
00373     }
00374   }
00375 
00376   egg_file->match_txa_groups(_palette_groups);
00377 
00378   if (got_cont) {
00379     // If we have the "cont" keyword, we should keep scanning for
00380     // another line, even though we matched this one.
00381     return false;
00382   }
00383 
00384   // Otherwise, in the normal case, a match ends the search for
00385   // matches.
00386   egg_file->clear_surprise();
00387 
00388   return true;
00389 }
00390 
00391 ////////////////////////////////////////////////////////////////////
00392 //     Function: TxaLine::match_texture
00393 //       Access: Public
00394 //  Description: Compares the patterns on the line to the indicated
00395 //               TextureImage.  If they match, updates the texture
00396 //               with the appropriate information.  Returns true if a
00397 //               match is detected and the search for another line
00398 //               should stop, or false if a match is not detected (or
00399 //               if the keyword "cont" is present, which means the
00400 //               search should continue regardless).
00401 ////////////////////////////////////////////////////////////////////
00402 bool TxaLine::
00403 match_texture(TextureImage *texture) const {
00404   string name = texture->get_name();
00405 
00406   bool matched_any = false;
00407   Patterns::const_iterator pi;
00408   for (pi = _texture_patterns.begin();
00409        pi != _texture_patterns.end() && !matched_any;
00410        ++pi) {
00411     matched_any = (*pi).matches(name);
00412   }
00413 
00414   if (!matched_any) {
00415     // No match this line; continue.
00416     return false;
00417   }
00418 
00419   SourceTextureImage *source = texture->get_preferred_source();
00420   TextureRequest &request = texture->_request;
00421 
00422   if (!request._got_size) {
00423     switch (_size_type) {
00424     case ST_none:
00425       break;
00426 
00427     case ST_scale:
00428       if (source != (SourceTextureImage *)NULL && source->get_size()) {
00429         request._got_size = true;
00430         request._x_size = max(1, (int)(source->get_x_size() * _scale / 100.0));
00431         request._y_size = max(1, (int)(source->get_y_size() * _scale / 100.0));
00432       }
00433       break;
00434 
00435     case ST_explicit_3:
00436       request._got_num_channels = true;
00437       request._num_channels = _num_channels;
00438       // fall through
00439 
00440     case ST_explicit_2:
00441       request._got_size = true;
00442       request._x_size = _x_size;
00443       request._y_size = _y_size;
00444       break;
00445     }
00446   }
00447 
00448   if (_got_margin) {
00449     request._margin = _margin;
00450   }
00451 
00452   if (_got_coverage_threshold) {
00453     request._coverage_threshold = _coverage_threshold;
00454   }
00455 
00456   if (_color_type != (PNMFileType *)NULL) {
00457     request._properties._color_type = _color_type;
00458     request._properties._alpha_type = _alpha_type;
00459   }
00460 
00461   if (_quality_level != EggTexture::QL_unspecified) {
00462     request._properties._quality_level = _quality_level;
00463   }
00464 
00465   if (_format != EggTexture::F_unspecified) {
00466     request._format = _format;
00467     request._force_format = _force_format;
00468     request._generic_format = false;
00469   }
00470 
00471   if (_generic_format) {
00472     request._generic_format = true;
00473   }
00474 
00475   if (_keep_format) {
00476     request._keep_format = true;
00477   }
00478 
00479   if (_alpha_mode != EggRenderMode::AM_unspecified) {
00480     request._alpha_mode = _alpha_mode;
00481   }
00482 
00483   if (_wrap_u != EggTexture::WM_unspecified) {
00484     request._wrap_u = _wrap_u;
00485   }
00486   if (_wrap_v != EggTexture::WM_unspecified) {
00487     request._wrap_v = _wrap_v;
00488   }
00489 
00490   bool got_cont = false;
00491   Keywords::const_iterator ki;
00492   for (ki = _keywords.begin(); ki != _keywords.end(); ++ki) {
00493     switch (*ki) {
00494     case KW_omit:
00495       request._omit = true;
00496       break;
00497 
00498     case KW_nearest:
00499       request._minfilter = EggTexture::FT_nearest;
00500       request._magfilter = EggTexture::FT_nearest;
00501       break;
00502 
00503     case KW_linear:
00504       request._minfilter = EggTexture::FT_linear;
00505       request._magfilter = EggTexture::FT_linear;
00506       break;
00507 
00508     case KW_mipmap:
00509       request._minfilter = EggTexture::FT_linear_mipmap_linear;
00510       request._magfilter = EggTexture::FT_linear_mipmap_linear;
00511       break;
00512 
00513     case KW_anisotropic:
00514       request._anisotropic_degree = _aniso_degree;
00515       break;
00516 
00517     case KW_cont:
00518       got_cont = true;
00519       break;
00520     }
00521   }
00522 
00523   texture->_explicitly_assigned_groups.make_union
00524     (texture->_explicitly_assigned_groups, _palette_groups);
00525   texture->_explicitly_assigned_groups.remove_null();
00526 
00527   if (got_cont) {
00528     // If we have the "cont" keyword, we should keep scanning for
00529     // another line, even though we matched this one.
00530     return false;
00531   }
00532 
00533   // Otherwise, in the normal case, a match ends the search for
00534   // matches.
00535   texture->_is_surprise = false;
00536 
00537   return true;
00538 }
00539 
00540 ////////////////////////////////////////////////////////////////////
00541 //     Function: TxaLine::output
00542 //       Access: Public
00543 //  Description:
00544 ////////////////////////////////////////////////////////////////////
00545 void TxaLine::
00546 output(ostream &out) const {
00547   Patterns::const_iterator pi;
00548   for (pi = _texture_patterns.begin(); pi != _texture_patterns.end(); ++pi) {
00549     out << (*pi) << " ";
00550   }
00551   for (pi = _egg_patterns.begin(); pi != _egg_patterns.end(); ++pi) {
00552     out << (*pi) << " ";
00553   }
00554   out << ":";
00555 
00556   switch (_size_type) {
00557   case ST_none:
00558     break;
00559 
00560   case ST_scale:
00561     out << " " << _scale << "%";
00562     break;
00563 
00564   case ST_explicit_2:
00565     out << " " << _x_size << " " << _y_size;
00566     break;
00567 
00568   case ST_explicit_3:
00569     out << " " << _x_size << " " << _y_size << " " << _num_channels;
00570     break;
00571   }
00572 
00573   if (_got_margin) {
00574     out << " margin " << _margin;
00575   }
00576 
00577   if (_got_coverage_threshold) {
00578     out << " coverage " << _coverage_threshold;
00579   }
00580 
00581   Keywords::const_iterator ki;
00582   for (ki = _keywords.begin(); ki != _keywords.end(); ++ki) {
00583     switch (*ki) {
00584     case KW_omit:
00585       out << " omit";
00586       break;
00587 
00588     case KW_nearest:
00589       out << " nearest";
00590       break;
00591 
00592     case KW_linear:
00593       out << " linear";
00594       break;
00595 
00596     case KW_mipmap:
00597       out << " mipmap";
00598       break;
00599 
00600     case KW_cont:
00601       out << " cont";
00602       break;
00603 
00604     case KW_anisotropic:
00605       out << " aniso " << _aniso_degree;
00606       break;
00607     }
00608   }
00609 
00610   PaletteGroups::const_iterator gi;
00611   for (gi = _palette_groups.begin(); gi != _palette_groups.end(); ++gi) {
00612     out << " " << (*gi)->get_name();
00613   }
00614 
00615   if (_format != EggTexture::F_unspecified) {
00616     out << " " << _format;
00617     if (_force_format) {
00618       out << " (forced)";
00619     }
00620   }
00621 
00622   if (_color_type != (PNMFileType *)NULL) {
00623     out << " " << _color_type->get_suggested_extension();
00624     if (_alpha_type != (PNMFileType *)NULL) {
00625       out << "," << _alpha_type->get_suggested_extension();
00626     }
00627   }
00628 }
 All Classes Functions Variables Enumerations