Skip to content

File CutCircularSawFeedback.cpp

File List > AIAC > Feedback > CutCircularSawFeedback.cpp

Go to the documentation of this file

// #####################################################################
// >>>>>>>>>>>>>>>>>>>>> BEGINNING OF LEGAL NOTICE >>>>>>>>>>>>>>>>>>>>>
//######################################################################
//
// This source file, along with its associated content, was authored by
// Andrea Settimi, Hong-Bin Yang, Naravich Chutisilp, and numerous other
// contributors. The code was originally developed at the Laboratory for
// Timber Construction (IBOIS, director: Prof. Yves Weinand) at the School of 
// Architecture, Civil and Environmental Engineering (ENAC) at the Swiss
// Federal Institute of Technology in Lausanne (EPFL) for the Doctoral
// Research "Augmented Carpentry" (PhD researcher: Andrea Settimi,
// co-director: Dr. Julien Gamerro, director: Prof. Yves Weinand).
//
// Although the entire repository is distributed under the GPL license,
// these particular source files may also be used under the terms of the
// MIT license. By accessing or using this file, you agree to the following:
//
// 1. You may reproduce, modify, and distribute this file in accordance
//    with the terms of the MIT license.
// 2. You must retain this legal notice in all copies or substantial
//    portions of this file.
// 3. This file is provided "AS IS," without any express or implied
//    warranties, including but not limited to the implied warranties of
//    merchantability and fitness for a particular purpose.
//
// If you cannot or will not comply with the above conditions, you are
// not permitted to use this file. By proceeding, you acknowledge and
// accept all terms and conditions herein.
//
//######################################################################
// <<<<<<<<<<<<<<<<<<<<<<< END OF LEGAL NOTICE <<<<<<<<<<<<<<<<<<<<<<<<
// #####################################################################
//


#include "AIAC/Application.h"
#include "CutCircularSawFeedback.h"

#include <sstream>
#include <iomanip>
#include <climits> // For INT_MIN and INT_MAX
#include <cmath> // For std::isfinite☺


namespace AIAC
{
    CutCircularSawDepthVisualizer::CutCircularSawDepthVisualizer()
    {
        m_LineDepth = GOLine::Add(GOPoint(0.f, 0.f, 0.f), GOPoint(0.f, 0.f, 0.f));
        m_TxtDepth = GOText::Add("0.0", GOPoint(0.f, 0.f, 0.f));
        m_PtBlade2ThicknessLineA = GOPoint::Add(GOPoint(0.f, 0.f, 0.f));
        m_PtBlade2ThicknessLineB = GOPoint::Add(GOPoint(0.f, 0.f, 0.f));

        m_LineDepth->SetColor(GOColor::PINK_TRANSP);
        m_TxtDepth->SetColor(GOColor::YELLOW);
        m_PtBlade2ThicknessLineA->SetColor(GOColor::PURPLE_TRANSP07);
        m_PtBlade2ThicknessLineB->SetColor(GOColor::PINK_TRANSP07);

        m_PtBlade2ThicknessLineA->SetWeight(GOWeight::MediumThick);
        m_PtBlade2ThicknessLineB->SetWeight(GOWeight::MediumThick);

        m_TxtDepth->SetTextSize(GOTextSize::BitSmall);

        m_AllPrimitives.push_back(m_LineDepth);
        m_AllPrimitives.push_back(m_TxtDepth);
        m_AllPrimitives.push_back(m_PtBlade2ThicknessLineA);
        m_AllPrimitives.push_back(m_PtBlade2ThicknessLineB);

        Deactivate();
    }

    CutCircularSawPositionStartVisualizer::CutCircularSawPositionStartVisualizer()
    {
        this->m_LineDistStart = GOLine::Add(GOPoint(0.f, 0.f, 0.f), GOPoint(0.f, 0.f, 0.f), GOWeight::Thick);
        this->m_TxtDistStart = GOText::Add("0.0", GOPoint(0.f, 0.f, 0.f));
        this->m_LineToBottomPt = GOLine::Add(GOPoint(0.f, 0.f, 0.f), GOPoint(0.f, 0.f, 0.f));

        this->m_LineDistStart->SetColor(GOColor::YELLOW);
        this->m_TxtDistStart->SetColor(GOColor::WHITE);
        this->m_LineToBottomPt->SetColor(GOColor::YELLOW);
        this->m_LineToBottomPt->SetVisibility(false);  // only for debugging

        this->m_TxtDistStart->SetTextSize(GOTextSize::BitSmall);

        this->m_AllPrimitives.push_back(m_LineDistStart);
        this->m_AllPrimitives.push_back(m_TxtDistStart);

        Deactivate();
    }

    CutCircularOrientationVisualizer::CutCircularOrientationVisualizer()
    {
        m_LineFaceNormal = GOLine::Add(GOPoint(0.f, 0.f, 0.f), GOPoint(0.f, 0.f, 0.f));
        m_LineBladeNormal = GOLine::Add(GOPoint(0.f, 0.f, 0.f), GOPoint(0.f, 0.f, 0.f));
        m_LineDebugA = GOLine::Add(GOPoint(0.f, 0.f, 0.f), GOPoint(0.f, 0.f, 0.f));
        m_LineDebugB = GOLine::Add(GOPoint(0.f, 0.f, 0.f), GOPoint(0.f, 0.f, 0.f));
        m_LineDebugC = GOLine::Add(GOPoint(0.f, 0.f, 0.f), GOPoint(0.f, 0.f, 0.f));
        m_LineDebugD = GOLine::Add(GOPoint(0.f, 0.f, 0.f), GOPoint(0.f, 0.f, 0.f));
        m_LineDebugE = GOLine::Add(GOPoint(0.f, 0.f, 0.f), GOPoint(0.f, 0.f, 0.f));
        m_LinePitchFeed = GOLine::Add(GOPoint(0.f, 0.f, 0.f), GOPoint(0.f, 0.f, 0.f), GOWeight::ExtraThick);
        m_GuideTxtRollPitch = GOText::Add("0.0", GOPoint(0.f, 0.f, 0.f));

        m_LineFaceNormal->SetColor(GOColor::BLUE);
        m_LineBladeNormal->SetColor(GOColor::MAGENTA);
        m_LineDebugA->SetColor(GOColor::ORANGE);
        m_LineDebugB->SetColor(GOColor::GREEN);
        m_LineDebugC->SetColor(GOColor::RED);
        m_LineDebugD->SetColor(GOColor::YELLOW);
        m_LineDebugE->SetColor(GOColor::WHITE);
        m_LinePitchFeed->SetColor(GOColor::RED);
        m_GuideTxtRollPitch->SetColor(GOColor::WHITE);

        m_GuideTxtRollPitch->SetTextSize(GOTextSize::BitSmall);

        m_LineFaceNormal->SetVisibility(false);
        m_LineBladeNormal->SetVisibility(false);
        m_LineDebugA->SetVisibility(false);
        m_LineDebugB->SetVisibility(false);
        m_LineDebugC->SetVisibility(false);
        m_LineDebugD->SetVisibility(false);
        m_LineDebugE->SetVisibility(false);

        m_AllPrimitives.push_back(m_LinePitchFeed);
        m_AllPrimitives.push_back(m_GuideTxtRollPitch);

        Deactivate();
    }

    void CutCircularSawFeedback::Update() {
        m_Cut = dynamic_cast<TimberInfo::Cut*>(AC_FF_COMP);

        if(m_Cut->IsSingleFace()) {
            this->EnableCutPlane(true);
        }

        UpdateToolPosition();
        UpdateRefFaces();
        UpdateFeedback();
    }

    void CutCircularSawFeedback::Activate() {
        m_OrientationVisualizer.Activate();
        m_PositionStartVisualizer.Activate();
        m_ThicknessVisualizer.Activate();
        m_DepthVisualizer.Activate();
        Update();
    }

    void CutCircularSawFeedback::Deactivate() {
        m_CutPlaneVisualizer.Deactivate();
        m_OrientationVisualizer.Deactivate();
        m_PositionStartVisualizer.Deactivate();
        m_ThicknessVisualizer.Deactivate();
        m_DepthVisualizer.Deactivate();
    }

    void CutCircularSawFeedback::UpdateToolPosition() {
        m_Radius = AC_FF_TOOL->GetData<CircularSawData>().RadiusACIT;
        m_Center = AC_FF_TOOL->GetData<CircularSawData>().CenterGO->GetPosition();
        m_NormalStart = m_Center; // AC_FF_TOOL->GetData<CircularSawData>().NormStartGO->GetPosition(); // this value is not initialized
        m_NormalEnd = AC_FF_TOOL->GetData<CircularSawData>().NormEndGO->GetPosition();
        m_Normal = glm::normalize(m_NormalEnd - m_NormalStart);
    }

    void CutCircularSawFeedback::ManuallyScrollRefFace(int scrollDirection) {
        auto iter = m_Cut->GetAllFaces().find(m_NearestParallelFaceID);

        // if scroll direction > 0 => goes to next, otherwise, goes back
        if (scrollDirection > 0) {
            iter++;
            if (iter == m_Cut->GetAllFaces().end()){
                iter = m_Cut->GetAllFaces().begin();
            }
        } else {
            if (iter == m_Cut->GetAllFaces().begin()){
                iter = m_Cut->GetAllFaces().end();
            }
            iter--;
        }

        m_NearestParallelFaceID = iter->first;

    }

    void CutCircularSawFeedback::UpdateRefFaces()
    {
        // --------------------------------------------------------------------
        // update the reference faces (parallel / perpendicular)
        if (IsRefFacesSelectedManually) {
            // In manually selection mode, when the m_cut is switched, we have to update the face
            if (m_Cut->GetAllFaces().find(m_NearestParallelFaceID) == m_Cut->GetAllFaces().end()){
                m_NearestParallelFaceID = m_Cut->GetAllFaces().begin()->first;
            }
        } else {
            // automatically determine the reference faces
            std::string nearestParallelFaceID;
            std::vector<std::pair<std::string, float>> allValidFaces;
            std::vector<std::pair<std::string, float>> perpenFaces;

            // get the distances to the blade circle
            for(auto const& [faceID, faceInfo]: m_Cut->GetAllFaces()){
                if (faceInfo.IsExposed()) continue;
                glm::vec3 ptOnCircleBlade = GOCircle::ClosestPointToCircle(
                        faceInfo.GetCenter(),
                        m_Center,
                        m_Normal,
                        m_Radius
                );
                auto dist = glm::abs(glm::distance(ptOnCircleBlade, faceInfo.GetCenter()));
                allValidFaces.push_back(std::make_pair(faceID, dist));
            }
            // filter the vector with an angle threshold, erase those elements in the vector that do not respect the check
            auto thresholdAngle = 0.7853f; // 45 degrees
            allValidFaces.erase(
                    std::remove_if(allValidFaces.begin(), allValidFaces.end(), [this, thresholdAngle](auto const& a){
                        auto faceInfo = m_Cut->GetFace(a.first);
                        auto faceNormal = faceInfo.GetNormal();
                        auto theta = glm::acos(
                                glm::dot(faceNormal, m_Normal) /
                                (glm::length(faceNormal) * glm::length(m_Normal))
                        );
                        return theta > thresholdAngle && (3.14159 - theta) > thresholdAngle;
                    }),
                    allValidFaces.end()
            );
            // reorder the faces by the abs distance
            std::sort(allValidFaces.begin(), allValidFaces.end(), [](auto const& a, auto const& b){
                return a.second < b.second;
            });
            // put the rest in the perpendicular faces
            for (auto const& [faceID, dist]: allValidFaces){
                if(faceID == nearestParallelFaceID) continue;
                perpenFaces.push_back(std::make_pair(faceID, dist));
            }
            // if it is the first time, nearestParallelFaceID.empty() gives the first face as the nearest parallel face
            nearestParallelFaceID = nearestParallelFaceID.empty() ? allValidFaces[0].first : nearestParallelFaceID;
            // sort perpendicular faces by distance
            std::sort(perpenFaces.begin(), perpenFaces.end(), [](auto const& a, auto const& b){
                return a.second < b.second;
            });

            // --------------------------------------------------------------------
            // update class members

            // Case 1: If the saw is place on the side for adjusting the height, there should be no parallel face
            // Therefore, the closest face would be considered as the face to be cut,
            // and the secondly closest face would be considered as the perpendicular surface that sits
            // at the bottom of the blade.
            // Case 2: During cut, everything is defined as normal
            if(nearestParallelFaceID.empty())
            {
                if(perpenFaces.size() < 2) {
                    // TODO: catch error
                    return;
                }
                m_NearestParallelFaceID = perpenFaces[0].first;
            } else {
                m_NearestParallelFaceID = nearestParallelFaceID;
            }
        }

        // --------------------------------------------------------------------
        // get the first and secondly closest neighbour face to the highlighted face and to the blade's center
        std::map<std::string, AIAC::TimberInfo::Cut::Face> neighbouringFaces = 
            this->m_Cut->GetFaceNeighbors(m_NearestParallelFaceID);
        float minDist = std::numeric_limits<float>::max();
        for (auto const& [faceID, faceInfo] : neighbouringFaces)
        {
            auto projCenter = GetProjectionPointOnPlane(
                faceInfo.GetNormal(),
                faceInfo.GetCenter(),
                m_Center);
            float distAbs = glm::abs(glm::distance(m_Center, projCenter));
            if (distAbs < minDist && distAbs > m_Radius)
            {
                minDist = distAbs;
                m_NearestNeighbourFaceIDToParallelFace = faceID;
            }
        }
        minDist = std::numeric_limits<float>::max();
        for (auto const& [faceID, faceInfo] : neighbouringFaces)
        {
            if (faceID == m_NearestNeighbourFaceIDToParallelFace) continue;
            auto projCenter = GetProjectionPointOnPlane(
                faceInfo.GetNormal(),
                faceInfo.GetCenter(),
                m_Center);
            float distAbs = glm::abs(glm::distance(m_Center, projCenter));
            if (distAbs < minDist && distAbs > m_Radius)
            {
                minDist = distAbs;
                m_SecondNearestNeighbourFaceIDToParallelFace = faceID;
            }
        }
    }

    void CutCircularSawFeedback::UpdateFeedback() {
        if(this->m_ToShowCutPlane || this->m_Cut->IsSingleFace())
        {
            this->m_CutPlaneVisualizer.Activate();
            UpdateCutPlaneFeedback();
        }
        else
            this->m_CutPlaneVisualizer.Deactivate();

        this->UpdateOrientationFeedback();
        this->UpdateThicknessFeedback();
        this->UpdateDepthFeedback();
        this->UpdateStartPosFeedback();
    }

    void CutCircularSawFeedback::UpdateOrientationFeedback()
    {
        if (!this->m_NearestParallelFaceID.empty())
        {
            this->m_Cut->HighlightFace(m_NearestParallelFaceID);
            auto faceInfo = m_Cut->GetFace(m_NearestParallelFaceID);
            auto faceNormal = faceInfo.GetNormal();
            auto faceCenter = faceInfo.GetCenter();
            this->m_OrientationVisualizer.m_LineFaceNormal->SetPts(faceCenter, faceCenter + faceNormal);

            auto bladeNormal = glm::normalize(m_Normal);
            this->m_OrientationVisualizer.m_LineBladeNormal->SetPts(faceCenter, faceCenter + bladeNormal);

            // get the axis system of the face with the projection of the blade normal
            glm::vec3 zVec = glm::normalize(faceNormal);
            glm::vec3 xVec = glm::normalize(glm::cross(faceNormal, faceCenter));
            glm::vec3 yVec = glm::normalize(glm::cross(faceNormal, xVec));
            glm::vec3 bladeNormalProjOnFace = (m_NormalEnd - m_NormalStart) - glm::dot(m_NormalEnd - m_NormalStart, zVec) * zVec;

            xVec = glm::normalize(bladeNormalProjOnFace);
            yVec = glm::normalize(glm::cross(zVec, xVec));

            // draw the rotated x axis as a GOLine
            this->m_OrientationVisualizer.m_LineDebugB->SetPts(faceCenter, faceCenter + xVec);
            this->m_OrientationVisualizer.m_LineDebugC->SetPts(faceCenter, faceCenter + yVec);

            // draw the line between the end of the m_LineBladeNormal and the end of the lineDebugC
            float pitch = glm::degrees(atan2(bladeNormalProjOnFace.y, bladeNormalProjOnFace.z));
            this->m_OrientationVisualizer.m_LineDebugD->SetPts(
                this->m_OrientationVisualizer.m_LineBladeNormal->GetPEnd(),
                this->m_OrientationVisualizer.m_LineDebugB->GetPEnd());
            // draw the line between the end of the m_LineBladeNormal and the end of the lineDebugB
            this->m_OrientationVisualizer.m_LineDebugE->SetPts(
                this->m_OrientationVisualizer.m_LineBladeNormal->GetPEnd(),
                this->m_OrientationVisualizer.m_LineDebugC->GetPEnd());

            // calculare the angle between m_LineDebugE and m_LineDebugC (the pitch)
            float anglePitch = this->m_OrientationVisualizer.m_LineDebugD->ComputeSignedAngle(
                this->m_OrientationVisualizer.m_LineDebugB
                );
            float anglePitchDiffNeg = -45.0f - anglePitch;
            float anglePitchDiffPos = 45.0f - anglePitch;
            float anglePitchDiff = std::abs(anglePitchDiffNeg) < std::abs(anglePitchDiffPos) ? anglePitchDiffNeg : anglePitchDiffPos;
            anglePitchDiff = std::round(anglePitchDiff * 10) / 10;

            // Set the axis system on the blade
            glm::vec3 bladeZVec = glm::normalize(m_NormalEnd - m_NormalStart);
            glm::vec3 bladeXVec = glm::normalize(glm::cross(m_Center - m_BottomPoint, bladeZVec));
            glm::vec3 bladeYVec = glm::normalize(glm::cos(1.5708f) * bladeXVec + glm::sin(1.5708f) * bladeZVec);

            // Pitch guidance
            // show the degree difference in text
            this->m_OrientationVisualizer.m_GuideTxtRollPitch->SetAnchor(m_Center + bladeYVec * 0.5f);
            std::ostringstream stream;
            stream << std::fixed << std::setprecision(1) << anglePitchDiff;
            std::string anglePitchDiffStr = stream.str();
            this->m_OrientationVisualizer.m_GuideTxtRollPitch->SetText("r:" + anglePitchDiffStr);
            if (anglePitchDiff > -this->m_OrientationVisualizer.m_tolAangleAcceptance && anglePitchDiff < this->m_OrientationVisualizer.m_tolAangleAcceptance)
                this->m_OrientationVisualizer.m_GuideTxtRollPitch->SetColor(GOColor::GREEN);
            else
                this->m_OrientationVisualizer.m_GuideTxtRollPitch->SetColor(GOColor::WHITE);

            // give a visual line feedback on the orientation
            if (anglePitchDiff > this->m_OrientationVisualizer.m_tolAangleAcceptance)
            {
                this->m_OrientationVisualizer.m_LinePitchFeed->SetColor(GOColor::MAGENTA);
                this->m_OrientationVisualizer.m_LinePitchFeed->SetPts(m_Center, m_Center + bladeYVec * anglePitchDiff);
            }
            else if (anglePitchDiff < -this->m_OrientationVisualizer.m_tolAangleAcceptance)
            {
                this->m_OrientationVisualizer.m_LinePitchFeed->SetColor(GOColor::RED);
                this->m_OrientationVisualizer.m_LinePitchFeed->SetPts(m_Center, m_Center + bladeYVec * anglePitchDiff);
            }
            else if (anglePitchDiff > -this->m_OrientationVisualizer.m_tolAangleAcceptance && anglePitchDiff < this->m_OrientationVisualizer.m_tolAangleAcceptance)
            {
                this->m_OrientationVisualizer.m_LinePitchFeed->SetColor(GOColor::GREEN);
                this->m_OrientationVisualizer.m_LinePitchFeed->SetPts(m_Center, m_Center + bladeYVec * 0.3f);
            }

            // set the visibility off for the debug lines
            this->m_OrientationVisualizer.m_LineFaceNormal->SetVisibility(false);
            this->m_OrientationVisualizer.m_LineBladeNormal->SetVisibility(false);
            this->m_OrientationVisualizer.m_LineDebugA->SetVisibility(false);
            this->m_OrientationVisualizer.m_LineDebugB->SetVisibility(false);
            this->m_OrientationVisualizer.m_LineDebugC->SetVisibility(false);
            this->m_OrientationVisualizer.m_LineDebugD->SetVisibility(false);
            this->m_OrientationVisualizer.m_LineDebugE->SetVisibility(false);
            this->m_OrientationVisualizer.m_LinePitchFeed->SetVisibility(true);
            this->m_OrientationVisualizer.m_GuideTxtRollPitch->SetVisibility(true);
        }
        else
        {
            this->m_OrientationVisualizer.Deactivate();
        }

    }

