Panda3D
Loading...
Searching...
No Matches
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
30using std::max;
31using std::min;
32
33TypeHandle TexturePlacement::_type_handle;
34
35/**
36 * The default constructor is only for the convenience of the Bam reader.
37 */
38TexturePlacement::
39TexturePlacement() {
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 */
53TexturePlacement::
54TexturePlacement(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 */
76TexturePlacement::
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 */
94const std::string &TexturePlacement::
95get_name() const {
96 return _texture->get_name();
97}
98
99/**
100 * Returns the texture that this placement represents.
101 */
103get_texture() const {
104 return _texture;
105}
106
107/**
108 * Returns the grouping properties of the image.
109 */
111get_properties() const {
112 return _texture->get_properties();
113}
114
115/**
116 * Returns the group that this placement represents.
117 */
119get_group() const {
120 return _group;
121}
122
123/**
124 * Records the fact that a particular egg file is using this particular
125 * TexturePlacement.
126 */
128add_egg(TextureReference *reference) {
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 */
145remove_egg(TextureReference *reference) {
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 */
182get_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.
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) {
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.
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.
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.
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.
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.
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);
366 }
367 } else {
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 */
392is_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 */
401get_omit_reason() const {
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 */
411get_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 */
422get_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 */
433get_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 */
448is_placed() const {
449 return _image != nullptr;
450}
451
452/**
453 * Returns the particular PaletteImage on which the texture has been placed.
454 */
456get_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 */
465get_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 */
475get_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 */
485get_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 */
495get_placed_x_size() const {
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 */
505get_placed_y_size() const {
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 */
515get_placed_uv_area() const {
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 */
527place_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) {
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) {
567 _omit_reason = OR_solitary;
568 }
569}
570
571/**
572 * Indicates that the texture, formerly indicated as solitary, is now no
573 * longer.
574 */
576not_solitary() {
577 nassertv(is_placed());
578 if (_omit_reason != OR_none) {
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 */
590intersects(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 */
608compute_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 */
646write_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 */
676is_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 */
694fill_image(PNMImage &image) {
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 */
823fill_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 */
939void TexturePlacement::
940compute_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 */
998write_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 */
1034complete_pointers(TypedWritable **p_list, BamReader *manager) {
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 */
1080TypedWritable* TexturePlacement::
1081make_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 */
1095void TexturePlacement::
1096fillin(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}
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
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
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This is the fundamental interface for extracting binary objects from a Bam file, as generated by a Ba...
Definition bamReader.h:110
void read_pointers(DatagramIterator &scan, int count)
A convenience function to read a contiguous list of pointers.
bool read_pointer(DatagramIterator &scan)
The interface for reading a pointer to another object from a Bam file.
static WritableFactory * get_factory()
Returns the global WritableFactory for generating TypedWritable objects.
Definition bamReader.I:177
This is the fundamental interface for writing binary objects to a Bam file, to be extracted later by ...
Definition bamWriter.h:63
void write_pointer(Datagram &packet, const TypedWritable *dest)
The interface for writing a pointer to another object to a Bam file.
A class to retrieve the individual data elements previously stored in a Datagram.
bool get_bool()
Extracts a boolean value.
int32_t get_int32()
Extracts a signed 32-bit integer.
An ordered list of data elements, formatted in memory for transmission over a socket or writing to a ...
Definition datagram.h:38
void add_int32(int32_t value)
Adds a signed 32-bit integer to the datagram.
Definition datagram.I:67
void add_bool(bool value)
Adds a boolean value to the datagram.
Definition datagram.I:34
This represents a texture filename as it has been resized and copied to the map directory (e....
An instance of this class is passed to the Factory when requesting it to do its business and construc...
int get_x_size() const
Returns the size of the image file in pixels in the X direction.
Definition imageFile.cxx:82
const TextureProperties & get_properties() const
Returns the grouping properties of the image.
bool is_size_known() const
Returns true if the size of the image file is known, false otherwise.
Definition imageFile.cxx:73
int get_y_size() const
Returns the size of the image file in pixels in the Y direction.
Definition imageFile.cxx:92
get_maxval
Returns the maximum channel value allowable for any pixel in this image; for instance,...
get_num_channels
Returns the number of channels in the image.
static bool has_alpha(ColorType color_type)
This static variant of has_alpha() returns true if the indicated image type includes an alpha channel...
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
void set_xel_val(int x, int y, const xel &value)
Changes the RGB color at the indicated pixel.
Definition pnmImage.I:419
void set_alpha_val(int x, int y, xelval a)
Sets the alpha component color only at the indicated pixel.
Definition pnmImage.I:559
float get_alpha(int x, int y) const
Returns the alpha component color at the indicated pixel.
Definition pnmImage.I:809
LRGBColorf get_xel(int x, int y) const
Returns the RGB color at the indicated pixel.
Definition pnmImage.I:569
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.
bool is_valid() const
Returns true if the image has been read in or correctly initialized with a height and width.
Definition pnmImage.I:342
void set_xel(int x, int y, const LRGBColorf &value)
Changes the RGB color at the indicated pixel.
Definition pnmImage.I:579
void set_alpha(int x, int y, float a)
Sets the alpha component color only at the indicated pixel.
Definition pnmImage.I:859
This is the highest level of grouping for TextureImages.
bool is_none_texture_swap() const
Returns textureswap information is set or not, True if it's not set.
void unplace(TexturePlacement *placement)
Removes the texture from its position on a PaletteImage, if it has been so placed.
int get_margin_override() const
Returns the set of groups this group depends on.
This is a single palette image, one of several within a PalettePage, which is in turn one of several ...
PalettePage * get_page() const
Returns the particular PalettePage this image is associated with.
void unplace(TexturePlacement *placement)
Removes the texture from the image.
This is a particular collection of textures, within a PaletteGroup, that all share the same TexturePr...
Definition palettePage.h:33
bool operator()(TexturePlacement *a, TexturePlacement *b) const
Compares two TexturePlacement objects and returns true if the first one is bigger than the second one...
This represents a single source texture that is referenced by one or more egg files.
EggTexture::WrapMode get_txa_wrap_v() const
Returns the wrap mode specified in the v direction in the txa file, or WM_unspecified.
int get_margin() const
Returns the appropriate margin for this texture.
bool get_omit() const
Returns true if the user specifically requested to omit this texture via the "omit" keyword in the ....
void release_source_image()
Frees the memory that was allocated by a previous call to read_source_image().
double get_coverage_threshold() const
Returns the appropriate coverage threshold for this texture.
const PNMImage & read_source_image()
Reads in the original image, if it has not already been read, and returns it.
EggTexture::WrapMode get_txa_wrap_u() const
Returns the wrap mode specified in the u direction in the txa file, or WM_unspecified.
This corresponds to a particular assignment of a TextureImage with a PaletteGroup,...
void write_placed(std::ostream &out, int indent_level=0)
Writes the placement position information on a line by itself.
void place_at(PaletteImage *image, int x, int y)
Assigns the texture to a particular position within the indicated PaletteImage.
double get_placed_uv_area() const
Returns the total area of the rectangle occupied by the UV minmax box, as it has been placed.
bool is_filled() const
Returns true if the texture has been filled (i.e.
OmitReason get_omit_reason() const
Returns the reason the texture has been omitted from a palette image, or OR_none if it has not.
DestTextureImage * get_dest() const
Returns the DestTextureImage that corresponds to this texture as it was copied to the install directo...
PaletteGroup * get_group() const
Returns the group that this placement represents.
void remove_egg(TextureReference *reference)
Notes that a particular egg file is no longer using this particular TexturePlacement.
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...
void add_egg(TextureReference *reference)
Records the fact that a particular egg file is using this particular TexturePlacement.
void mark_eggs_stale()
Marks all the egg files that reference this placement stale.
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 is_size_known() const
Returns true if the texture's size is known, false otherwise.
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...
const std::string & get_name() const
Returns the name of the texture that this placement represents.
double get_uv_area() const
Returns the total area of the rectangle occupied by the UV minmax box, in UV coordinates.
static void register_with_read_factory()
Registers the current object as something that can be read from a Bam file.
PaletteImage * get_image() const
Returns the particular PaletteImage on which the texture has been placed.
virtual void write_datagram(BamWriter *writer, Datagram &datagram)
Fills the indicated datagram up with a binary representation of the current object,...
void set_dest(DestTextureImage *dest)
Sets the DestTextureImage that corresponds to this texture as it was copied to the install directory.
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 fill_swapped_image(PNMImage &image, int index)
Fills in the rectangle of the swapped palette image represented by the texture placement with the ima...
void flag_error_image(PNMImage &image)
Sets the rectangle of the palette image represented by the texture placement to red,...
const TextureProperties & get_properties() const
Returns the grouping properties of the image.
void mark_unfilled()
Marks the texture as unfilled, so that it will need to be copied into the palette image again.
int get_placed_x() const
Returns the X pixel at which the texture has been placed within its PaletteImage.
void compute_tex_matrix(LMatrix3d &transform)
Stores in the indicated matrix the appropriate texture matrix transform for the new placement of the ...
int get_placed_y() const
Returns the Y pixel at which the texture has been placed within its PaletteImage.
bool is_placed() const
Returns true if the texture has been placed on a palette image, false otherwise.
TextureImage * get_texture() const
Returns the texture that this placement represents.
bool determine_size()
Attempts to determine the appropriate size of the texture for the given placement.
void fill_image(PNMImage &image)
Fills in the rectangle of the palette image represented by the texture placement with the image pixel...
void omit_solitary()
Sets the omit reason (returned by get_omit()) to OR_solitary, indicating that the palettized version ...
void not_solitary()
Indicates that the texture, formerly indicated as solitary, is now no longer.
void force_replace()
Removes the texture from its particular PaletteImage, but does not remove it from the PaletteGroup.
PalettePage * get_page() const
Returns the particular PalettePage on which the texture has been placed.
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...
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...
virtual void write_datagram(BamWriter *writer, Datagram &datagram)
Fills the indicated datagram up with a binary representation of the current object,...
void fillin(DatagramIterator &scan, BamReader *manager)
Reads the binary data from the given datagram iterator, which was written by a previous call to write...
This is the set of characteristics of a texture that, if different from another texture,...
This is the particular reference of a texture filename by an egg file.
bool has_uvs() const
Returns true if this TextureReference actually uses the texture on geometry, with UV's and everything...
const LTexCoordd & get_max_uv() const
Returns the maximum UV coordinate in use for the texture by this reference.
void clear_placement()
Removes any reference to a TexturePlacement.
EggTexture::WrapMode get_wrap_u() const
Returns the specification for the wrapping in the U direction.
EggTexture::WrapMode get_wrap_v() const
Returns the specification for the wrapping in the V direction.
TexturePlacement * get_placement() const
Returns the particular TexturePlacement that is appropriate for this egg file.
const LTexCoordd & get_min_uv() const
Returns the minimum UV coordinate in use for the texture by this reference.
void mark_egg_stale()
Marks the egg file that shares this reference as stale.
TypeHandle is the identifier used to differentiate C++ class types.
Definition typeHandle.h:81
Base class for objects that can be written to and read from Bam files.
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...
virtual void write_datagram(BamWriter *manager, Datagram &dg)
Writes the contents of this object to the datagram for shipping out to a Bam file.
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().
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.
std::ostream & indent(std::ostream &out, int indent_level)
A handy function for doing text formatting.
Definition indent.cxx:20
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
OmitReason
This enumerates the reasons why a texture may not have been placed in a palette image.
Definition omitReason.h:23
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.