From 24c8b32d4cefa0bf65bb51e98a58c643058139a1 Mon Sep 17 00:00:00 2001
From: Nelsson Huotari <unelsson@gmail.com>
Date: Fri, 7 Feb 2020 19:04:28 +0200
Subject: [PATCH] Implement brush outline for terrainshapemode

---
 apps/opencs/CMakeLists.txt                    |   2 +-
 apps/opencs/view/render/brushdraw.cpp         | 136 ++++++++++++++++++
 apps/opencs/view/render/brushdraw.hpp         |  31 ++++
 apps/opencs/view/render/editmode.cpp          |   2 +
 apps/opencs/view/render/editmode.hpp          |   2 +
 apps/opencs/view/render/terrainshapemode.cpp  |  17 +++
 apps/opencs/view/render/terrainshapemode.hpp  |   4 +
 .../opencs/view/render/terraintexturemode.cpp |   2 +
 .../opencs/view/render/terraintexturemode.hpp |   2 +
 apps/opencs/view/render/worldspacewidget.cpp  |   2 +
 10 files changed, 199 insertions(+), 1 deletion(-)
 create mode 100644 apps/opencs/view/render/brushdraw.cpp
 create mode 100644 apps/opencs/view/render/brushdraw.hpp

diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt
index af8bf8d559..6d0f2ad9f9 100644
--- a/apps/opencs/CMakeLists.txt
+++ b/apps/opencs/CMakeLists.txt
@@ -89,7 +89,7 @@ opencs_units (view/render
     scenewidget worldspacewidget pagedworldspacewidget unpagedworldspacewidget
     previewwidget editmode instancemode instanceselectionmode instancemovemode
     orbitcameramode pathgridmode selectionmode pathgridselectionmode cameracontroller
-    cellwater terraintexturemode actor terrainselection terrainshapemode
+    cellwater terraintexturemode actor terrainselection terrainshapemode brushdraw
     )
 
 opencs_units_noqt (view/render
diff --git a/apps/opencs/view/render/brushdraw.cpp b/apps/opencs/view/render/brushdraw.cpp
new file mode 100644
index 0000000000..a2874bd31e
--- /dev/null
+++ b/apps/opencs/view/render/brushdraw.cpp
@@ -0,0 +1,136 @@
+#include "brushdraw.hpp"
+
+#include <osg/Group>
+#include <osg/Geometry>
+#include <osg/Array>
+#include <osg/BlendFunc>
+#include <osg/CullFace>
+#include <osg/Material>
+
+#include <osgUtil/LineSegmentIntersector>
+
+#include "mask.hpp"
+
+CSVRender::BrushDraw::BrushDraw(osg::Group* parentNode) :
+    mParentNode(parentNode)
+{
+    mBrushDrawNode = new osg::Group();
+    mGeometry = new osg::Geometry();
+    mBrushDrawNode->addChild(mGeometry);
+    mParentNode->addChild(mBrushDrawNode);
+}
+
+CSVRender::BrushDraw::~BrushDraw()
+{
+    if (mBrushDrawNode->containsNode(mGeometry)) mBrushDrawNode->removeChild(mGeometry);
+    if (mParentNode->containsNode(mBrushDrawNode)) mParentNode->removeChild(mBrushDrawNode);
+}
+
+float CSVRender::BrushDraw::getIntersectionHeight (const osg::Vec3d& point)
+{
+    osg::Vec3d start = point;
+    osg::Vec3d end = point;
+    start.z() += 8000.0f; // these numbers need fixing
+    end.z() -= 8000.0f;
+    osg::Vec3d direction = end - start;
+
+    // Get intersection
+    osg::ref_ptr<osgUtil::LineSegmentIntersector> intersector (new osgUtil::LineSegmentIntersector(
+        osgUtil::Intersector::MODEL, start, end) );
+    intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::NO_LIMIT);
+    osgUtil::IntersectionVisitor visitor(intersector);
+
+    visitor.setTraversalMask(Mask_Terrain);
+
+    mParentNode->accept(visitor);
+
+    for (osgUtil::LineSegmentIntersector::Intersections::iterator it = intersector->getIntersections().begin();
+         it != intersector->getIntersections().end(); ++it)
+    {
+        osgUtil::LineSegmentIntersector::Intersection intersection = *it;
+
+        // reject back-facing polygons
+        if (direction * intersection.getWorldIntersectNormal() > 0)
+        {
+            continue;
+        }
+
+        return intersection.getWorldIntersectPoint().z();
+    }
+    return 0.0f;
+}
+
+
+void CSVRender::BrushDraw::buildGeometry(const float& radius, const osg::Vec3d& point, int amountOfPoints)
+{
+    osg::ref_ptr<osg::Geometry> geom = new osg::Geometry();
+    osg::ref_ptr<osg::Vec3Array> vertices (new osg::Vec3Array());
+    osg::ref_ptr<osg::Vec4Array> colors (new osg::Vec4Array());
+    const float step ((osg::PI * 2.0f) / static_cast<float>(amountOfPoints));
+
+    for (int i = 0; i < amountOfPoints + 2; i++)
+    {
+        float angle (static_cast<float>(i) * step);
+        vertices->push_back(osg::Vec3d(
+            point.x() + radius * cosf(angle),
+            point.y() + radius * sinf(angle),
+            getIntersectionHeight(osg::Vec3d(
+                point.x() + radius * cosf(angle),
+                point.y() + radius * sinf(angle),
+                point.z()) )));
+        colors->push_back(osg::Vec4f(
+            50.0f,
+            50.0f,
+            50.0f,
+            100.0f));
+        angle = static_cast<float>(i + 1) * step;
+        vertices->push_back(osg::Vec3d(
+            point.x() + radius * cosf(angle),
+            point.y() + radius * sinf(angle),
+            getIntersectionHeight(osg::Vec3d(
+                point.x() + radius * cosf(angle),
+                point.y() + radius * sinf(angle),
+                point.z()) ) + 200.0f));
+        colors->push_back(osg::Vec4f(
+            50.0f,
+            50.0f,
+            50.0f,
+            100.0f));
+    }
+
+    geom->setVertexArray(vertices);
+    geom->setColorArray(colors, osg::Array::BIND_PER_VERTEX);
+    geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::TRIANGLE_STRIP, 0, (amountOfPoints + 2) * 2 - 2));
+    mGeometry = geom;
+}
+
+void CSVRender::BrushDraw::update(osg::Vec3d point, int brushSize)
+{
+    if (mBrushDrawNode->containsNode(mGeometry)) mBrushDrawNode->removeChild(mGeometry);
+    mBrushDrawNode->setNodeMask (Mask_EditModeCursor);
+    float radius = static_cast<float>(brushSize * mLandSizeFactor);
+    int amountOfPoints = (osg::PI * 2.0f) * radius / 20;
+
+    buildGeometry(radius, point, amountOfPoints);
+
+    osg::BlendFunc* blendFunc = new osg::BlendFunc(osg::BlendFunc::SRC_ALPHA, osg::BlendFunc::ONE_MINUS_SRC_ALPHA);
+    mGeometry->getOrCreateStateSet()->setAttributeAndModes(blendFunc);
+
+    mGeometry->getOrCreateStateSet()->setMode( GL_CULL_FACE, osg::StateAttribute::OFF );
+
+    osg::ref_ptr<osg::Material> material = new osg::Material;
+    material->setColorMode(osg::Material::AMBIENT);
+    material->setAmbient (osg::Material::FRONT_AND_BACK, osg::Vec4(0.3, 0.3, 0.3, 1));
+    material->setAlpha(osg::Material::FRONT_AND_BACK, 0.5);
+
+    mGeometry->getOrCreateStateSet()->setAttributeAndModes(material.get(), osg::StateAttribute::ON);
+    mGeometry->getOrCreateStateSet()->setMode(GL_BLEND, osg::StateAttribute::ON);
+    mGeometry->getOrCreateStateSet()->setRenderingHint (osg::StateSet::TRANSPARENT_BIN);
+
+    mBrushDrawNode->addChild(mGeometry);
+}
+
+void CSVRender::BrushDraw::hide()
+{
+    if (mBrushDrawNode->containsNode(mGeometry)) mBrushDrawNode->removeChild(mGeometry);
+}
diff --git a/apps/opencs/view/render/brushdraw.hpp b/apps/opencs/view/render/brushdraw.hpp
new file mode 100644
index 0000000000..5e0a095cef
--- /dev/null
+++ b/apps/opencs/view/render/brushdraw.hpp
@@ -0,0 +1,31 @@
+#ifndef CSV_RENDER_BRUSHDRAW_H
+#define CSV_RENDER_BRUSHDRAW_H
+
+#include <osg/Group>
+#include <osg/Geometry>
+
+#include <components/esm/loadland.hpp>
+
+namespace CSVRender
+{
+    class BrushDraw
+    {
+        public:
+            BrushDraw(osg::Group* parentNode);
+            ~BrushDraw();
+
+            void update(osg::Vec3d point, int brushSize);
+            void hide();
+
+        private:
+            void buildGeometry(const float& radius, const osg::Vec3d& point, int amountOfPoints);
+            float getIntersectionHeight (const osg::Vec3d& point);
+
+            osg::Group* mParentNode;
+            osg::ref_ptr<osg::Group> mBrushDrawNode;
+            osg::ref_ptr<osg::Geometry> mGeometry;
+            float mLandSizeFactor = ESM::Land::REAL_SIZE / ESM::Land::LAND_SIZE / 2;
+    };
+}
+
+#endif
diff --git a/apps/opencs/view/render/editmode.cpp b/apps/opencs/view/render/editmode.cpp
index 03451bc1b1..ca4aa0fd55 100644
--- a/apps/opencs/view/render/editmode.cpp
+++ b/apps/opencs/view/render/editmode.cpp
@@ -73,6 +73,8 @@ void CSVRender::EditMode::dropEvent (QDropEvent *event) {}
 
 void CSVRender::EditMode::dragMoveEvent (QDragMoveEvent *event) {}
 
