/*****************************************************************************
 * $CAMITK_LICENCE_BEGIN$
 *
 * CamiTK - Computer Assisted Medical Intervention ToolKit
 * (c) 2001-2025 Univ. Grenoble Alpes, CNRS, Grenoble INP - UGA, TIMC, 38000 Grenoble, France
 *
 * Visit http://camitk.imag.fr for more information
 *
 * This file is part of CamiTK.
 *
 * CamiTK is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * CamiTK is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License version 3 for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with CamiTK.  If not, see <http://www.gnu.org/licenses/>.
 *
 * $CAMITK_LICENCE_END$
 ****************************************************************************/

#include "Geometry.h"

//-- vtk stuff
// disable warning generated by clang about the surrounded headers
#include "CamiTKDisableWarnings"
#include <vtkDataSetMapper.h>
#include <vtkActor.h>
#include <vtkTexture.h>
#include <vtkProperty.h>
#include <vtkTextMapper.h>
#include <vtkViewport.h>
#include "CamiTKReEnableWarnings"

#include <vtkUnstructuredGrid.h>
#include <vtkStructuredGrid.h>
#include <vtkDataSet.h>
#include <vtkGlyph3D.h>
#include <vtkTransform.h>
#include <vtkTubeFilter.h>
#include <vtkCastToConcrete.h>
#include <vtkCallbackCommand.h>
#include <vtkTextProperty.h>
#include <vtkSphereSource.h>
#include <vtkAlgorithmOutput.h>
#include <vtkPointData.h>
#include <vtkActor2D.h>
#include <vtkMath.h>
#include <vtkLookupTable.h>
#include <vtkTransformFilter.h>
#include <vtkIdList.h>

