58 lines
2.2 KiB
TypeScript
58 lines
2.2 KiB
TypeScript
import { Canvas } from "@react-three/fiber";
|
|
import { useGLTF } from "@react-three/drei";
|
|
import { Suspense, useMemo } from "react";
|
|
import * as THREE from "three";
|
|
import { clone as cloneSkeleton } from "three/examples/jsm/utils/SkeletonUtils.js";
|
|
import { BOSS_DEFINITIONS } from "../game/bossCatalog";
|
|
import { ALTERNATE_BOSS_CONFIG, bossVisualUrl } from "../game/bossVisuals";
|
|
import type { BossId } from "../game/types";
|
|
|
|
function PortraitModel({ bossId }: { bossId: BossId }) {
|
|
const gltf = useGLTF(bossVisualUrl(bossId), false, true);
|
|
const model = useMemo(() => {
|
|
const clone = cloneSkeleton(gltf.scene);
|
|
clone.updateMatrixWorld(true);
|
|
const bounds = new THREE.Box3().setFromObject(clone);
|
|
const center = bounds.getCenter(new THREE.Vector3());
|
|
const size = bounds.getSize(new THREE.Vector3());
|
|
const scale = 2.35 / Math.max(size.x, size.y, size.z, 0.001);
|
|
clone.traverse((object) => {
|
|
if (object instanceof THREE.Mesh) {
|
|
object.castShadow = false;
|
|
object.receiveShadow = false;
|
|
}
|
|
});
|
|
return { clone, center, scale };
|
|
}, [gltf.scene]);
|
|
|
|
return (
|
|
<primitive
|
|
object={model.clone}
|
|
position={[-model.center.x * model.scale, -model.center.y * model.scale, -model.center.z * model.scale]}
|
|
rotation={[0, bossId === "bulldrome" ? 0.35 : ALTERNATE_BOSS_CONFIG[bossId].rotationOffset + 0.35, 0]}
|
|
scale={model.scale}
|
|
/>
|
|
);
|
|
}
|
|
|
|
export function BossTrophyPortrait({ bossId }: { bossId: BossId }) {
|
|
const boss = BOSS_DEFINITIONS[bossId];
|
|
return (
|
|
<div className="trophy-portrait" aria-label={`${boss.name} portrait`} role="img">
|
|
<Canvas
|
|
camera={{ position: [3.4, 2.35, 4.8], zoom: 70, near: 0.1, far: 30 }}
|
|
dpr={1}
|
|
frameloop="demand"
|
|
gl={{ alpha: true, antialias: true, powerPreference: "low-power" }}
|
|
orthographic
|
|
>
|
|
<ambientLight intensity={1.9} />
|
|
<directionalLight color="#fff3cf" intensity={3.2} position={[3, 5, 4]} />
|
|
<directionalLight color={boss.accent} intensity={2.1} position={[-4, 2, -2]} />
|
|
<Suspense fallback={null}><PortraitModel bossId={bossId} /></Suspense>
|
|
</Canvas>
|
|
<span aria-hidden="true">{boss.icon}</span>
|
|
</div>
|
|
);
|
|
}
|