Repository navigation
3.40.3
A bugfix release. The ARKit blendshapes generated by qtmesh facerig no longer tear the mesh around the mouth.
The mouth tore open when the jaw did (#1061)
Reported as jagged spikes along the lip line whenever jawOpen fired. Vertices at the centre of the lip stayed completely still while every one of their neighbours moved about 4 units, so the triangles between them stretched into visible spikes.
The cause was in how a user vertex is matched to the template. Each one is matched to a triangle found by searching outward from the nearest correspondence vertex, and only triangles touching that vertex are ever considered. So that seed silently decides which surface gets searched.
At the lip centre the mouth-interior island passes about 0.14 units from the outer lip, closer than some of a lip vertex's own neighbours. Three lip vertices therefore seeded on the island — and every triangle touching an island vertex is an island triangle (7 of 7, 5 of 5, 7 of 7 measured). The correct lip triangle was never a candidate at all, so those vertices inherited the island's zero motion.
That is also why the obvious fixes did nothing, and three were tried and measured before the cause was understood. Widening the search to the 8 nearest vertices made it worse, 8 frozen vertices becoming 19, because it only added more island triangles. Restricting candidates to the seed's own connected component changed nothing, because the seed is itself on the island. Rejecting wrongly-oriented triangles also changed nothing, because it rejected every candidate and the fallback restored the bad match. The answer was never in the set being filtered.
The fix chooses the seed by surface orientation rather than distance alone. The two surfaces face opposite ways, which is precisely what makes them distinct surfaces, and that separates them where proximity cannot:
| distance | surface | orientation | |
|---|---|---|---|
| nearest overall | 0.144 | mouth interior | −0.974 |
| nearest agreeing | 0.118 | outer lip | +0.993 |
The correct vertex is both nearer and correctly oriented. Where no nearby vertex agrees, the nearest is used unchanged, so a correspondence can never be lost.
Result, measured on the ICT template rigged against itself, where a correct result is exact by construction. A tear is an edge whose endpoints disagree in motion by more than 1.0, which is what reads on screen as a spike:
jawOpen |
tears | worst tear |
|---|---|---|
| 3.40.1 | 1182 | 4.018 |
| 3.40.2 | 372 | 4.076 |
| 3.40.3 | 60 | 1.885 |
| the template's own data | 60 | 2.054 |
The output now reproduces the template's own discontinuities and nothing further. Those 60 remaining are ICT's authored data — the back of the mouth cavity legitimately holding still while the jaw rotates around it — so they are the floor, not a defect. Every shape measured improves and none regress; mouthClose and mouthRollLower reach zero tears.
Worth noting 3.40.2 cut the tear count threefold while leaving the worst tear untouched, which is why the mouth still looked wrong. Moving the worst case is what fixes the visible spikes.
Review hardening
Three further defects were found in review of the fix and corrected before release. A user mesh wound opposite to the template has globally negated normals, which would have inverted the orientation test and silently reintroduced the original bug; the global winding relationship is now measured once and applied. The seed search also gave up early in two different ways, each leaving the wrong-surface fallback reachable.