    void CutCircularSawFeedback::UpdateCutPlaneFeedback()
    {
        if(m_ToShowCutPlane) m_CutPlaneVisualizer.Update(m_Normal, m_Center);
    }

    void CircularSawCutBladeThicknessVisualizer::UpdateToolheadsData()
    {
        this->m_BladeTotalThicknessScaled = AC_FF_TOOL->GetData<CircularSawData>().ThicknessACIT;
        this->m_BladeOverhangScaled = AC_FF_TOOL->GetData<CircularSawData>().OverhangACIT;
        this->m_ToolheadRefCenter = AC_FF_TOOL->GetData<CircularSawData>().CenterGO->GetPosition();
        this->m_ToolheadRefNormStart = this->m_ToolheadRefCenter;
        this->m_ToolheadRefNormEnd = AC_FF_TOOL->GetData<CircularSawData>().NormEndGO->GetPosition();
        this->m_NormalUnitized = glm::normalize(this->m_ToolheadRefNormEnd - this->m_ToolheadRefNormStart);
        this->m_NormalOppositeUnitized = -this->m_NormalUnitized;
        this->m_DisplacedCenterTowardsCamera = this->m_ToolheadRefCenter + this->m_NormalUnitized * this->m_BladeOverhangScaled;
        this->m_DisplacedCenterAwayFromCamera = this->m_ToolheadRefCenter + this->m_NormalOppositeUnitized * (this->m_BladeTotalThicknessScaled - this->m_BladeOverhangScaled);
    }

