Panda3D
texturePlacement.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 texturePlacement.cxx
10  * @author drose
11  * @date 2000-11-30
12  */
13 
14 #include "texturePlacement.h"
15 #include "textureReference.h"
16 #include "textureImage.h"
17 #include "paletteGroup.h"
18 #include "paletteImage.h"
19 #include "palettizer.h"
20 #include "eggFile.h"
21 #include "destTextureImage.h"
22 
23 #include "indent.h"
24 #include "datagram.h"
25 #include "datagramIterator.h"
26 #include "bamReader.h"
27 #include "bamWriter.h"
28 #include "pnmImage.h"
29 
30 using std::max;
31 using std::min;
32 
33 TypeHandle TexturePlacement::_type_handle;
34 
35 /**
36  * The default constructor is only for the convenience of the Bam reader.
37  */
38 TexturePlacement::
39 TexturePlacement() {
40  _texture = nullptr;
41  _group = nullptr;
42  _image = nullptr;
43  _dest = nullptr;
44  _has_uvs = false;
45  _size_known = false;
46  _is_filled = true;
47  _omit_reason = OR_none;
48 }
49 
50 /**
51  *
52  */
53 TexturePlacement::
54 TexturePlacement(TextureImage *texture, PaletteGroup *group) :
55  _texture(texture),
56  _group(group)
57 {
58  _omit_reason = OR_working;
59 
60  if (!texture->is_size_known()) {
61  // If we were never able to figure out what size the texture actually is,
62  // then we can't place the texture on a palette.
63  _omit_reason = OR_unknown;
64  }
65 
66  _image = nullptr;
67  _dest = nullptr;
68  _has_uvs = false;
69  _size_known = false;
70  _is_filled = false;
71 }
72 
73 /**
74  *
75  */
76 TexturePlacement::
77 ~TexturePlacement() {
78  // Make sure we tell all our egg references they're not using us any more.
79  References::iterator ri;
80  References copy_references = _references;
81  for (ri = copy_references.begin(); ri != copy_references.end(); ++ri) {
82  TextureReference *reference = (*ri);
83  nassertv(reference->get_placement() == this);
84  reference->clear_placement();
85  }
86 
87  // And also our group, etc.
88  _group->unplace(this);
89 }
90 
91 /**
92  * Returns the name of the texture that this placement represents.
93  */
94 const std::string &TexturePlacement::
95 get_name() const {
96  return _texture->get_name();
97 }
98 
99 /**
100  * Returns the texture that this placement represents.
101  */
103 get_texture() const {
104  return _texture;
105 }
106 
107 /**
108  * Returns the grouping properties of the image.
109  */
111 get_properties() const {
112  return _texture->get_properties();
113 }
114 
115 /**
116  * Returns the group that this placement represents.
117  */
119 get_group() const {
120  return _group;
121 }
122 
123 /**
124  * Records the fact that a particular egg file is using this particular
125  * TexturePlacement.
126  */
129  reference->mark_egg_stale();
130 
131  // Turns out that turning these off is a bad idea, because it may make us
132  // forget the size information halfway through processing.
133  /*
134  _has_uvs = false;
135  _size_known = false;
136  */
137  _references.insert(reference);
138 }
139 
140 /**
141  * Notes that a particular egg file is no longer using this particular
142  * TexturePlacement.
143  */
146  reference->mark_egg_stale();
147  /*
148  _has_uvs = false;
149  _size_known = false;
150  */
151  _references.erase(reference);
152 }
153 
154 /**
155  * Marks all the egg files that reference this placement stale. Presumably
156  * this is called after moving the texture around in the palette or something.
157  */
160  References::iterator ri;
161  for (ri = _references.begin(); ri != _references.end(); ++ri) {
162  TextureReference *reference = (*ri);
163 
164  reference->mark_egg_stale();
165  }
166 }
167 
168 /**
169  * Sets the DestTextureImage that corresponds to this texture as it was copied
170  * to the install directory.
171  */
174  _dest = dest;
175 }
176 
177 /**
178  * Returns the DestTextureImage that corresponds to this texture as it was
179  * copied to the install directory.
180  */
182 get_dest() const {
183  return _dest;
184 }
185 
186 /**
187  * Attempts to determine the appropriate size of the texture for the given
188  * placement. This is based on the UV range of the egg files that reference
189  * the texture. Returns true on success, or false if the texture size cannot
190  * be determined (e.g. the texture file is unknown).
191  *
192  * After this returns true, get_x_size() and get_y_size() may safely be
193  * called.
194  */
197  if (!_texture->is_size_known()) {
198  // Too bad.
199  force_replace();
200  _omit_reason = OR_unknown;
201  return false;
202  }
203 
204  // This seems to be unnecessary (because of omit_solitary() and
205  // not_solitary()), and in fact bitches the logic in omit_solitary() and
206  // not_solitary() so that we call mark_egg_stale() unnecessarily.
207  /*
208  if (_omit_reason == OR_solitary) {
209  // If the texture was previously 'omitted' for being solitary, we give it
210  // a second chance now.
211  _omit_reason = OR_none;
212  }
213  */
214 
215  // Determine the actual minmax of the UV's in use, as well as whether we
216  // should wrap or clamp.
217  _has_uvs = false;
218  _position._wrap_u = EggTexture::WM_clamp;
219  _position._wrap_v = EggTexture::WM_clamp;
220 
221  LTexCoordd max_uv, min_uv;
222 
223  References::iterator ri;
224  for (ri = _references.begin(); ri != _references.end(); ++ri) {
225  TextureReference *reference = (*ri);
226  if (reference->has_uvs()) {
227  const LTexCoordd &n = reference->get_min_uv();
228  const LTexCoordd &x = reference->get_max_uv();
229 
230  if (_has_uvs) {
231  min_uv.set(min(min_uv[0], n[0]), min(min_uv[1], n[1]));
232  max_uv.set(max(max_uv[0], x[0]), max(max_uv[1], x[1]));
233  } else {
234  min_uv = n;
235  max_uv = x;
236  _has_uvs = true;
237  }
238  }
239 
240  // If any reference repeats the texture, the texture repeats in the
241  // palette.
242  if (reference->get_wrap_u() == EggTexture::WM_repeat) {
243  _position._wrap_u = EggTexture::WM_repeat;
244  }
245  if (reference->get_wrap_v() == EggTexture::WM_repeat) {
246  _position._wrap_v = EggTexture::WM_repeat;
247  }
248  }
249 
250  // However, if the user specified an explicit wrap mode, allow it to apply.
251  if (_texture->get_txa_wrap_u() != EggTexture::WM_unspecified) {
252  _position._wrap_u = _texture->get_txa_wrap_u();
253  }
254  if (_texture->get_txa_wrap_v() != EggTexture::WM_unspecified) {
255  _position._wrap_v = _texture->get_txa_wrap_v();
256  }
257 
258  if (!_has_uvs) {
259  force_replace();
260  _omit_reason = OR_unused;
261  return false;
262  }
263 
264  LTexCoordd rounded_min_uv = min_uv;
265  LTexCoordd rounded_max_uv = max_uv;
266 
267  // cout << get_name() << endl;
268 
269  // If so requested, round the minmax out to the next _round_unit. This cuts
270  // down on unnecessary resizing of textures within the palettes as the egg
271  // references change in trivial amounts. cout << "rounded_min_uv: " <<
272  // rounded_min_uv << endl; cout << "rounded_max_uv: " << rounded_max_uv <<
273  // endl;
274 
275  if (pal->_round_uvs) {
276  rounded_max_uv[0] =
277  ceil((rounded_max_uv[0] - pal->_round_fuzz) / pal->_round_unit) *
278  pal->_round_unit;
279  rounded_max_uv[1] =
280  ceil((rounded_max_uv[1] - pal->_round_fuzz) / pal->_round_unit) *
281  pal->_round_unit;
282 
283  rounded_min_uv[0] =
284  floor((rounded_min_uv[0] + pal->_round_fuzz) / pal->_round_unit) *
285  pal->_round_unit;
286  rounded_min_uv[1] =
287  floor((rounded_min_uv[1] + pal->_round_fuzz) / pal->_round_unit) *
288  pal->_round_unit;
289 
290  // cout << "after rounded_min_uv: " << rounded_min_uv << endl; cout <<
291  // "after rounded_max_uv: " << rounded_max_uv << endl;
292  }
293 
294  // Now determine the size in pixels we require based on the UV's that
295  // actually reference this texture.
296  compute_size_from_uvs(rounded_min_uv, rounded_max_uv);
297 
298  // Now, can it be placed?
299  if (_texture->get_omit()) {
300  // Not if the user says it can't.
301  force_replace();
302  _omit_reason = OR_omitted;
303 
304  } else if (get_uv_area() > _texture->get_coverage_threshold()) {
305  // If the texture repeats too many times, we can't place it.
306  force_replace();
307  _omit_reason = OR_coverage;
308 
309  } else if ((_position._x_size > pal->_pal_x_size ||
310  _position._y_size > pal->_pal_y_size) ||
311  (_position._x_size == pal->_pal_x_size &&
312  _position._y_size == pal->_pal_y_size)) {
313  // If the texture exceeds the size of an empty palette image in either
314  // dimension, or if it exactly equals the size of an empty palette image
315  // in both dimensions, we can't place it because it's too big.
316  force_replace();
317  _omit_reason = OR_size;
318 
319  } else if (pal->_omit_everything && (_group->is_none_texture_swap())) {
320  // If we're omitting everything, omit everything.
321  force_replace();
322  _omit_reason = OR_default_omit;
323 
324  } else if (_omit_reason == OR_omitted ||
325  _omit_reason == OR_default_omit ||
326  _omit_reason == OR_size ||
327  _omit_reason == OR_coverage ||
328  _omit_reason == OR_unknown) {
329  // On the other hand, if the texture was previously omitted explicitly, or
330  // because of its size or coverage, now it seems to fit.
331  force_replace();
332  mark_eggs_stale();
333  _omit_reason = OR_working;
334 
335  } else if (is_placed()) {
336  // It *can* be placed. If it was already placed previously, can we leave
337  // it where it is?
338 
339  if (_position._x_size != _placed._x_size ||
340  _position._y_size != _placed._y_size ||
341  _position._min_uv[0] < _placed._min_uv[0] ||
342  _position._min_uv[1] < _placed._min_uv[1] ||
343  _position._max_uv[0] > _placed._max_uv[0] ||
344  _position._max_uv[1] > _placed._max_uv[1]) {
345  // If the texture was previously placed but is now the wrong size, or if
346  // the area we need to cover is different, we need to re-place it.
347 
348  // However, we make a special exception: if it would have fit without
349  // rounding up the UV's, then screw rounding it up and just leave it
350  // alone.
351  if ((_position._x_size > _placed._x_size ||
352  _position._y_size > _placed._y_size) &&
353  pal->_round_uvs) {
354  compute_size_from_uvs(min_uv, max_uv);
355  if (_position._x_size <= _placed._x_size &&
356  _position._y_size <= _placed._y_size &&
357  _position._min_uv[0] >= _placed._min_uv[0] &&
358  _position._min_uv[1] >= _placed._min_uv[1] &&
359  _position._max_uv[0] <= _placed._max_uv[0] &&
360  _position._max_uv[1] <= _placed._max_uv[1]) {
361  // No problem! It fits here, so leave well enough alone.
362  } else {
363  // That's not good enough either, so go back to rounding.
364  compute_size_from_uvs(rounded_min_uv, rounded_max_uv);
365  force_replace();
366  }
367  } else {
368  force_replace();
369  }
370  }
371 
372  if (_position._wrap_u != _placed._wrap_u ||
373  _position._wrap_v != _placed._wrap_v) {
374  // The wrap mode properties have changed slightly. We may or may not
375  // need to re-place it, but we will need to update it.
376  _is_filled = false;
377  _placed._wrap_u = _position._wrap_u;
378  _placed._wrap_v = _position._wrap_v;
379  }
380  }
381 
382  return true;
383 }
384 
385 /**
386  * Returns true if the texture's size is known, false otherwise. Usually this
387  * can only be false after determine_size() has been called there is something
388  * wrong with the texture (in which case the placement will automatically omit
389  * itself from the palette anyway).
390  */
392 is_size_known() const {
393  return _size_known;
394 }
395 
396 /**
397  * Returns the reason the texture has been omitted from a palette image, or
398  * OR_none if it has not.
399  */
402  return _omit_reason;
403 }
404 
405 /**
406  * Returns the size in the X dimension, in pixels, of the texture image as it
407  * must appear in the palette. This accounts for any growing or shrinking of
408  * the texture due to the UV coordinate range.
409  */
411 get_x_size() const {
412  nassertr(_size_known, 0);
413  return _position._x_size;
414 }
415 
416 /**
417  * Returns the size in the Y dimension, in pixels, of the texture image as it
418  * must appear in the palette. This accounts for any growing or shrinking of
419  * the texture due to the UV coordinate range.
420  */
422 get_y_size() const {
423  nassertr(_size_known, 0);
424  return _position._y_size;
425 }
426 
427 /**
428  * Returns the total area of the rectangle occupied by the UV minmax box, in
429  * UV coordinates. 1.0 is the entire texture; values greater than 1 imply the
430  * texture repeats.
431  */
432 double TexturePlacement::
433 get_uv_area() const {
434  if (!_has_uvs) {
435  return 0.0;
436  }
437 
438  LTexCoordd range = _position._max_uv - _position._min_uv;
439  return range[0] * range[1];
440 }
441 
442 /**
443  * Returns true if the texture has been placed on a palette image, false
444  * otherwise. This will generally be true if get_omit_reason() returns
445  * OR_none or OR_solitary and false otherwise.
446  */
448 is_placed() const {
449  return _image != nullptr;
450 }
451 
452 /**
453  * Returns the particular PaletteImage on which the texture has been placed.
454  */
456 get_image() const {
457  nassertr(is_placed(), nullptr);
458  return _image;
459 }
460 
461 /**
462  * Returns the particular PalettePage on which the texture has been placed.
463  */
465 get_page() const {
466  nassertr(is_placed(), nullptr);
467  return _image->get_page();
468 }
469 
470 /**
471  * Returns the X pixel at which the texture has been placed within its
472  * PaletteImage. It is an error to call this unless is_placed() returns true.
473  */
475 get_placed_x() const {
476  nassertr(is_placed(), 0);
477  return _placed._x;
478 }
479 
480 /**
481  * Returns the Y pixel at which the texture has been placed within its
482  * PaletteImage. It is an error to call this unless is_placed() returns true.
483  */
485 get_placed_y() const {
486  nassertr(is_placed(), 0);
487  return _placed._y;
488 }
489 
490 /**
491  * Returns the size in the X dimension, in pixels, of the texture image as it
492  * has been placed within the palette.
493  */
496  nassertr(is_placed(), 0);
497  return _placed._x_size;
498 }
499 
500 /**
501  * Returns the size in the Y dimension, in pixels, of the texture image as it
502  * has been placed within the palette.
503  */
506  nassertr(is_placed(), 0);
507  return _placed._y_size;
508 }
509 
510 /**
511  * Returns the total area of the rectangle occupied by the UV minmax box, as
512  * it has been placed. See also get_uv_area().
513  */
514 double TexturePlacement::
516  nassertr(is_placed(), 0);
517  LTexCoordd range = _placed._max_uv - _placed._min_uv;
518  return range[0] * range[1];
519 }
520 
521 /**
522  * Assigns the texture to a particular position within the indicated
523  * PaletteImage. It is an error to call this if the texture has already been
524  * placed elsewhere.
525  */
527 place_at(PaletteImage *image, int x, int y) {
528  nassertv(!is_placed());
529  nassertv(_size_known);
530 
531  _image = image;
532  _is_filled = false;
533  _position._x = x;
534  _position._y = y;
535  _placed = _position;
536  _omit_reason = OR_none;
537 }
538 
539 /**
540  * Removes the texture from its particular PaletteImage, but does not remove
541  * it from the PaletteGroup. It will be re-placed when the
542  * PaletteGroup::place_all() is called.
543  */
546  if (_image != nullptr) {
547  _image->unplace(this);
548  _image = nullptr;
549  }
550  if (_omit_reason == OR_none) {
551  mark_eggs_stale();
552  }
553  _omit_reason = OR_working;
554 }
555 
556 /**
557  * Sets the omit reason (returned by get_omit()) to OR_solitary, indicating
558  * that the palettized version of the texture should not be used because it is
559  * the only texture on a PaletteImage. However, the texture is still
560  * considered placed, and is_placed() will return true.
561  */
564  nassertv(is_placed());
565  if (_omit_reason != OR_solitary) {
566  mark_eggs_stale();
567  _omit_reason = OR_solitary;
568  }
569 }
570 
571 /**
572  * Indicates that the texture, formerly indicated as solitary, is now no
573  * longer.
574  */
577  nassertv(is_placed());
578  if (_omit_reason != OR_none) {
579  mark_eggs_stale();
580  _omit_reason = OR_none;
581  }
582 }
583 
584 /**
585  * Returns true if the particular position this texture has been assigned to
586  * overlaps the rectangle whose top left corner is at x, y and whose size is
587  * given by x_size, y_size, or false otherwise.
588  */
590 intersects(int x, int y, int x_size, int y_size) {
591  nassertr(is_placed(), false);
592 
593  int hright = x + x_size;
594  int hbot = y + y_size;
595 
596  int mright = _placed._x + _placed._x_size;
597  int mbot = _placed._y + _placed._y_size;
598 
599  return !(x >= mright || hright <= _placed._x ||
600  y >= mbot || hbot <= _placed._y);
601 }
602 
603 /**
604  * Stores in the indicated matrix the appropriate texture matrix transform for
605  * the new placement of the texture.
606  */
608 compute_tex_matrix(LMatrix3d &transform) {
609  nassertv(is_placed());
610 
611  LMatrix3d source_uvs = LMatrix3d::ident_mat();
612 
613  LTexCoordd range = _placed._max_uv - _placed._min_uv;
614  if (range[0] != 0.0 && range[1] != 0.0) {
615  source_uvs =
616  LMatrix3d::translate_mat(-_placed._min_uv) *
617  LMatrix3d::scale_mat(1.0 / range[0], 1.0 / range[1]);
618  }
619 
620  int top = _placed._y + _placed._margin;
621  int left = _placed._x + _placed._margin;
622  int x_size = _placed._x_size - _placed._margin * 2;
623  int y_size = _placed._y_size - _placed._margin * 2;
624 
625  int bottom = top + y_size;
626  int pal_x_size = _image->get_x_size();
627  int pal_y_size = _image->get_y_size();
628 
629  LVecBase2d t((double)left / (double)pal_x_size,
630  (double)(pal_y_size - bottom) / (double)pal_y_size);
631  LVecBase2d s((double)x_size / (double)pal_x_size,
632  (double)y_size / (double)pal_y_size);
633 
634  LMatrix3d dest_uvs
635  (s[0], 0.0, 0.0,
636  0.0, s[1], 0.0,
637  t[0], t[1], 1.0);
638 
639  transform = source_uvs * dest_uvs;
640 }
641 
642 /**
643  * Writes the placement position information on a line by itself.
644  */
646 write_placed(std::ostream &out, int indent_level) {
647  indent(out, indent_level)
648  << get_texture()->get_name();
649 
650  if (is_placed()) {
651  out << " at "
652  << get_placed_x() << " " << get_placed_y() << " to "
653  << get_placed_x() + get_placed_x_size() << " "
654  << get_placed_y() + get_placed_y_size() << " (coverage "
655  << get_placed_uv_area() << ")";
656 
657  if (_placed._wrap_u != EggTexture::WM_unspecified ||
658  _placed._wrap_v != EggTexture::WM_unspecified) {
659  if (_placed._wrap_u != _placed._wrap_v) {
660  out << " (" << _placed._wrap_u << ", " << _placed._wrap_v << ")";
661  } else {
662  out << " " << _placed._wrap_u;
663  }
664  }
665  out << "\n";
666  } else {
667  out << " not yet placed.\n";
668  }
669 };
670 
671 /**
672  * Returns true if the texture has been filled (i.e. fill_image() has been
673  * called) since it was placed.
674  */
676 is_filled() const {
677  return _is_filled;
678 }
679 
680 /**
681  * Marks the texture as unfilled, so that it will need to be copied into the
682  * palette image again.
683  */
686  _is_filled = false;
687 }
688 
689 /**
690  * Fills in the rectangle of the palette image represented by the texture
691  * placement with the image pixels.
692  */
695  nassertv(is_placed());
696 
697  _is_filled = true;
698 
699  // We determine the pixels to place the source image at by transforming the
700  // unit texture box: the upper-left and lower-right corners. These corners,
701  // in the final texture coordinate space, represent where on the palette
702  // image the original texture should be located.
703 
704  LMatrix3d transform;
705  compute_tex_matrix(transform);
706  LTexCoordd ul = LTexCoordd(0.0, 1.0) * transform;
707  LTexCoordd lr = LTexCoordd(1.0, 0.0) * transform;
708 
709  // Now we convert those texture coordinates back to pixel units.
710  int pal_x_size = _image->get_x_size();
711  int pal_y_size = _image->get_y_size();
712 
713  int top = (int)floor((1.0 - ul[1]) * pal_y_size + 0.5);
714  int left = (int)floor(ul[0] * pal_x_size + 0.5);
715  int bottom = (int)floor((1.0 - lr[1]) * pal_y_size + 0.5);
716  int right = (int)floor(lr[0] * pal_x_size + 0.5);
717 
718  // And now we can determine the size to scale the image to based on that.
719  // This may not be the same as texture->size() because of margins.
720  int x_size = right - left;
721  int y_size = bottom - top;
722  nassertv(x_size >= 0 && y_size >= 0);
723 
724  // Now we get a PNMImage that represents the source texture at that size.
725  const PNMImage &source_full = _texture->read_source_image();
726  if (!source_full.is_valid()) {
727  flag_error_image(image);
728  return;
729  }
730 
731  PNMImage source(x_size, y_size, source_full.get_num_channels(),
732  source_full.get_maxval());
733  source.quick_filter_from(source_full);
734 
735  bool alpha = image.has_alpha();
736  bool source_alpha = source.has_alpha();
737 
738  // Now copy the pixels. We do this by walking through the rectangular
739  // region on the palette image that we have reserved for this texture; for
740  // each pixel in this region, we determine its appropriate color based on
741  // its relation to the actual texture image location (determined above), and
742  // on whether the texture wraps or clamps.
743  for (int y = _placed._y; y < _placed._y + _placed._y_size; y++) {
744  int sy = y - top;
745 
746  switch (_placed._wrap_v) {
747  case EggTexture::WM_clamp:
748  // Clamp at [0, y_size).
749  sy = max(min(sy, y_size - 1), 0);
750  break;
751 
752  case EggTexture::WM_mirror:
753  sy = (sy < 0) ? (y_size * 2) - 1 - ((-sy - 1) % (y_size * 2)) : sy % (y_size * 2);
754  sy = (sy < y_size) ? sy : 2 * y_size - sy - 1;
755  break;
756 
757  case EggTexture::WM_mirror_once:
758  sy = (sy < y_size) ? sy : 2 * y_size - sy - 1;
759  // Fall through
760 
761  case EggTexture::WM_border_color:
762  if (sy < 0 || sy >= y_size) {
763  continue;
764  }
765  break;
766 
767  default:
768  // Wrap: sign-independent modulo.
769  sy = (sy < 0) ? y_size - 1 - ((-sy - 1) % y_size) : sy % y_size;
770  break;
771  }
772 
773  for (int x = _placed._x; x < _placed._x + _placed._x_size; x++) {
774  int sx = x - left;
775 
776  switch (_placed._wrap_u) {
777  case EggTexture::WM_clamp:
778  // Clamp at [0, x_size).
779  sx = max(min(sx, x_size - 1), 0);
780  break;
781 
782  case EggTexture::WM_mirror:
783  sx = (sx < 0) ? (x_size * 2) - 1 - ((-sx - 1) % (x_size * 2)) : sx % (x_size * 2);
784  sx = (sx < x_size) ? sx : 2 * x_size - sx - 1;
785  break;
786 
787  case EggTexture::WM_mirror_once:
788  sx = (sx >= 0) ? sx : ~sx;
789  // Fall through
790 
791  case EggTexture::WM_border_color:
792  if (sx < 0 || sx >= x_size) {
793  continue;
794  }
795  break;
796 
797  default:
798  // Wrap: sign-independent modulo.
799  sx = (sx < 0) ? x_size - 1 - ((-sx - 1) % x_size) : sx % x_size;
800  break;
801  }
802 
803  image.set_xel(x, y, source.get_xel(sx, sy));
804  if (alpha) {
805  if (source_alpha) {
806  image.set_alpha(x, y, source.get_alpha(sx, sy));
807  } else {
808  image.set_alpha(x, y, 1.0);
809  }
810  }
811  }
812  }
813 
814  _texture->release_source_image();
815 }
816 
817 
818 /**
819  * Fills in the rectangle of the swapped palette image represented by the
820  * texture placement with the image pixels.
821  */
823 fill_swapped_image(PNMImage &image, int index) {
824  nassertv(is_placed());
825 
826  _is_filled = true;
827 
828  // We determine the pixels to place the source image at by transforming the
829  // unit texture box: the upper-left and lower-right corners. These corners,
830  // in the final texture coordinate space, represent where on the palette
831  // image the original texture should be located.
832 
833  LMatrix3d transform;
834  compute_tex_matrix(transform);
835  LTexCoordd ul = LTexCoordd(0.0, 1.0) * transform;
836  LTexCoordd lr = LTexCoordd(1.0, 0.0) * transform;
837 
838  // Now we convert those texture coordinates back to pixel units.
839  int pal_x_size = _image->get_x_size();
840  int pal_y_size = _image->get_y_size();
841 
842  int top = (int)floor((1.0 - ul[1]) * pal_y_size + 0.5);
843  int left = (int)floor(ul[0] * pal_x_size + 0.5);
844  int bottom = (int)floor((1.0 - lr[1]) * pal_y_size + 0.5);
845  int right = (int)floor(lr[0] * pal_x_size + 0.5);
846 
847  // And now we can determine the size to scale the image to based on that.
848  // This may not be the same as texture->size() because of margins.
849  int x_size = right - left;
850  int y_size = bottom - top;
851  nassertv(x_size >= 0 && y_size >= 0);
852 
853  // Now we get a PNMImage that represents the swapped texture at that size.
854  TextureSwaps::iterator tsi;
855  tsi = _textureSwaps.begin() + index;
856  TextureImage *swapTexture = (*tsi);
857  const PNMImage &source_full = swapTexture->read_source_image();
858  if (!source_full.is_valid()) {
859  flag_error_image(image);
860  return;
861  }
862 
863  PNMImage source(x_size, y_size, source_full.get_num_channels(),
864  source_full.get_maxval());
865  source.quick_filter_from(source_full);
866 
867  bool alpha = image.has_alpha();
868  bool source_alpha = source.has_alpha();
869 
870  // Now copy the pixels. We do this by walking through the rectangular
871  // region on the palette image that we have reserved for this texture; for
872  // each pixel in this region, we determine its appropriate color based on
873  // its relation to the actual texture image location (determined above), and
874  // on whether the texture wraps or clamps.
875  for (int y = _placed._y; y < _placed._y + _placed._y_size; y++) {
876  int sy = y - top;
877 
878  if (_placed._wrap_v == EggTexture::WM_clamp) {
879  // Clamp at [0, y_size).
880  sy = max(min(sy, y_size - 1), 0);
881 
882  } else {
883  // Wrap: sign-independent modulo.
884  sy = (sy < 0) ? y_size - 1 - ((-sy - 1) % y_size) : sy % y_size;
885  }
886 
887  for (int x = _placed._x; x < _placed._x + _placed._x_size; x++) {
888  int sx = x - left;
889 
890  if (_placed._wrap_u == EggTexture::WM_clamp) {
891  // Clamp at [0, x_size).
892  sx = max(min(sx, x_size - 1), 0);
893 
894  } else {
895  // Wrap: sign-independent modulo.
896  sx = (sx < 0) ? x_size - 1 - ((-sx - 1) % x_size) : sx % x_size;
897  }
898 
899  image.set_xel(x, y, source.get_xel(sx, sy));
900  if (alpha) {
901  if (source_alpha) {
902  image.set_alpha(x, y, source.get_alpha(sx, sy));
903  } else {
904  image.set_alpha(x, y, 1.0);
905  }
906  }
907  }
908  }
909 
910  swapTexture->release_source_image();
911 }
912 
913 /**
914  * Sets the rectangle of the palette image represented by the texture
915  * placement to red, to represent a missing texture.
916  */
919  nassertv(is_placed());
920  for (int y = _placed._y; y < _placed._y + _placed._y_size; y++) {
921  for (int x = _placed._x; x < _placed._x + _placed._x_size; x++) {
922  image.set_xel_val(x, y, 1, 0, 0);
923  }
924  }
925  if (image.has_alpha()) {
926  for (int y = _placed._y; y < _placed._y + _placed._y_size; y++) {
927  for (int x = _placed._x; x < _placed._x + _placed._x_size; x++) {
928  image.set_alpha_val(x, y, 1);
929  }
930  }
931  }
932 }
933 
934 /**
935  * A support function for determine_size(), this computes the appropriate size
936  * of the texture in pixels based on the UV coverage (as well as on the size
937  * of the source texture).
938  */
939 void TexturePlacement::
940 compute_size_from_uvs(const LTexCoordd &min_uv, const LTexCoordd &max_uv) {
941  _position._min_uv = min_uv;
942  _position._max_uv = max_uv;
943 
944  LTexCoordd range = _position._max_uv - _position._min_uv;
945  // cout << "range: " << range << endl;
946 
947  // cout << "_x_size texture: " << _texture->get_x_size() << endl; cout <<
948  // "_y_size texture: " << _texture->get_y_size() << endl;
949 
950  _position._x_size = (int)floor(_texture->get_x_size() * range[0] + 0.5);
951  _position._y_size = (int)floor(_texture->get_y_size() * range[1] + 0.5);
952 
953  // cout << "_x_size: " << _position._x_size << endl; cout << "_y_size: " <<
954  // _position._y_size << endl;
955 
956  // We arbitrarily require at least four pixels in each dimension. Fewer
957  // than this may be asking for trouble.
958  _position._x_size = max(_position._x_size, 4);
959  _position._y_size = max(_position._y_size, 4);
960 
961  if(get_group()->has_margin_override()) {
962  _position._margin = get_group()->get_margin_override();
963  } else {
964  _position._margin = _texture->get_margin();
965  }
966  // cout << "margin: " << _position._margin << endl;
967 
968  // Normally, we have interior margins, but if the image size is too small--
969  // i.e. the margin size is too great a percentage of the image size--we'll
970  // make them exterior margins so as not to overly degrade the quality of the
971  // image.
972  if ((double)_position._margin / (double)_position._x_size > 0.10) {
973  _position._x_size += _position._margin * 2;
974  }
975  if ((double)_position._margin / (double)_position._y_size > 0.10) {
976  _position._y_size += _position._margin * 2;
977  }
978 
979  _size_known = true;
980 }
981 
982 
983 
984 /**
985  * Registers the current object as something that can be read from a Bam file.
986  */
990  register_factory(get_class_type(), make_TexturePlacement);
991 }
992 
993 /**
994  * Fills the indicated datagram up with a binary representation of the current
995  * object, in preparation for writing to a Bam file.
996  */
998 write_datagram(BamWriter *writer, Datagram &datagram) {
999  TypedWritable::write_datagram(writer, datagram);
1000  writer->write_pointer(datagram, _texture);
1001  writer->write_pointer(datagram, _group);
1002  writer->write_pointer(datagram, _image);
1003  writer->write_pointer(datagram, _dest);
1004 
1005  datagram.add_bool(_has_uvs);
1006  datagram.add_bool(_size_known);
1007  _position.write_datagram(writer, datagram);
1008 
1009  datagram.add_bool(_is_filled);
1010  _placed.write_datagram(writer, datagram);
1011  datagram.add_int32((int)_omit_reason);
1012 
1013  datagram.add_int32(_references.size());
1014  References::const_iterator ri;
1015  for (ri = _references.begin(); ri != _references.end(); ++ri) {
1016  writer->write_pointer(datagram, (*ri));
1017  }
1018 
1019  datagram.add_int32(_textureSwaps.size());
1020  TextureSwaps::const_iterator tsi;
1021  for (tsi = _textureSwaps.begin(); tsi != _textureSwaps.end(); ++tsi) {
1022  writer->write_pointer(datagram, (*tsi));
1023  }
1024 
1025 }
1026 
1027 /**
1028  * Called after the object is otherwise completely read from a Bam file, this
1029  * function's job is to store the pointers that were retrieved from the Bam
1030  * file for each pointer object written. The return value is the number of
1031  * pointers processed from the list.
1032  */
1035  int index = TypedWritable::complete_pointers(p_list, manager);
1036 
1037  if (p_list[index] != nullptr) {
1038  DCAST_INTO_R(_texture, p_list[index], index);
1039  }
1040  index++;
1041 
1042  if (p_list[index] != nullptr) {
1043  DCAST_INTO_R(_group, p_list[index], index);
1044  }
1045  index++;
1046 
1047  if (p_list[index] != nullptr) {
1048  DCAST_INTO_R(_image, p_list[index], index);
1049  }
1050  index++;
1051 
1052  if (p_list[index] != nullptr) {
1053  DCAST_INTO_R(_dest, p_list[index], index);
1054  }
1055  index++;
1056 
1057  int i;
1058  for (i = 0; i < _num_references; i++) {
1059  TextureReference *reference;
1060  DCAST_INTO_R(reference, p_list[index], index);
1061  _references.insert(reference);
1062  index++;
1063  }
1064 
1065  for (i = 0; i < _num_textureSwaps; i++) {
1066  TextureImage *swapTexture;
1067  DCAST_INTO_R(swapTexture, p_list[index], index);
1068  _textureSwaps.push_back(swapTexture);
1069  index++;
1070  }
1071 
1072  return index;
1073 }
1074 
1075 /**
1076  * This method is called by the BamReader when an object of this type is
1077  * encountered in a Bam file; it should allocate and return a new object with
1078  * all the data read.
1079  */
1080 TypedWritable* TexturePlacement::
1081 make_TexturePlacement(const FactoryParams &params) {
1083  DatagramIterator scan;
1084  BamReader *manager;
1085 
1086  parse_params(params, scan, manager);
1087  me->fillin(scan, manager);
1088  return me;
1089 }
1090 
1091 /**
1092  * Reads the binary data from the given datagram iterator, which was written
1093  * by a previous call to write_datagram().
1094  */
1095 void TexturePlacement::
1096 fillin(DatagramIterator &scan, BamReader *manager) {
1097  TypedWritable::fillin(scan, manager);
1098 
1099  manager->read_pointer(scan); // _texture
1100  manager->read_pointer(scan); // _group
1101  manager->read_pointer(scan); // _image
1102  manager->read_pointer(scan); // _dest
1103 
1104  _has_uvs = scan.get_bool();
1105  _size_known = scan.get_bool();
1106  _position.fillin(scan, manager);
1107 
1108  _is_filled = scan.get_bool();
1109  _placed.fillin(scan, manager);
1110  _omit_reason = (OmitReason)scan.get_int32();
1111 
1112  _num_references = scan.get_int32();
1113  manager->read_pointers(scan, _num_references);
1114 
1115  if (Palettizer::_read_pi_version >= 20) {
1116  _num_textureSwaps = scan.get_int32();
1117  } else {
1118  _num_textureSwaps = 0;
1119  }
1120  manager->read_pointers(scan, _num_textureSwaps);
1121 }
1122 
1123 
1124 /**
1125  * Compares two TexturePlacement objects and returns true if the first one is
1126  * bigger than the second one, false otherwise.
1127  */
1130  if (a->get_y_size() < b->get_y_size()) {
1131  return false;
1132 
1133  } else if (b->get_y_size() < a->get_y_size()) {
1134  return true;
1135 
1136  } else if (a->get_x_size() < b->get_x_size()) {
1137  return false;
1138 
1139  } else if (b->get_x_size() < a->get_x_size()) {
1140  return true;
1141  } else if (a->get_name() < b->get_name()) {
1142  // use this fall through case to let alphabetically smaller textures show
1143  // up first
1144  return true;
1145  }
1146 
1147  return false;
1148 }
bool is_filled() const
Returns true if the texture has been filled (i.e.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
int get_y_size() const
Returns the size in the Y dimension, in pixels, of the texture image as it must appear in the palette...
This represents a texture filename as it has been resized and copied to the map directory (e....
void fillin(DatagramIterator &scan, BamReader *manager)
Reads the binary data from the given datagram iterator, which was written by a previous call to write...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void not_solitary()
Indicates that the texture, formerly indicated as solitary, is now no longer.
void flag_error_image(PNMImage &image)
Sets the rectangle of the palette image represented by the texture placement to red,...
bool determine_size()
Attempts to determine the appropriate size of the texture for the given placement.
bool get_bool()
Extracts a boolean value.
The name of this class derives from the fact that we originally implemented it as a layer on top of t...
Definition: pnmImage.h:58
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
get_num_channels
Returns the number of channels in the image.
EggTexture::WrapMode get_wrap_u() const
Returns the specification for the wrapping in the U direction.
void clear_placement()
Removes any reference to a TexturePlacement.
This is the fundamental interface for extracting binary objects from a Bam file, as generated by a Ba...
Definition: bamReader.h:110
int get_placed_x_size() const
Returns the size in the X dimension, in pixels, of the texture image as it has been placed within the...
This is the particular reference of a texture filename by an egg file.
virtual void write_datagram(BamWriter *writer, Datagram &datagram)
Fills the indicated datagram up with a binary representation of the current object,...
PaletteGroup * get_group() const
Returns the group that this placement represents.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
int get_y_size() const
Returns the size of the image file in pixels in the Y direction.
Definition: imageFile.cxx:92
virtual void write_datagram(BamWriter *writer, Datagram &datagram)
Fills the indicated datagram up with a binary representation of the current object,...
void set_xel_val(int x, int y, const xel &value)
Changes the RGB color at the indicated pixel.
Definition: pnmImage.I:330
PaletteImage * get_image() const
Returns the particular PaletteImage on which the texture has been placed.
Base class for objects that can be written to and read from Bam files.
Definition: typedWritable.h:35
This is the highest level of grouping for TextureImages.
Definition: paletteGroup.h:43
void unplace(TexturePlacement *placement)
Removes the texture from its position on a PaletteImage, if it has been so placed.
int get_placed_x() const
Returns the X pixel at which the texture has been placed within its PaletteImage.
const LTexCoordd & get_min_uv() const
Returns the minimum UV coordinate in use for the texture by this reference.
double get_placed_uv_area() const
Returns the total area of the rectangle occupied by the UV minmax box, as it has been placed.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
bool operator()(TexturePlacement *a, TexturePlacement *b) const
Compares two TexturePlacement objects and returns true if the first one is bigger than the second one...
const LTexCoordd & get_max_uv() const
Returns the maximum UV coordinate in use for the texture by this reference.
double get_uv_area() const
Returns the total area of the rectangle occupied by the UV minmax box, in UV coordinates.
This is the fundamental interface for writing binary objects to a Bam file, to be extracted later by ...
Definition: bamWriter.h:63
EggTexture::WrapMode get_txa_wrap_v() const
Returns the wrap mode specified in the v direction in the txa file, or WM_unspecified.
This is a particular collection of textures, within a PaletteGroup, that all share the same TexturePr...
Definition: palettePage.h:33
int32_t get_int32()
Extracts a signed 32-bit integer.
EggTexture::WrapMode get_wrap_v() const
Returns the specification for the wrapping in the V direction.
void place_at(PaletteImage *image, int x, int y)
Assigns the texture to a particular position within the indicated PaletteImage.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void omit_solitary()
Sets the omit reason (returned by get_omit()) to OR_solitary, indicating that the palettized version ...
virtual void fillin(DatagramIterator &scan, BamReader *manager)
This internal function is intended to be called by each class's make_from_bam() method to read in all...
static void register_with_read_factory()
Registers the current object as something that can be read from a Bam file.
const std::string & get_name() const
Returns the name of the texture that this placement represents.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
virtual void write_datagram(BamWriter *manager, Datagram &dg)
Writes the contents of this object to the datagram for shipping out to a Bam file.
bool is_none_texture_swap() const
Returns textureswap information is set or not, True if it's not set.
void quick_filter_from(const PNMImage &copy, int xborder=0, int yborder=0)
Resizes from the given image, with a fixed radius of 0.5.
int get_placed_y() const
Returns the Y pixel at which the texture has been placed within its PaletteImage.
bool is_valid() const
Returns true if the image has been read in or correctly initialized with a height and width.
Definition: pnmImage.I:253
PalettePage * get_page() const
Returns the particular PalettePage this image is associated with.
const TextureProperties & get_properties() const
Returns the grouping properties of the image.
Definition: imageFile.cxx:119
void parse_params(const FactoryParams &params, DatagramIterator &scan, BamReader *&manager)
Takes in a FactoryParams, passed from a WritableFactory into any TypedWritable's make function,...
Definition: bamReader.I:275
void force_replace()
Removes the texture from its particular PaletteImage, but does not remove it from the PaletteGroup.
void mark_unfilled()
Marks the texture as unfilled, so that it will need to be copied into the palette image again.
void add_bool(bool value)
Adds a boolean value to the datagram.
Definition: datagram.I:34
void compute_tex_matrix(LMatrix3d &transform)
Stores in the indicated matrix the appropriate texture matrix transform for the new placement of the ...
bool is_size_known() const
Returns true if the texture's size is known, false otherwise.
get_maxval
Returns the maximum channel value allowable for any pixel in this image; for instance,...
bool is_size_known() const
Returns true if the size of the image file is known, false otherwise.
Definition: imageFile.cxx:73
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
int get_x_size() const
Returns the size of the image file in pixels in the X direction.
Definition: imageFile.cxx:82
virtual int complete_pointers(TypedWritable **p_list, BamReader *manager)
Receives an array of pointers, one for each time manager->read_pointer() was called in fillin().
static bool has_alpha(ColorType color_type)
This static variant of has_alpha() returns true if the indicated image type includes an alpha channel...
std::ostream & indent(std::ostream &out, int indent_level)
A handy function for doing text formatting.
Definition: indent.cxx:20
bool get_omit() const
Returns true if the user specifically requested to omit this texture via the "omit" keyword in the ....
void read_pointers(DatagramIterator &scan, int count)
A convenience function to read a contiguous list of pointers.
Definition: bamReader.cxx:653
const TextureProperties & get_properties() const
Returns the grouping properties of the image.
An instance of this class is passed to the Factory when requesting it to do its business and construc...
Definition: factoryParams.h:36
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PalettePage * get_page() const
Returns the particular PalettePage on which the texture has been placed.
This corresponds to a particular assignment of a TextureImage with a PaletteGroup,...
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
double get_coverage_threshold() const
Returns the appropriate coverage threshold for this texture.
void add_egg(TextureReference *reference)
Records the fact that a particular egg file is using this particular TexturePlacement.
OmitReason get_omit_reason() const
Returns the reason the texture has been omitted from a palette image, or OR_none if it has not.
int get_margin() const
Returns the appropriate margin for this texture.
void mark_egg_stale()
Marks the egg file that shares this reference as stale.
TextureImage * get_texture() const
Returns the texture that this placement represents.
void add_int32(int32_t value)
Adds a signed 32-bit integer to the datagram.
Definition: datagram.I:67
void set_alpha(int x, int y, float a)
Sets the alpha component color only at the indicated pixel.
Definition: pnmImage.I:845
void remove_egg(TextureReference *reference)
Notes that a particular egg file is no longer using this particular TexturePlacement.
static WritableFactory * get_factory()
Returns the global WritableFactory for generating TypedWritable objects.
Definition: bamReader.I:177
const PNMImage & read_source_image()
Reads in the original image, if it has not already been read, and returns it.
int get_x_size() const
Returns the size in the X dimension, in pixels, of the texture image as it must appear in the palette...
void write_placed(std::ostream &out, int indent_level=0)
Writes the placement position information on a line by itself.
bool read_pointer(DatagramIterator &scan)
The interface for reading a pointer to another object from a Bam file.
Definition: bamReader.cxx:610
DestTextureImage * get_dest() const
Returns the DestTextureImage that corresponds to this texture as it was copied to the install directo...
void fill_swapped_image(PNMImage &image, int index)
Fills in the rectangle of the swapped palette image represented by the texture placement with the ima...
This is a single palette image, one of several within a PalettePage, which is in turn one of several ...
Definition: paletteImage.h:32
void unplace(TexturePlacement *placement)
Removes the texture from the image.
OmitReason
This enumerates the reasons why a texture may not have been placed in a palette image.
Definition: omitReason.h:23
bool intersects(int x, int y, int x_size, int y_size)
Returns true if the particular position this texture has been assigned to overlaps the rectangle whos...
bool has_uvs() const
Returns true if this TextureReference actually uses the texture on geometry, with UV's and everything...
void set_alpha_val(int x, int y, xelval a)
Sets the alpha component color only at the indicated pixel.
Definition: pnmImage.I:470
A class to retrieve the individual data elements previously stored in a Datagram.
This represents a single source texture that is referenced by one or more egg files.
Definition: textureImage.h:46
void set_dest(DestTextureImage *dest)
Sets the DestTextureImage that corresponds to this texture as it was copied to the install directory.
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
An ordered list of data elements, formatted in memory for transmission over a socket or writing to a ...
Definition: datagram.h:38
int get_placed_y_size() const
Returns the size in the Y dimension, in pixels, of the texture image as it has been placed within the...
EggTexture::WrapMode get_txa_wrap_u() const
Returns the wrap mode specified in the u direction in the txa file, or WM_unspecified.
void set_xel(int x, int y, const LRGBColorf &value)
Changes the RGB color at the indicated pixel.
Definition: pnmImage.I:522
TexturePlacement * get_placement() const
Returns the particular TexturePlacement that is appropriate for this egg file.
virtual int complete_pointers(TypedWritable **p_list, BamReader *manager)
Called after the object is otherwise completely read from a Bam file, this function's job is to store...
void mark_eggs_stale()
Marks all the egg files that reference this placement stale.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
void fill_image(PNMImage &image)
Fills in the rectangle of the palette image represented by the texture placement with the image pixel...
bool is_placed() const
Returns true if the texture has been placed on a palette image, false otherwise.
int get_margin_override() const
Returns the set of groups this group depends on.
void write_pointer(Datagram &packet, const TypedWritable *dest)
The interface for writing a pointer to another object to a Bam file.
Definition: bamWriter.cxx:317
void release_source_image()
Frees the memory that was allocated by a previous call to read_source_image().
This is the set of characteristics of a texture that, if different from another texture,...