diff --git a/panda/src/gobj/texturePeeker.I b/panda/src/gobj/texturePeeker.I index ccf6bdcc45..d53e5d5a91 100644 --- a/panda/src/gobj/texturePeeker.I +++ b/panda/src/gobj/texturePeeker.I @@ -56,3 +56,11 @@ INLINE bool TexturePeeker:: has_pixel(int x, int y) const { return x >= 0 && y >= 0 && x < _x_size && y < _y_size; } + +/** + * Returns whether a given coordinate is inside of the texture dimensions. + */ +INLINE bool TexturePeeker:: +has_pixel(int x, int y, int z) const { + return x >= 0 && y >= 0 && z >= 0 && x < _x_size && y < _y_size && z < _z_size; +} diff --git a/panda/src/gobj/texturePeeker.cxx b/panda/src/gobj/texturePeeker.cxx index ee82e4644b..97da4edec2 100644 --- a/panda/src/gobj/texturePeeker.cxx +++ b/panda/src/gobj/texturePeeker.cxx @@ -74,56 +74,42 @@ static double get_signed_int_i(const unsigned char *&p) { */ TexturePeeker:: TexturePeeker(Texture *tex, Texture::CData *cdata) { - if (cdata->_texture_type == Texture::TT_cube_map) { - // Cube map texture. We'll need to map from (u, v, w) to (u, v) within - // the appropriate page, where w indicates the page. + // Simple ram images are possible if it is a 2-d texture. + if (tex->do_has_ram_image(cdata) && cdata->_ram_image_compression == Texture::CM_off) { + // Get the regular RAM image if it is available. + _image = tex->do_get_ram_image(cdata); + _x_size = cdata->_x_size; + _y_size = cdata->_y_size; + _z_size = cdata->_z_size; + _pixel_width = cdata->_component_width * cdata->_num_components; + _format = cdata->_format; + _component_type = cdata->_component_type; + } + else if (!cdata->_simple_ram_image._image.empty()) { + // Get the simple RAM image if *that* is available. + _image = cdata->_simple_ram_image._image; + _x_size = cdata->_simple_x_size; + _y_size = cdata->_simple_y_size; + _z_size = 1; - // TODO: handle cube maps. - return; - - } else { - // Regular 1-d, 2-d, or 3-d texture. The coordinates map directly. - // Simple ram images are possible if it is a 2-d texture. - if (tex->do_has_ram_image(cdata) && cdata->_ram_image_compression == Texture::CM_off) { - // Get the regular RAM image if it is available. - _image = tex->do_get_ram_image(cdata); - _x_size = cdata->_x_size; - _y_size = cdata->_y_size; - _z_size = cdata->_z_size; - _component_width = cdata->_component_width; - _num_components = cdata->_num_components; - _format = cdata->_format; - _component_type = cdata->_component_type; - - } else if (!cdata->_simple_ram_image._image.empty()) { - // Get the simple RAM image if *that* is available. - _image = cdata->_simple_ram_image._image; - _x_size = cdata->_simple_x_size; - _y_size = cdata->_simple_y_size; - _z_size = 1; - - _component_width = 1; - _num_components = 4; - _format = Texture::F_rgba; - _component_type = Texture::T_unsigned_byte; - - } else { - // Failing that, reload and get the uncompressed RAM image. - _image = tex->do_get_uncompressed_ram_image(cdata); - _x_size = cdata->_x_size; - _y_size = cdata->_y_size; - _z_size = cdata->_z_size; - _component_width = cdata->_component_width; - _num_components = cdata->_num_components; - _format = cdata->_format; - _component_type = cdata->_component_type; - } + _pixel_width = 4; + _format = Texture::F_rgba; + _component_type = Texture::T_unsigned_byte; + } + else { + // Failing that, reload and get the uncompressed RAM image. + _image = tex->do_get_uncompressed_ram_image(cdata); + _x_size = cdata->_x_size; + _y_size = cdata->_y_size; + _z_size = cdata->_z_size; + _pixel_width = cdata->_component_width * cdata->_num_components; + _format = cdata->_format; + _component_type = cdata->_component_type; } if (_image.is_null()) { return; } - _pixel_width = _component_width * _num_components; if (Texture::is_integer(_format)) { switch (_component_type) { @@ -291,8 +277,10 @@ TexturePeeker(Texture *tex, Texture::CData *cdata) { _image.clear(); return; } -} + _is_cube = (cdata->_texture_type == Texture::TT_cube_map || + cdata->_texture_type == Texture::TT_cube_map_array); +} /** * Fills "color" with the RGBA color of the texel at point (u, v). @@ -304,22 +292,95 @@ TexturePeeker(Texture *tex, Texture::CData *cdata) { */ void TexturePeeker:: lookup(LColor &color, PN_stdfloat u, PN_stdfloat v) const { - int x = int((u - cfloor(u)) * (PN_stdfloat)_x_size) % _x_size; - int y = int((v - cfloor(v)) * (PN_stdfloat)_y_size) % _y_size; - fetch_pixel(color, x, y); + if (!_is_cube) { + int x = int((u - cfloor(u)) * (PN_stdfloat)_x_size) % _x_size; + int y = int((v - cfloor(v)) * (PN_stdfloat)_y_size) % _y_size; + fetch_pixel(color, x, y); + } + else { + lookup(color, u, v, 0); + } } /** - * Works like TexturePeeker::lookup(), but instead uv-coordinates integer - * coordinates are used. + * Fills "color" with the RGBA color of the texel at point (u, v, w). + * + * The texel color is determined via nearest-point sampling (no filtering of + * adjacent pixels), regardless of the filter type associated with the + * texture. u, v, and w will wrap around regardless of the texture's wrap + * mode. */ void TexturePeeker:: -fetch_pixel(LColor& color, int x, int y) const { +lookup(LColor &color, PN_stdfloat u, PN_stdfloat v, PN_stdfloat w) const { + if (!_is_cube) { + int x = int((u - cfloor(u)) * (PN_stdfloat)_x_size) % _x_size; + int y = int((v - cfloor(v)) * (PN_stdfloat)_y_size) % _y_size; + int z = int((w - cfloor(w)) * (PN_stdfloat)_z_size) % _z_size; + + nassertv(x >= 0 && x < _x_size && y >= 0 && y < _y_size && + z >= 0 && z < _z_size); + const unsigned char *p = _image.p() + (z * _x_size * _y_size + y * _x_size + x) * _pixel_width; + + (*_get_texel)(color, p, _get_component); + } + else { + PN_stdfloat absu = fabs(u), + absv = fabs(v), + absw = fabs(w); + PN_stdfloat magnitude; + PN_stdfloat u2d, v2d; + int z; + + // The following was pulled from: + // https://www.gamedev.net/forums/topic/687535-implementing-a-cube-map-lookup-function/ + if (absw >= absu && absw >= absv) { + z = 4 + (w < 0.0); + magnitude = 0.5 / absw; + u2d = w < 0.0 ? -u : u; + v2d = -v; + } + else if (absv >= absu) { + z = 2 + (v < 0.0); + magnitude = 0.5 / absv; + u2d = u; + v2d = v < 0.0 ? -w : w; + } + else { + z = 0 + (u < 0.0); + magnitude = 0.5 / absu; + u2d = u < 0.0 ? w : -w; + v2d = -v; + } + u2d = u2d * magnitude + 0.5; + v2d = v2d * magnitude + 0.5; + + int x = int((u2d - cfloor(u2d)) * (PN_stdfloat)_x_size) % _x_size; + int y = int((v2d - cfloor(v2d)) * (PN_stdfloat)_y_size) % _y_size; + fetch_pixel(color, x, y, z); + } +} + +/** + * Works like TexturePeeker::lookup(), but instead uv-coordinates integer + * coordinates are used. + */ +void TexturePeeker:: +fetch_pixel(LColor &color, int x, int y) const { nassertv(x >= 0 && x < _x_size && y >= 0 && y < _y_size); const unsigned char *p = _image.p() + (y * _x_size + x) * _pixel_width; (*_get_texel)(color, p, _get_component); } +/** + * Works like TexturePeeker::lookup(), but instead uv-coordinates integer + * coordinates are used. + */ +void TexturePeeker:: +fetch_pixel(LColor &color, int x, int y, int z) const { + nassertv(x >= 0 && x < _x_size && y >= 0 && y < _y_size && z >= 0 && z < _z_size); + const unsigned char *p = _image.p() + ((z * _y_size + y) * _x_size + x) * _pixel_width; + (*_get_texel)(color, p, _get_component); +} /** * Performs a bilinear lookup to retrieve the color value stored at the uv @@ -370,27 +431,6 @@ lookup_bilinear(LColor &color, PN_stdfloat u, PN_stdfloat v) const { return true; } -/** - * Fills "color" with the RGBA color of the texel at point (u, v, w). - * - * The texel color is determined via nearest-point sampling (no filtering of - * adjacent pixels), regardless of the filter type associated with the - * texture. u, v, and w will wrap around regardless of the texture's wrap - * mode. - */ -void TexturePeeker:: -lookup(LColor &color, PN_stdfloat u, PN_stdfloat v, PN_stdfloat w) const { - int x = int((u - cfloor(u)) * (PN_stdfloat)_x_size) % _x_size; - int y = int((v - cfloor(v)) * (PN_stdfloat)_y_size) % _y_size; - int z = int((w - cfloor(w)) * (PN_stdfloat)_z_size) % _z_size; - - nassertv(x >= 0 && x < _x_size && y >= 0 && y < _y_size && - z >= 0 && z < _z_size); - const unsigned char *p = _image.p() + (z * _x_size * _y_size + y * _x_size + x) * _pixel_width; - - (*_get_texel)(color, p, _get_component); -} - /** * Fills "color" with the average RGBA color of the texels within the * rectangle defined by the specified coordinate range. diff --git a/panda/src/gobj/texturePeeker.h b/panda/src/gobj/texturePeeker.h index e88603bcea..2894e349cc 100644 --- a/panda/src/gobj/texturePeeker.h +++ b/panda/src/gobj/texturePeeker.h @@ -37,9 +37,11 @@ PUBLISHED: INLINE int get_z_size() const; INLINE bool has_pixel(int x, int y) const; + INLINE bool has_pixel(int x, int y, int z) const; void lookup(LColor &color, PN_stdfloat u, PN_stdfloat v) const; void lookup(LColor &color, PN_stdfloat u, PN_stdfloat v, PN_stdfloat w) const; void fetch_pixel(LColor &color, int x, int y) const; + void fetch_pixel(LColor &color, int x, int y, int z) const; bool lookup_bilinear(LColor &color, PN_stdfloat u, PN_stdfloat v) const; void filter_rect(LColor &color, PN_stdfloat min_u, PN_stdfloat min_v, @@ -86,8 +88,8 @@ private: int _x_size; int _y_size; int _z_size; - int _component_width; - int _num_components; + int _is_cube; + int _unused1; int _pixel_width; Texture::Format _format; Texture::ComponentType _component_type; diff --git a/tests/gobj/test_texture_peek.py b/tests/gobj/test_texture_peek.py index b113312dbb..339b285ddb 100644 --- a/tests/gobj/test_texture_peek.py +++ b/tests/gobj/test_texture_peek.py @@ -158,3 +158,51 @@ def test_texture_peek_int_i(): col = LColor() peeker.fetch_pixel(col, 0, 0) assert col == (minval, -1, 0, maxval) + + +def test_texture_peek_cube(): + maxval = 255 + data_list = [] + for z in range(6): + for y in range(3): + for x in range(3): + data_list += [z, y, x, maxval] + data = array('B', data_list) + tex = Texture("") + tex.setup_cube_map(3, Texture.T_unsigned_byte, Texture.F_rgba8i) + tex.set_ram_image(data) + peeker = tex.peek() + assert peeker.has_pixel(0, 0) + assert peeker.has_pixel(0, 0, 0) + + # If no z is specified, face 0 is used by default + col = LColor() + peeker.fetch_pixel(col, 1, 2) + assert col == (1, 2, 0, maxval) + + # Now try each face + for faceidx in range(6): + col = LColor() + peeker.fetch_pixel(col, 0, 0, faceidx) + assert col == (0, 0, faceidx, maxval) + + # Try some vector lookups. + def lookup(*vec): + col = LColor() + peeker.lookup(col, *vec) + return col + assert lookup(1, 0, 0) == (1, 1, 0, maxval) + assert lookup(-1, 0, 0) == (1, 1, 1, maxval) + assert lookup(0, 1, 0) == (1, 1, 2, maxval) + assert lookup(0, -1, 0) == (1, 1, 3, maxval) + assert lookup(0, 0, 1) == (1, 1, 4, maxval) + assert lookup(0, 0, -1) == (1, 1, 5, maxval) + + # Magnitude shouldn't matter + assert lookup(0, 2, 0) == (1, 1, 2, maxval) + assert lookup(0, 0, -0.5) == (1, 1, 5, maxval) + + # Sample in corner (slight bias to disambiguate which face is selected) + assert lookup(1.00001, 1, 1) == (0, 0, 0, maxval) + assert lookup(1.00001, 1, 0) == (1, 0, 0, maxval) + assert lookup(1, 1.00001, 0) == (2, 1, 2, maxval)