fix bugs
This commit is contained in:
1 parent
1e1d2a9473
commit
157993d755
3 files changed
+71
-10
No files matched your search
@@ -32,6 +32,12 @@ export function QuickRegionNav() {
|
||||
const focusOnRegion = useAtlasStore((s) => s.focusOnRegion);
|
||||
const focusOnViewAngle = useAtlasStore((s) => s.focusOnViewAngle);
|
||||
const [pos, setPos] = useState<{ x: number; y: number } | null>(() => loadStoredPos());
|
||||
// Só pra realçar visualmente o último botão clicado em cada grupo — o
|
||||
// enquadramento inicial já é "Corpo Inteiro" + "Anterior" (ver Scene.tsx),
|
||||
// então começamos os dois já marcados como ativos pra bater com o que a
|
||||
// câmera mostra ao abrir.
|
||||
const [activeRegion, setActiveRegion] = useState<BodyRegion>("corpo_inteiro");
|
||||
const [activeAngle, setActiveAngle] = useState<"anterior" | "posterior">("anterior");
|
||||
const elRef = useRef<HTMLDivElement>(null);
|
||||
const dragRef = useRef<{ startX: number; startY: number; origX: number; origY: number } | null>(null);
|
||||
|
||||
@@ -111,40 +117,85 @@ export function QuickRegionNav() {
|
||||
onPointerDown={onGripPointerDown}
|
||||
onPointerMove={onGripPointerMove}
|
||||
onPointerUp={onGripPointerUp}
|
||||
className="shrink-0 cursor-move select-none text-slate-500 hover:text-slate-300 px-1 touch-none"
|
||||
className="shrink-0 cursor-move select-none text-slate-500 hover:text-slate-300 px-1 touch-none self-stretch flex items-center"
|
||||
title="Arrastar"
|
||||
aria-label="Arrastar menu de regiões"
|
||||
>
|
||||
⠿
|
||||
</div>
|
||||
<div className="flex gap-1.5 overflow-x-auto md:flex-wrap">
|
||||
|
||||
{/* Duas linhas em vez de um único flex-wrap: região e ângulo de vista
|
||||
são decisões diferentes (onde olhar x de que lado olhar), e
|
||||
misturadas numa fileira só quebravam de um jeito confuso (ex:
|
||||
"Corpo Inteiro" ficava orfão numa segunda linha, e Anterior/
|
||||
Posterior se perdiam no meio dos pills de região). */}
|
||||
<div className="flex flex-col gap-1.5 min-w-0">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="shrink-0 text-[10px] font-medium uppercase tracking-wide text-slate-500 w-14">
|
||||
Região
|
||||
</span>
|
||||
<div className="flex gap-1.5 overflow-x-auto md:flex-wrap md:max-w-[40vw]">
|
||||
{REGIONS.map((r) => (
|
||||
<button
|
||||
key={r.region}
|
||||
onClick={() => focusOnRegion(r.region)}
|
||||
className="shrink-0 text-xs px-3 py-1.5 rounded-full bg-white/5 hover:bg-accent hover:text-white text-slate-300 transition-colors"
|
||||
onClick={() => {
|
||||
setActiveRegion(r.region);
|
||||
focusOnRegion(r.region);
|
||||
}}
|
||||
className={
|
||||
"shrink-0 text-xs px-3 py-1.5 rounded-full transition-colors " +
|
||||
(activeRegion === r.region
|
||||
? "bg-accent text-white"
|
||||
: "bg-white/5 hover:bg-accent hover:text-white text-slate-300")
|
||||
}
|
||||
>
|
||||
{r.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="shrink-0 h-6 w-px bg-white/10" aria-hidden="true" />
|
||||
<div className="flex gap-1.5 shrink-0">
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="shrink-0 text-[10px] font-medium uppercase tracking-wide text-slate-500 w-14">
|
||||
Vista
|
||||
</span>
|
||||
{/* Toggle segmentado (2 opções, uma cápsula só) em vez de dois
|
||||
pills soltos — deixa claro que é uma escolha binária (frente
|
||||
OU costas), não mais um item de uma lista como as regiões. */}
|
||||
<div className="inline-flex shrink-0 rounded-full bg-white/5 p-0.5">
|
||||
<button
|
||||
onClick={() => focusOnViewAngle("anterior")}
|
||||
onClick={() => {
|
||||
setActiveAngle("anterior");
|
||||
focusOnViewAngle("anterior");
|
||||
}}
|
||||
title="Vista anterior (de frente)"
|
||||
className="shrink-0 text-xs px-3 py-1.5 rounded-full bg-white/5 hover:bg-accent hover:text-white text-slate-300 transition-colors"
|
||||
className={
|
||||
"text-xs px-3 py-1 rounded-full transition-colors " +
|
||||
(activeAngle === "anterior"
|
||||
? "bg-accent text-white"
|
||||
: "text-slate-300 hover:text-white")
|
||||
}
|
||||
>
|
||||
Anterior
|
||||
</button>
|
||||
<button
|
||||
onClick={() => focusOnViewAngle("posterior")}
|
||||
onClick={() => {
|
||||
setActiveAngle("posterior");
|
||||
focusOnViewAngle("posterior");
|
||||
}}
|
||||
title="Vista posterior (de costas)"
|
||||
className="shrink-0 text-xs px-3 py-1.5 rounded-full bg-white/5 hover:bg-accent hover:text-white text-slate-300 transition-colors"
|
||||
className={
|
||||
"text-xs px-3 py-1 rounded-full transition-colors " +
|
||||
(activeAngle === "posterior"
|
||||
? "bg-accent text-white"
|
||||
: "text-slate-300 hover:text-white")
|
||||
}
|
||||
>
|
||||
Posterior
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -382,6 +382,7 @@ export function Scene({
|
||||
sectionColor="#334155"
|
||||
fadeDistance={6}
|
||||
infiniteGrid
|
||||
userData={{ excludeFromFit: true }}
|
||||
/>
|
||||
<CameraController modelFiles={modelFiles} />
|
||||
<IntercostalLabels bySlug={bySlug} />
|
||||
|
||||
@@ -15,12 +15,21 @@ function isFiniteVec3(v: THREE.Vector3): boolean {
|
||||
// vértices degenerados/NaN, e Box3.setFromObject(scene) ingenuamente
|
||||
// incluiria isso, distorcendo o enquadramento (ou escondendo tudo o mais
|
||||
// fora do "zoom" resultante).
|
||||
//
|
||||
// Também pulamos objetos marcados com userData.excludeFromFit — o <Grid>
|
||||
// decorativo do chão (ver Scene.tsx) é uma malha de verdade (um
|
||||
// <planeGeometry args={[4,4]}/> por baixo do shader que faz ele PARECER
|
||||
// infinito), então sem essa exclusão ele entrava na conta do
|
||||
// computeVisibleBoundingBox(scene) e distorcia o enquadramento inicial da
|
||||
// câmera (cálculo de distância/aspecto erradOS, câmera ficando perto
|
||||
// demais ou longe demais do corpo de verdade).
|
||||
export function computeVisibleBoundingBox(root: THREE.Object3D): THREE.Box3 {
|
||||
const union = new THREE.Box3();
|
||||
const tmp = new THREE.Box3();
|
||||
root.traverse((obj) => {
|
||||
const mesh = obj as THREE.Mesh;
|
||||
if (!mesh.isMesh || !mesh.visible) return;
|
||||
if (mesh.userData?.excludeFromFit) return;
|
||||
tmp.setFromObject(mesh);
|
||||
if (tmp.isEmpty()) return;
|
||||
if (!isFiniteVec3(tmp.min) || !isFiniteVec3(tmp.max)) return;
|
||||
|
||||
Reference in new issue
Block a user