Skip to content

File CutChainSawFeedback.cpp

File List > AIAC > Feedback > CutChainSawFeedback.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 "CutChainSawFeedback.h"
#include "utils/GeometryUtils.h"

#include <sstream>
#include <iomanip>

namespace AIAC
{
    CutOrientationVisualizer::CutOrientationVisualizer()
    {
        // Normal face line 
        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::MediumThick);
        m_GuideTxtRollPitch = GOText::Add("RollPitch", GOPoint(0.f, 0.f, 0.f));

        m_GuideTxtRollPitch->SetTextSize(GOTextSize::Average);

        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_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();
    }

    CutChainSawAngleFeedVisualizer::CutChainSawAngleFeedVisualizer()
    {
        // Line
        m_LineEnd = GOLine::Add(GOPoint(0.f, 0.f, 0.f), GOPoint(0.f, 0.f, 0.f));
        m_LineChainBase = GOLine::Add(GOPoint(0.f, 0.f, 0.f), GOPoint(0.f, 0.f, 0.f));
        m_LineChainEnd = GOLine::Add(GOPoint(0.f, 0.f, 0.f), GOPoint(0.f, 0.f, 0.f));

        m_LineEnd->SetColor(GOColor::WHITE);
        m_LineChainBase->SetColor(GOColor::WHITE);
        m_LineChainEnd->SetColor(GOColor::WHITE);

        m_LineEnd->SetWeight(GOWeight::Bold);
        m_LineChainBase->SetWeight(GOWeight::MediumThick);
        m_LineChainEnd->SetWeight(GOWeight::MediumThick);

        m_AllPrimitives.push_back(m_LineEnd);
        m_AllPrimitives.push_back(m_LineChainBase);
        m_AllPrimitives.push_back(m_LineChainEnd);

        Deactivate();
    }

    CutChainSawDepthFeedVisualizer::CutChainSawDepthFeedVisualizer()
    {
        // Line
        m_LineIntersect = GOLine::Add(GOPoint(0.f, 0.f, 0.f), GOPoint(0.f, 0.f, 0.f));
        m_LineIntersectThickness = GOLine::Add(GOPoint(0.f, 0.f, 0.f), GOPoint(0.f, 0.f, 0.f));

        m_LineDepthFaceEdge1 = GOLine::Add(GOPoint(0.f, 0.f, 0.f), GOPoint(0.f, 0.f, 0.f));
        m_LineDepthFaceEdge2 = GOLine::Add(GOPoint(0.f, 0.f, 0.f), GOPoint(0.f, 0.f, 0.f));

        m_LineIntersect->SetWeight(GOWeight::Light);
        m_LineIntersectThickness->SetWeight(GOWeight::Light);

        m_LineIntersect->SetColor(GOColor::RED);
        m_LineIntersectThickness->SetColor(GOColor::RED);
        m_LineDepthFaceEdge1->SetColor(GOColor::YELLOW);
        m_LineDepthFaceEdge2->SetColor(GOColor::YELLOW);

        m_LineIntersect->SetWeight(GOWeight::Medium);
        m_LineIntersectThickness->SetWeight(GOWeight::Medium);
        m_LineDepthFaceEdge1->SetWeight(GOWeight::MediumThick);
        m_LineDepthFaceEdge2->SetWeight(GOWeight::MediumThick);

        m_AllPrimitives.push_back(m_LineIntersect);
        m_AllPrimitives.push_back(m_LineIntersectThickness);
        m_AllPrimitives.push_back(m_LineDepthFaceEdge1);
        m_AllPrimitives.push_back(m_LineDepthFaceEdge2);

        Deactivate();
    }

    CutChainSawFeedVisualizer::CutChainSawFeedVisualizer()
    {
        // Text
        m_GuideTxtChainBase = GOText::Add("ChainBase", GOPoint(0.f, 0.f, 0.f));
        m_GuideTxtFaceEdgeDepth = GOText::Add("FaceEdgeDepth2", GOPoint(0.f, 0.f, 0.f));

        m_GuideTxtChainBase->SetTextSize(GOTextSize::Small);
        m_GuideTxtFaceEdgeDepth->SetTextSize(GOTextSize::Average);

        m_GuideTxtChainBase->SetColor(GOColor::BLACK);
        m_GuideTxtFaceEdgeDepth->SetColor(GOColor::BLACK);

        m_AllPrimitives.push_back(m_GuideTxtChainBase);
        m_AllPrimitives.push_back(m_GuideTxtFaceEdgeDepth);

        Deactivate();
    }

    void CutChainSawFeedback::UpdateCutPlane ()
    {
        if(m_ToShowCutPlane) m_CutPlaneVisualizer.Update(m_NormalVec, m_NormStart);
    }

    void CutChainSawFeedback::UpdateRefFaces() {
        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;
            }

            // get the first closest neighbour face to the highlighted face as the perpendicular face
            // ** As we're limiting the cut to have maximum 3 faces, it doesn't really matter how to calculate.
            // the perpendicular plane. I think a better idea is to take the furthest neighbor of the nearest parallel
            // face or the face that maximum the length of the projection line formed by the m_ChainBase and m_ChainEnd.
            // However, since it's working, I'm not dare to touch it :3
            m_NearestPerpendicularFaceID = "";
            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_ChainMid);
                float distAbs = glm::abs(glm::distance(m_ChainMid, projCenter));
                if (distAbs < minDist)
                {
                    minDist = distAbs;
                    m_NearestPerpendicularFaceID = faceID;
                }
            }
        } else {
            // Find the nearest parallel/perpendicular face to highlight
            float nearestParallelFaceDist = 1e9f;
            float nearestPerpendicularFaceDist = 1e9f;
            std::vector<std::string> parallelFaceIDs;
            std::vector<std::string> perpendicularFaceIDs;

            for (auto const &[faceID, faceInfo]: m_Cut->GetAllFaces()) {
                if (faceInfo.IsExposed()) continue;
                auto faceNormal = faceInfo.GetNormal();
                auto theta = glm::acos(
                        glm::dot(faceNormal, m_NormalVec) / (glm::length(faceNormal) * glm::length(m_NormalVec)));

                auto distChainBase = glm::distance(faceInfo.GetCenter(), m_ChainBase);
                auto distChainEnd = glm::distance(faceInfo.GetCenter(), m_ChainEnd);
                auto totalDist = distChainBase + distChainEnd;

                // for parallel faces, find the nearest one
                auto threshold = 0.7853f; // 45 degrees
                if (theta < threshold || (3.14159 - theta) < threshold) {
                    parallelFaceIDs.push_back(faceID);
                    // update nearest parallel face
                    if (m_NearestParallelFaceID.empty() || totalDist < nearestParallelFaceDist) {
                        m_NearestParallelFaceID = faceID;
                        nearestParallelFaceDist = totalDist;
                    }
                } else {
                    perpendicularFaceIDs.push_back(faceID);
                    // update nearest perpendicular face
                    if (m_NearestPerpendicularFaceID.empty() || totalDist < nearestPerpendicularFaceDist) {
                        m_NearestPerpendicularFaceID = faceID;
                        nearestPerpendicularFaceDist = totalDist;
                    }
                }
            }
        }
    }

    void CutChainSawFeedback::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 CutChainSawFeedback::Update()
    {
        // calculate tool normal
        m_NormStart = AC_FF_TOOL->GetData<ChainSawData>().NormStartGO->GetPosition();
        m_NormEnd = AC_FF_TOOL->GetData<ChainSawData>().NormEndGO->GetPosition();
        m_ChainBase = AC_FF_TOOL->GetData<ChainSawData>().ChainBaseGO->GetPosition();
        m_ChainMid = AC_FF_TOOL->GetData<ChainSawData>().ChainMidGO->GetPosition();
        m_ChainEnd = AC_FF_TOOL->GetData<ChainSawData>().ChainEndGO->GetPosition();
        m_NormalVec = glm::normalize(m_NormEnd - m_NormStart);

        m_Cut = dynamic_cast<TimberInfo::Cut*>(AC_FF_COMP);
        auto& angleVisualizer = this->m_Visualizer.GetAngleFeedVisualizer();
        auto& depthVisualizer = this->m_Visualizer.GetDepthFeedVisualizer();

        if(m_ToShowCutPlane) UpdateCutPlane();

        // if it's a single face show the cutting plane
        if(m_Cut->IsSingleFace()) {
            this->EnableCutPlane(true);
        }

        this->UpdateRefFaces();

        // Highlight the face
        if (!m_Cut->IsSingleFace())
            m_Cut->HighlightFace(m_NearestParallelFaceID);

        // Update the m_Visualizer for the closest parallel face
        bool hasParallelFace = false, hasPerpendicularFace = false;

        double parallelEndDist = 0.0f;
        double parallelChainBaseDist = 0.0f;
        double perpendicularFaceEdge1Dist = 0.0f;
        double perpendicularFaceEdge2Dist = 0.0f;
        glm::vec3 perpIntersectLineSegPt1, perpIntersectLineSegPt2; // for depth text anchor

        // update angle visualizer
        if(!m_NearestParallelFaceID.empty())
        {
            hasParallelFace = true;
            angleVisualizer.Activate();

            // find the projection point of the three points on the face
            auto faceInfo = m_Cut->GetFace(m_NearestParallelFaceID);
            auto faceNormal = faceInfo.GetNormal();
            auto faceCenter = faceInfo.GetCenter();

            auto projNormStart = GetProjectionPointOnPlane(faceNormal, faceCenter, m_NormStart);
            auto projChainBase = GetProjectionPointOnPlane(faceNormal, faceCenter, m_ChainBase);

            // update the m_Visualizer
            angleVisualizer.m_LineChainBase->SetPts(m_ChainBase, projChainBase);
            parallelChainBaseDist = glm::distance(m_ChainBase, projChainBase);
            angleVisualizer.m_LineChainBase->SetColor(parallelChainBaseDist < 0.5f ? GOColor::GREEN : GOColor::WHITE);
        }
        else
        {
            angleVisualizer.Deactivate();
        }

        // extra orientation
        if (!m_NearestParallelFaceID.empty() or m_Cut->IsSingleFace())
        {
            m_CutOrientationVisualizer.Activate();

            // face and blade normal
            auto faceInfo = m_Cut->GetFace(m_NearestParallelFaceID);
            auto faceNormal = faceInfo.GetNormal();
            auto faceCenter = faceInfo.GetCenter();
            m_CutOrientationVisualizer.m_LineFaceNormal->SetPts(faceCenter, faceCenter + faceNormal);

            auto bladeNormal = glm::normalize(m_NormEnd - m_NormStart);
            m_CutOrientationVisualizer.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_NormEnd - m_NormStart) - glm::dot(m_NormEnd - m_NormStart, zVec) * zVec;

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

            // draw the rotated x axis as a GOLine
            m_CutOrientationVisualizer.m_LineDebugB->SetPts(faceCenter, faceCenter + xVec);
            m_CutOrientationVisualizer.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));
            m_CutOrientationVisualizer.m_LineDebugD->SetPts(
                m_CutOrientationVisualizer.m_LineBladeNormal->GetPEnd(),
                m_CutOrientationVisualizer.m_LineDebugB->GetPEnd());
            // draw the line between the end of the m_LineBladeNormal and the end of the lineDebugB
            m_CutOrientationVisualizer.m_LineDebugE->SetPts(
                m_CutOrientationVisualizer.m_LineBladeNormal->GetPEnd(),
                m_CutOrientationVisualizer.m_LineDebugC->GetPEnd());

            // calculare the angle between m_LineDebugE and m_LineDebugC (the pitch)
            float anglePitch = m_CutOrientationVisualizer.m_LineDebugD->ComputeSignedAngle(
                m_CutOrientationVisualizer.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_NormEnd - m_NormStart);
            glm::vec3 bladeXVec = glm::normalize(glm::cross(m_ChainMid - m_ChainBase, bladeZVec));
            glm::vec3 bladeYVec = glm::normalize(glm::cos(1.5708f) * bladeXVec + glm::sin(1.5708f) * bladeZVec);

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

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

            // set the visibility off for the debug lines
            m_CutOrientationVisualizer.m_LinePitchFeed->SetVisibility(true);
            m_CutOrientationVisualizer.m_GuideTxtRollPitch->SetVisibility(true);

        }
        else
        {
            m_CutOrientationVisualizer.Deactivate();
        }

        // Perpendicular face
        if(!m_NearestPerpendicularFaceID.empty() and !m_Cut->IsSingleFace())
        {
            hasPerpendicularFace = true;

            // find the projection point of the 2 points on the face
            auto faceInfo = m_Cut->GetFace(m_NearestPerpendicularFaceID);
            auto faceNormal = faceInfo.GetNormal();
            auto faceCenter = faceInfo.GetCenter();

            // Get the intersection line of the tool plane and the face plane
            glm::vec3 intersectLineVec, intersectLinePt;
            if(!GetIntersectLineOf2Planes(faceNormal, faceCenter,
                                          m_NormalVec, m_ChainBase,
                                          intersectLineVec, intersectLinePt)){
                AIAC_ERROR("Failed to get the intersect line of two planes");
                // Technically this should not happen
                // TODO: Error handling?
            }

            // Get the intersection point of the intersect line and the face's edges
            std::vector<glm::vec3> intersectPts;
            for(auto const& edgeID: faceInfo.GetEdges()){
                auto edge = m_Cut->GetEdge(edgeID);
                auto edgePt1 = edge.GetStartPt().GetPosition();
                auto edgePt2 = edge.GetEndPt().GetPosition();
                ExtendLineSeg(edgePt1, edgePt2, 0.5f);
                glm::vec3 intersectPt;
                if(GetIntersectPointOfLineAndLineSeg(intersectLineVec, intersectLinePt, edgePt1, edgePt2, intersectPt)) {
                    intersectPts.push_back(intersectPt);
                }
            }
            FormLongestLineSeg(intersectPts, perpIntersectLineSegPt1, perpIntersectLineSegPt2);

            // FIXME: here we should intersect instead of translate the lines at the end
            // TODO: clean up the thickness section
            // Thicknesses >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
            float bladeThicknessScaled = AC_FF_TOOL->GetData<ChainSawData>().ThicknessACIT;
            float overHangThicknessScaled = AC_FF_TOOL->GetData<ChainSawData>().OverhangACIT;
            float displacementTowardsCamera = overHangThicknessScaled;
            float displacementAwayFromCamera = bladeThicknessScaled - overHangThicknessScaled;

            // Lines based on tool
            auto projChainBase = GetNearestPtOnLine(intersectLineVec, intersectLinePt, m_ChainBase);
            auto projChainEnd = GetNearestPtOnLine(intersectLineVec, intersectLinePt, m_ChainEnd);
            glm::vec3 normalVec = glm::normalize(m_NormEnd - m_NormStart);
            auto projChainBaseTranslatedTwardsoCamera = projChainBase + normalVec * displacementTowardsCamera;
            auto projChainEndTranslatedTwardsoCamera = projChainEnd + normalVec * displacementTowardsCamera;
            depthVisualizer.m_LineIntersect->SetPts(projChainBaseTranslatedTwardsoCamera, projChainEndTranslatedTwardsoCamera);

            // depthVisualizer.m_LineIntersect->SetPts(projChainBase, projChainEnd);
            glm::vec3 oppositeNormalVec = -(glm::normalize(m_NormEnd - m_NormStart));
            auto projChainBaseTranslatedAwayFromCamera = projChainBase + oppositeNormalVec * displacementAwayFromCamera;
            auto projChainEndTranslatedAwayFromCamera = projChainEnd + oppositeNormalVec * displacementAwayFromCamera;
            depthVisualizer.m_LineIntersectThickness->SetPts(projChainBaseTranslatedAwayFromCamera, projChainEndTranslatedAwayFromCamera);
            // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

            // TODO: next is depth?? <<
            // Lines based on face edge
            // for face edge dist, we need to find the projection point of the two points on the saw first
            glm::vec3 pt1ProjPt, pt2ProjPt;
            auto pt1OnEndMid = GetNearestPtOnLine(m_ChainEnd - m_ChainMid, m_ChainEnd, perpIntersectLineSegPt1);
            auto pt1OnMidBase = GetNearestPtOnLine(m_ChainMid - m_ChainBase, m_ChainMid, perpIntersectLineSegPt1);
            auto pt2OnEndMid = GetNearestPtOnLine(m_ChainEnd - m_ChainMid, m_ChainEnd, perpIntersectLineSegPt2);
            auto pt2OnMidBase = GetNearestPtOnLine(m_ChainMid - m_ChainBase, m_ChainMid, perpIntersectLineSegPt2);

            bool pt1Found = false, pt2Found = false;
            if(IsPointBetweenLineSeg(pt1OnMidBase, m_ChainMid, m_ChainBase)){
                pt1ProjPt = pt1OnMidBase;
                pt1Found = true;
            } else if(IsPointBetweenLineSeg(pt1OnEndMid, m_ChainEnd, m_ChainMid)){
                pt1ProjPt = pt1OnEndMid;
                pt1Found = true;
            }
            if(IsPointBetweenLineSeg(pt2OnMidBase, m_ChainMid, m_ChainBase)){
                pt2ProjPt = pt2OnMidBase;
                pt2Found = true;
            } else if(IsPointBetweenLineSeg(pt2OnEndMid, m_ChainEnd, m_ChainMid)){
                pt2ProjPt = pt2OnEndMid;
                pt2Found = true;
            }
            if(pt1Found && pt2Found){
                depthVisualizer.m_LineDepthFaceEdge1->SetPts(perpIntersectLineSegPt1, pt1ProjPt);
                depthVisualizer.m_LineDepthFaceEdge2->SetPts(perpIntersectLineSegPt2, pt2ProjPt);

                perpendicularFaceEdge1Dist = glm::distance(perpIntersectLineSegPt1, pt1ProjPt);
                perpendicularFaceEdge2Dist = glm::distance(perpIntersectLineSegPt2, pt2ProjPt);
                float scaleFactor = AIAC::Config::Get<float>(AIAC::Config::SEC_AIAC, AIAC::Config::SCALE_FACTOR, 1.0f);
                float realPerpendicularFaceEdge1Dist = perpendicularFaceEdge1Dist / scaleFactor;
                float realPerpendicularFaceEdge2Dist = perpendicularFaceEdge2Dist / scaleFactor;

                // get the direction of tool
                auto toolUpVec = glm::normalize(m_NormStart - m_ChainBase);
                auto chainBaseVec = glm::normalize(m_ChainBase - projChainBase);
                auto chainEndVec = glm::normalize(m_ChainEnd - projChainEnd);
                auto faceEdge1Vec = glm::normalize(pt1ProjPt - perpIntersectLineSegPt1);
                auto faceEdge2Vec = glm::normalize(pt2ProjPt - perpIntersectLineSegPt2);

                if(glm::dot(toolUpVec, faceEdge1Vec) < 0){
                    depthVisualizer.m_LineDepthFaceEdge1->SetColor(GOColor::RED);
                    perpendicularFaceEdge1Dist = -perpendicularFaceEdge1Dist;
                } else {
                    depthVisualizer.m_LineDepthFaceEdge1->SetColor(GOColor::YELLOW);
                }
                if(glm::dot(toolUpVec, faceEdge2Vec) < 0){
                    depthVisualizer.m_LineDepthFaceEdge2->SetColor(GOColor::RED);
                    perpendicularFaceEdge2Dist = -perpendicularFaceEdge2Dist;
                } else {
                    depthVisualizer.m_LineDepthFaceEdge2->SetColor(GOColor::YELLOW);
                }

                // if the two guide lines have a close same distance than mark as yellow
                float diffPerpendicularFaceEdgeDist = std::abs(realPerpendicularFaceEdge1Dist - realPerpendicularFaceEdge2Dist);
                if(diffPerpendicularFaceEdgeDist < this->m_Visualizer.m_DistDepthAcceptance){
                    depthVisualizer.m_LineDepthFaceEdge1->SetColor(GOColor::GREEN);
                    depthVisualizer.m_LineDepthFaceEdge2->SetColor(GOColor::GREEN);
                }


                depthVisualizer.Activate();
            } else {
                depthVisualizer.Deactivate();
            }
        } else
        {
            depthVisualizer.Deactivate();
        }

        if(hasParallelFace || hasPerpendicularFace)
        {
            m_Visualizer.Activate();

            auto strEnd = FeedbackVisualizer::toString(parallelEndDist);
            auto strChainBase = FeedbackVisualizer::toString(parallelChainBaseDist);

            this->m_Visualizer.m_GuideTxtChainBase->SetText("s:"+strChainBase);
            this->m_Visualizer.m_GuideTxtFaceEdgeDepth->SetText("d:"+FeedbackVisualizer::toString(perpendicularFaceEdge2Dist));
            this->m_Visualizer.m_GuideTxtChainBase->SetAnchor(m_ChainBase);
            this->m_Visualizer.m_GuideTxtFaceEdgeDepth->SetAnchor(perpIntersectLineSegPt1);

            auto endColor = GOColor::WHITE;
            auto chainBaseColor = GOColor::WHITE;
            auto chainEndColor = GOColor::WHITE;

            auto faceEdgeTxt1Color = GOColor::BLACK;
            auto faceEdgeTxt2Color = GOColor::BLACK;

            if(parallelEndDist != 0 && parallelEndDist < 0.5f){
                endColor = GOColor::GREEN;
            }
            if(parallelChainBaseDist != 0 && parallelChainBaseDist < 0.5f){
                chainBaseColor = GOColor::GREEN;
            }

            if(perpendicularFaceEdge1Dist > 0 && perpendicularFaceEdge1Dist < 0.5f){
                faceEdgeTxt1Color = GOColor::GREEN;
            } else if (perpendicularFaceEdge1Dist < 0){
                faceEdgeTxt1Color = GOColor::RED;
            }
            if(perpendicularFaceEdge2Dist > 0 && perpendicularFaceEdge2Dist < 0.5f){
                faceEdgeTxt2Color = GOColor::ORANGE;
            } else if (perpendicularFaceEdge2Dist < 0){
                faceEdgeTxt2Color = GOColor::RED;
            }

            this->m_Visualizer.m_GuideTxtChainBase->SetColor(chainBaseColor);
            this->m_Visualizer.m_GuideTxtFaceEdgeDepth->SetColor(faceEdgeTxt2Color);
        }
        else m_Visualizer.Deactivate();
    }

    void CutChainSawFeedback::Activate()
    {
        Update();
        if(m_ToShowCutPlane)
        {
            this->m_CutPlaneVisualizer.Activate();
        }
    }

    void CutChainSawFeedback::Deactivate()
    {
        this->m_Visualizer.Deactivate();

        this->m_CutPlaneVisualizer.Deactivate();
        this->m_Visualizer.m_AngleFeedVisualizer.Deactivate();
        this->m_Visualizer.m_DepthFeedVisualizer.Deactivate();

        this->m_CutOrientationVisualizer.Deactivate();
    }
}