From 1515a7c6cf4a9c8daecb01bf16f63bd3e4885143 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9D=D0=B0=D0=B5=D1=80=20=D0=9C=D0=B8=D1=85=D0=B0=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=B8=D1=87?= Date: Thu, 27 Aug 2026 01:07:57 +0300 Subject: [PATCH] fix: merge Memento headset contours --- .../live-expression-preview-test.tsx | 17 ++ .../avatar/components/ExpressionWorkspace.tsx | 44 ++++- .../__tests__/snapshot-exporter-test.ts | 7 +- src/features/export/snapshotExporter.ts | 21 ++- .../__tests__/rendered-scene-test.ts | 15 ++ src/features/rendering/avatarAccessories.ts | 14 ++ .../rendering/components/AvatarCanvas.tsx | 154 ++++++++++++++++++ src/features/rendering/renderedScene.ts | 3 + .../studio/components/AvatarDrawer.tsx | 26 ++- 9 files changed, 293 insertions(+), 8 deletions(-) diff --git a/src/features/avatar/__tests__/live-expression-preview-test.tsx b/src/features/avatar/__tests__/live-expression-preview-test.tsx index 9a5d738..7e05570 100644 --- a/src/features/avatar/__tests__/live-expression-preview-test.tsx +++ b/src/features/avatar/__tests__/live-expression-preview-test.tsx @@ -33,9 +33,23 @@ it('keeps the headset visible and transformed in a live animation preview', () = ) const headset = view.container.querySelector('.avatar-headset') + const head = view.container.querySelector( + 'path[mask="url(#live-preview-animated-headset-headset-occlusion-head-mask)"]' + ) expect(headset).not.toBeNull() + expect(head?.getAttribute('mask')).toBe( + 'url(#live-preview-animated-headset-headset-occlusion-head-mask)' + ) expect(headset?.getAttribute('d')).toBe(geometry.frontPaths.at(-1)) expect(headset?.getAttribute('transform')).toBe(geometry.pathTransforms?.front.at(-1)) + expect(headset?.getAttribute('mask')).toBe( + 'url(#live-preview-animated-headset-headset-occlusion-mask)' + ) + expect( + view.container + .querySelector('#live-preview-animated-headset-headset-occlusion-left-half rect') + ?.getAttribute('width') + ).toBe('573.13') }) it('keeps the stroked Frame 172 skin unfilled in a live animation preview', () => { @@ -72,4 +86,7 @@ it('keeps the stroked Frame 172 skin unfilled in a live animation preview', () = expect(Array.from(eyes).every(eye => eye.style.fill === 'none')).toBe(true) expect(headset.style.fill).toBe('none') expect(headset?.getAttribute('transform')).toBe(geometry.pathTransforms?.front.at(-1)) + expect(headset?.getAttribute('mask')).toBe( + 'url(#live-preview-animated-stroke-headset-headset-occlusion-mask)' + ) }) diff --git a/src/features/avatar/components/ExpressionWorkspace.tsx b/src/features/avatar/components/ExpressionWorkspace.tsx index d81e6c4..75690e1 100644 --- a/src/features/avatar/components/ExpressionWorkspace.tsx +++ b/src/features/avatar/components/ExpressionWorkspace.tsx @@ -40,7 +40,13 @@ import { LivePixelAvatarCanvas, StaticPixelAvatarCanvas, } from '@/features/rendering/components/PixelAvatarCanvas' -import { SvgTransformPath } from '@/features/rendering/components/AvatarCanvas' +import { + headsetHeadOcclusionMaskValue, + headsetOcclusionMaskValue, + LiveHeadsetOcclusionMask, + StaticHeadsetOcclusionMask, + SvgTransformPath, +} from '@/features/rendering/components/AvatarCanvas' import type { RenderedColors, RenderedScene } from '@/features/rendering/renderedScene' export function SurfaceThumbnail({ surface }: { surface: SurfaceConfig }) { const geometry = getPreviewGeometry(defaultExpression, surface, emptyBodyNodes) @@ -125,12 +131,21 @@ export function ExpressionPreview({ ) } const clipId = `preview-${id}` + const headsetOcclusionId = `${clipId}-headset-occlusion` return (