Selaa lähdekoodia

gobj: Cube map sampling support in TexturePeeker

Closes #1098

Co-authored-by: Mitchell Stokes <[email protected]>
rdb 4 vuotta sitten
vanhempi
sitoutus
8372b8150a

+ 8 - 0
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;
+}

+ 113 - 73
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.
-
-    // 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;
-    }
+  // 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;
+
+    _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);
+  }
+}
+
+/**
+ * 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 {
+  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.
+ * Works like TexturePeeker::lookup(), but instead uv-coordinates integer
+ * coordinates are used.
  */
 void TexturePeeker::
-fetch_pixel(LColor& color, int x, int y) const {
+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.

+ 4 - 2
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;

+ 48 - 0
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)