    bool CircularSawCutBladeThicknessVisualizer::IntersectBladeWithNeighbours(
        TimberInfo::Cut* cut,
        TimberInfo::Cut::Face& face,
        bool isTowardsCamera,
        bool isDetectToolPlane,
        std::shared_ptr<GOLine>& lineIntersection)
    {
        glm::vec3 centerBlade;
        glm::vec3 normalBlade;
        if (!isDetectToolPlane)
        {
            if (isTowardsCamera)
            {
                normalBlade = this->m_NormalUnitized;
                centerBlade = this->m_DisplacedCenterTowardsCamera;
            }
            else
            {
                normalBlade = this->m_NormalOppositeUnitized;
                centerBlade = this->m_DisplacedCenterAwayFromCamera;
            }
        }
        else
        {
            normalBlade = this->m_NormalUnitized;
            centerBlade = this->m_ToolheadRefCenter;
        }
        glm::vec3 downVecBlade;
        auto prepFaceACenter = face.GetCenter();
        auto perpFaceANormal = face.GetNormal();
        glm::vec3 perpFaceOfBladeVec = glm::normalize(glm::cross(normalBlade, perpFaceANormal));
        glm::vec3 _ptPlaceHolder;
        GetIntersectLineOf2Planes(
            normalBlade,
            centerBlade,
            perpFaceOfBladeVec,
            centerBlade,
            downVecBlade,
            _ptPlaceHolder
        );

        auto sidePt1 = centerBlade + perpFaceOfBladeVec * AC_FF_TOOL->GetData<CircularSawData>().RadiusACIT;
        auto sidePt2 = centerBlade - perpFaceOfBladeVec * AC_FF_TOOL->GetData<CircularSawData>().RadiusACIT;
        glm::vec3 projSidePt1, projSidePt2;
        GetIntersectPointOfLineAndPlane(downVecBlade, sidePt1, perpFaceANormal, prepFaceACenter, projSidePt1);
        GetIntersectPointOfLineAndPlane(downVecBlade, sidePt2, perpFaceANormal, prepFaceACenter, projSidePt2);
        if (projSidePt1 == projSidePt2)
        {
            lineIntersection->SetPts(glm::vec3(0.f, 0.f, 0.f), glm::vec3(0.f, 0.f, 0.f));
            return false;
        }

        std::vector<glm::vec3> intersectPts;
        glm::vec3 perpIntersectLineSegPt1, perpIntersectLineSegPt2;
        for(auto const& edgeID: face.GetEdges()){
            auto edge = cut->GetEdge(edgeID);
            auto edgePt1 = edge.GetStartPt().GetPosition();
            auto edgePt2 = edge.GetEndPt().GetPosition();
            ExtendLineSeg(edgePt1, edgePt2, 1.0f);
            glm::vec3 intersectPt;
            if(GetIntersectPointOfLineAndLineSeg((projSidePt2 - projSidePt1), projSidePt1, edgePt1, edgePt2, intersectPt)) {
                intersectPts.push_back(intersectPt);
            }
        }
        FormLongestLineSeg(intersectPts, perpIntersectLineSegPt1, perpIntersectLineSegPt2);

        if (intersectPts.size() == 0)
        {
            lineIntersection->SetPts(glm::vec3(0.f, 0.f, 0.f), glm::vec3(0.f, 0.f, 0.f));
            return false;
        }
        lineIntersection->SetPts(perpIntersectLineSegPt1, perpIntersectLineSegPt2);
        return true;
    }

    void CutCircularSawFeedback::UpdateThicknessFeedback()
    {
        this->m_ThicknessVisualizer.Deactivate();
        this->m_ThicknessVisualizer.UpdateToolheadsData();

        std::map<std::string, AIAC::TimberInfo::Cut::Face> neighbouringFaces = 
            this->m_Cut->GetHighlightedFaceNeighbors();
        if (neighbouringFaces.size() > 2)
        {
            AIAC_WARN("The neighbouring faces are more than 2, this is not possible to show the thickness feedback");
            return;
        }

        AIAC::TimberInfo::Cut::Face faceNeighbour1 = this->m_Cut->GetFace(this->m_NearestNeighbourFaceIDToParallelFace);
        if (neighbouringFaces.size() == 1)
        {
            bool isfaceNeighbour1Intersected = this->m_ThicknessVisualizer.IntersectBladeWithNeighbours(
                this->m_Cut, faceNeighbour1, true, true, this->m_ThicknessVisualizer.m_LongestIntersectSegmenDetectToolPlane);
            bool isfaceNeighbour1IntersectedOnce = this->m_ThicknessVisualizer.IntersectBladeWithNeighbours(
                this->m_Cut, faceNeighbour1, true, false, this->m_ThicknessVisualizer.m_LongestIntersectSegmentTowardsCameraA);
            bool isfaceNeighbour1IntersectedTwice = this->m_ThicknessVisualizer.IntersectBladeWithNeighbours(
                this->m_Cut, faceNeighbour1, false, false, this->m_ThicknessVisualizer.m_LongestIntersectSegmentAwayFromCameraA);

            if (this->m_ThicknessVisualizer.IsSegmenDetectToolPlaneVisible)
                this->m_ThicknessVisualizer.m_LongestIntersectSegmenDetectToolPlane->SetVisibility(isfaceNeighbour1Intersected);
            this->m_ThicknessVisualizer.m_LongestIntersectSegmentTowardsCameraA->SetVisibility((isfaceNeighbour1IntersectedOnce && isfaceNeighbour1IntersectedTwice));
            this->m_ThicknessVisualizer.m_LongestIntersectSegmentAwayFromCameraA->SetVisibility((isfaceNeighbour1IntersectedOnce && isfaceNeighbour1IntersectedTwice));
        }
        else if (neighbouringFaces.size() == 2)
        {
            AIAC::TimberInfo::Cut::Face faceNeighbour2 = this->m_Cut->GetFace(this->m_SecondNearestNeighbourFaceIDToParallelFace);
            bool isfaceNeighbour1Intersected = this->m_ThicknessVisualizer.IntersectBladeWithNeighbours(
                this->m_Cut, faceNeighbour1, true, true, this->m_ThicknessVisualizer.m_LongestIntersectSegmenDetectToolPlane);
            bool isfaceNeighbour1IntersectedOnce = this->m_ThicknessVisualizer.IntersectBladeWithNeighbours(
                this->m_Cut, faceNeighbour1, true, false, this->m_ThicknessVisualizer.m_LongestIntersectSegmentTowardsCameraA);
            bool isfaceNeighbour1IntersectedTwice = this->m_ThicknessVisualizer.IntersectBladeWithNeighbours(
                this->m_Cut, faceNeighbour1, false, false, this->m_ThicknessVisualizer.m_LongestIntersectSegmentAwayFromCameraA);
            bool isfaceNeighbour2IntersectedOnce = this->m_ThicknessVisualizer.IntersectBladeWithNeighbours(
                this->m_Cut, faceNeighbour2, true, false, this->m_ThicknessVisualizer.m_LongestIntersectSegmentTowardsCameraB);
            bool isfaceNeighbour2IntersectedTwice = this->m_ThicknessVisualizer.IntersectBladeWithNeighbours(
                this->m_Cut, faceNeighbour2, false, false, this->m_ThicknessVisualizer.m_LongestIntersectSegmentAwayFromCameraB);

            if (this->m_ThicknessVisualizer.IsSegmenDetectToolPlaneVisible)
                this->m_ThicknessVisualizer.m_LongestIntersectSegmenDetectToolPlane->SetVisibility(isfaceNeighbour1Intersected);
            this->m_ThicknessVisualizer.m_LongestIntersectSegmentTowardsCameraA->SetVisibility((isfaceNeighbour1IntersectedOnce && isfaceNeighbour1IntersectedTwice));
            this->m_ThicknessVisualizer.m_LongestIntersectSegmentAwayFromCameraA->SetVisibility((isfaceNeighbour1IntersectedOnce && isfaceNeighbour1IntersectedTwice));
            this->m_ThicknessVisualizer.m_LongestIntersectSegmentTowardsCameraB->SetVisibility((isfaceNeighbour2IntersectedOnce && isfaceNeighbour2IntersectedTwice));
            this->m_ThicknessVisualizer.m_LongestIntersectSegmentAwayFromCameraB->SetVisibility((isfaceNeighbour2IntersectedOnce && isfaceNeighbour2IntersectedTwice));
        }
        else
        {
            this->m_ThicknessVisualizer.Deactivate();
        }
    }

