Skip to content

Commit

Permalink
feat: adding support of cinematics in tracks and timelines in animati…
Browse files Browse the repository at this point in the history
…on editor
  • Loading branch information
Julien Moreau-Mathis committed Sep 27, 2024
1 parent e2ceaa1 commit 2d4a5f5
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 43 deletions.
2 changes: 2 additions & 0 deletions editor/src/editor/layout/animation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ export class EditorAnimation extends Component<IEditorAnimationProps, IEditorAni
<EditorAnimationTracksPanel
animationEditor={this}
ref={(r) => this.tracks = r!}
cinematic={this.state.cinematic}
animatable={this.state.animatable}
/>

Expand All @@ -111,6 +112,7 @@ export class EditorAnimation extends Component<IEditorAnimationProps, IEditorAni
animationEditor={this}
editor={this.props.editor}
ref={(r) => this.timelines = r!}
cinematic={this.state.cinematic}
animatable={this.state.animatable}
/>

Expand Down
58 changes: 45 additions & 13 deletions editor/src/editor/layout/animation/timeline/timeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@ import { getInspectorPropertyValue } from "../../../../tools/property";

import { Editor } from "../../../main";

import { ICinematic } from "../cinematic/typings";

import { EditorAnimation } from "../../animation";

import { EditorAnimationTracker } from "./tracker";
import { EditorAnimationTimelineItem } from "./track";

export interface IEditorAnimationTimelinePanelProps {
editor: Editor;
cinematic: ICinematic | null;
animatable: IAnimatable | null;
animationEditor: EditorAnimation;
}
Expand Down Expand Up @@ -87,13 +90,23 @@ export class EditorAnimationTimelinePanel extends Component<IEditorAnimationTime
No animations found on this object.
</div>

<Button variant="secondary" className="flex items-center gap-2" onClick={() => this.props.animationEditor.tracks.addTrack()}>
<Button variant="secondary" className="flex items-center gap-2" onClick={() => this._handleAddTrack()}>
<AiOutlinePlus className="w-5 h-5" /> Add Track
</Button>
</div>
);
}

private _handleAddTrack(): void {
if (this.props.animatable) {
this.props.animationEditor.tracks.addAnimationTrack();
}

if (this.props.cinematic) {
// TODO: Add cinematic track
}
}

private _getAnimationsList(animations: Animation[]): ReactNode {
const width = this._getMaxWidthForTimeline();

Expand Down Expand Up @@ -150,11 +163,18 @@ export class EditorAnimationTimelinePanel extends Component<IEditorAnimationTime

private _getMaxFrameForTimeline(): number {
let frame = 0;
this.props.animatable?.animations?.forEach((animation) => {
animation.getKeys().forEach((key) => {
frame = Math.max(frame, key.frame);

if (this.props.animatable) {
this.props.animatable.animations?.forEach((animation) => {
animation.getKeys().forEach((key) => {
frame = Math.max(frame, key.frame);
});
});
});
}

if (this.props.cinematic) {
// TODO: get max frame for cinematic
}

return frame;
}
Expand Down Expand Up @@ -269,16 +289,22 @@ export class EditorAnimationTimelinePanel extends Component<IEditorAnimationTime
{ frame: maxFrame, value: maxFrame },
]);

scene.stopAnimation(this.props.animatable);
if (this.props.animatable) {
scene.stopAnimation(this.props.animatable);

this.props.animatable.animations.forEach((animation) => {
const keys = animation.getKeys();
const frame = keys[keys.length - 1].frame < currentTime ? keys[keys.length - 1].frame : currentTime;
this.props.animatable.animations.forEach((animation) => {
const keys = animation.getKeys();
const frame = keys[keys.length - 1].frame < currentTime ? keys[keys.length - 1].frame : currentTime;

this.props.editor.layout.preview.scene.beginDirectAnimation(this.props.animatable, [animation], frame, maxFrame, false, 1.0);
});
this.props.editor.layout.preview.scene.beginDirectAnimation(this.props.animatable, [animation], frame, maxFrame, false, 1.0);
});

this.props.editor.layout.preview.scene.beginDirectAnimation(this, [this._animation], currentTime, maxFrame, false, 1.0);
this.props.editor.layout.preview.scene.beginDirectAnimation(this, [this._animation], currentTime, maxFrame, false, 1.0);
}

if (this.props.cinematic) {
// TODO: stop cinematic
}

if (this._renderLoop) {
engine.stopRenderLoop(this._renderLoop);
Expand All @@ -301,7 +327,13 @@ export class EditorAnimationTimelinePanel extends Component<IEditorAnimationTime
this._renderLoop = null;
}

scene.stopAnimation(this.props.animatable);
if (this.props.animatable) {
scene.stopAnimation(this.props.animatable);
}

if (this.props.cinematic) {
// TODO: stop cinematic
}
}

