Panda3D
|
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 }