fix linhas
This commit is contained in:
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"/
|
||||||
|
|||||||
Reference in new issue
Block a user