I think a graph-based approach may work.
First, the list of triangular faces can be recovered by noting that the set of edges define an undirected graph G1(V1,E1)
for connectivity between the geometric vertices. A triangular face is any length 3 cycle in this graph.
for (i = all vertices in G1)
// form list of vertex triplets
list = find all length 3 cycles from ith vertex
// push new faces onto output
for (j = all triplets in list)
[v1,v2,v3] = list(j)
if ([v1,v2,v3] is not an existing face)
push triplet [v1,v2,v3] as a new face
endif
endfor
endfor
Next, the tetrahedra can be recovered by forming the undirected graph G2(V2,E2)
defining the connectivity between faces (i.e. faces are connected if they share an edge). A tetrahedra is any length 4 cycle in this graph.
for (i = all vertices in G2)
// form a list of face tuples
list = find all length 4 cycles from ith vertex
// push new tetrahedra onto output
for (j = all tuples in list)
[f1,f2,f3] = list(j)
[v1,v2,v3,v4] = unique vertices in faces [f1,f2,f3]
if ([v1,v2,v3,v4] is not an existing tetrahedra)
push tuple [v1,v2,v3,v4] as a new tetrahedra
endif
endif
endfor
Hope this helps.