From 902fdd5adc214040f61b24110df6468d2601c0c7 Mon Sep 17 00:00:00 2001 From: FromTheLOLAshes Date: Sun, 31 Mar 2024 08:36:46 +0700 Subject: [PATCH 01/22] Add play clip --- .../PageEnigma/comps/Timeline/TrackClip.tsx | 53 +++++++++++++++---- 1 file changed, 43 insertions(+), 10 deletions(-) diff --git a/app/pages/PageEnigma/comps/Timeline/TrackClip.tsx b/app/pages/PageEnigma/comps/Timeline/TrackClip.tsx index de24749444..e494003304 100644 --- a/app/pages/PageEnigma/comps/Timeline/TrackClip.tsx +++ b/app/pages/PageEnigma/comps/Timeline/TrackClip.tsx @@ -1,14 +1,19 @@ import { useMouseEventsClip } from "./utils/useMouseEventsClip"; -import { BaseClip } from "~/pages/PageEnigma/models/track"; import { TrackContext } from "~/pages/PageEnigma/contexts/TrackContext/TrackContext"; -import { useContext, useState } from "react"; +import { useCallback, useContext, useState } from "react"; import { scale } from "~/pages/PageEnigma/store"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faPlay } from "@fortawesome/pro-solid-svg-icons"; +import { Clip } from "~/pages/PageEnigma/models/track"; +import Queue from "~/pages/PageEnigma/Queue/Queue"; +import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames"; +import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions"; interface Props { min: number; max: number; style: "character" | "camera" | "audio" | "objects"; - clip: BaseClip; + clip: Clip; updateClip: (options: { id: string; offset: number; length: number }) => void; } @@ -28,9 +33,24 @@ export const TrackClip = ({ clip, min, max, style, updateClip }: Props) => { const { length, offset } = state; + const onPlayClick = useCallback( + (event: React.MouseEvent) => { + event.stopPropagation(); + event.preventDefault(); + Queue.publish({ + queueName: QueueNames.TO_ENGINE, + action: toEngineActions.PLAY_CLIP, + data: clip, + }); + }, + [clip], + ); + const classes = [ "absolute", - clip.id === selectedClip ? `bg-${style}-selected` : `bg-${style}-clip`, + clip.clip_uuid === selectedClip + ? `bg-${style}-selected` + : `bg-${style}-clip`, ]; return ( @@ -51,7 +71,7 @@ export const TrackClip = ({ clip, min, max, style, updateClip }: Props) => { ...classes, "rounded-l-lg", "block h-full", - clip.id === selectedClip + clip.clip_uuid === selectedClip ? "border border-b-2 border-l-2 border-r-0 border-t-2 border-white focus-visible:outline-0" : "", ].join(" ")} @@ -61,13 +81,13 @@ export const TrackClip = ({ clip, min, max, style, updateClip }: Props) => { cursor: "w-resize", }} onPointerDown={(event) => onPointerDown(event, "left")} - onClick={() => selectClip(clip.id)} + onClick={() => selectClip(clip.clip_uuid)} /> + )} ); }; From 786fccd303d1003d96bee21e861f637bc8536c71 Mon Sep 17 00:00:00 2001 From: FromTheLOLAshes Date: Sun, 31 Mar 2024 10:26:59 +0700 Subject: [PATCH 02/22] Start keyframes --- app/pages/PageEnigma/Queue/toEngineActions.ts | 2 + .../PageEnigma/comps/Timeline/Character.tsx | 7 +- .../PageEnigma/comps/Timeline/Timeline.tsx | 14 +-- .../PageEnigma/comps/Timeline/TrackClip.tsx | 18 ++-- .../comps/Timeline/TrackKeyFrame.tsx | 97 ++++++------------- .../comps/Timeline/TrackKeyFrames.tsx | 81 +--------------- .../Timeline/utils/useMouseEventsKeyframe.ts | 84 ++++++++++++++++ .../comps/Timeline/utils/useQueueHandler.ts | 25 ++++- .../contexts/TrackContext/TrackContext.tsx | 19 ++-- .../contexts/TrackContext/TrackProvider.tsx | 12 +-- .../TrackContext/utils/useUpdateKeyframe.ts | 17 ++++ .../TrackContext/utils/useUpdateObject.ts | 29 +++--- app/pages/PageEnigma/models/track.ts | 21 ++-- 13 files changed, 223 insertions(+), 203 deletions(-) create mode 100644 app/pages/PageEnigma/comps/Timeline/utils/useMouseEventsKeyframe.ts create mode 100644 app/pages/PageEnigma/contexts/TrackContext/utils/useUpdateKeyframe.ts diff --git a/app/pages/PageEnigma/Queue/toEngineActions.ts b/app/pages/PageEnigma/Queue/toEngineActions.ts index 867730e006..4f695a239c 100644 --- a/app/pages/PageEnigma/Queue/toEngineActions.ts +++ b/app/pages/PageEnigma/Queue/toEngineActions.ts @@ -15,4 +15,6 @@ export enum toEngineActions { // data=QueueClip UPDATE_TIME = "update_time", // data={currentTime: number} + ADD_KEYFRAME = "add_keyframe", + // data= QueueKeyframe } diff --git a/app/pages/PageEnigma/comps/Timeline/Character.tsx b/app/pages/PageEnigma/comps/Timeline/Character.tsx index a6d6fadd0e..6501221d2f 100644 --- a/app/pages/PageEnigma/comps/Timeline/Character.tsx +++ b/app/pages/PageEnigma/comps/Timeline/Character.tsx @@ -2,6 +2,7 @@ import { useCallback, useContext, useMemo } from "react"; import { TrackContext } from "~/pages/PageEnigma/contexts/TrackContext/TrackContext"; import { TrackClips } from "~/pages/PageEnigma/comps/Timeline/TrackClips"; import { fullWidth } from "~/pages/PageEnigma/store"; +import { TrackKeyFrames } from "~/pages/PageEnigma/comps/Timeline/TrackKeyFrames"; function buildUpdaters( updateCharacters: (options: { @@ -52,7 +53,7 @@ export const Character = ({ characterId }: Props) => { if (!character) { return false; } - const { animationClips, positionClips, lipSyncClips } = character; + const { animationClips, positionKeyframes, lipSyncClips } = character; return (
{ style="character" type="animations" /> - { const { characters, objects, - selectedClip, + selectedItem, deleteCharacterClip, deleteAudioClip, deleteCameraClip, @@ -43,20 +43,20 @@ export const Timeline = () => { const onDeleteAsk = useCallback( (event: KeyboardEvent) => { - if (["Backspace", "Delete"].indexOf(event.key) > -1 && selectedClip) { + if (["Backspace", "Delete"].indexOf(event.key) > -1 && selectedItem) { setDialogOpen(true); } }, - [selectedClip], + [selectedItem], ); const displayTime = time === -1 ? currentTime.value : time; const onDelete = useCallback(() => { - deleteCharacterClip(selectedClip!); - deleteCameraClip(selectedClip!); - deleteAudioClip(selectedClip!); - }, [selectedClip, deleteAudioClip, deleteCharacterClip, deleteCameraClip]); + deleteCharacterClip(selectedItem!); + deleteCameraClip(selectedItem!); + deleteAudioClip(selectedItem!); + }, [selectedItem, deleteAudioClip, deleteCharacterClip, deleteCameraClip]); useEffect(() => { document.addEventListener("keydown", onDeleteAsk); diff --git a/app/pages/PageEnigma/comps/Timeline/TrackClip.tsx b/app/pages/PageEnigma/comps/Timeline/TrackClip.tsx index e494003304..5747a8509f 100644 --- a/app/pages/PageEnigma/comps/Timeline/TrackClip.tsx +++ b/app/pages/PageEnigma/comps/Timeline/TrackClip.tsx @@ -18,7 +18,7 @@ interface Props { } export const TrackClip = ({ clip, min, max, style, updateClip }: Props) => { - const { selectClip, selectedClip } = useContext(TrackContext); + const { selectItem, selectedItem } = useContext(TrackContext); const [state, setState] = useState({ length: clip.length, offset: clip.offset, @@ -48,7 +48,7 @@ export const TrackClip = ({ clip, min, max, style, updateClip }: Props) => { const classes = [ "absolute", - clip.clip_uuid === selectedClip + clip.clip_uuid === selectedItem ? `bg-${style}-selected` : `bg-${style}-clip`, ]; @@ -71,7 +71,7 @@ export const TrackClip = ({ clip, min, max, style, updateClip }: Props) => { ...classes, "rounded-l-lg", "block h-full", - clip.clip_uuid === selectedClip + clip.clip_uuid === selectedItem ? "border border-b-2 border-l-2 border-r-0 border-t-2 border-white focus-visible:outline-0" : "", ].join(" ")} @@ -81,13 +81,13 @@ export const TrackClip = ({ clip, min, max, style, updateClip }: Props) => { cursor: "w-resize", }} onPointerDown={(event) => onPointerDown(event, "left")} - onClick={() => selectClip(clip.clip_uuid)} + onClick={() => selectItem(clip.clip_uuid)} /> ); }; diff --git a/app/pages/PageEnigma/comps/Timeline/TrackKeyFrames.tsx b/app/pages/PageEnigma/comps/Timeline/TrackKeyFrames.tsx index 950c0daedd..d0befa2ee7 100644 --- a/app/pages/PageEnigma/comps/Timeline/TrackKeyFrames.tsx +++ b/app/pages/PageEnigma/comps/Timeline/TrackKeyFrames.tsx @@ -1,24 +1,12 @@ -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faVolume, faVolumeSlash } from "@fortawesome/pro-solid-svg-icons"; -import { BaseKeyFrame } from "~/pages/PageEnigma/models/track"; -import { PointerEvent } from "react"; -import { - canDrop, - clipLength, - dragType, - dropId, - dropOffset, - filmLength, - scale, -} from "~/pages/PageEnigma/store"; +import { KeyFrame } from "~/pages/PageEnigma/models/track"; +import { filmLength } from "~/pages/PageEnigma/store"; import { TrackKeyFrame } from "~/pages/PageEnigma/comps/Timeline/TrackKeyFrame"; interface Props { id: string; - keyFrames: BaseKeyFrame[]; + keyFrames: KeyFrame[]; title: string; style: "character" | "audio" | "camera" | "objects"; - type?: "animations" | "positions" | "lipSync"; toggleMute?: () => void; muted?: boolean; updateClip: (options: { id: string; length: number; offset: number }) => void; @@ -27,62 +15,19 @@ interface Props { export const TrackKeyFrames = ({ id, keyFrames, - toggleMute, updateClip, - muted, title, style, - type, }: Props) => { - const trackType = type ?? style; - - function onPointerOver() { - if (dragType.value !== trackType) { - return; - } - dropId.value = id; - } - - function onPointerMove(event: PointerEvent) { - if (dragType.value !== trackType) { - return; - } - const track = document.getElementById(`track-${trackType}-${id}`); - if (!track) { - return; - } - - // Now check the the clip fits - const position = track.getBoundingClientRect(); - const clipOffset = (event.clientX - position.x) / 4 / scale.value; - - if (clipOffset + clipLength.value > filmLength.value * 60) { - canDrop.value = false; - return; - } - - canDrop.value = true; - dropOffset.value = clipOffset; - } - function onPointerLeave() { - if (dragType.value !== trackType) { - return; - } - canDrop.value = false; - } - return (
{keyFrames.map((keyFrame, index) => ( 0 ? keyFrames[index - 1].offset + 1 : 0} max={ index < keyFrames.length - 1 @@ -97,22 +42,6 @@ export const TrackKeyFrames = ({
{title}
- {!!toggleMute && ( - - )}
); diff --git a/app/pages/PageEnigma/comps/Timeline/utils/useMouseEventsKeyframe.ts b/app/pages/PageEnigma/comps/Timeline/utils/useMouseEventsKeyframe.ts new file mode 100644 index 0000000000..a1f30aee1f --- /dev/null +++ b/app/pages/PageEnigma/comps/Timeline/utils/useMouseEventsKeyframe.ts @@ -0,0 +1,84 @@ +import React, { Dispatch, useCallback, useEffect, useRef } from "react"; +import { KeyFrame } from "~/pages/PageEnigma/models/track"; +import { canDrop, scale } from "~/pages/PageEnigma/store"; +import { useSignals } from "@preact/signals-react/runtime"; + +export const useMouseEventsKeyframe = ({ + keyframe, + max, + min, + updateKeyframe, + setOffset, +}: { + keyframe: KeyFrame; + max: number; + min: number; + updateKeyframe: (args: { id: string; offset: number }) => void; + setOffset: Dispatch; +}) => { + useSignals(); + const currOffset = useRef(keyframe.offset); + const initOffset = useRef(keyframe.offset); + const isActive = useRef(""); + const clientX = useRef(0); + + const onPointerUp = useCallback(() => { + if (isActive.current) { + updateKeyframe({ + id: keyframe.keyframe_uuid, + offset: Math.round(currOffset.current), + }); + isActive.current = ""; + canDrop.value = false; + } + }, [updateKeyframe, keyframe.keyframe_uuid]); + + const onMouseMove = useCallback( + (event: MouseEvent) => { + const delta = (event.clientX - clientX.current) / 4 / scale.value; + const deltaOffset = delta + initOffset.current; + if (isActive.current === "drag") { + if (deltaOffset < min) { + currOffset.current = min; + return; + } + if (deltaOffset > max) { + currOffset.current = max; + return; + } + currOffset.current = deltaOffset; + } + setOffset(currOffset.current); + }, + [max, min, setOffset], + ); + + useEffect(() => { + currOffset.current = keyframe.offset; + initOffset.current = keyframe.offset; + }, [keyframe.offset]); + + useEffect(() => { + window.addEventListener("pointerup", onPointerUp); + window.addEventListener("pointermove", onMouseMove); + + return () => { + window.removeEventListener("pointerup", onPointerUp); + window.removeEventListener("pointermove", onMouseMove); + }; + }, [onPointerUp, onMouseMove]); + + return { + onPointerDown: ( + event: React.PointerEvent, + type: string, + ) => { + if (event.button === 0) { + event.stopPropagation(); + event.preventDefault(); + clientX.current = event.clientX; + isActive.current = type; + } + }, + }; +}; diff --git a/app/pages/PageEnigma/comps/Timeline/utils/useQueueHandler.ts b/app/pages/PageEnigma/comps/Timeline/utils/useQueueHandler.ts index 02e96793ca..0190377889 100644 --- a/app/pages/PageEnigma/comps/Timeline/utils/useQueueHandler.ts +++ b/app/pages/PageEnigma/comps/Timeline/utils/useQueueHandler.ts @@ -1,11 +1,12 @@ import { fromEngineActions } from "~/pages/PageEnigma/Queue/fromEngineActions"; import { useSignals } from "@preact/signals-react/runtime"; -import { useCallback, useEffect } from "react"; +import { useCallback, useContext, useEffect } from "react"; import { currentTime } from "~/pages/PageEnigma/store"; import Queue from "~/pages/PageEnigma/Queue/Queue"; import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames"; import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions"; import { QueueClip } from "~/pages/PageEnigma/models/track"; +import { TrackContext } from "~/pages/PageEnigma/contexts/TrackContext/TrackContext"; interface UpdateTime { currentTime: number; @@ -18,8 +19,10 @@ interface Arguments { export function useQueueHandler() { useSignals(); - const handleActions = useCallback(({ action, data }: Arguments) => { - console.log(`FROM ENGINE:${action} ${data}`) + const { addKeyframe } = useContext(TrackContext); + + const handleFromEngineActions = useCallback(({ action, data }: Arguments) => { + console.log(`FROM ENGINE:${action} ${data}`); switch (action) { case fromEngineActions.UPDATE_TIME: currentTime.value = (data as UpdateTime).currentTime; @@ -29,7 +32,19 @@ export function useQueueHandler() { } }, []); + const handleToTimelineActions = useCallback(({ action, data }: Arguments) => { + console.log(`FROM ENGINE:${action} ${data}`); + switch (action) { + case toTimelineActions.ADD_KEYFRAME: + addKeyframe(data); + break; + default: + throw new Error(`Unknown action ${action}`); + } + }, []); + useEffect(() => { - Queue.subscribe(QueueNames.FROM_ENGINE, handleActions); - }, [handleActions]); + Queue.subscribe(QueueNames.FROM_ENGINE, handleFromEngineActions); + Queue.subscribe(QueueNames.TO_TIMELINE, handleToTimelineActions); + }, [handleFromEngineActions, handleToTimelineActions]); } diff --git a/app/pages/PageEnigma/contexts/TrackContext/TrackContext.tsx b/app/pages/PageEnigma/contexts/TrackContext/TrackContext.tsx index f761df637e..48b2d2429e 100644 --- a/app/pages/PageEnigma/contexts/TrackContext/TrackContext.tsx +++ b/app/pages/PageEnigma/contexts/TrackContext/TrackContext.tsx @@ -4,8 +4,8 @@ import { CameraGroup, CharacterGroup, ObjectGroup, - ObjectTrack, MediaClip, + KeyFrame, } from "~/pages/PageEnigma/models/track"; export const TrackContext = createContext<{ @@ -62,11 +62,14 @@ export const TrackContext = createContext<{ // timeline objects group objects: ObjectGroup; updateObject: (options: { id: string; offset: number }) => void; - addObject: (options: ObjectTrack) => void; // current - only select one item - will be replaced - selectedClip: string | null; - selectClip: (clipId: string) => void; + selectedItem: string | null; + selectItem: (itemId: string) => void; + + // keyframes + addKeyframe: (options: { object_uuid: string }) => void; + deleteKeyframe: (options: KeyFrame) => void; // sidebar clips animationClips: MediaClip[]; @@ -102,10 +105,12 @@ export const TrackContext = createContext<{ objects: { id: "", objects: [] }, updateObject: () => {}, - addObject: () => {}, - selectedClip: null, - selectClip: () => {}, + addKeyframe: () => {}, + deleteKeyframe: () => {}, + + selectedItem: null, + selectItem: () => {}, animationClips: [], audioClips: [], diff --git a/app/pages/PageEnigma/contexts/TrackContext/TrackProvider.tsx b/app/pages/PageEnigma/contexts/TrackContext/TrackProvider.tsx index ee8b41971a..15d7fa7b1c 100644 --- a/app/pages/PageEnigma/contexts/TrackContext/TrackProvider.tsx +++ b/app/pages/PageEnigma/contexts/TrackContext/TrackProvider.tsx @@ -27,8 +27,8 @@ export const TrackProvider = ({ children }: Props) => { const { endDrag, ...dragDrop } = useUpdateDragDrop(); - const [selectedClip, setSelectedClip] = useState(null); - const selectClip = useCallback((clipId: string | null) => { + const [selectedItem, setSelectedClip] = useState(null); + const selectItem = useCallback((clipId: string | null) => { setSelectedClip(clipId); }, []); @@ -115,8 +115,8 @@ export const TrackProvider = ({ children }: Props) => { ...audio, ...objects, - selectClip, - selectedClip, + selectItem, + selectedItem, ...dragDrop, endDrag: dropClip, @@ -138,8 +138,8 @@ export const TrackProvider = ({ children }: Props) => { audio, objects, - selectClip, - selectedClip, + selectItem, + selectedItem, dragDrop, dropClip, diff --git a/app/pages/PageEnigma/contexts/TrackContext/utils/useUpdateKeyframe.ts b/app/pages/PageEnigma/contexts/TrackContext/utils/useUpdateKeyframe.ts new file mode 100644 index 0000000000..41e5c4279f --- /dev/null +++ b/app/pages/PageEnigma/contexts/TrackContext/utils/useUpdateKeyframe.ts @@ -0,0 +1,17 @@ +import { useCallback } from "react"; +import { ObjectTrack } from "~/pages/PageEnigma/models/track"; + +export default function useUpdateKeyframe() { + const addKeyframe = useCallback((obj: ObjectTrack) => { + setObjects((oldObjectGroup) => { + return { + ...oldObjectGroup, + objects: [...oldObjectGroup.objects, obj], + }; + }); + }, []); + + return { + addKeyframe, + }; +} diff --git a/app/pages/PageEnigma/contexts/TrackContext/utils/useUpdateObject.ts b/app/pages/PageEnigma/contexts/TrackContext/utils/useUpdateObject.ts index 55f9a71b0d..06398521be 100644 --- a/app/pages/PageEnigma/contexts/TrackContext/utils/useUpdateObject.ts +++ b/app/pages/PageEnigma/contexts/TrackContext/utils/useUpdateObject.ts @@ -1,21 +1,11 @@ import { useCallback, useState } from "react"; import { ObjectGroup, ObjectTrack } from "~/pages/PageEnigma/models/track"; +import * as uuid from "uuid"; export default function useUpdateObject() { const [objects, setObjects] = useState({ id: "OB1", - objects: [ - // { - // id: "OB1-O1", - // keyFrames: [ - // { - // id: "OB1-O1-1", - // offset: 0, - // name: "obj 1", - // }, - // ], - // }, - ], + objects: [], }); const updateObject = useCallback( @@ -27,7 +17,7 @@ export default function useUpdateObject() { id: object.id, keyFrames: object.keyFrames.map((keyFrame) => ({ ...keyFrame, - offset: keyFrame.id === id ? offset : keyFrame.offset, + offset: keyFrame.keyframe_uuid === id ? offset : keyFrame.offset, })), })), }; @@ -36,6 +26,18 @@ export default function useUpdateObject() { [], ); + const addObject = useCallback((name: string) => { + setObjects((oldObjectGroup) => { + return { + ...oldObjectGroup, + objects: [ + ...oldObjectGroup.objects, + { object_uuid: uuid.v4(), name: name, keyframes: [] }, + ], + }; + }); + }, []); + const addObject = useCallback((obj: ObjectTrack) => { setObjects((oldObjectGroup) => { return { @@ -49,5 +51,6 @@ export default function useUpdateObject() { objects, updateObject, addObject, + addKeyframe, }; } diff --git a/app/pages/PageEnigma/models/track.ts b/app/pages/PageEnigma/models/track.ts index dd290db8c6..f8ce1194e9 100644 --- a/app/pages/PageEnigma/models/track.ts +++ b/app/pages/PageEnigma/models/track.ts @@ -1,3 +1,5 @@ +import { XYZ } from "../datastructures/common"; + export interface QueueClip { version: number; type: ClipType; @@ -45,10 +47,15 @@ export enum ClipGroup { OBJECT = "object", } -export interface BaseKeyFrame { - id: string; - name: string; +export interface KeyFrame { + version: number; + keyframe_uuid: string; + group: ClipGroup; + object_uuid?: string; offset: number; + position: XYZ; + rotation: XYZ; + scale: XYZ; selected?: boolean; } @@ -56,13 +63,13 @@ export interface CharacterGroup { id: string; muted: boolean; animationClips: Clip[]; - positionClips: Clip[]; + positionKeyframes: KeyFrame[]; lipSyncClips: Clip[]; } export interface CameraGroup { id: string; - clips: Clip[]; + clips: KeyFrame[]; } export interface AudioGroup { @@ -78,7 +85,5 @@ export interface ObjectGroup { export interface ObjectTrack { id: string; - keyFrames: ObjectKeyFrame[]; + keyFrames: KeyFrame[]; } - -export interface ObjectKeyFrame extends BaseKeyFrame {} From 053c9aae8a95623f44ac11ee4a42f97b967d8ab2 Mon Sep 17 00:00:00 2001 From: FromTheLOLAshes Date: Sun, 31 Mar 2024 15:48:37 +0700 Subject: [PATCH 03/22] Add keyframes --- app/pages/PageEnigma/Queue/Queue.ts | 17 +- app/pages/PageEnigma/Queue/toEngineActions.ts | 10 +- .../PageEnigma/comps/Timeline/Camera.tsx | 10 +- .../PageEnigma/comps/Timeline/Character.tsx | 16 +- .../PageEnigma/comps/Timeline/Objects.tsx | 8 +- .../comps/Timeline/TrackKeyFrame.tsx | 4 +- .../comps/Timeline/TrackKeyFrames.tsx | 24 +- .../Timeline/utils/useMouseEventsKeyframe.ts | 4 +- .../comps/Timeline/utils/useQueueHandler.ts | 17 +- .../contexts/TrackContext/TrackContext.tsx | 19 +- .../contexts/TrackContext/TrackProvider.tsx | 18 +- .../TrackContext/utils/useUpdateCamera.ts | 94 ++- .../TrackContext/utils/useUpdateCharacters.ts | 91 ++- .../TrackContext/utils/useUpdateKeyframe.ts | 44 +- .../TrackContext/utils/useUpdateObject.ts | 100 ++- app/pages/PageEnigma/js/timeline.ts | 591 +++++++++--------- app/pages/PageEnigma/models/track.ts | 30 +- 17 files changed, 661 insertions(+), 436 deletions(-) diff --git a/app/pages/PageEnigma/Queue/Queue.ts b/app/pages/PageEnigma/Queue/Queue.ts index 7fba3bb6a7..d477964258 100644 --- a/app/pages/PageEnigma/Queue/Queue.ts +++ b/app/pages/PageEnigma/Queue/Queue.ts @@ -1,7 +1,10 @@ import { fromEngineActions } from "~/pages/PageEnigma/Queue/fromEngineActions"; import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions"; -import { QueueClip } from "~/pages/PageEnigma/models/track"; -import { XYZ } from "../datastructures/common"; +import { + QueueClip, + QueueKeyframe, + UpdateTime, +} from "~/pages/PageEnigma/models/track"; import { toTimelineActions } from "./toTimelineActions"; class Queue { @@ -9,14 +12,14 @@ class Queue { string, { action: fromEngineActions | toEngineActions | toTimelineActions; - data: QueueClip | { currentTime: number } | { position: XYZ, rotation: XYZ, scale: XYZ }; + data: QueueClip | UpdateTime | QueueKeyframe; }[] > = {}; private _subscribers: Record< string, (entry: { action: fromEngineActions | toEngineActions | toTimelineActions; - data: QueueClip | { currentTime: number } | { position: XYZ, rotation: XYZ, scale: XYZ }; + data: QueueClip | UpdateTime | QueueKeyframe; }) => void > = {}; @@ -26,8 +29,8 @@ class Queue { data, }: { queueName: string; - action: fromEngineActions | toEngineActions | toTimelineActions ; - data: QueueClip | { currentTime: number } | { position: XYZ, rotation: XYZ, scale: XYZ }; + action: fromEngineActions | toEngineActions | toTimelineActions; + data: QueueClip | UpdateTime | QueueKeyframe; }) { if (!this._queue[queueName]) { this._queue[queueName] = []; @@ -44,7 +47,7 @@ class Queue { queueName: string, onMessage: (entry: { action: fromEngineActions | toEngineActions | toTimelineActions; - data: QueueClip | { currentTime: number } | { position: XYZ, rotation: XYZ, scale: XYZ }; + data: QueueClip | UpdateTime | QueueKeyframe; }) => void, ) { this._subscribers[queueName] = onMessage; diff --git a/app/pages/PageEnigma/Queue/toEngineActions.ts b/app/pages/PageEnigma/Queue/toEngineActions.ts index 23bb4eefeb..9a601aa64f 100644 --- a/app/pages/PageEnigma/Queue/toEngineActions.ts +++ b/app/pages/PageEnigma/Queue/toEngineActions.ts @@ -1,8 +1,12 @@ export enum toEngineActions { ADD_CLIP = "add_clip", // data=QueueClip + ADD_KEYFRAME = "add_keyframe", + // data= QueueKeyframe DELETE_CLIP = "delete_clip", // data=QueueClip + DELETE_KEYFRAME = "delete_keyframe", + // data=QueueKeyframe MUTE = "mute", // data={version, type, group, object_uuid} if lipsync // data={version, type, group} if global_audio @@ -13,10 +17,8 @@ export enum toEngineActions { // data={version, type, group} if global_audio UPDATE_CLIP = "update_clip", // data=QueueClip + UPDATE_KEYFRAME = "update_keyframe", + // data=QueueKeyframe UPDATE_TIME = "update_time", - - ADD_KEYFRAME = "add_keyframe", // data={currentTime: number} - ADD_KEYFRAME = "add_keyframe", - // data= QueueKeyframe } diff --git a/app/pages/PageEnigma/comps/Timeline/Camera.tsx b/app/pages/PageEnigma/comps/Timeline/Camera.tsx index 693c5215e6..9f376edd18 100644 --- a/app/pages/PageEnigma/comps/Timeline/Camera.tsx +++ b/app/pages/PageEnigma/comps/Timeline/Camera.tsx @@ -1,11 +1,11 @@ import { useContext } from "react"; import { TrackContext } from "~/pages/PageEnigma/contexts/TrackContext/TrackContext"; -import { TrackClips } from "~/pages/PageEnigma/comps/Timeline/TrackClips"; import { fullWidth } from "~/pages/PageEnigma/store"; +import { TrackKeyFrames } from "~/pages/PageEnigma/comps/Timeline/TrackKeyFrames"; export const Camera = () => { const { camera, updateCamera } = useContext(TrackContext); - const { clips } = camera!; + const { keyframes } = camera!; return (
{ >
Camera
-
diff --git a/app/pages/PageEnigma/comps/Timeline/Character.tsx b/app/pages/PageEnigma/comps/Timeline/Character.tsx index 6501221d2f..10cb4f0e44 100644 --- a/app/pages/PageEnigma/comps/Timeline/Character.tsx +++ b/app/pages/PageEnigma/comps/Timeline/Character.tsx @@ -19,12 +19,8 @@ function buildUpdaters( }) { updateCharacters({ ...options, type: "animations" }); } - function updateClipPosition(options: { - id: string; - length: number; - offset: number; - }) { - updateCharacters({ ...options, type: "positions" }); + function updateClipPosition(options: { id: string; offset: number }) { + updateCharacters({ ...options, length: 0, type: "positions" }); } function updateClipLipSync(options: { id: string; @@ -46,9 +42,10 @@ export const Character = ({ characterId }: Props) => { const { updateClipLipSync, updateClipPosition, updateClipAnimations } = useMemo(() => buildUpdaters(updateCharacters), [updateCharacters]); + const toggleCharacterLipSyncMute = useCallback(() => { toggleLipSyncMute(character?.id ?? ""); - }, []); + }, [character?.id, toggleLipSyncMute]); if (!character) { return false; @@ -74,11 +71,10 @@ export const Character = ({ characterId }: Props) => { /> { Objects
{objects.objects.map((object) => ( -
+
diff --git a/app/pages/PageEnigma/comps/Timeline/TrackKeyFrame.tsx b/app/pages/PageEnigma/comps/Timeline/TrackKeyFrame.tsx index cc88996f39..d2e1da7e55 100644 --- a/app/pages/PageEnigma/comps/Timeline/TrackKeyFrame.tsx +++ b/app/pages/PageEnigma/comps/Timeline/TrackKeyFrame.tsx @@ -1,4 +1,4 @@ -import { KeyFrame } from "~/pages/PageEnigma/models/track"; +import { Keyframe } from "~/pages/PageEnigma/models/track"; import { TrackContext } from "~/pages/PageEnigma/contexts/TrackContext/TrackContext"; import { useContext, useState } from "react"; import { scale } from "~/pages/PageEnigma/store"; @@ -10,7 +10,7 @@ interface Props { min: number; max: number; style: "character" | "camera" | "audio" | "objects"; - keyframe: KeyFrame; + keyframe: Keyframe; updateKeyframe: (options: { id: string; offset: number }) => void; } diff --git a/app/pages/PageEnigma/comps/Timeline/TrackKeyFrames.tsx b/app/pages/PageEnigma/comps/Timeline/TrackKeyFrames.tsx index d0befa2ee7..a5c5edb377 100644 --- a/app/pages/PageEnigma/comps/Timeline/TrackKeyFrames.tsx +++ b/app/pages/PageEnigma/comps/Timeline/TrackKeyFrames.tsx @@ -1,21 +1,21 @@ -import { KeyFrame } from "~/pages/PageEnigma/models/track"; +import { Keyframe } from "~/pages/PageEnigma/models/track"; import { filmLength } from "~/pages/PageEnigma/store"; import { TrackKeyFrame } from "~/pages/PageEnigma/comps/Timeline/TrackKeyFrame"; interface Props { id: string; - keyFrames: KeyFrame[]; + keyframes: Keyframe[]; title: string; style: "character" | "audio" | "camera" | "objects"; toggleMute?: () => void; muted?: boolean; - updateClip: (options: { id: string; length: number; offset: number }) => void; + updateKeyframe: (options: { id: string; offset: number }) => void; } export const TrackKeyFrames = ({ id, - keyFrames, - updateClip, + keyframes, + updateKeyframe, title, style, }: Props) => { @@ -25,18 +25,18 @@ export const TrackKeyFrames = ({ id={`track-${style}-${id}`} className={`relative mt-4 block h-9 w-full rounded-lg bg-${style}-unselected`} > - {keyFrames.map((keyFrame, index) => ( + {keyframes.map((keyframe, index) => ( 0 ? keyFrames[index - 1].offset + 1 : 0} + key={keyframe.keyframe_uuid} + min={index > 0 ? keyframes[index - 1].offset + 1 : 0} max={ - index < keyFrames.length - 1 - ? keyFrames[index + 1].offset + index < keyframes.length - 1 + ? keyframes[index + 1].offset : filmLength.value * 60 } style={style} - updateClip={updateClip} - keyFrame={keyFrame} + updateKeyframe={updateKeyframe} + keyframe={keyframe} /> ))}
diff --git a/app/pages/PageEnigma/comps/Timeline/utils/useMouseEventsKeyframe.ts b/app/pages/PageEnigma/comps/Timeline/utils/useMouseEventsKeyframe.ts index a1f30aee1f..20f46e2e5f 100644 --- a/app/pages/PageEnigma/comps/Timeline/utils/useMouseEventsKeyframe.ts +++ b/app/pages/PageEnigma/comps/Timeline/utils/useMouseEventsKeyframe.ts @@ -1,5 +1,5 @@ import React, { Dispatch, useCallback, useEffect, useRef } from "react"; -import { KeyFrame } from "~/pages/PageEnigma/models/track"; +import { Keyframe } from "~/pages/PageEnigma/models/track"; import { canDrop, scale } from "~/pages/PageEnigma/store"; import { useSignals } from "@preact/signals-react/runtime"; @@ -10,7 +10,7 @@ export const useMouseEventsKeyframe = ({ updateKeyframe, setOffset, }: { - keyframe: KeyFrame; + keyframe: Keyframe; max: number; min: number; updateKeyframe: (args: { id: string; offset: number }) => void; diff --git a/app/pages/PageEnigma/comps/Timeline/utils/useQueueHandler.ts b/app/pages/PageEnigma/comps/Timeline/utils/useQueueHandler.ts index 0190377889..3c9cd3645e 100644 --- a/app/pages/PageEnigma/comps/Timeline/utils/useQueueHandler.ts +++ b/app/pages/PageEnigma/comps/Timeline/utils/useQueueHandler.ts @@ -5,16 +5,17 @@ import { currentTime } from "~/pages/PageEnigma/store"; import Queue from "~/pages/PageEnigma/Queue/Queue"; import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames"; import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions"; -import { QueueClip } from "~/pages/PageEnigma/models/track"; +import { + QueueClip, + QueueKeyframe, + UpdateTime, +} from "~/pages/PageEnigma/models/track"; import { TrackContext } from "~/pages/PageEnigma/contexts/TrackContext/TrackContext"; - -interface UpdateTime { - currentTime: number; -} +import { toTimelineActions } from "~/pages/PageEnigma/Queue/toTimelineActions"; interface Arguments { - action: fromEngineActions | toEngineActions; - data: QueueClip | UpdateTime; + action: fromEngineActions | toEngineActions | toTimelineActions; + data: QueueClip | UpdateTime | QueueKeyframe; } export function useQueueHandler() { @@ -36,7 +37,7 @@ export function useQueueHandler() { console.log(`FROM ENGINE:${action} ${data}`); switch (action) { case toTimelineActions.ADD_KEYFRAME: - addKeyframe(data); + addKeyframe(data as QueueKeyframe); break; default: throw new Error(`Unknown action ${action}`); diff --git a/app/pages/PageEnigma/contexts/TrackContext/TrackContext.tsx b/app/pages/PageEnigma/contexts/TrackContext/TrackContext.tsx index 48b2d2429e..66b0899179 100644 --- a/app/pages/PageEnigma/contexts/TrackContext/TrackContext.tsx +++ b/app/pages/PageEnigma/contexts/TrackContext/TrackContext.tsx @@ -5,7 +5,8 @@ import { CharacterGroup, ObjectGroup, MediaClip, - KeyFrame, + Keyframe, + QueueKeyframe, } from "~/pages/PageEnigma/models/track"; export const TrackContext = createContext<{ @@ -35,13 +36,8 @@ export const TrackContext = createContext<{ // timeline camera group camera: CameraGroup | null; - updateCamera: (options: { - id: string; - length: number; - offset: number; - }) => void; - selectCameraClip: (clipId: string) => void; - deleteCameraClip: (clipId: string) => void; + updateCamera: (options: { id: string; offset: number }) => void; + selectCameraKeyframe: (clipId: string) => void; // timeline global audio group audio: AudioGroup | null; @@ -68,8 +64,8 @@ export const TrackContext = createContext<{ selectItem: (itemId: string) => void; // keyframes - addKeyframe: (options: { object_uuid: string }) => void; - deleteKeyframe: (options: KeyFrame) => void; + addKeyframe: (keyframe: QueueKeyframe, offset: number) => void; + deleteKeyframe: (keyframe: Keyframe) => void; // sidebar clips animationClips: MediaClip[]; @@ -93,8 +89,7 @@ export const TrackContext = createContext<{ camera: null, updateCamera: () => {}, - selectCameraClip: () => {}, - deleteCameraClip: () => {}, + selectCameraKeyframe: () => {}, audio: null, updateAudio: () => {}, diff --git a/app/pages/PageEnigma/contexts/TrackContext/TrackProvider.tsx b/app/pages/PageEnigma/contexts/TrackContext/TrackProvider.tsx index 15d7fa7b1c..49216b1689 100644 --- a/app/pages/PageEnigma/contexts/TrackContext/TrackProvider.tsx +++ b/app/pages/PageEnigma/contexts/TrackContext/TrackProvider.tsx @@ -14,16 +14,24 @@ import { dropOffset, } from "~/pages/PageEnigma/store"; import * as uuid from "uuid"; +import useUpdateKeyframe from "~/pages/PageEnigma/contexts/TrackContext/utils/useUpdateKeyframe"; interface Props { children: ReactNode; } export const TrackProvider = ({ children }: Props) => { - const characters = useUpdateCharacters(); - const camera = useUpdateCamera(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { addCharacterKeyframe, deleteCharacterKeyframe, ...characters } = + useUpdateCharacters(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { addCameraKeyframe, deleteCameraKeyframe, ...camera } = + useUpdateCamera(); const audio = useUpdateAudio(); - const objects = useUpdateObject(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { addObjectKeyframe, deleteObjectKeyframe, ...objects } = + useUpdateObject(); + const keyframes = useUpdateKeyframe(); const { endDrag, ...dragDrop } = useUpdateDragDrop(); @@ -115,6 +123,8 @@ export const TrackProvider = ({ children }: Props) => { ...audio, ...objects, + ...keyframes, + selectItem, selectedItem, @@ -138,6 +148,8 @@ export const TrackProvider = ({ children }: Props) => { audio, objects, + keyframes, + selectItem, selectedItem, diff --git a/app/pages/PageEnigma/contexts/TrackContext/utils/useUpdateCamera.ts b/app/pages/PageEnigma/contexts/TrackContext/utils/useUpdateCamera.ts index d7bc1395b8..741e71a394 100644 --- a/app/pages/PageEnigma/contexts/TrackContext/utils/useUpdateCamera.ts +++ b/app/pages/PageEnigma/contexts/TrackContext/utils/useUpdateCamera.ts @@ -1,60 +1,89 @@ import { useCallback, useState } from "react"; -import { CameraGroup } from "~/pages/PageEnigma/models/track"; +import { + CameraGroup, + Keyframe, + QueueKeyframe, +} from "~/pages/PageEnigma/models/track"; import Queue from "~/pages/PageEnigma/Queue/Queue"; import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames"; import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions"; +import * as uuid from "uuid"; export default function useUpdateCamera() { const [camera, setCamera] = useState({ id: "CA1", - clips: [], + keyframes: [], }); const updateCamera = useCallback( - ({ - id, - offset, - length, - }: { - id: string; - length: number; - offset: number; - }) => { + ({ id, offset }: { id: string; offset: number }) => { setCamera((oldCamera) => { - const newClips = [...oldCamera.clips]; - const clipIndex = newClips.findIndex((row) => row.clip_uuid === id); - if (clipIndex === -1) { + const newKeyframes = [...oldCamera.keyframes]; + const keyframe = newKeyframes.find((row) => row.keyframe_uuid === id); + if (!keyframe) { return { ...oldCamera }; } - const clip = newClips[clipIndex]; - clip.offset = offset; - clip.length = length; + keyframe.offset = offset; Queue.publish({ queueName: QueueNames.TO_ENGINE, - action: toEngineActions.UPDATE_CLIP, - data: clip, + action: toEngineActions.UPDATE_KEYFRAME, + data: keyframe, }); return { ...oldCamera, - clips: newClips, + clips: newKeyframes, }; }); }, [], ); - const selectCameraClip = useCallback((clipId: string) => { + const addCameraKeyframe = useCallback( + (keyframe: QueueKeyframe, offset: number) => { + const newKeyframe = { + version: keyframe.version, + keyframe_uuid: uuid.v4(), + group: keyframe.group, + object_uuid: keyframe.object_uuid, + offset, + position: keyframe.position, + rotation: keyframe.rotation, + scale: keyframe.scale, + selected: false, + } as Keyframe; + + setCamera((oldCamera) => { + return { + ...oldCamera, + keyframes: [...oldCamera.keyframes, newKeyframe].sort( + (keyFrameA, keyframeB) => keyFrameA.offset - keyframeB.offset, + ), + }; + }); + + Queue.publish({ + queueName: QueueNames.TO_ENGINE, + action: toEngineActions.ADD_KEYFRAME, + data: newKeyframe, + }); + }, + [], + ); + + const selectCameraKeyframe = useCallback((keyframeId: string) => { setCamera((oldCamera) => { return { ...oldCamera, clips: [ - ...oldCamera.clips.map((clip) => { + ...oldCamera.keyframes.map((keyframe) => { return { - ...clip, + ...keyframe, selected: - clip.clip_uuid === clipId ? !clip.selected : clip.selected, + keyframe.keyframe_uuid === keyframeId + ? !keyframe.selected + : keyframe.selected, }; }), ], @@ -62,17 +91,17 @@ export default function useUpdateCamera() { }); }, []); - const deleteCameraClip = useCallback((clipId: string) => { + const deleteCameraKeyframe = useCallback((keyframeId: string) => { setCamera((oldCamera) => { return { ...oldCamera, - clips: [ - ...oldCamera.clips.filter((clip) => { - if (clip.clip_uuid === clipId) { + keyframes: [ + ...oldCamera.keyframes.filter((keyframe) => { + if (keyframe.keyframe_uuid === keyframeId) { Queue.publish({ queueName: QueueNames.TO_ENGINE, - action: toEngineActions.DELETE_CLIP, - data: clip!, + action: toEngineActions.DELETE_KEYFRAME, + data: keyframe, }); return false; } @@ -86,7 +115,8 @@ export default function useUpdateCamera() { return { camera, updateCamera, - selectCameraClip, - deleteCameraClip, + selectCameraKeyframe, + addCameraKeyframe, + deleteCameraKeyframe, }; } diff --git a/app/pages/PageEnigma/contexts/TrackContext/utils/useUpdateCharacters.ts b/app/pages/PageEnigma/contexts/TrackContext/utils/useUpdateCharacters.ts index cd429c0024..27a4a14435 100644 --- a/app/pages/PageEnigma/contexts/TrackContext/utils/useUpdateCharacters.ts +++ b/app/pages/PageEnigma/contexts/TrackContext/utils/useUpdateCharacters.ts @@ -5,6 +5,8 @@ import { ClipGroup, ClipType, MediaClip, + QueueKeyframe, + Keyframe, } from "~/pages/PageEnigma/models/track"; import * as uuid from "uuid"; import Queue from "~/pages/PageEnigma/Queue/Queue"; @@ -17,7 +19,7 @@ export default function useUpdateCharacters() { id: "CH1", muted: false, animationClips: [], - positionClips: [], + positionKeyframes: [], lipSyncClips: [], }, ]); @@ -66,26 +68,25 @@ export default function useUpdateCharacters() { if (type === "positions") { setCharacters((oldCharacters) => { return oldCharacters.map((character) => { - const newPositionClips = [...character.positionClips]; - const clipIndex = newPositionClips.findIndex( - (row) => row.clip_uuid === id, + const newPositionKeyframes = [...character.positionKeyframes]; + const keyframeIndex = newPositionKeyframes.findIndex( + (row) => row.keyframe_uuid === id, ); - if (clipIndex === -1) { + if (keyframeIndex === -1) { return { ...character }; } - const clip = newPositionClips[clipIndex]; - clip.offset = offset; - clip.length = length; + const keyframe = newPositionKeyframes[keyframeIndex]; + keyframe.offset = offset; Queue.publish({ queueName: QueueNames.TO_ENGINE, action: toEngineActions.UPDATE_CLIP, - data: clip, + data: keyframe, }); return { ...character, - positionClips: newPositionClips, + positionClips: newPositionKeyframes, }; }); }); @@ -221,6 +222,46 @@ export default function useUpdateCharacters() { [], ); + const addCharacterKeyframe = useCallback( + (keyframe: QueueKeyframe, offset: number) => { + const newKeyframe = { + version: keyframe.version, + keyframe_uuid: uuid.v4(), + group: keyframe.group, + object_uuid: keyframe.object_uuid, + offset, + position: keyframe.position, + rotation: keyframe.rotation, + scale: keyframe.scale, + selected: false, + } as Keyframe; + + setCharacters((oldCharacters) => { + return oldCharacters.map((character) => { + if (character.id !== keyframe.object_uuid) { + return { ...character }; + } + return { + ...character, + positionKeyframes: [ + ...character.positionKeyframes, + newKeyframe, + ].sort( + (keyFrameA, keyframeB) => keyFrameA.offset - keyframeB.offset, + ), + }; + }); + }); + + Queue.publish({ + queueName: QueueNames.TO_ENGINE, + action: toEngineActions.ADD_KEYFRAME, + data: newKeyframe, + }); + }, + [], + ); + const toggleLipSyncMute = useCallback((characterId: string) => { setCharacters((oldCharacters) => { return oldCharacters.map((character) => { @@ -258,10 +299,12 @@ export default function useUpdateCharacters() { selected: clip.clip_uuid === clipId ? !clip.selected : clip.selected, })), - positionClips: character.positionClips.map((clip) => ({ - ...clip, + positionClips: character.positionKeyframes.map((keyframe) => ({ + ...keyframe, selected: - clip.clip_uuid === clipId ? !clip.selected : clip.selected, + keyframe.keyframe_uuid === clipId + ? !keyframe.selected + : keyframe.selected, })), lipSyncClips: character.lipSyncClips.map((clip) => ({ ...clip, @@ -289,7 +332,7 @@ export default function useUpdateCharacters() { } return true; }), - positionClips: character.positionClips.filter((clip) => { + lipSyncClips: character.lipSyncClips.filter((clip) => { if (clip.clip_uuid === clipId) { Queue.publish({ queueName: QueueNames.TO_ENGINE, @@ -300,12 +343,22 @@ export default function useUpdateCharacters() { } return true; }), - lipSyncClips: character.lipSyncClips.filter((clip) => { - if (clip.clip_uuid === clipId) { + })), + ]; + }); + }, []); + + const deleteCharacterKeyframe = useCallback((keyframe: Keyframe) => { + setCharacters((oldCharacters) => { + return [ + ...oldCharacters.map((character) => ({ + ...character, + positionClips: character.positionKeyframes.filter((row) => { + if (row.keyframe_uuid === keyframe.keyframe_uuid) { Queue.publish({ queueName: QueueNames.TO_ENGINE, - action: toEngineActions.DELETE_CLIP, - data: clip!, + action: toEngineActions.DELETE_KEYFRAME, + data: row, }); return false; } @@ -324,5 +377,7 @@ export default function useUpdateCharacters() { addCharacterAudio, selectCharacterClip, deleteCharacterClip, + addCharacterKeyframe, + deleteCharacterKeyframe, }; } diff --git a/app/pages/PageEnigma/contexts/TrackContext/utils/useUpdateKeyframe.ts b/app/pages/PageEnigma/contexts/TrackContext/utils/useUpdateKeyframe.ts index 41e5c4279f..ebd8ffb6a4 100644 --- a/app/pages/PageEnigma/contexts/TrackContext/utils/useUpdateKeyframe.ts +++ b/app/pages/PageEnigma/contexts/TrackContext/utils/useUpdateKeyframe.ts @@ -1,17 +1,43 @@ import { useCallback } from "react"; -import { ObjectTrack } from "~/pages/PageEnigma/models/track"; +import { + ClipGroup, + QueueKeyframe, + Keyframe, +} from "~/pages/PageEnigma/models/track"; +import useUpdateObject from "~/pages/PageEnigma/contexts/TrackContext/utils/useUpdateObject"; +import useUpdateCharacters from "~/pages/PageEnigma/contexts/TrackContext/utils/useUpdateCharacters"; export default function useUpdateKeyframe() { - const addKeyframe = useCallback((obj: ObjectTrack) => { - setObjects((oldObjectGroup) => { - return { - ...oldObjectGroup, - objects: [...oldObjectGroup.objects, obj], - }; - }); - }, []); + const { addObjectKeyframe, deleteObjectKeyframe } = useUpdateObject(); + const { addCharacterKeyframe, deleteCharacterKeyframe } = + useUpdateCharacters(); + + const addKeyframe = useCallback( + (keyframe: QueueKeyframe, offset: number) => { + if (keyframe.group === ClipGroup.OBJECT) { + addObjectKeyframe(keyframe, offset); + } + if (keyframe.group === ClipGroup.CHARACTER) { + addCharacterKeyframe(keyframe, offset); + } + }, + [addCharacterKeyframe, addObjectKeyframe], + ); + + const deleteKeyframe = useCallback( + (keyframe: Keyframe) => { + if (keyframe.group === ClipGroup.OBJECT) { + deleteObjectKeyframe(keyframe); + } + if (keyframe.group === ClipGroup.CHARACTER) { + deleteCharacterKeyframe(keyframe); + } + }, + [deleteCharacterKeyframe, deleteObjectKeyframe], + ); return { addKeyframe, + deleteKeyframe, }; } diff --git a/app/pages/PageEnigma/contexts/TrackContext/utils/useUpdateObject.ts b/app/pages/PageEnigma/contexts/TrackContext/utils/useUpdateObject.ts index 06398521be..4d942363d2 100644 --- a/app/pages/PageEnigma/contexts/TrackContext/utils/useUpdateObject.ts +++ b/app/pages/PageEnigma/contexts/TrackContext/utils/useUpdateObject.ts @@ -1,5 +1,9 @@ import { useCallback, useState } from "react"; -import { ObjectGroup, ObjectTrack } from "~/pages/PageEnigma/models/track"; +import { + ObjectGroup, + QueueKeyframe, + Keyframe, +} from "~/pages/PageEnigma/models/track"; import * as uuid from "uuid"; export default function useUpdateObject() { @@ -14,8 +18,9 @@ export default function useUpdateObject() { return { id: oldObject.id, objects: oldObject.objects.map((object) => ({ - id: object.id, - keyFrames: object.keyFrames.map((keyFrame) => ({ + object_uuid: object.object_uuid, + name: object.name, + keyframes: object.keyframes.map((keyFrame) => ({ ...keyFrame, offset: keyFrame.keyframe_uuid === id ? offset : keyFrame.offset, })), @@ -26,31 +31,92 @@ export default function useUpdateObject() { [], ); - const addObject = useCallback((name: string) => { + const addObjectKeyframe = useCallback( + (keyframe: QueueKeyframe, offset: number) => { + setObjects((oldObjectGroup) => { + const obj = objects.objects.find( + (row) => row.object_uuid === keyframe.object_uuid, + ); + + const newObject = obj ?? { + object_uuid: keyframe.object_uuid, + name: keyframe.object_name ?? "unknown", + keyframes: [] as Keyframe[], + }; + newObject.keyframes.push({ + version: keyframe.version, + keyframe_uuid: uuid.v4(), + group: keyframe.group, + object_uuid: keyframe.object_uuid, + offset, + position: keyframe.position, + rotation: keyframe.rotation, + scale: keyframe.scale, + selected: false, + } as Keyframe); + newObject.keyframes.sort( + (keyframeA, keyframeB) => keyframeA.offset - keyframeB.offset, + ); + + return { + ...oldObjectGroup, + objects: [ + ...oldObjectGroup.objects.filter( + (object) => object.object_uuid !== keyframe.object_uuid, + ), + newObject, + ].sort((objA, objB) => + objA.object_uuid < objB.object_uuid ? -1 : 1, + ), + }; + }); + }, + [objects], + ); + + const deleteObjectKeyframe = useCallback((keyframe: Keyframe) => { setObjects((oldObjectGroup) => { + const obj = oldObjectGroup.objects.find( + (row) => row.object_uuid === keyframe.object_uuid, + ); + if (!obj) { + return oldObjectGroup; + } + + const newKeyframes = [ + ...obj.keyframes.filter( + (row) => row.keyframe_uuid !== keyframe.keyframe_uuid, + ), + ]; + + if (newKeyframes.length) { + return { + ...oldObjectGroup, + objects: [ + ...oldObjectGroup.objects.map((object) => ({ + ...object, + ...(object.object_uuid === keyframe.object_uuid + ? { keyframes: newKeyframes } + : {}), + })), + ], + }; + } return { ...oldObjectGroup, objects: [ - ...oldObjectGroup.objects, - { object_uuid: uuid.v4(), name: name, keyframes: [] }, + ...oldObjectGroup.objects.filter( + (object) => object.object_uuid !== keyframe.object_uuid, + ), ], }; }); }, []); - const addObject = useCallback((obj: ObjectTrack) => { - setObjects((oldObjectGroup) => { - return { - ...oldObjectGroup, - objects: [...oldObjectGroup.objects, obj], - }; - }); - }, []); - return { objects, updateObject, - addObject, - addKeyframe, + addObjectKeyframe, + deleteObjectKeyframe, }; } diff --git a/app/pages/PageEnigma/js/timeline.ts b/app/pages/PageEnigma/js/timeline.ts index a4cf18f054..51377b4170 100644 --- a/app/pages/PageEnigma/js/timeline.ts +++ b/app/pages/PageEnigma/js/timeline.ts @@ -15,318 +15,340 @@ import { fromEngineActions } from "../Queue/fromEngineActions"; import { currentTime } from "../store"; // Every object uuid / entity has a track. export class TimelineCurrentState { - is_editable: boolean - selected_object_ID: number - constructor() { - this.is_editable = true // can add clips to it - this.selected_object_ID = 0 - } + is_editable: boolean; + selected_object_ID: number; + constructor() { + this.is_editable = true; // can add clips to it + this.selected_object_ID = 0; + } } export class TimelineDataState { - timeline_items: ClipUI[] - scrubber_frame_position: number - constructor(timeline_items: ClipUI[] = [], - scrubber_frame_position: number = 0) { - this.timeline_items = timeline_items - this.scrubber_frame_position = scrubber_frame_position - } + timeline_items: ClipUI[]; + scrubber_frame_position: number; + constructor( + timeline_items: ClipUI[] = [], + scrubber_frame_position: number = 0, + ) { + this.timeline_items = timeline_items; + this.scrubber_frame_position = scrubber_frame_position; + } } export class TimeLine { + timeline_items: ClipUI[]; - timeline_items: ClipUI[] + timeline_limit: number; + scrubber_frame_position: number; + is_playing: boolean; - timeline_limit: number - scrubber_frame_position: number - is_playing: boolean + // plays audio + audio_engine: AudioEngine; + // key framing + transform_engine: TransformEngine; + // animation engine + animation_engine: AnimationEngine; + // lip sync engine + lipSync_engine: LipSyncEngine; - // plays audio - audio_engine: AudioEngine - // key framing - transform_engine: TransformEngine - // animation engine - animation_engine: AnimationEngine - // lip sync engine - lipSync_engine: LipSyncEngine + scene: Scene; - scene: Scene + current_time: number; + // ensure that the elements are loaded first. + constructor( + audio_engine: AudioEngine, + transform_engine: TransformEngine, + lipsync_engine: LipSyncEngine, + animation_engine: AnimationEngine, + scene: Scene, + ) { + this.timeline_items = []; + this.timeline_limit = 60 * 5; // 5 seconds - current_time: number - // ensure that the elements are loaded first. - constructor(audio_engine: AudioEngine, - transform_engine: TransformEngine, - lipsync_engine: LipSyncEngine, - animation_engine: AnimationEngine, - scene: Scene) { + this.is_playing = false; + this.scrubber_frame_position = 0; // in frames into the tl - this.timeline_items = [] - this.timeline_limit = 60 * 5 // 5 seconds + // this will be used to play the audio clips + this.audio_engine = audio_engine; + this.transform_engine = transform_engine; + this.lipSync_engine = lipsync_engine; + this.animation_engine = animation_engine; - this.is_playing = false - this.scrubber_frame_position = 0 // in frames into the tl + this.scene = scene; - // this will be used to play the audio clips - this.audio_engine = audio_engine - this.transform_engine = transform_engine - this.lipSync_engine = lipsync_engine - this.animation_engine = animation_engine + Queue.subscribe( + QueueNames.TO_ENGINE, + this.handleTimelineActions.bind(this), + ); - this.scene = scene; + this.current_time = 0; + // TODO: How to move the timeline should put in update. + // setInterval(()=> { + // this.current_time +=1 + // this.pushEvent(fromEngineActions.UPDATE_TIME, { currentTime: this.current_time }) + // },50) + } - Queue.subscribe(QueueNames.TO_ENGINE, this.handleTimelineActions.bind(this)); + public async pushEvent(action: fromEngineActions, data: any) { + this.current_time += 1; + Queue.publish({ + queueName: QueueNames.FROM_ENGINE, + action: fromEngineActions.UPDATE_TIME, + data: data, + }); + } - this.current_time = 0 - // TODO: How to move the timeline should put in update. - // setInterval(()=> { - // this.current_time +=1 - // this.pushEvent(fromEngineActions.UPDATE_TIME, { currentTime: this.current_time }) - // },50) + public async handleTimelineActions(data: any) { + const action = data["action"]; + switch (action) { + case toEngineActions.ADD_KEYFRAME: + await this.addKeyFrame(data); + break; + case toEngineActions.ADD_CLIP: + await this.addClip(data); + break; + case toEngineActions.DELETE_CLIP: + await this.deleteClip(data); + break; + case toEngineActions.UPDATE_CLIP: + await this.updateClip(data); + break; + case toEngineActions.UPDATE_TIME: + await this.scrub(data); + break; + case toEngineActions.MUTE: + await this.mute(data); + break; + default: + console.log("Action Not Wired"); + } + } + + public async addKeyFrame(data: any) { + // Keyframe Object + // version: number; + // clip_uuid: string; + // group: ClipGroup; + // object_uuid?: string; + // offset: number; + // position: XYZ + // rotation: XYZ; + // scale: XYZ; + // selected?: boolean; + } + + public async addClip(data: any) { + let object_uuid = data["data"]["object_uuid"]; + + if (object_uuid.length < 6) { + object_uuid = this.scene.selected?.uuid; //data['data']['object_uuid']; + if (object_uuid == undefined) { + return; + } } - public async pushEvent(action: fromEngineActions, data: any) { - this.current_time += 1 - Queue.publish({ - queueName: QueueNames.FROM_ENGINE, - action: fromEngineActions.UPDATE_TIME, - data: data, - }); - } + let media_id = data["data"]["media_id"]; + let name = data["data"]["name"]; + let group = data["data"]["group"]; + let version = data["data"]["group"]; + let type = data["data"]["type"]; + let offset = data["data"]["offset"]; + let end_offset = data["data"]["length"] + offset; - public async handleTimelineActions(data: any) { - const action = data["action"] - switch (action) { - case toEngineActions.ADD_KEYFRAME: - await this.addKeyFrame(data) - break - case toEngineActions.ADD_CLIP: - await this.addClip(data) - break - case toEngineActions.DELETE_CLIP: - await this.deleteClip(data) - break - case toEngineActions.UPDATE_CLIP: - await this.updateClip(data) - break - case toEngineActions.UPDATE_TIME: - await this.scrub(data) - break - case toEngineActions.MUTE: - await this.mute(data) - break - default: - console.log("Action Not Wired") + // media id for this as well it can be downloaded + this.addPlayableClip( + new ClipUI( + version, + type, + group, + name, + media_id, + object_uuid, + offset, + end_offset, + ), + ); + + switch (type) { + case "animation": + this.animation_engine.load_object(object_uuid, media_id, name); + break; + case "transform": + this.transform_engine.loadObject(object_uuid, data["data"]["length"]); + break; + } + } + public async updateClip(data: any) { + // only length and offset changes here. + console.log(data); + } + public async deleteClip(data: any) { + console.log(data); + } + + public async scrubberUpdate(data: any) { + console.log(data); + } + + public async mute(data: any) { + console.log(data); + } + + public async addPlayableClip(clip: ClipUI): Promise { + this.timeline_items.push(clip); + } + + // when given a media id item it will create the clip. + // Then the clip will be loaded by the engines, if they come from outside of the loaded scene. + public async createClipOffset( + media_id: string, + object_uuid: string, + type: string, + ): Promise { + // use engine to load based off media id and type animation | transform | + } + + // this will update the state of the clips based off uuid easing? + public async updatePlayableClip( + clip_uuid: string, + updates: AnyJson, + ): Promise {} + + public async deletePlayableClip(clip_uuid: string): Promise {} + + public async scrub(data: any): Promise { + if (this.is_playing) { + return; + } + this.setScrubberPosition(data["data"]["currentTime"]); + } + + public async scrubberDidStop(offset_frame: number) {} + // public streaming events into the timeline from + public async setScrubberPosition(offset: number) { + this.scrubber_frame_position = offset; // in ms + } + + // should play from the clip that is closest to the to scrubber + public async play(): Promise { + console.log(`Starting Timeline`); + this.is_playing = true; + } + + private async resetScene() { + this.timeline_limit = this.getEndPoint(); + for (const element of this.timeline_items) { + if (element.type == "transform") { + let object = this.scene.get_object_by_uuid(element.object_uuid); + if (object && this.transform_engine.clips[element.object_uuid]) { + this.transform_engine.clips[element.object_uuid].reset(object); } + } else if (element.type == "audio") { + this.audio_engine.loadClip(element.media_id); + } else if (element.type == "animation") { + this.animation_engine.clips[element.object_uuid].stop(); + } else if (element.type == "lipsync") { + this.lipSync_engine.clips[element.object_uuid].reset(); + } else { + this.stop(); + throw "Error New Type of element in the timeline"; + } + } + } + private getEndPoint(): number { + let longest = 0; + for (const element of this.timeline_items) { + if (longest < element.length) { + longest = element.length; + } + } + return longest; + } + + // called by the editor update loop on each frame + public async update(deltatime: number) { + //if (this.is_playing == false) return; // start and stop + + if (this.is_playing) { + this.current_time += 1; + this.pushEvent(fromEngineActions.UPDATE_TIME, { + currentTime: this.current_time, + }); + this.scrubber_frame_position = this.current_time; } - public async addKeyFrame(data: any) { - // KeyFrame Object - // version: number; - // clip_uuid: string; - // group: ClipGroup; - // object_uuid?: string; - // offset: number; - // position: XYZ - // rotation: XYZ; - // scale: XYZ; - // selected?: boolean; - + if (this.scrubber_frame_position <= 0) { + await this.resetScene(); } - public async addClip(data: any) { - let object_uuid = data['data']['object_uuid']; - - if (object_uuid.length < 6){ - object_uuid = this.scene.selected?.uuid;//data['data']['object_uuid']; - if(object_uuid == undefined) { return; } + //this.scrubber_frame_position += 1; + //2. allow stopping. + //3. smallest unit is a frame and it is set by the scene and is in fps, our videos will be 60fps but we can reprocess them using the pipeline. + for (const element of this.timeline_items) { + if ( + element.offset <= this.scrubber_frame_position && + this.scrubber_frame_position <= element.length + ) { + // run async + // element.play() + // remove the element from the list + let object = this.scene.get_object_by_uuid(element.object_uuid); + if (element.type == "transform") { + if (object && this.transform_engine.clips[element.object_uuid]) { + this.transform_engine.clips[element.object_uuid].length = + element.length - element.offset; + this.transform_engine.clips[element.object_uuid].step( + object, + element.offset, + this.scrubber_frame_position, + ); + } + } else if (element.type == "audio") { + if (this.scrubber_frame_position + 1 >= element.length) { + this.audio_engine.stopClip(element.media_id); + } else { + this.audio_engine.playClip(element.media_id); + } + } else if (element.type == "lipsync") { + if (this.scrubber_frame_position + 1 >= element.length) { + this.lipSync_engine.clips[element.object_uuid].stop(); + } else if (object) { + await this.lipSync_engine.clips[element.object_uuid].play(object); + this.lipSync_engine.clips[element.object_uuid].step(); + } + } else if (element.type == "animation") { + if (object) { + await this.animation_engine.clips[object.uuid].play(object); + this.animation_engine.clips[object.uuid].step( + this.scrubber_frame_position / 60, + ); + } + } else { + this.stop(); + throw "Error New Type of element in the timeline"; } - - let media_id = data['data']['media_id']; - let name = data['data']['name']; - let group = data['data']['group']; - let version = data['data']['group']; - let type = data['data']['type']; - let offset = data['data']['offset']; - let end_offset = data['data']['length']+offset; - - - // media id for this as well it can be downloaded - this.addPlayableClip( - new ClipUI(version, type, - group, name, - media_id, object_uuid, - offset, end_offset)); - - switch(type) { - case "animation": - this.animation_engine.load_object(object_uuid, media_id, name); - break - case "transform": - this.transform_engine.loadObject(object_uuid, data['data']['length']); - break - } - } - public async updateClip(data: any) { - // only length and offset changes here. - console.log(data) - } - public async deleteClip(data: any) { - console.log(data) + //this.timelineItems = this.timelineItems.filter(item => item !== element) + } } - public async scrubberUpdate(data: any) { - console.log(data) + if ( + this.scrubber_frame_position >= this.timeline_limit && + this.is_playing + ) { + await this.stop(); } + } - public async mute(data: any) { - console.log(data) - } - - public async addPlayableClip(clip: ClipUI): Promise { - this.timeline_items.push(clip) - } - - // when given a media id item it will create the clip. - // Then the clip will be loaded by the engines, if they come from outside of the loaded scene. - public async createClipOffset(media_id: string, object_uuid: string, type: string): Promise { - // use engine to load based off media id and type animation | transform | - } - - // this will update the state of the clips based off uuid easing? - public async updatePlayableClip(clip_uuid: string, updates: AnyJson): Promise { - - } - - public async deletePlayableClip(clip_uuid: string): Promise { - - } - - public async scrub(data: any): Promise { - if (this.is_playing) { return } - this.setScrubberPosition(data['data']['currentTime']); - } - - public async scrubberDidStop(offset_frame: number) { - - } - // public streaming events into the timeline from - public async setScrubberPosition(offset: number) { - this.scrubber_frame_position = offset // in ms - } - - // should play from the clip that is closest to the to scrubber - public async play(): Promise { - console.log(`Starting Timeline`) - this.is_playing = true - } - - private async resetScene() { - this.timeline_limit = this.getEndPoint(); - for (const element of this.timeline_items) { - if (element.type == "transform") { - let object = this.scene.get_object_by_uuid(element.object_uuid); - if (object && this.transform_engine.clips[element.object_uuid]) { this.transform_engine.clips[element.object_uuid].reset(object); } - } - else if (element.type == "audio") { - this.audio_engine.loadClip(element.media_id); - } - else if (element.type == "animation") { - this.animation_engine.clips[element.object_uuid].stop(); - } - else if (element.type == "lipsync") { - this.lipSync_engine.clips[element.object_uuid].reset(); - } - else { - this.stop() - throw "Error New Type of element in the timeline" - } - } - } - - private getEndPoint(): number { - let longest = 0; - for (const element of this.timeline_items) { - if(longest < element.length) { - longest = element.length; - } - } - return longest; - } - - // called by the editor update loop on each frame - public async update(deltatime: number) { - //if (this.is_playing == false) return; // start and stop - - - if(this.is_playing) { - this.current_time += 1; - this.pushEvent(fromEngineActions.UPDATE_TIME, { currentTime: this.current_time }) - this.scrubber_frame_position = this.current_time; - } - - if (this.scrubber_frame_position <= 0) { - await this.resetScene(); - } - - //this.scrubber_frame_position += 1; - //2. allow stopping. - //3. smallest unit is a frame and it is set by the scene and is in fps, our videos will be 60fps but we can reprocess them using the pipeline. - for (const element of this.timeline_items) { - if (element.offset <= this.scrubber_frame_position && this.scrubber_frame_position <= element.length) { - // run async - // element.play() - // remove the element from the list - let object = this.scene.get_object_by_uuid(element.object_uuid) - if (element.type == "transform") { - if (object && this.transform_engine.clips[element.object_uuid]) { - this.transform_engine.clips[element.object_uuid].length = (element.length - element.offset); - this.transform_engine.clips[element.object_uuid].step(object, element.offset, this.scrubber_frame_position); - } - } - else if (element.type == "audio") { - if (this.scrubber_frame_position + 1 >= element.length) { - this.audio_engine.stopClip(element.media_id); - } - else { - this.audio_engine.playClip(element.media_id); - } - } - else if (element.type == "lipsync") { - if (this.scrubber_frame_position + 1 >= element.length) { - this.lipSync_engine.clips[element.object_uuid].stop(); - } - else if (object) { - await this.lipSync_engine.clips[element.object_uuid].play(object); - this.lipSync_engine.clips[element.object_uuid].step(); - } - } - else if (element.type == "animation") { - if (object) { - await this.animation_engine.clips[object.uuid].play(object); - this.animation_engine.clips[object.uuid].step(this.scrubber_frame_position/60); - } - } - else { - this.stop() - throw "Error New Type of element in the timeline" - } - //this.timelineItems = this.timelineItems.filter(item => item !== element) - } - } - - if (this.scrubber_frame_position >= this.timeline_limit && this.is_playing) { - await this.stop(); - } - } - - private async stop(): Promise { - await this.resetScene(); - this.is_playing = false - console.log(`Stopping Timeline`) - this.current_time = 0; - this.pushEvent(fromEngineActions.UPDATE_TIME, { currentTime: this.current_time }) - } + private async stop(): Promise { + await this.resetScene(); + this.is_playing = false; + console.log(`Stopping Timeline`); + this.current_time = 0; + this.pushEvent(fromEngineActions.UPDATE_TIME, { + currentTime: this.current_time, + }); + } } // How much timeline precision we have @@ -387,7 +409,7 @@ export class TimeLine { // } // function CheckIfTimeLineStartAfterClipPlays() { -// const timeline = new TimeLine() +// const timeline = new TimeLine() // timeline.addPlayableClip(new ClipUI("clip3",0,1000)) // timeline.play() // setInterval(async ()=> { @@ -397,7 +419,7 @@ export class TimeLine { // } // function CheckIfClipsPlayAllTogetherConcurrently() { -// const timeline = new TimeLine() +// const timeline = new TimeLine() // timeline.addPlayableClip(new ClipUI("clip1",0,1000)) // timeline.addPlayableClip(new ClipUI("clip2",0,1000)) // timeline.addPlayableClip(new ClipUI("clip3",0,1000)) @@ -406,7 +428,7 @@ export class TimeLine { // } // function CheckIfClipsPlayAllTogether() { -// const timeline = new TimeLine() +// const timeline = new TimeLine() // timeline.addPlayableClip(new TrackClip("clip1",0,1000)) // timeline.addPlayableClip(new TrackClip("clip2",0,1000)) // timeline.addPlayableClip(new TrackClip("clip3",0,1000)) @@ -422,4 +444,3 @@ export class TimeLine { // CheckIfClipsPlayAllTogetherConcurrently() // CheckIfClipsPlayAllTogether() - diff --git a/app/pages/PageEnigma/models/track.ts b/app/pages/PageEnigma/models/track.ts index f8ce1194e9..a6d316ed42 100644 --- a/app/pages/PageEnigma/models/track.ts +++ b/app/pages/PageEnigma/models/track.ts @@ -47,11 +47,11 @@ export enum ClipGroup { OBJECT = "object", } -export interface KeyFrame { +export interface Keyframe { version: number; keyframe_uuid: string; group: ClipGroup; - object_uuid?: string; + object_uuid: string; offset: number; position: XYZ; rotation: XYZ; @@ -59,17 +59,30 @@ export interface KeyFrame { selected?: boolean; } +export interface QueueKeyframe { + version: number; + keyframe_uuid?: string; + group: ClipGroup; + object_uuid: string; + object_name?: string; + offset?: number; + position: XYZ; + rotation: XYZ; + scale: XYZ; + selected?: boolean; +} + export interface CharacterGroup { id: string; muted: boolean; animationClips: Clip[]; - positionKeyframes: KeyFrame[]; + positionKeyframes: Keyframe[]; lipSyncClips: Clip[]; } export interface CameraGroup { id: string; - clips: KeyFrame[]; + keyframes: Keyframe[]; } export interface AudioGroup { @@ -84,6 +97,11 @@ export interface ObjectGroup { } export interface ObjectTrack { - id: string; - keyFrames: KeyFrame[]; + object_uuid: string; + name: string; + keyframes: Keyframe[]; +} + +export interface UpdateTime { + currentTime: number; } From f2a280b26fc0514017d113ae53e7168cfceb1337 Mon Sep 17 00:00:00 2001 From: Michael Chung <6252325+ArEnSc@users.noreply.github.com> Date: Sun, 31 Mar 2024 13:54:52 -0400 Subject: [PATCH 04/22] Adding extra parameters to send the select modal --- app/pages/PageEnigma/js/editor.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/pages/PageEnigma/js/editor.ts b/app/pages/PageEnigma/js/editor.ts index 8f879f53dd..7e5afc7f8b 100644 --- a/app/pages/PageEnigma/js/editor.ts +++ b/app/pages/PageEnigma/js/editor.ts @@ -1061,14 +1061,19 @@ class Editor { updateSelectedUI() { if (this.selected == undefined) { return; } + let pos = this.selected.position; let rot = this.selected.rotation; let scale = this.selected.scale; - + this.dispatchAppUiState({ type: APPUI_ACTION_TYPES.SHOW_CONTROLPANELS_SCENEOBJECT, payload: { currentSceneObject: { + group: "object", // TODO: add meta data to determine what it is a camera or a object or a character into prefab clips + object_uuid: this.selected.uuid, + object_name: this.selected.name, + version: this.version, objectVectors: { position: { x: parseFloat(pos.x.toFixed(2)), y: parseFloat(pos.y.toFixed(2)), z: parseFloat(pos.z.toFixed(2)) }, rotation: { x: parseFloat(rot.x.toFixed(2)), y: parseFloat(rot.y.toFixed(2)), z: parseFloat(rot.z.toFixed(2)) }, @@ -1079,7 +1084,6 @@ class Editor { }); } - // Automaticly resize scene. onWindowResize() { // Calculate the maximum possible dimensions while maintaining the aspect ratio From a6580bd3597b6bbc9d0b236a5db4000f13a348b1 Mon Sep 17 00:00:00 2001 From: Michael Chung <6252325+ArEnSc@users.noreply.github.com> Date: Sun, 31 Mar 2024 15:38:35 -0400 Subject: [PATCH 05/22] Need wil to verify and review the code... --- app/pages/PageEnigma/Queue/Queue.ts | 4 +- .../ControlPanelSceneObject.tsx | 97 +++++++++++++++---- app/pages/PageEnigma/js/editor.ts | 2 +- .../reducers/appUiReducer/reducer.tsx | 13 +++ .../reducers/appUiReducer/types.tsx | 26 ++++- 5 files changed, 117 insertions(+), 25 deletions(-) diff --git a/app/pages/PageEnigma/Queue/Queue.ts b/app/pages/PageEnigma/Queue/Queue.ts index 7fba3bb6a7..2589416095 100644 --- a/app/pages/PageEnigma/Queue/Queue.ts +++ b/app/pages/PageEnigma/Queue/Queue.ts @@ -1,6 +1,6 @@ import { fromEngineActions } from "~/pages/PageEnigma/Queue/fromEngineActions"; import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions"; -import { QueueClip } from "~/pages/PageEnigma/models/track"; +import { ClipGroup, QueueClip } from "~/pages/PageEnigma/models/track"; import { XYZ } from "../datastructures/common"; import { toTimelineActions } from "./toTimelineActions"; @@ -27,7 +27,7 @@ class Queue { }: { queueName: string; action: fromEngineActions | toEngineActions | toTimelineActions ; - data: QueueClip | { currentTime: number } | { position: XYZ, rotation: XYZ, scale: XYZ }; + data: QueueClip | { currentTime: number } | { group:ClipGroup, object_uuid:string, object_name:string, version:string,position: XYZ, rotation: XYZ, scale: XYZ }; }) { if (!this._queue[queueName]) { this._queue[queueName] = []; diff --git a/app/pages/PageEnigma/comps/ControlPanelSceneObject/ControlPanelSceneObject.tsx b/app/pages/PageEnigma/comps/ControlPanelSceneObject/ControlPanelSceneObject.tsx index f8125e81bd..5218b5acfa 100644 --- a/app/pages/PageEnigma/comps/ControlPanelSceneObject/ControlPanelSceneObject.tsx +++ b/app/pages/PageEnigma/comps/ControlPanelSceneObject/ControlPanelSceneObject.tsx @@ -21,14 +21,27 @@ export const ControlPanelSceneObject = () => { const position = appUiState?.currentSceneObject.objectVectors.position; const rotation = appUiState?.currentSceneObject.objectVectors.rotation; - const scalar = appUiState?.currentSceneObject.objectVectors.scale; + const scale = appUiState?.currentSceneObject.objectVectors.scale; + const currentSceneObject = appUiState?.currentSceneObject; + const handlePositionChange = (xyz: XYZ) => { - if (appUiState) + if (appUiState) { + + + if (currentSceneObject == null) { + console.log("Missing Scene Object Position"); + return; + } + dispatchAppUiState({ type: ACTION_TYPES.UPDATE_CONTROLPANELS_SCENEOBJECT, payload: { currentSceneObject: { + group:currentSceneObject.group, + object_uuid:currentSceneObject.object_uuid, + object_name:currentSceneObject.object_name, + version:currentSceneObject.version, objectVectors: { position: { ...xyz }, rotation: appUiState.currentSceneObject.objectVectors.rotation, @@ -37,13 +50,24 @@ export const ControlPanelSceneObject = () => { }, }, }); + } }; const handleRotationChange = (xyz: XYZ) => { - if (appUiState) + if (appUiState) { + + if (currentSceneObject == null) { + console.log("Missing Scene Object Rotation"); + return; + } + dispatchAppUiState({ type: ACTION_TYPES.UPDATE_CONTROLPANELS_SCENEOBJECT, payload: { currentSceneObject: { + group:currentSceneObject.group, + object_uuid:currentSceneObject.object_uuid, + object_name:currentSceneObject.object_name, + version:currentSceneObject.version, objectVectors: { position: appUiState.currentSceneObject.objectVectors.position, rotation: { ...xyz }, @@ -52,13 +76,23 @@ export const ControlPanelSceneObject = () => { }, }, }); + } }; - const handleScalarChange = (xyz: XYZ) => { - if (appUiState) + const handleScaleChange = (xyz: XYZ) => { + if (appUiState) { + + if (currentSceneObject == null) { + console.log("Missing Scene Object Scale"); + return; + } dispatchAppUiState({ type: ACTION_TYPES.UPDATE_CONTROLPANELS_SCENEOBJECT, payload: { currentSceneObject: { + group:currentSceneObject.group, + object_uuid:currentSceneObject.object_uuid, + object_name:currentSceneObject.object_name, + version:currentSceneObject.version, objectVectors: { position: appUiState.currentSceneObject.objectVectors.position, rotation: appUiState.currentSceneObject.objectVectors.rotation, @@ -67,29 +101,50 @@ export const ControlPanelSceneObject = () => { }, }, }); + + } }; const handleOnAddKeyFrame = () => { if (appUiState) { - console.log(`${appUiState.currentSceneObject.objectVectors.position.x}`) - console.log(`${appUiState.currentSceneObject.objectVectors.position.y}`) - console.log(`${appUiState.currentSceneObject.objectVectors.position.z}`) - console.log(`${appUiState.currentSceneObject.objectVectors.rotation.x}`) - console.log(`${appUiState.currentSceneObject.objectVectors.rotation.y}`) - console.log(`${appUiState.currentSceneObject.objectVectors.rotation.z}`) + if (position == null || + rotation == null || + scale == null || + currentSceneObject == null) + { return; } - console.log(`${appUiState.currentSceneObject.objectVectors.scale.x}`) - console.log(`${appUiState.currentSceneObject.objectVectors.scale.y}`) - console.log(`${appUiState.currentSceneObject.objectVectors.scale.z}`) + console.log(`Position ${position.x}`) + console.log(`Position ${position.y}`) + console.log(`Position ${position.z}`) + + console.log(`Rotation ${rotation.x}`) + console.log(`Rotation ${rotation.y}`) + console.log(`Rotation ${rotation.z}`) + + console.log(`Scale ${scale.x}`) + console.log(`Scale ${scale.y}`) + console.log(`Scale ${scale.z}`) + + console.log(`Group ${currentSceneObject.group}`) + console.log(`Object UUID ${currentSceneObject.object_uuid}`) + console.log(`Object Name ${currentSceneObject.object_name}`) + console.log(`Object Version ${currentSceneObject.version}`) Queue.publish({queueName:QueueNames.TO_TIMELINE, action: toTimelineActions.ADD_KEYFRAME, data: { - position: appUiState.currentSceneObject.objectVectors.position, - rotation: appUiState.currentSceneObject.objectVectors.rotation, - scale:appUiState.currentSceneObject.objectVectors.scale + group:currentSceneObject.group, + object_uuid:currentSceneObject.object_uuid, + object_name:currentSceneObject.object_name, + version:currentSceneObject.version, + + position: currentSceneObject.objectVectors.position, + rotation: currentSceneObject.objectVectors.rotation, + scale:currentSceneObject.objectVectors.scale } }) + + } } @@ -139,10 +194,10 @@ export const ControlPanelSceneObject = () => {
Scale
diff --git a/app/pages/PageEnigma/js/editor.ts b/app/pages/PageEnigma/js/editor.ts index 7e5afc7f8b..c7c61bd3d4 100644 --- a/app/pages/PageEnigma/js/editor.ts +++ b/app/pages/PageEnigma/js/editor.ts @@ -1077,7 +1077,7 @@ class Editor { objectVectors: { position: { x: parseFloat(pos.x.toFixed(2)), y: parseFloat(pos.y.toFixed(2)), z: parseFloat(pos.z.toFixed(2)) }, rotation: { x: parseFloat(rot.x.toFixed(2)), y: parseFloat(rot.y.toFixed(2)), z: parseFloat(rot.z.toFixed(2)) }, - scalar: { x: parseFloat(scale.x.toFixed(2)), y: parseFloat(scale.y.toFixed(2)), z: parseFloat(scale.z.toFixed(2)) }, + scale: { x: parseFloat(scale.x.toFixed(2)), y: parseFloat(scale.y.toFixed(2)), z: parseFloat(scale.z.toFixed(2)) }, } } } diff --git a/app/pages/PageEnigma/reducers/appUiReducer/reducer.tsx b/app/pages/PageEnigma/reducers/appUiReducer/reducer.tsx index e95d3bb301..da1dc3b169 100644 --- a/app/pages/PageEnigma/reducers/appUiReducer/reducer.tsx +++ b/app/pages/PageEnigma/reducers/appUiReducer/reducer.tsx @@ -2,10 +2,15 @@ import { State, Action, ACTION_TYPES} from './types'; export function reducer(state: State, action: Action): State { switch(action.type){ + case ACTION_TYPES.SHOW_CONTROLPANELS_SCENEOBJECT:{ return{ ...state, currentSceneObject: { + group:action.payload.currentSceneObject.group, + object_name:action.payload.currentSceneObject.object_name, + object_uuid:action.payload.currentSceneObject.object_uuid, + version:action.payload.currentSceneObject.version, isShowing: true, objectVectors: {...action.payload.currentSceneObject.objectVectors}, }, @@ -15,6 +20,10 @@ export function reducer(state: State, action: Action): State { return{ ...state, currentSceneObject: { + group:action.payload.currentSceneObject.group, + object_name:action.payload.currentSceneObject.object_name, + object_uuid:action.payload.currentSceneObject.object_uuid, + version:action.payload.currentSceneObject.version, isShowing: state.currentSceneObject.isShowing, objectVectors: {...action.payload.currentSceneObject.objectVectors}, }, @@ -24,6 +33,10 @@ export function reducer(state: State, action: Action): State { return{ ...state, currentSceneObject: { + group:action.payload.currentSceneObject.group, + object_name:action.payload.currentSceneObject.object_name, + object_uuid:action.payload.currentSceneObject.object_uuid, + version:action.payload.currentSceneObject.version, isShowing: false, objectVectors: state.currentSceneObject.objectVectors, }, diff --git a/app/pages/PageEnigma/reducers/appUiReducer/types.tsx b/app/pages/PageEnigma/reducers/appUiReducer/types.tsx index 6772ea4d2c..205695680a 100644 --- a/app/pages/PageEnigma/reducers/appUiReducer/types.tsx +++ b/app/pages/PageEnigma/reducers/appUiReducer/types.tsx @@ -1,4 +1,5 @@ import { Simple3DVector } from "../../datastructures/common"; +import { ClipGroup } from "../../models/track"; export enum VIEW_MODES { EDITOR = 'editor', SIDE_BY_SIDE = 'side-by-side' @@ -21,6 +22,10 @@ export type State = { useFakeTimer?: number; } currentSceneObject:{ + group: ClipGroup, // TODO: add meta data to determine what it is a camera or a object or a character into prefab clips + object_uuid: string, + object_name: string, + version: string isShowing: boolean; objectVectors: Simple3DVector; } @@ -41,11 +46,26 @@ export enum ACTION_TYPES { }; export type Action = - | {type: ACTION_TYPES.HIDE_CONTROLPANELS_SCENEOBJECT} + | { // Not sure why we need this but type script won't stop complaning fix wil. + type: ACTION_TYPES.HIDE_CONTROLPANELS_SCENEOBJECT, + payload:{ + currentSceneObject: { + group: ClipGroup, + object_uuid: string, + object_name: string, + version: string, + objectVectors: Simple3DVector; + }; + } + } | { type: ACTION_TYPES.UPDATE_CONTROLPANELS_SCENEOBJECT, payload:{ currentSceneObject: { + group: ClipGroup, + object_uuid: string, + object_name: string, + version: string, objectVectors: Simple3DVector; }; } @@ -54,6 +74,10 @@ export type Action = type: ACTION_TYPES.SHOW_CONTROLPANELS_SCENEOBJECT, payload:{ currentSceneObject: { + group: ClipGroup, + object_uuid: string, + object_name: string, + version: string, objectVectors: Simple3DVector; }; } From a6bd1b7e31657ac9d190f0c37078a2ab971fadb9 Mon Sep 17 00:00:00 2001 From: Michael Chung <6252325+ArEnSc@users.noreply.github.com> Date: Sun, 31 Mar 2024 15:49:08 -0400 Subject: [PATCH 06/22] Should work properly need to add scene loading and saving. --- .../ControlPanelSceneObject.tsx | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app/pages/PageEnigma/comps/ControlPanelSceneObject/ControlPanelSceneObject.tsx b/app/pages/PageEnigma/comps/ControlPanelSceneObject/ControlPanelSceneObject.tsx index 5218b5acfa..245db0b3bb 100644 --- a/app/pages/PageEnigma/comps/ControlPanelSceneObject/ControlPanelSceneObject.tsx +++ b/app/pages/PageEnigma/comps/ControlPanelSceneObject/ControlPanelSceneObject.tsx @@ -114,17 +114,17 @@ export const ControlPanelSceneObject = () => { currentSceneObject == null) { return; } - console.log(`Position ${position.x}`) - console.log(`Position ${position.y}`) - console.log(`Position ${position.z}`) + // console.log(`Position ${position.x}`) + // console.log(`Position ${position.y}`) + // console.log(`Position ${position.z}`) - console.log(`Rotation ${rotation.x}`) - console.log(`Rotation ${rotation.y}`) - console.log(`Rotation ${rotation.z}`) + // console.log(`Rotation ${rotation.x}`) + // console.log(`Rotation ${rotation.y}`) + // console.log(`Rotation ${rotation.z}`) - console.log(`Scale ${scale.x}`) - console.log(`Scale ${scale.y}`) - console.log(`Scale ${scale.z}`) + // console.log(`Scale ${scale.x}`) + // console.log(`Scale ${scale.y}`) + // console.log(`Scale ${scale.z}`) console.log(`Group ${currentSceneObject.group}`) console.log(`Object UUID ${currentSceneObject.object_uuid}`) @@ -137,7 +137,7 @@ export const ControlPanelSceneObject = () => { object_uuid:currentSceneObject.object_uuid, object_name:currentSceneObject.object_name, version:currentSceneObject.version, - + position: currentSceneObject.objectVectors.position, rotation: currentSceneObject.objectVectors.rotation, scale:currentSceneObject.objectVectors.scale From a86927bff8b99a4e22b3d7fca7b11a34c4d9dde9 Mon Sep 17 00:00:00 2001 From: Michael Chung <6252325+ArEnSc@users.noreply.github.com> Date: Sun, 31 Mar 2024 16:01:50 -0400 Subject: [PATCH 07/22] fixed minor typescript issue. --- app/pages/PageEnigma/js/timeline.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/pages/PageEnigma/js/timeline.ts b/app/pages/PageEnigma/js/timeline.ts index a4cf18f054..a750f5a6b1 100644 --- a/app/pages/PageEnigma/js/timeline.ts +++ b/app/pages/PageEnigma/js/timeline.ts @@ -255,7 +255,7 @@ export class TimeLine { } // called by the editor update loop on each frame - public async update(deltatime: number) { + public async update() { //if (this.is_playing == false) return; // start and stop From 9347782812431a77a3bd2256b71cbf1994bad977 Mon Sep 17 00:00:00 2001 From: FromTheLOLAshes Date: Mon, 1 Apr 2024 09:18:02 +0700 Subject: [PATCH 08/22] Keyframe and speed ups. --- app/pages/PageEnigma/PageEnigmaComponent.tsx | 30 +- .../ControlPanelSceneObject.tsx | 127 +++--- .../comps/DragComponent/DragComponent.tsx | 33 ++ .../PageEnigma/comps/Scrubber/Scrubber.tsx | 29 ++ app/pages/PageEnigma/comps/Timeline/Audio.tsx | 16 +- .../PageEnigma/comps/Timeline/Camera.tsx | 9 +- .../PageEnigma/comps/Timeline/Character.tsx | 18 +- .../PageEnigma/comps/Timeline/Characters.tsx | 14 + .../comps/Timeline/ObjectGroups.tsx | 16 + .../Timeline/{Objects.tsx => ObjectTrack.tsx} | 10 +- .../PageEnigma/comps/Timeline/Timeline.tsx | 116 +----- .../PageEnigma/comps/Timeline/TrackClip.tsx | 26 +- .../comps/Timeline/utils/useQueueHandler.ts | 7 +- .../PageEnigma/comps/TimerGrid/TimerGrid.tsx | 55 +++ .../contexts/TrackContext/TrackContext.tsx | 77 +--- .../contexts/TrackContext/TrackProvider.tsx | 29 +- .../TrackContext/utils/useUpdateAudio.ts | 153 ------- .../TrackContext/utils/useUpdateCamera.ts | 122 ------ .../TrackContext/utils/useUpdateCharacters.ts | 383 ------------------ .../TrackContext/utils/useUpdateKeyframe.ts | 52 ++- .../TrackContext/utils/useUpdateObject.ts | 122 ------ app/pages/PageEnigma/store/audioGroup.ts | 136 +++++++ app/pages/PageEnigma/store/cameraGroup.ts | 99 +++++ app/pages/PageEnigma/store/characterGroups.ts | 331 +++++++++++++++ app/pages/PageEnigma/store/index.ts | 4 + app/pages/PageEnigma/store/objectGroup.ts | 107 +++++ app/pages/PageEnigma/store/sizing.ts | 11 + 27 files changed, 989 insertions(+), 1143 deletions(-) create mode 100644 app/pages/PageEnigma/comps/DragComponent/DragComponent.tsx create mode 100644 app/pages/PageEnigma/comps/Scrubber/Scrubber.tsx create mode 100644 app/pages/PageEnigma/comps/Timeline/Characters.tsx create mode 100644 app/pages/PageEnigma/comps/Timeline/ObjectGroups.tsx rename app/pages/PageEnigma/comps/Timeline/{Objects.tsx => ObjectTrack.tsx} (68%) create mode 100644 app/pages/PageEnigma/comps/TimerGrid/TimerGrid.tsx delete mode 100644 app/pages/PageEnigma/contexts/TrackContext/utils/useUpdateAudio.ts delete mode 100644 app/pages/PageEnigma/contexts/TrackContext/utils/useUpdateCamera.ts delete mode 100644 app/pages/PageEnigma/contexts/TrackContext/utils/useUpdateCharacters.ts delete mode 100644 app/pages/PageEnigma/contexts/TrackContext/utils/useUpdateObject.ts create mode 100644 app/pages/PageEnigma/store/audioGroup.ts create mode 100644 app/pages/PageEnigma/store/cameraGroup.ts create mode 100644 app/pages/PageEnigma/store/characterGroups.ts create mode 100644 app/pages/PageEnigma/store/objectGroup.ts diff --git a/app/pages/PageEnigma/PageEnigmaComponent.tsx b/app/pages/PageEnigma/PageEnigmaComponent.tsx index 9bcf8a62d1..b1a36217e6 100644 --- a/app/pages/PageEnigma/PageEnigmaComponent.tsx +++ b/app/pages/PageEnigma/PageEnigmaComponent.tsx @@ -22,21 +22,12 @@ import { appUiInitialStateValues, APPUI_VIEW_MODES, } from "./reducers"; -import { - canDrop, - clipLength, - currPosition, - dragId, - overTimeline, - scale, - timelineHeight, -} from "~/pages/PageEnigma/store"; +import { timelineHeight } from "~/pages/PageEnigma/store"; import { useSignals } from "@preact/signals-react/runtime"; +import { DragComponent } from "~/pages/PageEnigma/comps/DragComponent/DragComponent"; export const PageEnigmaComponent = () => { useSignals(); - const { currX, currY } = currPosition.value; - const [appUiState, dispatchAppUiState] = useReducer( appUiReducer, appUiInitialStateValues, @@ -129,22 +120,7 @@ export const PageEnigmaComponent = () => {
-
+
); }; diff --git a/app/pages/PageEnigma/comps/ControlPanelSceneObject/ControlPanelSceneObject.tsx b/app/pages/PageEnigma/comps/ControlPanelSceneObject/ControlPanelSceneObject.tsx index 245db0b3bb..3b8f4a764d 100644 --- a/app/pages/PageEnigma/comps/ControlPanelSceneObject/ControlPanelSceneObject.tsx +++ b/app/pages/PageEnigma/comps/ControlPanelSceneObject/ControlPanelSceneObject.tsx @@ -8,7 +8,7 @@ import { import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { AppUiContext } from "../../contexts/AppUiContext"; -import { Button, H4, H5, H6, InputVector } from "~/components"; +import { Button, H5, InputVector } from "~/components"; import { XYZ } from "../../datastructures/common"; import { ACTION_TYPES } from "../../reducers/appUiReducer/types"; @@ -16,6 +16,7 @@ import { ACTION_TYPES } from "../../reducers/appUiReducer/types"; import { QueueNames } from "../../Queue/QueueNames"; import Queue from "~/pages/PageEnigma/Queue/Queue"; import { toTimelineActions } from "../../Queue/toTimelineActions"; +import { QueueKeyframe } from "~/pages/PageEnigma/models/track"; export const ControlPanelSceneObject = () => { const [appUiState, dispatchAppUiState] = useContext(AppUiContext); @@ -24,24 +25,21 @@ export const ControlPanelSceneObject = () => { const scale = appUiState?.currentSceneObject.objectVectors.scale; const currentSceneObject = appUiState?.currentSceneObject; - const handlePositionChange = (xyz: XYZ) => { if (appUiState) { - - if (currentSceneObject == null) { - console.log("Missing Scene Object Position"); - return; + console.log("Missing Scene Object Position"); + return; } dispatchAppUiState({ type: ACTION_TYPES.UPDATE_CONTROLPANELS_SCENEOBJECT, payload: { currentSceneObject: { - group:currentSceneObject.group, - object_uuid:currentSceneObject.object_uuid, - object_name:currentSceneObject.object_name, - version:currentSceneObject.version, + group: currentSceneObject.group, + object_uuid: currentSceneObject.object_uuid, + object_name: currentSceneObject.object_name, + version: currentSceneObject.version, objectVectors: { position: { ...xyz }, rotation: appUiState.currentSceneObject.objectVectors.rotation, @@ -54,20 +52,19 @@ export const ControlPanelSceneObject = () => { }; const handleRotationChange = (xyz: XYZ) => { if (appUiState) { - - if (currentSceneObject == null) { - console.log("Missing Scene Object Rotation"); - return; - } + if (currentSceneObject == null) { + console.log("Missing Scene Object Rotation"); + return; + } dispatchAppUiState({ type: ACTION_TYPES.UPDATE_CONTROLPANELS_SCENEOBJECT, payload: { currentSceneObject: { - group:currentSceneObject.group, - object_uuid:currentSceneObject.object_uuid, - object_name:currentSceneObject.object_name, - version:currentSceneObject.version, + group: currentSceneObject.group, + object_uuid: currentSceneObject.object_uuid, + object_name: currentSceneObject.object_name, + version: currentSceneObject.version, objectVectors: { position: appUiState.currentSceneObject.objectVectors.position, rotation: { ...xyz }, @@ -80,19 +77,18 @@ export const ControlPanelSceneObject = () => { }; const handleScaleChange = (xyz: XYZ) => { if (appUiState) { - if (currentSceneObject == null) { - console.log("Missing Scene Object Scale"); - return; + console.log("Missing Scene Object Scale"); + return; } dispatchAppUiState({ type: ACTION_TYPES.UPDATE_CONTROLPANELS_SCENEOBJECT, payload: { currentSceneObject: { - group:currentSceneObject.group, - object_uuid:currentSceneObject.object_uuid, - object_name:currentSceneObject.object_name, - version:currentSceneObject.version, + group: currentSceneObject.group, + object_uuid: currentSceneObject.object_uuid, + object_name: currentSceneObject.object_name, + version: currentSceneObject.version, objectVectors: { position: appUiState.currentSceneObject.objectVectors.position, rotation: appUiState.currentSceneObject.objectVectors.rotation, @@ -101,52 +97,53 @@ export const ControlPanelSceneObject = () => { }, }, }); - - } + } }; const handleOnAddKeyFrame = () => { if (appUiState) { + if ( + position == null || + rotation == null || + scale == null || + currentSceneObject == null + ) { + return; + } - if (position == null || - rotation == null || - scale == null || - currentSceneObject == null) - { return; } + // console.log(`Position ${position.x}`) + // console.log(`Position ${position.y}`) + // console.log(`Position ${position.z}`) - // console.log(`Position ${position.x}`) - // console.log(`Position ${position.y}`) - // console.log(`Position ${position.z}`) + // console.log(`Rotation ${rotation.x}`) + // console.log(`Rotation ${rotation.y}`) + // console.log(`Rotation ${rotation.z}`) - // console.log(`Rotation ${rotation.x}`) - // console.log(`Rotation ${rotation.y}`) - // console.log(`Rotation ${rotation.z}`) - - // console.log(`Scale ${scale.x}`) - // console.log(`Scale ${scale.y}`) - // console.log(`Scale ${scale.z}`) + // console.log(`Scale ${scale.x}`) + // console.log(`Scale ${scale.y}`) + // console.log(`Scale ${scale.z}`) - console.log(`Group ${currentSceneObject.group}`) - console.log(`Object UUID ${currentSceneObject.object_uuid}`) - console.log(`Object Name ${currentSceneObject.object_name}`) - console.log(`Object Version ${currentSceneObject.version}`) + console.log(`Group ${currentSceneObject.group}`); + console.log(`Object UUID ${currentSceneObject.object_uuid}`); + console.log(`Object Name ${currentSceneObject.object_name}`); + console.log(`Object Version ${currentSceneObject.version}`); - Queue.publish({queueName:QueueNames.TO_TIMELINE, action: toTimelineActions.ADD_KEYFRAME, - data: { - group:currentSceneObject.group, - object_uuid:currentSceneObject.object_uuid, - object_name:currentSceneObject.object_name, - version:currentSceneObject.version, + Queue.publish({ + queueName: QueueNames.TO_TIMELINE, + action: toTimelineActions.ADD_KEYFRAME, + data: { + group: currentSceneObject.group, + object_uuid: currentSceneObject.object_uuid, + object_name: currentSceneObject.object_name, + version: 1, - position: currentSceneObject.objectVectors.position, - rotation: currentSceneObject.objectVectors.rotation, - scale:currentSceneObject.objectVectors.scale - } - }) - - + position: currentSceneObject.objectVectors.position, + rotation: currentSceneObject.objectVectors.rotation, + scale: currentSceneObject.objectVectors.scale, + } as QueueKeyframe, + }); } - } + }; return ( {
- - )} + {/*{selectedItem === clip.clip_uuid && (*/} + {/* */} + {/* */} + {/* */} + {/*)}*/} ); }; diff --git a/app/pages/PageEnigma/comps/Timeline/utils/useQueueHandler.ts b/app/pages/PageEnigma/comps/Timeline/utils/useQueueHandler.ts index 3c9cd3645e..f0b4d0a97a 100644 --- a/app/pages/PageEnigma/comps/Timeline/utils/useQueueHandler.ts +++ b/app/pages/PageEnigma/comps/Timeline/utils/useQueueHandler.ts @@ -23,7 +23,7 @@ export function useQueueHandler() { const { addKeyframe } = useContext(TrackContext); const handleFromEngineActions = useCallback(({ action, data }: Arguments) => { - console.log(`FROM ENGINE:${action} ${data}`); + console.log("FROM ENGINE", action, data); switch (action) { case fromEngineActions.UPDATE_TIME: currentTime.value = (data as UpdateTime).currentTime; @@ -34,10 +34,11 @@ export function useQueueHandler() { }, []); const handleToTimelineActions = useCallback(({ action, data }: Arguments) => { - console.log(`FROM ENGINE:${action} ${data}`); + console.log("TO TIMELINE", action, data); switch (action) { case toTimelineActions.ADD_KEYFRAME: - addKeyframe(data as QueueKeyframe); + console.log(1); + addKeyframe(data as QueueKeyframe, currentTime.value); break; default: throw new Error(`Unknown action ${action}`); diff --git a/app/pages/PageEnigma/comps/TimerGrid/TimerGrid.tsx b/app/pages/PageEnigma/comps/TimerGrid/TimerGrid.tsx new file mode 100644 index 0000000000..6a160f6896 --- /dev/null +++ b/app/pages/PageEnigma/comps/TimerGrid/TimerGrid.tsx @@ -0,0 +1,55 @@ +import { filmLength, fullHeight, scale } from "~/pages/PageEnigma/store"; +import { Fragment } from "react"; + +export const TimerGrid = () => { + const sectionWidth = 60 * 4 * scale.value; + + return ( +
+ {Array(filmLength.value) + .fill(0) + .map((_, index) => ( + +
+ 00:{index < 10 ? "0" + index.toString() : index.toString()} +
+
+ + ))} +
+ 00: + {filmLength.value < 10 + ? "0" + filmLength.value.toString() + : filmLength.value.toString()} +
+
+
+ ); +}; diff --git a/app/pages/PageEnigma/contexts/TrackContext/TrackContext.tsx b/app/pages/PageEnigma/contexts/TrackContext/TrackContext.tsx index 66b0899179..38a514de09 100644 --- a/app/pages/PageEnigma/contexts/TrackContext/TrackContext.tsx +++ b/app/pages/PageEnigma/contexts/TrackContext/TrackContext.tsx @@ -10,63 +10,14 @@ import { } from "~/pages/PageEnigma/models/track"; export const TrackContext = createContext<{ - // timeline characters and it's functions - characters: CharacterGroup[]; - updateCharacters: (options: { - type: "animations" | "positions" | "lipSync"; - id: string; - length: number; - offset: number; - }) => void; - toggleLipSyncMute: (characterId: string) => void; - addCharacterAnimation: (options: { - clipId: string; - characterId: string; - animationClips: MediaClip[]; - offset: number; - }) => void; - addCharacterAudio: (options: { - clipId: string; - characterId: string; - audioClips: MediaClip[]; - offset: number; - }) => void; - selectCharacterClip: (clipId: string) => void; - deleteCharacterClip: (clipId: string) => void; - - // timeline camera group - camera: CameraGroup | null; - updateCamera: (options: { id: string; offset: number }) => void; - selectCameraKeyframe: (clipId: string) => void; - - // timeline global audio group - audio: AudioGroup | null; - updateAudio: (options: { - id: string; - length: number; - offset: number; - }) => void; - toggleAudioMute: () => void; - addGlobalAudio: ( - clipId: string, - animationClips: MediaClip[], - offset: number, - ) => void; - selectAudioClip: (clipId: string) => void; - deleteAudioClip: (clipId: string) => void; - - // timeline objects group - objects: ObjectGroup; - updateObject: (options: { id: string; offset: number }) => void; + // keyframes + addKeyframe: (keyframe: QueueKeyframe, offset: number) => void; + deleteKeyframe: (keyframe: Keyframe) => void; // current - only select one item - will be replaced selectedItem: string | null; selectItem: (itemId: string) => void; - // keyframes - addKeyframe: (keyframe: QueueKeyframe, offset: number) => void; - deleteKeyframe: (keyframe: Keyframe) => void; - // sidebar clips animationClips: MediaClip[]; audioClips: MediaClip[]; @@ -79,28 +30,6 @@ export const TrackContext = createContext<{ ) => void; endDrag: () => void; }>({ - characters: [], - updateCharacters: () => {}, - toggleLipSyncMute: () => {}, - addCharacterAnimation: () => {}, - addCharacterAudio: () => {}, - selectCharacterClip: () => {}, - deleteCharacterClip: () => {}, - - camera: null, - updateCamera: () => {}, - selectCameraKeyframe: () => {}, - - audio: null, - updateAudio: () => {}, - toggleAudioMute: () => {}, - selectAudioClip: () => {}, - addGlobalAudio: () => {}, - deleteAudioClip: () => {}, - - objects: { id: "", objects: [] }, - updateObject: () => {}, - addKeyframe: () => {}, deleteKeyframe: () => {}, diff --git a/app/pages/PageEnigma/contexts/TrackContext/TrackProvider.tsx b/app/pages/PageEnigma/contexts/TrackContext/TrackProvider.tsx index 49216b1689..106d839efe 100644 --- a/app/pages/PageEnigma/contexts/TrackContext/TrackProvider.tsx +++ b/app/pages/PageEnigma/contexts/TrackContext/TrackProvider.tsx @@ -1,12 +1,9 @@ import { TrackContext } from "~/pages/PageEnigma/contexts/TrackContext/TrackContext"; import { ReactNode, useCallback, useEffect, useMemo, useState } from "react"; -import useUpdateCharacters from "~/pages/PageEnigma/contexts/TrackContext/utils/useUpdateCharacters"; -import useUpdateCamera from "~/pages/PageEnigma/contexts/TrackContext/utils/useUpdateCamera"; -import useUpdateAudio from "~/pages/PageEnigma/contexts/TrackContext/utils/useUpdateAudio"; -import useUpdateObject from "~/pages/PageEnigma/contexts/TrackContext/utils/useUpdateObject"; import { ClipType, MediaClip } from "~/pages/PageEnigma/models/track"; import useUpdateDragDrop from "~/pages/PageEnigma/contexts/TrackContext/utils/useUpdateDragDrop"; import { + addCharacterAnimation, canDrop, dragId, dragType, @@ -21,16 +18,6 @@ interface Props { } export const TrackProvider = ({ children }: Props) => { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { addCharacterKeyframe, deleteCharacterKeyframe, ...characters } = - useUpdateCharacters(); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { addCameraKeyframe, deleteCameraKeyframe, ...camera } = - useUpdateCamera(); - const audio = useUpdateAudio(); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { addObjectKeyframe, deleteObjectKeyframe, ...objects } = - useUpdateObject(); const keyframes = useUpdateKeyframe(); const { endDrag, ...dragDrop } = useUpdateDragDrop(); @@ -65,7 +52,7 @@ export const TrackProvider = ({ children }: Props) => { const dropClip = useCallback(() => { if (canDrop.value) { if (dragType.value === "animations") { - characters.addCharacterAnimation({ + addCharacterAnimation({ clipId: dragId.value!, characterId: dropId.value, animationClips, @@ -74,7 +61,7 @@ export const TrackProvider = ({ children }: Props) => { } } endDrag(); - }, [animationClips, characters, endDrag]); + }, [animationClips, endDrag]); const fullWidth = useMemo(() => { return length * 60 * 4 * scale; @@ -118,11 +105,6 @@ export const TrackProvider = ({ children }: Props) => { const values = useMemo(() => { return { - ...characters, - ...camera, - ...audio, - ...objects, - ...keyframes, selectItem, @@ -143,11 +125,6 @@ export const TrackProvider = ({ children }: Props) => { setTimelineHeight, }; }, [ - characters, - camera, - audio, - objects, - keyframes, selectItem, diff --git a/app/pages/PageEnigma/contexts/TrackContext/utils/useUpdateAudio.ts b/app/pages/PageEnigma/contexts/TrackContext/utils/useUpdateAudio.ts deleted file mode 100644 index 5804527373..0000000000 --- a/app/pages/PageEnigma/contexts/TrackContext/utils/useUpdateAudio.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { useCallback, useState } from "react"; -import { - AudioGroup, - Clip, - ClipGroup, - ClipType, - MediaClip, -} from "~/pages/PageEnigma/models/track"; -import Queue from "~/pages/PageEnigma/Queue/Queue"; -import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames"; -import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions"; -import * as uuid from "uuid"; - -export default function useUpdateAudio() { - const [audio, setAudio] = useState({ - id: "AU1", - muted: false, - clips: [], - }); - - const updateAudio = useCallback( - ({ - id, - offset, - length, - }: { - id: string; - length: number; - offset: number; - }) => { - setAudio((oldAudio) => { - const newClips = [...oldAudio.clips]; - const clipIndex = newClips.findIndex((row) => row.clip_uuid === id); - if (clipIndex === -1) { - return { ...oldAudio }; - } - const clip = newClips[clipIndex]; - clip.offset = offset; - clip.length = length; - - Queue.publish({ - queueName: QueueNames.TO_ENGINE, - action: toEngineActions.UPDATE_CLIP, - data: clip, - }); - - return { - ...oldAudio, - clips: newClips, - }; - }); - }, - [], - ); - - const addGlobalAudio = useCallback( - (dragId: string, audioClips: MediaClip[], offset: number) => { - const clip = audioClips.find((row) => row.media_id === dragId); - if (!clip) { - return; - } - - const clip_uuid = uuid.v4(); - const newClip = { - ...clip, - type: ClipType.AUDIO, - group: ClipGroup.GLOBAL_AUDIO, - offset, - clip_uuid, - } as Clip; - - setAudio((oldAudio) => { - return { - ...oldAudio, - clips: [...oldAudio.clips, newClip], - }; - }); - - Queue.publish({ - queueName: QueueNames.TO_ENGINE, - action: toEngineActions.ADD_CLIP, - data: newClip, - }); - }, - [], - ); - - const toggleAudioMute = useCallback(() => { - setAudio((oldAudio) => { - Queue.publish({ - queueName: QueueNames.TO_ENGINE, - action: oldAudio?.muted ? toEngineActions.UNMUTE : toEngineActions.MUTE, - data: { - version: 1, - type: ClipType.AUDIO, - group: ClipGroup.GLOBAL_AUDIO, - }, - }); - - return { - ...oldAudio, - muted: !oldAudio.muted, - }; - }); - }, []); - - const selectAudioClip = useCallback((clipId: string) => { - setAudio((oldAudio) => { - return { - ...oldAudio, - clips: [ - ...oldAudio.clips.map((clip) => { - return { - ...clip, - selected: - clip.clip_uuid === clipId ? !clip.selected : clip.selected, - }; - }), - ], - }; - }); - }, []); - - const deleteAudioClip = useCallback((clipId: string) => { - setAudio((oldAudio) => { - return { - ...oldAudio, - clips: [ - ...oldAudio.clips.filter((clip) => { - if (clip.clip_uuid === clipId) { - Queue.publish({ - queueName: QueueNames.TO_ENGINE, - action: toEngineActions.DELETE_CLIP, - data: clip!, - }); - return false; - } - return true; - }), - ], - }; - }); - }, []); - - return { - audio, - updateAudio, - toggleAudioMute, - selectAudioClip, - addGlobalAudio, - deleteAudioClip, - }; -} diff --git a/app/pages/PageEnigma/contexts/TrackContext/utils/useUpdateCamera.ts b/app/pages/PageEnigma/contexts/TrackContext/utils/useUpdateCamera.ts deleted file mode 100644 index 741e71a394..0000000000 --- a/app/pages/PageEnigma/contexts/TrackContext/utils/useUpdateCamera.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { useCallback, useState } from "react"; -import { - CameraGroup, - Keyframe, - QueueKeyframe, -} from "~/pages/PageEnigma/models/track"; -import Queue from "~/pages/PageEnigma/Queue/Queue"; -import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames"; -import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions"; -import * as uuid from "uuid"; - -export default function useUpdateCamera() { - const [camera, setCamera] = useState({ - id: "CA1", - keyframes: [], - }); - - const updateCamera = useCallback( - ({ id, offset }: { id: string; offset: number }) => { - setCamera((oldCamera) => { - const newKeyframes = [...oldCamera.keyframes]; - const keyframe = newKeyframes.find((row) => row.keyframe_uuid === id); - if (!keyframe) { - return { ...oldCamera }; - } - keyframe.offset = offset; - - Queue.publish({ - queueName: QueueNames.TO_ENGINE, - action: toEngineActions.UPDATE_KEYFRAME, - data: keyframe, - }); - - return { - ...oldCamera, - clips: newKeyframes, - }; - }); - }, - [], - ); - - const addCameraKeyframe = useCallback( - (keyframe: QueueKeyframe, offset: number) => { - const newKeyframe = { - version: keyframe.version, - keyframe_uuid: uuid.v4(), - group: keyframe.group, - object_uuid: keyframe.object_uuid, - offset, - position: keyframe.position, - rotation: keyframe.rotation, - scale: keyframe.scale, - selected: false, - } as Keyframe; - - setCamera((oldCamera) => { - return { - ...oldCamera, - keyframes: [...oldCamera.keyframes, newKeyframe].sort( - (keyFrameA, keyframeB) => keyFrameA.offset - keyframeB.offset, - ), - }; - }); - - Queue.publish({ - queueName: QueueNames.TO_ENGINE, - action: toEngineActions.ADD_KEYFRAME, - data: newKeyframe, - }); - }, - [], - ); - - const selectCameraKeyframe = useCallback((keyframeId: string) => { - setCamera((oldCamera) => { - return { - ...oldCamera, - clips: [ - ...oldCamera.keyframes.map((keyframe) => { - return { - ...keyframe, - selected: - keyframe.keyframe_uuid === keyframeId - ? !keyframe.selected - : keyframe.selected, - }; - }), - ], - }; - }); - }, []); - - const deleteCameraKeyframe = useCallback((keyframeId: string) => { - setCamera((oldCamera) => { - return { - ...oldCamera, - keyframes: [ - ...oldCamera.keyframes.filter((keyframe) => { - if (keyframe.keyframe_uuid === keyframeId) { - Queue.publish({ - queueName: QueueNames.TO_ENGINE, - action: toEngineActions.DELETE_KEYFRAME, - data: keyframe, - }); - return false; - } - return true; - }), - ], - }; - }); - }, []); - - return { - camera, - updateCamera, - selectCameraKeyframe, - addCameraKeyframe, - deleteCameraKeyframe, - }; -} diff --git a/app/pages/PageEnigma/contexts/TrackContext/utils/useUpdateCharacters.ts b/app/pages/PageEnigma/contexts/TrackContext/utils/useUpdateCharacters.ts deleted file mode 100644 index 27a4a14435..0000000000 --- a/app/pages/PageEnigma/contexts/TrackContext/utils/useUpdateCharacters.ts +++ /dev/null @@ -1,383 +0,0 @@ -import { useCallback, useState } from "react"; -import { - Clip, - CharacterGroup, - ClipGroup, - ClipType, - MediaClip, - QueueKeyframe, - Keyframe, -} from "~/pages/PageEnigma/models/track"; -import * as uuid from "uuid"; -import Queue from "~/pages/PageEnigma/Queue/Queue"; -import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames"; -import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions"; - -export default function useUpdateCharacters() { - const [characters, setCharacters] = useState([ - { - id: "CH1", - muted: false, - animationClips: [], - positionKeyframes: [], - lipSyncClips: [], - }, - ]); - - const updateCharacters = useCallback( - ({ - type, - id, - offset, - length, - }: { - type: "animations" | "positions" | "lipSync"; - id: string; - length: number; - offset: number; - }) => { - if (type === "animations") { - setCharacters((oldCharacters) => { - return oldCharacters.map((character) => { - const newAnimationClips = [...character.animationClips]; - const clipIndex = newAnimationClips.findIndex( - (row) => row.clip_uuid === id, - ); - if (clipIndex === -1) { - return { ...character }; - } - const clip = newAnimationClips[clipIndex]; - clip.offset = offset; - clip.length = length; - - Queue.publish({ - queueName: QueueNames.TO_ENGINE, - action: toEngineActions.UPDATE_CLIP, - data: clip, - }); - - return { - ...character, - animationClips: newAnimationClips, - }; - }); - }); - return; - } - - if (type === "positions") { - setCharacters((oldCharacters) => { - return oldCharacters.map((character) => { - const newPositionKeyframes = [...character.positionKeyframes]; - const keyframeIndex = newPositionKeyframes.findIndex( - (row) => row.keyframe_uuid === id, - ); - if (keyframeIndex === -1) { - return { ...character }; - } - const keyframe = newPositionKeyframes[keyframeIndex]; - keyframe.offset = offset; - - Queue.publish({ - queueName: QueueNames.TO_ENGINE, - action: toEngineActions.UPDATE_CLIP, - data: keyframe, - }); - - return { - ...character, - positionClips: newPositionKeyframes, - }; - }); - }); - return; - } - if (type === "lipSync") { - setCharacters((oldCharacters) => { - return oldCharacters.map((character) => { - const newLipSyncClips = [...character.lipSyncClips]; - const clipIndex = newLipSyncClips.findIndex( - (row) => row.clip_uuid === id, - ); - if (clipIndex === -1) { - return { ...character }; - } - const clip = newLipSyncClips[clipIndex]; - clip.offset = offset; - clip.length = length; - - Queue.publish({ - queueName: QueueNames.TO_ENGINE, - action: toEngineActions.UPDATE_CLIP, - data: clip, - }); - - return { - ...character, - lipSyncClips: newLipSyncClips, - }; - }); - }); - } - }, - [], - ); - - const addCharacterAnimation = useCallback( - ({ - clipId, - characterId, - animationClips, - offset, - }: { - clipId: string; - characterId: string; - animationClips: MediaClip[]; - offset: number; - }) => { - const clip = animationClips.find((row) => row.media_id === clipId); - if (!clip) { - return; - } - - const clip_uuid = uuid.v4(); - const newClip = { - ...clip, - group: ClipGroup.CHARACTER, - offset, - clip_uuid, - object_uuid: characterId, - } as Clip; - - setCharacters((oldCharacters) => { - return oldCharacters.map((character) => { - if (character.id !== characterId) { - return { ...character }; - } - return { - ...character, - animationClips: [...character.animationClips, newClip].sort( - (clipA, clipB) => clipA.offset - clipB.offset, - ), - }; - }); - }); - - Queue.publish({ - queueName: QueueNames.TO_ENGINE, - action: toEngineActions.ADD_CLIP, - data: newClip, - }); - }, - [], - ); - - const addCharacterAudio = useCallback( - ({ - clipId, - characterId, - audioClips, - offset, - }: { - clipId: string; - characterId: string; - audioClips: MediaClip[]; - offset: number; - }) => { - const clip = audioClips.find((row) => row.media_id === clipId); - if (!clip) { - return; - } - - const clip_uuid = uuid.v4(); - const newClip = { - ...clip, - type: ClipType.AUDIO, - group: ClipGroup.CHARACTER, - offset, - clip_uuid, - object_uuid: characterId, - } as Clip; - - setCharacters((oldCharacters) => { - return oldCharacters.map((character) => { - if (character.id !== characterId) { - return { ...character }; - } - return { - ...character, - lipSyncClips: [...character.lipSyncClips, newClip].sort( - (clipA, clipB) => clipA.offset - clipB.offset, - ), - }; - }); - }); - - Queue.publish({ - queueName: QueueNames.TO_ENGINE, - action: toEngineActions.ADD_CLIP, - data: newClip, - }); - }, - [], - ); - - const addCharacterKeyframe = useCallback( - (keyframe: QueueKeyframe, offset: number) => { - const newKeyframe = { - version: keyframe.version, - keyframe_uuid: uuid.v4(), - group: keyframe.group, - object_uuid: keyframe.object_uuid, - offset, - position: keyframe.position, - rotation: keyframe.rotation, - scale: keyframe.scale, - selected: false, - } as Keyframe; - - setCharacters((oldCharacters) => { - return oldCharacters.map((character) => { - if (character.id !== keyframe.object_uuid) { - return { ...character }; - } - return { - ...character, - positionKeyframes: [ - ...character.positionKeyframes, - newKeyframe, - ].sort( - (keyFrameA, keyframeB) => keyFrameA.offset - keyframeB.offset, - ), - }; - }); - }); - - Queue.publish({ - queueName: QueueNames.TO_ENGINE, - action: toEngineActions.ADD_KEYFRAME, - data: newKeyframe, - }); - }, - [], - ); - - const toggleLipSyncMute = useCallback((characterId: string) => { - setCharacters((oldCharacters) => { - return oldCharacters.map((character) => { - if (character.id === characterId) { - Queue.publish({ - queueName: QueueNames.TO_ENGINE, - action: character?.muted - ? toEngineActions.UNMUTE - : toEngineActions.MUTE, - data: { - version: 1, - type: ClipType.AUDIO, - group: ClipGroup.CHARACTER, - object_uuid: characterId, - }, - }); - } - - return { - ...character, - muted: - character.id === characterId ? !character.muted : character.muted, - }; - }); - }); - }, []); - - const selectCharacterClip = useCallback((clipId: string) => { - setCharacters((oldCharacters) => { - return [ - ...oldCharacters.map((character) => ({ - ...character, - animationClips: character.animationClips.map((clip) => ({ - ...clip, - selected: - clip.clip_uuid === clipId ? !clip.selected : clip.selected, - })), - positionClips: character.positionKeyframes.map((keyframe) => ({ - ...keyframe, - selected: - keyframe.keyframe_uuid === clipId - ? !keyframe.selected - : keyframe.selected, - })), - lipSyncClips: character.lipSyncClips.map((clip) => ({ - ...clip, - selected: - clip.clip_uuid === clipId ? !clip.selected : clip.selected, - })), - })), - ]; - }); - }, []); - - const deleteCharacterClip = useCallback((clipId: string) => { - setCharacters((oldCharacters) => { - return [ - ...oldCharacters.map((character) => ({ - ...character, - animationClips: character.animationClips.filter((clip) => { - if (clip.clip_uuid === clipId) { - Queue.publish({ - queueName: QueueNames.TO_ENGINE, - action: toEngineActions.DELETE_CLIP, - data: clip!, - }); - return false; - } - return true; - }), - lipSyncClips: character.lipSyncClips.filter((clip) => { - if (clip.clip_uuid === clipId) { - Queue.publish({ - queueName: QueueNames.TO_ENGINE, - action: toEngineActions.DELETE_CLIP, - data: clip!, - }); - return false; - } - return true; - }), - })), - ]; - }); - }, []); - - const deleteCharacterKeyframe = useCallback((keyframe: Keyframe) => { - setCharacters((oldCharacters) => { - return [ - ...oldCharacters.map((character) => ({ - ...character, - positionClips: character.positionKeyframes.filter((row) => { - if (row.keyframe_uuid === keyframe.keyframe_uuid) { - Queue.publish({ - queueName: QueueNames.TO_ENGINE, - action: toEngineActions.DELETE_KEYFRAME, - data: row, - }); - return false; - } - return true; - }), - })), - ]; - }); - }, []); - - return { - characters, - updateCharacters, - toggleLipSyncMute, - addCharacterAnimation, - addCharacterAudio, - selectCharacterClip, - deleteCharacterClip, - addCharacterKeyframe, - deleteCharacterKeyframe, - }; -} diff --git a/app/pages/PageEnigma/contexts/TrackContext/utils/useUpdateKeyframe.ts b/app/pages/PageEnigma/contexts/TrackContext/utils/useUpdateKeyframe.ts index ebd8ffb6a4..41153a9bcf 100644 --- a/app/pages/PageEnigma/contexts/TrackContext/utils/useUpdateKeyframe.ts +++ b/app/pages/PageEnigma/contexts/TrackContext/utils/useUpdateKeyframe.ts @@ -4,37 +4,33 @@ import { QueueKeyframe, Keyframe, } from "~/pages/PageEnigma/models/track"; -import useUpdateObject from "~/pages/PageEnigma/contexts/TrackContext/utils/useUpdateObject"; -import useUpdateCharacters from "~/pages/PageEnigma/contexts/TrackContext/utils/useUpdateCharacters"; +import { + addCharacterKeyframe, + addObjectKeyframe, + deleteCharacterKeyframe, + deleteObjectKeyframe, +} from "~/pages/PageEnigma/store"; export default function useUpdateKeyframe() { - const { addObjectKeyframe, deleteObjectKeyframe } = useUpdateObject(); - const { addCharacterKeyframe, deleteCharacterKeyframe } = - useUpdateCharacters(); + const addKeyframe = useCallback((keyframe: QueueKeyframe, offset: number) => { + console.log(keyframe, offset); + if (keyframe.group === ClipGroup.OBJECT) { + console.log(2); + addObjectKeyframe(keyframe, offset); + } + if (keyframe.group === ClipGroup.CHARACTER) { + addCharacterKeyframe(keyframe, offset); + } + }, []); - const addKeyframe = useCallback( - (keyframe: QueueKeyframe, offset: number) => { - if (keyframe.group === ClipGroup.OBJECT) { - addObjectKeyframe(keyframe, offset); - } - if (keyframe.group === ClipGroup.CHARACTER) { - addCharacterKeyframe(keyframe, offset); - } - }, - [addCharacterKeyframe, addObjectKeyframe], - ); - - const deleteKeyframe = useCallback( - (keyframe: Keyframe) => { - if (keyframe.group === ClipGroup.OBJECT) { - deleteObjectKeyframe(keyframe); - } - if (keyframe.group === ClipGroup.CHARACTER) { - deleteCharacterKeyframe(keyframe); - } - }, - [deleteCharacterKeyframe, deleteObjectKeyframe], - ); + const deleteKeyframe = useCallback((keyframe: Keyframe) => { + if (keyframe.group === ClipGroup.OBJECT) { + deleteObjectKeyframe(keyframe); + } + if (keyframe.group === ClipGroup.CHARACTER) { + deleteCharacterKeyframe(keyframe); + } + }, []); return { addKeyframe, diff --git a/app/pages/PageEnigma/contexts/TrackContext/utils/useUpdateObject.ts b/app/pages/PageEnigma/contexts/TrackContext/utils/useUpdateObject.ts deleted file mode 100644 index 4d942363d2..0000000000 --- a/app/pages/PageEnigma/contexts/TrackContext/utils/useUpdateObject.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { useCallback, useState } from "react"; -import { - ObjectGroup, - QueueKeyframe, - Keyframe, -} from "~/pages/PageEnigma/models/track"; -import * as uuid from "uuid"; - -export default function useUpdateObject() { - const [objects, setObjects] = useState({ - id: "OB1", - objects: [], - }); - - const updateObject = useCallback( - ({ id, offset }: { id: string; offset: number }) => { - setObjects((oldObject) => { - return { - id: oldObject.id, - objects: oldObject.objects.map((object) => ({ - object_uuid: object.object_uuid, - name: object.name, - keyframes: object.keyframes.map((keyFrame) => ({ - ...keyFrame, - offset: keyFrame.keyframe_uuid === id ? offset : keyFrame.offset, - })), - })), - }; - }); - }, - [], - ); - - const addObjectKeyframe = useCallback( - (keyframe: QueueKeyframe, offset: number) => { - setObjects((oldObjectGroup) => { - const obj = objects.objects.find( - (row) => row.object_uuid === keyframe.object_uuid, - ); - - const newObject = obj ?? { - object_uuid: keyframe.object_uuid, - name: keyframe.object_name ?? "unknown", - keyframes: [] as Keyframe[], - }; - newObject.keyframes.push({ - version: keyframe.version, - keyframe_uuid: uuid.v4(), - group: keyframe.group, - object_uuid: keyframe.object_uuid, - offset, - position: keyframe.position, - rotation: keyframe.rotation, - scale: keyframe.scale, - selected: false, - } as Keyframe); - newObject.keyframes.sort( - (keyframeA, keyframeB) => keyframeA.offset - keyframeB.offset, - ); - - return { - ...oldObjectGroup, - objects: [ - ...oldObjectGroup.objects.filter( - (object) => object.object_uuid !== keyframe.object_uuid, - ), - newObject, - ].sort((objA, objB) => - objA.object_uuid < objB.object_uuid ? -1 : 1, - ), - }; - }); - }, - [objects], - ); - - const deleteObjectKeyframe = useCallback((keyframe: Keyframe) => { - setObjects((oldObjectGroup) => { - const obj = oldObjectGroup.objects.find( - (row) => row.object_uuid === keyframe.object_uuid, - ); - if (!obj) { - return oldObjectGroup; - } - - const newKeyframes = [ - ...obj.keyframes.filter( - (row) => row.keyframe_uuid !== keyframe.keyframe_uuid, - ), - ]; - - if (newKeyframes.length) { - return { - ...oldObjectGroup, - objects: [ - ...oldObjectGroup.objects.map((object) => ({ - ...object, - ...(object.object_uuid === keyframe.object_uuid - ? { keyframes: newKeyframes } - : {}), - })), - ], - }; - } - return { - ...oldObjectGroup, - objects: [ - ...oldObjectGroup.objects.filter( - (object) => object.object_uuid !== keyframe.object_uuid, - ), - ], - }; - }); - }, []); - - return { - objects, - updateObject, - addObjectKeyframe, - deleteObjectKeyframe, - }; -} diff --git a/app/pages/PageEnigma/store/audioGroup.ts b/app/pages/PageEnigma/store/audioGroup.ts new file mode 100644 index 0000000000..07be9b937e --- /dev/null +++ b/app/pages/PageEnigma/store/audioGroup.ts @@ -0,0 +1,136 @@ +import { + AudioGroup, + Clip, + ClipGroup, + ClipType, + MediaClip, +} from "~/pages/PageEnigma/models/track"; +import Queue from "~/pages/PageEnigma/Queue/Queue"; +import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames"; +import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions"; +import * as uuid from "uuid"; +import { signal } from "@preact/signals-core"; + +export const audioGroup = signal({ + id: "AG-1", + clips: [], + muted: false, +}); + +export function updateAudio({ + id, + offset, + length, +}: { + id: string; + length: number; + offset: number; +}) { + const oldAudioGroup = audioGroup.value; + const newClips = [...oldAudioGroup.clips]; + const clipIndex = newClips.findIndex((row) => row.clip_uuid === id); + if (clipIndex === -1) { + return { ...oldAudioGroup }; + } + const clip = newClips[clipIndex]; + clip.offset = offset; + clip.length = length; + + Queue.publish({ + queueName: QueueNames.TO_ENGINE, + action: toEngineActions.UPDATE_CLIP, + data: clip, + }); + + audioGroup.value = { + ...oldAudioGroup, + clips: newClips, + }; +} + +export function addGlobalAudio( + dragId: string, + audioClips: MediaClip[], + offset: number, +) { + const clip = audioClips.find((row) => row.media_id === dragId); + if (!clip) { + return; + } + + const clip_uuid = uuid.v4(); + const newClip = { + ...clip, + type: ClipType.AUDIO, + group: ClipGroup.GLOBAL_AUDIO, + offset, + clip_uuid, + } as Clip; + + const oldAudioGroup = audioGroup.value; + audioGroup.value = { + ...oldAudioGroup, + clips: [...oldAudioGroup.clips, newClip], + }; + + Queue.publish({ + queueName: QueueNames.TO_ENGINE, + action: toEngineActions.ADD_CLIP, + data: newClip, + }); +} + +export function toggleAudioMute() { + const oldAudioGroup = audioGroup.value; + Queue.publish({ + queueName: QueueNames.TO_ENGINE, + action: oldAudioGroup?.muted + ? toEngineActions.UNMUTE + : toEngineActions.MUTE, + data: { + version: 1, + type: ClipType.AUDIO, + group: ClipGroup.GLOBAL_AUDIO, + }, + }); + + audioGroup.value = { + ...oldAudioGroup, + muted: !oldAudioGroup.muted, + }; +} + +export function selectAudioClip(clipId: string) { + const oldAudioGroup = audioGroup.value; + audioGroup.value = { + ...oldAudioGroup, + clips: [ + ...oldAudioGroup.clips.map((clip) => { + return { + ...clip, + selected: clip.clip_uuid === clipId ? !clip.selected : clip.selected, + }; + }), + ], + }; +} + +export function deleteAudioClip(clipId: string) { + const oldAudioGroup = audioGroup.value; + audioGroup.value = { + ...oldAudioGroup, + clips: [ + ...oldAudioGroup.clips.filter((clip) => { + if (clip.clip_uuid === clipId) { + Queue.publish({ + queueName: QueueNames.TO_ENGINE, + action: toEngineActions.DELETE_CLIP, + data: clip!, + }); + return false; + } + return true; + }), + ], + }; +} diff --git a/app/pages/PageEnigma/store/cameraGroup.ts b/app/pages/PageEnigma/store/cameraGroup.ts new file mode 100644 index 0000000000..951cc2d9a0 --- /dev/null +++ b/app/pages/PageEnigma/store/cameraGroup.ts @@ -0,0 +1,99 @@ +import { + CameraGroup, + Keyframe, + QueueKeyframe, +} from "~/pages/PageEnigma/models/track"; +import Queue from "~/pages/PageEnigma/Queue/Queue"; +import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames"; +import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions"; +import * as uuid from "uuid"; +import { signal } from "@preact/signals-core"; + +export const cameraGroup = signal({ id: "CG1", keyframes: [] }); + +export function updateCamera({ id, offset }: { id: string; offset: number }) { + const oldCameraGroup = cameraGroup.value; + const newKeyframes = [...oldCameraGroup.keyframes]; + const keyframe = newKeyframes.find((row) => row.keyframe_uuid === id); + if (!keyframe) { + return; + } + keyframe.offset = offset; + + Queue.publish({ + queueName: QueueNames.TO_ENGINE, + action: toEngineActions.UPDATE_KEYFRAME, + data: keyframe, + }); + + cameraGroup.value = { + ...oldCameraGroup, + keyframes: newKeyframes, + }; +} + +export function addCameraKeyframe(keyframe: QueueKeyframe, offset: number) { + const newKeyframe = { + version: keyframe.version, + keyframe_uuid: uuid.v4(), + group: keyframe.group, + object_uuid: keyframe.object_uuid, + offset, + position: keyframe.position, + rotation: keyframe.rotation, + scale: keyframe.scale, + selected: false, + } as Keyframe; + + const oldCameraGroup = cameraGroup.value; + cameraGroup.value = { + ...oldCameraGroup, + keyframes: [...oldCameraGroup.keyframes, newKeyframe].sort( + (keyFrameA, keyframeB) => keyFrameA.offset - keyframeB.offset, + ), + }; + + Queue.publish({ + queueName: QueueNames.TO_ENGINE, + action: toEngineActions.ADD_KEYFRAME, + data: newKeyframe, + }); +} + +export function selectCameraKeyframe(keyframeId: string) { + const oldCameraGroup = cameraGroup.value; + cameraGroup.value = { + ...oldCameraGroup, + keyframes: [ + ...oldCameraGroup.keyframes.map((keyframe) => { + return { + ...keyframe, + selected: + keyframe.keyframe_uuid === keyframeId + ? !keyframe.selected + : keyframe.selected, + }; + }), + ], + }; +} + +export function deleteCameraKeyframe(keyframeId: string) { + const oldCameraGroup = cameraGroup.value; + cameraGroup.value = { + ...oldCameraGroup, + keyframes: [ + ...oldCameraGroup.keyframes.filter((keyframe) => { + if (keyframe.keyframe_uuid === keyframeId) { + Queue.publish({ + queueName: QueueNames.TO_ENGINE, + action: toEngineActions.DELETE_KEYFRAME, + data: keyframe, + }); + return false; + } + return true; + }), + ], + }; +} diff --git a/app/pages/PageEnigma/store/characterGroups.ts b/app/pages/PageEnigma/store/characterGroups.ts new file mode 100644 index 0000000000..7311bc5185 --- /dev/null +++ b/app/pages/PageEnigma/store/characterGroups.ts @@ -0,0 +1,331 @@ +import { + CharacterGroup, + Clip, + ClipGroup, + ClipType, + Keyframe, + MediaClip, + QueueKeyframe, +} from "~/pages/PageEnigma/models/track"; +import { signal } from "@preact/signals-core"; +import Queue from "~/pages/PageEnigma/Queue/Queue"; +import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames"; +import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions"; +import * as uuid from "uuid"; + +export const characterGroups = signal([ + { + id: "CH1", + muted: false, + animationClips: [], + positionKeyframes: [], + lipSyncClips: [], + }, +]); + +export function updateCharacters({ + type, + id, + offset, + length, +}: { + type: "animations" | "positions" | "lipSync"; + id: string; + length: number; + offset: number; +}) { + const oldCharacterGroups = characterGroups.value; + if (type === "animations") { + characterGroups.value = oldCharacterGroups.map((character) => { + const newAnimationClips = [...character.animationClips]; + const clipIndex = newAnimationClips.findIndex( + (row) => row.clip_uuid === id, + ); + if (clipIndex === -1) { + return { ...character }; + } + const clip = newAnimationClips[clipIndex]; + clip.offset = offset; + clip.length = length; + + Queue.publish({ + queueName: QueueNames.TO_ENGINE, + action: toEngineActions.UPDATE_CLIP, + data: clip, + }); + + return { + ...character, + animationClips: newAnimationClips, + }; + }); + } + + if (type === "positions") { + characterGroups.value = oldCharacterGroups.map((character) => { + const newPositionKeyframes = [...character.positionKeyframes]; + const keyframeIndex = newPositionKeyframes.findIndex( + (row) => row.keyframe_uuid === id, + ); + if (keyframeIndex === -1) { + return { ...character }; + } + const keyframe = newPositionKeyframes[keyframeIndex]; + keyframe.offset = offset; + + Queue.publish({ + queueName: QueueNames.TO_ENGINE, + action: toEngineActions.UPDATE_CLIP, + data: keyframe, + }); + + return { + ...character, + positionClips: newPositionKeyframes, + }; + }); + } + if (type === "lipSync") { + characterGroups.value = oldCharacterGroups.map((character) => { + const newLipSyncClips = [...character.lipSyncClips]; + const clipIndex = newLipSyncClips.findIndex( + (row) => row.clip_uuid === id, + ); + if (clipIndex === -1) { + return { ...character }; + } + const clip = newLipSyncClips[clipIndex]; + clip.offset = offset; + clip.length = length; + + Queue.publish({ + queueName: QueueNames.TO_ENGINE, + action: toEngineActions.UPDATE_CLIP, + data: clip, + }); + + return { + ...character, + lipSyncClips: newLipSyncClips, + }; + }); + } +} + +export function addCharacterAnimation({ + clipId, + characterId, + animationClips, + offset, +}: { + clipId: string; + characterId: string; + animationClips: MediaClip[]; + offset: number; +}) { + const clip = animationClips.find((row) => row.media_id === clipId); + if (!clip) { + return; + } + + const clip_uuid = uuid.v4(); + const newClip = { + ...clip, + group: ClipGroup.CHARACTER, + offset, + clip_uuid, + object_uuid: characterId, + } as Clip; + + const oldCharacterGroups = characterGroups.value; + characterGroups.value = oldCharacterGroups.map((character) => { + if (character.id !== characterId) { + return { ...character }; + } + return { + ...character, + animationClips: [...character.animationClips, newClip].sort( + (clipA, clipB) => clipA.offset - clipB.offset, + ), + }; + }); + + Queue.publish({ + queueName: QueueNames.TO_ENGINE, + action: toEngineActions.ADD_CLIP, + data: newClip, + }); +} + +export function addCharacterAudio({ + clipId, + characterId, + audioClips, + offset, +}: { + clipId: string; + characterId: string; + audioClips: MediaClip[]; + offset: number; +}) { + const clip = audioClips.find((row) => row.media_id === clipId); + if (!clip) { + return; + } + + const clip_uuid = uuid.v4(); + const newClip = { + ...clip, + type: ClipType.AUDIO, + group: ClipGroup.CHARACTER, + offset, + clip_uuid, + object_uuid: characterId, + } as Clip; + + const oldCharacterGroups = characterGroups.value; + characterGroups.value = oldCharacterGroups.map((character) => { + if (character.id !== characterId) { + return { ...character }; + } + return { + ...character, + lipSyncClips: [...character.lipSyncClips, newClip].sort( + (clipA, clipB) => clipA.offset - clipB.offset, + ), + }; + }); + + Queue.publish({ + queueName: QueueNames.TO_ENGINE, + action: toEngineActions.ADD_CLIP, + data: newClip, + }); +} + +export function addCharacterKeyframe(keyframe: QueueKeyframe, offset: number) { + const newKeyframe = { + version: keyframe.version, + keyframe_uuid: uuid.v4(), + group: keyframe.group, + object_uuid: keyframe.object_uuid, + offset, + position: keyframe.position, + rotation: keyframe.rotation, + scale: keyframe.scale, + selected: false, + } as Keyframe; + + const oldCharacterGroups = characterGroups.value; + characterGroups.value = oldCharacterGroups.map((character) => { + if (character.id !== keyframe.object_uuid) { + return { ...character }; + } + return { + ...character, + positionKeyframes: [...character.positionKeyframes, newKeyframe].sort( + (keyFrameA, keyframeB) => keyFrameA.offset - keyframeB.offset, + ), + }; + }); + + Queue.publish({ + queueName: QueueNames.TO_ENGINE, + action: toEngineActions.ADD_KEYFRAME, + data: newKeyframe, + }); +} + +export function toggleLipSyncMute(characterId: string) { + const oldCharacterGroups = characterGroups.value; + characterGroups.value = oldCharacterGroups.map((character) => { + if (character.id === characterId) { + Queue.publish({ + queueName: QueueNames.TO_ENGINE, + action: character?.muted + ? toEngineActions.UNMUTE + : toEngineActions.MUTE, + data: { + version: 1, + type: ClipType.AUDIO, + group: ClipGroup.CHARACTER, + object_uuid: characterId, + }, + }); + } + + return { + ...character, + muted: character.id === characterId ? !character.muted : character.muted, + }; + }); +} + +export function selectCharacterClip(clipId: string) { + const oldCharacterGroups = characterGroups.value; + characterGroups.value = oldCharacterGroups.map((character) => ({ + ...character, + animationClips: character.animationClips.map((clip) => ({ + ...clip, + selected: clip.clip_uuid === clipId ? !clip.selected : clip.selected, + })), + positionClips: character.positionKeyframes.map((keyframe) => ({ + ...keyframe, + selected: + keyframe.keyframe_uuid === clipId + ? !keyframe.selected + : keyframe.selected, + })), + lipSyncClips: character.lipSyncClips.map((clip) => ({ + ...clip, + selected: clip.clip_uuid === clipId ? !clip.selected : clip.selected, + })), + })); +} + +export function deleteCharacterClip(clipId: string) { + const oldCharacterGroups = characterGroups.value; + characterGroups.value = oldCharacterGroups.map((character) => ({ + ...character, + animationClips: character.animationClips.filter((clip) => { + if (clip.clip_uuid === clipId) { + Queue.publish({ + queueName: QueueNames.TO_ENGINE, + action: toEngineActions.DELETE_CLIP, + data: clip!, + }); + return false; + } + return true; + }), + lipSyncClips: character.lipSyncClips.filter((clip) => { + if (clip.clip_uuid === clipId) { + Queue.publish({ + queueName: QueueNames.TO_ENGINE, + action: toEngineActions.DELETE_CLIP, + data: clip!, + }); + return false; + } + return true; + }), + })); +} + +export function deleteCharacterKeyframe(keyframe: Keyframe) { + const oldCharacterGroups = characterGroups.value; + characterGroups.value = oldCharacterGroups.map((character) => ({ + ...character, + positionClips: character.positionKeyframes.filter((row) => { + if (row.keyframe_uuid === keyframe.keyframe_uuid) { + Queue.publish({ + queueName: QueueNames.TO_ENGINE, + action: toEngineActions.DELETE_KEYFRAME, + data: row, + }); + return false; + } + return true; + }), + })); +} diff --git a/app/pages/PageEnigma/store/index.ts b/app/pages/PageEnigma/store/index.ts index fb12ae01bb..2c8a560d91 100644 --- a/app/pages/PageEnigma/store/index.ts +++ b/app/pages/PageEnigma/store/index.ts @@ -1,3 +1,7 @@ +export * from "./audioGroup"; +export * from "./cameraGroup"; +export * from "./characterGroups"; export * from "./dragAndDrop"; +export * from "./objectGroup"; export * from "./sizing"; export * from "./timing"; diff --git a/app/pages/PageEnigma/store/objectGroup.ts b/app/pages/PageEnigma/store/objectGroup.ts new file mode 100644 index 0000000000..8b94c4f509 --- /dev/null +++ b/app/pages/PageEnigma/store/objectGroup.ts @@ -0,0 +1,107 @@ +import { signal } from "@preact/signals-core"; +import { + Keyframe, + ObjectGroup, + QueueKeyframe, +} from "~/pages/PageEnigma/models/track"; +import { useCallback } from "react"; +import * as uuid from "uuid"; + +export const objectGroup = signal({ + id: "OB1", + objects: [], +}); + +export function updateObject({ id, offset }: { id: string; offset: number }) { + const oldObject = objectGroup.value; + objectGroup.value = { + id: oldObject.id, + objects: oldObject.objects.map((object) => ({ + object_uuid: object.object_uuid, + name: object.name, + keyframes: object.keyframes.map((keyFrame) => ({ + ...keyFrame, + offset: keyFrame.keyframe_uuid === id ? offset : keyFrame.offset, + })), + })), + }; +} + +export function addObjectKeyframe(keyframe: QueueKeyframe, offset: number) { + console.log(keyframe, offset); + const oldObjectGroup = objectGroup.value; + const obj = oldObjectGroup.objects.find( + (row) => row.object_uuid === keyframe.object_uuid, + ); + + const newObject = obj ?? { + object_uuid: keyframe.object_uuid, + name: keyframe.object_name ?? "unknown", + keyframes: [] as Keyframe[], + }; + newObject.keyframes.push({ + version: keyframe.version, + keyframe_uuid: uuid.v4(), + group: keyframe.group, + object_uuid: keyframe.object_uuid, + offset, + position: keyframe.position, + rotation: keyframe.rotation, + scale: keyframe.scale, + selected: false, + } as Keyframe); + console.log(newObject); + newObject.keyframes.sort( + (keyframeA, keyframeB) => keyframeA.offset - keyframeB.offset, + ); + + console.log(4); + objectGroup.value = { + ...oldObjectGroup, + objects: [ + ...oldObjectGroup.objects.filter( + (object) => object.object_uuid !== keyframe.object_uuid, + ), + newObject, + ].sort((objA, objB) => (objA.object_uuid < objB.object_uuid ? -1 : 1)), + }; + console.log("T", objectGroup.value); +} + +export function deleteObjectKeyframe(keyframe: Keyframe) { + const oldObjectGroup = objectGroup.value; + const obj = oldObjectGroup.objects.find( + (row) => row.object_uuid === keyframe.object_uuid, + ); + if (!obj) { + return oldObjectGroup; + } + + const newKeyframes = [ + ...obj.keyframes.filter( + (row) => row.keyframe_uuid !== keyframe.keyframe_uuid, + ), + ]; + + if (newKeyframes.length) { + return { + ...oldObjectGroup, + objects: [ + ...oldObjectGroup.objects.map((object) => ({ + ...object, + ...(object.object_uuid === keyframe.object_uuid + ? { keyframes: newKeyframes } + : {}), + })), + ], + }; + } + objectGroup.value = { + ...oldObjectGroup, + objects: [ + ...oldObjectGroup.objects.filter( + (object) => object.object_uuid !== keyframe.object_uuid, + ), + ], + }; +} diff --git a/app/pages/PageEnigma/store/sizing.ts b/app/pages/PageEnigma/store/sizing.ts index 778621080b..9eb952e54e 100644 --- a/app/pages/PageEnigma/store/sizing.ts +++ b/app/pages/PageEnigma/store/sizing.ts @@ -1,4 +1,6 @@ import { signal, computed } from "@preact/signals-core"; +import { characterGroups } from "~/pages/PageEnigma/store/characterGroups"; +import { objectGroup } from "~/pages/PageEnigma/store/objectGroup"; export const scale = signal(1); export const filmLength = signal(12); @@ -6,3 +8,12 @@ export const timelineHeight = signal(0); export const fullWidth = computed(() => { return filmLength.value * 60 * 4 * scale.value; }); + +export const fullHeight = computed(() => { + return ( + characterGroups.value.length * 268 + + objectGroup.value.objects.length * 60 + + 300 + + 96 + ); +}); From 565280f2dfdeb6a3cc7bc54166eff8e00c2d2826 Mon Sep 17 00:00:00 2001 From: FromTheLOLAshes Date: Mon, 1 Apr 2024 14:58:47 +0700 Subject: [PATCH 09/22] Some code cleanup, get keyframes working --- app/components/ItemPicker.tsx | 22 +- app/pages/PageEnigma/PageEnigma.tsx | 10 +- app/pages/PageEnigma/PageEnigmaComponent.tsx | 146 ++++---- .../ControlPanelSceneObject.tsx | 101 +++--- .../ControlsTopButtons/ControlsTopButtons.tsx | 122 ++++--- .../comps/SidePanelTabs/TabStylization.tsx | 115 +++--- .../PageEnigma/comps/Timeline/Timeline.tsx | 1 - .../comps/Timeline/utils/useQueueHandler.ts | 1 - .../PageEnigma/contexts/AppUiContext.tsx | 29 +- .../TrackContext/utils/useUpdateKeyframe.ts | 35 +- app/pages/PageEnigma/js/editor.ts | 341 +++++++++++------- .../reducers/appUiReducer/reducer.tsx | 100 +++-- .../reducers/appUiReducer/types.tsx | 135 +++---- .../reducers/appUiReducer/values.tsx | 39 +- app/pages/PageEnigma/store/cameraGroup.ts | 4 +- app/pages/PageEnigma/store/objectGroup.ts | 15 +- 16 files changed, 653 insertions(+), 563 deletions(-) diff --git a/app/components/ItemPicker.tsx b/app/components/ItemPicker.tsx index 8e80dd2742..6831e1ef0e 100644 --- a/app/components/ItemPicker.tsx +++ b/app/components/ItemPicker.tsx @@ -1,11 +1,11 @@ import { twMerge } from "tailwind-merge"; import { H4 } from "."; - +import { ArtStyle } from "~/pages/PageEnigma/js/api_manager"; interface ItemPickerProps extends React.ImgHTMLAttributes { - label: string + label: ArtStyle; selected: boolean; - onSelected : (val:string)=>void; + onSelected: (picked: ArtStyle) => void; } export const ItemPicker = ({ @@ -13,20 +13,22 @@ export const ItemPicker = ({ selected = false, onSelected, ...imgProps -}:ItemPickerProps) => { - - const handleSelected = ()=>{ +}: ItemPickerProps) => { + const handleSelected = () => { onSelected(label); }; return (
-
+

{label}

- ) -} \ No newline at end of file + ); +}; diff --git a/app/pages/PageEnigma/PageEnigma.tsx b/app/pages/PageEnigma/PageEnigma.tsx index 6507fd86fe..da3f729918 100644 --- a/app/pages/PageEnigma/PageEnigma.tsx +++ b/app/pages/PageEnigma/PageEnigma.tsx @@ -1,10 +1,18 @@ import { PageEnigmaComponent } from "./PageEnigmaComponent"; import { TrackProvider } from "~/pages/PageEnigma/contexts/TrackContext/TrackProvider"; +import { AppUIProvider } from "~/pages/PageEnigma/contexts/AppUiContext"; +import { EngineProvider } from "~/pages/PageEnigma/contexts/EngineProvider"; +import { DragComponent } from "~/pages/PageEnigma/comps/DragComponent/DragComponent"; export const PageEnigma = () => { return ( - + + + + + + ); }; diff --git a/app/pages/PageEnigma/PageEnigmaComponent.tsx b/app/pages/PageEnigma/PageEnigmaComponent.tsx index b1a36217e6..b8416749c1 100644 --- a/app/pages/PageEnigma/PageEnigmaComponent.tsx +++ b/app/pages/PageEnigma/PageEnigmaComponent.tsx @@ -1,4 +1,4 @@ -import { useReducer } from "react"; +import { useContext } from "react"; import { faSparkles } from "@fortawesome/pro-solid-svg-icons"; @@ -15,23 +15,14 @@ import { ViewSideBySide } from "./comps/ViewSideBySide"; import { SidePanelTabs } from "./comps/SidePanelTabs"; import { Timeline } from "./comps/Timeline"; -import { EngineProvider } from "./contexts/EngineProvider"; -import { AppUIProvider } from "./contexts/AppUiContext"; -import { - appUiReducer, - appUiInitialStateValues, - APPUI_VIEW_MODES, -} from "./reducers"; +import { APPUI_VIEW_MODES } from "./reducers"; import { timelineHeight } from "~/pages/PageEnigma/store"; import { useSignals } from "@preact/signals-react/runtime"; -import { DragComponent } from "~/pages/PageEnigma/comps/DragComponent/DragComponent"; +import { AppUiContext } from "~/pages/PageEnigma/contexts/AppUiContext"; export const PageEnigmaComponent = () => { useSignals(); - const [appUiState, dispatchAppUiState] = useReducer( - appUiReducer, - appUiInitialStateValues, - ); + const [appUiState] = useContext(AppUiContext); //To prevent the click event from propagating to the canvas: TODO: HANDLE THIS BETTER? const handleOverlayClick = (event: React.MouseEvent) => { @@ -45,82 +36,77 @@ export const PageEnigmaComponent = () => { - - -
- {/* Engine section/side panel */} +
+ {/* Engine section/side panel */} +
+
-
-
- + - {/* Top controls */} -
-
- - -
-
- - {/* Bottom controls */} -
- - - -
+ {/* Top controls */} +
+
+ +
- {appUiState.viewMode === APPUI_VIEW_MODES.SIDE_BY_SIDE && ( - - )} - -
- {/* Side panel */} -
- - - + {/* Bottom controls */} +
+ + +
- - {/* Timeline */} -
- -
+ {appUiState.viewMode === APPUI_VIEW_MODES.SIDE_BY_SIDE && ( + + )} + +
- - - + + {/* Side panel */} +
+ + + +
+
+ + {/* Timeline */} +
+ +
+
); }; diff --git a/app/pages/PageEnigma/comps/ControlPanelSceneObject/ControlPanelSceneObject.tsx b/app/pages/PageEnigma/comps/ControlPanelSceneObject/ControlPanelSceneObject.tsx index 3b8f4a764d..7d9e3d17e0 100644 --- a/app/pages/PageEnigma/comps/ControlPanelSceneObject/ControlPanelSceneObject.tsx +++ b/app/pages/PageEnigma/comps/ControlPanelSceneObject/ControlPanelSceneObject.tsx @@ -20,36 +20,30 @@ import { QueueKeyframe } from "~/pages/PageEnigma/models/track"; export const ControlPanelSceneObject = () => { const [appUiState, dispatchAppUiState] = useContext(AppUiContext); - const position = appUiState?.currentSceneObject.objectVectors.position; - const rotation = appUiState?.currentSceneObject.objectVectors.rotation; - const scale = appUiState?.currentSceneObject.objectVectors.scale; - const currentSceneObject = appUiState?.currentSceneObject; - const handlePositionChange = (xyz: XYZ) => { - if (appUiState) { - if (currentSceneObject == null) { - console.log("Missing Scene Object Position"); - return; - } - - dispatchAppUiState({ - type: ACTION_TYPES.UPDATE_CONTROLPANELS_SCENEOBJECT, - payload: { - currentSceneObject: { - group: currentSceneObject.group, - object_uuid: currentSceneObject.object_uuid, - object_name: currentSceneObject.object_name, - version: currentSceneObject.version, - objectVectors: { - position: { ...xyz }, - rotation: appUiState.currentSceneObject.objectVectors.rotation, - scale: appUiState.currentSceneObject.objectVectors.scale, - }, - }, - }, - }); + if (!currentSceneObject) { + console.log("Missing Scene Object Position"); + return; } + + dispatchAppUiState({ + type: ACTION_TYPES.UPDATE_CONTROLPANELS_SCENEOBJECT, + payload: { + group: currentSceneObject.group, + object_uuid: currentSceneObject.object_uuid, + object_name: currentSceneObject.object_name, + version: currentSceneObject.version, + objectVectors: { + position: { ...xyz }, + rotation: appUiState?.controlPanel.currentSceneObject?.objectVectors + ?.rotation ?? { x: 0, y: 0, z: 0 }, + scale: appUiState?.controlPanel.currentSceneObject?.objectVectors + ?.scale ?? { x: 0, y: 0, z: 0 }, + }, + }, + }); }; + const handleRotationChange = (xyz: XYZ) => { if (appUiState) { if (currentSceneObject == null) { @@ -60,21 +54,22 @@ export const ControlPanelSceneObject = () => { dispatchAppUiState({ type: ACTION_TYPES.UPDATE_CONTROLPANELS_SCENEOBJECT, payload: { - currentSceneObject: { - group: currentSceneObject.group, - object_uuid: currentSceneObject.object_uuid, - object_name: currentSceneObject.object_name, - version: currentSceneObject.version, - objectVectors: { - position: appUiState.currentSceneObject.objectVectors.position, - rotation: { ...xyz }, - scale: appUiState.currentSceneObject.objectVectors.scale, - }, + group: currentSceneObject.group, + object_uuid: currentSceneObject.object_uuid, + object_name: currentSceneObject.object_name, + version: currentSceneObject.version, + objectVectors: { + position: + appUiState.controlPanel.currentSceneObject.objectVectors.position, + rotation: { ...xyz }, + scale: + appUiState.controlPanel.currentSceneObject.objectVectors.scale, }, }, }); } }; + const handleScaleChange = (xyz: XYZ) => { if (appUiState) { if (currentSceneObject == null) { @@ -84,16 +79,16 @@ export const ControlPanelSceneObject = () => { dispatchAppUiState({ type: ACTION_TYPES.UPDATE_CONTROLPANELS_SCENEOBJECT, payload: { - currentSceneObject: { - group: currentSceneObject.group, - object_uuid: currentSceneObject.object_uuid, - object_name: currentSceneObject.object_name, - version: currentSceneObject.version, - objectVectors: { - position: appUiState.currentSceneObject.objectVectors.position, - rotation: appUiState.currentSceneObject.objectVectors.rotation, - scale: { ...xyz }, - }, + group: currentSceneObject.group, + object_uuid: currentSceneObject.object_uuid, + object_name: currentSceneObject.object_name, + version: currentSceneObject.version, + objectVectors: { + position: + appUiState.controlPanel.currentSceneObject.objectVectors.position, + rotation: + appUiState.controlPanel.currentSceneObject.objectVectors.rotation, + scale: { ...xyz }, }, }, }); @@ -145,9 +140,19 @@ export const ControlPanelSceneObject = () => { } }; + if (!appUiState.controlPanel.currentSceneObject) { + return null; + } + const position = + appUiState.controlPanel.currentSceneObject.objectVectors.position; + const rotation = + appUiState.controlPanel.currentSceneObject.objectVectors.rotation; + const scale = appUiState.controlPanel.currentSceneObject.objectVectors.scale; + const currentSceneObject = appUiState.controlPanel.currentSceneObject; + return ( { const editorEngine = useContext(EngineContext); - const [ appUiState, dispatchAppUiState ] = useContext(AppUiContext); + const [appUiState, dispatchAppUiState] = useContext(AppUiContext); const [sceneName, setSceneName] = useState(""); const [sceneToken, setSceneToken] = useState(""); @@ -22,8 +23,8 @@ export const ControlsTopButtons = () => { }; const handleButtonTest = () => { - editorEngine?.testStylizeRequest() - + editorEngine?.testStylizeRequest(); + // if(appUiState?.showEditorLoadingBar.isShowing){ //TO GO TO A HUNDRED AND DISAPPER // dispatchAppUiState({ @@ -41,15 +42,15 @@ export const ControlsTopButtons = () => { // }, 1000); //END :TO GO TO A HUNDRED AND DISAPPER - //EAMPLE OF CHANING LOADING MESSAGE - // dispatchAppUiState({ - // type: APPUI_ACTION_TYPES.UPDATE_EDITOR_LOADINGBAR, - // payload:{ - // showEditorLoadingBar: { - // message: "new message", - // } - // } - // }); + //EAMPLE OF CHANING LOADING MESSAGE + // dispatchAppUiState({ + // type: APPUI_ACTION_TYPES.UPDATE_EDITOR_LOADINGBAR, + // payload:{ + // showEditorLoadingBar: { + // message: "new message", + // } + // } + // }); // }else{ // dispatchAppUiState({ // type: APPUI_ACTION_TYPES.SHOW_EDITOR_LOADINGBAR, @@ -59,31 +60,33 @@ export const ControlsTopButtons = () => { // progress: 10, // } // } - + // }); //} }; const handleObjectPanelTest = () => { - if (appUiState?.currentSceneObject.isShowing) { + if (appUiState?.controlPanel.isShowing) { dispatchAppUiState({ - type: APPUI_ACTION_TYPES.HIDE_CONTROLPANELS_SCENEOBJECT + type: APPUI_ACTION_TYPES.HIDE_CONTROLPANELS_SCENEOBJECT, }); - }else{ + } else { dispatchAppUiState({ - type:APPUI_ACTION_TYPES.SHOW_CONTROLPANELS_SCENEOBJECT, - payload:{ - currentSceneObject:{ - objectVectors: { - position: {x:1,y:2,z:3}, - rotation: {x:4,y:5,z:6}, - scale: {x:7,y:8,z:9}, - } - } - } + type: APPUI_ACTION_TYPES.SHOW_CONTROLPANELS_SCENEOBJECT, + payload: { + group: ClipGroup.OBJECT, + object_name: "", + object_uuid: "", + version: "1", + objectVectors: { + position: { x: 1, y: 2, z: 3 }, + rotation: { x: 4, y: 5, z: 6 }, + scale: { x: 7, y: 8, z: 9 }, + }, + }, }); } - } + }; const handleButtonCameraView = () => { editorEngine?.switchCameraView(); @@ -111,40 +114,44 @@ export const ControlsTopButtons = () => {
+ buttonProps={{ + variant: "secondary", + label: "Load Scene", + }} + title="Load Scene" + confirmButtonProps={{ + label: "Load", + disabled: sceneToken === "", + onClick: handleButtonLoadScene, + }} + > {setSceneToken(e.target.value)}} + onChange={(e) => { + setSceneToken(e.target.value); + }} /> - - {setSceneName(e.target.value)}} - /> - + + { + setSceneName(e.target.value); + }} + /> + {
- @@ -181,7 +187,7 @@ export const ControlsTopButtons = () => { diff --git a/app/pages/PageEnigma/comps/SidePanelTabs/TabStylization.tsx b/app/pages/PageEnigma/comps/SidePanelTabs/TabStylization.tsx index d4ee6b842d..d04ce64519 100644 --- a/app/pages/PageEnigma/comps/SidePanelTabs/TabStylization.tsx +++ b/app/pages/PageEnigma/comps/SidePanelTabs/TabStylization.tsx @@ -1,105 +1,114 @@ -import { useContext, useState } from 'react'; +import { useContext, useState } from "react"; -import { - Button, - H4, - ItemPicker, - Textarea -} from "~/components" +import { Button, H4, ItemPicker, Textarea } from "~/components"; -import { AppUiContext } from '../../contexts/AppUiContext'; -import { APPUI_ACTION_TYPES } from '../../reducers'; -import { APPUI_VIEW_MODES } from '../../reducers'; -import { EngineContext } from '../../contexts/EngineContext'; -import { ArtStyle } from '../../js/api_manager'; -export const TabStylization = ()=>{ +import { AppUiContext } from "../../contexts/AppUiContext"; +import { APPUI_ACTION_TYPES, APPUI_VIEW_MODES } from "../../reducers"; +import { EngineContext } from "../../contexts/EngineContext"; +import { ArtStyle } from "../../js/api_manager"; + +export const TabStylization = () => { const [appUiState, dispatchAppUiState] = useContext(AppUiContext); - const [ selection, setSelection ] = useState("Anime"); + const [selection, setSelection] = useState("Anime"); const editorEngine = useContext(EngineContext); - const handlePickingStylizer = (picked:ArtStyle)=>{ + const handlePickingStylizer = (picked: ArtStyle) => { console.log(`Picked style: ${picked}`); - if (editorEngine == null) { console.log("Editor is null"); return;} - editorEngine.art_style = picked + if (editorEngine == null) { + console.log("Editor is null"); + return; + } + editorEngine.art_style = picked; setSelection(picked); - } + }; - const onChangeHandlerNegative = (e:React.ChangeEvent) => { - if (editorEngine == null) { console.log("Editor is null"); return;} - editorEngine.negative_prompt = e.target.value - console.log(e.target.value) - } + const onChangeHandlerNegative = ( + e: React.ChangeEvent, + ) => { + if (editorEngine == null) { + console.log("Editor is null"); + return; + } + editorEngine.negative_prompt = e.target.value; + console.log(e.target.value); + }; - const onChangeHandlerPositive = (e:React.ChangeEvent) => { - if (editorEngine == null) { console.log("Editor is null"); return;} - editorEngine.positive_prompt = e.target.value - console.log(e.target.value) - } - const handleChangeViewMode = ()=>{ - if(dispatchAppUiState!==null){ + const onChangeHandlerPositive = ( + e: React.ChangeEvent, + ) => { + if (editorEngine == null) { + console.log("Editor is null"); + return; + } + editorEngine.positive_prompt = e.target.value; + console.log(e.target.value); + }; + const handleChangeViewMode = () => { + if (dispatchAppUiState !== null) { dispatchAppUiState({ type: APPUI_ACTION_TYPES.ON_CHANGE_VIEW_MODE, payload: { - viewMode: appUiState?.viewMode === APPUI_VIEW_MODES.SIDE_BY_SIDE - ? APPUI_VIEW_MODES.EDITOR : APPUI_VIEW_MODES.SIDE_BY_SIDE, - } - }) + viewMode: + appUiState?.viewMode === APPUI_VIEW_MODES.SIDE_BY_SIDE + ? APPUI_VIEW_MODES.EDITOR + : APPUI_VIEW_MODES.SIDE_BY_SIDE, + }, + }); } - } - return( -
+ }; + return ( +

Select Base Stylizer

-
+

Enter a Prompt

-
+