@@ -99,10 +99,11 @@ export default class DcaAnimation extends AnimatorGumballConsumer {
9999 readonly displayTime = new LO ( 0 , this . onDirty )
100100 readonly maxTime = new LO ( 1 , this . onDirty )
101101 readonly playing = new LO ( false , this . onDirty )
102- displayTimeMatch : boolean = true
103102
104103 readonly loopData : KeyframeLoopData
104+ readonly loopingKeyframe : DcaKeyframe ;
105105 readonly shouldContinueLooping = new LO ( false )
106+ isCurrentlyLooping = false
106107
107108 readonly keyframeLayers = new LO < readonly KeyframeLayerData [ ] > ( [ ] , this . onDirty )
108109
@@ -142,15 +143,25 @@ export default class DcaAnimation extends AnimatorGumballConsumer {
142143 this . loopData = new KeyframeLoopData ( )
143144 this . animatorGumball = new AnimatorGumball ( project )
144145 this . time . addListener ( value => {
145- if ( this . displayTimeMatch ) {
146+ if ( ! this . isCurrentlyLooping ) {
146147 this . displayTime . value = value
147148 }
148149 } )
149150
151+ this . loopingKeyframe = new DcaKeyframe ( this . project , this ) ;
152+
150153 this . loopData . exists . applyToSection ( this . _section , "loop_exists" ) . addListener ( this . onDirty )
154+ this . loopData . exists . addPostListener ( ( ) => this . onKeyframeChanged ( ) )
155+
151156 this . loopData . start . applyToSection ( this . _section , "loop_start" ) . addListener ( this . onDirty )
157+ this . loopData . start . addPostListener ( ( ) => this . onKeyframeChanged ( ) )
158+
152159 this . loopData . end . applyToSection ( this . _section , "loop_end" ) . addListener ( this . onDirty )
160+ this . loopData . end . addPostListener ( ( ) => this . onKeyframeChanged ( ) )
161+
153162 this . loopData . duration . applyToSection ( this . _section , "loop_duration" ) . addListener ( this . onDirty )
163+ this . loopData . duration . addPostListener ( ( ) => this . onKeyframeChanged ( ) )
164+
154165
155166 this . loopData . start . addPreModifyListener ( ( value , _ , naughtyModifyValue ) => {
156167 if ( value > this . loopData . end . value ) {
@@ -333,12 +344,37 @@ export default class DcaAnimation extends AnimatorGumballConsumer {
333344 }
334345
335346 animate ( delta : number ) {
347+ let time = this . time . value + delta ;
336348 if ( this . playing . value ) {
349+ if ( this . loopData . exists . value ) {
350+ const loopStart = this . loopData . start . value
351+ const loopEnd = this . loopData . end . value
352+ const loopDuration = this . loopData . duration . value
353+
354+ if ( time >= loopEnd && ( this . isCurrentlyLooping || this . shouldContinueLooping . value ) ) {
355+ //If the ticks are after the looping end + the looping duration, then set the ticks back.
356+ if ( time - delta >= loopEnd + loopDuration ) {
357+ this . time . value = time = loopStart + time - ( loopEnd + loopDuration )
358+ this . isCurrentlyLooping = false
359+ } else {
360+ //Animate all the keyframes at the end, and animate the looping keyframe in reverse.
361+ const percentDone = ( time - loopEnd ) / loopDuration ;
362+ this . displayTime . value = loopEnd + ( loopStart - loopEnd ) * percentDone
363+ this . loopingKeyframe . animate ( time - loopEnd )
364+ time = loopEnd ;
365+ this . isCurrentlyLooping = true
366+ }
367+
368+ }
369+ } else {
370+ this . isCurrentlyLooping = false
371+ }
372+
337373 this . updatingTimeNaturally = true
338374 this . time . value += delta
339375 this . updatingTimeNaturally = false
376+
340377 }
341- const time = this . time . value
342378 const skipForced = this . isDraggingTimeline || this . playing . value
343379 this . animateAt ( skipForced ? time : ( this . forceAnimationTime ?? time ) )
344380 }
@@ -425,6 +461,71 @@ export default class DcaAnimation extends AnimatorGumballConsumer {
425461 return animation
426462 }
427463
464+ onKeyframeChanged ( keyframe ?: DcaKeyframe ) {
465+ if ( keyframe === this . loopingKeyframe ) {
466+ return
467+ }
468+ this . needsSaving . value = true
469+
470+ this . loopingKeyframe . rotation . clear ( )
471+ this . loopingKeyframe . position . clear ( )
472+ this . loopingKeyframe . cubeGrow . clear ( )
473+
474+ this . project . model . resetVisuals ( )
475+ this . animateAt ( this . loopData . start . value )
476+ const dataStart = this . captureModel ( )
477+
478+ this . project . model . resetVisuals ( )
479+ this . animateAt ( this . loopData . end . value )
480+ const dataEnd = this . captureModel ( )
481+
482+ const subArrays = ( a : NumArray , b : NumArray ) => [
483+ a [ 0 ] - b [ 0 ] ,
484+ a [ 1 ] - b [ 1 ] ,
485+ a [ 2 ] - b [ 2 ] ,
486+ ] as const
487+
488+ this . project . model . identifierCubeMap . forEach ( ( cube , identifier ) => {
489+ const start = dataStart [ identifier ]
490+ const end = dataEnd [ identifier ]
491+ if ( start !== undefined && end !== undefined ) {
492+ this . loopingKeyframe . rotation . set ( cube . name . value , subArrays ( start . rotation , end . rotation ) )
493+ this . loopingKeyframe . position . set ( cube . name . value , subArrays ( start . position , end . position ) )
494+ this . loopingKeyframe . cubeGrow . set ( cube . name . value , subArrays ( start . cubeGrow , end . cubeGrow ) )
495+ }
496+ } )
497+
498+ console . log ( this . loopingKeyframe )
499+
500+ this . loopingKeyframe . duration . value = this . loopData . duration . value
501+ }
502+
503+ private captureModel ( ) {
504+ const data : Record < string , Record < "rotation" | "position" | "cubeGrow" , NumArray > > = { }
505+
506+ Array . from ( this . project . model . identifierCubeMap . values ( ) ) . forEach ( cube => {
507+ data [ cube . identifier ] = {
508+ rotation : [
509+ cube . cubeGroup . rotation . x ,
510+ cube . cubeGroup . rotation . y ,
511+ cube . cubeGroup . rotation . z ,
512+ ] ,
513+ position : [
514+ cube . cubeGroup . position . x ,
515+ cube . cubeGroup . position . y ,
516+ cube . cubeGroup . position . z ,
517+ ] ,
518+ cubeGrow : [
519+ cube . cubeGrowGroup . position . x ,
520+ cube . cubeGrowGroup . position . y ,
521+ cube . cubeGrowGroup . position . z ,
522+ ] ,
523+ }
524+ } )
525+
526+ return data
527+ }
528+
428529}
429530
430531export type ProgressionPoint = Readonly < { required ?: boolean , x : number , y : number } >
@@ -434,7 +535,7 @@ export class DcaKeyframe extends AnimatorGumballConsumerPart {
434535 readonly animation : DcaAnimation
435536 readonly _section : SectionHandle < UndoRedoDataType , KeyframeSectionType >
436537
437- private readonly onDirty = ( ) => this . animation . needsSaving . value = true
538+ private readonly onDirty = ( ) => this . animation . onKeyframeChanged ( this )
438539
439540 readonly startTime : LO < number >
440541 readonly duration : LO < number >
@@ -911,7 +1012,7 @@ export class KeyframeLayerData {
9111012 locked = false ,
9121013 definedMode = false
9131014 ) {
914- const onDirty = ( ) => parentAnimation . needsSaving . value = true
1015+ const onDirty = ( ) => parentAnimation . onKeyframeChanged ( )
9151016 this . _section = parentAnimation . undoRedoHandler . createNewSection ( `layer_${ this . layerId } ` as `layer_0`) //layer_0 is to trick the compiler to knowing that layer_{layerid} a number
9161017 this . _section . modifyFirst ( "layerId" , this . layerId , ( ) => { throw new Error ( "Tried to modify layerId" ) } )
9171018
0 commit comments