private _handlePointerDown(ev: MouseEvent<HTMLDivElement, globalThis.MouseEvent>): void {
Expand Down
35 changes: 31 additions & 4 deletions editor/src/editor/layout/animation/tracks/item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@ import { Animation } from "babylonjs";
import { Button } from "../../../../ui/shadcn/ui/button";

import { EditorAnimation } from "../../animation";
import { ICinematicTrack } from "../cinematic/typings";

export interface IEditorAnimationTrackItemProps {
animation: Animation;
animation: Animation | null;
cinematicTrack: ICinematicTrack | null;

animationEditor: EditorAnimation;

onRemove: (animation: Animation) => void;
onRemoveAnimation: (animation: Animation) => void;
}

export class EditorAnimationTrackItem extends Component<IEditorAnimationTrackItemProps> {
Expand All @@ -27,21 +30,45 @@ export class EditorAnimationTrackItem extends Component<IEditorAnimationTrackIte
`}
>
<div>
{this.props.animation.targetProperty}
{this._getTitle()}
</div>

<Button
variant="ghost"
onClick={() => this._handleRemove()}
className={`
w-8 h-8 p-1
${this.props.animationEditor.state.selectedAnimation === this.props.animation ? "opacity-100" : "opacity-0"}
transition-all duration-300 ease-in-out
`}
onClick={() => this.props.onRemove(this.props.animation)}
>
<HiOutlineTrash className="w-5 h-5" />
</Button>
</div>
);
}

private _getTitle(): string {
if (this.props.animation) {
return this.props.animation.targetProperty;
}

if (this.props.cinematicTrack) {
if (this.props.cinematicTrack.propertyPath) {
return this.props.cinematicTrack.propertyPath;
}

if (this.props.cinematicTrack.animationGroups) {
return "Animation Groups";
}
}

return "Unknown property.";
}

private _handleRemove(): void {
if (this.props.animation) {
this.props.onRemoveAnimation(this.props.animation);
}
}
}
80 changes: 54 additions & 26 deletions editor/src/editor/layout/animation/tracks/tracks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,65 +11,93 @@ import { registerUndoRedo } from "../../../../tools/undoredo";
import { getInspectorPropertyValue } from "../../../../tools/property";
import { getAnimationTypeForObject } from "../../../../tools/animation/tools";

import { ICinematic, ICinematicTrack } from "../cinematic/typings";

import { EditorAnimation } from "../../animation";

import { showAddTrackPrompt } from "./add";
import { EditorAnimationTrackItem } from "./item";

export interface IEditorAnimationTracksPanelProps {
cinematic: ICinematic | null;
animatable: IAnimatable | null;

animationEditor: EditorAnimation;
}

export class EditorAnimationTracksPanel extends Component<IEditorAnimationTracksPanelProps> {
public render(): ReactNode {
if (this.props.animatable) {
return this._getAnimationsList(this.props.animatable.animations!);
if (!this.props.animatable && !this.props.cinematic) {
return this._getEmpty();
}

return this._getEmpty();
}

private _getEmpty(): ReactNode {
return (
<div className="flex justify-center items-center font-semibold text-xl w-96 h-full">
No object selected.
</div>
);
}

private _getAnimationsList(animations: Animation[]): ReactNode {
return (
<div className="flex flex-col w-96 h-full">
<div className="flex justify-between items-center w-full h-10 p-2">
<div className="font-thin text-muted-foreground">
({animations.length} tracks)
({this.props.animatable?.animations?.length ?? this.props.cinematic?.tracks.length} tracks)
</div>

<Button variant="ghost" className="w-8 h-8 p-1" onClick={() => this.addTrack()}>
<Button variant="ghost" className="w-8 h-8 p-1" onClick={() => this._handleAddTrack()}>
<AiOutlinePlus className="w-5 h-5" />
</Button>
</div>

<div className="flex flex-col w-full">
{animations.map((animation, index) => (
<EditorAnimationTrackItem
key={`${animation.targetProperty}${index}`}
animation={animation}
animationEditor={this.props.animationEditor}
onRemove={(animation) => this._handleRemoveTrack(animation)}
/>
))}
{this.props.animatable && this._getAnimationsList(this.props.animatable.animations!)}
{this.props.cinematic && this._getCinematicTracksList(this.props.cinematic.tracks)}
</div>
</div>
);
}

private _getEmpty(): ReactNode {
return (
<div className="flex justify-center items-center font-semibold text-xl w-96 h-full">
No object selected.
</div>
);
}

private _getAnimationsList(animations: Animation[]): ReactNode[] {
return animations.map((animation, index) => (
<EditorAnimationTrackItem
key={`${animation.targetProperty}${index}`}
animation={animation}
cinematicTrack={null}
animationEditor={this.props.animationEditor}
onRemoveAnimation={(animation) => this._handleRemoveAnimationTrack(animation)}
/>
));
}

private _getCinematicTracksList(tracks: ICinematicTrack[]): ReactNode[] {
return tracks.map((track, index) => (
<EditorAnimationTrackItem
key={`${track.propertyPath}${index}`}
animation={null}
cinematicTrack={track}
animationEditor={this.props.animationEditor}
onRemoveAnimation={(animation) => this._handleRemoveAnimationTrack(animation)}
/>
));
}

private _handleAddTrack(): void {
if (this.props.animatable) {
this.addAnimationTrack();
}

if (this.props.cinematic) {
// TODO: Add cinematic track
}
}

/**
* Shows a prompt to add a new track to the animatable object.
* Aka. animate a property on the currently selected animatable.
*/
public async addTrack(): Promise<unknown> {
public async addAnimationTrack(): Promise<unknown> {
const animatable = this.props.animatable;
if (!animatable) {
return;
Expand Down Expand Up @@ -132,7 +160,7 @@ export class EditorAnimationTracksPanel extends Component<IEditorAnimationTracks
this.props.animationEditor.forceUpdate();
}

private _handleRemoveTrack(animation: Animation): void {
private _handleRemoveAnimationTrack(animation: Animation): void {
const animatable = this.props.animatable;
if (!animatable) {
return;
Expand Down

0 comments on commit 2d4a5f5

Please sign in to comment.