Refract Manipulator
This commit is contained in:
parent
4cb6ec7341
commit
ae4a5ab96d
41
README.md
41
README.md
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
A C++17 / Windows desktop application providing a **Maya Render Layers-style** USD scene editor using **OpenUSD v25.05**, **ImGui 1.92.7** (docking branch), and **OpenGL 3.3** (via GLAD). Users can open, edit, and save USD files, manage layered stage overrides, inspect and edit prim properties, and interact with 3D scenes through a Hydra viewport.
|
A C++17 / Windows desktop application providing a **Maya Render Layers-style** USD scene editor using **OpenUSD v25.05**, **ImGui 1.92.7** (docking branch), and **OpenGL 3.3** (via GLAD). Users can open, edit, and save USD files, manage layered stage overrides, inspect and edit prim properties, and interact with 3D scenes through a Hydra viewport.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
|
|
@ -96,4 +96,41 @@ src/
|
||||||
| `cmake/modules/FindImgui.cmake` | ImGui source integration |
|
| `cmake/modules/FindImgui.cmake` | ImGui source integration |
|
||||||
| `cmake/modules/FindGlad.cmake` | GLAD loader setup |
|
| `cmake/modules/FindGlad.cmake` | GLAD loader setup |
|
||||||
| `CMakePresets.json` | Build presets (VS 17 2022, x64) |
|
| `CMakePresets.json` | Build presets (VS 17 2022, x64) |
|
||||||
| `AGENTS.md` | AI agent build/architecture guide |
|
| `AGENTS.md` | AI agent build/architecture guide |
|
||||||
|
| `.kilo/` | Kilo AI configuration (commands, agents, skills) |
|
||||||
|
| `openspec/changes/` | Feature proposals and implementation tracking |
|
||||||
|
|
||||||
|
## Development Workflow
|
||||||
|
|
||||||
|
This project uses **OpenSpec** for feature development:
|
||||||
|
|
||||||
|
1. **Propose** — Create a new change under `openspec/changes/<name>/` with `proposal.md`, `design.md`, `specs/`, and `tasks.md`
|
||||||
|
2. **Implement** — Work through checkbox-tracked tasks in `tasks.md`
|
||||||
|
3. **Archive** — Move completed changes to `openspec/archive/` when done
|
||||||
|
|
||||||
|
Use Kilo's `openspec-*` skills to streamline this workflow.
|
||||||
|
|
||||||
|
## Runtime Requirements
|
||||||
|
|
||||||
|
- **Python 3.12** — OpenUSD requires `python312.dll` at runtime. CMake copies it to the output directory via `POST_BUILD` commands.
|
||||||
|
- **USD Plugin Path** — The app scans `<exe_dir>/usd/` for `plugInfo.json` and registers plugins via `pxr::PlugRegistry`.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Before writing any OpenUSD API call, verify the API exists in the actual SDK:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
findstr /r /s "FunctionName" third_party\OpenUSD_v25.05\include\
|
||||||
|
```
|
||||||
|
|
||||||
|
Always build and install before running tests:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
cmake --build build --config Release
|
||||||
|
cmake --install build --config Release
|
||||||
|
ctest --test-dir build -C Release
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
See [LICENSE](LICENSE) file for details.
|
||||||
|
|
@ -7,205 +7,126 @@
|
||||||
#include <pxr/base/gf/vec3d.h>
|
#include <pxr/base/gf/vec3d.h>
|
||||||
#include <pxr/base/gf/vec3f.h>
|
#include <pxr/base/gf/vec3f.h>
|
||||||
#include <pxr/base/gf/matrix4d.h>
|
#include <pxr/base/gf/matrix4d.h>
|
||||||
|
#include <pxr/base/gf/line.h>
|
||||||
|
#include <pxr/base/gf/plane.h>
|
||||||
|
#include <pxr/base/gf/ray.h>
|
||||||
#include <pxr/usd/usdGeom/xformCommonAPI.h>
|
#include <pxr/usd/usdGeom/xformCommonAPI.h>
|
||||||
|
|
||||||
namespace UsdLayerManager {
|
namespace UsdLayerManager {
|
||||||
|
|
||||||
class CommandHistory;
|
class CommandHistory;
|
||||||
|
|
||||||
/// Active transform tool mode — mirrors Maya Q/W/E/R convention.
|
enum class ManipulatorMode { Select, Move, Rotate, Scale };
|
||||||
enum class ManipulatorMode {
|
enum class TransformSpace { Object, World };
|
||||||
Select, ///< Q — no gizmo, normal click-to-select
|
|
||||||
Move, ///< W — translate along axis arrows
|
|
||||||
Rotate, ///< E — rotate around axis rings
|
|
||||||
Scale ///< R — scale along axis handles
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Coordinate space in which the gizmo axes are expressed.
|
|
||||||
enum class TransformSpace {
|
|
||||||
Object, ///< Gizmo axes align with the selected prim's local axes (default)
|
|
||||||
World, ///< Gizmo axes are fixed world-space X/Y/Z
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Maya-style interactive transform gizmo.
|
|
||||||
///
|
|
||||||
/// Rendering is done with ImGui DrawList (2-D screen-space overlay), drawn
|
|
||||||
/// AFTER ImGui::Image() for the viewport — exactly the same approach used by
|
|
||||||
/// ImGuizmo. No OpenGL resources are needed; the FBO bind/unbind dance is
|
|
||||||
/// entirely eliminated.
|
|
||||||
///
|
|
||||||
/// Gizmo world-space size is computed each frame using ImGuizmo's screen-
|
|
||||||
/// factor formula: project a camera-aligned unit vector to clip space and
|
|
||||||
/// derive the world size that spans a fixed fraction of the screen. This
|
|
||||||
/// gives constant apparent size regardless of camera distance or FOV.
|
|
||||||
class TransformManipulator {
|
class TransformManipulator {
|
||||||
public:
|
public:
|
||||||
TransformManipulator() = default;
|
TransformManipulator() = default;
|
||||||
~TransformManipulator() = default;
|
~TransformManipulator() = default;
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
void SetMode(ManipulatorMode m) { m_mode = m; }
|
||||||
// Mode
|
ManipulatorMode GetMode() const { return m_mode; }
|
||||||
// -----------------------------------------------------------------------
|
void SetTransformSpace(TransformSpace s) { m_transformSpace = s; }
|
||||||
void SetMode(ManipulatorMode mode) { m_mode = mode; }
|
TransformSpace GetTransformSpace() const { return m_transformSpace; }
|
||||||
ManipulatorMode GetMode() const { return m_mode; }
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
// Transform space
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
void SetTransformSpace(TransformSpace space) { m_transformSpace = space; }
|
|
||||||
TransformSpace GetTransformSpace() const { return m_transformSpace; }
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
// Stage / selection
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
void SetStage(pxr::UsdStageRefPtr stage);
|
void SetStage(pxr::UsdStageRefPtr stage);
|
||||||
void SetSelectedPrim(const pxr::SdfPath& path);
|
void SetSelectedPrim(const pxr::SdfPath& path);
|
||||||
void SetCommandHistory(CommandHistory* history) { m_commandHistory = history; }
|
void SetCommandHistory(CommandHistory* h) { m_commandHistory = h; }
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
void Render(ImDrawList* dl, const pxr::GfMatrix4d& viewProj,
|
||||||
// Per-frame API (called from ViewportPanel::Render)
|
const pxr::GfVec3d& pivot, const pxr::GfVec3d& cameraEye,
|
||||||
// -----------------------------------------------------------------------
|
const ImVec2& imagePos, int viewW, int viewH);
|
||||||
|
|
||||||
/// Draw the gizmo as a 2-D overlay onto @p dl.
|
|
||||||
/// Call AFTER ImGui::Image() so the overlay appears on top of the scene.
|
|
||||||
/// @param dl ImGui::GetWindowDrawList() of the Viewport window.
|
|
||||||
/// @param viewProj Combined view × projection matrix (USD row-major).
|
|
||||||
/// @param pivot World-space pivot (bounding-box centre of selection).
|
|
||||||
/// @param cameraEye World-space camera eye position (for half-arc orientation).
|
|
||||||
/// @param imagePos Screen-space top-left corner of the rendered image.
|
|
||||||
/// @param viewW/H Viewport pixel dimensions.
|
|
||||||
void Render(ImDrawList* dl,
|
|
||||||
const pxr::GfMatrix4d& viewProj,
|
|
||||||
const pxr::GfVec3d& pivot,
|
|
||||||
const pxr::GfVec3d& cameraEye,
|
|
||||||
const ImVec2& imagePos,
|
|
||||||
int viewW, int viewH);
|
|
||||||
|
|
||||||
/// Process mouse input. Must be called BEFORE camera-drag / prim-pick
|
|
||||||
/// logic in ViewportPanel so the gizmo can consume LMB clicks first.
|
|
||||||
/// @return true if the gizmo consumed the event.
|
|
||||||
bool HandleInput(const pxr::GfMatrix4d& viewProj,
|
bool HandleInput(const pxr::GfMatrix4d& viewProj,
|
||||||
const pxr::GfVec3d& pivot,
|
const pxr::GfVec3d& pivot, const pxr::GfVec3d& cameraEye,
|
||||||
const pxr::GfVec3d& cameraEye,
|
const ImVec2& imagePos, int viewW, int viewH,
|
||||||
const ImVec2& imagePos,
|
|
||||||
int viewW, int viewH,
|
|
||||||
bool viewportHovered);
|
bool viewportHovered);
|
||||||
|
|
||||||
bool IsDragging() const { return m_isDragging; }
|
bool IsDragging() const { return m_isDragging; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// -----------------------------------------------------------------------
|
static float ComputeScreenFactor(const pxr::GfMatrix4d& vp,
|
||||||
// ImGuizmo-style screen-factor computation
|
const pxr::GfVec3d& pivot,
|
||||||
// -----------------------------------------------------------------------
|
int vW, int vH, float frac = 0.15f);
|
||||||
|
static bool WorldToScreen(const pxr::GfVec3d& world,
|
||||||
/// Compute the world-space gizmo size so that the gizmo spans
|
const pxr::GfMatrix4d& vp,
|
||||||
/// @p desiredFraction of the smaller viewport dimension in NDC.
|
int vW, int vH,
|
||||||
///
|
const ImVec2& imgPos, ImVec2& out);
|
||||||
/// Algorithm (from ImGuizmo):
|
|
||||||
/// 1. Project @p pivot to clip space.
|
|
||||||
/// 2. Project @p pivot + each world axis unit vector to clip space.
|
|
||||||
/// 3. Measure clip-space length (aspect-ratio corrected).
|
|
||||||
/// 4. screenFactor = desiredFraction / maxClipLen.
|
|
||||||
static float ComputeScreenFactor(const pxr::GfMatrix4d& viewProj,
|
|
||||||
const pxr::GfVec3d& pivot,
|
|
||||||
int viewW, int viewH,
|
|
||||||
float desiredFraction = 0.15f);
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
// Screen-space helpers
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
|
|
||||||
/// Project a world-space point to absolute screen coordinates.
|
|
||||||
/// Returns false if the point is behind the camera (w ≤ 0).
|
|
||||||
static bool WorldToScreen(const pxr::GfVec3d& world,
|
|
||||||
const pxr::GfMatrix4d& viewProj,
|
|
||||||
int viewW, int viewH,
|
|
||||||
const ImVec2& imagePos,
|
|
||||||
ImVec2& outScreen);
|
|
||||||
|
|
||||||
/// Distance from point @p p to line segment @p a – @p b (2-D).
|
|
||||||
static float PointToSegmentDist(ImVec2 p, ImVec2 a, ImVec2 b);
|
static float PointToSegmentDist(ImVec2 p, ImVec2 a, ImVec2 b);
|
||||||
|
static pxr::GfRay ComputeMouseRay(const pxr::GfMatrix4d& vp,
|
||||||
|
const ImVec2& mouse,
|
||||||
|
const ImVec2& imgPos, int vW, int vH);
|
||||||
|
|
||||||
|
void DrawMoveGizmo (ImDrawList*, const pxr::GfMatrix4d&, const pxr::GfVec3d&,
|
||||||
|
float, const ImVec2&, int, int, const pxr::GfVec3d[3]);
|
||||||
|
void DrawRotateGizmo(ImDrawList*, const pxr::GfMatrix4d&, const pxr::GfVec3d&,
|
||||||
|
float, const pxr::GfVec3d&, const ImVec2&, int, int,
|
||||||
|
const pxr::GfVec3d[3]);
|
||||||
|
void DrawScaleGizmo (ImDrawList*, const pxr::GfMatrix4d&, const pxr::GfVec3d&,
|
||||||
|
float, const ImVec2&, int, int, const pxr::GfVec3d[3]);
|
||||||
|
|
||||||
|
int HitTestAxes(const pxr::GfMatrix4d&, const pxr::GfVec3d&, float,
|
||||||
|
const ImVec2&, int, int, const ImVec2&,
|
||||||
|
const pxr::GfVec3d[3]) const;
|
||||||
|
int HitTestRotateRings(const pxr::GfMatrix4d&, const pxr::GfVec3d&, float,
|
||||||
|
const pxr::GfVec3d&, const ImVec2&, int, int,
|
||||||
|
const ImVec2&, const pxr::GfVec3d[3]) const;
|
||||||
|
|
||||||
|
void ApplyTranslate(const pxr::GfVec3d&);
|
||||||
|
void ApplyRotate(const pxr::GfVec3f&, pxr::UsdGeomXformCommonAPI::RotationOrder);
|
||||||
|
void ApplyScale(const pxr::GfVec3f&);
|
||||||
|
void GetGizmoAxes(pxr::GfVec3d[3]) const;
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
// Per-mode drawing (all ImGui DrawList, screen-space)
|
ManipulatorMode m_mode = ManipulatorMode::Select;
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
void DrawMoveGizmo (ImDrawList* dl, const pxr::GfMatrix4d& vp,
|
|
||||||
const pxr::GfVec3d& pivot, float sf,
|
|
||||||
const ImVec2& imgPos, int vW, int vH,
|
|
||||||
const pxr::GfVec3d axes[3]);
|
|
||||||
void DrawRotateGizmo(ImDrawList* dl, const pxr::GfMatrix4d& vp,
|
|
||||||
const pxr::GfVec3d& pivot, float sf,
|
|
||||||
const pxr::GfVec3d& cameraEye,
|
|
||||||
const ImVec2& imgPos, int vW, int vH,
|
|
||||||
const pxr::GfVec3d axes[3]);
|
|
||||||
void DrawScaleGizmo (ImDrawList* dl, const pxr::GfMatrix4d& vp,
|
|
||||||
const pxr::GfVec3d& pivot, float sf,
|
|
||||||
const ImVec2& imgPos, int vW, int vH,
|
|
||||||
const pxr::GfVec3d axes[3]);
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
// Hit-testing
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
|
|
||||||
/// Returns axis index 0=X 1=Y 2=Z, or -1 if nothing hit (move / scale).
|
|
||||||
int HitTestAxes(const pxr::GfMatrix4d& vp,
|
|
||||||
const pxr::GfVec3d& pivot, float sf,
|
|
||||||
const ImVec2& imgPos, int vW, int vH,
|
|
||||||
const ImVec2& mousePosAbsolute,
|
|
||||||
const pxr::GfVec3d axes[3]) const;
|
|
||||||
|
|
||||||
/// Returns axis index 0=X 1=Y 2=Z, or -1 if nothing hit (rotate rings).
|
|
||||||
/// Uses proximity to the VISIBLE front-facing half-arc only.
|
|
||||||
int HitTestRotateRings(const pxr::GfMatrix4d& vp,
|
|
||||||
const pxr::GfVec3d& pivot, float sf,
|
|
||||||
const pxr::GfVec3d& cameraEye,
|
|
||||||
const ImVec2& imgPos, int vW, int vH,
|
|
||||||
const ImVec2& mousePosAbsolute,
|
|
||||||
const pxr::GfVec3d axes[3]) const;
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
// USD transform write helpers
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
void ApplyMoveDelta (const pxr::GfVec3d& worldDelta);
|
|
||||||
void ApplyRotateDelta(int axisIndex, float angleDeg);
|
|
||||||
void ApplyScaleDelta (int axisIndex, float factor);
|
|
||||||
|
|
||||||
/// Fills @p outAxes[3] with the gizmo X/Y/Z axis directions in world space.
|
|
||||||
/// In World space: fixed unit vectors.
|
|
||||||
/// In Object space: the prim's local axes extracted from its local-to-world matrix.
|
|
||||||
void GetGizmoAxes(pxr::GfVec3d outAxes[3]) const;
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
// State
|
|
||||||
// -----------------------------------------------------------------------
|
|
||||||
ManipulatorMode m_mode = ManipulatorMode::Select;
|
|
||||||
TransformSpace m_transformSpace = TransformSpace::Object;
|
TransformSpace m_transformSpace = TransformSpace::Object;
|
||||||
pxr::UsdStageRefPtr m_stage;
|
pxr::UsdStageRefPtr m_stage;
|
||||||
pxr::SdfPath m_primPath;
|
pxr::SdfPath m_primPath;
|
||||||
CommandHistory* m_commandHistory = nullptr;
|
CommandHistory* m_commandHistory = nullptr;
|
||||||
|
|
||||||
// Drag state
|
|
||||||
bool m_isDragging = false;
|
bool m_isDragging = false;
|
||||||
int m_dragAxis = -1;
|
int m_dragAxis = -1;
|
||||||
ImVec2 m_dragLastPos = {0.f, 0.f};
|
int m_hoveredAxis = -1;
|
||||||
|
|
||||||
// For rotation drag: screen-angle around projected pivot center
|
// ---- Translate drag ---------------------------------------------------
|
||||||
float m_dragRotateLastAngle = 0.f; ///< atan2 angle of mouse around pivot (radians)
|
pxr::GfLine m_dragMoveAxisLine;
|
||||||
|
pxr::GfVec3d m_dragMoveOriginOnAxis;
|
||||||
|
|
||||||
// Saved xform at drag START (never mutated during drag — used for undo)
|
// ---- Rotate drag (usdtweak clock-hand, extended with space-switch) ----
|
||||||
pxr::GfVec3d m_dragOriginalTranslate = {0.0, 0.0, 0.0};
|
//
|
||||||
pxr::GfVec3f m_dragOriginalRotate = {0.f, 0.f, 0.f};
|
// Ring plane: world-space ring normal (for mouse-ray intersection + sign).
|
||||||
pxr::GfVec3f m_dragOriginalScale = {1.f, 1.f, 1.f};
|
//
|
||||||
|
// Delta-rotation axis differs by TransformSpace:
|
||||||
|
// Object => initRot.GetRow3(axis) -- local axis in PARENT space
|
||||||
|
// (exact usdtweak localPlaneNormal convention)
|
||||||
|
// World => world unit vector
|
||||||
|
// (correct for root-level / simple hierarchies)
|
||||||
|
//
|
||||||
|
// resultingRotation formula:
|
||||||
|
// Object => GfMatrix4d(1).SetRotate(delta) * initRot
|
||||||
|
// (usdtweak: delta applied in local frame before initRot)
|
||||||
|
// World => initRot * GfMatrix4d(1).SetRotate(delta)
|
||||||
|
// (USD row-vector: initRot maps local->parent, then world delta)
|
||||||
|
pxr::GfVec3d m_dragRotatePlaneNormal; // world-space
|
||||||
|
pxr::GfVec3d m_dragRotateDeltaAxis; // space-dependent (see above)
|
||||||
|
pxr::GfVec3d m_dragRotateFrom; // clock-hand at drag start
|
||||||
|
pxr::GfMatrix4d m_dragRotateInitialRotMat; // local->parent rot at drag start
|
||||||
|
bool m_dragRotateObjectSpace = true;
|
||||||
|
|
||||||
|
// ---- Scale drag -------------------------------------------------------
|
||||||
|
pxr::GfLine m_dragScaleAxisLine;
|
||||||
|
pxr::GfVec3d m_dragScaleOriginOnAxis;
|
||||||
|
|
||||||
|
// ---- Snapshot ---------------------------------------------------------
|
||||||
|
pxr::GfVec3d m_dragOriginalTranslate = {0,0,0};
|
||||||
|
pxr::GfVec3f m_dragOriginalRotate = {0,0,0};
|
||||||
|
pxr::GfVec3f m_dragOriginalScale = {1,1,1};
|
||||||
pxr::UsdGeomXformCommonAPI::RotationOrder m_dragOriginalRotOrder =
|
pxr::UsdGeomXformCommonAPI::RotationOrder m_dragOriginalRotOrder =
|
||||||
pxr::UsdGeomXformCommonAPI::RotationOrderXYZ;
|
pxr::UsdGeomXformCommonAPI::RotationOrderXYZ;
|
||||||
|
|
||||||
// Working accumulator for the current drag (updated each frame)
|
pxr::GfVec3d m_dragStartTranslate = {0,0,0};
|
||||||
pxr::GfVec3d m_dragStartTranslate = {0.0, 0.0, 0.0};
|
pxr::GfVec3f m_dragStartRotate = {0,0,0};
|
||||||
pxr::GfVec3f m_dragStartRotate = {0.f, 0.f, 0.f};
|
pxr::GfVec3f m_dragStartScale = {1,1,1};
|
||||||
pxr::GfVec3f m_dragStartScale = {1.f, 1.f, 1.f};
|
|
||||||
|
|
||||||
// Hover highlight
|
|
||||||
int m_hoveredAxis = -1;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace UsdLayerManager
|
} // namespace UsdLayerManager
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue