@aura3d/editor-runtime 1.4.0 → 1.4.2

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.
Files changed (48) hide show
  1. package/package.json +36 -5
  2. package/src/AnimationSceneEditor.ts +0 -368
  3. package/src/AssetDropZone.ts +0 -144
  4. package/src/CameraPathEditor.ts +0 -223
  5. package/src/Command.ts +0 -11
  6. package/src/CommandHistory.ts +0 -136
  7. package/src/CurveEditor.ts +0 -152
  8. package/src/DiagnosticsOverlayModel.ts +0 -85
  9. package/src/EditorPluginHost.ts +0 -106
  10. package/src/EditorRuntime.ts +0 -349
  11. package/src/EditorStateModel.ts +0 -336
  12. package/src/EpisodeReviewPanel.ts +0 -236
  13. package/src/EventTrackEditor.ts +0 -169
  14. package/src/Gizmo.ts +0 -101
  15. package/src/HierarchyModel.ts +0 -57
  16. package/src/InspectorModel.ts +0 -71
  17. package/src/KeyframeEditor.ts +0 -214
  18. package/src/LocalizationAccessibilityFixtures.ts +0 -368
  19. package/src/MaterialVariantWorkflow.ts +0 -101
  20. package/src/MultiUserReviewWorkflow.ts +0 -229
  21. package/src/NonlinearAnimationEditor.ts +0 -262
  22. package/src/PickingService.ts +0 -174
  23. package/src/PlayModeBridge.ts +0 -27
  24. package/src/PrefabRegistry.ts +0 -164
  25. package/src/ProjectSerializer.ts +0 -200
  26. package/src/PropertyPanel.ts +0 -82
  27. package/src/RenderQueuePanel.ts +0 -106
  28. package/src/RotateGizmo.ts +0 -35
  29. package/src/ScaleGizmo.ts +0 -40
  30. package/src/SceneOutliner.ts +0 -78
  31. package/src/Selection.ts +0 -63
  32. package/src/ShaderGraphModel.ts +0 -254
  33. package/src/StaticExportRuntime.ts +0 -322
  34. package/src/TimelineEditorController.ts +0 -525
  35. package/src/TimelineModel.ts +0 -466
  36. package/src/TimelineRuntimeBridge.ts +0 -280
  37. package/src/TimelineTrackTypes.ts +0 -89
  38. package/src/TimelineUI.ts +0 -153
  39. package/src/TranslateGizmo.ts +0 -39
  40. package/src/VisualReviewDashboard.ts +0 -87
  41. package/src/commands/CreateNodeCommand.ts +0 -23
  42. package/src/commands/DeleteNodeCommand.ts +0 -74
  43. package/src/commands/ReparentNodeCommand.ts +0 -55
  44. package/src/commands/SetPropertyCommand.ts +0 -47
  45. package/src/commands/TransformCommand.ts +0 -107
  46. package/src/index.ts +0 -140
  47. package/tests/editor-runtime.test.ts +0 -1087
  48. package/tsconfig.json +0 -7