    void CutCircularSawFeedback::UpdateStartPosFeedback()
    {
        if (!this->m_NearestParallelFaceID.empty())
        {
            this->m_PositionStartVisualizer.Activate();

            AIAC::TimberInfo::Cut::Face faceInfo = this->m_Cut->GetFace(this->m_NearestParallelFaceID);
            glm::vec3 faceNormal = faceInfo.GetNormal();
            glm::vec3 faceCenter = faceInfo.GetCenter();

            // closest displaced center of the blade (towards or away from the camera)
            glm::vec3 projBladeNorm;
            glm::vec3 centerBlade;
            glm::vec3 projBladeCenterTowards2Face = GetProjectionPointOnPlane(
                faceNormal,
                faceCenter,
                this->m_ThicknessVisualizer.m_DisplacedCenterTowardsCamera
            );
            glm::vec3 projBladeCenterAway2Face = GetProjectionPointOnPlane(
                faceNormal,
                faceCenter,
                this->m_ThicknessVisualizer.m_DisplacedCenterAwayFromCamera
            );
            // get the closest one
            float distTowards = glm::abs(glm::distance(this->m_ThicknessVisualizer.m_DisplacedCenterTowardsCamera, projBladeCenterTowards2Face));
            float distAway = glm::abs(glm::distance(this->m_ThicknessVisualizer.m_DisplacedCenterAwayFromCamera, projBladeCenterAway2Face));
            if (distTowards < distAway)
            {
                projBladeNorm = projBladeCenterTowards2Face;
                centerBlade = this->m_ThicknessVisualizer.m_DisplacedCenterTowardsCamera;
            }
            else
            {
                projBladeNorm = projBladeCenterAway2Face;
                centerBlade = this->m_ThicknessVisualizer.m_DisplacedCenterAwayFromCamera;
            }
            // calculate the distance
            glm::vec3 centerBladeProjOnFace = GetProjectionPointOnPlane(
                faceNormal,
                faceCenter,
                centerBlade);
            float parallelDistCtrBlade2PtFace = glm::distance(
                centerBlade,
                centerBladeProjOnFace);

            // Visualization
            // set the visuals and print the distance feed
            // move the center down of half the radius
            auto prepFaceInfo = m_Cut->GetFace(this->m_NearestNeighbourFaceIDToParallelFace);
            auto prepPlnCenter = prepFaceInfo.GetCenter();
            auto perpPlnNormal = prepFaceInfo.GetNormal();
            glm::vec3 perpFaceOfBladeVec = glm::normalize(glm::cross(m_Normal, perpPlnNormal));
            glm::vec3 _ptPlaceHolder;
            GetIntersectLineOf2Planes(
                m_Normal, m_Center,
                perpFaceOfBladeVec, m_Center,
                m_DownVec, _ptPlaceHolder
            );

            if(glm::distance(m_Center + m_DownVec * m_Radius, faceInfo.GetCenter()) >
            glm::distance(m_Center - m_DownVec * m_Radius, faceInfo.GetCenter())){
                m_DownVec = -m_DownVec;
            }

            // get the bottom point and update
            glm::vec3 bottomPoint = m_Center + m_DownVec * m_Radius;
            this->m_PositionStartVisualizer.m_LineToBottomPt->SetPts(m_Center, bottomPoint);

            // set the visuals and print the distance feed
            glm::vec3 midPt = this->m_PositionStartVisualizer.m_LineToBottomPt->GetMidPointValues();
            glm::vec3 midPtProj = GetProjectionPointOnPlane(
                faceNormal,
                faceCenter,
                midPt);
            this->m_PositionStartVisualizer.m_LineDistStart->SetPts(
                midPt,
                midPtProj);

            std::ostringstream stream;
            stream << std::fixed << std::setprecision(1) << parallelDistCtrBlade2PtFace;
            std::string parallelDistCtrBlade2PtFaceStr = stream.str();
            this->m_PositionStartVisualizer.m_TxtDistStart->SetAnchor(
                midPtProj
                );
            this->m_PositionStartVisualizer.m_TxtDistStart->SetText("s:" + parallelDistCtrBlade2PtFaceStr);
            this->m_PositionStartVisualizer.m_TxtDistStart->SetColor(
                parallelDistCtrBlade2PtFace < this->m_PositionStartVisualizer.ToleranceStartThreshold ? GOColor::GREEN : GOColor::YELLOW);
            this->m_PositionStartVisualizer.m_LineDistStart->SetColor(
                parallelDistCtrBlade2PtFace < this->m_PositionStartVisualizer.ToleranceStartThreshold ? GOColor::GREEN : GOColor::YELLOW);
        }
        else
        {
            this->m_PositionStartVisualizer.Deactivate();
        }
    }

