forked from phenom/healer-man
82 lines
2.7 KiB
TypeScript
82 lines
2.7 KiB
TypeScript
import { Bounds, useBounds } from "@react-three/drei";
|
|
import { Canvas } from "@react-three/fiber";
|
|
import { useCallback } from "react";
|
|
import type { CharacterDraft, CharacterProfile } from "../app/types";
|
|
import { CharacterModel } from "../avatar/CharacterModel";
|
|
import { ProceduralAvatar } from "../avatar/ProceduralAvatar";
|
|
import { classById, raceById } from "../app/characterCatalog";
|
|
import {
|
|
numericFrameRateLimit,
|
|
type PreviewFrameRateLimit,
|
|
} from "../game/frameRateSettings";
|
|
import { useDocumentVisibility } from "../platform/useDocumentVisibility";
|
|
import { CappedFrameLoop } from "../scene/CappedFrameLoop";
|
|
|
|
function FittedCharacter({
|
|
characterClassColor,
|
|
source,
|
|
turnDegrees,
|
|
}: {
|
|
characterClassColor: string;
|
|
source: CharacterProfile | CharacterDraft;
|
|
turnDegrees: number;
|
|
}) {
|
|
const bounds = useBounds();
|
|
const refit = useCallback(() => {
|
|
bounds.refresh().clip().fit();
|
|
}, [bounds]);
|
|
return (
|
|
<CharacterModel
|
|
identity={source}
|
|
onReady={refit}
|
|
preview
|
|
turnDegrees={turnDegrees}
|
|
fallback={<ProceduralAvatar accent={characterClassColor} />}
|
|
/>
|
|
);
|
|
}
|
|
|
|
export default function CharacterPreview3D({ frameRateLimit, source }: {
|
|
frameRateLimit: PreviewFrameRateLimit;
|
|
source: CharacterProfile | CharacterDraft;
|
|
}) {
|
|
const race = raceById(source.raceId);
|
|
const characterClass = classById(source.classId);
|
|
const turnDegrees = "rotation" in source ? source.rotation : -8;
|
|
const documentVisible = useDocumentVisibility();
|
|
const targetFps = numericFrameRateLimit(frameRateLimit);
|
|
const frameloop = !documentVisible
|
|
? "demand"
|
|
: targetFps === null ? "always" : "never";
|
|
return (
|
|
<div
|
|
className="character-preview__viewport"
|
|
aria-hidden="true"
|
|
data-frame-rate-limit={frameRateLimit}
|
|
data-frame-loop={frameloop}
|
|
>
|
|
<Canvas
|
|
dpr={[1, 1.25]}
|
|
frameloop={frameloop}
|
|
camera={{ fov: 32, near: 0.05, far: 40, position: [3.2, 1.8, 4.6] }}
|
|
gl={{ alpha: true, antialias: true, powerPreference: "default" }}
|
|
>
|
|
{targetFps !== null && frameloop === "never" && (
|
|
<CappedFrameLoop active={documentVisible} targetFps={targetFps} />
|
|
)}
|
|
<ambientLight intensity={1.15} color="#d8e2ca" />
|
|
<hemisphereLight args={["#d8e9ce", "#142019", 1.8]} />
|
|
<directionalLight position={[3, 5, 4]} intensity={2.1} color="#f0e5bf" />
|
|
<directionalLight position={[-4, 2, -3]} intensity={1.1} color={race.accent} />
|
|
<Bounds fit clip observe margin={1.2}>
|
|
<FittedCharacter
|
|
characterClassColor={characterClass.color}
|
|
source={source}
|
|
turnDegrees={turnDegrees}
|
|
/>
|
|
</Bounds>
|
|
</Canvas>
|
|
</div>
|
|
);
|
|
}
|