From 157abcd052628729252f5f37e0312f8c72e0afda Mon Sep 17 00:00:00 2001 From: "C. J. Howard" Date: Sat, 15 Jul 2017 03:22:42 +0800 Subject: [PATCH] Add pie menu and skybox rendering --- CMakeLists.txt | 2 + data | 2 +- lib/emergent | 2 +- src/application.cpp | 54 +++++++++- src/application.hpp | 15 +++ src/game/ant.cpp | 17 +-- src/game/ant.hpp | 3 +- src/game/colony.cpp | 6 +- src/game/habitat.cpp | 6 +- src/game/navmesh.cpp | 2 +- src/game/tool.hpp | 17 +++ src/render-passes.cpp | 102 +++++++++++++++++- src/render-passes.hpp | 30 ++++++ src/states/play-state.cpp | 49 ++++++++- src/states/play-state.hpp | 6 ++ src/ui/pie-menu.cpp | 218 ++++++++++++++++++++++++++++++++++++++ src/ui/pie-menu.hpp | 65 ++++++++++++ src/ui/toolbar.cpp | 7 +- src/ui/ui.cpp | 12 +-- src/ui/ui.hpp | 12 +-- 20 files changed, 586 insertions(+), 41 deletions(-) create mode 100644 src/game/tool.hpp create mode 100644 src/ui/pie-menu.cpp create mode 100644 src/ui/pie-menu.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ca0392..b5d425c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -114,6 +114,8 @@ set(EXECUTABLE_SOURCES ${EXECUTABLE_SOURCE_DIR}/ui/tween.cpp ${EXECUTABLE_SOURCE_DIR}/ui/toolbar.hpp ${EXECUTABLE_SOURCE_DIR}/ui/toolbar.cpp + ${EXECUTABLE_SOURCE_DIR}/ui/pie-menu.hpp + ${EXECUTABLE_SOURCE_DIR}/ui/pie-menu.cpp ${EXECUTABLE_SOURCE_DIR}/render-passes.cpp ${EXECUTABLE_SOURCE_DIR}/game/ant.hpp ${EXECUTABLE_SOURCE_DIR}/game/ant.cpp diff --git a/data b/data index e100b6f..38fc778 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit e100b6f66b464e97f3896153a9bb26e586d81271 +Subproject commit 38fc778db84b0fd8c176c828a54fbf7145b948e1 diff --git a/lib/emergent b/lib/emergent index 0de6664..fbeb27d 160000 --- a/lib/emergent +++ b/lib/emergent @@ -1 +1 @@ -Subproject commit 0de6664dde3dfa888b1d6997ce73618c4981f25c +Subproject commit fbeb27d70b08fc64543ccf6cfcd0c7c67399e358 diff --git a/src/application.cpp b/src/application.cpp index 7e7c713..7430696 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -28,6 +28,7 @@ #include "states/play-state.hpp" #include "game/colony.hpp" #include "ui/toolbar.hpp" +#include "ui/pie-menu.hpp" #include "debug.hpp" #include "camera-controller.hpp" #include @@ -632,6 +633,10 @@ bool Application::loadScene() bgCamera.setCompositor(&bgCompositor); bgCamera.setCompositeIndex(0); + // Setup skybox pass + skyboxPass.setRenderTarget(&defaultRenderTarget); + defaultCompositor.addPass(&skyboxPass); + // Setup soil pass soilPass.setRenderTarget(&defaultRenderTarget); defaultCompositor.addPass(&soilPass); @@ -704,6 +709,7 @@ bool Application::loadUI() pauseButtonTexture = textureLoader->load("data/textures/pause-button.png"); playButtonTexture = textureLoader->load("data/textures/play-button.png"); rectangularPaletteTexture = textureLoader->load("data/textures/rectangular-palette.png"); + foodIndicatorTexture = textureLoader->load("data/textures/food-indicator.png"); toolBrushTexture = textureLoader->load("data/textures/tool-brush.png"); toolLensTexture = textureLoader->load("data/textures/tool-lens.png"); toolForcepsTexture = textureLoader->load("data/textures/tool-forceps.png"); @@ -715,6 +721,14 @@ bool Application::loadUI() toolbarButtonRaisedTexture = textureLoader->load("data/textures/toolbar-button-raised.png"); toolbarButtonDepressedTexture = textureLoader->load("data/textures/toolbar-button-depressed.png"); + arcNorthTexture = textureLoader->load("data/textures/pie-menu-arc-north.png"); + arcEastTexture = textureLoader->load("data/textures/pie-menu-arc-east.png"); + arcSouthTexture = textureLoader->load("data/textures/pie-menu-arc-south.png"); + arcWestTexture = textureLoader->load("data/textures/pie-menu-arc-west.png"); + + mouseLeftTexture = textureLoader->load("data/textures/mouse-left.png"); + mouseRightTexture = textureLoader->load("data/textures/mouse-right.png"); + // Get strings std::string pressAnyKeyString; std::string copyrightString; @@ -755,6 +769,9 @@ bool Application::loadUI() selectedColor = Vector4(1.0f, 1.0f, 1.0f, 1.0f); deselectedColor = Vector4(1.0f, 1.0f, 1.0f, 0.35f); + // Create tweener + tweener = new Tweener(); + // Setup root UI element uiRootElement = new UIContainer(); uiRootElement->setDimensions(Vector2(width, height)); @@ -1018,14 +1035,28 @@ bool Application::loadUI() uiRootElement->addChild(playButtonImage); rectangularPaletteImage = new UIImage(); - rectangularPaletteImage->setAnchor(Vector2(0.5f, 1.0f)); + rectangularPaletteImage->setAnchor(Vector2(0.0f, 1.0f)); rectangularPaletteImage->setDimensions(Vector2(rectangularPaletteTexture->getWidth(), rectangularPaletteTexture->getHeight())); - rectangularPaletteImage->setTranslation(Vector2(0.0f, -16.0f)); + rectangularPaletteImage->setTranslation(Vector2(16.0f, -16.0f)); rectangularPaletteImage->setTexture(rectangularPaletteTexture); rectangularPaletteImage->setVisible(false); rectangularPaletteImage->setActive(false); uiRootElement->addChild(rectangularPaletteImage); + contextButtonImage0 = new UIImage(); + contextButtonImage0->setAnchor(Vector2(0.5f, 1.0f)); + contextButtonImage0->setDimensions(Vector2(mouseLeftTexture->getWidth(), mouseLeftTexture->getHeight())); + contextButtonImage0->setTranslation(Vector2(0.0f, -16.0f)); + contextButtonImage0->setTexture(mouseLeftTexture); + uiRootElement->addChild(contextButtonImage0); + + foodIndicatorImage = new UIImage(); + foodIndicatorImage->setAnchor(Vector2(1.0f, 0.0f)); + foodIndicatorImage->setDimensions(Vector2(foodIndicatorTexture->getWidth(), foodIndicatorTexture->getHeight())); + foodIndicatorImage->setTranslation(Vector2(-16.0f, 16.0f)); + foodIndicatorImage->setTexture(foodIndicatorTexture); + uiRootElement->addChild(foodIndicatorImage); + // Create toolbar toolbar = new Toolbar(); toolbar->setToolbarTopTexture(toolbarTopTexture); @@ -1038,12 +1069,22 @@ bool Application::loadUI() toolbar->addButton(toolForcepsTexture, std::bind(SceneObject::setActive, &forcepsModelInstance, true), std::bind(SceneObject::setActive, &forcepsModelInstance, false)); toolbar->addButton(toolTrowelTexture, std::bind(std::printf, "3\n"), std::bind(std::printf, "3\n")); toolbar->resize(); - uiRootElement->addChild(toolbar->getContainer()); + //uiRootElement->addChild(toolbar->getContainer()); toolbar->getContainer()->setVisible(false); toolbar->getContainer()->setActive(false); - // Create tweener - tweener = new Tweener(); + // Create pie menu + pieMenu = new PieMenu(tweener); + pieMenu->addOption(arcNorthTexture, toolLensTexture, std::bind(std::printf, "0 on\n"), std::bind(std::printf, "0 off\n")); + pieMenu->addOption(arcEastTexture, toolForcepsTexture, std::bind(std::printf, "1 on\n"), std::bind(std::printf, "1 off\n")); + pieMenu->addOption(arcSouthTexture, toolTrowelTexture, std::bind(std::printf, "2 on\n"), std::bind(std::printf, "2 off\n")); + pieMenu->addOption(arcWestTexture, toolBrushTexture, std::bind(std::printf, "3 on\n"), std::bind(std::printf, "3 off\n")); + uiRootElement->addChild(pieMenu->getContainer()); + pieMenu->resize(); + pieMenu->getContainer()->setVisible(false); + pieMenu->getContainer()->setActive(true); + + // Setup screen fade in/fade out tween fadeInTween = new Tween(EaseFunction::IN_CUBIC, 0.0f, 1.5f, Vector4(0.0f, 0.0f, 0.0f, 1.0f), Vector4(0.0f, 0.0f, 0.0f, -1.0f)); @@ -1523,6 +1564,9 @@ void Application::loadLevel() std::string heightmap = std::string("data/textures/") + level->heightmap; terrain.load(heightmap); + // Set skybox + skyboxPass.setCubemap(biome->specularCubemap); + changeState(playState); } diff --git a/src/application.hpp b/src/application.hpp index b25ca6c..4ca7ed6 100644 --- a/src/application.hpp +++ b/src/application.hpp @@ -50,6 +50,7 @@ class LineBatcher; class ModelLoader; class MaterialLoader; class Toolbar; +class PieMenu; /** * Encapsulates the state of the application. @@ -173,6 +174,7 @@ public: BillboardBatch bgBatch; Compositor bgCompositor; VignetteRenderPass vignettePass; + SkyboxRenderPass skyboxPass; TextureLoader* textureLoader; MaterialLoader* materialLoader; ModelLoader* modelLoader; @@ -230,6 +232,7 @@ public: Texture* pauseButtonTexture; Texture* playButtonTexture; Texture* rectangularPaletteTexture; + Texture* foodIndicatorTexture; Texture* toolBrushTexture; Texture* toolLensTexture; Texture* toolForcepsTexture; @@ -241,6 +244,13 @@ public: Texture* toolbarButtonRaisedTexture; Texture* toolbarButtonDepressedTexture; + Texture* arcNorthTexture; + Texture* arcEastTexture; + Texture* arcSouthTexture; + Texture* arcWestTexture; + Texture* mouseLeftTexture; + Texture* mouseRightTexture; + // UI elements Vector4 selectedColor; Vector4 deselectedColor; @@ -287,7 +297,12 @@ public: UIImage* playButtonImage; UIImage* rectangularPaletteImage; + UIImage* foodIndicatorImage; + UIImage* contextButtonImage0; + UIImage* contextButtonImage1; + Toolbar* toolbar; + PieMenu* pieMenu; // Animation Tweener* tweener; diff --git a/src/game/ant.cpp b/src/game/ant.cpp index 8ec690e..9980bd4 100644 --- a/src/game/ant.cpp +++ b/src/game/ant.cpp @@ -90,7 +90,7 @@ void Ant::update(float dt) AABB neighborhoodAABB(getPosition() - Vector3(neighborhoodSize * 0.5f), getPosition() + Vector3(neighborhoodSize * 0.5f)); std::list neighbors; colony->queryAnts(neighborhoodAABB, &neighbors); - + // Calculate separation force Vector3 separationForce = separation(neighbors); @@ -107,7 +107,7 @@ void Ant::update(float dt) // Move ant move(velocity); } - else + else if (state == Ant::State::IDLE) { Vector3 leftProbe = getForward() * probeForwardOffset - getRight() * probeLateralOffset; Vector3 rightProbe = getForward() * probeForwardOffset + getRight() * probeLateralOffset; @@ -137,11 +137,14 @@ void Ant::update(float dt) */ // Update transform - transform.translation = getPosition(); - transform.rotation = getRotation(); - - // Update model instance - modelInstance.setTransform(transform); + if (state != Ant::State::DEAD) + { + transform.translation = getPosition(); + transform.rotation = getRotation(); + + // Update model instance + modelInstance.setTransform(transform); + } } Vector3 Ant::forage(const Vector3& leftReceptor, const Vector3& rightReceptor) diff --git a/src/game/ant.hpp b/src/game/ant.hpp index aea9d3f..c886402 100644 --- a/src/game/ant.hpp +++ b/src/game/ant.hpp @@ -57,7 +57,8 @@ public: enum class State { IDLE, - WANDER + WANDER, + DEAD }; /** diff --git a/src/game/colony.cpp b/src/game/colony.cpp index 1018771..e8a1492 100644 --- a/src/game/colony.cpp +++ b/src/game/colony.cpp @@ -24,8 +24,8 @@ Colony::Colony(): antModel(nullptr) { - antOctree = new Octree(AABB(Vector3(-8.0f), Vector3(8.0f)), 5); - pheromoneOctree = new Octree(AABB(Vector3(-8.0f), Vector3(8.0f)), 5); + antOctree = new Octree(5, AABB(Vector3(-26.0f), Vector3(26.0f))); + pheromoneOctree = new Octree(5, AABB(Vector3(-26.0f), Vector3(26.0f))); } Colony::~Colony() @@ -53,7 +53,7 @@ void Colony::update(float dt) antOctree->clear(); for (Ant* ant: ants) { - antOctree->insert(AABB(ant->getPosition(), ant->getPosition()), ant); + antOctree->insert(ant->getModelInstance()->getBounds(), ant); } // Update ants diff --git a/src/game/habitat.cpp b/src/game/habitat.cpp index 7c18e8c..af2d294 100644 --- a/src/game/habitat.cpp +++ b/src/game/habitat.cpp @@ -21,9 +21,9 @@ Habitat::Habitat(const AABB& bounds, int maxOctreeDepth) { - obstacleOctree = new Octree(bounds, maxOctreeDepth); - pheromoneOctree = new Octree(bounds, maxOctreeDepth); - agentOctree = new Octree(bounds, maxOctreeDepth); + obstacleOctree = new Octree(maxOctreeDepth, bounds); + pheromoneOctree = new Octree(maxOctreeDepth, bounds); + agentOctree = new Octree(maxOctreeDepth, bounds); } Habitat::~Habitat() diff --git a/src/game/navmesh.cpp b/src/game/navmesh.cpp index 640301f..b291813 100644 --- a/src/game/navmesh.cpp +++ b/src/game/navmesh.cpp @@ -670,7 +670,7 @@ std::tuple intersects(const Ray& r Octree* Navmesh::createOctree(std::size_t maxDepth) { - Octree* result = new Octree(bounds, maxDepth); + Octree* result = new Octree(maxDepth, bounds); for (Navmesh::Triangle* triangle: triangles) { diff --git a/src/game/tool.hpp b/src/game/tool.hpp new file mode 100644 index 0000000..dade7f1 --- /dev/null +++ b/src/game/tool.hpp @@ -0,0 +1,17 @@ + +enum class ToolContext +{ + BRUSH_PAINT, + LENS_FOCUS, + FORCEPS_PINCH, + FORCEPS_RELEASE, + FORCEPS_PICK_UP, + FORCEPS_PUT_DOWN +}; + +class Tool +{ +public: + + ToolContext gatherContext(); +}; \ No newline at end of file diff --git a/src/render-passes.cpp b/src/render-passes.cpp index 7ba9c1e..df10a2e 100644 --- a/src/render-passes.cpp +++ b/src/render-passes.cpp @@ -334,7 +334,7 @@ bool LightingRenderPass::load(const RenderContext* renderContext) // Load cubemap textureLoader.setCubemap(true); textureLoader.setMipmapChain(false); - diffuseCubemap = textureLoader.load("data/textures/galileo-diffuse.png"); + diffuseCubemap = textureLoader.load("data/textures/campus-diffuse.png"); if (!diffuseCubemap) { std::cerr << "Failed to load cubemap" << std::endl; @@ -342,7 +342,7 @@ bool LightingRenderPass::load(const RenderContext* renderContext) textureLoader.setCubemap(true); textureLoader.setMipmapChain(true); - specularCubemap = textureLoader.load("data/textures/galileo-specular_m%02d.png"); + specularCubemap = textureLoader.load("data/textures/campus-specular_m%02d.png"); if (!specularCubemap) { std::cerr << "Failed to load cubemap" << std::endl; @@ -1260,3 +1260,101 @@ void VignetteRenderPass::render(const RenderContext* renderContext) glDrawElementsBaseVertex(GL_TRIANGLES, operation.triangleCount * 3, GL_UNSIGNED_INT, (void*)0, operation.indexOffset); } } + +SkyboxRenderPass::SkyboxRenderPass(): + shader(nullptr), + cubemap(nullptr) +{ + matrixParam = parameterSet.addParameter("matrix", ShaderParameter::Type::MATRIX_4, 1); + cubemapParam = parameterSet.addParameter("cubemap", ShaderParameter::Type::INT, 1); +} + +bool SkyboxRenderPass::load(const RenderContext* renderContext) +{ + shaderLoader.undefine(); + shaderLoader.define("VERTEX_POSITION", EMERGENT_VERTEX_POSITION); + + shader = shaderLoader.load("data/shaders/skybox.glsl", ¶meterSet); + if (!shader) + { + return false; + } + + + const float quadVertexData[] = + { + -1.0f, 1.0f, 0.0f, + -1.0f, -1.0f, 0.0f, + 1.0f, -1.0f, 0.0f, + 1.0f, 1.0f, 0.0f + }; + + const std::uint32_t quadIndexData[] = + { + 0, 1, 3, + 3, 1, 2 + }; + + quadVertexCount = 4; + quadIndexCount = 6; + + // Create AABB geometry + glGenVertexArrays(1, &quadVAO); + glBindVertexArray(quadVAO); + glGenBuffers(1, &quadVBO); + glBindBuffer(GL_ARRAY_BUFFER, quadVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 3 * quadVertexCount, quadVertexData, GL_STATIC_DRAW); + glEnableVertexAttribArray(EMERGENT_VERTEX_POSITION); + glVertexAttribPointer(EMERGENT_VERTEX_POSITION, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (char*)0 + 0*sizeof(float)); + glGenBuffers(1, &quadIBO); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, quadIBO); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(std::uint32_t) * quadIndexCount, quadIndexData, GL_STATIC_DRAW); + + return true; +} + +void SkyboxRenderPass::unload() +{ + delete shader; + shader = nullptr; + + glDeleteBuffers(1, &quadIBO); + glDeleteBuffers(1, &quadVBO); + glDeleteVertexArrays(1, &quadVAO); +} + +void SkyboxRenderPass::render(const RenderContext* renderContext) +{ + if (!cubemap) + { + return; + } + + glDisable(GL_DEPTH_TEST); + glDepthMask(GL_FALSE); + + //glDisable(GL_CULL_FACE); + //glCullFace(GL_BACK); + + // Bind shader + shader->bind(); + + // Bind cubemap texture + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_CUBE_MAP, cubemap->getTextureID()); + + // Pass texture unit to shader + shader->setParameter(cubemapParam, 0); + + // Calculate matrix + const Camera& camera = *(renderContext->camera); + Matrix4 modelView = Matrix4(Matrix3(camera.getView())); + Matrix4 matrix = glm::inverse(modelView) * glm::inverse(camera.getProjection()); + + // Pass matrix to shader + shader->setParameter(matrixParam, matrix); + + // Render quad + glBindVertexArray(quadVAO); + glDrawElementsBaseVertex(GL_TRIANGLES, quadIndexCount, GL_UNSIGNED_INT, (void*)0, 0); +} diff --git a/src/render-passes.hpp b/src/render-passes.hpp index ceeaf4e..564adeb 100644 --- a/src/render-passes.hpp +++ b/src/render-passes.hpp @@ -233,5 +233,35 @@ private: GLuint bayerTextureID; }; +/** + * Renders a skybox + */ +class SkyboxRenderPass: public RenderPass +{ +public: + SkyboxRenderPass(); + + inline void setCubemap(Texture* cubemap) { this->cubemap = cubemap; } + virtual bool load(const RenderContext* renderContext); + virtual void unload(); + virtual void render(const RenderContext* renderContext); + +private: + ShaderParameterSet parameterSet; + const ShaderParameter* matrixParam; + const ShaderParameter* cubemapParam; + + ShaderLoader shaderLoader; + Shader* shader; + Texture* cubemap; + + int quadVertexCount; + int quadIndexCount; + GLuint quadVAO; + GLuint quadVBO; + GLuint quadIBO; +}; + + #endif // RENDER_PASSES_HPP diff --git a/src/states/play-state.cpp b/src/states/play-state.cpp index 51442bb..ef1bce3 100644 --- a/src/states/play-state.cpp +++ b/src/states/play-state.cpp @@ -100,6 +100,8 @@ void PlayState::enter() application->simulationPaused = false; application->mouse->addMouseButtonObserver(this); + + pickAnt = nullptr; } void PlayState::execute() @@ -172,10 +174,8 @@ void PlayState::execute() Vector3 mouseNear = application->camera.unproject(Vector3(mousePosition.x, mousePosition.y, 0.0f), viewport); Vector3 mouseFar = application->camera.unproject(Vector3(mousePosition.x, mousePosition.y, 1.0f), viewport); - Ray pickingRay; pickingRay.origin = mouseNear; pickingRay.direction = glm::normalize(mouseFar - mouseNear); - Vector3 pick; std::list triangles; application->terrain.getSurfaceOctree()->query(pickingRay, &triangles); @@ -186,7 +186,7 @@ void PlayState::execute() pick = pickingRay.extrapolate(std::get<1>(result)); std::size_t triangleIndex = std::get<3>(result); - const Navmesh::Triangle* triangle = (*application->terrain.getSurfaceNavmesh()->getTriangles())[triangleIndex]; + pickTriangle = (*application->terrain.getSurfaceNavmesh()->getTriangles())[triangleIndex]; float forcepsDistance = (application->forcepsClosed) ? 0.0f : 0.5f; @@ -201,6 +201,11 @@ void PlayState::execute() application->forcepsModelInstance.setRotation(rotation); } + if (pickAnt != nullptr) + { + pickAnt->getModelInstance()->setTranslation(pick); + } + // Update colony if (!application->simulationPaused) { @@ -235,6 +240,31 @@ void PlayState::mouseButtonPressed(int button, int x, int y) if (button == 1) { application->forcepsClosed = true; + + Sphere forcepsSphere = Sphere(pick, 0.35f); + + std::list ants; + pickAnt = nullptr; + float closestDistance = std::numeric_limits::infinity(); + + application->colony->queryAnts(forcepsSphere, &ants); + for (Agent* agent: ants) + { + Ant* ant = static_cast(agent); + + Vector3 difference = ant->getPosition() - pick; + float distanceSquared = glm::dot(difference, difference); + if (distanceSquared < closestDistance) + { + closestDistance = distanceSquared; + pickAnt = ant; + } + } + + if (pickAnt != nullptr) + { + pickAnt->setState(Ant::State::DEAD); + } } } @@ -243,5 +273,18 @@ void PlayState::mouseButtonReleased(int button, int x, int y) if (button == 1) { application->forcepsClosed = false; + + if (pickAnt != nullptr) + { + auto result = intersects(pickingRay, pickTriangle); + if (std::get<0>(result)) + { + Vector3 barycentricPosition = Vector3(std::get<2>(result), std::get<3>(result), 1.0f - std::get<2>(result) - std::get<3>(result)); + pickAnt->setPosition(pickTriangle, barycentricPosition); + } + + pickAnt->setState(Ant::State::WANDER); + pickAnt = nullptr; + } } } diff --git a/src/states/play-state.hpp b/src/states/play-state.hpp index e4c853a..f60607f 100644 --- a/src/states/play-state.hpp +++ b/src/states/play-state.hpp @@ -22,6 +22,8 @@ #include "../application-state.hpp" #include "../input.hpp" +#include "../game/ant.hpp" +#include "../game/navmesh.hpp" #include using namespace Emergent; @@ -42,6 +44,10 @@ public: private: ModelInstance terrainSurface; ModelInstance terrainSubsurface; + Vector3 pick; + Ray pickingRay; + Navmesh::Triangle* pickTriangle; + Ant* pickAnt; }; #endif // PLAY_STATE_HPP \ No newline at end of file diff --git a/src/ui/pie-menu.cpp b/src/ui/pie-menu.cpp new file mode 100644 index 0000000..94a9860 --- /dev/null +++ b/src/ui/pie-menu.cpp @@ -0,0 +1,218 @@ +#include "pie-menu.hpp" +#include + +PieMenu::PieMenu(Tweener* tweener): + tweener(tweener), + scaleUpTween(nullptr), + scaleDownTween(nullptr), + scale(1.0f), + selectionIndex(0), + dragging(false), + dragStart(0.0f) +{ + // Setup fullscreen container + fullscreenContainer.addChild(&croppedContainer); + fullscreenContainer.setMouseMovedCallback(std::bind(PieMenu::mouseMoved, this, std::placeholders::_1, std::placeholders::_2)); + fullscreenContainer.setMousePressedCallback(std::bind(PieMenu::mouseButtonPressed, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + fullscreenContainer.setMouseReleasedCallback(std::bind(PieMenu::mouseButtonReleased, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + + // Setup cropped container + croppedContainer.addChild(&scalingContainer); + + // Setup scaling container + scalingContainer.setAnchor(Vector2(0.5f)); + + // Create tweens + scaleUpTween = new Tween(EaseFunction::OUT_SINE, 0.0f, 0.1f, 0.0f, 1.0f); + scaleUpTween->setUpdateCallback(std::bind(PieMenu::setScale, this, std::placeholders::_1)); + scaleDownTween = new Tween(EaseFunction::IN_SINE, 0.0f, 0.1f, 1.0f, -1.0f); + scaleDownTween->setUpdateCallback(std::bind(PieMenu::setScale, this, std::placeholders::_1)); + + // Add tweens + tweener->addTween(scaleUpTween); + tweener->addTween(scaleDownTween); +} + +void PieMenu::resize() +{ + float iconDistance = 0.0f; + + if (fullscreenContainer.getParent() == nullptr) + { + return; + } + + // Resize fullscreen container + fullscreenContainer.setDimensions(fullscreenContainer.getParent()->getDimensions()); + + // Resize cropped container + croppedContainer.setDimensions(Vector2(options[0]->getTexture()->getWidth(), options[0]->getTexture()->getHeight())); + + // Place options + for (std::size_t i = 0; i < options.size(); ++i) + { + float angle = glm::radians(360.0f) / static_cast(i + 1) / static_cast(options.size()); + + options[i]->setAnchor(Vector2(0.5f, 0.5f)); + options[i]->setTranslation(Vector2(0.0f, 0.0f)); + + icons[i]->setAnchor(Vector2(0.5f, 0.5f)); + icons[i]->setTranslation(Vector2(0.0f, 0.0f)); + } +} + +void PieMenu::setScale(float scale) +{ + for (std::size_t i = 0; i < options.size(); ++i) + { + options[i]->setDimensions(Vector2(options[i]->getTexture()->getWidth(), options[i]->getTexture()->getHeight()) * scale); + icons[i]->setDimensions(Vector2(icons[i]->getTexture()->getWidth(), icons[i]->getTexture()->getHeight()) * scale); + } +} + +void PieMenu::addOption(Texture* backgroundTexture, Texture* iconTexture, std::function selectedCallback, std::function deselectedCallback) +{ + // Allocate new option + UIImage* option = new UIImage(); + option->setTexture(backgroundTexture); + option->setDimensions(Vector2(backgroundTexture->getWidth(), backgroundTexture->getHeight())); + option->setTintColor(Vector4(1.0f, 1.0f, 1.0f, 0.50f)); + options.push_back(option); + + UIImage* icon = new UIImage(); + icon->setTexture(iconTexture); + icon->setDimensions(Vector2(iconTexture->getWidth(), iconTexture->getHeight())); + icon->setTintColor(Vector4(1.0f, 1.0f, 1.0f, 0.50f)); + icons.push_back(icon); + + // Add icon to option + scalingContainer.addChild(icon); + + // Add option to menu + scalingContainer.addChild(option); + + // Setup callbacks + selectedCallbacks.push_back(selectedCallback); + deselectedCallbacks.push_back(deselectedCallback); +} + +void PieMenu::select(std::size_t index) +{ + if (index != selectionIndex && selectionIndex < options.size()) + { + deselect(selectionIndex); + } + + selectionIndex = index; + selectedCallbacks[index](); +} + +void PieMenu::deselect(std::size_t index) +{ + deselectedCallbacks[index](); +} + +void PieMenu::highlight(std::size_t index) +{ + options[index]->setTintColor(Vector4(1.0f, 1.0f, 1.0f, 1.0f)); + icons[index]->setTintColor(Vector4(1.0f, 1.0f, 1.0f, 1.0f)); +} + +void PieMenu::unhighlight(std::size_t index) +{ + options[index]->setTintColor(Vector4(1.0f, 1.0f, 1.0f, 0.50f)); + icons[index]->setTintColor(Vector4(1.0f, 1.0f, 1.0f, 0.50f)); +} + +void PieMenu::mouseMoved(int x, int y) +{ + if (dragging) + { + Vector2 direction = Vector2(x, y) - dragStart; + if (direction.x != 0.0f || direction.y != 0.0f) + { + direction = glm::normalize(direction); + + // Calculate arc length + float arcLength = glm::radians(360.0f) / static_cast(options.size()); + + // Calculate angle between cursor and pie menu + float angle = std::atan2(direction.y, direction.x) + glm::radians(90.0f) + arcLength * 0.5f; + while (angle < 0.0f) angle += glm::radians(360.0f); + + // Determine option index from angle + std::size_t index = static_cast(angle / arcLength); + + if (index != highlightedIndex) + { + if (highlightedIndex < options.size()) + { + unhighlight(highlightedIndex); + } + + highlight(index); + highlightedIndex = index; + } + } + } +} + +void PieMenu::mouseButtonPressed(int button, int x, int y) +{ + if (button == 3) + { + // Start dragging + dragging = true; + dragStart.x = x; + dragStart.y = y; + + // Set pie menu position + Vector2 halfDimensions = croppedContainer.getDimensions() * 0.5f; + croppedContainer.setTranslation(Vector2(x - halfDimensions.x, y - halfDimensions.y)); + + // Clear highlights + for (std::size_t i = 0; i < options.size(); ++i) + { + unhighlight(i); + } + + // Reset highlighted index + highlightedIndex = options.size(); + + // Show pie menu + fullscreenContainer.setVisible(true); + + // Start scale-up tween + scaleDownTween->stop(); + scaleUpTween->start(); + } +} + +void PieMenu::mouseButtonReleased(int button, int x, int y) +{ + if (button == 3) + { + // Stop dragging + dragging = false; + + // Select highlighted index + if (highlightedIndex != selectionIndex && highlightedIndex < options.size()) + { + select(highlightedIndex); + } + + // Clear highlights + for (std::size_t i = 0; i < options.size(); ++i) + { + unhighlight(i); + } + + // Reset highlighted index + highlightedIndex = options.size(); + + // Start scale-down tween + scaleUpTween->stop(); + scaleDownTween->start(); + //fullscreenContainer.setVisible(false); + } +} diff --git a/src/ui/pie-menu.hpp b/src/ui/pie-menu.hpp new file mode 100644 index 0000000..4aeacf0 --- /dev/null +++ b/src/ui/pie-menu.hpp @@ -0,0 +1,65 @@ +#ifndef PIE_MENU_HPP +#define PIE_MENU_HPP + +#include "ui.hpp" +#include "tween.hpp" +#include +#include + +#include +using namespace Emergent; + +class PieMenu +{ +public: + PieMenu(Tweener* tweener); + + void resize(); + + void addOption(Texture* backgroundTexture, Texture* iconTexture, std::function selectedCallback, std::function deselectedCallback); + + void select(std::size_t index); + void deselect(std::size_t index); + + const UIContainer* getContainer() const; + UIContainer* getContainer(); + void mouseMoved(int x, int y); + void mouseButtonPressed(int button, int x, int y); + void mouseButtonReleased(int button, int x, int y); + + void setScale(float scale); + +private: + void highlight(std::size_t index); + void unhighlight(std::size_t index); + + Tweener* tweener; + Tween* scaleUpTween; + Tween* scaleDownTween; + float scale; + + UIContainer fullscreenContainer; + UIContainer croppedContainer; + UIContainer scalingContainer; + std::vector options; + std::vector icons; + std::vector> selectedCallbacks; + std::vector> deselectedCallbacks; + std::size_t selectionIndex; + + bool dragging; + Vector2 dragStart; + std::size_t highlightedIndex; +}; + +inline const UIContainer* PieMenu::getContainer() const +{ + return &fullscreenContainer; +} + +inline UIContainer* PieMenu::getContainer() +{ + return &fullscreenContainer; +} + +#endif // PIE_MENU_HPP diff --git a/src/ui/toolbar.cpp b/src/ui/toolbar.cpp index 18eef7c..ffb60cd 100644 --- a/src/ui/toolbar.cpp +++ b/src/ui/toolbar.cpp @@ -94,6 +94,7 @@ void Toolbar::addButton(Texture* iconTexture, std::function pressCallbac UIImage* icon = new UIImage(); icon->setTexture(iconTexture); + icon->setTintColor(Vector4(1.0f, 1.0f, 1.0f, 0.30f)); icons.push_back(icon); // Add button to toolbar @@ -124,7 +125,8 @@ void Toolbar::pressButton(std::size_t index) { depressedButtonIndex = index; buttons[index]->setTexture(buttonDepressedTexture); - icons[index]->setTranslation(Vector2(2.0f, 2.0f)); + //icons[index]->setTranslation(Vector2(2.0f, 2.0f)); + icons[index]->setTintColor(Vector4(1.0f, 1.0f, 1.0f, 1.0f)); pressCallbacks[index](); } @@ -135,7 +137,8 @@ void Toolbar::releaseButton(std::size_t index) if (index < buttons.size()) { buttons[index]->setTexture(buttonRaisedTexture); - icons[index]->setTranslation(Vector2(0.0f, 0.0f)); + //icons[index]->setTranslation(Vector2(0.0f, 0.0f)); + icons[index]->setTintColor(Vector4(1.0f, 1.0f, 1.0f, 0.30f)); releaseCallbacks[index](); } diff --git a/src/ui/ui.cpp b/src/ui/ui.cpp index c648fc1..dbcf986 100644 --- a/src/ui/ui.cpp +++ b/src/ui/ui.cpp @@ -113,17 +113,17 @@ void UIElement::setMouseOutCallback(std::function callback) mouseOutCallback = callback; } -void UIElement::setMouseMovedCallback(std::function callback) +void UIElement::setMouseMovedCallback(std::function callback) { mouseMovedCallback = callback; } -void UIElement::setMousePressedCallback(std::function callback) +void UIElement::setMousePressedCallback(std::function callback) { mousePressedCallback = callback; } -void UIElement::setMouseReleasedCallback(std::function callback) +void UIElement::setMouseReleasedCallback(std::function callback) { mouseReleasedCallback = callback; } @@ -148,7 +148,7 @@ void UIElement::mouseMoved(int x, int y) if (mouseMovedCallback) { - mouseMovedCallback(); + mouseMovedCallback(x, y); } } else if (mouseOver) @@ -177,7 +177,7 @@ void UIElement::mouseButtonPressed(int button, int x, int y) { if (mousePressedCallback) { - mousePressedCallback(button); + mousePressedCallback(button, x, y); } for (UIElement* child: children) @@ -198,7 +198,7 @@ void UIElement::mouseButtonReleased(int button, int x, int y) { if (mouseReleasedCallback) { - mouseReleasedCallback(button); + mouseReleasedCallback(button, x , y); } for (UIElement* child: children) diff --git a/src/ui/ui.hpp b/src/ui/ui.hpp index 0eaaee0..3e37e5f 100644 --- a/src/ui/ui.hpp +++ b/src/ui/ui.hpp @@ -151,9 +151,9 @@ public: void setMouseOverCallback(std::function callback); void setMouseOutCallback(std::function callback); - void setMouseMovedCallback(std::function callback); - void setMousePressedCallback(std::function callback); - void setMouseReleasedCallback(std::function callback); + void setMouseMovedCallback(std::function callback); + void setMousePressedCallback(std::function callback); + void setMouseReleasedCallback(std::function callback); void mouseMoved(int x, int y); void mouseButtonPressed(int button, int x, int y); @@ -181,9 +181,9 @@ private: bool mouseOver; std::function mouseOverCallback; std::function mouseOutCallback; - std::function mouseMovedCallback; - std::function mousePressedCallback; - std::function mouseReleasedCallback; + std::function mouseMovedCallback; + std::function mousePressedCallback; + std::function mouseReleasedCallback; }; inline void UIElement::setAnchor(const Vector2& anchor)