ranjeethmahankali / alum

Halfedge mesh library in Rust
BSD 3-Clause "New" or "Revised" License
18 stars 0 forks source link

Texture Coordinate Representation #17

Open engstad opened 2 weeks ago

engstad commented 2 weeks ago

Properties seem to be either per-vertex, per-edge or per-face. This is not enough to represent everything in a mesh, especially per (vertex, face) pair, such as texture coordinates. In addition, for UV-mapping it is desirable to refer to the sub-topologies of the maps (which is a subset of the topology of the mesh itself). Imagine, for instance a cube where four of the faces have texture coordinates (0,0), (1,0), (1,1), (0, 1) in some permutation and two adjacent faces have texture coordinates (0, 0), (0, 0.5), (0, 1.0), (1, 1.0), (0.5, 1.0), (0, 1.0).

ranjeethmahankali commented 2 weeks ago

@engstad I think this property system is adequate to do what you're describing. You need to assign your texture coordinates to halfedges. Each halfedge uniquely belongs to a face, and points to a vertex that is also incident on said face. You can interpret the texture coordinate of a halfedge as belonging to the vertex at the head of the halfedge. So in theory, each vertex in a cube can have three different texture coordinates, stored as properties of the three incoming halfedges, associated with the three incident faces. A halfedge is an effective proxy for a face-vertex pair in this mesh representation.

Depending on what is convenient you can also store the per-face texture coordinates of a vertex on outgoing halfedges.

Did I understand your problem correctly? Does that answer your question?

ranjeethmahankali commented 1 week ago

@engstad Closing this issue for now. Feel free to reopen it if you have any further concerns.

engstad commented 1 week ago

That does solve the problem of storing texture-coordinates somewhat, but it has some problems

   7----6          7--6
  /.   /|          |  |
 3----2 |          3--2
 | 4..|.5   --->   |  |
 |.   |/           0--1
 0----1

The UV-map above is mapped onto two of the cube faces. Assume I put (map_id, u, v) on a half-edge (with the meaning, as you suggest, that If the half-edge is I->J then the UV at vertex J equals (u, v).

First problem is that I want to share the UV-coordinate of edge 1->2 (vertex 2 in face associated with 0123) with 3->2 (face 3267). If I modify one of them, I have to make sure to modify the other. That could be quite messy.

Second problem is how can I investigate the UV topology? It is easy to traverse the mesh-topology, but it would be much harder to traverse the UV-map.

As you can see above, the UV-maps are really a sub-topology of the original mesh. It would be nice if the UV-map itself could be described as a half-edge structure that can be traversed naturally.

ranjeethmahankali commented 1 week ago

Sounds like the topology of your textures does not match the topology of your geometry. This mismatch is the cause of difficulty. I don't see how this problem relates to halfedge mesh datastructures / property systems.

Can you explain to me how you would represent uv coordinates like this in other mesh datastructures (not halfedge based)? Because it looks like you're choosing to ignore the 3-2 edge, but not the 1-2 edge. This is arbitrary, and creates arbitrary combinations of vertices and faces for which you'd have to store the uv coordinates.

I think something like this has to be solved by matching the topology of textures and topology of geometry.

engstad commented 1 week ago

This is always tricky and depends on the data-structure used. Very often this capability is ignored and people put hacks on top of the underlying data structure, making it slow and/or hard to use. In fact, you will not find much support for this in academic papers even though it is a tricky problem in practice.

I am not sure what you mean by "ignoring the 3-2 edge". I am not. The difference between the 3-2 edge and the 1-2 edge is that the 3-2 edge is an internal (and continuous) edge in the UV-topology, whereas the 1-2 edge is an external edge in the UV-topology. Look at this to see how the wrapping works:

Blender's UV unwrapping

I have seen the following ways to deal with it:

  1. Split up the mesh into multiple meshes even before using the data-structure. This way you are sure there is a 1-to-1 correspondence between the UV-topology and the original topology. Downsides: You duplicate shared edges and you have to be careful about downstream transformations. For instance, if you do mesh decimation then you can introduce holes between the meshes.
  2. Append a list (or a map) of UV information per face, vertex or edge. Downsides: All mesh transformations has to be "UV-information" aware. In mesh deformation, you must make sure to not do that if you go outside the UV-topology.

I hope you understand the problem now!

ranjeethmahankali commented 1 week ago

yes I understand the problem.

How about storing the UV coordinates in a separate list and storing an index into that list as a halfedge property as per the original idea?

I your original cube example the halfedges 3-2 and 1-2 would have the same index. Where as the halfedge 6-2 would have a different index. When you update uv coordinates, you just update them in your list, and automatically the halfedges 3-2 and 1-2 point to the same index, and same uv coordinates.

Does that work?

In any case, I don't see any way of doing this other than having some other datastructure that captures the intent of how you want to split the mesh for unwrapping, which edges are internal vs external etc. This intent has to be captured one way or another. Either by splitting the mesh, or by tracking indices into another list of coordinates. I don't see a more elegant way of doing this.

ranjeethmahankali commented 1 week ago

Maybe you can use the properties to keep track of whether the edges / vertices are internal or external in your unwrapped topology. That way, you should be able to traverse the topology better.

ranjeethmahankali commented 1 week ago

Here's a more detailed explanation of my earlier idea. I will use the simplest example possible. Say you have a mesh with two triangles, and the two triangle are split up in the UV unwrapping of the mesh, so the vertices along the diagonal have different uv coordinates depending on which triangle they belong to.

                                     (a, b)   (g, h)   (i, j)
0------1                               0         0------1
|\     |                               |\         \     |
| \    |                               | \         \    |
|  \   |   ==== UV unwrapping ====>    |  \         \   |
|   \  |                               |   \         \  |
|    \ |                               |    \         \ |
|     \|                               |     \         \|
3------2                               3------2         2
                                   (c, d)    (e, f)    (k, l)

The second picture shows the uv coordinates. Vertices 0 and 2 have two sets of uv coordinates. You can have a list of all texture coordinates that looks like:

0: (a, b)
1: (c, d)
2: (e, f)
3: (g, h)
4: (i, j)
5: (k. l)

And you'd have halfedge properties that look like:

2->0: 0
0->3: 1
3->2: 2
1->0: 3
2->1: 4
0->2: 5

This is the cleanest solution I can think of. Makes full use of the property system, no duplication of data anywhere, and lets you traverse the topology with full freedon. You can also identify which edges are internal / border in your uv unwrapping by examining the texture coordinate indices stored in halfedge properties. You also don't need to synchronize the halfedges that share the same UV coordinate.