namespace camitk {
//------------------------------- constructor -------------------------------
Geometry::Geometry(QString label, vtkSmartPointer<vtkPointSet> pointSetPtr, const RenderingModes renderingModes) {

    this->label = label;

    // the Geometry takes control of the vtkPointSet
    // We need to use a deepcopy because of some strange behaviour of mingw
    // (In mingw variable seems to become out of scope when going from one DLL to another one,
    // which generates a loss of smartness in the vtkSmartPointer)
    // TODO use a MeshUtil class for typeOf?
    if (pointSetPtr->IsA("vtkPolyData")) {
        pointSet = vtkSmartPointer<vtkPolyData>::New();
        vtkPolyData::SafeDownCast(pointSet)->Allocate();
    }
    else if (pointSetPtr->IsA("vtkUnstructuredGrid")) {
        pointSet = vtkSmartPointer<vtkUnstructuredGrid>::New();
        vtkUnstructuredGrid::SafeDownCast(pointSet)->Allocate();
    }
    else if (pointSetPtr->IsA("vtkStructuredGrid")) {
        pointSet = vtkSmartPointer<vtkStructuredGrid>::New();
    }
    // Disconnect the previous Vtk Pipeline without memcopy
    // (equivalent of ITK DisconnectPipeline()...)
    // Note : this (i.e disconnect/deepcopy/shallowcopy) should not have to be done
    // in the components...
    pointSet->ShallowCopy(pointSetPtr);

    this->renderingModes = renderingModes;

    // vtk stuff
    // By default, nothing is created.
    // The vtkThings are only created when required (lazy initialization design-pattern)
    surfaceActor = nullptr;
    wireframeActor = nullptr;
    pointsActor = nullptr;
    tube = nullptr;
    texture = nullptr;
    worldTransformFilter = nullptr;

    // display options
    enhancedModes = InterfaceGeometry::Normal;

    //-- init color
    for (unsigned int i = 0; i < 3; i++) {
        surfaceColor[i] = 0.8;
    }
    surfaceColor[3] = 1.0;

    wireframeColor[0] = 1.0;
    wireframeColor[1] = 0.0;
    wireframeColor[2] = 0.0;
    wireframeColor[3] = 1.0;

    pointsColor[0] = 0.0;
    pointsColor[1] = 0.0;
    pointsColor[2] = 1.0;
    pointsColor[3] = 1.0;

    alphaShaded = 0.2;

    //-- init generic filter and dataOutput and customPipelineOuput
    concreteData = vtkSmartPointer<vtkCastToConcrete>::New();
    concreteData->SetInputData(this->pointSet);

    worldTransformFilter = vtkSmartPointer<vtkTransformFilter>::New();
    vtkSmartPointer<vtkTransform> transform = vtkSmartPointer<vtkTransform>::New();
    transform->Identity();
    worldTransformFilter->SetTransform(transform);
    worldTransformFilter->SetInputConnection(concreteData->GetOutputPort());

    dataOutput = worldTransformFilter->GetOutputPort();
    customPipelineOutput = dataOutput;

    //-- init mapper
    mapper = vtkSmartPointer<vtkDataSetMapper>::New();
    mapper->SetInputConnection(customPipelineOutput);
    mapper->SetResolveCoincidentTopologyToPolygonOffset();

    // set the rainbow lookup table from blue to red
    vtkSmartPointer<vtkLookupTable> lut = vtkSmartPointer<vtkLookupTable>::New();
    lut->SetHueRange(0.667, 0.0);
    mapper->SetLookupTable(lut);

    //-- update glyph type size
    setGlyphType(InterfaceGeometry::Sphere);

    //-- make sure there is at least one cell
    createPointCloudVisualization();
}

//------------------------------- destructor -------------------------------
Geometry::~Geometry() {
    // no memory was allocated, thanks to vtkSmartPointers...

    // just remove the extra prop from the visualization
    // warning: this has to be duplicate of code from the removeProp(..) method
    // as the removal is needed here in the middle of the loop.
    QMutableMapIterator<QString, vtkSmartPointer<vtkProp> > i(extraProp);
    while (i.hasNext()) {
        i.next();
        // remove the prop from any renderer/consummers
        vtkSmartPointer<vtkProp> prop = extraProp.value(i.key());
        prop->VisibilityOff();
        for (int ic = 0; ic < prop->GetNumberOfConsumers(); ic++) {
            vtkViewport* viewer = vtkViewport::SafeDownCast(prop->GetConsumer(ic));
            if (viewer != nullptr) {
                viewer->RemoveViewProp(prop);
            }
        }
        // remove it from the maps
        i.remove();
    }

}

//------------------------------- setPointSet -------------------------------
void Geometry::setPointSet(vtkSmartPointer<vtkPointSet> ps) {
    if (pointSet)
        // Disconnect the previous Vtk Pipeline without memcopy
        // (equivalent of ITK DisconnectPipeline()...)
        // Note : this (i.e disconnect/deepcopy/shallowcopy) should not have to be done
        // in the components...
    {
        pointSet->ShallowCopy(ps);
    }
    else {
        pointSet = ps;
    }

    //-- update glyph type size
    setGlyphType(InterfaceGeometry::Sphere);

    //-- make sure there is at least one cell
    createPointCloudVisualization();
}

//------------------------------- createPointCouldVisualization -------------------------------
void Geometry::createPointCloudVisualization() {
    // assert: pointset is a valid pointset
    if (pointSet->GetNumberOfCells() == 0) {
        // create the point ids for the unique cell (contains all points)
        vtkSmartPointer<vtkIdList> idList = vtkSmartPointer<vtkIdList>::New();
        for (vtkIdType i = 0; i < pointSet->GetNumberOfPoints(); i++) {
            idList->InsertNextId(i);
        }

        // add the cells depending on the point set subtype
        // if the pointset is a structured grid: nothing to do as a structured grid has a specific intern representation with cells.
        if (pointSet->IsA("vtkPolyData")) {
            vtkSmartPointer< vtkPolyData > result = vtkPolyData::SafeDownCast(pointSet);
            result->Allocate(1);
            result->InsertNextCell(VTK_POLY_VERTEX, idList);
        }
        else if (pointSet->IsA("vtkUnstructuredGrid"))     {
            vtkSmartPointer< vtkUnstructuredGrid > result = vtkUnstructuredGrid::SafeDownCast(pointSet);
            result->Allocate(1);
            result->InsertNextCell(VTK_POLY_VERTEX, idList);
        }

    }
}

//------------------------------- setMeshWorldTransform -------------------------------
void Geometry::setMeshWorldTransform(vtkSmartPointer<vtkTransform> transform) {
    // Do not delete vtkSmartTransform previous transform, but set it to nullptr to
    // decrement its counter
    worldTransformFilter->SetTransform(transform);
    worldTransformFilter->Update();
}

//------------------------------- setDataConnection -------------------------------
void Geometry::setDataConnection(vtkSmartPointer<vtkAlgorithmOutput> outputPort) {
    if (outputPort == nullptr) {
        customPipelineOutput = dataOutput;
    }
    else {
        customPipelineOutput = outputPort;
    }

    if (tube) {
        tube->SetInputConnection(customPipelineOutput);
    }
    else {
        mapper->SetInputConnection(customPipelineOutput);
    }

}

//------------------------------- getActor -------------------------------
vtkSmartPointer<vtkActor> Geometry::getActor(RenderingModes mode) {
    if (enhancedModes & InterfaceGeometry::Hidden) {
        return nullptr;
    }

    if ((renderingModes  & InterfaceGeometry::Surface) && (mode & InterfaceGeometry::Surface)) {
        if (!surfaceActor) {
            // This actor does not exist yet; create it.
            surfaceActor = vtkSmartPointer<vtkActor>::New();
            surfaceActor->SetMapper(mapper);
            setActorColor(InterfaceGeometry::Surface, surfaceColor);
            if (texture) {
                surfaceActor->SetTexture(texture);
            }

            // surface light
            surfaceActor->GetProperty()->SetInterpolationToPhong();
            surfaceActor->GetProperty()->SetDiffuse(.9);
            surfaceActor->GetProperty()->SetSpecular(.1);
            surfaceActor->GetProperty()->SetAmbient(.1);
            surfaceActor->GetProperty()->SetSpecularColor(.4, .4, .4);
            surfaceActor->GetProperty()->SetSpecularPower(100);

            // picking
            surfaceActor->PickableOn();
        }
        return surfaceActor;
    }
    else if ((renderingModes & InterfaceGeometry::Wireframe) && (mode & InterfaceGeometry::Wireframe)) {
        if (!wireframeActor) {
            // This actor doesn't exist yet; create it.
            wireframeActor = vtkSmartPointer<vtkActor>::New();
            wireframeActor->SetMapper(mapper);
            wireframeActor->GetProperty()->SetRepresentationToWireframe();
            wireframeActor->GetProperty()->SetLineWidth(1.0);
            setActorColor(InterfaceGeometry::Wireframe, wireframeColor);

            // picking
            wireframeActor->PickableOn();
        }
        return wireframeActor;
    }
    else if ((renderingModes & InterfaceGeometry::Points) && (mode & InterfaceGeometry::Points)) {
        if (!pointsActor) {
            // This actor doesn't exist yet; create it.
            pointsActor = vtkSmartPointer<vtkActor>::New();
            pointsActor->SetMapper(mapper);
            pointsActor->GetProperty()->SetRepresentationToPoints();
            setActorColor(InterfaceGeometry::Points, pointsColor);

            // picking
            pointsActor->PickableOn();
        }
        return pointsActor;
    }
    else {
        return nullptr;
    }
}


//------------------------------- buildGlyph -------------------------------
void Geometry::buildGlyph(const GlyphTypes type) {
    if (!extraProp.contains("glyph")) {
        // the glyph attached to this Geometry
        vtkSmartPointer<vtkGlyph3D> glyph3D = vtkSmartPointer<vtkGlyph3D>::New();

        // the glyph geometry
        vtkSmartPointer<vtkPolyDataAlgorithm> glyphGeom;
        switch (type) {
            case Sphere: {
                // create sphere
                sphereGeom = vtkSmartPointer<vtkSphereSource>::New();
                sphereGeom->SetRadius(glyphSize);
                glyphGeom = sphereGeom;
                break;
            }
            default:
                break;
        }
        // connect the glyph
        glyphGeom->Update();
        glyph3D->SetSourceConnection(glyphGeom->GetOutputPort());

        // check tubing pipeline
        if (tube) {
            glyph3D->SetInputConnection(tube->GetOutputPort());
        }
        else {
            glyph3D->SetInputConnection(customPipelineOutput);
        }

        // create a mapper and actor for the glyph
        vtkSmartPointer<vtkDataSetMapper> glyphActorMapper = vtkSmartPointer<vtkDataSetMapper>::New();
        glyphActorMapper->SetInputConnection(glyph3D->GetOutputPort());
        vtkSmartPointer<vtkActor> glyphActor = vtkSmartPointer<vtkActor>::New();
        glyphActor->SetMapper(glyphActorMapper);

        // compute color
        double rgb[4], hsv[3];
        getActorColor(Surface, rgb);
        // if the color is not defined (i.e. black) or gray -> full red
        if ((rgb[0] == 0.0 && rgb[1] == 0.0 && rgb[2] == 0.0) || (rgb[0] == rgb[1] && rgb[1] == rgb[2])) {
            rgb[0] = 1.0;
        }
        else {
            // compute complementary color
            vtkMath::RGBToHSV(rgb, hsv);
            hsv[0] = int(hsv[0] + 180.0) % 360;
            double newV = hsv[2] * (hsv[1] - 1.0) + 1.0;
            hsv[1] = hsv[1] * hsv[2] / newV;
            hsv[2] = newV;
            vtkMath::HSVToRGB(hsv, rgb);
            // reduce intensity (too bright is not nice)
            for (unsigned int i = 0; i < 3; i++) {
                if (rgb[i] >= 1.0) {
                    rgb[i] = 1.0;
                }
                if (rgb[i] >= 0.9) {
                    rgb[i] *= .8;
                }
            }
        }
        glyphActor->GetProperty()->SetColor(rgb);
        glyphActor->SetVisibility(false);
        // insert the glyph in the visualization pipeline, as an extraprop
        addProp("glyph", glyphActor);
    }
    else {
        sphereGeom->SetRadius(glyphSize);
        sphereGeom->Update();
    }
}

//------------------------------- buildLabelProp -------------------------------
void Geometry::buildLabelProp() {
    if (!extraProp.contains("label")) {
        vtkSmartPointer<vtkTextProperty> tprop = vtkSmartPointer<vtkTextProperty>::New();
        tprop->SetFontSize(14);
        tprop->SetFontFamilyToArial();
        tprop->SetJustificationToLeft();
        tprop->BoldOn();
        tprop->ItalicOn();
        tprop->ShadowOn();

        labelActorMapper = vtkSmartPointer<vtkTextMapper>::New();
        labelActorMapper->SetTextProperty(tprop);

        vtkSmartPointer<vtkActor2D> labelActor = vtkSmartPointer<vtkActor2D>::New();
        labelActor->GetPositionCoordinate()->SetCoordinateSystemToWorld();
        labelActor->SetMapper(labelActorMapper);
        labelActor->SetVisibility(false);
        addProp("label", labelActor);

        updateLabelProp();
    }
}

//------------------------------- updateLabelProp -------------------------------
void Geometry::updateLabelProp() {
    if (extraProp.contains("label")) {
        //-- update the text
        labelActorMapper->SetInput(label.toStdString().c_str());

        //-- update the position
        double position[3];
        pointSet->GetCenter(position);

        vtkActor2D::SafeDownCast(extraProp.value("label"))->GetPositionCoordinate()->SetValue(position[0], position[1], position[2]);
    }
}

//------------------------------- updateLabel -------------------------------
void Geometry::updateLabel(const QString& label) {
    this->label = label;
    updateLabelProp();
}

//------------------------------- addProp -------------------------------
bool Geometry::addProp(const QString& name, vtkSmartPointer< vtkProp > prop) {
    if (!extraProp.contains(name)) {
        extraProp.insert(name, prop);
        return true;
    }
    else {
        return false;
    }
}

//------------------------------- getProp -------------------------------
vtkSmartPointer< vtkProp > Geometry::getProp(const QString& name) {
    // check for extra prop lazy instantiation
    if (name == "glyph") {
        buildGlyph(Sphere);
    }
    else if (name == "label") {
        buildLabelProp();
    }

    if (extraProp.contains(name)) {
        return extraProp.value(name);
    }
    else {
        return nullptr;
    }
}

//------------------------------- getNumberOfProp -------------------------------
unsigned int Geometry::getNumberOfProp() const {
    return extraProp.size();
}

//------------------------------- getProp -------------------------------
vtkSmartPointer< vtkProp > Geometry::getProp(unsigned int index) {
    return extraProp.values().at(index);
}

//------------------------------- removeProp -------------------------------
bool Geometry::removeProp(const QString& name) {
    if (extraProp.contains(name)) {
        // remove the prop from any renderer/consummers
        vtkSmartPointer<vtkProp> prop = extraProp.value(name);
        prop->VisibilityOff();
        for (int i = 0; i < prop->GetNumberOfConsumers(); i++) {
            vtkViewport* viewer = vtkViewport::SafeDownCast(prop->GetConsumer(i));
            if (viewer) {
                viewer->RemoveViewProp(prop);
            }
        }
        // remove it from the maps
        extraProp.remove(name);
        return true;
    }
    else {
        return false;
    }
}

//------------------------------- setPointPosition -------------------------------
void Geometry::setPointPosition(const unsigned int orderNumber, const double x, const double y, const double z) {
    pointSet->GetPoints()->SetPoint(orderNumber, x, y, z);
    pointSet->Modified();
}

//------------------------------- setActorColor -------------------------------
void Geometry::setActorColor(const RenderingModes mode, double* color) {
    if (mode & InterfaceGeometry::Surface) {
        for (unsigned int i = 0; i < 4; i++) {
            surfaceColor[i] = color[i];
        }
        if (surfaceActor) {
            surfaceActor->GetProperty()->SetColor(color);
            surfaceActor->GetProperty()->SetOpacity(color[3]);
        }
    }

    if (mode & InterfaceGeometry::Wireframe) {
        for (unsigned int i = 0; i < 4; i++) {
            wireframeColor[i] = color[i];
        }
        if (wireframeActor) {
            wireframeActor->GetProperty()->SetColor(color);
            wireframeActor->GetProperty()->SetEdgeColor(color);
            wireframeActor->GetProperty()->SetOpacity(color[3]);
        }
    }

    if (mode & InterfaceGeometry::Points) {
        for (unsigned int i = 0; i < 4; i++) {
            pointsColor[i] = color[i];
        }
        if (pointsActor) {
            pointsActor->GetProperty()->SetColor(color);
            pointsActor->GetProperty()->SetOpacity(color[3]);
        }
    }
}

//------------------------------- setActorColor -------------------------------
void Geometry::setActorColor(const RenderingModes mode, const double r, const double g, const double b) {
    double color[4];
    color[0] = r;
    color[1] = g;
    color[2] = b;
    color[3] = getActorOpacity(mode);
    setActorColor(mode, color);
}

//------------------------------- getActorColor -------------------------------
void Geometry::getActorColor(const RenderingModes mode, double* color, bool ignoreEnhancedModes) const {
    if (mode & InterfaceGeometry::Surface) {
        for (unsigned int i = 0; i < 4; i++) {
            color[i] = surfaceColor[i];
        }
        if (ignoreEnhancedModes && enhancedModes != EnhancedMode::Normal) {
            color[3] = oldAlphaSurface;
        }
    }
    else if (mode & InterfaceGeometry::Wireframe) {
        for (unsigned int i = 0; i < 4; i++) {
            color[i] = wireframeColor[i];
        }
        if (ignoreEnhancedModes && enhancedModes != EnhancedMode::Normal) {
            color[3] = oldAlphaWireframe;
        }
    }
    else if (mode & InterfaceGeometry::Points) {
        for (unsigned int i = 0; i < 4; i++) {
            color[i] = pointsColor[i];
        }
        if (ignoreEnhancedModes && enhancedModes != EnhancedMode::Normal) {
            color[3] = oldAlphaPoints;
        }
    }
    else {
        for (unsigned int i = 0; i < 4; i++) {
            color[i] = 0.0;
        }
    }
}

//------------------------------- setColor -------------------------------
void Geometry::setColor(const double r, const double g, const double b) {
    setActorColor(InterfaceGeometry::Surface | InterfaceGeometry::Wireframe | InterfaceGeometry::Points, r, g, b);
}

//------------------------------- setColor -------------------------------
void Geometry::setColor(const double r, const double g, const double b, const double a) {
    double color[4];
    color[0] = r;
    color[1] = g;
    color[2] = b;
    color[3] = a;

    setActorColor(InterfaceGeometry::Surface | InterfaceGeometry::Wireframe | InterfaceGeometry::Points, color);
}

//------------------------------- setActorOpacity -------------------------------
void Geometry::setActorOpacity(const RenderingModes mode, const double alpha) {
    // opacity value must be bounded between 0 and 1
    double opacity = alpha;

    if (alpha < 0.0) {
        opacity = 0.0;
    }
    else if (alpha > 1.0) {
        opacity = 1.0;
    }

    if (mode & InterfaceGeometry::Surface) {
        surfaceColor[3] = opacity;
        if (surfaceActor) {
            surfaceActor->GetProperty()->SetOpacity(opacity);
        }
    }

    if (mode & InterfaceGeometry::Wireframe) {
        wireframeColor[3] = opacity;
        if (wireframeActor) {
            wireframeActor->GetProperty()->SetOpacity(opacity);
        }
    }

    if (mode & InterfaceGeometry::Points) {
        pointsColor[3] = opacity;
        if (pointsActor) {
            pointsActor->GetProperty()->SetOpacity(opacity);
        }
    }

    pointSet->Modified(); // force an update on the vtk pipeline

}

//------------------------------- getActorOpacity -------------------------------
double Geometry::getActorOpacity(const RenderingModes mode) const {
    if (mode & InterfaceGeometry::Surface) {
        return surfaceColor[3];
    }
    else if (mode & InterfaceGeometry::Wireframe) {
        return wireframeColor[3];
    }
    else if (mode & InterfaceGeometry::Points) {
        return pointsColor[3];
    }
    else {
        return 0.0;
    }
}

//------------------------------- setOpacity -------------------------------
void Geometry::setOpacity(const double alpha) {
    setActorOpacity(InterfaceGeometry::Surface | InterfaceGeometry::Wireframe | InterfaceGeometry::Points, alpha);
}

//------------------------------- setMapperScalarRange -------------------------------
void Geometry::setMapperScalarRange(double min, double max) {
    mapper->SetScalarRange(min, max);
}

//------------------------------- setTexture -------------------------------
void Geometry::setTexture(vtkSmartPointer<vtkTexture> texture) {
    this->texture = texture;
    mapper->SetScalarVisibility(0);

    // texture is applied only on surface actor, not wireframe nor points one
    if (surfaceActor) {
        surfaceActor->SetTexture(texture);
    }
}

// -------------------- setGlyphType --------------------
void Geometry::setGlyphType(const GlyphTypes type, const double size) {

    // update size
    if (type == NoGlyph) {
        getProp("glyph")->VisibilityOff();
        // update size to 0
        glyphSize = 0.0;
        // nothing else to do!
    }
    else {
        // check size
        if (size == 0.0) {
            // glyph is a not NoGlyph and size is zero, use a default value
            glyphSize = getBoundingRadius() / 100.0;
        }
        else {
            glyphSize = size; // can not be 0.0
        }
        // build the new glyph (get it ready)
        buildGlyph(type);
    }

}

// -------------------- setEnhancedModes --------------------
void Geometry::setEnhancedModes(const EnhancedModes emode) {
    // set hightlighted
    if (!(enhancedModes & InterfaceGeometry::Highlighted) && (emode & InterfaceGeometry::Highlighted)) {
        // backup opacity
        oldAlphaSurface = surfaceColor[3];
        oldAlphaWireframe = wireframeColor[3];
        oldAlphaPoints = pointsColor[3];

        // Force opacity: set actors completely opaque (opacity = 1.0)
        setOpacity(1.0);

        // display the object in wireframe as well
        renderingModes |= InterfaceGeometry::Wireframe;
    }

    // unset hightlighted
    if ((enhancedModes & InterfaceGeometry::Highlighted) && !(emode & InterfaceGeometry::Highlighted)) {
        // restore the original alpha values
        setActorOpacity(InterfaceGeometry::Surface, oldAlphaSurface);
        setActorOpacity(InterfaceGeometry::Wireframe, oldAlphaWireframe);
        setActorOpacity(InterfaceGeometry::Points, oldAlphaPoints);

        // if before the highlight, the wireframe isn't visible
        //      we restore the last display
        renderingModes ^= InterfaceGeometry::Wireframe;
    }

    // set shaded
    if (!(enhancedModes & InterfaceGeometry::Shaded) && (emode & InterfaceGeometry::Shaded)) {
        // If this object is highlighted, unhighlight it
        if (enhancedModes & InterfaceGeometry::Highlighted) {
            setActorOpacity(InterfaceGeometry::Surface, oldAlphaSurface);
            setActorOpacity(InterfaceGeometry::Wireframe, oldAlphaWireframe);
            setActorOpacity(InterfaceGeometry::Points, oldAlphaPoints);
            renderingModes ^= InterfaceGeometry::Wireframe;
        }

        // backup opacity
        oldAlphaSurface = surfaceColor[3];
        oldAlphaWireframe = wireframeColor[3];
        oldAlphaPoints = pointsColor[3];

        // Force opacity: set actors to be relatively transparent
        setOpacity(alphaShaded);
    }

    if ((enhancedModes & InterfaceGeometry::Shaded) && !(emode & InterfaceGeometry::Shaded)) {
        setActorOpacity(InterfaceGeometry::Surface, oldAlphaSurface);
        setActorOpacity(InterfaceGeometry::Wireframe, oldAlphaWireframe);
        setActorOpacity(InterfaceGeometry::Points, oldAlphaPoints);
    }

    // update the state
    enhancedModes = emode;
}

// -------------------- setLinesAsTubes --------------------
void Geometry::setLinesAsTubes(bool isTubes, bool radiusFromLength, double radiusFactor, int numberOfSides) {
    // only works on polydata that contains only lines
    vtkSmartPointer<vtkPolyData> polyData = vtkPolyData::SafeDownCast(pointSet);

    if (polyData && isTubes && !tube) {
        // check if the tube filter is really needed!
        if (polyData->GetNumberOfVerts() == 0 && polyData->GetNumberOfLines() > 0 && polyData->GetNumberOfPolys() == 0 && polyData->GetNumberOfStrips() == 0) {
            //-- create the tube filter
            tube = vtkSmartPointer<vtkTubeFilter>::New();
            tube->SetVaryRadiusToVaryRadiusOff();
            if (radiusFromLength) {
                tube->SetRadius(pointSet->GetLength()* radiusFactor);  //maybe radius proportions should be defined better
            }
            else {
                tube->SetRadius(radiusFactor);
            }

            tube->SetNumberOfSides(numberOfSides);
            //-- update the pipeline
            tube->SetInputConnection(customPipelineOutput);
            mapper->SetInputConnection(tube->GetOutputPort());
        }
    }
    else {
        mapper->SetInputConnection(customPipelineOutput);
        tube = nullptr;
    }

}

// -------------------- getBounds --------------------
void Geometry::getBounds(double* bounds) {
    pointSet->GetBounds(bounds);
}

// -------------------- getBoundingRadius --------------------
double Geometry::getBoundingRadius() {
    double radius = 0.0;
    if (pointSet->GetNumberOfPoints() == 1) {
        //-- update the position
        double pos[3] = {0.0, 0.0, 0.0};
        pointSet->GetPoint(0, pos);
        // radius = position
        radius = sqrt(pos[0] * pos[0] + pos[1] * pos[1] + pos[2] * pos[2]);
    }
    else {
        double bb[6];
        getBounds(bb);
        double xLength, yLength, zLength;
        xLength = fabs(bb[1] - bb[0]);
        yLength = fabs(bb[3] - bb[2]);
        zLength = fabs(bb[5] - bb[4]);
        radius = sqrt(xLength * xLength + yLength * yLength + zLength * zLength) / 2.0;
    }
    return radius;
}

// -------------------- setPointData --------------------
void Geometry::setPointData(vtkSmartPointer<vtkDataArray> da) {
    pointSet->GetPointData()->SetScalars(da);
    if (da) {
        if (da->GetName() == nullptr) {
            da->SetName("Point Data");
        }
        pointSet->GetPointData()->SetActiveScalars(da->GetName());
        // rescale colors
        mapper->SetScalarVisibility(1);
        double range[2] = {0.0, 1.0};
        pointSet->GetPointData()->GetScalars(da->GetName())->GetRange(range);
        mapper->SetScalarRange(range);
    }
    // make sure the change is taken into account
    updateLabelProp();
    pointSet->Modified();
    mapper->Update();
}

// -------------------- setColorMode --------------------
void Geometry::setColorMode(int vtkColorMode) {
    // VTK_COLOR_MODE_DEFAULT VTK_COLOR_MODE_MAP_SCALARS VTK_COLOR_MODE_DIRECT_SCALARS
    mapper->SetColorMode(vtkColorMode);
}

}



