Created
October 12, 2024 19:02
-
-
Save dov/77cf7d66fdb8cacce897dc8c4f893f7c to your computer and use it in GitHub Desktop.
VulkanSceneGraph rectangular selection input
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//====================================================================== | |
// 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