Add animation curve editor with Bezier F-curve round-trip
Maya-style Graph Editor panel (View > Curve Editor) that fits Catmull-Rom Bezier curves through USD time samples, allows interactive editing with tangent handles, and bakes the result back to dense linear samples on Apply. Key behaviours: - Left panel lists all animated attributes of the selected prim, decomposed into per-component channels (translate [X/Y/Z], etc.) with colour swatches and visibility toggles - ImDrawList canvas: smooth Bezier polylines, always-visible tangent handle lines/circles, diamond keyframe markers - Pan (MMB/Alt+drag), zoom (scroll / Shift+scroll), Frame All (F) - LMB drag moves keyframes; tangent handle drag reshapes curve with mirrored or broken handles; box-select for multi-selection - Double-click canvas adds a keyframe; Delete removes selected keyframes - RMB context menu: Delete, Flatten, Break/Unify Tangents, Auto Tangents - Simplify toggle (Ramer-Douglas-Peucker) reduces baked sample count - Time cursor draggable to scrub the timeline - UsdNotice::ObjectsChanged listener re-fits clean channels whenever the stage changes externally (Property Panel edits, Auto-Key, undo/redo), while preserving channels with unsaved Bezier edits - Bake is a single undoable AttributeSetCommand (Ctrl+Z restores original sparse samples); Revert discards in-editor edits without touching USD Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
#pragma once
|
||||
|
||||
#include "../core/CommandHistory.h"
|
||||
#include <pxr/usd/usd/stage.h>
|
||||
#include <pxr/usd/usd/attribute.h>
|
||||
#include <pxr/usd/usd/timeCode.h>
|
||||
#include <pxr/usd/usd/notice.h>
|
||||
#include <pxr/base/tf/weakBase.h>
|
||||
#include <pxr/base/tf/notice.h>
|
||||
#include <imgui.h>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace UsdLayerManager {
|
||||
|
||||
class CurveEditorPanel : public pxr::TfWeakBase {
|
||||
public:
|
||||
void SetStage(pxr::UsdStageRefPtr stage);
|
||||
void SetCommandHistory(CommandHistory* history) { m_commandHistory = history; }
|
||||
void SetSelectedPrimPath(const std::string& path);
|
||||
void SetTimeCodes(pxr::UsdTimeCode displayTime, pxr::UsdTimeCode editTime);
|
||||
void Render();
|
||||
|
||||
// Fires when the user drags the time cursor; drives the Timeline panel.
|
||||
std::function<void(double frame)> OnScrub;
|
||||
|
||||
private:
|
||||
// ── Internal data ────────────────────────────────────────────────────────
|
||||
|
||||
struct BezierKey {
|
||||
double time = 0.0;
|
||||
double value = 0.0;
|
||||
// Tangent handles as (dt, dv) offsets from the key.
|
||||
// in-handle is left of the key (dt < 0), out-handle is right (dt > 0).
|
||||
double inTangentDt = -1.0, inTangentDv = 0.0;
|
||||
double outTangentDt = 1.0, outTangentDv = 0.0;
|
||||
bool tangentsBroken = false;
|
||||
};
|
||||
|
||||
struct CurveChannel {
|
||||
std::string displayName;
|
||||
pxr::UsdAttribute attr;
|
||||
int component = -1; // -1=scalar, 0/1/2/3 = X/Y/Z/W
|
||||
ImVec4 color = {1,1,1,1};
|
||||
bool visible = true;
|
||||
bool dirty = false;
|
||||
std::vector<double> origTimes;
|
||||
std::vector<double> origValues;
|
||||
std::vector<BezierKey> keys;
|
||||
};
|
||||
|
||||
enum class HandlePart { Key, InTangent, OutTangent };
|
||||
struct SelectedHandle { int chanIdx; int keyIdx; HandlePart part; };
|
||||
|
||||
// ── Methods ──────────────────────────────────────────────────────────────
|
||||
|
||||
void RefreshFromStage();
|
||||
void FitCurveFromSamples(CurveChannel& ch);
|
||||
void BakeChannelToUsd(CurveChannel& ch);
|
||||
|
||||
// Bezier math
|
||||
double EvaluateBezier(const std::vector<BezierKey>& keys, double T) const;
|
||||
void DrawBezierSegment(ImDrawList* dl, const BezierKey& k0, const BezierKey& k1,
|
||||
ImVec4 color, ImVec2 canvasMin, ImVec2 canvasMax) const;
|
||||
|
||||
// Coordinate helpers
|
||||
float TimeToX(double t, ImVec2 cMin, ImVec2 cMax) const;
|
||||
float ValToY (double v, ImVec2 cMin, ImVec2 cMax) const;
|
||||
double XToTime(float x, ImVec2 cMin, ImVec2 cMax) const;
|
||||
double YToVal (float y, ImVec2 cMin, ImVec2 cMax) const;
|
||||
ImVec2 KeyToScreen(const BezierKey& k, ImVec2 cMin, ImVec2 cMax) const;
|
||||
|
||||
// USD channel read/write helpers
|
||||
double ReadChannelValue (const pxr::UsdAttribute& attr, int comp, double time) const;
|
||||
void WriteChannelValue(const pxr::UsdAttribute& attr, int comp,
|
||||
double time, double val) const;
|
||||
void ClearAllTimeSamples(const pxr::UsdAttribute& attr) const;
|
||||
|
||||
// Rendering sub-sections
|
||||
void RenderToolbar();
|
||||
void RenderAttrList(float listWidth);
|
||||
void RenderCanvas(ImVec2 canvasMin, ImVec2 canvasMax);
|
||||
void RenderGrid(ImDrawList* dl, ImVec2 cMin, ImVec2 cMax);
|
||||
void RenderTimeCursor(ImDrawList* dl, ImVec2 cMin, ImVec2 cMax);
|
||||
void RenderCurve(ImDrawList* dl, CurveChannel& ch, ImVec2 cMin, ImVec2 cMax);
|
||||
void HandleCanvasInput(ImVec2 cMin, ImVec2 cMax);
|
||||
|
||||
// Selection helpers
|
||||
bool IsSelected(int chanIdx, int keyIdx, HandlePart part) const;
|
||||
void Select(int chanIdx, int keyIdx, HandlePart part, bool additive);
|
||||
void ClearSelection();
|
||||
|
||||
// Interaction helpers
|
||||
void FrameAll();
|
||||
void ApplyAllDirty();
|
||||
void RevertAll();
|
||||
void DeleteSelectedKeys();
|
||||
void AddKeyAt(int chanIdx, double time, double value);
|
||||
// Re-fit Catmull-Rom tangents for neighbors of a modified key
|
||||
void RefitNeighborTangents(CurveChannel& ch, int keyIdx);
|
||||
|
||||
// ── State ────────────────────────────────────────────────────────────────
|
||||
|
||||
pxr::UsdStageRefPtr m_stage;
|
||||
CommandHistory* m_commandHistory = nullptr;
|
||||
std::string m_selectedPrimPath;
|
||||
pxr::UsdTimeCode m_displayTime = pxr::UsdTimeCode::Default();
|
||||
pxr::UsdTimeCode m_editTime = pxr::UsdTimeCode::Default();
|
||||
|
||||
std::vector<CurveChannel> m_channels;
|
||||
std::vector<SelectedHandle> m_selection;
|
||||
int m_activeChannel = -1; // for double-click-to-add
|
||||
|
||||
bool m_needsRefresh = true;
|
||||
|
||||
// USD change notification
|
||||
pxr::TfNotice::Key m_stageChangeKey;
|
||||
void OnObjectsChanged(const pxr::UsdNotice::ObjectsChanged& notice,
|
||||
const pxr::UsdStageWeakPtr& sender);
|
||||
|
||||
// View range
|
||||
double m_viewMinTime = 0.0, m_viewMaxTime = 100.0;
|
||||
double m_viewMinVal = -1.0, m_viewMaxVal = 1.0;
|
||||
|
||||
// Pan/zoom drag state
|
||||
bool m_isPanning = false;
|
||||
ImVec2 m_panLastMouse = {0,0};
|
||||
double m_panMinTimeStart = 0.0, m_panMaxTimeStart = 0.0;
|
||||
double m_panMinValStart = 0.0, m_panMaxValStart = 0.0;
|
||||
|
||||
// Drag-move state
|
||||
bool m_isDragging = false;
|
||||
ImVec2 m_dragStartMouse= {0,0};
|
||||
double m_dragStartTime = 0.0, m_dragStartValue = 0.0;
|
||||
struct KeySnapshot { int chanIdx; int keyIdx; double time; double value;
|
||||
double inDt, inDv, outDt, outDv; };
|
||||
std::vector<KeySnapshot> m_dragSnapshot;
|
||||
|
||||
// Selection box drag
|
||||
bool m_isBoxSelecting = false;
|
||||
ImVec2 m_boxSelectStart = {0,0};
|
||||
|
||||
// Scrub cursor drag
|
||||
bool m_isDraggingCursor = false;
|
||||
|
||||
// Options
|
||||
bool m_simplifyOnBake = false;
|
||||
float m_simplifyTol = 0.01f;
|
||||
bool m_snapToFrame = true;
|
||||
};
|
||||
|
||||
} // namespace UsdLayerManager
|
||||
Reference in New Issue
Block a user