Download src/video-engine/physics/continuousCanvasState.ts from asnannp/DocDoeAI: direct link, hf CLI and curl.
- Browser
- Download file 5.05 kB
-
https://huggingface.co/spaces/asnannp/DocDoeAI/resolve/main/src/video-engine/physics/continuousCanvasState.ts
- Command line
-
hf download hf://spaces/asnannp/DocDoeAI/src/video-engine/physics/continuousCanvasState.ts
-
curl -L -o continuousCanvasState.ts https://huggingface.co/spaces/asnannp/DocDoeAI/resolve/main/src/video-engine/physics/continuousCanvasState.ts
5.05 kB
| import type { CanvasRegion, VisualAction, VisualBeatGraph } from "./contracts"; | |
| export type CanvasObjectState = { | |
| objectId: string; | |
| kind: "particle_field" | "slinky" | "wave_graph" | "formula" | "numerical" | "note" | "recap" | "text" | "label" | "arrow" | "measurement"; | |
| region: CanvasRegion; | |
| text?: string; | |
| role?: "title" | "explanation" | "definition" | "caption" | "recap"; | |
| equation?: string; | |
| explanation?: string; | |
| tokens?: string[]; | |
| answer?: string; | |
| answerExplanation?: string; | |
| label?: string; | |
| dimmed?: boolean; | |
| highlighted?: boolean; | |
| appearedAtMs: number; | |
| }; | |
| export type ContinuousCanvasState = { | |
| objects: CanvasObjectState[]; | |
| activeBeatIndex: number; | |
| activeBeatProgress: number; | |
| }; | |
| function defaultTextRegion(action: Extract<VisualAction, { type: "write_text" }>): CanvasRegion { | |
| if (action.role === "title") return "title"; | |
| if (action.role === "recap") return "centre"; | |
| return "right"; | |
| } | |
| export function deriveContinuousCanvasState(graph: VisualBeatGraph, timeMs: number): ContinuousCanvasState { | |
| const objects = new Map<string, CanvasObjectState>(); | |
| let activeBeatIndex = graph.beats.findIndex( | |
| (beat) => timeMs >= beat.estimatedStartMs && timeMs < beat.estimatedEndMs, | |
| ); | |
| if (activeBeatIndex < 0) activeBeatIndex = Math.max(0, graph.beats.length - 1); | |
| for (const beat of graph.beats) { | |
| if (beat.estimatedStartMs > timeMs) break; | |
| for (const action of beat.actions) { | |
| applyAction(objects, action, beat.estimatedStartMs); | |
| } | |
| } | |
| const active = graph.beats[activeBeatIndex]; | |
| const activeBeatProgress = active | |
| ? Math.max(0, Math.min(1, (timeMs - active.estimatedStartMs) / (active.estimatedEndMs - active.estimatedStartMs))) | |
| : 0; | |
| return { objects: [...objects.values()], activeBeatIndex, activeBeatProgress }; | |
| } | |
| function applyAction(objects: Map<string, CanvasObjectState>, action: VisualAction, appearedAtMs: number) { | |
| switch (action.type) { | |
| case "add_object": | |
| objects.set(action.objectId, { | |
| objectId: action.objectId, | |
| kind: action.objectType, | |
| region: action.region, | |
| appearedAtMs, | |
| }); | |
| return; | |
| case "remove_object": | |
| objects.delete(action.objectId); | |
| return; | |
| case "clear_region": | |
| for (const [id, object] of objects) { | |
| if (object.region === action.region) objects.delete(id); | |
| } | |
| return; | |
| case "move_object": { | |
| const object = objects.get(action.objectId); | |
| if (object) objects.set(action.objectId, { ...object, region: action.region }); | |
| return; | |
| } | |
| case "write_text": | |
| objects.set(action.objectId, { | |
| objectId: action.objectId, | |
| kind: "text", | |
| region: defaultTextRegion(action), | |
| text: action.text, | |
| role: action.role, | |
| appearedAtMs, | |
| }); | |
| return; | |
| case "write_equation": { | |
| const current = objects.get(action.objectId); | |
| objects.set(action.objectId, { | |
| objectId: action.objectId, | |
| kind: "formula", | |
| region: current?.region ?? "formula", | |
| equation: action.equation, | |
| explanation: action.explanation, | |
| tokens: action.tokens, | |
| appearedAtMs, | |
| }); | |
| return; | |
| } | |
| case "question": { | |
| const current = objects.get(action.objectId); | |
| objects.set(action.objectId, { | |
| objectId: action.objectId, | |
| kind: "numerical", | |
| region: current?.region ?? "centre", | |
| text: action.prompt, | |
| appearedAtMs, | |
| }); | |
| return; | |
| } | |
| case "answer_reveal": { | |
| const current = objects.get(action.objectId); | |
| if (current) { | |
| objects.set(action.objectId, { | |
| ...current, | |
| answer: action.answer, | |
| answerExplanation: action.explanation, | |
| }); | |
| } | |
| return; | |
| } | |
| case "reveal_label": | |
| objects.set(action.objectId, { | |
| objectId: action.objectId, | |
| kind: "label", | |
| region: "centre", | |
| text: action.text, | |
| label: action.targetId, | |
| appearedAtMs, | |
| }); | |
| return; | |
| case "draw_arrow": | |
| objects.set(action.objectId, { | |
| objectId: action.objectId, | |
| kind: "arrow", | |
| region: "centre", | |
| label: action.label, | |
| appearedAtMs, | |
| }); | |
| return; | |
| case "show_measurement": | |
| objects.set(action.objectId, { | |
| objectId: action.objectId, | |
| kind: "measurement", | |
| region: "centre", | |
| label: action.label, | |
| appearedAtMs, | |
| }); | |
| return; | |
| case "highlight": { | |
| const object = objects.get(action.targetId); | |
| if (object) objects.set(action.targetId, { ...object, highlighted: true, dimmed: false }); | |
| return; | |
| } | |
| case "dim": { | |
| const object = objects.get(action.targetId); | |
| if (object) objects.set(action.targetId, { ...object, dimmed: true }); | |
| return; | |
| } | |
| case "focus": { | |
| const object = objects.get(action.targetId); | |
| if (object) objects.set(action.targetId, { ...object, dimmed: false }); | |
| return; | |
| } | |
| default: | |
| return; | |
| } | |
| } | |