Background info
Polygonal meshes use UV sets to map image textures to their surface. Using UDIM textures you can use integer tiles to apply different textures as within what integer bounds the UV tile is, e.g. tile [0, 0] can have a different texture than UV tile [0, 1].
Given the UV shells/points I want to quickly detect the integer tiles the UV set is currently using. In practice/production UVs rarely span UV tile boundaries since they are laid out per UDIM tile. Nevertheless I'd like accurately detect these less frequent cases too.
Goal
Given a mesh' UVs I want to detect what UV tiles are occupied. The example image above has shells within the bounding boxes of UV tiles but technically it is possible for the UVs to cross the boundaries of a UV tile and extend into the next (or even further).
I'd like to query/detect what UV tiles are being used:
- The UV points can all be outside of a tile (e.g. 4 points of a polygon around tile [0, 0]) and the tile is encompassed in the polygon. The tile should be include as used in the UVs.
- The UV points can go around a tile (e.g. an NGon UV of more than 4 points that curve around a tile (so bounding box hits the tile, but the UVs dont actually include the tile).
- Other cases are UVs can just overlap/intersect with a Tile or be totally encompassed within a Tile. The tiles should then be included.
I'm trying to do this in Autodesk Maya with Python yet I'm happy with any mathematical approach that's fast and I can easily figure out how to code with Python.
In essence it's querying what integer tiles/cells a 2D Polygon is overlappign with. I don't need to know intersections points - just need to know what UV tiles contain UVs.
I'd prefer to avoid including the tiles that are only hit on the boundary. For example, a UV point/edge on [0, 1] doesn't need to include tile [0, 1] if the polygon only overlaps [0, 0].
What I have so far
Currently I only have some prototypes done within Maya that compared based on the boundaries in a gist here. Some other approach in the comments too. Code:
from maya import cmds
def get_bounding_box_tiles(bb):
u_minmax, v_minmax = bb
# If the max is exactly on the integer boundary we allow it to be
# part of the tile of the previos integer so we can subtract one.
# But since we'll need to add one to iterate "up to" the maximum for
# the `range` function we will instead add one to the opposite cases.
# Inputs are tuples so don't assign elements but override tuple
if u_minmax[1] != int(u_minmax[1]):
u_minmax = (u_minmax[0], u_minmax[1] + 1)
if v_minmax[1] != int(v_minmax[1]):
v_minmax = (v_minmax[0], v_minmax[1] + 1)
tiles = []
for v in range(*map(int, v_minmax)):
for u in range(*map(int, u_minmax)):
tiles.append((u, v))
return tiles
def get_uv_udim_tiles(mesh, uv_set=None):
"""Return the UV tiles used by the UVs of the input mesh.
Warning:
This does not capture the case where a single UV shell
might be layout in such a way that all its UV points are
around another tile since it uses the UV shell bounding
box to compute the used tiles. In the image below imagine
the lines being the UVs of a single shell and the `x` being
an emtpy UV tile. It will not detect the empty tile.
/ - \
| x |
Args:
mesh (str): Mesh node name.
uv_set (str): The UV set to sample. When not
provided the current UV map is used.
Returns:
list: sorted list of uv tiles
"""
kwargs = {}
if uv_set is not None:
kwargs["uvSetName"] = uv_set
bb = cmds.polyEvaluate(mesh, boundingBox2d=True, **kwargs)
tiles = get_bounding_box_tiles(bb)
if len(tiles) == 1:
# If there's only a single tile for the bounding box
# it'll be impossible for there to be empty tiles
# in-between so we just return the given tiles
return tiles
# Get the bounding box per UV shell
uv_shells = cmds.polyEvaluate(mesh, uvShell=True, **kwargs)
if uv_shells == 1:
# If there's only a single UV shell it must span
# all the UV tiles
return tiles
tiles = set()
for i in range(uv_shells):
shell_uvs = cmds.polyEvaluate(mesh, uvsInShell=i, **kwargs)
shell_bb = cmds.polyEvaluate(shell_uvs, boundingBoxComponent2d=True, **kwargs)
shell_tiles = get_bounding_box_tiles(shell_bb)
tiles.update(shell_tiles)
return sorted(tiles, key=uv2udim)
def uv2udim(tile):
"""UV tile to UDIM number.
Note that an input integer of 2 means it's
the UV tile range using 2.0-3.0.
Examples:
>>> uv2udim((0, 0)
# 1001
>>> uv2udim((0, 1)
# 1011
>>> uv2udim((2, 0)
# 1003
>>> uv2udim(8, 899)
# 9999
Returns:
int: UDIM tile number
"""
u, v = tile
return 1001 + u + 10 * v
# Example usage
for mesh in cmds.ls(selection=True):
tiles = get_uv_udim_tiles(mesh)
print mesh
print tiles
for tile in tiles:
print uv2udim(tile)
Related
- How to check intersections with two rotated rectangles - This would be a potential solution. However a single UV polygon can technically be concave where this implementation would fail. I'd assume also since we're checking with axis-aligned integer tiles the algorithm could be simpler.
- AABB and Concave Polygon Intersection - Only would work with Concave Polygons - but other than that similar question.