Skip to content

Instantly share code, notes, and snippets.

@dov
Created October 12, 2024 19:02
Show Gist options
  • Save dov/77cf7d66fdb8cacce897dc8c4f893f7c to your computer and use it in GitHub Desktop.
Save dov/77cf7d66fdb8cacce897dc8c4f893f7c to your computer and use it in GitHub Desktop.
VulkanSceneGraph rectangular selection input
//======================================================================
// select-rectangle.cpp
//
// This program should demonstrate how to select multiple objects
// by drawing a rectangle with the mouse.
//
// This snippet is licensed under the MIT license.
//
// Dov Grobgeld <[email protected]>
// 2024-10-11 Fri
//----------------------------------------------------------------------
#include <fmt/format.h>
#include <vsg/all.h>
#include <vsg/app/ProjectionMatrix.h>
#include <array>
#include <iostream>
using fmt::print;
using namespace std;
struct Rectangle
{
vsg::dvec2 pos;
vsg::dvec2 size;
bool operator!=(const Rectangle& other) const
{
return pos != other.pos || size != other.size;
}
bool operator!=(Rectangle& other) const
{
return pos != other.pos || size != other.size;
}
};
// The MouseSelector handles both the visitor aspect and the
// five quad transformations vsg nodes used for mouse selection.
// The order of the transformations are:
//
// 0: center rectangle
// 1: upper outline horizontal line
// 2: lower outline horizontal line
// 3: left outline vertical line
// 4: right outline vertical line
class MouseSelector : public vsg::Inherit<vsg::Visitor, MouseSelector>
{
public:
// Constructor. Creates a view and adds it to the renderGraph
MouseSelector(vsg::ref_ptr<vsg::RenderGraph> renderGraph,
vsg::ref_ptr<vsg::Window> window)
: _window(window)
{
// Create the static orthonormal camera
_camera = vsg::Camera::create(
vsg::Orthographic::create(0, 1, 0, 1, -10,
10), // left, right, bottom, top, near, far
vsg::LookAt::create(vsg::dvec3(0.0, 0.0, 1.0),
vsg::dvec3(0.0, 0.0, 0.0),
vsg::dvec3(0.0, 1.0, 0.0)),
vsg::ViewportState::create(window->extent2D()));
auto view = vsg::View::create(_camera);
// The builder objects need a headlight
view->addChild(vsg::createHeadlight());
// Create a switch for turning on and off the rectangle
_switch = vsg::Switch::create();
view->addChild(_switch);
// Add the five quads and create their transformations
_switch->addChild(false, createRectangle());
renderGraph->addChild(view);
}
void apply(vsg::PointerEvent& pointerEvent) override
{
auto mousePos = vsg::dvec2{1.0 * pointerEvent.x, 1.0 * pointerEvent.y};
// Use RMB for selections as an example
bool isPressed = (pointerEvent.mask & vsg::BUTTON_MASK_3) > 0;
pointerEvent.handled = isPressed;
// Update the mouse selection
updateMouse(isPressed, mousePos);
}
void apply(vsg::ConfigureWindowEvent& confEvent) override
{
_camera->viewportState->set(0, 0, confEvent.width, confEvent.height);
}
// Create the five nodes for the rectangle and populate the
// rect_transforms with default matrices
vsg::ref_ptr<vsg::Node> createRectangle()
{
auto rect_group = vsg::Group::create();
vsg::ref_ptr<vsg::Builder> builder = vsg::Builder::create();
vsg::ref_ptr<vsg::Group> node = vsg::Group::create();
vsg::ref_ptr<vsg::Node> quatNode;
vsg::GeometryInfo geomInfo;
vsg::StateInfo stateInfo;
stateInfo.blending = true;
{
auto t = vsg::MatrixTransform::create(vsg::dmat4(1.0));
this->_rectTransforms[0] = t;
// transparent interior rectangle
geomInfo.color = rectColor;
t->addChild(builder->createQuad(geomInfo, stateInfo));
rect_group->addChild(t);
}
// The borders
geomInfo.color = outlineColor;
for (int i = 0; i < 4; i++)
{
auto t = vsg::MatrixTransform::create(vsg::dmat4(1.0));
this->_rectTransforms[1 + i] = t;
t->addChild(builder->createQuad(geomInfo, stateInfo));
rect_group->addChild(t);
}
return rect_group;
}
// Draw the rectangle at the indicated position by updating the
// five Matrix transformations
Rectangle lastRect;
void updateRectTransforms(const Rectangle& rect)
{
int w = _window->extent2D().width;
int h = _window->extent2D().height;
// Central rectangle
_rectTransforms[0]->matrix =
vsg::translate(rect.pos.x / w, rect.pos.y / h, 0.0) *
vsg::scale(rect.size.x / w, rect.size.y / h, 1.0) *
vsg::translate(0.5, 0.5, 0.0);
// Horizontal border
int i = 1;
for (auto y :
{rect.pos.y - outlineThicknessInPixels, rect.pos.y + rect.size.y})
{
_rectTransforms[i++]->matrix =
vsg::translate(rect.pos.x / w, y / h, 0.0) *
vsg::scale(rect.size.x / w, outlineThicknessInPixels / h, 1.0) *
vsg::translate(0.5, 0.5, 0.0);
}
// Vertical border
for (auto x :
{rect.pos.x - outlineThicknessInPixels, rect.pos.x + rect.size.x})
{
_rectTransforms[i++]->matrix =
vsg::translate(x / w, (rect.pos.y - outlineThicknessInPixels) / h,
0.0) *
vsg::scale(outlineThicknessInPixels / w,
(rect.size.y + 2 * outlineThicknessInPixels) / h, 1.0) *
vsg::translate(0.5, 0.5, 0.0);
}
}
// Update the mouse rectangle by the mouse state
void updateMouse(bool isPressed, vsg::dvec2 mousePos)
{
// update the mouse drawn rectangle
if (isPressed && !mouseDown)
{
mouseDown = true;
mouseDownPos = mousePos;
}
else if (!isPressed && mouseDown)
mouseDown = false;
if (mouseDown)
{
auto rect = Rectangle{mouseDownPos, mousePos - mouseDownPos};
rect.pos.y = _window->extent2D().height - rect.pos.y;
rect.size.y = -rect.size.y;
if (rect.size.x < 0)
{
rect.pos.x += rect.size.x;
rect.size.x = -rect.size.x;
}
if (rect.size.y < 0)
{
rect.pos.y += rect.size.y;
rect.size.y = -rect.size.y;
}
updateRectTransforms(rect);
_switch->setAllChildren(true); // show the rect
// Call virtual function with the coordinates of a new rectangle
if (rect != _prevRect) updateRect(rect);
_prevRect = rect;
}
else
_switch->setAllChildren(false); // hide the rect
}
// Override this function to get updates about a new rectangle
virtual void updateRect(const Rectangle& rect)
{
print("Got new rect: [{},{}],[{},{}] extent={},{}\n", rect.pos.x,
rect.pos.y, rect.size.x, rect.size.y, _window->extent2D().width,
_window->extent2D().height);
}
// Visual properties of the mouse rectangle selection
double outlineThicknessInPixels = 2;
vsg::dvec4 outlineColor{0.0, 1.0, 0.0, 0.7};
vsg::dvec4 rectColor{0.0, 1.0, 0.0, 0.2};
private:
vsg::ref_ptr<vsg::Window> _window;
vsg::ref_ptr<vsg::Camera> _camera;
// The previous rectangle to optimize the number of calls to updateRect()
Rectangle _prevRect;
// transformations for the selection rectangle parts
// The switch for the whether to show the selection rectangle
vsg::ref_ptr<vsg::Switch> _switch;
array<vsg::ref_ptr<vsg::MatrixTransform>, 5> _rectTransforms;
// mouse state
bool mouseDown = false;
vsg::dvec2 mouseDownPos{0, 0};
};
// A sample scene composed of a ground plane and a matrix of cubes
vsg::ref_ptr<vsg::Node> buildSampleScene()
{
auto builder = vsg::Builder::create();
auto scene = vsg::Group::create();
vsg::ref_ptr<vsg::Node> node;
{
vsg::GeometryInfo geomInfo;
vsg::StateInfo stateInfo;
// Change box color to red
geomInfo.color = vsg::vec4{0.5f, 0.5f, 0.5f, 1.0f};
geomInfo.transform = vsg::scale(10.0f, 10.0f, 10.0f);
auto ground = builder->createQuad(geomInfo, stateInfo);
scene->addChild(ground);
}
{
vsg::GeometryInfo geomInfo;
vsg::StateInfo stateInfo;
// Change box color to red
geomInfo.color = vsg::vec4{1.0f, 0.0f, 0.0f, 1.0f};
for (int i = 0; i < 16; i++)
{
geomInfo.transform =
vsg::translate(vsg::vec3(0.3f * (i / 4), 0.3f * (i % 4), 0.3f)) *
vsg::scale(0.1f, 0.1f, 0.1f);
node = builder->createBox(geomInfo, stateInfo);
scene->addChild(node);
}
}
return scene;
}
int main(int argc, char** argv)
{
auto windowTraits = vsg::WindowTraits::create();
windowTraits->windowTitle = "select-rectangle";
windowTraits->width = 800;
windowTraits->height = 600;
windowTraits->debugLayer = true;
auto scene = buildSampleScene();
// compute the bounds of the scene graph to help position the camera
vsg::ComputeBounds computeBounds;
scene->accept(computeBounds);
vsg::dvec3 centre =
(computeBounds.bounds.min + computeBounds.bounds.max) * 0.5;
// create the viewer and assign window(s) to it
auto viewer = vsg::Viewer::create();
auto window = vsg::Window::create(windowTraits);
if (!window)
{
std::cout << "Could not create window." << std::endl;
return 1;
}
viewer->addWindow(window);
// set up the camera
double radius = 1.0;
vsg::ref_ptr<vsg::LookAt> lookAt = vsg::LookAt::create(
centre + vsg::dvec3(-2 * radius, -radius * 2, 0.5 * radius) * 3.5, centre,
vsg::dvec3(0.0, 0.0, 1.0));
double nearFarRatio = 0.0001;
auto perspective = vsg::Perspective::create(
30.0,
static_cast<double>(window->extent2D().width) /
static_cast<double>(window->extent2D().height),
nearFarRatio * radius, radius * 100.0);
auto commandGraph = vsg::CommandGraph::create(window);
auto renderGraph = vsg::RenderGraph::create(window);
// The 3D view layer
auto camera = vsg::Camera::create(
perspective, lookAt, vsg::ViewportState::create(window->extent2D()));
auto view = vsg::View::create(camera);
view->addChild(vsg::createHeadlight());
view->addChild(scene);
renderGraph->addChild(view);
// Create the mouse selector view layer and add it to the render graph
auto mouse_selector = MouseSelector::create(renderGraph, window);
commandGraph->addChild(renderGraph);
viewer->assignRecordAndSubmitTaskAndPresentation({commandGraph});
// Add all event handlers
viewer->addEventHandler(mouse_selector);
viewer->addEventHandler(vsg::CloseHandler::create(viewer));
viewer->addEventHandler(vsg::Trackball::create(camera));
viewer->compile();
while (viewer->advanceToNextFrame())
{
viewer->handleEvents();
viewer->update();
viewer->recordAndSubmit();
viewer->present();
}
exit(0);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment