Add free full-body 3D live camera avatar examples
这个提交包含在:
@@ -19,9 +19,11 @@ import {
|
||||
createEmptyStabilizedActionMeta,
|
||||
createStableActionState,
|
||||
drawLiveCameraOverlay,
|
||||
getAvatarPreset,
|
||||
resolveAvatarKeyFromPrompt,
|
||||
stabilizeActionStream,
|
||||
type AvatarKey,
|
||||
type AvatarPreset,
|
||||
type AvatarRenderState,
|
||||
type FrameActionSample,
|
||||
type LiveActionType,
|
||||
@@ -32,6 +34,7 @@ import {
|
||||
Camera,
|
||||
CameraOff,
|
||||
CheckCircle2,
|
||||
ExternalLink,
|
||||
FlipHorizontal,
|
||||
Maximize2,
|
||||
Minus,
|
||||
@@ -1114,7 +1117,10 @@ export default function LiveCamera() {
|
||||
const heroAction = ACTION_META[currentAction];
|
||||
const rawActionMeta = ACTION_META[rawAction];
|
||||
const pendingActionMeta = stabilityMeta.pendingAction ? ACTION_META[stabilityMeta.pendingAction] : null;
|
||||
const resolvedAvatarLabel = AVATAR_PRESETS.find((preset) => preset.key === resolvedAvatarKey)?.label || "猩猩";
|
||||
const resolvedAvatarPreset = getAvatarPreset(resolvedAvatarKey);
|
||||
const resolvedAvatarLabel = resolvedAvatarPreset?.label || "猩猩";
|
||||
const animalAvatarPresets = AVATAR_PRESETS.filter((preset) => preset.category === "animal");
|
||||
const fullBodyAvatarPresets = AVATAR_PRESETS.filter((preset) => preset.category === "full-body-3d");
|
||||
const previewTitle = analyzing
|
||||
? stabilityMeta.pending && pendingActionMeta
|
||||
? `${pendingActionMeta.label} 切换确认中`
|
||||
@@ -1204,6 +1210,78 @@ export default function LiveCamera() {
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderAvatarShowcaseCard = (preset: AvatarPreset) => {
|
||||
const active = resolvedAvatarKey === preset.key;
|
||||
return (
|
||||
<div
|
||||
key={preset.key}
|
||||
className={`overflow-hidden rounded-[22px] border transition ${
|
||||
active
|
||||
? "border-primary/50 bg-primary/5 shadow-lg shadow-primary/10"
|
||||
: "border-border/60 bg-background hover:border-primary/30 hover:bg-muted/40"
|
||||
}`}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setAvatarKey(preset.key);
|
||||
setAvatarEnabled(true);
|
||||
}}
|
||||
className="group block w-full text-left"
|
||||
>
|
||||
<div className="relative aspect-[4/5] overflow-hidden bg-[radial-gradient(circle_at_top,_rgba(255,255,255,0.9),_rgba(226,232,240,0.18)_38%,_rgba(15,23,42,0.92))]">
|
||||
<img
|
||||
src={`/avatars/opensource3d/${
|
||||
preset.key === "beachKing"
|
||||
? "beach-king"
|
||||
: preset.key === "sportTv"
|
||||
? "sport-tv"
|
||||
: preset.key === "juanita3d"
|
||||
? "juanita"
|
||||
: "jenny"
|
||||
}.webp`}
|
||||
alt={preset.label}
|
||||
className="h-full w-full object-contain p-3 transition duration-300 group-hover:scale-[1.03]"
|
||||
loading="lazy"
|
||||
/>
|
||||
<div className="pointer-events-none absolute inset-x-0 bottom-0 h-20 bg-gradient-to-t from-slate-950 via-slate-950/55 to-transparent" />
|
||||
<div className="absolute left-3 top-3">
|
||||
<Badge className="border-white/10 bg-black/60 text-white hover:bg-black/60">3D 全身示例</Badge>
|
||||
</div>
|
||||
{active ? (
|
||||
<div className="absolute right-3 top-3">
|
||||
<Badge className="border-primary/20 bg-primary text-primary-foreground hover:bg-primary">当前使用</Badge>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="space-y-3 p-4">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<div className="text-sm font-semibold">{preset.label}</div>
|
||||
<div className="mt-1 text-xs text-muted-foreground">{preset.collection} · {preset.license}</div>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs leading-5 text-muted-foreground">{preset.description}</p>
|
||||
</div>
|
||||
</button>
|
||||
<div className="flex items-center justify-between gap-3 border-t border-border/60 px-4 py-3">
|
||||
<div className="text-[11px] uppercase tracking-[0.16em] text-muted-foreground">VRM 示例源</div>
|
||||
{preset.modelUrl ? (
|
||||
<a
|
||||
href={preset.modelUrl}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="inline-flex items-center gap-1 text-xs font-medium text-primary"
|
||||
>
|
||||
查看模型
|
||||
<ExternalLink className="h-3.5 w-3.5" />
|
||||
</a>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4 mobile-safe-bottom">
|
||||
<Dialog open={showSetupGuide} onOpenChange={setShowSetupGuide}>
|
||||
@@ -1320,7 +1398,7 @@ export default function LiveCamera() {
|
||||
<div>
|
||||
<h1 className="text-3xl font-semibold tracking-tight">实时分析中枢</h1>
|
||||
<p className="mt-2 max-w-2xl text-sm leading-6 text-white/70">
|
||||
摄像头启动后会持续识别正手、反手、发球、截击、高压、切削、挑高球与未知动作。系统会用 24 帧时间窗口统一动作,再把稳定动作写入片段、训练记录与评分;开启虚拟形象后,画面中的人体会被猩猩、猴子、狗、猪、猫、狐狸、熊猫、狮子、老虎或兔子形象覆盖显示。
|
||||
摄像头启动后会持续识别正手、反手、发球、截击、高压、切削、挑高球与未知动作。系统会用 24 帧时间窗口统一动作,再把稳定动作写入片段、训练记录与评分;开启虚拟形象后,画面中的人体可切换为 10 个轻量动物替身,或 4 个免费的全身 3D Avatar 示例覆盖显示。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1455,7 +1533,7 @@ export default function LiveCamera() {
|
||||
<div>
|
||||
<div className="text-sm font-medium">虚拟形象替换</div>
|
||||
<div className="mt-1 text-xs text-muted-foreground">
|
||||
开启后实时画面会用 10 个免费动物虚拟形象之一覆盖主体,仅影响前端叠加显示,不改变动作识别与原视频归档。
|
||||
开启后实时画面可使用 10 个免费动物替身,或 4 个免费的全身 3D Avatar 示例覆盖主体。当前只影响前端叠加显示,不改变动作识别与原视频归档。
|
||||
</div>
|
||||
</div>
|
||||
<Switch
|
||||
@@ -1467,7 +1545,7 @@ export default function LiveCamera() {
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
当前映射:{resolvedAvatarLabel}
|
||||
{avatarPrompt.trim() ? ` · 输入 ${avatarPrompt.trim()}` : " · 可输入猩猩、狐狸、熊猫、兔子等别名自动映射"}
|
||||
{avatarPrompt.trim() ? ` · 输入 ${avatarPrompt.trim()}` : " · 可输入猩猩、狐狸、熊猫、兔子,或 BeachKing、Juanita 等别名自动映射"}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
@@ -1477,9 +1555,12 @@ export default function LiveCamera() {
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{AVATAR_PRESETS.map((preset) => (
|
||||
{animalAvatarPresets.map((preset) => (
|
||||
<SelectItem key={preset.key} value={preset.key}>{preset.label}</SelectItem>
|
||||
))}
|
||||
{fullBodyAvatarPresets.map((preset) => (
|
||||
<SelectItem key={preset.key} value={preset.key}>{preset.label} · 3D</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
@@ -1488,11 +1569,25 @@ export default function LiveCamera() {
|
||||
<Input
|
||||
value={avatarPrompt}
|
||||
onChange={(event) => setAvatarPrompt(event.target.value)}
|
||||
placeholder="例如 狐狸 / panda coach / dog mascot"
|
||||
placeholder="例如 狐狸 / panda coach / BeachKing / Juanita"
|
||||
className="h-12 rounded-2xl border-border/60"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 rounded-[24px] border border-border/60 bg-background/80 p-4">
|
||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||
<div>
|
||||
<div className="text-sm font-medium">免费 3D 全身范例</div>
|
||||
<div className="mt-1 text-xs text-muted-foreground">
|
||||
这 4 个示例来自 Open Source Avatars 的 CC0 集合,当前已处理成轻量透明素材用于实时覆盖;后续若切换到 VRM/three-vrm,可继续沿用同一批模型源。
|
||||
</div>
|
||||
</div>
|
||||
<Badge variant="secondary" className="rounded-full px-3 py-1 text-xs">CC0 · Open Source Avatars</Badge>
|
||||
</div>
|
||||
<div className="mt-4 grid gap-3 sm:grid-cols-2 xl:grid-cols-4">
|
||||
{fullBodyAvatarPresets.map(renderAvatarShowcaseCard)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
在新工单中引用
屏蔽一个用户