diff --git a/CMakeLists.txt b/CMakeLists.txt index d55ca14..9ca0392 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -112,6 +112,8 @@ set(EXECUTABLE_SOURCES ${EXECUTABLE_SOURCE_DIR}/ui/ui.cpp ${EXECUTABLE_SOURCE_DIR}/ui/tween.hpp ${EXECUTABLE_SOURCE_DIR}/ui/tween.cpp + ${EXECUTABLE_SOURCE_DIR}/ui/toolbar.hpp + ${EXECUTABLE_SOURCE_DIR}/ui/toolbar.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 c11a70b..e100b6f 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit c11a70bf013de27252281cbbd80788ad3a1b5eaf +Subproject commit e100b6f66b464e97f3896153a9bb26e586d81271 diff --git a/lib/emergent b/lib/emergent index 14b8aeb..0de6664 160000 --- a/lib/emergent +++ b/lib/emergent @@ -1 +1 @@ -Subproject commit 14b8aeb2bf524d6f55407df259bad3eee85ef95c +Subproject commit 0de6664dde3dfa888b1d6997ce73618c4981f25c diff --git a/src/application.cpp b/src/application.cpp index a86d322..7e7c713 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -27,6 +27,7 @@ #include "states/main-menu-state.hpp" #include "states/play-state.hpp" #include "game/colony.hpp" +#include "ui/toolbar.hpp" #include "debug.hpp" #include "camera-controller.hpp" #include @@ -381,6 +382,10 @@ int Application::execute() { state->execute(); + // Update controls + menuControlProfile->update(); + gameControlProfile->update(); + // Perform tweening tweener->update(dt); @@ -407,9 +412,7 @@ int Application::execute() } } - // Update controls - menuControlProfile->update(); - gameControlProfile->update(); + // Update input inputManager->update(); @@ -530,6 +533,9 @@ void Application::changeFullscreen() settings.set("fullscreen", fullscreen); saveUserSettings(); + // Resize UI + resizeUI(); + // Notify window observers inputManager->update(); } @@ -580,6 +586,7 @@ bool Application::loadModels() antModel = modelLoader->load("data/models/debug-worker.mdl"); antHillModel = modelLoader->load("data/models/ant-hill.mdl"); nestModel = modelLoader->load("data/models/nest.mdl"); + forcepsModel = modelLoader->load("data/models/forceps.mdl"); if (!antModel || !antHillModel || !nestModel) { @@ -591,6 +598,7 @@ bool Application::loadModels() antHillModelInstance.setModel(antHillModel); antHillModelInstance.setRotation(glm::angleAxis(glm::radians(90.0f), Vector3(1, 0, 0))); nestModelInstance.setModel(nestModel); + forcepsModelInstance.setModel(forcepsModel); // Create terrain terrain.create(255, 255, Vector3(50, 20, 50)); @@ -685,6 +693,9 @@ bool Application::loadUI() textureLoader->setCubemap(false); textureLoader->setMipmapChain(false); textureLoader->setMaxAnisotropy(1.0f); + textureLoader->setWrapS(false); + textureLoader->setWrapT(false); + splashTexture = textureLoader->load("data/textures/ui-splash.png"); titleTexture = textureLoader->load("data/textures/ui-title.png"); levelActiveTexture = textureLoader->load("data/textures/ui-level-active.png"); @@ -692,6 +703,17 @@ bool Application::loadUI() levelConnectorTexture = textureLoader->load("data/textures/ui-level-connector.png"); pauseButtonTexture = textureLoader->load("data/textures/pause-button.png"); playButtonTexture = textureLoader->load("data/textures/play-button.png"); + rectangularPaletteTexture = textureLoader->load("data/textures/rectangular-palette.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"); + toolTrowelTexture = textureLoader->load("data/textures/tool-trowel.png"); + + toolbarTopTexture = textureLoader->load("data/textures/toolbar-top.png"); + toolbarBottomTexture = textureLoader->load("data/textures/toolbar-bottom.png"); + toolbarMiddleTexture = textureLoader->load("data/textures/toolbar-middle.png"); + toolbarButtonRaisedTexture = textureLoader->load("data/textures/toolbar-button-raised.png"); + toolbarButtonDepressedTexture = textureLoader->load("data/textures/toolbar-button-depressed.png"); // Get strings std::string pressAnyKeyString; @@ -995,6 +1017,31 @@ bool Application::loadUI() playButtonImage->setActive(false); uiRootElement->addChild(playButtonImage); + rectangularPaletteImage = new UIImage(); + rectangularPaletteImage->setAnchor(Vector2(0.5f, 1.0f)); + rectangularPaletteImage->setDimensions(Vector2(rectangularPaletteTexture->getWidth(), rectangularPaletteTexture->getHeight())); + rectangularPaletteImage->setTranslation(Vector2(0.0f, -16.0f)); + rectangularPaletteImage->setTexture(rectangularPaletteTexture); + rectangularPaletteImage->setVisible(false); + rectangularPaletteImage->setActive(false); + uiRootElement->addChild(rectangularPaletteImage); + + // Create toolbar + toolbar = new Toolbar(); + toolbar->setToolbarTopTexture(toolbarTopTexture); + toolbar->setToolbarBottomTexture(toolbarBottomTexture); + toolbar->setToolbarMiddleTexture(toolbarMiddleTexture); + toolbar->setButtonRaisedTexture(toolbarButtonRaisedTexture); + toolbar->setButtonDepressedTexture(toolbarButtonDepressedTexture); + toolbar->addButton(toolBrushTexture, std::bind(std::printf, "0\n"), std::bind(std::printf, "0\n")); + toolbar->addButton(toolLensTexture, std::bind(std::printf, "1\n"), std::bind(std::printf, "1\n")); + 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()); + toolbar->getContainer()->setVisible(false); + toolbar->getContainer()->setActive(false); + // Create tweener tweener = new Tweener(); diff --git a/src/application.hpp b/src/application.hpp index 6a53a98..b25ca6c 100644 --- a/src/application.hpp +++ b/src/application.hpp @@ -49,6 +49,7 @@ class TunnelCameraController; class LineBatcher; class ModelLoader; class MaterialLoader; +class Toolbar; /** * Encapsulates the state of the application. @@ -147,8 +148,7 @@ public: DirectionalLight sunlight; Spotlight lensHotspot; Spotlight lensFalloff; - ModelInstance lensToolObject; - ModelInstance forcepsToolObject; + ModelInstance forcepsModelInstance; ModelInstance navigatorObject; ModelInstance antModelInstance; ModelInstance antHillModelInstance; @@ -229,6 +229,17 @@ public: Texture* levelConnectorTexture; Texture* pauseButtonTexture; Texture* playButtonTexture; + Texture* rectangularPaletteTexture; + Texture* toolBrushTexture; + Texture* toolLensTexture; + Texture* toolForcepsTexture; + Texture* toolTrowelTexture; + + Texture* toolbarTopTexture; + Texture* toolbarBottomTexture; + Texture* toolbarMiddleTexture; + Texture* toolbarButtonRaisedTexture; + Texture* toolbarButtonDepressedTexture; // UI elements Vector4 selectedColor; @@ -274,6 +285,9 @@ public: UIImage* levelConnectors[9]; UIImage* pauseButtonImage; UIImage* playButtonImage; + UIImage* rectangularPaletteImage; + + Toolbar* toolbar; // Animation Tweener* tweener; @@ -314,6 +328,7 @@ public: Model* antModel; Model* antHillModel; Model* nestModel; + Model* forcepsModel; // Game variables Campaign campaign; @@ -328,6 +343,7 @@ public: bool cameraNestView; int toolIndex; bool simulationPaused; + bool forcepsClosed; // Debug LineBatcher* lineBatcher; diff --git a/src/game/biome.cpp b/src/game/biome.cpp index 7062387..2a5b9de 100644 --- a/src/game/biome.cpp +++ b/src/game/biome.cpp @@ -34,6 +34,8 @@ bool Biome::load() TextureLoader textureLoader; textureLoader.setCubemap(false); textureLoader.setMipmapChain(false); + textureLoader.setWrapS(true); + textureLoader.setWrapT(true); // Load soil horizon textures soilHorizonO = textureLoader.load(std::string("data/textures/") + soilHorizonOFilename); @@ -44,6 +46,10 @@ bool Biome::load() // Load diffuse cubemap textureLoader.setCubemap(true); textureLoader.setMipmapChain(false); + textureLoader.setWrapS(false); + textureLoader.setWrapT(false); + textureLoader.setWrapR(false); + std::string diffuseCubemapFilename = std::string("data/textures/") + cubemapName + std::string("-diffuse.png"); diffuseCubemap = textureLoader.load(diffuseCubemapFilename); if (!diffuseCubemap) diff --git a/src/game/navmesh.cpp b/src/game/navmesh.cpp index 6e56384..640301f 100644 --- a/src/game/navmesh.cpp +++ b/src/game/navmesh.cpp @@ -137,6 +137,7 @@ bool Navmesh::create(const std::vector& vertices, const std::vector::infinity()); + Vector3 max(-std::numeric_limits::infinity()); + + for (const Navmesh::Vertex* vertex: vertices) + { + min.x = std::min(min.x, vertex->position.x); + min.y = std::min(min.y, vertex->position.y); + min.z = std::min(min.z, vertex->position.z); + + max.x = std::max(max.x, vertex->position.x); + max.y = std::max(max.y, vertex->position.y); + max.z = std::max(max.z, vertex->position.z); + } + + bounds.setMin(min); + bounds.setMax(max); +} + bool Navmesh::readOBJ(std::istream* stream, const std::string& filename) { std::string line; @@ -553,3 +574,118 @@ void Navmesh::closestPointOnTriangle(const Vector3& p, const Navmesh::Triangle* *closestPoint = Vector3(1.0f - v - w, v, w); *closestEdge = nullptr; } + +std::tuple intersects(const Ray& ray, const Navmesh::Triangle* triangle) +{ + return ray.intersects(triangle->edge->vertex->position, triangle->edge->next->vertex->position, triangle->edge->previous->vertex->position); +} + +std::tuple intersects(const Ray& ray, const std::list& triangles) +{ + bool intersection = false; + float t0 = std::numeric_limits::infinity(); + float t1 = -std::numeric_limits::infinity(); + std::size_t index0 = triangles.size(); + std::size_t index1 = triangles.size(); + + for (const Navmesh::Triangle* triangle: triangles) + { + auto result = intersects(ray, triangle); + + if (std::get<0>(result)) + { + intersection = true; + + float t = std::get<1>(result); + float cosTheta = glm::dot(ray.direction, triangle->normal); + + if (cosTheta <= 0.0f) + { + // Front-facing + if (t < t0) + { + t0 = t; + index0 = triangle->index; + } + + } + else + { + // Back-facing + if (t > t1) + { + t1 = t; + index1 = triangle->index; + } + } + } + } + + return std::make_tuple(intersection, t0, t1, index0, index1); +} + +std::tuple intersects(const Ray& ray, const Navmesh& mesh) +{ + const std::vector& triangles = *mesh.getTriangles(); + + bool intersection = false; + float t0 = std::numeric_limits::infinity(); + float t1 = -std::numeric_limits::infinity(); + std::size_t index0 = triangles.size(); + std::size_t index1 = triangles.size(); + + for (std::size_t i = 0; i < triangles.size(); ++i) + { + const Navmesh::Triangle* triangle = triangles[i]; + const Vector3& a = triangle->edge->vertex->position; + const Vector3& b = triangle->edge->next->vertex->position; + const Vector3& c = triangle->edge->previous->vertex->position; + + auto result = ray.intersects(a, b, c); + + if (std::get<0>(result)) + { + intersection = true; + + float t = std::get<1>(result); + float cosTheta = glm::dot(ray.direction, triangle->normal); + + if (cosTheta <= 0.0f) + { + // Front-facing + t0 = std::min(t0, t); + index0 = i; + } + else + { + // Back-facing + t1 = std::max(t1, t); + index1 = i; + } + } + } + + return std::make_tuple(intersection, t0, t1, index0, index1); +} + +Octree* Navmesh::createOctree(std::size_t maxDepth) +{ + Octree* result = new Octree(bounds, maxDepth); + + for (Navmesh::Triangle* triangle: triangles) + { + Vector3 min; + min.x = std::min(triangle->edge->vertex->position.x, std::min(triangle->edge->next->vertex->position.x, triangle->edge->previous->vertex->position.x)); + min.y = std::min(triangle->edge->vertex->position.y, std::min(triangle->edge->next->vertex->position.y, triangle->edge->previous->vertex->position.y)); + min.z = std::min(triangle->edge->vertex->position.z, std::min(triangle->edge->next->vertex->position.z, triangle->edge->previous->vertex->position.z)); + + Vector3 max; + max.x = std::max(triangle->edge->vertex->position.x, std::max(triangle->edge->next->vertex->position.x, triangle->edge->previous->vertex->position.x)); + max.y = std::max(triangle->edge->vertex->position.y, std::max(triangle->edge->next->vertex->position.y, triangle->edge->previous->vertex->position.y)); + max.z = std::max(triangle->edge->vertex->position.z, std::max(triangle->edge->next->vertex->position.z, triangle->edge->previous->vertex->position.z)); + + result->insert(AABB(min, max), triangle); + } + + return result; +} diff --git a/src/game/navmesh.hpp b/src/game/navmesh.hpp index c1f0f55..910b551 100644 --- a/src/game/navmesh.hpp +++ b/src/game/navmesh.hpp @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -82,6 +83,11 @@ public: */ static void traverse(Navmesh::Triangle* startTriangle, const Vector3& startPosition, const Vector3& startVelocity, std::vector* traversal); + /** + * Creates an octree of navmesh triangles + */ + Octree* createOctree(std::size_t maxDepth); + /// Returns a pointer to the navmesh vertices const std::vector* getVertices() const; @@ -100,6 +106,9 @@ public: /// @copydoc Navmesh::getTriangles() const std::vector* getTriangles(); + /// Returns an AABB which contains this navmesh + const AABB& getBounds() const; + /** * Half-edge vertex which contains a pointer to its parent edge, a position vector, and an index. */ @@ -186,6 +195,11 @@ public: */ void calculateNormals(); + /** + * Calculates an AABB which contains the navmesh. + */ + void calculateBounds(); + private: /** * Reads Wavefront OBJ data from an input stream @@ -229,6 +243,7 @@ private: std::vector vertices; std::vector edges; std::vector triangles; + AABB bounds; }; inline const std::vector* Navmesh::getVertices() const @@ -261,4 +276,14 @@ inline std::vector* Navmesh::getTriangles() return &triangles; } +inline const AABB& Navmesh::getBounds() const +{ + return bounds; +} + +std::tuple intersects(const Ray& ray, const Navmesh::Triangle* triangle); +std::tuple intersects(const Ray& ray, const std::list& triangles); + +std::tuple intersects(const Ray& ray, const Navmesh& mesh); + #endif // NAVMESH_HPP diff --git a/src/game/terrain.cpp b/src/game/terrain.cpp index 87c5b69..7459309 100644 --- a/src/game/terrain.cpp +++ b/src/game/terrain.cpp @@ -1,5 +1,15 @@ #include "terrain.hpp" +Terrain::Terrain() +{ + surfaceOctree = nullptr; +} + +Terrain::~Terrain() +{ + delete surfaceOctree; +} + void Terrain::create(int columns, int rows, const Vector3& dimensions) { this->columns = columns; @@ -114,6 +124,12 @@ void Terrain::createSurface() // Add group to the model surfaceModel.addGroup(group); + + // Set model bounds + surfaceModel.setBounds(surfaceNavmesh.getBounds()); + + // Calculate octree + surfaceOctree = surfaceNavmesh.createOctree(5); } void Terrain::createSubsurface() @@ -366,6 +382,9 @@ void Terrain::createSubsurface() // Add group to the model subsurfaceModel.addGroup(group); + + // Set model bounds + subsurfaceModel.setBounds(subsurfaceNavmesh.getBounds()); } void Terrain::calculateSurfaceNormals() @@ -484,6 +503,10 @@ bool Terrain::load(const std::string& filename) surfaceNavmesh.calculateNormals(); subsurfaceNavmesh.calculateNormals(); + // Calculate navmesh bounds + surfaceNavmesh.calculateBounds(); + subsurfaceNavmesh.calculateBounds(); + // Calculate vertex normals calculateSurfaceNormals(); @@ -493,6 +516,14 @@ bool Terrain::load(const std::string& filename) glBindBuffer(GL_ARRAY_BUFFER, subsurfaceVBO); glBufferSubData(GL_ARRAY_BUFFER, 0, subsurfaceVertexCount * subsurfaceVertexSize * sizeof(float), subsurfaceVertexData); + // Update bounds + surfaceModel.setBounds(surfaceNavmesh.getBounds()); + subsurfaceModel.setBounds(subsurfaceNavmesh.getBounds()); + + // Calculate octree + delete surfaceOctree; + surfaceOctree = surfaceNavmesh.createOctree(5); + return true; } diff --git a/src/game/terrain.hpp b/src/game/terrain.hpp index d5784d6..9e08dcc 100644 --- a/src/game/terrain.hpp +++ b/src/game/terrain.hpp @@ -29,6 +29,9 @@ using namespace Emergent; class Terrain { public: + Terrain(); + ~Terrain(); + /** * Creates a flat terrain surface. * @@ -65,6 +68,8 @@ public: /// Returns the model representing the terrain subsurface. Model* getSubsurfaceModel(); + const Octree* getSurfaceOctree() const; + private: void createSurface(); void createSubsurface(); @@ -90,6 +95,7 @@ private: PhysicalMaterial surfaceMaterial; Model surfaceModel; Navmesh surfaceNavmesh; + Octree* surfaceOctree; // Subsurface std::size_t subsurfaceVertexSize; @@ -148,4 +154,9 @@ inline Model* Terrain::getSubsurfaceModel() return &subsurfaceModel; } +inline const Octree* Terrain::getSurfaceOctree() const +{ + return surfaceOctree; +} + #endif // TERRAIN_HPP diff --git a/src/states/play-state.cpp b/src/states/play-state.cpp index a0ef114..51442bb 100644 --- a/src/states/play-state.cpp +++ b/src/states/play-state.cpp @@ -22,6 +22,8 @@ #include "../camera-controller.hpp" #include "../game/colony.hpp" #include "../game/ant.hpp" +#include "../ui/toolbar.hpp" +#include PlayState::PlayState(Application* application): ApplicationState(application) @@ -41,6 +43,17 @@ void PlayState::enter() application->pauseButtonImage->setActive(false); application->playButtonImage->setVisible(false); application->playButtonImage->setActive(false); + application->rectangularPaletteImage->setVisible(true); + application->rectangularPaletteImage->setActive(true); + application->toolbar->getContainer()->setVisible(true); + application->toolbar->getContainer()->setActive(true); + + // Setup tools + application->forcepsClosed = false; + + // Add background + //application->backgroundLayer->addObject(&application->bgCamera); + //application->backgroundLayer->addObject(&application->bgBatch); // Create terrain model instances application->terrain.getSurfaceModel()->getGroup(0)->material = application->materialLoader->load("data/materials/debug-terrain-surface.mtl"); @@ -53,6 +66,9 @@ void PlayState::enter() application->defaultLayer->addObject(&terrainSurface); application->defaultLayer->addObject(&terrainSubsurface); + // Add forceps to scene + application->defaultLayer->addObject(&application->forcepsModelInstance); + // Spawn ants Navmesh* navmesh = application->terrain.getSurfaceNavmesh(); for (int i = 0; i < 50; ++i) @@ -82,15 +98,34 @@ void PlayState::enter() application->surfaceCam->update(0.0f); application->simulationPaused = false; + + application->mouse->addMouseButtonObserver(this); } void PlayState::execute() { - // Update colony - if (!application->simulationPaused) + + + + + /* + else { - application->colony->update(application->dt); + Plane plane; + plane.set(Vector3(0, 1, 0), Vector3(0.0f)); + auto result = pickingRay.intersects(plane); + pick = pickingRay.extrapolate(std::get<1>(result)); } + */ + + static float rotationTime = 0.0f; + float iconRotation = std::sin(rotationTime * 3.0f) * glm::radians(10.0f); + rotationTime += application->dt; + + + //application->blaImage->setRotation(iconRotation); + + // Move camera Vector2 movementVector(0.0f); @@ -111,16 +146,67 @@ void PlayState::execute() } // Zoom camera - float zoomFactor = application->surfaceCam->getFocalDistance() / 20.0f * application->dt / (1.0f / 60.0f); + float zoomFactor = application->surfaceCam->getFocalDistance() / 10.0f * application->dt / (1.0f / 60.0f); if (application->cameraZoomIn.isTriggered()) application->surfaceCam->zoom(zoomFactor * application->cameraZoomIn.getCurrentValue()); if (application->cameraZoomOut.isTriggered()) application->surfaceCam->zoom(-zoomFactor * application->cameraZoomOut.getCurrentValue()); - application->surfaceCam->update(application->dt); + + // Rotate camera + if (application->cameraRotateCW.isTriggered() && !application->cameraRotateCW.wasTriggered()) + { + application->surfaceCam->rotate(glm::radians(-45.0f)); + } + if (application->cameraRotateCCW.isTriggered() && !application->cameraRotateCCW.wasTriggered()) + { + application->surfaceCam->rotate(glm::radians(45.0f)); + } // Update camera application->surfaceCam->update(application->dt); + // Picking + glm::ivec2 mousePosition = application->mouse->getCurrentPosition(); + mousePosition.y = application->height - mousePosition.y; + Vector4 viewport(0.0f, 0.0f, application->width, application->height); + 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); + + auto result = intersects(pickingRay, triangles); + if (std::get<0>(result)) + { + pick = pickingRay.extrapolate(std::get<1>(result)); + + std::size_t triangleIndex = std::get<3>(result); + const Navmesh::Triangle* triangle = (*application->terrain.getSurfaceNavmesh()->getTriangles())[triangleIndex]; + + float forcepsDistance = (application->forcepsClosed) ? 0.0f : 0.5f; + + //Quaternion rotation = glm::rotation(Vector3(0, 1, 0), triangle->normal); + Quaternion rotation = glm::angleAxis(application->surfaceCam->getAzimuth(), Vector3(0, 1, 0)) * + glm::angleAxis(glm::radians(15.0f), Vector3(0, 0, -1)); + + Vector3 translation = pick + rotation * Vector3(0, forcepsDistance, 0); + + // Set tool position + application->forcepsModelInstance.setTranslation(translation); + application->forcepsModelInstance.setRotation(rotation); + } + + // Update colony + if (!application->simulationPaused) + { + application->colony->update(application->dt); + } + // Pause simulation if (application->togglePause.isTriggered() && !application->togglePause.wasTriggered()) { @@ -137,12 +223,25 @@ void PlayState::execute() void PlayState::exit() { + // Remove background + //application->backgroundLayer->removeObject(&application->bgCamera); + //application->backgroundLayer->removeObject(&application->bgBatch); + + application->mouse->removeMouseButtonObserver(this); } void PlayState::mouseButtonPressed(int button, int x, int y) { + if (button == 1) + { + application->forcepsClosed = true; + } } void PlayState::mouseButtonReleased(int button, int x, int y) { + if (button == 1) + { + application->forcepsClosed = false; + } } diff --git a/src/ui/toolbar.cpp b/src/ui/toolbar.cpp new file mode 100644 index 0000000..18eef7c --- /dev/null +++ b/src/ui/toolbar.cpp @@ -0,0 +1,142 @@ +#include "toolbar.hpp" + +Toolbar::Toolbar(): + toolbarTopTexture(nullptr), + toolbarBottomTexture(nullptr), + toolbarMiddleTexture(nullptr), + buttonRaisedTexture(nullptr), + buttonDepressedTexture(nullptr), + depressedButtonIndex(0) +{ + toolbarContainer.addChild(&toolbarTopImage); + toolbarContainer.addChild(&toolbarBottomImage); + toolbarContainer.addChild(&toolbarMiddleImage); +} + +void Toolbar::setToolbarTopTexture(Texture* texture) +{ + toolbarTopTexture = texture; + toolbarTopImage.setTexture(toolbarTopTexture); +} + +void Toolbar::setToolbarBottomTexture(Texture* texture) +{ + toolbarBottomTexture = texture; + toolbarBottomImage.setTexture(toolbarBottomTexture); +} + +void Toolbar::setToolbarMiddleTexture(Texture* texture) +{ + toolbarMiddleTexture = texture; + toolbarMiddleImage.setTexture(toolbarMiddleTexture); +} + +void Toolbar::setButtonRaisedTexture(Texture* texture) +{ + buttonRaisedTexture = texture; +} + +void Toolbar::setButtonDepressedTexture(Texture* texture) +{ + buttonDepressedTexture = texture; +} + +void Toolbar::resize() +{ + int toolbarWidth = toolbarMiddleTexture->getWidth(); + int toolbarHeight = toolbarTopTexture->getHeight() + toolbarBottomTexture->getHeight() + toolbarMiddleTexture->getHeight() * std::max(0, (int)buttons.size() - 1); + float borderSpacing = 8.0f; + float buttonOffsetY = ((toolbarTopTexture->getHeight() + toolbarBottomTexture->getHeight()) - buttonRaisedTexture->getHeight()) / 2; + + // Resize toolbar + toolbarContainer.setAnchor(Vector2(0.0f, 0.5f)); + toolbarContainer.setDimensions(Vector2(toolbarWidth, toolbarHeight)); + toolbarContainer.setTranslation(Vector2(borderSpacing, 0.0f)); + + toolbarTopImage.setAnchor(Vector2(0.0f, 0.0f)); + toolbarTopImage.setDimensions(Vector2(toolbarTopTexture->getWidth(), toolbarTopTexture->getHeight())); + toolbarTopImage.setTranslation(Vector2(0.0f, 0.0f)); + + toolbarBottomImage.setAnchor(Vector2(0.0f, 1.0f)); + toolbarBottomImage.setDimensions(Vector2(toolbarBottomTexture->getWidth(), toolbarBottomTexture->getHeight())); + toolbarBottomImage.setTranslation(Vector2(0.0f, 0.0f)); + + toolbarMiddleImage.setAnchor(Vector2(0.0f, 0.5f)); + toolbarMiddleImage.setDimensions(Vector2(toolbarMiddleTexture->getWidth(), toolbarMiddleTexture->getHeight() * std::max(0, (int)buttons.size() - 1))); + toolbarMiddleImage.setTranslation(Vector2(0.0f, 0.0f)); + + // Resize buttons and icons + for (std::size_t i = 0; i < buttons.size(); ++i) + { + UIImage* button = buttons[i]; + button->setAnchor(Vector2(0.5f, 0.0f)); + button->setDimensions(Vector2(buttonRaisedTexture->getWidth(), buttonRaisedTexture->getHeight())); + button->setTranslation(Vector2(0.0f, buttonOffsetY + i * toolbarMiddleTexture->getHeight())); + + UIImage* icon = icons[i]; + icon->setAnchor(Vector2(0.5f, 0.5f)); + icon->setDimensions(Vector2(icon->getTexture()->getWidth(), icon->getTexture()->getHeight())); + icon->setTranslation(Vector2(0.0f, 0.0f)); + } +} + +void Toolbar::addButton(Texture* iconTexture, std::function pressCallback, std::function releaseCallback) +{ + if (depressedButtonIndex == buttons.size()) + { + ++depressedButtonIndex; + } + + // Allocate new button and icon + UIImage* button = new UIImage(); + button->setTexture(buttonRaisedTexture); + buttons.push_back(button); + + UIImage* icon = new UIImage(); + icon->setTexture(iconTexture); + icons.push_back(icon); + + // Add button to toolbar + toolbarContainer.addChild(button); + + // Add icon to button + button->addChild(icon); + + // Setup callbacks + std::size_t buttonIndex = buttons.size() - 1; + //button->setMouseOverCallback(std::bind(Toolbar::selectMenuItem, this, buttonIndex)); + //button->setMouseMovedCallback(std::bind(Toolbar::selectMenuItem, this, buttonIndex)); + button->setMousePressedCallback(std::bind(Toolbar::pressButton, this, buttonIndex)); + + pressCallbacks.push_back(pressCallback); + releaseCallbacks.push_back(releaseCallback); +} + +void Toolbar::pressButton(std::size_t index) +{ + releaseButton(depressedButtonIndex); + + if (index == depressedButtonIndex) + { + depressedButtonIndex = buttons.size(); + } + else + { + depressedButtonIndex = index; + buttons[index]->setTexture(buttonDepressedTexture); + icons[index]->setTranslation(Vector2(2.0f, 2.0f)); + + pressCallbacks[index](); + } +} + +void Toolbar::releaseButton(std::size_t index) +{ + if (index < buttons.size()) + { + buttons[index]->setTexture(buttonRaisedTexture); + icons[index]->setTranslation(Vector2(0.0f, 0.0f)); + + releaseCallbacks[index](); + } +} diff --git a/src/ui/toolbar.hpp b/src/ui/toolbar.hpp new file mode 100644 index 0000000..53caf1b --- /dev/null +++ b/src/ui/toolbar.hpp @@ -0,0 +1,61 @@ +#ifndef TOOLBAR_HPP +#define TOOLBAR_HPP + +#include "ui.hpp" +#include + +#include + +using namespace Emergent; + +class Toolbar +{ +public: + Toolbar(); + + void setToolbarTopTexture(Texture* texture); + void setToolbarBottomTexture(Texture* texture); + void setToolbarMiddleTexture(Texture* texture); + void setButtonRaisedTexture(Texture* texture); + void setButtonDepressedTexture(Texture* texture); + + void resize(); + + void addButton(Texture* iconTexture, std::function pressCallback, std::function releaseCallback); + + void pressButton(std::size_t index); + void releaseButton(std::size_t index); + + const UIContainer* getContainer() const; + UIContainer* getContainer(); + +private: + Texture* toolbarTopTexture; + Texture* toolbarBottomTexture; + Texture* toolbarMiddleTexture; + Texture* buttonRaisedTexture; + Texture* buttonDepressedTexture; + + UIContainer toolbarContainer; + UIImage toolbarTopImage; + UIImage toolbarBottomImage; + UIImage toolbarMiddleImage; + std::vector buttons; + std::vector icons; + std::vector> pressCallbacks; + std::vector> releaseCallbacks; + + std::size_t depressedButtonIndex; +}; + +inline const UIContainer* Toolbar::getContainer() const +{ + return &toolbarContainer; +} + +inline UIContainer* Toolbar::getContainer() +{ + return &toolbarContainer; +} + +#endif // TOOLBAR_HPP diff --git a/src/ui/ui.cpp b/src/ui/ui.cpp index d42bd47..c648fc1 100644 --- a/src/ui/ui.cpp +++ b/src/ui/ui.cpp @@ -33,6 +33,7 @@ UIElement::UIElement(): layer(0), origin(0.0f), translation(0.0f), + rotation(0.0f), dimensions(0.0f), position(0.0f), bounds(position, position), @@ -384,6 +385,12 @@ void UIBatcher::batchImage(BillboardBatch* result, const UIImage* image) Billboard* billboard = result->getBillboard(index); billboard->setDimensions(image->getDimensions()); billboard->setTranslation(translation); + + if (image->getRotation() != 0.0f) + { + billboard->setRotation(glm::angleAxis(image->getRotation(), Vector3(0, 0, -1.0f))); + } + billboard->setTextureCoordinates(image->getTextureBounds().getMin(), image->getTextureBounds().getMax()); billboard->setTintColor(image->getColor()); diff --git a/src/ui/ui.hpp b/src/ui/ui.hpp index 738cec9..0eaaee0 100644 --- a/src/ui/ui.hpp +++ b/src/ui/ui.hpp @@ -62,6 +62,9 @@ public: /// Sets the translation of the element, relative to its position in its parent element void setTranslation(const Vector2& translation); + /// Sets the rotation of the element + void setRotation(float angle); + /// Sets the dimensions of the element void setDimensions(const Vector2& dimensions); @@ -113,6 +116,9 @@ public: /// Returns the translation of this element, relative to its parent const Vector2& getTranslation() const; + /// Returns the rotation of this element + float getRotation() const; + /// Returns the dimensions of this element const Vector2& getDimensions() const; @@ -164,6 +170,7 @@ private: int layer; Vector2 origin; Vector2 translation; + float rotation; Vector2 dimensions; Vector2 position; Rect bounds; @@ -199,6 +206,11 @@ inline void UIElement::setTranslation(const Vector2& translation) this->translation = translation; } +inline void UIElement::setRotation(float angle) +{ + this->rotation = angle; +} + inline void UIElement::setDimensions(const Vector2& dimensions) { this->dimensions = dimensions; @@ -279,6 +291,11 @@ inline const Vector2& UIElement::getTranslation() const return translation; } +inline float UIElement::getRotation() const +{ + return rotation; +} + inline const Vector2& UIElement::getDimensions() const { return dimensions;