@@ -1,223 +0,0 @@
1
- import { CommandHistory } from "./CommandHistory";
2
- import { CurveEditor } from "./CurveEditor";
3
- import { KeyframeEditor, readTimelineKeyframes, type TimelineKeyframe } from "./KeyframeEditor";
4
- import type { TimelineClip } from "./TimelineModel";
5
-
6
- export type EditorCameraVector3 = readonly [number, number, number];
7
-
8
- export interface EditorCameraPathKeyframe {
9
- readonly id: string;
10
- readonly time: number;
11
- readonly position: EditorCameraVector3;
12
- readonly target: EditorCameraVector3;
13
- readonly fov: number;
14
- readonly focusDistance?: number;
15
- readonly shake?: number;
16
- readonly interpolation?: TimelineKeyframe["interpolation"];
17
- }
18
-
19
- export interface EditorCameraPathSample {
20
- readonly time: number;
21
- readonly position: EditorCameraVector3;
22
- readonly target: EditorCameraVector3;
23
- readonly fov: number;
24
- readonly focusDistance?: number;
25
- readonly shake: number;
26
- }
27
-
28
- export interface EditorCameraPathEvidence {
29
- readonly clipId: string;
30
- readonly cameraKeyframeCount: number;
31
- readonly editablePropertyCount: number;
32
- readonly sampledPointCount: number;
33
- readonly evidence: {
34
- readonly shotCameraMoveEditing: boolean;
35
- readonly cameraPositionCurves: boolean;
36
- readonly cameraTargetCurves: boolean;
37
- readonly fovCurve: boolean;
38
- readonly deterministicSampling: true;
39
- };
40
- }
41
-
42
- const CAMERA_PROPERTY_PATHS = [
43
- "camera.position.x",
44
- "camera.position.y",
45
- "camera.position.z",
46
- "camera.target.x",
47
- "camera.target.y",
48
- "camera.target.z",
49
- "camera.fov",
50
- "camera.focusDistance",
51
- "camera.shake"
52
- ] as const;
53
-
54
- type CameraPropertyPath = typeof CAMERA_PROPERTY_PATHS[number];
55
-
56
- export class CameraPathEditor {
57
- private readonly keyframes: KeyframeEditor;
58
- private readonly curves: CurveEditor;
59
-
60
- constructor(history = new CommandHistory()) {
61
- this.keyframes = new KeyframeEditor(history);
62
- this.curves = new CurveEditor(history);
63
- }
64
-
65
- get commandHistory(): CommandHistory {
66
- return this.keyframes.commandHistory;
67
- }
68
-
69
- async setCameraKeyframe(clip: TimelineClip, keyframe: EditorCameraPathKeyframe): Promise<EditorCameraPathKeyframe> {
70
- const normalized = normalizeCameraKeyframe(keyframe);
71
- const interpolation = normalized.interpolation ?? "linear";
72
- for (const [propertyPath, value] of cameraKeyframeEntries(normalized)) {
73
- await this.keyframes.addKeyframe(clip, {
74
- id: cameraKeyframePropertyId(normalized.id, propertyPath),
75
- propertyPath,
76
- time: normalized.time,
77
- value,
78
- interpolation
79
- });
80
- }
81
- clip.properties.cameraPathEdited = "true";
82
- clip.properties.cameraPathKeyframeIds = JSON.stringify(readCameraPathKeyframes(clip).map((frame) => frame.id));
83
- return normalized;
84
- }
85
-
86
- readCameraPathKeyframes(clip: TimelineClip): readonly EditorCameraPathKeyframe[] {
87
- return readCameraPathKeyframes(clip);
88
- }
89
-
90
- sample(clip: TimelineClip, time: number): EditorCameraPathSample {
91
- assertFinite(time, "Camera path sample time");
92
- const fallback = this.readCameraPathKeyframes(clip)[0];
93
- if (!fallback) throw new Error(`Camera path clip "${clip.id}" has no editable camera keyframes.`);
94
- return {
95
- time,
96
- position: [
97
- numericCurveValue(this.curves.sample(clip, "camera.position.x", time).value, fallback.position[0]),
98
- numericCurveValue(this.curves.sample(clip, "camera.position.y", time).value, fallback.position[1]),
99
- numericCurveValue(this.curves.sample(clip, "camera.position.z", time).value, fallback.position[2])
100
- ],
101
- target: [
102
- numericCurveValue(this.curves.sample(clip, "camera.target.x", time).value, fallback.target[0]),
103
- numericCurveValue(this.curves.sample(clip, "camera.target.y", time).value, fallback.target[1]),
104
- numericCurveValue(this.curves.sample(clip, "camera.target.z", time).value, fallback.target[2])
105
- ],
106
- fov: numericCurveValue(this.curves.sample(clip, "camera.fov", time).value, fallback.fov),
107
- focusDistance: optionalNumericCurveValue(this.curves.sample(clip, "camera.focusDistance", time).value, fallback.focusDistance),
108
- shake: numericCurveValue(this.curves.sample(clip, "camera.shake", time).value, fallback.shake ?? 0)
109
- };
110
- }
111
-
112
- evidence(clip: TimelineClip): EditorCameraPathEvidence {
113
- const keyframes = this.readCameraPathKeyframes(clip);
114
- const encodedKeyframes = readTimelineKeyframes(clip);
115
- const fovEvidence = this.curves.evidence(clip, "camera.fov");
116
- return {
117
- clipId: clip.id,
118
- cameraKeyframeCount: keyframes.length,
119
- editablePropertyCount: CAMERA_PROPERTY_PATHS.filter((path) => encodedKeyframes.some((keyframe) => keyframe.propertyPath === path)).length,
120
- sampledPointCount: fovEvidence.sampledPointCount,
121
- evidence: {
122
- shotCameraMoveEditing: clip.properties.cameraPathEdited === "true" && keyframes.length >= 2,
123
- cameraPositionCurves: ["camera.position.x", "camera.position.y", "camera.position.z"].every((path) => encodedKeyframes.some((keyframe) => keyframe.propertyPath === path)),
124
- cameraTargetCurves: ["camera.target.x", "camera.target.y", "camera.target.z"].every((path) => encodedKeyframes.some((keyframe) => keyframe.propertyPath === path)),
125
- fovCurve: fovEvidence.keyframeCount >= 2,
126
- deterministicSampling: true
127
- }
128
- };
129
- }
130
- }
131
-
132
- export function createCameraPathEditor(history?: CommandHistory): CameraPathEditor {
133
- return new CameraPathEditor(history);
134
- }
135
-
136
- export function readCameraPathKeyframes(clip: TimelineClip): readonly EditorCameraPathKeyframe[] {
137
- const groups = new Map<string, Partial<Record<CameraPropertyPath, number>> & { time?: number }>();
138
- for (const keyframe of readTimelineKeyframes(clip)) {
139
- const parsed = parseCameraKeyframePropertyId(keyframe.id);
140
- if (!parsed) continue;
141
- if (typeof keyframe.value !== "number") continue;
142
- const group = groups.get(parsed.frameId) ?? {};
143
- group[parsed.propertyPath] = keyframe.value;
144
- group.time = keyframe.time;
145
- groups.set(parsed.frameId, group);
146
- }
147
- return [...groups.entries()].flatMap(([id, values]) => {
148
- const position = tupleFrom(values["camera.position.x"], values["camera.position.y"], values["camera.position.z"]);
149
- const target = tupleFrom(values["camera.target.x"], values["camera.target.y"], values["camera.target.z"]);
150
- if (!position || !target || values["camera.fov"] === undefined || values.time === undefined) return [];
151
- return [{
152
- id,
153
- time: values.time,
154
- position,
155
- target,
156
- fov: values["camera.fov"],
157
- focusDistance: values["camera.focusDistance"],
158
- shake: values["camera.shake"]
159
- }];
160
- }).sort((a, b) => a.time - b.time || a.id.localeCompare(b.id));
161
- }
162
-
163
- function cameraKeyframeEntries(keyframe: EditorCameraPathKeyframe): readonly (readonly [CameraPropertyPath, number])[] {
164
- return [
165
- ["camera.position.x", keyframe.position[0]],
166
- ["camera.position.y", keyframe.position[1]],
167
- ["camera.position.z", keyframe.position[2]],
168
- ["camera.target.x", keyframe.target[0]],
169
- ["camera.target.y", keyframe.target[1]],
170
- ["camera.target.z", keyframe.target[2]],
171
- ["camera.fov", keyframe.fov],
172
- ["camera.focusDistance", keyframe.focusDistance ?? 0],
173
- ["camera.shake", keyframe.shake ?? 0]
174
- ];
175
- }
176
-
177
- function normalizeCameraKeyframe(keyframe: EditorCameraPathKeyframe): EditorCameraPathKeyframe {
178
- const id = keyframe.id.trim();
179
- if (!id) throw new Error("Camera path keyframe id is required.");
180
- assertFinite(keyframe.time, "Camera path keyframe time");
181
- if (keyframe.time < 0) throw new RangeError("Camera path keyframe time must be non-negative.");
182
- assertVector3(keyframe.position, "Camera path keyframe position");
183
- assertVector3(keyframe.target, "Camera path keyframe target");
184
- assertFinite(keyframe.fov, "Camera path keyframe fov");
185
- if (keyframe.fov <= 0) throw new RangeError("Camera path keyframe fov must be positive.");
186
- if (keyframe.focusDistance !== undefined) assertFinite(keyframe.focusDistance, "Camera path keyframe focusDistance");
187
- if (keyframe.shake !== undefined) assertFinite(keyframe.shake, "Camera path keyframe shake");
188
- return { ...keyframe, id };
189
- }
190
-
191
- function cameraKeyframePropertyId(frameId: string, propertyPath: string): string {
192
- return `${frameId}#${propertyPath}`;
193
- }
194
-
195
- function parseCameraKeyframePropertyId(id: string): { readonly frameId: string; readonly propertyPath: CameraPropertyPath } | undefined {
196
- const separator = id.lastIndexOf("#");
197
- if (separator <= 0) return undefined;
198
- const propertyPath = id.slice(separator + 1);
199
- if (!CAMERA_PROPERTY_PATHS.includes(propertyPath as CameraPropertyPath)) return undefined;
200
- return { frameId: id.slice(0, separator), propertyPath: propertyPath as CameraPropertyPath };
201
- }
202
-
203
- function tupleFrom(x: number | undefined, y: number | undefined, z: number | undefined): EditorCameraVector3 | undefined {
204
- return x === undefined || y === undefined || z === undefined ? undefined : [x, y, z];
205
- }
206
-
207
- function numericCurveValue(value: unknown, fallback: number): number {
208
- return typeof value === "number" && Number.isFinite(value) ? value : fallback;
209
- }
210
-
211
- function optionalNumericCurveValue(value: unknown, fallback: number | undefined): number | undefined {
212
- if (typeof value === "number" && Number.isFinite(value)) return value;
213
- return fallback;
214
- }
215
-
216
- function assertVector3(value: EditorCameraVector3, label: string): void {
217
- if (value.length !== 3) throw new Error(`${label} must contain three numeric components.`);
218
- for (const component of value) assertFinite(component, label);
219
- }
220
-
221
- function assertFinite(value: number, label: string): void {
222
- if (!Number.isFinite(value)) throw new RangeError(`${label} must be finite.`);
223
- }
package/src/Command.ts DELETED
@@ -1,11 +0,0 @@
1
- export interface CommandContext {
2
- readonly services?: ReadonlyMap<string, unknown>;
3
- }
4
-
5
- export interface Command {
6
- readonly name: string;
7
- execute(context?: CommandContext): void | Promise<void>;
8
- undo(context?: CommandContext): void | Promise<void>;
9
- canMerge?(next: Command): boolean;
10
- merge?(next: Command): Command;
11
- }
@@ -1,136 +0,0 @@
1
- import type { Command, CommandContext } from "./Command";
2
-
3
- export class CommandTransactionError extends Error {
4
- readonly cause: unknown;
5
- readonly rollbackErrors: readonly unknown[];
6
-
7
- constructor(cause: unknown, rollbackErrors: readonly unknown[]) {
8
- super("Command transaction failed and rollback reported errors.");
9
- this.name = "CommandTransactionError";
10
- this.cause = cause;
11
- this.rollbackErrors = rollbackErrors;
12
- }
13
- }
14
-
15
- class CompositeCommand implements Command {
16
- readonly name: string;
17
-
18
- constructor(private readonly commands: readonly Command[]) {
19
- this.name = commands.map((command) => command.name).join(", ");
20
- }
21
-
22
- async execute(context?: CommandContext): Promise<void> {
23
- await executeCommandsAtomically(this.commands, context);
24
- }
25
-
26
- async undo(context?: CommandContext): Promise<void> {
27
- for (const command of [...this.commands].reverse()) {
28
- await command.undo(context);
29
- }
30
- }
31
- }
32
-
33
- export class CommandHistory {
34
- private readonly undoStack: Command[] = [];
35
- private readonly redoStack: Command[] = [];
36
-
37
- get canUndo(): boolean {
38
- return this.undoStack.length > 0;
39
- }
40
-
41
- get canRedo(): boolean {
42
- return this.redoStack.length > 0;
43
- }
44
-
45
- get undoDepth(): number {
46
- return this.undoStack.length;
47
- }
48
-
49
- get redoDepth(): number {
50
- return this.redoStack.length;
51
- }
52
-
53
- async execute(command: Command, context?: CommandContext): Promise<void> {
54
- try {
55
- await command.execute(context);
56
- } catch (error) {
57
- await Promise.resolve(command.undo(context)).catch(() => undefined);
58
- throw error;
59
- }
60
-
61
- const previous = this.undoStack[this.undoStack.length - 1];
62
- if (previous?.canMerge?.(command) && previous.merge) {
63
- this.undoStack[this.undoStack.length - 1] = previous.merge(command);
64
- } else {
65
- this.undoStack.push(command);
66
- }
67
- this.redoStack.length = 0;
68
- }
69
-
70
- async executeTransaction(commands: readonly Command[], context?: CommandContext): Promise<void> {
71
- if (commands.length === 0) {
72
- return;
73
- }
74
-
75
- await executeCommandsAtomically(commands, context);
76
-
77
- this.undoStack.push(new CompositeCommand([...commands]));
78
- this.redoStack.length = 0;
79
- }
80
-
81
- async undo(context?: CommandContext): Promise<void> {
82
- const command = this.undoStack.pop();
83
- if (!command) {
84
- return;
85
- }
86
- try {
87
- await command.undo(context);
88
- } catch (error) {
89
- this.undoStack.push(command);
90
- throw error;
91
- }
92
- this.redoStack.push(command);
93
- }
94
-
95
- async redo(context?: CommandContext): Promise<void> {
96
- const command = this.redoStack.pop();
97
- if (!command) {
98
- return;
99
- }
100
- try {
101
- await command.execute(context);
102
- } catch (error) {
103
- this.redoStack.push(command);
104
- throw error;
105
- }
106
- this.undoStack.push(command);
107
- }
108
-
109
- clear(): void {
110
- this.undoStack.length = 0;
111
- this.redoStack.length = 0;
112
- }
113
- }
114
-
115
- async function executeCommandsAtomically(commands: readonly Command[], context?: CommandContext): Promise<void> {
116
- const executed: Command[] = [];
117
- try {
118
- for (const command of commands) {
119
- await command.execute(context);
120
- executed.push(command);
121
- }
122
- } catch (error) {
123
- const rollbackErrors: unknown[] = [];
124
- for (const command of executed.reverse()) {
125
- try {
126
- await command.undo(context);
127
- } catch (rollbackError) {
128
- rollbackErrors.push(rollbackError);
129
- }
130
- }
131
- if (rollbackErrors.length > 0) {
132
- throw new CommandTransactionError(error, rollbackErrors);
133
- }
134
- throw error;
135
- }
136
- }
@@ -1,152 +0,0 @@
1
- import { CommandHistory } from "./CommandHistory";
2
- import { KeyframeEditor, readTimelineKeyframes, type TimelineBezierHandle, type TimelineKeyframe, type TimelineKeyframeValue } from "./KeyframeEditor";
3
- import type { TimelineClip } from "./TimelineModel";
4
-
5
- export interface TimelineCurvePoint {
6
- readonly time: number;
7
- readonly value: number;
8
- }
9
-
10
- export interface TimelineCurveSample {
11
- readonly propertyPath: string;
12
- readonly time: number;
13
- readonly value: TimelineKeyframeValue | undefined;
14
- readonly segmentStartId?: string;
15
- readonly segmentEndId?: string;
16
- }
17
-
18
- export interface TimelineCurveEvidence {
19
- readonly propertyPath: string;
20
- readonly keyframeCount: number;
21
- readonly bezierSegmentCount: number;
22
- readonly sampledPointCount: number;
23
- readonly evidence: {
24
- readonly deterministicSampling: true;
25
- readonly bezierHandles: boolean;
26
- readonly holdInterpolation: boolean;
27
- };
28
- }
29
-
30
- export class CurveEditor {
31
- private readonly keyframes: KeyframeEditor;
32
-
33
- constructor(history = new CommandHistory()) {
34
- this.keyframes = new KeyframeEditor(history);
35
- }
36
-
37
- get commandHistory(): CommandHistory {
38
- return this.keyframes.commandHistory;
39
- }
40
-
41
- sample(clip: TimelineClip, propertyPath: string, time: number): TimelineCurveSample {
42
- assertFinite(time, "Curve sample time");
43
- const keyframes = propertyKeyframes(clip, propertyPath);
44
- if (keyframes.length === 0) {
45
- return { propertyPath, time, value: undefined };
46
- }
47
- if (time <= keyframes[0].time) {
48
- return { propertyPath, time, value: keyframes[0].value, segmentStartId: keyframes[0].id };
49
- }
50
- const last = keyframes[keyframes.length - 1];
51
- if (time >= last.time) {
52
- return { propertyPath, time, value: last.value, segmentStartId: last.id };
53
- }
54
- const endIndex = keyframes.findIndex((keyframe) => keyframe.time >= time);
55
- const start = keyframes[endIndex - 1];
56
- const end = keyframes[endIndex];
57
- if (!start || !end) {
58
- return { propertyPath, time, value: last.value, segmentStartId: last.id };
59
- }
60
- return {
61
- propertyPath,
62
- time,
63
- value: sampleSegment(start, end, time),
64
- segmentStartId: start.id,
65
- segmentEndId: end.id
66
- };
67
- }
68
-
69
- samplePoints(clip: TimelineClip, propertyPath: string, startTime: number, endTime: number, steps: number): readonly TimelineCurvePoint[] {
70
- assertFinite(startTime, "Curve startTime");
71
- assertFinite(endTime, "Curve endTime");
72
- if (endTime < startTime) throw new RangeError("Curve endTime must be greater than or equal to startTime.");
73
- if (!Number.isInteger(steps) || steps < 1) throw new RangeError("Curve sample steps must be a positive integer.");
74
- const points: TimelineCurvePoint[] = [];
75
- for (let index = 0; index <= steps; index += 1) {
76
- const time = startTime + (endTime - startTime) * (index / steps);
77
- const value = this.sample(clip, propertyPath, time).value;
78
- if (typeof value === "number") {
79
- points.push({ time: Number(time.toFixed(4)), value: Number(value.toFixed(4)) });
80
- }
81
- }
82
- return points;
83
- }
84
-
85
- async setBezierHandles(
86
- clip: TimelineClip,
87
- keyframeId: string,
88
- handles: { readonly inHandle?: TimelineBezierHandle; readonly outHandle?: TimelineBezierHandle }
89
- ): Promise<TimelineKeyframe> {
90
- const keyframes = readTimelineKeyframes(clip);
91
- const target = keyframes.find((keyframe) => keyframe.id === keyframeId);
92
- if (!target) throw new Error(`Timeline keyframe does not exist: ${keyframeId}`);
93
- const updated: TimelineKeyframe = {
94
- ...target,
95
- interpolation: "bezier",
96
- inHandle: handles.inHandle,
97
- outHandle: handles.outHandle
98
- };
99
- await this.keyframes.replaceKeyframes(
100
- clip,
101
- keyframes.map((keyframe) => keyframe.id === keyframeId ? updated : keyframe),
102
- `Set bezier handles ${keyframeId}`
103
- );
104
- return updated;
105
- }
106
-
107
- evidence(clip: TimelineClip, propertyPath: string, sampleCount = 8): TimelineCurveEvidence {
108
- const keyframes = propertyKeyframes(clip, propertyPath);
109
- const startTime = keyframes[0]?.time ?? 0;
110
- const endTime = keyframes[keyframes.length - 1]?.time ?? startTime;
111
- return {
112
- propertyPath,
113
- keyframeCount: keyframes.length,
114
- bezierSegmentCount: keyframes.filter((keyframe) => keyframe.interpolation === "bezier").length,
115
- sampledPointCount: this.samplePoints(clip, propertyPath, startTime, endTime, Math.max(1, sampleCount)).length,
116
- evidence: {
117
- deterministicSampling: true,
118
- bezierHandles: keyframes.some((keyframe) => keyframe.inHandle || keyframe.outHandle),
119
- holdInterpolation: keyframes.some((keyframe) => keyframe.interpolation === "hold")
120
- }
121
- };
122
- }
123
- }
124
-
125
- function propertyKeyframes(clip: TimelineClip, propertyPath: string): readonly TimelineKeyframe[] {
126
- return readTimelineKeyframes(clip).filter((keyframe) => keyframe.propertyPath === propertyPath);
127
- }
128
-
129
- function sampleSegment(start: TimelineKeyframe, end: TimelineKeyframe, time: number): TimelineKeyframeValue {
130
- if (start.interpolation === "hold" || typeof start.value !== "number" || typeof end.value !== "number") {
131
- return start.value;
132
- }
133
- const duration = end.time - start.time;
134
- const t = duration <= 0 ? 1 : clamp((time - start.time) / duration, 0, 1);
135
- if (start.interpolation !== "bezier") {
136
- return Number((start.value + (end.value - start.value) * t).toFixed(4));
137
- }
138
- const p0 = start.value;
139
- const p1 = start.value + (start.outHandle?.value ?? 0);
140
- const p2 = end.value + (end.inHandle?.value ?? 0);
141
- const p3 = end.value;
142
- const inv = 1 - t;
143
- return Number((inv * inv * inv * p0 + 3 * inv * inv * t * p1 + 3 * inv * t * t * p2 + t * t * t * p3).toFixed(4));
144
- }
145
-
146
- function assertFinite(value: number, label: string): void {
147
- if (!Number.isFinite(value)) throw new RangeError(`${label} must be finite.`);
148
- }
149
-
150
- function clamp(value: number, min: number, max: number): number {
151
- return Math.max(min, Math.min(max, value));
152
- }
@@ -1,85 +0,0 @@
1
- export interface EditorDiagnosticsResource {
2
- readonly id: string;
3
- readonly label: string;
4
- readonly kind: "scene-node" | "asset" | "material" | "shader" | "script";
5
- readonly status: "ok" | "warning" | "error";
6
- readonly detail?: string;
7
- }
8
-
9
- export interface EditorDiagnosticsInput {
10
- readonly frameTimeMs: number;
11
- readonly drawCalls: number;
12
- readonly triangleCount?: number;
13
- readonly nodeCount: number;
14
- readonly assetCount: number;
15
- readonly physicsBodies: number;
16
- readonly resources?: readonly EditorDiagnosticsResource[];
17
- }
18
-
19
- export interface EditorDiagnosticsSnapshot extends EditorDiagnosticsInput {
20
- readonly warnings: number;
21
- readonly errors: number;
22
- readonly shaderWarnings: readonly EditorDiagnosticsResource[];
23
- readonly resourceWarnings: readonly EditorDiagnosticsResource[];
24
- }
25
-
26
- export class DiagnosticsOverlayModel {
27
- private snapshotRef: EditorDiagnosticsSnapshot = this.createSnapshot({
28
- frameTimeMs: 0,
29
- drawCalls: 0,
30
- nodeCount: 0,
31
- assetCount: 0,
32
- physicsBodies: 0,
33
- resources: []
34
- });
35
-
36
- update(input: EditorDiagnosticsInput): EditorDiagnosticsSnapshot {
37
- if (!Number.isFinite(input.frameTimeMs) || input.frameTimeMs < 0) {
38
- throw new Error("Editor diagnostics frame time must be a non-negative finite number.");
39
- }
40
- if (!Number.isInteger(input.drawCalls) || input.drawCalls < 0) {
41
- throw new Error("Editor diagnostics draw calls must be a non-negative integer.");
42
- }
43
- if (!Number.isInteger(input.nodeCount) || input.nodeCount < 0) {
44
- throw new Error("Editor diagnostics node count must be a non-negative integer.");
45
- }
46
- if (!Number.isInteger(input.assetCount) || input.assetCount < 0) {
47
- throw new Error("Editor diagnostics asset count must be a non-negative integer.");
48
- }
49
- if (!Number.isInteger(input.physicsBodies) || input.physicsBodies < 0) {
50
- throw new Error("Editor diagnostics physics body count must be a non-negative integer.");
51
- }
52
- this.snapshotRef = this.createSnapshot(input);
53
- return this.snapshotRef;
54
- }
55
-
56
- snapshot(): EditorDiagnosticsSnapshot {
57
- return this.snapshotRef;
58
- }
59
-
60
- clear(): void {
61
- this.snapshotRef = this.createSnapshot({
62
- frameTimeMs: 0,
63
- drawCalls: 0,
64
- nodeCount: 0,
65
- assetCount: 0,
66
- physicsBodies: 0,
67
- resources: []
68
- });
69
- }
70
-
71
- private createSnapshot(input: EditorDiagnosticsInput): EditorDiagnosticsSnapshot {
72
- const resources = [...(input.resources ?? [])];
73
- const warnings = resources.filter((resource) => resource.status === "warning").length;
74
- const errors = resources.filter((resource) => resource.status === "error").length;
75
- return {
76
- ...input,
77
- triangleCount: input.triangleCount ?? 0,
78
- resources,
79
- warnings,
80
- errors,
81
- shaderWarnings: resources.filter((resource) => resource.kind === "shader" && resource.status !== "ok"),
82
- resourceWarnings: resources.filter((resource) => resource.kind !== "shader" && resource.status !== "ok")
83
- };
84
- }
85
- }