@@ -8,7 +8,11 @@ import type {
88import * as THREE from "three" ;
99import { saveFileToFS } from "@/utils/file-system/fs.web" ;
1010import { disposeParsedModel , parseModel } from "@/utils/model" ;
11- import { getRuntimeModel } from "@/utils/model-downgrade-runtime" ;
11+ import {
12+ clearDowngradedRuntimeModel ,
13+ getRuntimeModel ,
14+ setOriginalRuntimeClips ,
15+ } from "@/utils/model-downgrade-runtime" ;
1216import {
1317 withHistory ,
1418 type FieldWatcher ,
@@ -42,6 +46,9 @@ export type SerializableModel = Omit<
4246> ;
4347
4448export type LoopType = THREE . AnimationActionLoopStyles ;
49+ export type SerializableAnimationClip = ReturnType <
50+ typeof THREE . AnimationClip . toJSON
51+ > ;
4552
4653export interface ModelsState {
4754 models : Record < string , ModelComponent > ;
@@ -59,6 +66,7 @@ export interface ModelsState {
5966 loops : Record < string , Record < string , LoopType > > ;
6067 hiddenAnimations : Record < string , string [ ] > ;
6168 animationRenames : Record < string , Record < string , string > > ;
69+ importedClips : Record < string , Record < string , SerializableAnimationClip > > ;
6270 currentTime : Record < string , number > ;
6371 frameStep : Record < string , number > ;
6472 freeze : Record < string , boolean > ;
@@ -98,6 +106,7 @@ interface ModelsActions extends SnapshotEnabledStore<ModelsState> {
98106 action : THREE . AnimationAction ;
99107 clip : THREE . AnimationClip ;
100108 } [ ] ,
109+ options ?: { applyPersistedImports ?: boolean } ,
101110 ) => void ;
102111 setMixerRef : ( uuid : string , mixer : THREE . AnimationMixer | null ) => void ;
103112 setAnimation : ( uuid : string , animation : string ) => void ;
@@ -151,6 +160,7 @@ const initialState: ModelsState = {
151160 loops : { } ,
152161 hiddenAnimations : { } ,
153162 animationRenames : { } ,
163+ importedClips : { } ,
154164 currentTime : { } ,
155165 frameStep : { } ,
156166 freeze : { } ,
@@ -300,6 +310,124 @@ function applyAnimationRenamesToClips(
300310 return changed ? renamedClips : clips ;
301311}
302312
313+ function serializeAnimationClip (
314+ clip : THREE . AnimationClip ,
315+ ) : SerializableAnimationClip {
316+ return THREE . AnimationClip . toJSON ( clip ) as SerializableAnimationClip ;
317+ }
318+
319+ function deserializeAnimationClip (
320+ clipSnapshot : SerializableAnimationClip ,
321+ ) : THREE . AnimationClip | null {
322+ try {
323+ return THREE . AnimationClip . parse ( clipSnapshot ) ;
324+ } catch {
325+ return null ;
326+ }
327+ }
328+
329+ function getClipEntryMixer (
330+ uuid : string ,
331+ state : ModelsStore ,
332+ clips : ClipEntry [ ] ,
333+ ) {
334+ const currentMixer = state . mixerRef [ uuid ] ?? mixerCache . get ( uuid ) ;
335+ if ( currentMixer ) return currentMixer ;
336+
337+ const actionMixer = (
338+ clips [ 0 ] ?. action as { getMixer ?: ( ) => THREE . AnimationMixer }
339+ ) ?. getMixer ?.( ) ;
340+ if ( actionMixer ) return actionMixer ;
341+
342+ const runtime = getRuntimeModel ( uuid ) ;
343+ return runtime ?. object ? new THREE . AnimationMixer ( runtime . object ) : null ;
344+ }
345+
346+ function mergePersistedImportedClips (
347+ uuid : string ,
348+ clips : ClipEntry [ ] ,
349+ state : ModelsStore ,
350+ ) {
351+ const importedClips = state . importedClips [ uuid ] ?? { } ;
352+ if ( Object . keys ( importedClips ) . length === 0 ) {
353+ return {
354+ clips : applyAnimationRenamesToClips ( uuid , clips , state . animationRenames ) ,
355+ mixer : state . mixerRef [ uuid ] ?? mixerCache . get ( uuid ) ?? null ,
356+ } ;
357+ }
358+
359+ const mixer = getClipEntryMixer ( uuid , state , clips ) ;
360+ const nextClips = [
361+ ...applyAnimationRenamesToClips ( uuid , clips , state . animationRenames ) ,
362+ ] ;
363+ const renames = state . animationRenames [ uuid ] ?? { } ;
364+
365+ for ( const clipSnapshot of Object . values ( importedClips ) ) {
366+ const importedClip = deserializeAnimationClip ( clipSnapshot ) ;
367+ if ( ! importedClip ) continue ;
368+
369+ importedClip . name = renames [ importedClip . name ] ?? importedClip . name ;
370+ const action = mixer
371+ ? mixer . clipAction ( importedClip )
372+ : ( { } as THREE . AnimationAction ) ;
373+ const index = nextClips . findIndex (
374+ ( entry ) => entry . clip . name === importedClip . name ,
375+ ) ;
376+
377+ if ( index >= 0 ) {
378+ nextClips [ index ] = { action, clip : importedClip } ;
379+ continue ;
380+ }
381+
382+ nextClips . push ( { action, clip : importedClip } ) ;
383+ }
384+
385+ return { clips : nextClips , mixer } ;
386+ }
387+
388+ function upsertImportedClipSnapshots (
389+ importedClips : ModelsState [ "importedClips" ] ,
390+ uuid : string ,
391+ clips : THREE . AnimationClip [ ] ,
392+ ) {
393+ if ( clips . length === 0 ) return importedClips ;
394+
395+ return {
396+ ...importedClips ,
397+ [ uuid ] : {
398+ ...( importedClips [ uuid ] ?? { } ) ,
399+ ...Object . fromEntries (
400+ clips . map ( ( clip ) => [ clip . name , serializeAnimationClip ( clip ) ] ) ,
401+ ) ,
402+ } ,
403+ } ;
404+ }
405+
406+ function renameImportedClipSnapshot (
407+ importedClips : ModelsState [ "importedClips" ] ,
408+ uuid : string ,
409+ fromName : string ,
410+ toName : string ,
411+ ) {
412+ const current = importedClips [ uuid ] ;
413+ const snapshot = current ?. [ fromName ] ;
414+ if ( ! current || ! snapshot ) return importedClips ;
415+
416+ const clip = deserializeAnimationClip ( snapshot ) ;
417+ if ( ! clip ) return importedClips ;
418+
419+ clip . name = toName ;
420+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
421+ const { [ fromName ] : _removed , ...rest } = current ;
422+ return {
423+ ...importedClips ,
424+ [ uuid ] : {
425+ ...rest ,
426+ [ toName ] : serializeAnimationClip ( clip ) ,
427+ } ,
428+ } ;
429+ }
430+
303431function renameClipEntry (
304432 entry : ClipEntry ,
305433 mixer : THREE . AnimationMixer | null | undefined ,
@@ -389,6 +517,12 @@ function renameAnimationInState(
389517 fromName ,
390518 toName ,
391519 ) ,
520+ importedClips : renameImportedClipSnapshot (
521+ state . importedClips ,
522+ uuid ,
523+ fromName ,
524+ toName ,
525+ ) ,
392526 } ;
393527}
394528
@@ -540,11 +674,14 @@ export const useModelsStore = create<ModelsStore>()(
540674 const { [ uuid ] : _________ , ...animationRenames } =
541675 state . animationRenames ;
542676 // eslint-disable-next-line @typescript-eslint/no-unused-vars
543- const { [ uuid ] : __________ , ...currentTime } = state . currentTime ;
677+ const { [ uuid ] : __________ , ...importedClips } =
678+ state . importedClips ;
544679 // eslint-disable-next-line @typescript-eslint/no-unused-vars
545- const { [ uuid ] : ___________ , ...frameStep } = state . frameStep ;
680+ const { [ uuid ] : ___________ , ...currentTime } = state . currentTime ;
546681 // eslint-disable-next-line @typescript-eslint/no-unused-vars
547- const { [ uuid ] : ____________ , ...freeze } = state . freeze ;
682+ const { [ uuid ] : ____________ , ...frameStep } = state . frameStep ;
683+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
684+ const { [ uuid ] : _____________ , ...freeze } = state . freeze ;
548685
549686 return {
550687 models,
@@ -556,22 +693,44 @@ export const useModelsStore = create<ModelsStore>()(
556693 loops,
557694 hiddenAnimations,
558695 animationRenames,
696+ importedClips,
559697 currentTime,
560698 frameStep,
561699 freeze,
562700 } ;
563701 } ) ;
564702 } ,
565703
566- setClips : ( uuid , clips ) =>
704+ setClips : ( uuid , clips , options = { } ) =>
567705 set ( ( state ) => {
568- const renamedClips = applyAnimationRenamesToClips (
569- uuid ,
570- clips ,
571- state . animationRenames ,
572- ) ;
573- clipsCache . set ( uuid , renamedClips ) ;
574- return { clips : { ...state . clips , [ uuid ] : renamedClips } } ;
706+ const shouldApplyImports = options . applyPersistedImports ?? true ;
707+ const result = shouldApplyImports
708+ ? mergePersistedImportedClips ( uuid , clips , state )
709+ : {
710+ clips : applyAnimationRenamesToClips (
711+ uuid ,
712+ clips ,
713+ state . animationRenames ,
714+ ) ,
715+ mixer : state . mixerRef [ uuid ] ?? mixerCache . get ( uuid ) ?? null ,
716+ } ;
717+
718+ if ( result . mixer ) {
719+ mixerCache . set ( uuid , result . mixer ) ;
720+ }
721+ clipsCache . set ( uuid , result . clips ) ;
722+
723+ return {
724+ clips : { ...state . clips , [ uuid ] : result . clips } ,
725+ ...( result . mixer && state . mixerRef [ uuid ] !== result . mixer
726+ ? {
727+ mixerRef : {
728+ ...state . mixerRef ,
729+ [ uuid ] : result . mixer ,
730+ } ,
731+ }
732+ : { } ) ,
733+ } ;
575734 } ) ,
576735
577736 setMixerRef : ( uuid , mixer ) => {
@@ -697,8 +856,19 @@ export const useModelsStore = create<ModelsStore>()(
697856 const existing = get ( ) . clips [ uuid ] ?? [ ] ;
698857 const updated = [ ...existing , { action, clip } ] ;
699858 clipsCache . set ( uuid , updated ) ;
700- console . log ( "addClip" , uuid , clip ) ;
701- get ( ) . setClips ( uuid , updated ) ;
859+ setOriginalRuntimeClips ( uuid , updated , mixer ) ;
860+ clearDowngradedRuntimeModel ( uuid ) ;
861+ set ( ( state ) => ( {
862+ clips : {
863+ ...state . clips ,
864+ [ uuid ] : updated ,
865+ } ,
866+ importedClips : upsertImportedClipSnapshots (
867+ state . importedClips ,
868+ uuid ,
869+ [ clip ] ,
870+ ) ,
871+ } ) ) ;
702872 } ,
703873
704874 importAnimationsFromSource : async ( targetUuid , source ) => {
@@ -876,6 +1046,8 @@ export const useModelsStore = create<ModelsStore>()(
8761046 }
8771047
8781048 clipsCache . set ( targetUuid , nextClips ) ;
1049+ setOriginalRuntimeClips ( targetUuid , nextClips , targetMixer ) ;
1050+ clearDowngradedRuntimeModel ( targetUuid ) ;
8791051
8801052 return {
8811053 mixerRef : {
@@ -898,6 +1070,11 @@ export const useModelsStore = create<ModelsStore>()(
8981070 ...state . loops ,
8991071 [ targetUuid ] : loopMap ,
9001072 } ,
1073+ importedClips : upsertImportedClipSnapshots (
1074+ state . importedClips ,
1075+ targetUuid ,
1076+ preparedClips . map ( ( entry ) => entry . clip ) ,
1077+ ) ,
9011078 } ;
9021079 } ) ;
9031080
@@ -975,6 +1152,8 @@ export const useModelsStore = create<ModelsStore>()(
9751152 clip : nextClip ,
9761153 } ;
9771154 clipsCache . set ( targetUuid , nextClips ) ;
1155+ setOriginalRuntimeClips ( targetUuid , nextClips , targetMixer ) ;
1156+ clearDowngradedRuntimeModel ( targetUuid ) ;
9781157
9791158 set ( ( state ) => ( {
9801159 mixerRef : {
@@ -985,6 +1164,15 @@ export const useModelsStore = create<ModelsStore>()(
9851164 ...state . clips ,
9861165 [ targetUuid ] : nextClips ,
9871166 } ,
1167+ ...( state . importedClips [ targetUuid ] ?. [ clipName ]
1168+ ? {
1169+ importedClips : upsertImportedClipSnapshots (
1170+ state . importedClips ,
1171+ targetUuid ,
1172+ [ nextClip ] ,
1173+ ) ,
1174+ }
1175+ : { } ) ,
9881176 } ) ) ;
9891177
9901178 return { name : nextClip . name } ;
@@ -1025,6 +1213,7 @@ export const useModelsStore = create<ModelsStore>()(
10251213 loops : get ( ) . loops ,
10261214 hiddenAnimations : get ( ) . hiddenAnimations ,
10271215 animationRenames : get ( ) . animationRenames ,
1216+ importedClips : get ( ) . importedClips ,
10281217 currentTime : get ( ) . currentTime ,
10291218 frameStep : get ( ) . frameStep ,
10301219 freeze : get ( ) . freeze ,
@@ -1040,6 +1229,7 @@ export const useModelsStore = create<ModelsStore>()(
10401229 loops : snapshot . loops ,
10411230 hiddenAnimations : snapshot . hiddenAnimations ?? { } ,
10421231 animationRenames : snapshot . animationRenames ?? { } ,
1232+ importedClips : snapshot . importedClips ?? { } ,
10431233 currentTime : snapshot . currentTime ,
10441234 frameStep : snapshot . frameStep ,
10451235 freeze : snapshot . freeze ,
0 commit comments