    void CutCircularSawFeedback::UpdateDepthFeedback()
    {
        if (!this->m_NearestParallelFaceID.empty() && !m_Cut->IsSingleFace())
        {
            // calculate distances (closest point to line/segment to circle)
            float distLineDepth = GOCircle::ClosestDistanceFromLineToCircle(
            this->m_ThicknessVisualizer.m_LongestIntersectSegmenDetectToolPlane,
                m_Center,
                m_Radius
            );
            std::pair<float, std::pair<glm::vec3, glm::vec3>> res = GOCircle::ClosestDistanceFromSegmentToCircle(
                this->m_ThicknessVisualizer.m_LongestIntersectSegmentTowardsCameraA,
                m_Center,
                m_Radius
            );
            float distSegDepth = res.first;
            float distLineDepthAbs = glm::abs(distLineDepth);
            float distSegDepthAbs = glm::abs(distSegDepth);

            // visualization
            auto prepFaceInfo = m_Cut->GetFace(this->m_NearestNeighbourFaceIDToParallelFace);
            auto prepPlnCenter = prepFaceInfo.GetCenter();
            auto perpPlnNormal = prepFaceInfo.GetNormal();
            auto projBladeCenter = GetProjectionPointOnPlane(
                perpPlnNormal,
                prepPlnCenter,
                m_Center);
            this->m_DepthVisualizer.m_TxtDepth->SetAnchor(projBladeCenter);
            this->m_DepthVisualizer.m_LineDepth->SetPts(m_Center, projBladeCenter);

            this->m_DepthVisualizer.m_TxtDepth->SetText(
                "d:" + FeedbackVisualizer::toString(distLineDepth) +
                "|" + FeedbackVisualizer::toString(distSegDepthAbs) + ""
                );
            if (std::isfinite(distLineDepth) && distLineDepth > static_cast<float>(INT_MIN) && distLineDepth < static_cast<float>(INT_MAX))
            {
                if (distLineDepthAbs < this->m_DepthVisualizer.m_ToleranceDepthThreshold)
                    this->m_DepthVisualizer.m_TxtDepth->SetColor(GOColor::GREEN);
                else if (distLineDepthAbs < this->m_DepthVisualizer.m_ToleranceDepthThreshold * 20)
                    this->m_DepthVisualizer.m_TxtDepth->SetColor(GOColor::YELLOW);
                else
                    this->m_DepthVisualizer.m_TxtDepth->SetColor(GOColor::RED);
            }
            else
            {
                this->m_DepthVisualizer.m_TxtDepth->SetText("///");
                this->m_DepthVisualizer.m_TxtDepth->SetColor(GOColor::MAGENTA);
            }


            this->m_DepthVisualizer.m_PtBlade2ThicknessLineA->SetPosition(res.second.first);
            this->m_DepthVisualizer.m_PtBlade2ThicknessLineB->SetPosition(res.second.second);


        }
        else
        {
            this->m_DepthVisualizer.Deactivate();
        }
    }

}