diff options
Diffstat (limited to 'public')
| -rw-r--r-- | public/bluebey-studio/assets/bluebey.glb | bin | 3253400 -> 3109288 bytes | |||
| -rw-r--r-- | public/bluebey-studio/src/model.js | 33 |
2 files changed, 33 insertions, 0 deletions
diff --git a/public/bluebey-studio/assets/bluebey.glb b/public/bluebey-studio/assets/bluebey.glb Binary files differindex 13131f5..0072864 100644 --- a/public/bluebey-studio/assets/bluebey.glb +++ b/public/bluebey-studio/assets/bluebey.glb diff --git a/public/bluebey-studio/src/model.js b/public/bluebey-studio/src/model.js index 68f1255..3925e85 100644 --- a/public/bluebey-studio/src/model.js +++ b/public/bluebey-studio/src/model.js @@ -134,6 +134,10 @@ export async function loadModel(source, { onProgress } = {}) { const mouth = mouthPlate ?? mouthOriginal; // The wrapping hair shell, if the model carries one (`hair-plate`). const hairMesh = hairPlate; + // Its cylindrical UV has a seam at the back of the head. Triangles that cross + // it get one UV near 0 and the next near 1, so the whole texture is stretched + // across them - a visible streak down the back. Close the seam per triangle. + if (hairMesh) healCylinderSeam(hairMesh); if (!eyeMesh) throw new Error('eye mesh not found in the GLB'); if (!mouth) throw new Error('mouth mesh not found in the GLB'); @@ -248,3 +252,32 @@ function inflateOverlay(mesh, centre, distance) { geometry.computeBoundingBox(); geometry.computeBoundingSphere(); } + +/** + * Make every triangle of a cylindrical UV island sit on one side of the seam. + * + * A cylinder unwrap cuts the surface open along one line (here the back of the + * head). A triangle that spans the cut has its corners at u ~ 1 and u ~ 0, so + * interpolating them drags the whole texture across the triangle - the streak + * seen down the back. Wrapping the low corners up by one turn keeps each + * triangle local; the texture's two edges are identical (the covering is drawn + * right across), so the join is invisible. + */ +function healCylinderSeam(mesh) { + const source = mesh?.geometry; + if (!source?.attributes?.uv) return; + // Give every triangle its own corners first. The seam's low and high sides + // share vertices, so editing a shared one would drag its neighbours too; a + // non-indexed copy isolates each triangle and the fix cannot leak. + const geometry = source.index ? source.toNonIndexed() : source; + const uv = geometry.attributes.uv; + for (let i = 0; i < uv.count; i += 3) { + const top = Math.max(uv.getX(i), uv.getX(i + 1), uv.getX(i + 2)); + if (top - Math.min(uv.getX(i), uv.getX(i + 1), uv.getX(i + 2)) <= 0.5) continue; + for (let k = 0; k < 3; k += 1) { + if (uv.getX(i + k) < top - 0.5) uv.setX(i + k, uv.getX(i + k) + 1); + } + } + uv.needsUpdate = true; + mesh.geometry = geometry; +} |
