Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,8 @@ yarn-error.log*

# turbo
.turbo

# gen
sw.js
sw.js.map
workbox-*.*
2 changes: 1 addition & 1 deletion apps/studio/src/contexts/CreatePortalContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const CreatePortalContext = ({ children }: PropsWithChildren<{}>) => {
const { darkMode } = useOptions()
return (
<Context.Provider value={node => overlay.current === null ? null : createPortal(node, overlay.current)}>
<div ref={overlay} className={"absolute z-10 pointer-events-none overflow-hidden h-full w-full " + (darkMode ? "dark" : "")}></div>
<div ref={overlay} className={"absolute z-30 pointer-events-none overflow-hidden h-full w-full " + (darkMode ? "dark" : "")}></div>
{children}
</Context.Provider>
)
Expand Down
2 changes: 1 addition & 1 deletion apps/studio/src/contexts/TooltipContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ const TooltipContextProvider = ({ children }: PropsWithChildren<{}>) => {
{tooltipValue !== null && tooltipValue !== undefined && createPortal(
<div
ref={containerRef}
className="absolute text-center border border-black p-0.5 dark:text-gray-300 dark:bg-gray-800 bg-gray-300"
className="z-50 absolute text-center border border-black p-0.5 dark:text-gray-300 dark:bg-gray-800 bg-gray-300"
>
{tooltipValue}
</div>
Expand Down
16 changes: 8 additions & 8 deletions apps/studio/src/studio/formats/animations/DCALoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@ const loadDCAAnimation = async (project: DcProject, name: string, buffer: ArrayB

animation.keyframes.value = data.keyframes.map(kf => readKeyframe(animation, kf))

animation.keyframeData.exits.value = data.loopData.exists
animation.keyframeData.start.value = data.loopData.start
animation.keyframeData.end.value = data.loopData.end
animation.keyframeData.duration.value = data.loopData.duration
animation.loopData.exists.value = data.loopData.exists
animation.loopData.start.value = data.loopData.start
animation.loopData.end.value = data.loopData.end
animation.loopData.duration.value = data.loopData.duration

convertRecordToMap(data.cubeNameOverrides ?? {}, animation.keyframeNameOverrides)

Expand Down Expand Up @@ -97,10 +97,10 @@ export const writeDCAAnimationWithFormat = async <T extends keyof OutputByType>(
name: animation.name.value,
keyframes: animation.keyframes.value.map(kf => writeKeyframe(kf)),
loopData: {
exists: animation.keyframeData.exits.value,
start: animation.keyframeData.start.value,
end: animation.keyframeData.end.value,
duration: animation.keyframeData.duration.value,
exists: animation.loopData.exists.value,
start: animation.loopData.start.value,
end: animation.loopData.end.value,
duration: animation.loopData.duration.value,
},
cubeNameOverrides: convertMapToRecord(animation.keyframeNameOverrides),
isSkeleton: animation.isSkeleton.value,
Expand Down
164 changes: 144 additions & 20 deletions apps/studio/src/studio/formats/animations/DcaAnimation.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { Euler, Quaternion, Vector3 } from 'three';
import { v4 } from 'uuid';
import { drawProgressionPointGraph, GraphType } from '../../../views/animator/logic/ProgressionPointGraph';
import { readFromClipboard, writeToClipboard } from '../../clipboard/Clipboard';
Expand All @@ -21,11 +20,6 @@ const kfmap_position = "pos_"
const kfmap_rotation = "rot_"
const kfmap_cubegrow = "cg_"

const tempVec = new Vector3()
const tempQuat = new Quaternion()
const tempEuler = new Euler()

let debug = false

type RootDataSectionType = {
section_name: "root_data",
Expand All @@ -36,7 +30,13 @@ type RootDataSectionType = {
keyframe_layers: readonly { layerId: number }[],
propertiesMode: "local" | "global",
time: number,
[k: `${typeof skeletal_export_named}_${string}`]: string

loop_exists: boolean,
loop_start: number,
loop_end: number,
loop_duration: number,

[k: `${typeof skeletal_export_named}_${string}`]: string,
}
}

Expand Down Expand Up @@ -99,9 +99,12 @@ export default class DcaAnimation extends AnimatorGumballConsumer {
readonly displayTime = new LO(0, this.onDirty)
readonly maxTime = new LO(1, this.onDirty)
readonly playing = new LO(false, this.onDirty)
displayTimeMatch: boolean = true

readonly keyframeData: KeyframeLoopData
readonly loopData: KeyframeLoopData
readonly loopingKeyframe: DcaKeyframe;
readonly shouldContinueLooping = new LO(false)
isCurrentlyLooping = false

readonly keyframeLayers = new LO<readonly KeyframeLayerData[]>([], this.onDirty)

readonly scroll = new LO(0, this.onDirty)
Expand Down Expand Up @@ -137,14 +140,45 @@ export default class DcaAnimation extends AnimatorGumballConsumer {

this.name = new LO(name, this.onDirty).applyToSection(this._section, "name")
this.project = project
this.keyframeData = new KeyframeLoopData()
this.loopData = new KeyframeLoopData()
this.animatorGumball = new AnimatorGumball(project)
this.time.addListener(value => {
if (this.displayTimeMatch) {
if (!this.isCurrentlyLooping) {
this.displayTime.value = value
}
})

this.loopingKeyframe = new DcaKeyframe(this.project, this, v4(), -1);

this.loopData.exists.applyToSection(this._section, "loop_exists").addListener(this.onDirty)
this.loopData.exists.addPostListener(() => this.onKeyframeChanged())

this.loopData.start.applyToSection(this._section, "loop_start").addListener(this.onDirty)
this.loopData.start.addPostListener(() => this.onKeyframeChanged())

this.loopData.end.applyToSection(this._section, "loop_end").addListener(this.onDirty)
this.loopData.end.addPostListener(() => this.onKeyframeChanged())

this.loopData.duration.applyToSection(this._section, "loop_duration").addListener(this.onDirty)
this.loopData.duration.addPostListener(() => this.onKeyframeChanged())


this.loopData.start.addPreModifyListener((value, _, naughtyModifyValue) => {
if (value > this.loopData.end.value) {
const end = this.loopData.end.value
this.loopData.end.value = value
naughtyModifyValue(end)
}
})

this.loopData.end.addPreModifyListener((value, _, naughtyModifyValue) => {
if (value < this.loopData.start.value) {
const start = this.loopData.start.value
this.loopData.start.value = value
naughtyModifyValue(start)
}
})

this.needsSaving.addPreModifyListener((newValue, oldValue, naughtyModifyValue) => naughtyModifyValue(oldValue || (newValue && !this.undoRedoHandler.ignoreActions)))

this.keyframes.addListener(value => {
Expand Down Expand Up @@ -210,7 +244,7 @@ export default class DcaAnimation extends AnimatorGumballConsumer {
}

ensureLayerExists(layerId: number) {
if (!this.keyframeLayers.value.some(l => l.layerId === layerId)) {
if (layerId >= 0 && !this.keyframeLayers.value.some(l => l.layerId === layerId)) {
this.keyframeLayers.value = this.keyframeLayers.value.concat(new KeyframeLayerData(this, layerId))
}
}
Expand Down Expand Up @@ -310,12 +344,37 @@ export default class DcaAnimation extends AnimatorGumballConsumer {
}

animate(delta: number) {
let time = this.time.value + delta;
if (this.playing.value) {
if (this.loopData.exists.value) {
const loopStart = this.loopData.start.value
const loopEnd = this.loopData.end.value
const loopDuration = this.loopData.duration.value

if (time >= loopEnd && (this.isCurrentlyLooping || this.shouldContinueLooping.value)) {
//If the ticks are after the looping end + the looping duration, then set the ticks back.
if (time - delta >= loopEnd + loopDuration) {
this.time.value = time = loopStart + time - (loopEnd + loopDuration)
this.isCurrentlyLooping = false
} else {
//Animate all the keyframes at the end, and animate the looping keyframe in reverse.
const percentDone = (time - loopEnd) / loopDuration;
this.displayTime.value = loopEnd + (loopStart - loopEnd) * percentDone
this.loopingKeyframe.animate(time - loopEnd)
time = loopEnd;
this.isCurrentlyLooping = true
}

}
} else {
this.isCurrentlyLooping = false
}

this.updatingTimeNaturally = true
this.time.value += delta
this.updatingTimeNaturally = false

}
const time = this.time.value
const skipForced = this.isDraggingTimeline || this.playing.value
this.animateAt(skipForced ? time : (this.forceAnimationTime ?? time))
}
Expand Down Expand Up @@ -392,16 +451,81 @@ export default class DcaAnimation extends AnimatorGumballConsumer {
cloneAnimation() {
const animation = new DcaAnimation(this.project, this.name.value)

animation.keyframeData.exits.value = this.keyframeData.exits.value
animation.keyframeData.start.value = this.keyframeData.start.value
animation.keyframeData.end.value = this.keyframeData.end.value
animation.keyframeData.duration.value = this.keyframeData.duration.value
animation.loopData.exists.value = this.loopData.exists.value
animation.loopData.start.value = this.loopData.start.value
animation.loopData.end.value = this.loopData.end.value
animation.loopData.duration.value = this.loopData.duration.value

animation.keyframes.value = this.keyframes.value.map(kf => kf.cloneBasics(animation))

return animation
}

onKeyframeChanged(keyframe?: DcaKeyframe) {
if (keyframe === this.loopingKeyframe) {
return
}
this.needsSaving.value = true

this.loopingKeyframe.rotation.clear()
this.loopingKeyframe.position.clear()
this.loopingKeyframe.cubeGrow.clear()

this.project.model.resetVisuals()
this.animateAt(this.loopData.start.value)
const dataStart = this.captureModel()

this.project.model.resetVisuals()
this.animateAt(this.loopData.end.value)
const dataEnd = this.captureModel()

const subArrays = (a: NumArray, b: NumArray) => [
a[0] - b[0],
a[1] - b[1],
a[2] - b[2],
] as const

this.project.model.identifierCubeMap.forEach((cube, identifier) => {
const start = dataStart[identifier]
const end = dataEnd[identifier]
if (start !== undefined && end !== undefined) {
this.loopingKeyframe.rotation.set(cube.name.value, subArrays(start.rotation, end.rotation))
this.loopingKeyframe.position.set(cube.name.value, subArrays(start.position, end.position))
this.loopingKeyframe.cubeGrow.set(cube.name.value, subArrays(start.cubeGrow, end.cubeGrow))
}
})

console.log(this.loopingKeyframe)

this.loopingKeyframe.duration.value = this.loopData.duration.value
}

private captureModel() {
const data: Record<string, Record<"rotation" | "position" | "cubeGrow", NumArray>> = {}

Array.from(this.project.model.identifierCubeMap.values()).forEach(cube => {
data[cube.identifier] = {
rotation: [
cube.cubeGroup.rotation.x,
cube.cubeGroup.rotation.y,
cube.cubeGroup.rotation.z,
],
position: [
cube.cubeGroup.position.x,
cube.cubeGroup.position.y,
cube.cubeGroup.position.z,
],
cubeGrow: [
cube.cubeGrowGroup.position.x,
cube.cubeGrowGroup.position.y,
cube.cubeGrowGroup.position.z,
],
}
})

return data
}

}

export type ProgressionPoint = Readonly<{ required?: boolean, x: number, y: number }>
Expand All @@ -411,7 +535,7 @@ export class DcaKeyframe extends AnimatorGumballConsumerPart {
readonly animation: DcaAnimation
readonly _section: SectionHandle<UndoRedoDataType, KeyframeSectionType>

private readonly onDirty = () => this.animation.needsSaving.value = true
private readonly onDirty = () => this.animation.onKeyframeChanged(this)

readonly startTime: LO<number>
readonly duration: LO<number>
Expand Down Expand Up @@ -888,7 +1012,7 @@ export class KeyframeLayerData {
locked = false,
definedMode = false
) {
const onDirty = () => parentAnimation.needsSaving.value = true
const onDirty = () => parentAnimation.onKeyframeChanged()
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
this._section.modifyFirst("layerId", this.layerId, () => { throw new Error("Tried to modify layerId") })

Expand Down Expand Up @@ -916,7 +1040,7 @@ export class KeyframeLayerData {
}

export class KeyframeLoopData {
readonly exits = new LO(false)
readonly exists = new LO(false)
readonly start = new LO<number>(0)
readonly end = new LO<number>(0)
readonly duration = new LO<number>(0)
Expand Down
8 changes: 4 additions & 4 deletions apps/studio/src/studio/formats/animations/OldDcaLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ export const loadDCAAnimationOLD = (project: DcProject, name: string, buffer: St

//Read the loop data
if (version >= 9 && buffer.readBool()) {
animation.keyframeData.start.value = buffer.readNumber()
animation.keyframeData.end.value = buffer.readNumber()
animation.keyframeData.duration.value = buffer.readNumber()
animation.keyframeData.exits.value = true
animation.loopData.start.value = buffer.readNumber()
animation.loopData.end.value = buffer.readNumber()
animation.loopData.duration.value = buffer.readNumber()
animation.loopData.exists.value = true
}
//Read the keyframes
const keyframes: DcaKeyframe[] = []
Expand Down
2 changes: 1 addition & 1 deletion apps/studio/src/studio/formats/project/DcProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ export default class DcProject {
if (animaion.isSkeleton.value) {
continue
}
for (let keyframe of animaion.keyframes.value) {
for (let keyframe of [animaion.loopingKeyframe, ...animaion.keyframes.value]) {
for (let map of [keyframe.position, keyframe.rotation, keyframe.cubeGrow]) {
const value = map.get(oldName)
if (value !== undefined) {
Expand Down
1 change: 1 addition & 0 deletions apps/studio/src/studio/keycombos/KeyCombos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ const keyCombos = {
pause_or_play: new KeyCombo('Pause/Play', "Pause or play the animation", 'Space', false),
restart_animation: new KeyCombo('Restart Animation', "Restart the animation", 'Space', true),
stop_animation: new KeyCombo('Stop Animation', "Stop the animation", 'Space', false, true),
toggle_looping: new KeyCombo('Toggle Looping', "Toggle looping", 'L', true),
}
},
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export class LO<T> {
private postListeners: Set<Listener<T>> = new Set(),
) {
if (defaultCallback) {
this.listners.add(defaultCallback)
this.postListeners.add(defaultCallback)
}
this.internalValue = _value;
}
Expand Down
Loading