+void CSVRender::EditMode::mouseMoveEvent (QMouseEvent *event) {}
+
 int CSVRender::EditMode::getSubMode() const
 {
     return -1;
diff --git a/apps/opencs/view/render/editmode.hpp b/apps/opencs/view/render/editmode.hpp
index 9f3b289578..9115943273 100644
--- a/apps/opencs/view/render/editmode.hpp
+++ b/apps/opencs/view/render/editmode.hpp
@@ -98,6 +98,8 @@ namespace CSVRender
             /// Default-implementation: ignored
             virtual void dragMoveEvent (QDragMoveEvent *event);
 
+            virtual void mouseMoveEvent (QMouseEvent *event);
+
             /// Default: return -1
             virtual int getSubMode() const;
     };
diff --git a/apps/opencs/view/render/terrainshapemode.cpp b/apps/opencs/view/render/terrainshapemode.cpp
index 2a6d7f33f4..53fe92f8e2 100644
--- a/apps/opencs/view/render/terrainshapemode.cpp
+++ b/apps/opencs/view/render/terrainshapemode.cpp
@@ -37,6 +37,7 @@
 #include "../../model/world/tablemimedata.hpp"
 #include "../../model/world/universalid.hpp"
 
+#include "brushdraw.hpp"
 #include "editmode.hpp"
 #include "pagedworldspacewidget.hpp"
 #include "tagbase.hpp"
