전 저장소 리팩터링과 SSOT 정비
This commit is contained in:
parent
14ecbd4e7d
commit
3dfddcac6f
173 changed files with 19679 additions and 6952 deletions
|
|
@ -35,7 +35,6 @@ import { Brows } from "./Brows";
|
|||
import { Mouth } from "./Mouth";
|
||||
import { live2dModel3Path, live2dModelForPersonaCode, live2dMotionForExpression } from "./live2dModel";
|
||||
import { useExpressionTransition } from "./useExpressionTransition";
|
||||
import { RasterBust } from "./RasterBust";
|
||||
import "./client-avatar.css";
|
||||
|
||||
/* ── 공개 타입 재노출 (기존 import 경로 호환) ──────────────────────────
|
||||
|
|
@ -232,7 +231,6 @@ export function ClientAvatar({
|
|||
const outfitColor = persona.outfitColor ?? hairColor;
|
||||
const irisColor = persona.eyeColor ?? "#2B2B2B";
|
||||
const realism = Math.min(0.45, Math.max(0.3, persona.realism)); // 안전 클램프
|
||||
const useRaster = Boolean(persona.rasterArtSet);
|
||||
const age = ageLookFor(persona.ageBand);
|
||||
const expressionLabel = expressionLabelFor(affect);
|
||||
const live2dModel = useMemo(() => live2dModelForPersonaCode(persona.code), [persona.code]);
|
||||
|
|
@ -295,7 +293,7 @@ export function ClientAvatar({
|
|||
data-live2d-expression-count={live2dModel.expressions.length}
|
||||
data-persona-code={persona.code ?? ""}
|
||||
data-avatar-animated={motionEnabled ? "true" : "false"}
|
||||
data-render-mode={useRaster ? "raster" : "svg"}
|
||||
data-render-mode="svg"
|
||||
data-realism={realism} /* 사실성은 데이터로만 유지(시각 표식 비노출, §4.6) */
|
||||
aria-label={`교육용 가상 내담자: ${persona.label}, ${STATE_TEXT[state]}, ${expressionLabel}`}
|
||||
>
|
||||
|
|
@ -319,28 +317,14 @@ export function ClientAvatar({
|
|||
reduced={reduced}
|
||||
/>
|
||||
|
||||
{useRaster ? (
|
||||
<RasterBust
|
||||
artSet={persona.rasterArtSet as string}
|
||||
affect={affect}
|
||||
state={state}
|
||||
breath={breath}
|
||||
blink={blink}
|
||||
mouth={mouthOpen}
|
||||
gazeX={gazeX}
|
||||
gazeY={gazeY}
|
||||
headTilt={params.headTilt}
|
||||
reduced={reduced}
|
||||
/>
|
||||
) : (
|
||||
<svg
|
||||
className="vg-avatar__svg"
|
||||
viewBox="0 0 200 200"
|
||||
width={size}
|
||||
height={size}
|
||||
role="img"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<svg
|
||||
className="vg-avatar__svg"
|
||||
viewBox="0 0 200 200"
|
||||
width={size}
|
||||
height={size}
|
||||
role="img"
|
||||
aria-hidden="true"
|
||||
>
|
||||
{/* 흉상 그룹: 어깨 호흡(translateY) + 저항 시 미세 회전 */}
|
||||
<g transform={`translate(0 ${-breath}) rotate(${shoulderRotate} 100 150)`}>
|
||||
{/* 어깨/상반신 실루엣 */}
|
||||
|
|
@ -381,8 +365,7 @@ export function ClientAvatar({
|
|||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
)}
|
||||
</svg>
|
||||
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -43,6 +43,18 @@ const PERSONA_GENERATED_ART_SETS = [
|
|||
];
|
||||
const PSB_GROUP_ART_SETS = new Set(["seoyeon-live2d-psb", "seoyeon-live2d-psd-v2", ...PERSONA_GENERATED_ART_SETS]);
|
||||
const PSB_CRYING_ART_SETS = new Set(["seoyeon-live2d-psd-v2", ...PERSONA_GENERATED_ART_SETS]);
|
||||
const EYE_DETAIL_LAYERS: ReadonlyArray<{
|
||||
name: string;
|
||||
mask: string;
|
||||
zIndex: number;
|
||||
}> = [
|
||||
{ name: "iris-left", mask: "eye-white-left", zIndex: 9 },
|
||||
{ name: "iris-right", mask: "eye-white-right", zIndex: 9 },
|
||||
{ name: "pupil-left", mask: "eye-white-left", zIndex: 10 },
|
||||
{ name: "pupil-right", mask: "eye-white-right", zIndex: 10 },
|
||||
{ name: "highlight-left", mask: "eye-white-left", zIndex: 11 },
|
||||
{ name: "highlight-right", mask: "eye-white-right", zIndex: 11 },
|
||||
];
|
||||
|
||||
export function rasterVariantFor(expression: AvatarExpression): RasterVariant {
|
||||
return VARIANT_FOR_EXPRESSION[expression];
|
||||
|
|
@ -294,14 +306,6 @@ export function RasterBust({
|
|||
const browLeftPart = useSadCryingParts ? "brow-sad-left" : "brow-left";
|
||||
const browRightPart = useSadCryingParts ? "brow-sad-right" : "brow-right";
|
||||
const tearOpacity = useSadCryingParts ? clamp01(0.82 + openEyes * 0.18 - closedEyes * 0.35) : 0;
|
||||
const eyeDetailLayers: Array<{ name: string; mask: string; zIndex: number }> = [
|
||||
{ name: "iris-left", mask: "eye-white-left", zIndex: 9 },
|
||||
{ name: "iris-right", mask: "eye-white-right", zIndex: 9 },
|
||||
{ name: "pupil-left", mask: "eye-white-left", zIndex: 10 },
|
||||
{ name: "pupil-right", mask: "eye-white-right", zIndex: 10 },
|
||||
{ name: "highlight-left", mask: "eye-white-left", zIndex: 11 },
|
||||
{ name: "highlight-right", mask: "eye-white-right", zIndex: 11 },
|
||||
];
|
||||
const renderLayer = (
|
||||
name: string,
|
||||
className: string,
|
||||
|
|
@ -395,7 +399,7 @@ export function RasterBust({
|
|||
...eyeApertureClipStyle(name.endsWith("left") ? "left" : "right", upperLidY, lowerLidY, closedEyes),
|
||||
}),
|
||||
)}
|
||||
{eyeDetailLayers.map(({ name, mask, zIndex }) =>
|
||||
{EYE_DETAIL_LAYERS.map(({ name, mask, zIndex }) =>
|
||||
renderLayer(name, "vg-raster__layer--eyes", zIndex, {
|
||||
opacity: openEyes,
|
||||
transform: layerTransform(
|
||||
|
|
@ -526,14 +530,6 @@ export function RasterBust({
|
|||
const hairSway = reduced ? 0 : breath * 0.24 + headTilt * 0.18;
|
||||
const brow = browPoseForVariant(variant);
|
||||
const expressionMouth = mouthForVariant(variant);
|
||||
const eyeDetailLayers: Array<{ name: string; mask: string; zIndex: number }> = [
|
||||
{ name: "iris-left", mask: "eye-white-left", zIndex: 9 },
|
||||
{ name: "iris-right", mask: "eye-white-right", zIndex: 9 },
|
||||
{ name: "pupil-left", mask: "eye-white-left", zIndex: 10 },
|
||||
{ name: "pupil-right", mask: "eye-white-right", zIndex: 10 },
|
||||
{ name: "highlight-left", mask: "eye-white-left", zIndex: 11 },
|
||||
{ name: "highlight-right", mask: "eye-white-right", zIndex: 11 },
|
||||
];
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
@ -626,7 +622,7 @@ export function RasterBust({
|
|||
style={{ opacity: openEyes, zIndex: name.startsWith("lash") ? 12 : 9, transform: layerTransform() }}
|
||||
/>
|
||||
))}
|
||||
{eyeDetailLayers.map(
|
||||
{EYE_DETAIL_LAYERS.map(
|
||||
({ name, mask, zIndex }) => (
|
||||
<img
|
||||
className="vg-raster__layer vg-raster__layer--eyes"
|
||||
|
|
|
|||
|
|
@ -118,12 +118,6 @@ export interface AvatarPersona {
|
|||
eyeColor?: string;
|
||||
/** 캐릭터 고유 보조색 */
|
||||
accentColor?: string;
|
||||
/**
|
||||
* 래스터(비트맵) 아트셋 코드. 지정하면 SVG 파라미터 리그 대신
|
||||
* `/avatar/<artSet>/<variant>.png` 레이어 합성(Live2D식)으로 렌더링한다.
|
||||
* 미지정 시 기존 SVG 리그. asset 은 imagegen(gpt-image-2)+BiRefNet 누끼로 생성.
|
||||
*/
|
||||
rasterArtSet?: string;
|
||||
/** 사실성 0.35~0.45 고정 (불쾌한 골짜기 회피) */
|
||||
realism: number;
|
||||
/**
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue