From d432da70e95240ad67c400726b8e2685e2a73115 Mon Sep 17 00:00:00 2001 From: Victor Giers Date: Wed, 1 Apr 2026 00:00:49 +0200 Subject: [PATCH] Update tasks.md and implement interaction link creation functions --- .kiro/specs/animation-playback/tasks.md | 2 +- src/interactions/interaction-links.ts | 60 +++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/.kiro/specs/animation-playback/tasks.md b/.kiro/specs/animation-playback/tasks.md index c9801dcb..9f732719 100644 --- a/.kiro/specs/animation-playback/tasks.md +++ b/.kiro/specs/animation-playback/tasks.md @@ -50,7 +50,7 @@ Implement animation playback for imported GLB/GLTF assets in vertical slice orde - Generate calls with empty string in each required field; assert each throws - **Validates: Requirements 4.5** -- [~] 4. Extend the runtime build layer +- [-] 4. Extend the runtime build layer - Add `animationClipName?: string` and `animationAutoplay?: boolean` to `RuntimeModelInstance` in `src/runtime-three/runtime-scene-build.ts` - Update `buildRuntimeModelInstance` to propagate the new fields from the document model instance - Extend `RuntimeInteractionDispatcher` in `src/runtime-three/runtime-interaction-system.ts` with `playAnimation` and `stopAnimation` methods diff --git a/src/interactions/interaction-links.ts b/src/interactions/interaction-links.ts index 459bc1bf..0b33e5ba 100644 --- a/src/interactions/interaction-links.ts +++ b/src/interactions/interaction-links.ts @@ -121,6 +121,53 @@ export function createToggleVisibilityInteractionLink(options: CreateToggleVisib }; } +export interface CreatePlayAnimationInteractionLinkOptions { + id?: string; + sourceEntityId: string; + trigger?: InteractionTriggerKind; + targetModelInstanceId: string; + clipName: string; +} + +export interface CreateStopAnimationInteractionLinkOptions { + id?: string; + sourceEntityId: string; + trigger?: InteractionTriggerKind; + targetModelInstanceId: string; +} + +export function createPlayAnimationInteractionLink(options: CreatePlayAnimationInteractionLinkOptions): InteractionLink { + assertNonEmptyString(options.sourceEntityId, "Interaction source entity id"); + assertNonEmptyString(options.targetModelInstanceId, "Play animation target model instance id"); + assertNonEmptyString(options.clipName, "Play animation clip name"); + + return { + id: options.id ?? createOpaqueId("interaction-link"), + sourceEntityId: options.sourceEntityId, + trigger: options.trigger ?? "enter", + action: { + type: "playAnimation", + targetModelInstanceId: options.targetModelInstanceId, + clipName: options.clipName + } + }; +} + +export function createStopAnimationInteractionLink(options: CreateStopAnimationInteractionLinkOptions): InteractionLink { + assertNonEmptyString(options.sourceEntityId, "Interaction source entity id"); + assertNonEmptyString(options.targetModelInstanceId, "Stop animation target model instance id"); + + return { + id: options.id ?? createOpaqueId("interaction-link"), + sourceEntityId: options.sourceEntityId, + trigger: options.trigger ?? "enter", + action: { + type: "stopAnimation", + targetModelInstanceId: options.targetModelInstanceId + } + }; +} + export function cloneInteractionLink(link: InteractionLink): InteractionLink { return { id: link.id, @@ -147,6 +194,19 @@ export function areInteractionLinksEqual(left: InteractionLink, right: Interacti left.action.targetBrushId === (right.action as ToggleVisibilityAction).targetBrushId && left.action.visible === (right.action as ToggleVisibilityAction).visible ); + case "playAnimation": + return ( + left.action.targetModelInstanceId === (right.action as PlayAnimationAction).targetModelInstanceId && + left.action.clipName === (right.action as PlayAnimationAction).clipName + ); + case "stopAnimation": + return left.action.targetModelInstanceId === (right.action as StopAnimationAction).targetModelInstanceId; + default: { + // Exhaustive check — TypeScript should never reach here + const _exhaustive: never = left.action; + void _exhaustive; + return false; + } } }