I should add - there are actually a huge number of different possible duals and it can be quite a large topic rather than a single answer.

Topologically things are relatively simple - for each face in the input we get a vertex in the output and vice-versa (though there are a number of options for what we can consider the dual faces for boundary vertices).

Geometrically however, we can choose where to place these dual vertices in all sorts of ways.

Even if we take the specific case where the input has only triangular faces, we can use any of the thousands of possible ways of defining the center of a triangle to place each dual vertex.

If we allow the dual vertex position to be determined not only by the 3 points of the primal face, but other geometric properties of its neighbourhood, this expands the possibilities even further, for example having input weights for the primal vertices, trying to keep similar areas for dual faces, or similar lengths for the dual edges, more planar dual faces, and so on.

Which variation is most appropriate depends on the application.

(However, when the mesh is fairly smooth with triangles fairly close to equilateral, the differences between all these variations can become quite subtle, so a lot of the time almost any of them will do).

The script I just posted above gives the incircular dual, but the one in TriRemesh uses some ideas from the Hodge Optimised Triangulations paper to give ‘nice’ dual faces.