Panda3D
Loading...
Searching...
No Matches
txaLine.cxx
Go to the documentation of this file.
1/**
2 * PANDA 3D SOFTWARE
3 * Copyright (c) Carnegie Mellon University. All rights reserved.
4 *
5 * All use of this software is subject to the terms of the revised BSD
6 * license. You should have received a copy of this license along
7 * with this source code in a file named "LICENSE."
8 *
9 * @file txaLine.cxx
10 * @author drose
11 * @date 2000-11-30
12 */
13
14#include "txaLine.h"
15#include "pal_string_utils.h"
16#include "eggFile.h"
17#include "palettizer.h"
18#include "textureImage.h"
19#include "sourceTextureImage.h"
20#include "paletteGroup.h"
21
22#include "pnotify.h"
23#include "pnmFileType.h"
24
25using std::string;
26
27/**
28 *
29 */
30TxaLine::
31TxaLine() {
32 _size_type = ST_none;
33 _scale = 0.0;
34 _x_size = 0;
35 _y_size = 0;
36 _aniso_degree = 0;
37 _num_channels = 0;
38 _format = EggTexture::F_unspecified;
39 _force_format = false;
40 _generic_format = false;
41 _keep_format = false;
42 _alpha_mode = EggRenderMode::AM_unspecified;
43 _wrap_u = EggTexture::WM_unspecified;
44 _wrap_v = EggTexture::WM_unspecified;
45 _quality_level = EggTexture::QL_unspecified;
46 _got_margin = false;
47 _margin = 0;
48 _got_coverage_threshold = false;
49 _coverage_threshold = 0.0;
50 _color_type = nullptr;
51 _alpha_type = nullptr;
52}
53
54/**
55 * Accepts a string that defines a line of the .txa file and parses it into
56 * its constinuent parts. Returns true if successful, false on error.
57 */
59parse(const string &line) {
60 size_t colon = line.find(':');
61 if (colon == string::npos) {
62 nout << "Colon required.\n";
63 return false;
64 }
65
66 // Chop up the first part of the string (preceding the colon) into its
67 // individual words. These are patterns to match.
68 vector_string words;
69 extract_words(line.substr(0, colon), words);
70
71 vector_string::iterator wi;
72 for (wi = words.begin(); wi != words.end(); ++wi) {
73 string word = (*wi);
74
75 // If the pattern ends in the string ".egg", and only if it ends in this
76 // string, it is deemed an egg pattern and will only be tested against egg
77 // files. If it ends in anything else, it is deemed a texture pattern and
78 // will only be tested against textures.
79 if (word.length() > 4 && word.substr(word.length() - 4) == ".egg") {
80 GlobPattern pattern(word);
81 pattern.set_case_sensitive(false);
82 _egg_patterns.push_back(pattern);
83
84 } else {
85 // However, the filename extension, if any, is stripped off because the
86 // texture key names nowadays don't include them.
87 size_t dot = word.rfind('.');
88 if (dot != string::npos) {
89 word = word.substr(0, dot);
90 }
91 GlobPattern pattern(word);
92 pattern.set_case_sensitive(false);
93 _texture_patterns.push_back(pattern);
94 }
95 }
96
97 if (_egg_patterns.empty() && _texture_patterns.empty()) {
98 nout << "No texture or egg filenames given.\n";
99 return false;
100 }
101
102 // Now chop up the rest of the string (following the colon) into its
103 // individual words. These are keywords and size indications.
104 words.clear();
105 extract_words(line.substr(colon + 1), words);
106
107 wi = words.begin();
108 while (wi != words.end()) {
109 const string &word = *wi;
110 nassertr(!word.empty(), false);
111
112 if (isdigit(word[0])) {
113 // This is either a new size or a scale percentage.
114 if (_size_type != ST_none) {
115 nout << "Invalid repeated size request: " << word << "\n";
116 return false;
117 }
118 if (word[word.length() - 1] == '%') {
119 // It's a scale percentage!
120 _size_type = ST_scale;
121
122 string tail;
123 _scale = string_to_double(word, tail);
124 if (!(tail == "%")) {
125 // This is an invalid number.
126 return false;
127 }
128 ++wi;
129
130 } else {
131 // Collect a number of consecutive numeric fields.
132 pvector<int> numbers;
133 while (wi != words.end() && isdigit((*wi)[0])) {
134 const string &word = *wi;
135 int num;
136 if (!string_to_int(word, num)) {
137 nout << "Invalid size: " << word << "\n";
138 return false;
139 }
140 numbers.push_back(num);
141 ++wi;
142 }
143 if (numbers.size() < 2) {
144 nout << "At least two size numbers must be given, or a percent sign used to indicate scaling.\n";
145 return false;
146
147 } else if (numbers.size() == 2) {
148 _size_type = ST_explicit_2;
149 _x_size = numbers[0];
150 _y_size = numbers[1];
151
152 } else if (numbers.size() == 3) {
153 _size_type = ST_explicit_3;
154 _x_size = numbers[0];
155 _y_size = numbers[1];
156 _num_channels = numbers[2];
157
158 } else {
159 nout << "Too many size numbers given.\n";
160 return false;
161 }
162 }
163
164 } else {
165 // The word does not begin with a digit; therefore it's either a keyword
166 // or an image file type request.
167 if (word == "omit") {
168 _keywords.push_back(KW_omit);
169
170 } else if (word == "nearest") {
171 _keywords.push_back(KW_nearest);
172
173 } else if (word == "linear") {
174 _keywords.push_back(KW_linear);
175
176 } else if (word == "mipmap") {
177 _keywords.push_back(KW_mipmap);
178
179 } else if (word == "cont") {
180 _keywords.push_back(KW_cont);
181
182 } else if (word == "margin") {
183 ++wi;
184 if (wi == words.end()) {
185 nout << "Argument required for 'margin'.\n";
186 return false;
187 }
188
189 const string &arg = (*wi);
190 if (!string_to_int(arg, _margin)) {
191 nout << "Not an integer: " << arg << "\n";
192 return false;
193 }
194 if (_margin < 0) {
195 nout << "Invalid margin: " << _margin << "\n";
196 return false;
197 }
198 _got_margin = true;
199
200 } else if (word == "aniso") {
201 ++wi;
202 if (wi == words.end()) {
203 nout << "Integer argument required for 'aniso'.\n";
204 return false;
205 }
206
207 const string &arg = (*wi);
208 if (!string_to_int(arg, _aniso_degree)) {
209 nout << "Not an integer: " << arg << "\n";
210 return false;
211 }
212 if ((_aniso_degree < 2) || (_aniso_degree > 16)) {
213 // make it an error to specific degree 0 or 1, which means no
214 // anisotropy so it's probably an input mistake
215 nout << "Invalid anistropic degree (range is 2-16): " << _aniso_degree << "\n";
216 return false;
217 }
218
219 _keywords.push_back(KW_anisotropic);
220
221 } else if (word == "coverage") {
222 ++wi;
223 if (wi == words.end()) {
224 nout << "Argument required for 'coverage'.\n";
225 return false;
226 }
227
228 const string &arg = (*wi);
229 if (!string_to_double(arg, _coverage_threshold)) {
230 nout << "Not a number: " << arg << "\n";
231 return false;
232 }
233 if (_coverage_threshold <= 0.0) {
234 nout << "Invalid coverage threshold: " << _coverage_threshold << "\n";
235 return false;
236 }
237 _got_coverage_threshold = true;
238
239 } else if (word.substr(0, 6) == "force-") {
240 // Force a particular format, despite the number of channels in the
241 // image.
242 string format_name = word.substr(6);
243 EggTexture::Format format = EggTexture::string_format(format_name);
244 if (format != EggTexture::F_unspecified) {
245 _format = format;
246 _force_format = true;
247 } else {
248 nout << "Unknown image format: " << format_name << "\n";
249 return false;
250 }
251
252 } else if (word == "generic") {
253 // Genericize the image format by replacing bitcount-specific formats
254 // with their generic equivalents, e.g. rgba8 becomes rgba.
255 _generic_format = true;
256
257 } else if (word == "keep-format") {
258 // Keep whatever image format was specified.
259 _keep_format = true;
260
261 } else {
262 // Maybe it's a group name.
263 PaletteGroup *group = pal->test_palette_group(word);
264 if (group != nullptr) {
265 _palette_groups.insert(group);
266
267 } else {
268 // Maybe it's a format name. This suggests an image format, but may
269 // be overridden to reflect the number of channels in the image.
270 EggTexture::Format format = EggTexture::string_format(word);
271 if (format != EggTexture::F_unspecified) {
272 if (!_force_format) {
273 _format = format;
274 }
275 } else {
276 // Maybe it's an alpha mode.
277 EggRenderMode::AlphaMode am = EggRenderMode::string_alpha_mode(word);
278 if (am != EggRenderMode::AM_unspecified) {
279 _alpha_mode = am;
280
281 } else {
282 // Maybe it's a quality level.
283 EggTexture::QualityLevel ql = EggTexture::string_quality_level(word);
284 if (ql != EggTexture::QL_unspecified) {
285 _quality_level = ql;
286
287 } else if (word.length() > 2 && word[word.length() - 2] == '_' &&
288 strchr("uv", word[word.length() - 1]) != nullptr) {
289 // It must be a wrap mode for u or v.
290 string prefix = word.substr(0, word.length() - 2);
291 EggTexture::WrapMode wm = EggTexture::string_wrap_mode(prefix);
292 if (wm == EggTexture::WM_unspecified) {
293 return false;
294 }
295 switch (word[word.length() - 1]) {
296 case 'u':
297 _wrap_u = wm;
298 break;
299
300 case 'v':
301 _wrap_v = wm;
302 break;
303 }
304
305 } else {
306 // Maybe it's an image file request.
307 if (!parse_image_type_request(word, _color_type, _alpha_type)) {
308 return false;
309 }
310 }
311 }
312 }
313 }
314 }
315 ++wi;
316 }
317 }
318
319 return true;
320}
321
322/**
323 * Compares the patterns on the line to the indicated EggFile. If they match,
324 * updates the egg with the appropriate information. Returns true if a match
325 * is detected and the search for another line should stop, or false if a
326 * match is not detected (or if the keyword "cont" is present, which means the
327 * search should continue regardless).
328 */
330match_egg(EggFile *egg_file) const {
331 string name = egg_file->get_name();
332
333 bool matched_any = false;
334 Patterns::const_iterator pi;
335 for (pi = _egg_patterns.begin();
336 pi != _egg_patterns.end() && !matched_any;
337 ++pi) {
338 matched_any = (*pi).matches(name);
339 }
340
341 if (!matched_any) {
342 // No match this line; continue.
343 return false;
344 }
345
346 bool got_cont = false;
347 Keywords::const_iterator ki;
348 for (ki = _keywords.begin(); ki != _keywords.end(); ++ki) {
349 switch (*ki) {
350 case KW_omit:
351 break;
352
353 case KW_nearest:
354 case KW_linear:
355 case KW_mipmap:
356 case KW_anisotropic:
357 // These mean nothing to an egg file.
358 break;
359
360 case KW_cont:
361 got_cont = true;
362 break;
363 }
364 }
365
366 egg_file->match_txa_groups(_palette_groups);
367
368 if (got_cont) {
369 // If we have the "cont" keyword, we should keep scanning for another
370 // line, even though we matched this one.
371 return false;
372 }
373
374 // Otherwise, in the normal case, a match ends the search for matches.
375 egg_file->clear_surprise();
376
377 return true;
378}
379
380/**
381 * Compares the patterns on the line to the indicated TextureImage. If they
382 * match, updates the texture with the appropriate information. Returns true
383 * if a match is detected and the search for another line should stop, or
384 * false if a match is not detected (or if the keyword "cont" is present,
385 * which means the search should continue regardless).
386 */
388match_texture(TextureImage *texture) const {
389 string name = texture->get_name();
390
391 bool matched_any = false;
392 Patterns::const_iterator pi;
393 for (pi = _texture_patterns.begin();
394 pi != _texture_patterns.end() && !matched_any;
395 ++pi) {
396 matched_any = (*pi).matches(name);
397 }
398
399 if (!matched_any) {
400 // No match this line; continue.
401 return false;
402 }
403
404 SourceTextureImage *source = texture->get_preferred_source();
405 TextureRequest &request = texture->_request;
406
407 if (!request._got_size) {
408 switch (_size_type) {
409 case ST_none:
410 break;
411
412 case ST_scale:
413 if (source != nullptr && source->get_size()) {
414 request._got_size = true;
415 request._x_size = std::max(1, (int)(source->get_x_size() * _scale / 100.0));
416 request._y_size = std::max(1, (int)(source->get_y_size() * _scale / 100.0));
417 }
418 break;
419
420 case ST_explicit_3:
421 request._got_num_channels = true;
422 request._num_channels = _num_channels;
423 // fall through
424
425 case ST_explicit_2:
426 request._got_size = true;
427 request._x_size = _x_size;
428 request._y_size = _y_size;
429 break;
430 }
431 }
432
433 if (_got_margin) {
434 request._margin = _margin;
435 }
436
437 if (_got_coverage_threshold) {
438 request._coverage_threshold = _coverage_threshold;
439 }
440
441 if (_color_type != nullptr) {
442 request._properties._color_type = _color_type;
443 request._properties._alpha_type = _alpha_type;
444 }
445
446 if (_quality_level != EggTexture::QL_unspecified) {
447 request._properties._quality_level = _quality_level;
448 }
449
450 if (_format != EggTexture::F_unspecified) {
451 request._format = _format;
452 request._force_format = _force_format;
453 request._generic_format = false;
454 }
455
456 if (_generic_format) {
457 request._generic_format = true;
458 }
459
460 if (_keep_format) {
461 request._keep_format = true;
462 }
463
464 if (_alpha_mode != EggRenderMode::AM_unspecified) {
465 request._alpha_mode = _alpha_mode;
466 }
467
468 if (_wrap_u != EggTexture::WM_unspecified) {
469 request._wrap_u = _wrap_u;
470 }
471 if (_wrap_v != EggTexture::WM_unspecified) {
472 request._wrap_v = _wrap_v;
473 }
474
475 bool got_cont = false;
476 Keywords::const_iterator ki;
477 for (ki = _keywords.begin(); ki != _keywords.end(); ++ki) {
478 switch (*ki) {
479 case KW_omit:
480 request._omit = true;
481 break;
482
483 case KW_nearest:
484 request._minfilter = EggTexture::FT_nearest;
485 request._magfilter = EggTexture::FT_nearest;
486 break;
487
488 case KW_linear:
489 request._minfilter = EggTexture::FT_linear;
490 request._magfilter = EggTexture::FT_linear;
491 break;
492
493 case KW_mipmap:
494 request._minfilter = EggTexture::FT_linear_mipmap_linear;
495 request._magfilter = EggTexture::FT_linear_mipmap_linear;
496 break;
497
498 case KW_anisotropic:
499 request._anisotropic_degree = _aniso_degree;
500 break;
501
502 case KW_cont:
503 got_cont = true;
504 break;
505 }
506 }
507
508 texture->_explicitly_assigned_groups.make_union
509 (texture->_explicitly_assigned_groups, _palette_groups);
510 texture->_explicitly_assigned_groups.remove_null();
511
512 if (got_cont) {
513 // If we have the "cont" keyword, we should keep scanning for another
514 // line, even though we matched this one.
515 return false;
516 }
517
518 // Otherwise, in the normal case, a match ends the search for matches.
519 texture->_is_surprise = false;
520
521 return true;
522}
523
524/**
525 *
526 */
527void TxaLine::
528output(std::ostream &out) const {
529 Patterns::const_iterator pi;
530 for (pi = _texture_patterns.begin(); pi != _texture_patterns.end(); ++pi) {
531 out << (*pi) << " ";
532 }
533 for (pi = _egg_patterns.begin(); pi != _egg_patterns.end(); ++pi) {
534 out << (*pi) << " ";
535 }
536 out << ":";
537
538 switch (_size_type) {
539 case ST_none:
540 break;
541
542 case ST_scale:
543 out << " " << _scale << "%";
544 break;
545
546 case ST_explicit_2:
547 out << " " << _x_size << " " << _y_size;
548 break;
549
550 case ST_explicit_3:
551 out << " " << _x_size << " " << _y_size << " " << _num_channels;
552 break;
553 }
554
555 if (_got_margin) {
556 out << " margin " << _margin;
557 }
558
559 if (_got_coverage_threshold) {
560 out << " coverage " << _coverage_threshold;
561 }
562
563 Keywords::const_iterator ki;
564 for (ki = _keywords.begin(); ki != _keywords.end(); ++ki) {
565 switch (*ki) {
566 case KW_omit:
567 out << " omit";
568 break;
569
570 case KW_nearest:
571 out << " nearest";
572 break;
573
574 case KW_linear:
575 out << " linear";
576 break;
577
578 case KW_mipmap:
579 out << " mipmap";
580 break;
581
582 case KW_cont:
583 out << " cont";
584 break;
585
586 case KW_anisotropic:
587 out << " aniso " << _aniso_degree;
588 break;
589 }
590 }
591
592 PaletteGroups::const_iterator gi;
593 for (gi = _palette_groups.begin(); gi != _palette_groups.end(); ++gi) {
594 out << " " << (*gi)->get_name();
595 }
596
597 if (_format != EggTexture::F_unspecified) {
598 out << " " << _format;
599 if (_force_format) {
600 out << " (forced)";
601 }
602 }
603
604 if (_color_type != nullptr) {
605 out << " " << _color_type->get_suggested_extension();
606 if (_alpha_type != nullptr) {
607 out << "," << _alpha_type->get_suggested_extension();
608 }
609 }
610}
This represents a single egg file known to the palettizer.
Definition eggFile.h:36
void match_txa_groups(const PaletteGroups &groups)
Adds the indicated set of groups, read from the .txa file, to the set of groups to which the egg file...
Definition eggFile.cxx:231
void clear_surprise()
Removes the 'surprise' flag; this file has been successfully matched against a line in the ....
Definition eggFile.cxx:285
static AlphaMode string_alpha_mode(const std::string &string)
Returns the AlphaMode value associated with the given string representation, or AM_unspecified if the...
static Format string_format(const std::string &string)
Returns the Format value associated with the given string representation, or F_unspecified if the str...
static QualityLevel string_quality_level(const std::string &string)
Returns the TexGen value associated with the given string representation, or ET_unspecified if the st...
static WrapMode string_wrap_mode(const std::string &string)
Returns the WrapMode value associated with the given string representation, or WM_unspecified if the ...
This class can be used to test for string matches against standard Unix- shell filename globbing conv...
Definition globPattern.h:32
set_case_sensitive
Sets whether the match is case sensitive (true) or case insensitive (false).
Definition globPattern.h:48
int get_x_size() const
Returns the size of the image file in pixels in the X direction.
Definition imageFile.cxx:82
int get_y_size() const
Returns the size of the image file in pixels in the Y direction.
Definition imageFile.cxx:92
get_suggested_extension
Returns a suitable filename extension (without a leading dot) to suggest for files of this type,...
Definition pnmFileType.h:49
This is the highest level of grouping for TextureImages.
iterator begin() const
Returns an iterator suitable for traversing the set.
void insert(PaletteGroup *group)
Inserts a new group to the set, if it is not already there.
void remove_null()
Removes the special "null" group from the set.
void make_union(const PaletteGroups &a, const PaletteGroups &b)
Computes the union of PaletteGroups a and b, and stores the result in this object.
iterator end() const
Returns an iterator suitable for traversing the set.
PaletteGroup * test_palette_group(const std::string &name) const
Returns the PaletteGroup with the given name.
This is a texture image reference as it appears in an egg file: the source image of the texture.
bool get_size()
Determines the size of the SourceTextureImage, if it is not already known.
This represents a single source texture that is referenced by one or more egg files.
SourceTextureImage * get_preferred_source()
Determines the preferred source image for examining size and reading pixels, etc.
These are the things that a user might explicitly request to adjust on a texture via a line in the ....
bool match_egg(EggFile *egg_file) const
Compares the patterns on the line to the indicated EggFile.
Definition txaLine.cxx:330
bool match_texture(TextureImage *texture) const
Compares the patterns on the line to the indicated TextureImage.
Definition txaLine.cxx:388
bool parse(const std::string &line)
Accepts a string that defines a line of the .txa file and parses it into its constinuent parts.
Definition txaLine.cxx:59
This is our own Panda specialization on the default STL vector.
Definition pvector.h:42
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
int extract_words(const string &str, vector_string &words)
Divides the string into a number of words according to whitespace.
double string_to_double(const string &str, string &tail)
A string-interface wrapper around the C library strtol().
int string_to_int(const string &str, string &tail)
A string-interface wrapper around the C library strtol().
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.