@@ -49,6 +50,12 @@ CSVRender::TerrainShapeMode::TerrainShapeMode (WorldspaceWidget *worldspaceWidge
 {
 }
 
+CSVRender::TerrainShapeMode::~TerrainShapeMode ()
+{
+    if (mBrushDraw)
+        mBrushDraw.reset();
+}
+
 void CSVRender::TerrainShapeMode::activate(CSVWidget::SceneToolbar* toolbar)
 {
     if (!mTerrainShapeSelection)
@@ -67,6 +74,9 @@ void CSVRender::TerrainShapeMode::activate(CSVWidget::SceneToolbar* toolbar)
         connect(mShapeBrushScenetool->mShapeBrushWindow->mToolStrengthSlider, SIGNAL(valueChanged(int)), this, SLOT(setShapeEditToolStrength(int)));
     }
 
+    if (!mBrushDraw)
+        mBrushDraw.reset(new BrushDraw(mParentNode));
+
     EditMode::activate(toolbar);
     toolbar->addTool (mShapeBrushScenetool);
 }
@@ -1382,6 +1392,13 @@ void CSVRender::TerrainShapeMode::dragMoveEvent (QDragMoveEvent *event)
 {
 }
 
+void CSVRender::TerrainShapeMode::mouseMoveEvent (QMouseEvent *event)
+{
+    WorldspaceHitResult hit = getWorldspaceWidget().mousePick(event->pos(), getInteractionMask());
+    if (hit.hit && mBrushDraw && !(mShapeEditTool == ShapeEditTool_Drag && mIsEditing)) mBrushDraw->update(hit.worldPos, mBrushSize);
+    if (!hit.hit && !(mShapeEditTool == ShapeEditTool_Drag && mIsEditing)) mBrushDraw->hide();
+}
+
 void CSVRender::TerrainShapeMode::setBrushSize(int brushSize)
 {
     mBrushSize = brushSize;
diff --git a/apps/opencs/view/render/terrainshapemode.hpp b/apps/opencs/view/render/terrainshapemode.hpp
index 68f2fbf9da..fb80e79af4 100644
--- a/apps/opencs/view/render/terrainshapemode.hpp
+++ b/apps/opencs/view/render/terrainshapemode.hpp
@@ -19,6 +19,7 @@
 #include "../widget/brushshapes.hpp"
 #endif
 
+#include "brushdraw.hpp"
 #include "terrainselection.hpp"
 
 namespace CSVWidget
@@ -57,6 +58,7 @@ namespace CSVRender
 
             /// Editmode for terrain shape grid
             TerrainShapeMode(WorldspaceWidget*, osg::Group* parentNode, QWidget* parent = nullptr);
+            ~TerrainShapeMode();
 
             void primaryOpenPressed (const WorldspaceHitResult& hit) final;
 
@@ -89,6 +91,7 @@ namespace CSVRender
 
             void dragWheel (int diff, double speedFactor) final;
             void dragMoveEvent (QDragMoveEvent *event) final;
+            void mouseMoveEvent (QMouseEvent *event) final;
 
         private:
 
@@ -168,6 +171,7 @@ namespace CSVRender
             std::string mBrushTexture;
             int mBrushSize = 1;
             CSVWidget::BrushShape mBrushShape = CSVWidget::BrushShape_Point;
+            std::unique_ptr<BrushDraw> mBrushDraw;
             std::vector<std::pair<int, int>> mCustomBrushShape;
             CSVWidget::SceneToolShapeBrush *mShapeBrushScenetool = nullptr;
             int mDragMode = InteractionType_None;
diff --git a/apps/opencs/view/render/terraintexturemode.cpp b/apps/opencs/view/render/terraintexturemode.cpp
index efdb600b87..e25d65a892 100644
--- a/apps/opencs/view/render/terraintexturemode.cpp
+++ b/apps/opencs/view/render/terraintexturemode.cpp
@@ -707,6 +707,8 @@ void CSVRender::TerrainTextureMode::dragMoveEvent (QDragMoveEvent *event)
 {
 }
 
+void CSVRender::TerrainTextureMode::mouseMoveEvent (QMouseEvent *event) {}
+
 void CSVRender::TerrainTextureMode::setBrushSize(int brushSize)
 {
     mBrushSize = brushSize;
diff --git a/apps/opencs/view/render/terraintexturemode.hpp b/apps/opencs/view/render/terraintexturemode.hpp
index 4176abefea..3b94097ee9 100644
--- a/apps/opencs/view/render/terraintexturemode.hpp
+++ b/apps/opencs/view/render/terraintexturemode.hpp
@@ -81,6 +81,8 @@ namespace CSVRender
             void dragWheel (int diff, double speedFactor) final;
             void dragMoveEvent (QDragMoveEvent *event) final;
 
+            void mouseMoveEvent (QMouseEvent *event) final;
+
         private:
             /// \brief Handle brush mechanics, maths regarding worldspace hit etc.
             void editTerrainTextureGrid (const WorldspaceHitResult& hit);
diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp
index 6ab4b041b9..4755de97bf 100644
--- a/apps/opencs/view/render/worldspacewidget.cpp
+++ b/apps/opencs/view/render/worldspacewidget.cpp
@@ -613,6 +613,8 @@ void CSVRender::WorldspaceWidget::updateOverlay()
 
 void CSVRender::WorldspaceWidget::mouseMoveEvent (QMouseEvent *event)
 {
+    dynamic_cast<CSVRender::EditMode&> (*mEditMode->getCurrent()).mouseMoveEvent (event);
+
     if (mDragging)
     {
         int diffX = event->x() - mDragX;