@aura3d/editor-runtime 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AnimationSceneEditor.d.ts +111 -0
- package/dist/AnimationSceneEditor.d.ts.map +1 -0
- package/dist/AnimationSceneEditor.js +285 -0
- package/dist/AnimationSceneEditor.js.map +1 -0
- package/dist/AssetDropZone.d.ts +9 -9
- package/dist/AssetDropZone.d.ts.map +1 -1
- package/dist/AssetDropZone.js +4 -4
- package/dist/AssetDropZone.js.map +1 -1
- package/dist/EventTrackEditor.d.ts +54 -0
- package/dist/EventTrackEditor.d.ts.map +1 -0
- package/dist/EventTrackEditor.js +124 -0
- package/dist/EventTrackEditor.js.map +1 -0
- package/dist/SceneOutliner.d.ts +3 -3
- package/dist/SceneOutliner.d.ts.map +1 -1
- package/dist/SceneOutliner.js.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/package.json +5 -4
- package/src/{CartoonSceneEditor.ts → AnimationSceneEditor.ts} +68 -68
- package/src/AssetDropZone.ts +16 -16
- package/src/EventTrackEditor.ts +169 -0
- package/src/SceneOutliner.ts +4 -4
- package/src/index.ts +5 -3
- package/tests/editor-runtime.test.ts +5 -5
package/src/AssetDropZone.ts
CHANGED
|
@@ -2,12 +2,12 @@ import type { Command } from "./Command";
|
|
|
2
2
|
import { CommandHistory } from "./CommandHistory";
|
|
3
3
|
import type { EditorProjectAssetDocument } from "./ProjectSerializer";
|
|
4
4
|
|
|
5
|
-
export type
|
|
5
|
+
export type AnimationAssetCategory = "character" | "set" | "prop" | "camera" | "light" | "audio" | "material" | "unknown";
|
|
6
6
|
|
|
7
|
-
export interface
|
|
7
|
+
export interface AnimationEditorAssetReference extends EditorProjectAssetDocument {
|
|
8
8
|
readonly kind?: "aura-asset-ref" | "editor-asset-ref";
|
|
9
|
-
readonly category?:
|
|
10
|
-
readonly profile?: "
|
|
9
|
+
readonly category?: AnimationAssetCategory;
|
|
10
|
+
readonly profile?: "animation-character" | "animation-set" | "animation-prop" | "animation-audio" | string;
|
|
11
11
|
readonly style?: string;
|
|
12
12
|
readonly rigType?: "humanoid" | "creature" | "prop" | "none";
|
|
13
13
|
readonly lipSyncReady?: boolean;
|
|
@@ -21,14 +21,14 @@ export interface AssetDropPlacement {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export interface AssetDropResult<TNode> {
|
|
24
|
-
readonly asset:
|
|
24
|
+
readonly asset: AnimationEditorAssetReference;
|
|
25
25
|
readonly node: TNode;
|
|
26
26
|
readonly placement: AssetDropPlacement;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
export interface AssetDropZoneOptions<TNode> {
|
|
30
30
|
readonly history?: CommandHistory;
|
|
31
|
-
readonly createNode: (asset:
|
|
31
|
+
readonly createNode: (asset: AnimationEditorAssetReference, placement: AssetDropPlacement) => TNode;
|
|
32
32
|
readonly addNode: (node: TNode, placement: AssetDropPlacement) => void;
|
|
33
33
|
readonly removeNode: (node: TNode) => void;
|
|
34
34
|
readonly onDrop?: (result: AssetDropResult<TNode>) => void;
|
|
@@ -41,7 +41,7 @@ export class AssetDropZone<TNode = unknown> {
|
|
|
41
41
|
this.history = options.history ?? new CommandHistory();
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
async dropAsset(asset:
|
|
44
|
+
async dropAsset(asset: AnimationEditorAssetReference, placement: AssetDropPlacement = {}): Promise<AssetDropResult<TNode>> {
|
|
45
45
|
validateAssetReference(asset);
|
|
46
46
|
validatePlacement(placement);
|
|
47
47
|
const node = this.options.createNode(asset, placement);
|
|
@@ -82,7 +82,7 @@ class DropAssetCommand<TNode> implements Command {
|
|
|
82
82
|
|
|
83
83
|
constructor(
|
|
84
84
|
private readonly options: Pick<AssetDropZoneOptions<TNode>, "addNode" | "removeNode">,
|
|
85
|
-
private readonly asset:
|
|
85
|
+
private readonly asset: AnimationEditorAssetReference,
|
|
86
86
|
private readonly node: TNode,
|
|
87
87
|
private readonly placement: AssetDropPlacement
|
|
88
88
|
) {
|
|
@@ -98,21 +98,21 @@ class DropAssetCommand<TNode> implements Command {
|
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
export function serializeAssetForDrag(asset:
|
|
101
|
+
export function serializeAssetForDrag(asset: AnimationEditorAssetReference): string {
|
|
102
102
|
validateAssetReference(asset);
|
|
103
103
|
return JSON.stringify(asset);
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
export function readAssetFromDataTransfer(dataTransfer: DataTransfer | null):
|
|
106
|
+
export function readAssetFromDataTransfer(dataTransfer: DataTransfer | null): AnimationEditorAssetReference | undefined {
|
|
107
107
|
if (!dataTransfer) return undefined;
|
|
108
108
|
const encoded = dataTransfer.getData("application/x-aura3d-asset") || dataTransfer.getData("text/plain");
|
|
109
109
|
if (!encoded.trim()) return undefined;
|
|
110
|
-
const parsed = JSON.parse(encoded) as
|
|
110
|
+
const parsed = JSON.parse(encoded) as AnimationEditorAssetReference;
|
|
111
111
|
validateAssetReference(parsed);
|
|
112
112
|
return parsed;
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
function validateAssetReference(asset:
|
|
115
|
+
function validateAssetReference(asset: AnimationEditorAssetReference): void {
|
|
116
116
|
if (!asset.id?.trim()) throw new Error("Dropped Aura3D asset id is required.");
|
|
117
117
|
if (!asset.name?.trim()) throw new Error(`Dropped Aura3D asset name is required: ${asset.id}`);
|
|
118
118
|
if ((asset.uri && /^https?:\/\//i.test(asset.uri) || asset.source && /^https?:\/\//i.test(asset.source)) && asset.kind !== "aura-asset-ref") {
|
|
@@ -121,12 +121,12 @@ function validateAssetReference(asset: CartoonEditorAssetReference): void {
|
|
|
121
121
|
if (asset.source !== undefined && !asset.source.trim()) throw new Error(`Dropped Aura3D asset source cannot be empty: ${asset.id}`);
|
|
122
122
|
if (asset.license !== undefined && !asset.license.trim()) throw new Error(`Dropped Aura3D asset license cannot be empty: ${asset.id}`);
|
|
123
123
|
if (asset.category === "character") {
|
|
124
|
-
if (asset.rigType === "none") throw new Error(`
|
|
125
|
-
if ((asset.clips?.length ?? 0) === 0) throw new Error(`
|
|
126
|
-
if (!asset.lipSyncReady) throw new Error(`
|
|
124
|
+
if (asset.rigType === "none") throw new Error(`Animation character asset "${asset.id}" must be rigged.`);
|
|
125
|
+
if ((asset.clips?.length ?? 0) === 0) throw new Error(`Animation character asset "${asset.id}" requires animation clip metadata.`);
|
|
126
|
+
if (!asset.lipSyncReady) throw new Error(`Animation character asset "${asset.id}" requires lip-sync readiness metadata.`);
|
|
127
127
|
}
|
|
128
128
|
if ((asset.category === "set" || asset.category === "audio") && !asset.license) {
|
|
129
|
-
throw new Error(`
|
|
129
|
+
throw new Error(`Animation ${asset.category} asset "${asset.id}" requires license metadata.`);
|
|
130
130
|
}
|
|
131
131
|
}
|
|
132
132
|
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
// Editor authoring controller for animation event tracks (T2.2). Lets a timeline UI add / move /
|
|
2
|
+
// delete typed markers on named lanes (hitbox active-frames, footsteps, VFX, generic markers) and
|
|
3
|
+
// serialize to the canonical `animation-event-tracks/v1` shape that `@aura3d/animation`'s
|
|
4
|
+
// `deserializeAnimationEventTracks` / `sampleClipEvents` consume — so authored tracks drive gameplay
|
|
5
|
+
// (hitbox windows) and playback (footstep/VFX triggers) deterministically.
|
|
6
|
+
//
|
|
7
|
+
// Type-only imports from `@aura3d/animation` keep this controller free of any runtime coupling to
|
|
8
|
+
// that package (the types are erased at build time); the serialized output is verified to round-trip
|
|
9
|
+
// through the animation package's deserializer in tests.
|
|
10
|
+
|
|
11
|
+
import type {
|
|
12
|
+
AnimationClipEvent,
|
|
13
|
+
AnimationClipEventType,
|
|
14
|
+
AnimationEventTrack,
|
|
15
|
+
SerializedAnimationEventTracks
|
|
16
|
+
} from "@aura3d/animation";
|
|
17
|
+
|
|
18
|
+
export interface EventMarkerView {
|
|
19
|
+
readonly id: string;
|
|
20
|
+
readonly time: number;
|
|
21
|
+
readonly duration?: number;
|
|
22
|
+
readonly type: AnimationClipEventType;
|
|
23
|
+
readonly selected: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface EventTrackLane {
|
|
27
|
+
readonly name: string;
|
|
28
|
+
readonly type: AnimationClipEventType;
|
|
29
|
+
readonly markers: readonly EventMarkerView[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface EventTrackEditorOptions {
|
|
33
|
+
readonly clipId: string;
|
|
34
|
+
readonly duration: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface AddEventMarkerOptions {
|
|
38
|
+
readonly id?: string;
|
|
39
|
+
readonly type?: AnimationClipEventType;
|
|
40
|
+
/** Window length (seconds). >0 makes the marker an active-frame window (e.g. hitbox on/off). */
|
|
41
|
+
readonly duration?: number;
|
|
42
|
+
readonly payload?: unknown;
|
|
43
|
+
readonly tags?: readonly string[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface LaneState {
|
|
47
|
+
type: AnimationClipEventType;
|
|
48
|
+
markers: AnimationClipEvent[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Timeline event-track authoring controller. Deterministic: marker ids come from a monotonic
|
|
53
|
+
* counter (no `Date.now`/`Math.random`), so undo/redo and serialization are reproducible.
|
|
54
|
+
*/
|
|
55
|
+
export class EventTrackEditor {
|
|
56
|
+
readonly clipId: string;
|
|
57
|
+
duration: number;
|
|
58
|
+
private readonly order: string[] = [];
|
|
59
|
+
private readonly lanesMap = new Map<string, LaneState>();
|
|
60
|
+
private selectedId: string | undefined;
|
|
61
|
+
private idCounter = 0;
|
|
62
|
+
|
|
63
|
+
constructor(options: EventTrackEditorOptions) {
|
|
64
|
+
if (!Number.isFinite(options.duration) || options.duration < 0) {
|
|
65
|
+
throw new Error("EventTrackEditor duration must be a finite, non-negative number.");
|
|
66
|
+
}
|
|
67
|
+
this.clipId = options.clipId;
|
|
68
|
+
this.duration = options.duration;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
addTrack(name: string, type: AnimationClipEventType = "marker"): this {
|
|
72
|
+
if (!this.lanesMap.has(name)) {
|
|
73
|
+
this.lanesMap.set(name, { type, markers: [] });
|
|
74
|
+
this.order.push(name);
|
|
75
|
+
}
|
|
76
|
+
return this;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
removeTrack(name: string): this {
|
|
80
|
+
if (this.lanesMap.delete(name)) this.order.splice(this.order.indexOf(name), 1);
|
|
81
|
+
return this;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Add a marker to a lane (auto-creates the lane). Returns the new marker id. */
|
|
85
|
+
addMarker(trackName: string, time: number, options: AddEventMarkerOptions = {}): string {
|
|
86
|
+
if (!Number.isFinite(time) || time < 0) {
|
|
87
|
+
throw new Error("Event marker time must be a finite, non-negative number.");
|
|
88
|
+
}
|
|
89
|
+
this.addTrack(trackName, options.type);
|
|
90
|
+
const lane = this.lanesMap.get(trackName)!;
|
|
91
|
+
const id = options.id ?? `${trackName}#${this.idCounter++}`;
|
|
92
|
+
lane.markers.push({
|
|
93
|
+
id,
|
|
94
|
+
name: trackName,
|
|
95
|
+
type: options.type ?? lane.type,
|
|
96
|
+
time,
|
|
97
|
+
...(options.duration !== undefined ? { duration: options.duration } : {}),
|
|
98
|
+
...(options.payload !== undefined ? { payload: options.payload } : {}),
|
|
99
|
+
...(options.tags !== undefined ? { tags: options.tags } : {})
|
|
100
|
+
});
|
|
101
|
+
lane.markers.sort((a, b) => a.time - b.time);
|
|
102
|
+
return id;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Move an existing marker to a new time. */
|
|
106
|
+
moveMarker(trackName: string, markerId: string, time: number): this {
|
|
107
|
+
if (!Number.isFinite(time) || time < 0) {
|
|
108
|
+
throw new Error("Event marker time must be a finite, non-negative number.");
|
|
109
|
+
}
|
|
110
|
+
const lane = this.lanesMap.get(trackName);
|
|
111
|
+
const index = lane?.markers.findIndex((m) => m.id === markerId) ?? -1;
|
|
112
|
+
if (!lane || index < 0) throw new Error(`Marker ${markerId} not found on track ${trackName}.`);
|
|
113
|
+
lane.markers[index] = { ...lane.markers[index]!, time };
|
|
114
|
+
lane.markers.sort((a, b) => a.time - b.time);
|
|
115
|
+
return this;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Delete a marker. */
|
|
119
|
+
removeMarker(trackName: string, markerId: string): this {
|
|
120
|
+
const lane = this.lanesMap.get(trackName);
|
|
121
|
+
if (!lane) return this;
|
|
122
|
+
const index = lane.markers.findIndex((m) => m.id === markerId);
|
|
123
|
+
if (index >= 0) lane.markers.splice(index, 1);
|
|
124
|
+
if (this.selectedId === markerId) this.selectedId = undefined;
|
|
125
|
+
return this;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
select(markerId: string | undefined): this {
|
|
129
|
+
this.selectedId = markerId;
|
|
130
|
+
return this;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
markerCount(): number {
|
|
134
|
+
let count = 0;
|
|
135
|
+
for (const lane of this.lanesMap.values()) count += lane.markers.length;
|
|
136
|
+
return count;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** View-model for the timeline UI: lanes with marker positions + selection state. */
|
|
140
|
+
lanes(): readonly EventTrackLane[] {
|
|
141
|
+
return this.order.map((name) => {
|
|
142
|
+
const lane = this.lanesMap.get(name)!;
|
|
143
|
+
return {
|
|
144
|
+
name,
|
|
145
|
+
type: lane.type,
|
|
146
|
+
markers: lane.markers.map((m) => ({
|
|
147
|
+
id: m.id!,
|
|
148
|
+
time: m.time,
|
|
149
|
+
...(m.duration !== undefined ? { duration: m.duration } : {}),
|
|
150
|
+
type: m.type ?? lane.type,
|
|
151
|
+
selected: m.id === this.selectedId
|
|
152
|
+
}))
|
|
153
|
+
};
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Serialize to the canonical `animation-event-tracks/v1` shape. */
|
|
158
|
+
serialize(): SerializedAnimationEventTracks {
|
|
159
|
+
const tracks: AnimationEventTrack[] = this.order.map((name) => {
|
|
160
|
+
const lane = this.lanesMap.get(name)!;
|
|
161
|
+
return { name, type: lane.type, markers: lane.markers.map((m) => ({ ...m })) };
|
|
162
|
+
});
|
|
163
|
+
return { schema: "animation-event-tracks/v1", clipId: this.clipId, duration: this.duration, tracks };
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function createEventTrackEditor(options: EventTrackEditorOptions): EventTrackEditor {
|
|
168
|
+
return new EventTrackEditor(options);
|
|
169
|
+
}
|
package/src/SceneOutliner.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { HierarchyModel, type HierarchyNodeDescriptor } from "./HierarchyModel";
|
|
2
2
|
import type { SelectionId } from "./Selection";
|
|
3
|
-
import type {
|
|
3
|
+
import type { AnimationSceneNode } from "./AnimationSceneEditor";
|
|
4
4
|
|
|
5
5
|
export interface SceneOutlinerItem extends HierarchyNodeDescriptor {
|
|
6
6
|
readonly icon: string;
|
|
@@ -16,7 +16,7 @@ export class SceneOutliner {
|
|
|
16
16
|
private readonly hierarchy = new HierarchyModel();
|
|
17
17
|
private disposers: (() => void)[] = [];
|
|
18
18
|
|
|
19
|
-
describe(root:
|
|
19
|
+
describe(root: AnimationSceneNode, selectedIds: ReadonlySet<SelectionId> = new Set()): readonly SceneOutlinerItem[] {
|
|
20
20
|
return this.hierarchy.flatten(root, selectedIds).map((item) => {
|
|
21
21
|
const node = findNode(root, String(item.id));
|
|
22
22
|
const kind = node?.kind ?? "unknown";
|
|
@@ -28,7 +28,7 @@ export class SceneOutliner {
|
|
|
28
28
|
});
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
render(root:
|
|
31
|
+
render(root: AnimationSceneNode, container: HTMLElement, options: SceneOutlinerOptions = {}): readonly SceneOutlinerItem[] {
|
|
32
32
|
this.dispose();
|
|
33
33
|
const items = this.describe(root, options.selectedIds);
|
|
34
34
|
container.replaceChildren();
|
|
@@ -68,7 +68,7 @@ function iconForKind(kind: string): string {
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
function findNode(root:
|
|
71
|
+
function findNode(root: AnimationSceneNode, id: string): AnimationSceneNode | undefined {
|
|
72
72
|
if (root.id === id) return root;
|
|
73
73
|
for (const child of root.children) {
|
|
74
74
|
const found = findNode(child, id);
|
package/src/index.ts
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
export type { Command, CommandContext } from "./Command";
|
|
2
2
|
export { CommandHistory, CommandTransactionError } from "./CommandHistory";
|
|
3
3
|
export { AssetDropZone, readAssetFromDataTransfer, serializeAssetForDrag } from "./AssetDropZone";
|
|
4
|
-
export type { AssetDropPlacement, AssetDropResult, AssetDropZoneOptions,
|
|
4
|
+
export type { AssetDropPlacement, AssetDropResult, AssetDropZoneOptions, AnimationAssetCategory, AnimationEditorAssetReference } from "./AssetDropZone";
|
|
5
5
|
export { CameraPathEditor, createCameraPathEditor, readCameraPathKeyframes } from "./CameraPathEditor";
|
|
6
6
|
export type { EditorCameraPathEvidence, EditorCameraPathKeyframe, EditorCameraPathSample, EditorCameraVector3 } from "./CameraPathEditor";
|
|
7
|
-
export {
|
|
8
|
-
export type {
|
|
7
|
+
export { AnimationSceneEditor, createAnimationSceneNode } from "./AnimationSceneEditor";
|
|
8
|
+
export type { AnimationSceneEditorOptions, AnimationSceneEditorSnapshot, AnimationSceneEpisodeSnapshot, AnimationSceneEpisodeState, AnimationSceneNode, AnimationSceneTransform } from "./AnimationSceneEditor";
|
|
9
9
|
export { CurveEditor } from "./CurveEditor";
|
|
10
10
|
export type { TimelineCurveEvidence, TimelineCurvePoint, TimelineCurveSample } from "./CurveEditor";
|
|
11
11
|
export { DiagnosticsOverlayModel } from "./DiagnosticsOverlayModel";
|
|
12
12
|
export type { EditorDiagnosticsInput, EditorDiagnosticsResource, EditorDiagnosticsSnapshot } from "./DiagnosticsOverlayModel";
|
|
13
13
|
export { EditorRuntime } from "./EditorRuntime";
|
|
14
14
|
export type { EditorMode, EditorRuntimeSnapshot } from "./EditorRuntime";
|
|
15
|
+
export { EventTrackEditor, createEventTrackEditor } from "./EventTrackEditor";
|
|
16
|
+
export type { AddEventMarkerOptions, EventMarkerView, EventTrackEditorOptions, EventTrackLane } from "./EventTrackEditor";
|
|
15
17
|
export { EpisodeReviewPanel, createEpisodeReviewPanel } from "./EpisodeReviewPanel";
|
|
16
18
|
export type { EpisodeReviewManualVisemeEdit, EpisodeReviewNote, EpisodeReviewPanelSnapshot, EpisodeReviewPanelState, EpisodeReviewRejectedFrame, EpisodeReviewStatus, EpisodeReviewWaveformLane } from "./EpisodeReviewPanel";
|
|
17
19
|
export { EditorStateModel, createMemoryEditorStateStorage } from "./EditorStateModel";
|
|
@@ -4,7 +4,7 @@ import { Ray, Vector3 } from "@aura3d/math";
|
|
|
4
4
|
import { Scene } from "@aura3d/scene";
|
|
5
5
|
import {
|
|
6
6
|
CommandHistory,
|
|
7
|
-
|
|
7
|
+
AnimationSceneEditor,
|
|
8
8
|
CameraPathEditor,
|
|
9
9
|
CurveEditor,
|
|
10
10
|
CreateNodeCommand,
|
|
@@ -692,8 +692,8 @@ test("KeyframeEditor and CurveEditor author deterministic bezier keyframes on ti
|
|
|
692
692
|
assert.equal(curve.evidence(clip, "transform.position.x").evidence.deterministicSampling, true);
|
|
693
693
|
});
|
|
694
694
|
|
|
695
|
-
test("
|
|
696
|
-
const editor = new
|
|
695
|
+
test("AnimationSceneEditor, AssetDropZone, SceneOutliner, and PropertyPanel compose and edit animation scene nodes", async () => {
|
|
696
|
+
const editor = new AnimationSceneEditor();
|
|
697
697
|
const reviewPanel = new EpisodeReviewPanel({
|
|
698
698
|
packageId: "moon-garden-001",
|
|
699
699
|
packageHash: "sha256-review",
|
|
@@ -757,7 +757,7 @@ test("CartoonSceneEditor, AssetDropZone, SceneOutliner, and PropertyPanel compos
|
|
|
757
757
|
assert.equal(node.transform.position.x, 1);
|
|
758
758
|
|
|
759
759
|
const serialized = editor.serializeScene();
|
|
760
|
-
const restored = new
|
|
760
|
+
const restored = new AnimationSceneEditor();
|
|
761
761
|
restored.loadScene(serialized);
|
|
762
762
|
assert.equal(restored.snapshot().nodeCount, 2);
|
|
763
763
|
|
|
@@ -772,7 +772,7 @@ test("CartoonSceneEditor, AssetDropZone, SceneOutliner, and PropertyPanel compos
|
|
|
772
772
|
}), /typed Aura3D asset reference/);
|
|
773
773
|
});
|
|
774
774
|
|
|
775
|
-
test("Timeline track type helpers create
|
|
775
|
+
test("Timeline track type helpers create animation timeline lanes compatible with route playback", () => {
|
|
776
776
|
const dialogue = createTimelineTrackConfig("dialogue", "Dialogue");
|
|
777
777
|
const captions = createTimelineTrackConfig("caption", "Captions");
|
|
778
778
|
const shots = createTimelineTrackConfig("shot", "Shots");
|