fix linhas

This commit is contained in:
Rodrigo Bertoldo de Sousa Martins committed 2026-09-11 12:32:15 -03:00
1 parent dedf2e5291
commit 89b08a19ef
1 file changed
+80
@@ -87,6 +87,77 @@ function resolveSkeletalBone(rawName) {
return { canonicalKey: entry.canonicalKey, pt: entry.pt, side: "single" }; return { canonicalKey: entry.canonicalKey, pt: entry.pt, side: "single" };
} }
// "Sistema Nervoso" e "Regiões do corpo" não têm uma lista de exclusão
// como o esquelético (206 ossos) — lá, QUALQUER malha nomeada vira uma
// estrutura. O problema: o Z-Anatomy original usa "linhas de chamada"
// (pontos apontando de uma estrutura pra um rótulo de texto, como nos
// diagramas didáticos) que às vezes são a PRÓPRIA malha da estrutura (não
// dá pra filtrar por nome — o usuário confirmou que clicar numa dessas
// linhas resolve num nome real, ex: "Right Anterior region of forearm").
// Em vez de nome, detectamos pela GEOMETRIA: uma linha de chamada é um
// segmento reto e fino (baixíssimo desvio perpendicular ao seu próprio
// eixo mais longo) — bem diferente de uma estrutura anatômica de verdade
// (mesmo um nervo fino tem curvas/ramificações reais). Isso não depende
// de nome nenhum, então não arrisca excluir a estrutura errada.
const LEADER_LINE_SOURCE_KEYS = new Set(["nervous", "regions"]);
function isLeaderLineMesh(mesh) {
try {
const prims = mesh.listPrimitives();
if (prims.length === 0) return false;
return prims.every((prim) => isLeaderLinePrimitive(prim));
} catch {
return false;
}
}
function isLeaderLinePrimitive(prim) {
const accessor = prim.getAttribute("POSITION");
if (!accessor) return false;
const count = accessor.getCount();
if (count < 2) return false;
const arr = accessor.getArray();
if (!arr) return false;
const at = (i) => [arr[i * 3], arr[i * 3 + 1], arr[i * 3 + 2]];
const dist2 = (a, b) => {
const dx = a[0] - b[0], dy = a[1] - b[1], dz = a[2] - b[2];
return dx * dx + dy * dy + dz * dz;
};
// Acha os dois pontos mais distantes entre si (técnica padrão e barata
// pra aproximar o "eixo" mais longo de uma nuvem de pontos): parte de
// um ponto qualquer, acha o mais distante dele (a), depois o mais
// distante de "a" (b) — "a" e "b" aproximam as duas pontas do objeto.
let a = at(0), bestD = 0;
for (let i = 1; i < count; i++) {
const d = dist2(a, at(i));
if (d > bestD) { bestD = d; a = at(i); }
}
let b = a; bestD = 0;
for (let i = 0; i < count; i++) {
const d = dist2(a, at(i));
if (d > bestD) { bestD = d; b = at(i); }
}
const axisLenSq = dist2(a, b);
const axisLen = Math.sqrt(axisLenSq);
if (axisLen < 0.02) return false; // curto demais pra ser uma linha de chamada (poste real de âncora)
const ax = [(b[0] - a[0]) / axisLen, (b[1] - a[1]) / axisLen, (b[2] - a[2]) / axisLen];
let maxPerp = 0;
for (let i = 0; i < count; i++) {
const p = at(i);
const v = [p[0] - a[0], p[1] - a[1], p[2] - a[2]];
const t = v[0] * ax[0] + v[1] * ax[1] + v[2] * ax[2];
const perp2 =
(v[0] - t * ax[0]) ** 2 + (v[1] - t * ax[1]) ** 2 + (v[2] - t * ax[2]) ** 2;
if (perp2 > maxPerp) maxPerp = perp2;
}
// "Linha reta e fina": todos os pontos ficam muito perto do próprio eixo
// mais longo (desvio perpendicular pequeno comparado ao comprimento).
return Math.sqrt(maxPerp) < axisLen * 0.08;
}
function classifySystem(key, rawName) { function classifySystem(key, rawName) {
const target = SOURCE_TARGETS[key]; const target = SOURCE_TARGETS[key];
if (target.system) return target.system; if (target.system) return target.system;
@@ -165,6 +236,15 @@ for (const { fbx, key } of SOURCES) {
namePt = `${resolved.pt}${sideLabel}`; namePt = `${resolved.pt}${sideLabel}`;
nameEn = `${sideWord}${resolved.canonicalKey}`; nameEn = `${sideWord}${resolved.canonicalKey}`;
description = BONE_DESCRIPTIONS[resolved.canonicalKey] ?? ""; description = BONE_DESCRIPTIONS[resolved.canonicalKey] ?? "";
} else if (LEADER_LINE_SOURCE_KEYS.has(key) && isLeaderLineMesh(mesh)) {
// Segmento reto e fino (linha de chamada de rótulo do app
// original) — mesmo tendo um nome "real", não é uma estrutura
// anatômica visível/clicável de verdade. Marcamos excluído (não
// vira estrutura, mesh fica no .glb mas oculto) do mesmo jeito
// que o esquelético já faz pros seus "acidentes ósseos".
node.setExtras({ excluded: true });
for (const child of node.listChildren()) visit(child);
return;
} else { } else {
systemSlug = classifySystem(key, rawName); systemSlug = classifySystem(key, rawName);
// Nomes de muscular/regiões/referências vêm com sufixo ".l"/".r"/ // Nomes de muscular/regiões/referências vêm com sufixo ".l"/".r"/