gobj: Cube map sampling support in TexturePeeker

Closes #1098

Co-authored-by: Mitchell Stokes <mogurijin@gmail.com>
This commit is contained in:
rdb 2021-03-24 12:50:42 +01:00
parent 1fb8480585
commit 8372b8150a
4 changed files with 172 additions and 74 deletions

View File

@ -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;
}

View File

@ -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.

View File

@ -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;

View File

@ -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)