using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;
using System.Xml;
using System.Xml.Linq;
using System;
using System.IO;
using System.Globalization;

//source: https://numerics.mathdotnet.com/
using MathNet.Numerics.LinearAlgebra;
//NuGet in Unity editor source: https://github.com/GlitchEnzo/NuGetForUnity

// source: https://github.com/SrejonKhan/AnotherFileBrowser
using AnotherFileBrowser.Windows;


[RequireComponent(typeof(MeshFilter))]
public class RoscanModelUnity : MonoBehaviour
{
    #region Enums
    
    // Enum of information of ModelLayers. Which one is avaible layer.
    public enum ModelLayers
    {
        NONE = 0,
        SURFACE = 1,
        REFLECTIVITY = 2,
        REFL_WIDTH = 4,
        TEMPERATURE = 8,
        COLOR = 16,
        DISTANCE = 32
    }

    #endregion

    #region constants
    // Color of the point
    private readonly Color COLOR_NO_VALUE = new Color(255, 255, 255, 255);

    // Palette scales. Palette's values from XMLHelper.cs
    public const float EPSILON = 1e-005f;
    public readonly Color[] THERMO_SCALE = new Color[] { new Color(0, 17, 24), new Color(0, 17, 24), new Color(0, 17, 24), new Color(0, 17, 24), new Color(0, 16, 29), new Color(0, 14, 36), new Color(0, 14, 36), new Color(0, 13, 41), new Color(0, 13, 41), new Color(0, 12, 47), new Color(0, 12, 47), new Color(0, 11, 54), new Color(0, 9, 59), new Color(0, 9, 59), new Color(0, 8, 64), new Color(0, 8, 64), new Color(0, 7, 69), new Color(0, 7, 69), new Color(0, 6, 75), new Color(0, 6, 75), new Color(0, 4, 80), new Color(1, 4, 85), new Color(1, 4, 85), new Color(5, 3, 88), new Color(5, 3, 88), new Color(10, 2, 93), new Color(10, 2, 93), new Color(14, 1, 99), new Color(17, 1, 102), new Color(17, 1, 102), new Color(21, 0, 107), new Color(21, 0, 107), new Color(26, 0, 110), new Color(26, 0, 110), new Color(28, 0, 113), new Color(28, 0, 113), new Color(33, 0, 116), new Color(36, 0, 122), new Color(36, 0, 122), new Color(40, 0, 125), new Color(40, 0, 125), new Color(44, 0, 128), new Color(44, 0, 128), new Color(47, 0, 131), new Color(51, 0, 134), new Color(51, 0, 134), new Color(54, 0, 136), new Color(54, 0, 136), new Color(57, 0, 139), new Color(57, 0, 139), new Color(61, 0, 142), new Color(64, 0, 143), new Color(64, 0, 143), new Color(67, 0, 145), new Color(67, 0, 145), new Color(70, 0, 148), new Color(70, 0, 148), new Color(73, 0, 149), new Color(73, 0, 149), new Color(77, 0, 151), new Color(80, 0, 154), new Color(80, 0, 154), new Color(83, 0, 155), new Color(83, 0, 155), new Color(85, 0, 156), new Color(85, 0, 156), new Color(90, 0, 157), new Color(92, 0, 158), new Color(92, 0, 158), new Color(95, 0, 160), new Color(95, 0, 160), new Color(98, 0, 161), new Color(98, 0, 161), new Color(101, 0, 162), new Color(101, 0, 162), new Color(105, 0, 163), new Color(108, 0, 164), new Color(108, 0, 164), new Color(111, 0, 165), new Color(111, 0, 165), new Color(113, 0, 167), new Color(113, 0, 167), new Color(116, 0, 166), new Color(119, 0, 167), new Color(119, 0, 167), new Color(122, 0, 168), new Color(122, 0, 168), new Color(124, 0, 167), new Color(124, 0, 167), new Color(127, 0, 168), new Color(127, 0, 168), new Color(130, 0, 168), new Color(133, 0, 167), new Color(133, 0, 167), new Color(135, 0, 168), new Color(135, 0, 168), new Color(138, 0, 167), new Color(138, 0, 167), new Color(139, 1, 166), new Color(142, 1, 167), new Color(142, 1, 167), new Color(145, 2, 166), new Color(145, 2, 166), new Color(148, 3, 166), new Color(148, 3, 166), new Color(150, 4, 165), new Color(150, 4, 165), new Color(153, 4, 164), new Color(154, 6, 163), new Color(154, 6, 163), new Color(157, 7, 162), new Color(157, 7, 162), new Color(160, 7, 161), new Color(160, 7, 161), new Color(161, 9, 160), new Color(164, 10, 160), new Color(164, 10, 160), new Color(167, 10, 159), new Color(167, 10, 159), new Color(168, 12, 158), new Color(168, 12, 158), new Color(170, 13, 157), new Color(170, 13, 157), new Color(173, 14, 154), new Color(174, 15, 153), new Color(174, 15, 153), new Color(177, 16, 152), new Color(177, 16, 152), new Color(178, 18, 152), new Color(178, 18, 152), new Color(181, 19, 149), new Color(182, 20, 148), new Color(182, 20, 148), new Color(185, 21, 147), new Color(185, 21, 147), new Color(186, 23, 144), new Color(186, 23, 144), new Color(189, 24, 143), new Color(189, 24, 143), new Color(190, 26, 140), new Color(193, 27, 140), new Color(193, 27, 140), new Color(194, 28, 137), new Color(194, 28, 137), new Color(197, 29, 136), new Color(197, 29, 136), new Color(198, 31, 133), new Color(199, 33, 132), new Color(199, 33, 132), new Color(202, 34, 129), new Color(202, 34, 129), new Color(203, 35, 128), new Color(203, 35, 128), new Color(203, 36, 124), new Color(203, 36, 124), new Color(206, 37, 121), new Color(207, 39, 121), new Color(207, 39, 121), new Color(208, 41, 118), new Color(208, 41, 118), new Color(211, 42, 115), new Color(211, 42, 115), new Color(212, 43, 114), new Color(213, 45, 111), new Color(213, 45, 111), new Color(214, 47, 108), new Color(214, 47, 108), new Color(216, 49, 107), new Color(216, 49, 107), new Color(218, 50, 105), new Color(218, 50, 105), new Color(219, 52, 102), new Color(221, 54, 99), new Color(221, 54, 99), new Color(222, 56, 98), new Color(222, 56, 98), new Color(223, 57, 95), new Color(223, 57, 95), new Color(224, 59, 92), new Color(225, 61, 91), new Color(225, 61, 91), new Color(226, 63, 89), new Color(226, 63, 89), new Color(228, 65, 86), new Color(228, 65, 86), new Color(229, 67, 83), new Color(229, 67, 83), new Color(230, 69, 80), new Color(231, 70, 79), new Color(231, 70, 79), new Color(232, 72, 76), new Color(232, 72, 76), new Color(233, 74, 73), new Color(233, 74, 73), new Color(235, 76, 70), new Color(236, 78, 70), new Color(236, 78, 70), new Color(237, 80, 67), new Color(237, 80, 67), new Color(238, 82, 64), new Color(238, 82, 64), new Color(239, 83, 63), new Color(239, 83, 63), new Color(240, 85, 60), new Color(242, 87, 57), new Color(242, 87, 57), new Color(243, 89, 54), new Color(243, 89, 54), new Color(244, 91, 53), new Color(244, 91, 53), new Color(243, 93, 51), new Color(245, 95, 48), new Color(245, 95, 48), new Color(246, 97, 47), new Color(246, 97, 47), new Color(247, 99, 44), new Color(247, 99, 44), new Color(248, 101, 41), new Color(248, 101, 41), new Color(248, 103, 40), new Color(249, 105, 37), new Color(249, 105, 37), new Color(250, 107, 35), new Color(250, 107, 35), new Color(251, 109, 34), new Color(251, 109, 34), new Color(251, 111, 31), new Color(252, 113, 30), new Color(252, 113, 30), new Color(253, 115, 27), new Color(253, 115, 27), new Color(253, 117, 26), new Color(253, 117, 26), new Color(254, 119, 23), new Color(254, 119, 23), new Color(255, 121, 23), new Color(255, 123, 20), new Color(255, 123, 20), new Color(255, 125, 19), new Color(255, 125, 19), new Color(255, 127, 16), new Color(255, 127, 16), new Color(255, 129, 15), new Color(255, 131, 14), new Color(255, 131, 14), new Color(255, 134, 11), new Color(255, 134, 11), new Color(255, 135, 10), new Color(255, 135, 10), new Color(255, 137, 10), new Color(255, 137, 10), new Color(255, 139, 9), new Color(255, 140, 5), new Color(255, 140, 5), new Color(255, 142, 4), new Color(255, 142, 4), new Color(255, 144, 3), new Color(255, 144, 3), new Color(255, 146, 2), new Color(255, 148, 1), new Color(255, 148, 1), new Color(255, 150, 0), new Color(255, 150, 0), new Color(255, 152, 0), new Color(255, 152, 0), new Color(255, 154, 0), new Color(255, 154, 0), new Color(255, 156, 0), new Color(255, 158, 0), new Color(255, 158, 0), new Color(255, 160, 0), new Color(255, 160, 0), new Color(255, 162, 0), new Color(255, 162, 0), new Color(255, 164, 0), new Color(255, 165, 0), new Color(255, 165, 0), new Color(255, 168, 0), new Color(255, 168, 0), new Color(255, 170, 0), new Color(255, 170, 0), new Color(255, 171, 0), new Color(255, 171, 0), new Color(255, 174, 0), new Color(255, 176, 0), new Color(255, 176, 0), new Color(255, 177, 0), new Color(255, 177, 0), new Color(255, 179, 0), new Color(255, 179, 0), new Color(255, 181, 0), new Color(255, 183, 0), new Color(255, 183, 0), new Color(255, 185, 0), new Color(255, 185, 0), new Color(255, 187, 0), new Color(255, 187, 0), new Color(255, 188, 0), new Color(255, 188, 0), new Color(255, 190, 0), new Color(255, 192, 0), new Color(255, 192, 0), new Color(255, 194, 1), new Color(255, 194, 1), new Color(255, 195, 2), new Color(255, 195, 2), new Color(255, 197, 3), new Color(255, 199, 5), new Color(255, 199, 5), new Color(255, 201, 6), new Color(255, 201, 6), new Color(255, 203, 7), new Color(255, 203, 7), new Color(255, 204, 10), new Color(255, 204, 10), new Color(255, 206, 11), new Color(255, 208, 12), new Color(255, 208, 12), new Color(255, 209, 16), new Color(255, 209, 16), new Color(255, 211, 17), new Color(255, 211, 17), new Color(255, 213, 20), new Color(255, 214, 23), new Color(255, 214, 23), new Color(255, 216, 24), new Color(255, 216, 24), new Color(255, 217, 27), new Color(255, 217, 27), new Color(255, 219, 31), new Color(255, 219, 31), new Color(255, 221, 34), new Color(255, 222, 37), new Color(255, 222, 37), new Color(255, 224, 40), new Color(255, 224, 40), new Color(255, 225, 43), new Color(255, 225, 43), new Color(255, 227, 46), new Color(255, 228, 50), new Color(255, 228, 50), new Color(255, 229, 53), new Color(255, 229, 53), new Color(255, 230, 58), new Color(255, 230, 58), new Color(255, 232, 61), new Color(255, 232, 61), new Color(255, 233, 66), new Color(255, 235, 70), new Color(255, 235, 70), new Color(255, 236, 75), new Color(255, 236, 75), new Color(255, 236, 79), new Color(255, 236, 79), new Color(255, 237, 84), new Color(255, 238, 89), new Color(255, 238, 89), new Color(255, 240, 94), new Color(255, 240, 94), new Color(254, 241, 100), new Color(254, 241, 100), new Color(254, 242, 105), new Color(253, 243, 110), new Color(253, 243, 110), new Color(253, 244, 115), new Color(253, 244, 115), new Color(253, 245, 122), new Color(253, 245, 122), new Color(252, 246, 128), new Color(252, 246, 128), new Color(252, 247, 135), new Color(251, 248, 140), new Color(251, 248, 140), new Color(251, 249, 147), new Color(251, 249, 147), new Color(250, 250, 155), new Color(250, 250, 155), new Color(250, 251, 162), new Color(250, 252, 167), new Color(250, 252, 167), new Color(248, 253, 176), new Color(248, 253, 176), new Color(247, 254, 183), new Color(247, 254, 183), new Color(247, 255, 191), new Color(247, 255, 191), new Color(246, 255, 198), new Color(246, 255, 207), new Color(246, 255, 207), new Color(245, 255, 214), new Color(245, 255, 214), new Color(245, 255, 224), new Color(245, 255, 224), new Color(245, 255, 231), new Color(244, 255, 240), new Color(244, 255, 240), new Color(244, 255, 249), new Color(244, 255, 249) };
    public readonly Color[] GRAY_SCALE = new Color[] { new Color(0, 0, 0), new Color(255, 255, 255) };
    public readonly Color[] RAINBOW_SCALE = new Color[] { new Color(191, 64, 64), new Color(191, 67, 64), new Color(191, 70, 64), new Color(191, 73, 64), new Color(191, 76, 64), new Color(191, 79, 64), new Color(191, 82, 64), new Color(191, 85, 64), new Color(191, 88, 64), new Color(191, 91, 64), new Color(191, 94, 64), new Color(191, 97, 64), new Color(191, 100, 64), new Color(191, 103, 64), new Color(191, 106, 64), new Color(191, 109, 64), new Color(191, 112, 64), new Color(191, 115, 64), new Color(191, 118, 64), new Color(191, 121, 64), new Color(191, 124, 64), new Color(191, 127, 64), new Color(191, 130, 64), new Color(191, 133, 64), new Color(191, 136, 64), new Color(191, 139, 64), new Color(191, 142, 64), new Color(191, 145, 64), new Color(191, 148, 64), new Color(191, 151, 64), new Color(191, 154, 64), new Color(191, 157, 64), new Color(191, 160, 64), new Color(191, 163, 64), new Color(191, 166, 64), new Color(191, 169, 64), new Color(191, 172, 64), new Color(191, 175, 64), new Color(191, 178, 64), new Color(191, 181, 64), new Color(191, 184, 64), new Color(191, 187, 64), new Color(191, 190, 64), new Color(190, 191, 64), new Color(187, 191, 64), new Color(184, 191, 64), new Color(181, 191, 64), new Color(178, 191, 64), new Color(175, 191, 64), new Color(172, 191, 64), new Color(169, 191, 64), new Color(166, 191, 64), new Color(163, 191, 64), new Color(160, 191, 64), new Color(157, 191, 64), new Color(154, 191, 64), new Color(151, 191, 64), new Color(148, 191, 64), new Color(145, 191, 64), new Color(142, 191, 64), new Color(139, 191, 64), new Color(136, 191, 64), new Color(133, 191, 64), new Color(130, 191, 64), new Color(127, 191, 64), new Color(124, 191, 64), new Color(121, 191, 64), new Color(118, 191, 64), new Color(115, 191, 64), new Color(112, 191, 64), new Color(109, 191, 64), new Color(106, 191, 64), new Color(103, 191, 64), new Color(100, 191, 64), new Color(97, 191, 64), new Color(94, 191, 64), new Color(91, 191, 64), new Color(88, 191, 64), new Color(85, 191, 64), new Color(82, 191, 64), new Color(79, 191, 64), new Color(76, 191, 64), new Color(73, 191, 64), new Color(70, 191, 64), new Color(67, 191, 64), new Color(64, 191, 64), new Color(64, 191, 67), new Color(64, 191, 70), new Color(64, 191, 73), new Color(64, 191, 76), new Color(64, 191, 79), new Color(64, 191, 82), new Color(64, 191, 85), new Color(64, 191, 88), new Color(64, 191, 91), new Color(64, 191, 94), new Color(64, 191, 97), new Color(64, 191, 100), new Color(64, 191, 103), new Color(64, 191, 106), new Color(64, 191, 109), new Color(64, 191, 112), new Color(64, 191, 115), new Color(64, 191, 118), new Color(64, 191, 121), new Color(64, 191, 124), new Color(64, 191, 127), new Color(64, 191, 130), new Color(64, 191, 133), new Color(64, 191, 136), new Color(64, 191, 139), new Color(64, 191, 142), new Color(64, 191, 145), new Color(64, 191, 148), new Color(64, 191, 151), new Color(64, 191, 154), new Color(64, 191, 157), new Color(64, 191, 160), new Color(64, 191, 163), new Color(64, 191, 166), new Color(64, 191, 169), new Color(64, 191, 172), new Color(64, 191, 175), new Color(64, 191, 178), new Color(64, 191, 181), new Color(64, 191, 184), new Color(64, 191, 187), new Color(64, 191, 190), new Color(64, 190, 191), new Color(64, 187, 191), new Color(64, 184, 191), new Color(64, 181, 191), new Color(64, 178, 191), new Color(64, 175, 191), new Color(64, 172, 191), new Color(64, 169, 191), new Color(64, 166, 191), new Color(64, 163, 191), new Color(64, 160, 191), new Color(64, 157, 191), new Color(64, 154, 191), new Color(64, 151, 191), new Color(64, 148, 191), new Color(64, 145, 191), new Color(64, 142, 191), new Color(64, 139, 191), new Color(64, 136, 191), new Color(64, 133, 191), new Color(64, 130, 191), new Color(64, 127, 191), new Color(64, 124, 191), new Color(64, 121, 191), new Color(64, 118, 191), new Color(64, 115, 191), new Color(64, 112, 191), new Color(64, 109, 191), new Color(64, 106, 191), new Color(64, 103, 191), new Color(64, 100, 191), new Color(64, 97, 191), new Color(64, 94, 191), new Color(64, 91, 191), new Color(64, 88, 191), new Color(64, 85, 191), new Color(64, 82, 191), new Color(64, 79, 191), new Color(64, 76, 191), new Color(64, 73, 191), new Color(64, 70, 191), new Color(64, 67, 191), new Color(64, 64, 191), new Color(67, 64, 191), new Color(70, 64, 191), new Color(73, 64, 191), new Color(76, 64, 191), new Color(79, 64, 191), new Color(82, 64, 191), new Color(85, 64, 191), new Color(88, 64, 191), new Color(91, 64, 191), new Color(94, 64, 191), new Color(97, 64, 191), new Color(100, 64, 191), new Color(103, 64, 191), new Color(106, 64, 191), new Color(109, 64, 191), new Color(112, 64, 191), new Color(115, 64, 191), new Color(118, 64, 191), new Color(121, 64, 191), new Color(124, 64, 191), new Color(127, 64, 191), new Color(130, 64, 191), new Color(133, 64, 191), new Color(136, 64, 191), new Color(139, 64, 191), new Color(142, 64, 191), new Color(145, 64, 191), new Color(148, 64, 191), new Color(151, 64, 191), new Color(154, 64, 191), new Color(157, 64, 191), new Color(160, 64, 191), new Color(163, 64, 191), new Color(166, 64, 191), new Color(169, 64, 191), new Color(172, 64, 191), new Color(175, 64, 191), new Color(178, 64, 191), new Color(181, 64, 191), new Color(184, 64, 191), new Color(187, 64, 191), new Color(190, 64, 191), new Color(191, 64, 190), new Color(191, 64, 187), new Color(191, 64, 184), new Color(191, 64, 181), new Color(191, 64, 178), new Color(191, 64, 175), new Color(191, 64, 172), new Color(191, 64, 169), new Color(191, 64, 166), new Color(191, 64, 163), new Color(191, 64, 160), new Color(191, 64, 157), new Color(191, 64, 154), new Color(191, 64, 151), new Color(191, 64, 148), new Color(191, 64, 145), new Color(191, 64, 142), new Color(191, 64, 139), new Color(191, 64, 136), new Color(191, 64, 133), new Color(191, 64, 130), new Color(191, 64, 127), new Color(191, 64, 124), new Color(191, 64, 121), new Color(191, 64, 118), new Color(191, 64, 115), new Color(191, 64, 112), new Color(191, 64, 109), new Color(191, 64, 106), new Color(191, 64, 103), new Color(191, 64, 100), new Color(191, 64, 97), new Color(191, 64, 94), new Color(191, 64, 91), new Color(191, 64, 88), new Color(191, 64, 85), new Color(191, 64, 82), new Color(191, 64, 79), new Color(191, 64, 76), new Color(191, 64, 73), new Color(191, 64, 70), new Color(191, 64, 67) };

    #endregion

    #region class definitions

    public class VertexPositionColorNormal
    {
        public Vector3 Position;
        public Color Color;
        public Vector3 Normal;

        public VertexPositionColorNormal(Vector3 p, Color c, Vector3 n)
        {
            Position = p;
            Color = c;
            Normal = n;
        }

    }

    public class ModelPoint
    {
        /// <summary>Default color of point (used e.g. in Surface Only view)</summary>
        public static readonly Color COLOR_DEFAULT = new Color(float.NaN, float.NaN, float.NaN, float.NaN);

        /// <summary>Vertex = 3D position + actually rendered color + normal.</summary>
        public VertexPositionColorNormal Vertex = new VertexPositionColorNormal(Vector3.zero, COLOR_DEFAULT, Vector3.zero);

        /// <summary>Original colour of this point (captured by color camera)</summary>
        public Color Color;

        /// <summary>Reflectivity of this point (0..255)</summary>
        public float Reflectivity = 0;

        /// <summary>Width of reflected beam of this point (0..255)</summary>
        public float ReflWidth = 0;

        /// <summary>Temperature of this point</summary>
        public float Temperature = float.NaN;

        /// <summary>Distance between to models - Only used Compare file is load </summary>
        public float Distance = float.NaN;

        /// <summary>Pointer to profile, into which this point belongs to. If null, the point is not connected with any profile.</summary>
        //public Profile? Profile = null;

        public ModelPoint()
        {
            Color = COLOR_DEFAULT;
        }

        public ModelPoint(float x, float y, float z)
        {
            Vertex.Position.x = x;
            Vertex.Position.y = y;
            Vertex.Position.z = z;
            Vertex.Color = COLOR_DEFAULT;
        }
        public ModelPoint(float x, float y, float z, Color color)
        {
            Vertex.Position.x = x;
            Vertex.Position.y = y;
            Vertex.Position.z = z;
            Vertex.Color = color;
        }
    }

    #endregion

    #region private data mambers

    //model itself

    // <summary>created mesh object</summary>
    private Mesh mesh;
    // <summary>created mesh object 1 for shape</summary>
    private Mesh shapeMesh1;
    // <summary>created mesh object 2 for shape</summary>
    private Mesh shapeMesh2;
    // <summary>Helpful array for saving the indices (the element's order is flipped in array)</summary>
    private int[] indicesFlip;
    // <summary>array for defining default value of every vertex (to white)</summary>
    private Color[] colorsDefault;
    // <summary>Helpful array for saving the colors of every vertex</summary>
    private Color[] colors;
    /// <summary>raw XML block from file with model matadata</summary>
    private XElement modelMetadata = new XElement("block", new XAttribute("type", "metadata"));
    // <summary>Helpful array for saving the original vertices</summary>
    private Vector3[] origVert;
    // <summary>Helpful array for saving the original colors (0..255)</summary>
    private Color32[] origColor;
    // <summary>Helpful array for saving the original normals</summary>
    public Vector3[] origNormal;
    // <summary>Set the quality of simplifing the mesh</summary>
    public float quality = 0.2f;


    /// <summary>Parent object (cube) for Mesh</summary>
    [SerializeField] private GameObject mainCube;
    /// <summary> Help class to print results to UI </summary>
    [SerializeField] private MeasurementInfo results;
    /// <summary> Toggle to switch between Auto/manual recomputing </summary>
    [SerializeField] private Toggle manualRecompute;
    /// <summary> Lines between selected points for both shapes </summary>
    [SerializeField] private LineRenderer closeSegment1, openSegment1, closeSegment2, openSegment2;
    /// <summary> Mesh to show shapes 1 </summary>
    [SerializeField] private GameObject cutInterShape1;
    /// <summary> Mesh to show shapes 2 </summary>
    [SerializeField] private GameObject cutInterShape2;
    /// <summary> Toggle to switch between Cube/crosshair points </summary>
    [SerializeField] private Toggle crosshairPoints;
    /// <summary> Material cut shape </summary>
    [SerializeField] private Material cutMaterial;
    /// <summary> Material Intershape </summary>
    [SerializeField] private Material intershapeMaterial;
    

    /// <summary>raw XML block from file with color history</summary>
    private XElement modelColorHistory;
    /// <summary>raw XML block from file with trajectory history</summary>
    private XElement modelTempHistory;

    

    //private internal (don't use)

    /// <summary>do not access directly, only by public property!</summary>
    private float _reflMin;                                              //changed -> kdyz to nebude fungovat predelat na public.
    /// <summary>do not access directly, only by public property!</summary>
    private float _reflMax;
    /// <summary>do not access directly, only by public property!</summary>
    private float _reflWidthMin;
    /// <summary>do not access directly, only by public property!</summary>
    private float _reflWidthMax;
    /// <summary>do not access directly, only by public property!</summary>
    private float _tempMin;
    /// <summary>do not access directly, only by public property!</summary>
    private float _tempMax;
    /// <summary>do not access directly, only by public property!</summary>
    private float _disMin;
    /// <summary>do not access directly, only by public property!</summary>
    private float _disMax;
    /// <summary>do not access directly, only by public property!</summary>
    private float _paletteMin = 0;
    /// <summary>do not access directly, only by public property!</summary>
    private float _paletteMax = 0;
    /// <summary>do not access directly, only by public property!</summary>
    private float _VerticesXMax;
    /// <summary>do not access directly, only by public property!</summary>
    private float _VerticesXMin;
    /// <summary>do not access directly, only by public property!</summary>
    private float _VerticesYMax;
    /// <summary>do not access directly, only by public property!</summary>
    private float _VerticesYMin;
    /// <summary>do not access directly, only by public property!</summary>
    private float _VerticesZMax;
    /// <summary>do not access directly, only by public property!</summary>
    private float _VerticesZMin;
    /// <summary>do not access directly, only by public property!</summary>
    private int _unit = 1;
    /// <summary>do not access directly, only by public property!</summary>
    private string _shape = "SNAPPED_SHAPE_SURFACE";

    //measurements variables

    /// <summary>Clicked points Shape1 for measurements</summary>
    public List<int> measShape1Points = new List<int>();
    /// <summary>Snapped polygon Shape1 for measurements</summary>
    private List<int> measShape1Poly = new List<int>();
    /// <summary>number of snapped points for each segment</summary>
    private List<int> segmentLength1 = new List<int>();
    /// <summary>snapped points between last and start boundary point</summary>
    private List<int> closingSegment1 = new List<int>();
    /// <summary>Clicked points Shape2 for measurements</summary>
    public List<int> measShape2Points = new List<int>();
    /// <summary>Snapped polygon Shape2 for measurements</summary>
    private List<int> measShape2Poly = new List<int>();
    /// <summary>number of snapped points for each segment</summary>
    private List<int> segmentLength2 = new List<int>();
    /// <summary>snapped points between last and start boundary point</summary>
    private List<int> closingSegment2 = new List<int>();
    /// <summary>lookup table for neigbour points</summary>
    private Dictionary<int, HashSet<int>> neighbourPointTable = new Dictionary<int, HashSet<int>>();
    /// <summary>area of surface covering entire model</summary>
    private float entireModelArea = 0;
    /// <summary>area of each triangle in model</summary>
    private List<float> triangleAreas = new List<float>();
    /// <summary>signed volume of each triangle in model</summary>
    private List<float> triangleVolumes = new List<float>();
    /// <summary>points enclosed by shape</summary>
    private HashSet<int> innerPoints1 = new HashSet<int>();
    /// <summary>cutting surface for volume measuring vertices</summary>
    private List<int> cuttingSurface1 = new List<int>();
    /// <summary>points enclosed by shape</summary>
    private HashSet<int> innerPoints2 = new HashSet<int>();
    /// <summary>cutting surface for volume measuring vertices</summary>
    private List<int> cuttingSurface2 = new List<int>();
    /// <summary>point defining inner part of shape 1</summary>
    private int selectionPoint1 = -1;
    /// <summary>point defining inner part of shape 2</summary>
    private int selectionPoint2 = -1;
    /// <summary>intershape vertices </summary>
    private List<int> intershape = new List<int>();

    #endregion

    #region public data mambers


    /// <summary>Information about the file ( = metadata)</summary>
    public Dictionary<string, string> FileInformation { get; private set; } = new Dictionary<string, string>();

    /// <summary>List of vertices (position, normal, colors)</summary>
    public List<ModelPoint> points = new List<ModelPoint>();
    /// <summary>Array of indices information</summary>
    public int[] indices;
    // <summary>name of the path of the choosen file</summary>
    public string path;
    // <summary>name of the path of the choosen file - MEASURMENT</summary>
    public string pathMeas;
    // <summary>Helpful array for saving the vertices</summary>
    public Vector3[] vertices;
    // <summary>simplifed the mesh - vertices</summary>
    public Vector3[] simplVert;
    // <summary>simplifed the mesh - normals</summary>
    public Vector3[] simplNorm;

    /// <summary>Returns available layers of actualy loaded model</summary>
    public ModelLayers AvailableLayers { get; private set; } = ModelLayers.NONE;

    /// <summary>Actually rendered layer</summary>
    public ModelLayers ActualLayer { get; set; } = ModelLayers.SURFACE;

    /// <summary>Viewing palette for reflection layer</summary>
    public int ReflectionPalette { get; set; } = 0;

    /// <summary>Actual value corresponding to lowest color of pallete</summary>
    public float PaletteMin { get { return _paletteMin; } set { _paletteMin = value; } }

    /// <summary>Actual value corresponding to lowest color of pallete</summary>
    public float PaletteMax { get { return _paletteMax; } set { _paletteMax = value; } }

    /// <summary>Get information about center of model</summary>
    public float[] ModelCenter { get; set; } = new float[3];

    /// <summary>Get state of Point from UI</summary>
    public string measState { get; set; } = "RED";

    /// <summary>Rows of table with measurements results</summary>
    public List<string> MeasurementResults { get; private set; } = new List<string>();

    /// <summary>Unit used for measurements</summary>
    public int Unit { get { return _unit; } set { _unit = value; Measure_Recompute(); } }
    /// <summary>Unit name - used for measurements</summary>
    public string UnitName { get; set; } = "mm";
    /// <summary>Unit name - used for measurements</summary>
    public string MeasShape { get; set; } = "SNAPPED_SHAPE_SURFACE";
    /// <summary> Recompute button was clicked </summary>
    public bool wasClicked { get; set; } = false;

    /// <summary>Returns value of selected layer corresponding to lowest color</summary>
    public float LayerValueMin
    {
        get
        {
            switch (ActualLayer)
            {
                case ModelLayers.COLOR: return 0;
                case ModelLayers.SURFACE: return 0;
                case ModelLayers.REFLECTIVITY: return _reflMin;
                case ModelLayers.REFL_WIDTH: return _reflWidthMin;
                case ModelLayers.TEMPERATURE: return _tempMin;
                case ModelLayers.DISTANCE: return _disMin;
                default: return float.NaN;
            }
        }
    }

    /// <summary>Returns value of selected layer corresponding to highest color</summary>
    public float LayerValueMax
    {
        get
        {
            switch (ActualLayer)
            {
                case ModelLayers.COLOR: return 0;
                case ModelLayers.SURFACE: return 0;
                case ModelLayers.REFLECTIVITY: return _reflMax;
                case ModelLayers.REFL_WIDTH: return _reflWidthMax;
                case ModelLayers.TEMPERATURE: return _tempMax;
                case ModelLayers.DISTANCE: return _disMax;
                default: return float.NaN;
            }
        }
    }

    #endregion

    #region Private methods

    #region Parse/Save the information from/to file

    /// <summary>Decodes indices from file</summary>
    private void LoadIndices(byte[] bytes)
    {
        indices = new int[bytes.Length / 4];

        Buffer.BlockCopy(bytes, 0, indices, 0, bytes.Length);
    }

    /// <summary>Decodes vertices from file</summary>
    private void LoadVertices(byte[] bytes)
    {
        points.Clear();
        points.Capacity = bytes.Length / (4 * 3); //preallocation of memory

        _VerticesYMax = float.MinValue;
        _VerticesYMin = float.MaxValue;
        _VerticesXMax = float.MinValue;
        _VerticesXMin = float.MaxValue;
        _VerticesZMax = float.MinValue;
        _VerticesZMin = float.MaxValue;

        for (int i = 0; i < bytes.Length; i += (4 * 3))
        {
            float[] floatdata = new float[3];
            Buffer.BlockCopy(bytes, i, floatdata, 0, 4 * 3);
            ModelPoint p = new ModelPoint(floatdata[0], floatdata[1], floatdata[2]);
            points.Add(p);
            
            if (floatdata[0] > _VerticesXMax)
                _VerticesXMax = floatdata[0];
            else
            {
                if (floatdata[0] < _VerticesXMin)
                    _VerticesXMin = floatdata[0];
            }
            
            if (floatdata[1] > _VerticesYMax)
                _VerticesYMax = floatdata[1];
            else
            {
                if (floatdata[1] < _VerticesYMin)
                    _VerticesYMin = floatdata[1];
            }
            
            if (floatdata[2] > _VerticesZMax)
                _VerticesZMax = floatdata[2];
            else
            {
                if (floatdata[2] < _VerticesZMin)
                    _VerticesZMin = floatdata[2];
            }

        }
    }

    /// <summary>Decodes reflectivity block from file</summary>
    private void LoadReflectivity(byte[] bytes)
    {
        if (!AvailableLayers.HasFlag(ModelLayers.DISTANCE))
        {
            var numOfItems = bytes.Length / sizeof(ushort);
            ushort[] modelReflectivity = new ushort[numOfItems];
            Buffer.BlockCopy(bytes, 0, modelReflectivity, 0, bytes.Length);

            _reflMin = ushort.MaxValue;
            _reflMax = ushort.MinValue;

            for (int i = 0; i < numOfItems; i++)
            {
                points[i].Reflectivity = modelReflectivity[i];
                if (modelReflectivity[i] > _reflMax)
                    _reflMax = modelReflectivity[i];
                if (modelReflectivity[i] < _reflMin)
                    _reflMin = modelReflectivity[i];
            }
        }
        else 
        {
            var numOfItems = bytes.Length / sizeof(float);
            float[] modelReflectivity = new float[numOfItems];
            Buffer.BlockCopy(bytes, 0, modelReflectivity, 0, bytes.Length);

            _reflMin = float.MaxValue;
            _reflMax = float.MinValue;

            for (int i = 0; i < numOfItems; i++)
            {
                points[i].Reflectivity = modelReflectivity[i];
                if (modelReflectivity[i] > _reflMax)
                    _reflMax = modelReflectivity[i];
                if (modelReflectivity[i] < _reflMin)
                    _reflMin = modelReflectivity[i];
            }
        }
    }

    /// <summary>Decodes refl.width block from file</summary>
    private void LoadReflWidth(byte[] bytes)
    {
        if (!AvailableLayers.HasFlag(ModelLayers.DISTANCE))
        {
            var numOfItems = bytes.Length / sizeof(ushort);
            ushort[] modelReflWidth = new ushort[numOfItems];
            Buffer.BlockCopy(bytes, 0, modelReflWidth, 0, bytes.Length);

            _reflWidthMin = ushort.MaxValue;
            _reflWidthMax = ushort.MinValue;

            for (int i = 0; i < numOfItems; i++)
            {
                points[i].ReflWidth = modelReflWidth[i];
                if (modelReflWidth[i] > _reflWidthMax)
                    _reflWidthMax = modelReflWidth[i];
                if (modelReflWidth[i] < _reflWidthMin)
                    _reflWidthMin = modelReflWidth[i];
            }
        }
        else
        {
            var numOfItems = bytes.Length / sizeof(float);
            float[] modelReflWidth = new float[numOfItems];
            Buffer.BlockCopy(bytes, 0, modelReflWidth, 0, bytes.Length);

            _reflWidthMin = float.MaxValue;
            _reflWidthMax = float.MinValue;

            for (int i = 0; i < numOfItems; i++)
            {
                points[i].ReflWidth = modelReflWidth[i];
                if (modelReflWidth[i] > _reflWidthMax)
                    _reflWidthMax = modelReflWidth[i];
                if (modelReflWidth[i] < _reflWidthMin)
                    _reflWidthMin = modelReflWidth[i];
            }
        }
    }

    /// <summary>Decodes color block from file</summary>
    private void LoadColors(byte[] bytes)
    {
        var numOfItems = bytes.Length / 4;  //RGBA = 4 bytes
        for (int i = 0; i < bytes.Length; i += 4)
        {
            points[i / 4].Color = new Color(bytes[i], bytes[i + 1], bytes[i + 2], bytes[i + 3]);
        }
    }


    /// <summary>Decodes temperature block from file</summary>
    private void LoadTemperature(byte[] bytes)
    {
        var numOfItems = bytes.Length / sizeof(float);
        float[] modelTemp = new float[numOfItems];
        Buffer.BlockCopy(bytes, 0, modelTemp, 0, bytes.Length);

        _tempMin = float.MaxValue;
        _tempMax = float.MinValue;

        for (int i = 0; i < numOfItems; i++)
        {
            points[i].Temperature = modelTemp[i];
            if (modelTemp[i] > _tempMax)
                _tempMax = modelTemp[i];
            if (modelTemp[i] < _tempMin)
                _tempMin = modelTemp[i];
        }
    }

    /// <summary>Decodes distance block from file - Only COMPARE MODELS </summary>
    private void LoadDistance(byte[] bytes)
    {
        var numOfItems = bytes.Length / sizeof(float);
        float[] modelDis = new float[numOfItems];
        Buffer.BlockCopy(bytes, 0, modelDis, 0, bytes.Length);

        _disMin = float.MaxValue;
        _disMax = float.MinValue;

        for (int i = 0; i < numOfItems; i++)
        {
            points[i].Distance = modelDis[i];
            if (modelDis[i] > _disMax)
                _disMax = modelDis[i];
            if (modelDis[i] < _disMin)
                _disMin = modelDis[i];
        }
    }

    /// <summary>Reads metadata from element and publish then in GridList</summary>
    private void ParseMetadata(XElement metadata)
    {
        FileInformation.Clear();

        foreach (XElement element in metadata.Descendants())
        {
            FileInformation.Add(element.Attribute("caption").Value, element.Value);
        }

        string layers = "";
        if (AvailableLayers.HasFlag(ModelLayers.SURFACE))
            layers += "Surface";
        if (AvailableLayers.HasFlag(ModelLayers.REFLECTIVITY))
            layers += ", Reflectivity";
        if (AvailableLayers.HasFlag(ModelLayers.REFL_WIDTH))
            layers += ", Reflection Width";
        if (AvailableLayers.HasFlag(ModelLayers.TEMPERATURE))
            layers += ", Temperature";
        if (AvailableLayers.HasFlag(ModelLayers.COLOR))
            layers += ", Color";
        FileInformation.Add("Available Layers", layers);
    }

    /// <summary>Set color of specified model points</summary>
    private void SetColorPoints(HashSet<int> indices, Color color)
    {
        SetColorPoints(points.Where((x, i) => indices.Contains(i)).ToList(), color);
    }

    /// <summary>Set color of specified model points</summary>
    private void SetColorPoints(List<ModelPoint> pointList, Color color)
    {
        foreach (ModelPoint point in pointList)
            point.Vertex.Color = color;

        //UpdateModelData(); //update buffers
    }

    /// <summary>Update color of selected vertices according to desired layer and palette settings and its initial state.</summary>
    private void ResetColorPoints(HashSet<int> indices)
    {
        ResetColorPoints(points.Where((x, i) => indices.Contains(i)).ToList());
    }

    /// <summary>Export only point cloud without any additional information</summary>
    private void SaveXYZ(string pathToFile)
    {
        StreamWriter sw = new StreamWriter(pathToFile);
        for(int i = 0; i < origVert.Length; i++)
        {
            sw.WriteLine(
               origVert[i].x.ToString("", CultureInfo.InvariantCulture) + " " +
               origVert[i].y.ToString("", CultureInfo.InvariantCulture) + " " +
               origVert[i].z.ToString("", CultureInfo.InvariantCulture)
            );
        }
    }

    /// <summary>Export point cloud with color, normal and scalar value</summary>
    private void SavePTS(string pathToFile)
    {
        StreamWriter sw = new StreamWriter(pathToFile);
        sw.WriteLine("X Y Z Nx Ny Nz R G B");

        for (int i = 0; i < origVert.Length; i++)
        {
            sw.WriteLine(
            origVert[i].x.ToString("", CultureInfo.InvariantCulture) + " " +
            origVert[i].y.ToString("", CultureInfo.InvariantCulture) + " " +
            origVert[i].z.ToString("", CultureInfo.InvariantCulture) + " " +
            points[i].Vertex.Normal.x.ToString() + " " +
            points[i].Vertex.Normal.y.ToString() + " " +
            points[i].Vertex.Normal.z.ToString() + " " +
            points[i].Vertex.Color.r.ToString("F0", CultureInfo.InvariantCulture) + " " +
            points[i].Vertex.Color.g.ToString("F0", CultureInfo.InvariantCulture) + " " +
            points[i].Vertex.Color.b.ToString("F0", CultureInfo.InvariantCulture) + " "
            );
        }
    }

    /// <summary>Export complete MESH in PLY</summary>
    private void SavePLY(string pathToFile)
    {
        StreamWriter sw = new StreamWriter(pathToFile);

        sw.WriteLine("ply");
        sw.WriteLine("format ascii 1.0");
        sw.WriteLine("comment model exported from RoScan Analyzer at " + DateTime.Now.ToString());
        sw.WriteLine("comment original file: " + path);
        sw.WriteLine("element vertex " + points.Count.ToString()); //number of vertices following after header
        sw.WriteLine("property float x"); //meaning of columns at vertex definition
        sw.WriteLine("property float y");
        sw.WriteLine("property float z");
        sw.WriteLine("property float nx");
        sw.WriteLine("property float ny");
        sw.WriteLine("property float nz");
        sw.WriteLine("property uchar red");
        sw.WriteLine("property uchar green");
        sw.WriteLine("property uchar blue");
        sw.WriteLine("element face " + (indices.Length / 3).ToString()); //number of indices following after header
        sw.WriteLine("property list uchar int vertex_index"); //first element defines number of indices followed at same line
        sw.WriteLine("end_header");


        for (int i = 0; i < origVert.Length; i++)
        {
            string line =
            origVert[i].x.ToString("F8", CultureInfo.InvariantCulture) + " " +
            origVert[i].y.ToString("F8", CultureInfo.InvariantCulture) + " " +
            origVert[i].z.ToString("F8", CultureInfo.InvariantCulture) + " " +
            points[i].Vertex.Normal.x.ToString("F8", CultureInfo.InvariantCulture) + " " +
            points[i].Vertex.Normal.y.ToString("F8", CultureInfo.InvariantCulture) + " " +
            points[i].Vertex.Normal.z.ToString("F8", CultureInfo.InvariantCulture) + " " +
            points[i].Vertex.Color.r.ToString("F0", CultureInfo.InvariantCulture) + " " +
            points[i].Vertex.Color.g.ToString("F0", CultureInfo.InvariantCulture) + " " +
            points[i].Vertex.Color.b.ToString("F0", CultureInfo.InvariantCulture) + " ";

            line = line.Replace("NaN", "0");
            sw.WriteLine(line);
           
        }

        for (int i = 0; i < indices.Length; i += 3)
        {
            sw.WriteLine("3 " + indices[i].ToString() + " " + indices[i + 2].ToString() + " " + indices[i + 1].ToString()); //switch direction of triangles, since they must be defined counter-clockwise and we are using clockwise logic.
        }

    }

    /// <summary>Export measured values to XML (NOT FINISHED)</summary>
    private void Measure_Export(string pathToFile)
    {
        XmlWriterSettings xml_settings = new XmlWriterSettings();
        xml_settings.Indent = true;

        XmlWriter xml = XmlWriter.Create(pathToFile, xml_settings);
        xml.WriteStartDocument();
        xml.WriteStartElement("measurements");
        xml.WriteAttributeString("version", "1.0");
        xml.WriteAttributeString("date", DateTime.Now.ToLongTimeString());

        //write boundary points
        xml.WriteStartElement("points1");
        foreach (int p in measShape1Points)
        {
            var point = points[p];
            xml.WriteStartElement("point");
            xml.WriteAttributeString("x", point.Vertex.Position.x.ToString());
            xml.WriteAttributeString("y", point.Vertex.Position.y.ToString());
            xml.WriteAttributeString("z", point.Vertex.Position.z.ToString());
            xml.WriteAttributeString("vertex", p.ToString());
            xml.WriteEndElement();
        }
        xml.WriteEndElement();

        //write boundary points
        xml.WriteStartElement("points2");
        foreach (int p in measShape2Points)
        {
            var point = points[p];
            xml.WriteStartElement("point");
            xml.WriteAttributeString("x", point.Vertex.Position.x.ToString());
            xml.WriteAttributeString("y", point.Vertex.Position.y.ToString());
            xml.WriteAttributeString("z", point.Vertex.Position.z.ToString());
            xml.WriteAttributeString("vertex", p.ToString());
            xml.WriteEndElement();
        }
        xml.WriteEndElement();

        //write selected points
        xml.WriteStartElement("selectedpoints");

        foreach (int p in new List<int> { selectionPoint1, selectionPoint2 })
        {
            if (p != -1)
            {
                var point = points[p];
                xml.WriteStartElement("point");
                xml.WriteAttributeString("x", point.Vertex.Position.x.ToString());
                xml.WriteAttributeString("y", point.Vertex.Position.y.ToString());
                xml.WriteAttributeString("z", point.Vertex.Position.z.ToString());
                xml.WriteAttributeString("vertex", p.ToString());
                xml.WriteEndElement();
            }
            else
            {
                xml.WriteStartElement("point");
                xml.WriteAttributeString("vertex", p.ToString());
                xml.WriteEndElement();
            }
        }
        xml.WriteEndElement();

        xml.WriteEndElement();
        xml.WriteEndDocument();
        xml.Close();
    }

    /// <summary>Load measurement XML file (allows to continue measurement)</summary>
    private void Measure_Load(string pathToFile)
    {
        XmlDocument xml = new XmlDocument();
        xml.Load(pathToFile);

        measState = "RED";
        XmlNodeList nodes = xml.DocumentElement.SelectNodes("/measurements/points1/point");
        foreach (XmlNode node in nodes)
        {
            SelectMeasShapePoint(int.Parse(node.Attributes["vertex"].Value));
        }

        measState = "BLUE";
        nodes = xml.DocumentElement.SelectNodes("/measurements/points2/point");
        foreach (XmlNode node in nodes)
        {
            SelectMeasShapePoint(int.Parse(node.Attributes["vertex"].Value));
        }

        measState = "RED";
        nodes = xml.DocumentElement.SelectNodes("/measurements/selectedpoints/point");
        selectionPoint1 = int.Parse(nodes[0].Attributes["vertex"].Value);
        selectionPoint2 = int.Parse(nodes[1].Attributes["vertex"].Value);

        Measure_Recompute();
    }

    #endregion

    #region Help Methods for create the model
    /// <summary>Method to add information of Center of model to FileInformation</summary>
    private void ParseVerticesData()
    {
        string ModelInfo = "";
        ModelInfo = "X: " + _VerticesXMin.ToString("0.0") + " to " + _VerticesXMax.ToString("0.0") + "\nY: "
            + _VerticesYMin.ToString("0.0") + " to " + _VerticesYMax.ToString("0.0") + "\nZ: " +
            _VerticesZMin.ToString("0.0") + " to " + _VerticesZMax.ToString("0.0");
        FileInformation.Add("Model Constraints", ModelInfo);

        GetModelCenter();
        ModelInfo = "[" + ModelCenter[0].ToString("0.0") + " " + ModelCenter[1].ToString("0.0") + " " + ModelCenter[2].ToString("0.0") + "]";
        FileInformation.Add("Model Center", ModelInfo);
    }

    /// <summary> Insert vertices's position into array (Vector3)</summary>
    private void ToArray(Vector3[] vector)
    {
        for (int i = 0; i < points.Count; i++)
        {
            vector[i].x = points[i].Vertex.Position.x;
            vector[i].y = points[i].Vertex.Position.y;
            vector[i].z = points[i].Vertex.Position.z;
        }
    }

    /// <summary> Insert colors of every vertex into array (Color) /summary>
    private void ToArray(Color[] colors)
    {
        for(int i = 0; i < points.Count; i++)
        {
            colors[i] = points[i].Vertex.Color;
            colors[i] = colors[i] / 255;
        }
   }

    /// <summary> Making the default (white) array </summary>
    private void DefaultValues(Color[] colorsDefault)
   {
       for (int i = 0; i < colorsDefault.Length; i++)
       {
           colorsDefault[i].r = 1;
           colorsDefault[i].g = 1;
           colorsDefault[i].b = 1;
           colorsDefault[i].a = 1;
       }
   }

    /// <summary> Flipping order of elements into array of indices </summary>
    private void FlipElements(int[] array) // if you dont need, you can delete it
   {
        int j = 0;
        for (int i = 0; i < indices.Length; i++)
        {
            array[i] = indices[i];
        }

        for (int i = indices.Length; i < array.Length && j < indices.Length; i++, j++)
        {
            array[i] = indices[indices.Length - 1 - j];
        } 
    }

    /// <summary> Get original info of vertices,colors and normal to helping arrays </summary>
    private void getOriginData()
    {
        origVert = new Vector3[points.Count];
        origColor = new Color32[points.Count];
        origNormal = new Vector3[points.Count];

            for (int i = 0; i < points.Count; i++)
        {
            origVert[i] = points[i].Vertex.Position;
            origNormal[i] = points[i].Vertex.Normal;
            origColor[i] = points[i].Color;
        }
    }

    /// <summary> Calculate center of model and set into array (for Info panel) </summary>
    private void GetModelCenter()
    {
        ModelCenter[0] = _VerticesXMax - (_VerticesXMax - _VerticesXMin) / 2;
        ModelCenter[1] = _VerticesYMax - (_VerticesYMax - _VerticesYMin) / 2;
        ModelCenter[2] = _VerticesZMax - (_VerticesZMax - _VerticesZMin) / 2;
    }

    /// <summary> Change position of every vertices and position of the MAIN camera </summary>
    public void MeshPosition(float[] ModelCenter)
    {
        getOriginData();

        mainCube.transform.position = new Vector3(0, 0, 0);

        for (int i = 0; i < points.Count; i++)
        {
           points[i].Vertex.Position.x -= ModelCenter[0];
           points[i].Vertex.Position.y -= ModelCenter[1];
           points[i].Vertex.Position.z = -points[i].Vertex.Position.z;
           points[i].Vertex.Position.z += ModelCenter[2];
        }

        float CameraZ = Camera.main.transform.position.z;
        Camera.main.transform.position = new Vector3(30, CameraZ, 0);

    }

    /// <summary>Fill in normal properties in given vertex buffer using index buffer. Modifies only last part of vertices.</summary>
    /// <param name="points">Vertex buffer. Passed to function with zero normals, after function ends, normals are defined.</param>
    /// <param name="indices">Index buffer</param>
    /// <param name="startFrom">Index of index in index buffer which start from. If negative, start index is counted from end of index buffer.</param>
    public void CalculateNormals(List<ModelPoint> points, int[] indices, int startFrom = 0)
    {
        if (startFrom < 0) //if negative, compute position from end
            startFrom = indices.Length + startFrom;

        HashSet<int> modifiedVertices = new HashSet<int>();

        for (int i = startFrom / 3; i < indices.Length / 3; i++) //for each triangle, compute normal
        {
            int pointA = indices[i * 3];
            int pointB = indices[i * 3 + 1];
            int pointC = indices[i * 3 + 2];

            modifiedVertices.Add(pointA);
            modifiedVertices.Add(pointB);
            modifiedVertices.Add(pointC);

            VertexPositionColorNormal vertexA = points[pointA].Vertex;
            VertexPositionColorNormal vertexB = points[pointB].Vertex;
            VertexPositionColorNormal vertexC = points[pointC].Vertex;

            Vector3 sideB = vertexA.Position - vertexC.Position; //first side of triangle
            Vector3 sideC = vertexA.Position - vertexB.Position; //second side of triangle
            Vector3 normal = Vector3.Cross(sideB, sideC); //compute normal of plane, in which triangle lays
            normal.Normalize();

            vertexA.Normal += normal; //add this normal to each point of triangle
            vertexB.Normal += normal;
            vertexC.Normal += normal;

            points[pointA].Vertex = vertexA;
            points[pointB].Vertex = vertexB;
            points[pointC].Vertex = vertexC;

        }

        foreach (int ivertex in modifiedVertices) //normalize normal vectors
        {
            points[ivertex].Vertex.Normal.Normalize();
            //VertexPositionColorNormal vertex = points[ivertex];
            //vertex.Normal.Normalize();
            //points[ivertex] = vertex;
        }


        //each point has now normal which is average of normals of triangles which this point belongs to
    }

    #endregion

    #region Create a Shape of model

    /// <summary> Creating the shape of model (init, preparing the obtained datas) </summary>
    private void CreateShape()
    {

        if (points.Count < 3) //at least one triangle required
        {
            Debug.Log("Parse Error: no vertices");
            return;
        }

        if (indices.Length < 3) //at least one triangle required
        {
            Debug.Log("Parse Error: no indices");
            return;
        }

        mesh = null;
        mesh = new Mesh();

        vertices = new Vector3[points.Count];
        ToArray(vertices);

        // for Flipping the indices - can be deleted
        //indicesFlip = new int[indices.Length*2]; 
        //FlipElements(indicesFlip);

        colors = new Color[points.Count];
        colorsDefault = new Color[points.Count];

        DefaultValues(colorsDefault);
        ToArray(colors);

        MeshUpdate();
    }
    #endregion

    #region Measurement

    #region Helpful methods for creating shapes

    /// <summary>Create Lines for Snapped Shape</summary>
    private void SnappedShapeLines(bool shapeChanged)
    {
        int tempLength1 = 0;
        int tempLength2 = 0;

        if (measState == "RED" || shapeChanged)
        {
            for (int i = 0; i < segmentLength1.Count; i++)
            {
                tempLength1 += segmentLength1[i];
            }

            openSegment1.positionCount = tempLength1;
            closeSegment1.positionCount = closingSegment1.Count;

            Vector3[] tempVertices1Open = new Vector3[tempLength1];
            Vector3[] tempVertices1Close = new Vector3[closingSegment1.Count];

            for (int j = 0; j < tempLength1; j++)
            {
                tempVertices1Open[j] = vertices[measShape1Poly[j]];
            }

            for (int k = 0; k < closingSegment1.Count; k++)
            {
                tempVertices1Close[k] = vertices[closingSegment1[k]];
            }
            openSegment1.SetPositions(tempVertices1Open);
            closeSegment1.SetPositions(tempVertices1Close);
        }

        if (measState == "BLUE" || shapeChanged)
        {
            for (int i = 0; i < segmentLength2.Count; i++)
            {
                tempLength2 += segmentLength2[i];
            }

            openSegment2.positionCount = tempLength2;
            closeSegment2.positionCount = closingSegment2.Count;

            Vector3[] tempVertices2Open = new Vector3[tempLength2];
            Vector3[] tempVertices2Close = new Vector3[closingSegment2.Count];

            for (int j = 0; j < tempLength2; j++)
            {
                tempVertices2Open[j] = vertices[measShape2Poly[j]];
            }

            for (int k = 0; k < closingSegment2.Count; k++)
            {
                tempVertices2Close[k] = vertices[closingSegment2[k]];
            }
            openSegment2.SetPositions(tempVertices2Open);
            closeSegment2.SetPositions(tempVertices2Close);
        }
    }

    /// <summary>Create Lines for Direct Shape</summary>
    private void DirectShapeLines(bool valueChanged)
    {

        if (measState == "RED" || valueChanged)
        {
            if (measShape1Points.Count == 0)
                return;

            openSegment1.positionCount = measShape1Points.Count;
            closeSegment1.positionCount = 2;

            Vector3[] tempVertices1Open = new Vector3[measShape1Points.Count];
            Vector3[] tempVertices1Close = new Vector3[2];

            for (int j = 0; j < measShape1Points.Count; j++)
            {
                tempVertices1Open[j] = vertices[measShape1Points[j]];
            }

            tempVertices1Close = new Vector3[]{vertices[measShape1Points[0]], vertices[measShape1Points[measShape1Points.Count-1]]};

            openSegment1.SetPositions(tempVertices1Open);
            closeSegment1.SetPositions(tempVertices1Close);
        }

        if (measState == "BLUE" || valueChanged)
        {
            if (measShape2Points.Count == 0)
                return;

            openSegment2.positionCount = measShape2Points.Count;
            closeSegment2.positionCount = 2;

            Vector3[] tempVertices2Open = new Vector3[measShape2Points.Count];
            Vector3[] tempVertices2Close = new Vector3[2];

            for (int j = 0; j < measShape2Points.Count; j++)
            {
                tempVertices2Open[j] = vertices[measShape2Points[j]];
            }

            tempVertices2Close = new Vector3[] { vertices[measShape2Points[0]], vertices[measShape2Points[measShape2Points.Count - 1]] };

            openSegment2.SetPositions(tempVertices2Open);
            closeSegment2.SetPositions(tempVertices2Close);
        }
    }

    /// <summary>Get value to create a mesh for Cut surface</summary>
    private void DrawCutInterSurface(List<int> indices, Color color)
    {
        if (((indices.Count % 3) != 0) || (indices.Count < 1)) //three points creates triangle
            return;

        Color[] mesh2Color = new Color[vertices.Length];
        Vector3[] mesh2Verices = new Vector3[indices.Count];
        int[] mesh2Indices = new int[indices.Count];
        string tempShape = "shape1";

        for (int i = 0; i < indices.Count; i++)
        {
            mesh2Indices[i] = indices[i];
        }

        for(int j = 0; j < vertices.Length; j++)
        {
            mesh2Color[j] = color;
        }

        if(color == Color.red || color == Color.green)
            CreateCutInterMesh(tempShape, mesh2Indices, mesh2Color);
        else if(color == Color.blue)
        {
            tempShape = "shape2";
            CreateCutInterMesh(tempShape, mesh2Indices, mesh2Color);
        }


    }

    private void CreateCutInterMesh(string shape, int[] indices, Color[] color)
    {

        if ( vertices.Length < 3) //at least one triangle required
        {
            Debug.Log("Parse Error: no vertices");
            return;
        }

        if (indices.Length < 3) //at least one triangle required
        {
            Debug.Log("Parse Error: no indices");
            return;
        }

        MeshFilter mesh2MF = null;

        if (shape == "shape1")
        {
            mesh2MF = cutInterShape1.GetComponent<MeshFilter>();
            mesh2MF.mesh = shapeMesh1;

            if(color[1] == Color.green)
            {
                cutInterShape1.GetComponent<MeshRenderer>().material = intershapeMaterial;
            }
            else
            {
                cutInterShape1.GetComponent<MeshRenderer>().material = cutMaterial;

            }

        }
        else
        {
            mesh2MF = cutInterShape2.GetComponent<MeshFilter>();
            mesh2MF.mesh = shapeMesh2;
        }
        
        mesh2MF.mesh.SetVertices(vertices);

        mesh2MF.mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;

        mesh2MF.mesh.SetColors(color);

        mesh2MF.mesh.SetTriangles(indices, 0);

        mesh2MF.mesh.RecalculateNormals();
        mesh2MF.mesh.Optimize();
              
    }

    #endregion

    #region Lines and Points methods

    /// <summary>Create Points after every click</summary>
    private void CreatePoints(int IDPoint)
    {
        if (!crosshairPoints.isOn)
        {
            GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
            cube.transform.parent = this.transform;

            if (measState == "RED")
            {
                cube.transform.position = points[IDPoint].Vertex.Position;
                cube.transform.localScale = new Vector3(2, 2, 2);
                cube.name = "cubeRED" + measShape1Points.Count.ToString() + this.name.ToString();
                cube.GetComponent<Renderer>().material.color = Color.red;
                Destroy(cube.GetComponent<BoxCollider>());

            }
            else
            {
                cube.transform.position = points[IDPoint].Vertex.Position;
                cube.transform.localScale = new Vector3(2, 2, 2);
                cube.name = "cubeBLUE" + measShape2Points.Count.ToString() + this.name.ToString();
                cube.GetComponent<Renderer>().material.color = Color.blue;
                Destroy(cube.GetComponent<BoxCollider>());
            }
        }
        else
        {

            GameObject crosshair = new GameObject();
            GameObject lineX = new GameObject();
            GameObject lineY = new GameObject();
            GameObject lineZ = new GameObject();

            crosshair.transform.parent = this.transform;
            lineX.transform.parent = crosshair.transform;
            lineY.transform.parent = crosshair.transform;
            lineZ.transform.parent = crosshair.transform;
            lineX.AddComponent<LineRenderer>();
            lineY.AddComponent<LineRenderer>();
            lineZ.AddComponent<LineRenderer>();


            if (measState == "RED")
            {
                crosshair.transform.position = points[IDPoint].Vertex.Position;
                Vector3 crossPoint = crosshair.transform.position;
                crosshair.name = "crosshairRED" + measShape1Points.Count.ToString();
                DrawCrosshair(crossPoint, lineX, lineY, lineZ, new Color(0.4716981f, 0, 0));
               
            }
            else
            {
                crosshair.transform.position = points[IDPoint].Vertex.Position;
                Vector3 crossPoint = crosshair.transform.position;
                crosshair.name = "crosshairBLUE" + measShape2Points.Count.ToString();
                DrawCrosshair(crossPoint, lineX, lineY, lineZ, new Color(0,0,0.4716981f));

            }

        }
    }

    /// <summary>Create crosshair on the selected point</summary>
    private void DrawCrosshair(Vector3 crossPoint, GameObject lineX, GameObject lineY, GameObject lineZ, Color color)
    {
        //lines - set numer of vertices and names
        lineX.GetComponent<LineRenderer>().positionCount = 3;
        lineY.GetComponent<LineRenderer>().positionCount = 3;
        lineZ.GetComponent<LineRenderer>().positionCount = 3;

        lineX.name = "LineX";
        lineY.name = "LineY";
        lineZ.name = "LineZ";


        //setcolor
        Shader tempShader = openSegment1.material.shader;

        lineX.GetComponent<LineRenderer>().material.shader = tempShader; //= Shader.Find("shaderName");
        lineY.GetComponent<LineRenderer>().material.shader = tempShader; //= Shader.Find("shaderName");
        lineZ.GetComponent<LineRenderer>().material.shader = tempShader; //= Shader.Find("Default-Line");

        lineX.GetComponent<LineRenderer>().startColor = color;
        lineY.GetComponent<LineRenderer>().startColor = color;
        lineZ.GetComponent<LineRenderer>().startColor = color;
        lineX.GetComponent<LineRenderer>().endColor = color;
        lineY.GetComponent<LineRenderer>().endColor = color;
        lineZ.GetComponent<LineRenderer>().endColor = color;

        // change width
        lineX.GetComponent<LineRenderer>().startWidth = 0.5f;
        lineY.GetComponent<LineRenderer>().startWidth = 0.5f;
        lineZ.GetComponent<LineRenderer>().startWidth = 0.5f;


        //set positions of vertices
        lineX.GetComponent<LineRenderer>().SetPositions(new Vector3[3] { new Vector3(crossPoint.x + 4, crossPoint.y, crossPoint.z), crossPoint, new Vector3(crossPoint.x - 4, crossPoint.y, crossPoint.z) });
        lineY.GetComponent<LineRenderer>().SetPositions(new Vector3[3] { new Vector3(crossPoint.x, crossPoint.y + 4, crossPoint.z), crossPoint, new Vector3(crossPoint.x, crossPoint.y - 4, crossPoint.z) });
        lineZ.GetComponent<LineRenderer>().SetPositions(new Vector3[3] { new Vector3(crossPoint.x, crossPoint.y, crossPoint.z + 4), crossPoint, new Vector3(crossPoint.x, crossPoint.y, crossPoint.z - 4) });

    }

    /// <summary>Delete previous right-click point and create new on mouse position</summary>
    private void CreateRightPoint(int IDPoint)
    {
        GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
        cube.transform.parent = this.transform;

        if (measState == "RED")
        {
            Destroy(GameObject.Find("cubeREDSPACE"));
            ResetColorPoints(innerPoints1);

            cube.transform.position = points[IDPoint].Vertex.Position;
            cube.transform.localScale = new Vector3(2, 2, 2);
            cube.name = "cubeREDSPACE";
            cube.GetComponent<Renderer>().material.color = Color.yellow;
            Destroy(cube.GetComponent<BoxCollider>());
        }
        else
        {
            Destroy(GameObject.Find("cubeBLUESPACE"));
            ResetColorPoints(innerPoints2);

            cube.transform.position = points[IDPoint].Vertex.Position;
            cube.transform.localScale = new Vector3(2, 2, 2);
            cube.name = "cubeBLUESPACE";
            cube.GetComponent<Renderer>().material.color = Color.cyan;
            Destroy(cube.GetComponent<BoxCollider>());
        }
    }

    /// <summary>Delete last point</summary>
    private void DeletePoint()
    {
        if (measState == "RED")
        {
            //cube
            Destroy(GameObject.Find("cubeRED" + (measShape1Points.Count + 1).ToString() + this.name.ToString()));
            //crosshair
            Destroy(GameObject.Find("crosshairRED" + (measShape1Points.Count + 1).ToString() + this.name.ToString()));
        }
        else
        {
            //cube
            Destroy(GameObject.Find("cubeBLUE" + (measShape2Points.Count + 1).ToString() + this.name.ToString()));
            //crosshair
            Destroy(GameObject.Find("crosshairBLUE" + (measShape2Points.Count + 1).ToString() + this.name.ToString()));
        }
    }

    /// <summary>Delete previous lanes of shape</summary>
    private void DeleteLanes(bool valueChanged)
    {
        if(measState == "RED" || valueChanged)
        {
            closeSegment1.positionCount = 0;
            openSegment1.positionCount = 0;

        }

        if (measState == "BLUE" || valueChanged)
        {
            closeSegment2.positionCount = 0;
            openSegment2.positionCount = 0;
        }
    }
    /// <summary>Resets definition of shape 1</summary>
    private void ResetShape1()
    {
        measShape1Points.Clear();
        measShape1Poly.Clear();
        closingSegment1.Clear();
        segmentLength1.Clear();
        neighbourPointTable.Clear();
        intershape.Clear();
        selectionPoint1 = -1;
    }

    /// <summary>Resets definition of shape 1</summary>
    private void ResetShape2()
    {
        measShape2Points.Clear();
        measShape2Poly.Clear();
        closingSegment2.Clear();
        segmentLength2.Clear();
        neighbourPointTable.Clear();
        intershape.Clear();
        selectionPoint2 = -1;
    }

    #endregion

    #region Calculate the measurement informations
    /// <summary>Add inner points to set of points defining shape boundary</summary>
    private HashSet<int> FillShape(List<int> boundary, int startpoint)
    {
        if (boundary.Count < 3) //skip if boundary has not enough of points
            return new HashSet<int>();
        if (startpoint < 0) //skip if startpoint not valid
            return new HashSet<int>();

        HashSet<int> filled = new HashSet<int>();
        HashSet<int> queue = new HashSet<int>();

        //filling algorithm
        queue.Add(startpoint);
        while (queue.Count > 0) //queue of points to process
        {
            int current = queue.First();
            queue.Remove(current);

            if ((!boundary.Contains(current)) && (!filled.Contains(current))) //is not at boundary and not yet filled
            {
                filled.Add(current); //fill point
                foreach (int point in neighbourPointTable[current]) //add to queue all neighbours not yet filled
                {
                    if (!filled.Contains(point))
                        queue.Add(point);
                }
            }
        }

        return filled;
    }

    /// <summary>Generate triangles of surface given by boundary (direct, not snapped). Used for cutting surface.</summary>
    private List<int> SurfaceTriangulation(List<int> boundary, List<int> segmentLength, int closingSegLength, List<int> clickedPoints, int selectionPoint)
    {
        List<int> output = new List<int>(); //output list of triangle indices
        List<int> entirePolygon = new List<int>(boundary); //copy of boundary
        List<int> segLength = new List<int>(segmentLength); //copy of segment length list
        segLength.Add(closingSegLength);

        entirePolygon.AddRange(clickedPoints); //add clicked points to processing list
        segLength.Add(clickedPoints.Count);

        while (entirePolygon.Count > 0) //perform triangulation for each segment of boundary
        {
            List<int> polygon = new List<int>(entirePolygon.GetRange(0, segLength[0])); //copy of segment
            entirePolygon.RemoveRange(0, segLength[0]); //remove first segment
            segLength.RemoveAt(0);

            while (polygon.Count > 2) //until at least one triangle remains
            {
                for (int i = 0; i < polygon.Count; i++) //remove all convex angles
                {
                    int previous = (i - 1) < 0 ? (polygon.Count - 1) : (i - 1);
                    int next = (i + 1) >= polygon.Count ? 0 : (i + 1);

                    // TODO: test zda ho nic neprotina a zda je konvexni

                    output.Add(polygon[previous]);
                    output.Add(polygon[i]);
                    output.Add(polygon[next]);

                    polygon.RemoveAt(i); //removing point skips next point, what is wanted
                }
            }
        }

        //compute triangle orientation relative to select point
        List<bool> orientation = new List<bool>();
        for (int i = 1; i < clickedPoints.Count - 1; i++)
            orientation.Add(PointAbovePlane(clickedPoints[0], clickedPoints[i], clickedPoints[i + 1], selectionPoint));

        int numOfAbove = orientation.Where(x => x).Count(); //how many positive items?

        if (numOfAbove > (orientation.Count / 2)) //majority of triangles have wrong orientation
            for (int i = 0; i < output.Count; i += 3) //flip triangles
            {
                int aux = output[i];
                output[i] = output[i + 2];
                output[i + 2] = aux;
            }

        return output;
    }

    /// <summary>Generate triangles of intershape surface given by boundary (direct, not snapped).</summary>
    private List<int> IntershapeTriangulation(List<int> boundary1, List<int> boundary2)
    {
        if ((boundary1.Count < 1) || (boundary2.Count < 1))//at least one point must be present
            return new List<int>();

        List<int> shape1 = new List<int>(boundary1.Distinct()); //copy of boundary
        List<int> shape2 = new List<int>(boundary2.Distinct()); //copy of boundary
        List<int> indices = new List<int>(); //output indices

        //extend shapes to same length
        if (shape1.Count < shape2.Count) //shape 1 is shorter
            shape1 = StretchList(shape1, shape2.Count);
        if (shape2.Count < shape1.Count) //shape 2 is shorter
            shape2 = StretchList(shape2, shape1.Count);

        //perform triangulation
        for (int i = 1; i < shape1.Count; i++)
        {
            indices.Add(shape1[i - 1]);
            indices.Add(shape1[i]);
            indices.Add(shape2[i - 1]);

            indices.Add(shape1[i]);
            indices.Add(shape2[i]);
            indices.Add(shape2[i - 1]);
        }

        return indices;
    }

    /// <summary>Stretch list to defined length.</summary>
    private List<int> StretchList(List<int> list, int length)
    {
        List<int> output = new List<int>();

        if (list.Count < 1) //at least one item required
            return output;

        int pList = -1; //pointer to actual item in list
        float spacing = (float)length / (float)(list.Count - 1);

        for (int i = 0; i < length; i++)
        {
            if (i >= ((float)pList * spacing))
                pList++;
            output.Add(list[pList]);
        }

        return output;
    }
    #endregion

    /// <summary>Selects clicked point on model defined by its index.</summary>
    private void SelectMeasShapePoint(int IDpoint)
    {

        if (measState == "RED")
        {
            measShape1Points.Add(IDpoint); //save index of point to shape

            CreatePoints(IDpoint); // create a point

            if (measShape1Points.Count > 1) //compute and save snapped polygon for actually added point
            {
                var lastSeg = FindShortestPath(measShape1Points[measShape1Points.Count - 2], measShape1Points[measShape1Points.Count - 1]);
                measShape1Poly.AddRange(lastSeg);
                segmentLength1.Add(lastSeg.Count);
                closingSegment1 = FindShortestPath(measShape1Points[measShape1Points.Count - 1], measShape1Points[0]);
            }
        }
        else
        {
            measShape2Points.Add(IDpoint); //save point to shape

            CreatePoints(IDpoint); // create a point

            if (measShape2Points.Count > 1) //compute and save snapped polygon for actually added point
            {

                var lastSeg = FindShortestPath(measShape2Points[measShape2Points.Count - 2], measShape2Points[measShape2Points.Count - 1]);
                measShape2Poly.AddRange(lastSeg);
                segmentLength2.Add(lastSeg.Count);
                closingSegment2 = FindShortestPath(measShape2Points[measShape2Points.Count - 1], measShape2Points[0]);
            }
        }


        CreateLines();
        Measure_Recompute();
    }

    #endregion

    #region Math methods - Measurement

    /// <summary>Finds shortest path between two points, return list of points to go through</summary>
    private List<int> FindShortestPath(int start, int end)
    {
        //source: http://www.policyalmanac.org/games/aStarTutorial.htm

        if (neighbourPointTable.Count <= 0) //if not yet done, prepare table of neigbours atc.
            ComputeCommonPrerequisites();

        HashSet<int> openSet = new HashSet<int>(); // The set of tentative nodes to be evaluated, initially containing the start node
        HashSet<int> closedSet = new HashSet<int>(); // Verified nodes
        Dictionary<int, int> parent = new Dictionary<int, int>(); // Parents of nodes. Key=child, value=his parent
        Dictionary<int, float> fScore = new Dictionary<int, float>(); // fScores of each node
        Dictionary<int, float> gScore = new Dictionary<int, float>(); // gScores of each node
        Dictionary<int, float> hScore = new Dictionary<int, float>(); // hScores of each node

        openSet.Add(start);
        gScore[start] = 0;
        hScore[start] = GetMmDistance(start, end);
        fScore[start] = gScore[start] + hScore[start];

        while (openSet.Count > 0) //is anything in openSet?
        {
            int current = hScore.Where(x => openSet.Contains(x.Key)).OrderBy(x => x.Value).First().Key; //choose node from openSet with minimal hScore (in source fScore is used)

            if (current == end) //we found the target
                return ReconstructPath(parent, current);

            openSet.Remove(current); //move current node to closedSet
            closedSet.Add(current);

            HashSet<int> neigbours = neighbourPointTable[current]; //neighbours of current point
            foreach (int node in neigbours)
            {
                if (closedSet.Contains(node)) //if it is in closedSet, ignore it
                    continue;

                if (openSet.Contains(node)) //already in openSet
                {
                    float gScoreEstimated = gScore[current] + GetMmDistance(current, node);
                    if (gScoreEstimated < gScore[node])
                    {
                        parent[node] = current;
                        gScore[node] = gScoreEstimated;
                        fScore[node] = gScore[node] + hScore[node];
                    }
                }
                else //not yet in openSet
                {
                    openSet.Add(node);
                    parent[node] = current;
                    gScore[node] = gScore[current] + GetMmDistance(current, node);
                    hScore[node] = GetMmDistance(node, end);
                    fScore[node] = gScore[node] + hScore[node];
                }
            }
        }

        return new List<int>(); //path not found
    }

    /// <summary>Recursively computes path from Astar data. Used for FindShortestPath method.</summary>
    private List<int> ReconstructPath(Dictionary<int, int> cameFrom, int currentNode)
    {
        if (cameFrom.ContainsKey(currentNode))
        {
            List<int> path = ReconstructPath(cameFrom, cameFrom[currentNode]);
            path.Add(currentNode);
            return path;
        }
        else
        {
            List<int> path = new List<int>(1);
            path.Add(currentNode);
            return path;
        }
    }

    /// <summary>Computes several lookup tables used in math functions</summary>
    private void ComputeCommonPrerequisites()
    {
        for (int i = 0; i < indices.Length; i++)
            neighbourPointTable[indices[i]] = new HashSet<int>(); //initialize

        for (int i = 0; i < indices.Length; i += 3) //for each triangle
        {
            neighbourPointTable[indices[i]].Add(indices[i + 1]);
            neighbourPointTable[indices[i]].Add(indices[i + 2]);

            neighbourPointTable[indices[i + 1]].Add(indices[i]);
            neighbourPointTable[indices[i + 1]].Add(indices[i + 2]);

            neighbourPointTable[indices[i + 2]].Add(indices[i]);
            neighbourPointTable[indices[i + 2]].Add(indices[i + 1]);

            float area = ComputeTriangleArea(points[indices[i]].Vertex.Position, points[indices[i + 1]].Vertex.Position, points[indices[i + 2]].Vertex.Position);
            float volume = ComputeSignedTriangleVolume(points[indices[i]].Vertex.Position, points[indices[i + 1]].Vertex.Position, points[indices[i + 2]].Vertex.Position);
            triangleAreas.Add(area);
            triangleVolumes.Add(volume);
            entireModelArea += area;
        }
    }

    /// <summary>Computes area of giver triangle {in mm}</summary>
    private float ComputeTriangleArea(Vector3 P1, Vector3 P2, Vector3 P3)
    {
        Vector3 AB = P2 - P1;
        Vector3 AC = P3 - P1;
        return (Vector3.Cross(AB, AC).magnitude / 2.0f);
    }

    /// <summary>Compute surface area from shape {in units}</summary>
    private float ComputeShapeArea(HashSet<int> innerPoints, List<int> boundaryPoints = null)
    {
        HashSet<int> points = new HashSet<int>(innerPoints);
        if (boundaryPoints != null)
            points.UnionWith(boundaryPoints);

        float area = 0;
        for (int i = 0; i < indices.Length; i += 3)
        {
            if (points.Contains(indices[i]) && points.Contains(indices[i + 1]) && points.Contains(indices[i + 2])) //if all triangle vertices belongs to shape 
            {
                area += triangleAreas[i / 3]; //add area of traingle preprepared in lookup table
            }
        }
        return area / (((float)Unit) * ((float)Unit));
    }

    /// <summary>Computes angle in degrees defined by three points. Range 0 to 180, if convex specified 0 to 360.</summary>
    private float GetAngle(int A, int V, int B, bool isConvex = true)
    {
        if ((A >= points.Count) || (V >= points.Count) || (B >= points.Count))
            return float.NaN;

        Vector3 v1 = points[A].Vertex.Position - points[V].Vertex.Position;
        Vector3 v2 = points[B].Vertex.Position - points[V].Vertex.Position;
        v1.Normalize();
        v2.Normalize();

        float angle = Mathf.Rad2Deg * (float)Math.Acos(Vector3.Dot(v1, v2));
        if (isConvex)
            return angle;
        else
            return 360 - angle;
    }

    /// <summary>Compute distance between two points in mm. Used for FindShortestPath method as a heuristic estimate.</summary>
    private float GetMmDistance(int from, int to)
    {
        return (points[to].Vertex.Position - points[from].Vertex.Position).magnitude;
    }

    /// <summary>Compute distance between two points {in units}.</summary>
    private float GetDistance(int from, int to)
    {
        return GetMmDistance(from, to) / (float)Unit;
    }

    /// <summary>Returns if point lays in the same half-space as normal of delimiting plane defined by plane vertices.</summary>
    private bool PointAbovePlane(int planeA, int planeB, int planeC, int point)
    {
        if ((planeA < 0) || (planeB < 0) || (planeC < 0) || (point < 0))
            return false;

        Vector3 A = points[planeA].Vertex.Position - points[planeB].Vertex.Position;
        Vector3 C = points[planeC].Vertex.Position - points[planeB].Vertex.Position;
        Vector3 X = points[point].Vertex.Position - points[planeB].Vertex.Position;
        float determinant = A.x * C.y * X.z + C.x * X.y * A.z + X.x * A.y * C.z - X.x * C.y * A.z - C.x * A.y * X.z - A.x * X.y * C.z;
        return (determinant > 0);
    }

    /// <summary>Computes area of giver triangle {in mm}</summary>
    private float ComputeSignedTriangleVolume(Vector3 P1, Vector3 P2, Vector3 P3)
    {
        //source: http://research.microsoft.com/en-us/um/people/chazhang/publications/icip01_ChaZhang.pdf

        var v321 = P3.x * P2.y * P1.z;
        var v231 = P2.x * P3.y * P1.z;
        var v312 = P3.x * P1.y * P2.z;
        var v132 = P1.x * P3.y * P2.z;
        var v213 = P2.x * P1.y * P3.z;
        var v123 = P1.x * P2.y * P3.z;
        return (1.0f / 6.0f) * (-v321 + v231 + v312 - v132 - v213 + v123);
    }

    /// <summary>Compute volume of shape {in units}</summary>
    private float ComputeShapeVolume(HashSet<int> innerPoints, List<int> cuttingSurface, List<int> boundaryPoints = null)
    {
        HashSet<int> shapePoints = new HashSet<int>(innerPoints);
        if (boundaryPoints != null)
            shapePoints.UnionWith(boundaryPoints);

        float volume = 0;
        for (int i = 0; i < indices.Length; i += 3)
        {
            if (shapePoints.Contains(indices[i]) && shapePoints.Contains(indices[i + 1]) && shapePoints.Contains(indices[i + 2])) //if all triangle vertices belongs to shape 
            {
                volume += triangleVolumes[i / 3]; //add signed volume of traingle preprepared in lookup table
            }
        }

        //Cutting surface signed volume adding
        for (int i = 0; i < cuttingSurface.Count; i += 3)
            volume += ComputeSignedTriangleVolume(points[cuttingSurface[i]].Vertex.Position, points[cuttingSurface[i + 1]].Vertex.Position, points[cuttingSurface[i + 2]].Vertex.Position);

        return Math.Abs(volume) / (((float)Unit) * ((float)Unit) * ((float)Unit));
    }

    /// <summary>Compute volume of intershape {in units}</summary>
    private float ComputeIntershapeVolume(HashSet<int> shape1InnerPoints, HashSet<int> shape2InnerPoints, List<int> intershapeTriangles, List<int> boundary1Points = null, List<int> boundary2Points = null)
    {
        HashSet<int> shapePoints = new HashSet<int>(shape1InnerPoints);
        shapePoints.UnionWith(shape2InnerPoints);
        if (boundary1Points != null)
            shapePoints.UnionWith(boundary1Points);
        if (boundary2Points != null)
            shapePoints.UnionWith(boundary2Points);

        float volume = 0;
        for (int i = 0; i < indices.Length; i += 3)
        {
            if (shapePoints.Contains(indices[i]) && shapePoints.Contains(indices[i + 1]) && shapePoints.Contains(indices[i + 2])) //if all triangle vertices belongs to shape 
            {
                volume += triangleVolumes[i / 3]; //add signed volume of traingle preprepared in lookup table
            }
        }

        //adding volumes of intershape triangles
        for (int i = 0; i < intershapeTriangles.Count; i += 3)
            volume += ComputeSignedTriangleVolume(points[intershapeTriangles[i]].Vertex.Position, points[intershapeTriangles[i + 1]].Vertex.Position, points[intershapeTriangles[i + 2]].Vertex.Position);

        return Math.Abs(volume) / (((float)Unit) * ((float)Unit) * ((float)Unit));
    }

    /// <summary>Compute average temperature of selected shape {in degrees of celsius}</summary>
    private float ComputeShapeTemperature(HashSet<int> innerPoints, List<int> boundaryPoints = null)
    {
        HashSet<int> pointIndices = new HashSet<int>(innerPoints);
        if (boundaryPoints != null)
            pointIndices.UnionWith(boundaryPoints);

        if (pointIndices.Count > 0)
            return points.Where((x, i) => pointIndices.Contains(i)).Select(x => x.Temperature).Average();
        else
            return float.NaN;

    }

    /// <summary>Computes length of polygon given by indices {in units}</summary>
    private float PolygonLength(List<int> polygon)
    {
        float sum = 0;

        if (polygon.Count > 1)
            for (int i = 1; i < polygon.Count; i++)
                sum += GetDistance(polygon[i - 1], polygon[i]);

        return sum;
    }

    /// <summary>Computes length of polygon given by indices {in units} and saves information about each segment into the string 'lengths'</summary>
    private float PolygonLength(List<int> polygon, ref string lengths)
    {
        float sum = 0;

        if (polygon.Count > 1)
            for (int i = 1; i < polygon.Count; i++)
            {
                float distance = GetDistance(polygon[i - 1], polygon[i]);
                sum += distance;
                string angleString = "";
                if ((i + 1) < polygon.Count)
                    angleString = " / " + GetAngle(polygon[i - 1], polygon[i], polygon[i + 1]).ToString("N2") + "°";
                else
                    angleString = " / " + GetAngle(polygon[i - 1], polygon[i], polygon[0]).ToString("N2") + "°";


                lengths += distance.ToString("N2") + " " + UnitName + angleString + Environment.NewLine;
            }

        return sum;
    }

    #endregion

    #region Other help methods
    /// <summary>Returns nerest point of pointGroup to given ray. If pointGroup omitted, whole model is evaluated. If distance to nearest pount is bigger than MAX_CLICKED_DISTANCE, the -1 is returned.</summary>
    /// <param name="ray"></param>
    /// <returns></returns>
    private int NearestPointToRay(Ray ray, RaycastHit hits, List<ModelPoint> pointGroup = null)
    {
        if (pointGroup == null)
            pointGroup = points;

        //find nearest point to ray => find selected point
        float minDist = float.MaxValue;
        int selectedPoint = -1;

        for (int i = 0; i < pointGroup.Count; i++)
        {
            float distRayToPoint = Vector3.Cross(ray.direction, pointGroup[i].Vertex.Position - hits.point).magnitude;
            if (distRayToPoint < minDist)
            {

                selectedPoint = i;
                minDist = distRayToPoint;
            }
        }

        return selectedPoint;
    }

    /// <summary> disable rendering 2nd model </summary>
    private void DisableModel()
    {
        GameObject model2 = GameObject.Find("Mesh Generator2");
        model2.GetComponent<Renderer>().enabled = false;
    }

    #endregion

    #endregion

    #region Public methods

    #region Build the model

    /// <summary> Updating the mesh after the changing any data </summary>
    public void MeshUpdate()
    {
        mesh.SetVertices(vertices);

        mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;

        mesh.SetTriangles(indices /*indicesFlip*/, 0); //can change the normals

        mesh.SetNormals(origNormal);

        ToArray(colors);
        mesh.SetColors(colors);

        //set mesh collider for created mesh
        GetComponent<MeshFilter>().sharedMesh = mesh;
        GetComponent<MeshCollider>().sharedMesh = mesh;

        GetComponent<MeshCollider>().enabled = false;
        GetComponent<MeshCollider>().enabled = true;

        //mesh.RecalculateNormals();
        mesh.RecalculateBounds();
        mesh.Optimize();

    }

    /// <summary> Start of building - get the data from file </summary>
    public void File_explore()
    {
        GC.Collect();

        // reset previous layers
        AvailableLayers = 0;
        // destroy 2nd mesh
        DisableModel();

        Get3dmodPath();
        if (path != null)
        {
            XmlReader file2 = XmlReader.Create(path);

            while (file2.Read())
            {
                if (file2.Name.Equals("block") && (file2.NodeType == XmlNodeType.Element))
                {
                    if (file2.GetAttribute("type") == "distance") //read color layer
                    {
                        AvailableLayers |= RoscanModelUnity.ModelLayers.DISTANCE;
                    }
                }
            }
            file2.Close();

            XmlReader file = XmlReader.Create(path);
            while (file.Read())
            {
                if (file.Name.Equals("block") && (file.NodeType == XmlNodeType.Element))
                {
                    if (file.GetAttribute("type") == "metadata")
                    {
                        modelMetadata = XNode.ReadFrom(file) as XElement;
                        ParseMetadata(modelMetadata);
                    }
                    if (file.GetAttribute("type") == "vertices") //read vertices layer
                    {
                        byte[] bytes = new byte[int.Parse(file.GetAttribute("length"))];
                        file.ReadElementContentAsBase64(bytes, 0, bytes.Length);
                        LoadVertices(bytes);
                        AvailableLayers |= RoscanModelUnity.ModelLayers.SURFACE;
                        ParseVerticesData();
                    }
                    if (file.GetAttribute("type") == "indices") //read indices layer
                    {
                        byte[] bytes = new byte[int.Parse(file.GetAttribute("length"))];
                        file.ReadElementContentAsBase64(bytes, 0, bytes.Length);
                        LoadIndices(bytes);
                    }
                    if (file.GetAttribute("type") == "reflectivity") //read reflectivity layer
                    {
                        byte[] bytes = new byte[int.Parse(file.GetAttribute("length"))];
                        file.ReadElementContentAsBase64(bytes, 0, bytes.Length);
                        LoadReflectivity(bytes);
                        AvailableLayers |= RoscanModelUnity.ModelLayers.REFLECTIVITY;
                    }
                    if (file.GetAttribute("type") == "refl_width") //read reflectionWidth layer
                    {
                        byte[] bytes = new byte[int.Parse(file.GetAttribute("length"))];
                        file.ReadElementContentAsBase64(bytes, 0, bytes.Length);
                        LoadReflWidth(bytes);
                        AvailableLayers |= RoscanModelUnity.ModelLayers.REFL_WIDTH;
                    }
                    if (file.GetAttribute("type") == "color") //read color layer
                    {
                        byte[] bytes = new byte[int.Parse(file.GetAttribute("length"))];
                        file.ReadElementContentAsBase64(bytes, 0, bytes.Length);
                        LoadColors(bytes);
                        AvailableLayers |= RoscanModelUnity.ModelLayers.COLOR;
                    }
                    if (file.GetAttribute("type") == "temperature") //read temperature layer
                    {
                        byte[] bytes = new byte[int.Parse(file.GetAttribute("length"))];
                        file.ReadElementContentAsBase64(bytes, 0, bytes.Length);
                        LoadTemperature(bytes);
                        AvailableLayers |= RoscanModelUnity.ModelLayers.TEMPERATURE;
                    }
                    if (file.GetAttribute("type") == "color_history") //read color layer
                    {
                        modelColorHistory = XNode.ReadFrom(file) as XElement;
                    }
                    if (file.GetAttribute("type") == "temperature_history") //read temperature layer
                    {
                        modelTempHistory = XNode.ReadFrom(file) as XElement;
                    }
                    if (file.GetAttribute("type") == "distance") //read distance layer - ONLY COMPARE
                    {
                        byte[] bytes = new byte[int.Parse(file.GetAttribute("length"))];
                        file.ReadElementContentAsBase64(bytes, 0, bytes.Length);
                        LoadDistance(bytes);
                        AvailableLayers |= RoscanModelUnity.ModelLayers.DISTANCE;
                    }
                }
            }
            file.Close();

            CalculateNormals(points, indices);
            MeshPosition(ModelCenter);
            CreateShape();
            SwitchLayer(ModelLayers.SURFACE);
        }
        else
            return;
    }

    #endregion

    #region Layer methods

    /// <summary>Switches to another file layer</summary>
    public void SwitchLayer(ModelLayers layer)
    {
        ActualLayer = layer;

        PaletteMin = LayerValueMin;
        PaletteMax = LayerValueMax;

        if (points.Count <= 0)
            return;

        ResetColorPoints(points); //sets color of points
    }

    /// <summary>Update color of selected vertices according to desired layer and palette settings and its initial state.</summary>
    public void ResetColorPoints(List<ModelPoint> pointList)
    {
        if (ActualLayer == ModelLayers.COLOR)
        {
            foreach (ModelPoint point in pointList)
                point.Vertex.Color = point.Color; //reset to original color
        }
        else if (ActualLayer == ModelLayers.SURFACE)
        {
            foreach (ModelPoint point in pointList)
            {
                point.Vertex.Color = COLOR_NO_VALUE;

            }
        }
        else //it is scalar layer -> we have to make false colors
        {
            Color[] palette;

            switch (ReflectionPalette)
            {
                case 1: palette = RAINBOW_SCALE; break;
                case 0: palette = GRAY_SCALE; break;
                case 2: palette = THERMO_SCALE; break;
                default: palette = THERMO_SCALE; break;
            }

            if (ActualLayer == ModelLayers.REFLECTIVITY)
            {
                foreach (ModelPoint point in pointList)
                    point.Vertex.Color = GetColorByValue(point.Reflectivity, palette, PaletteMin, PaletteMax);
            }
            if (ActualLayer == ModelLayers.REFL_WIDTH)
            {
                foreach (ModelPoint point in pointList)
                    point.Vertex.Color = GetColorByValue(point.ReflWidth, palette, PaletteMin, PaletteMax);
            }
            if (ActualLayer == ModelLayers.TEMPERATURE)
            {
                foreach (ModelPoint point in pointList)
                    point.Vertex.Color = GetColorByValue(point.Temperature, palette, PaletteMin, PaletteMax);
            }
            if (ActualLayer == ModelLayers.DISTANCE)
            {
                foreach (ModelPoint point in pointList)
                    point.Vertex.Color = GetColorByValue(point.Distance, palette, PaletteMin, PaletteMax);
            }
        }

        MeshUpdate(); //update

    }
    #endregion

    #region Public help methods

    /// <summary>Returns color defined by given bar</summary>
    public static Color GetColorByValue(double value, Color[] scale, double minValue, double maxValue)
    {
        float scaledT = (float)((value - minValue) / (maxValue - minValue)) * (float)(scale.Length - 1);

        if (scaledT > 0 && scaledT < (scale.Length - 1))
        {
            Color prevColor = scale[(int)Math.Floor(scaledT)];
            Color nextColor = scale[(int)Math.Ceiling(scaledT)];
            float newT = scaledT - (float)Math.Floor(scaledT);
            return Color.Lerp(prevColor, nextColor, newT);
        }
        else if (scaledT >= (scale.Length - 1))
            return scale[scale.Length - 1];
        else
            return scale[0];
    }

    /// <summary>Sets pallete max according to percantage in 3 times wider range</summary>
    public void SetPalleteMinAsPercentage()
    {
        PaletteMin = LayerValueMin - (LayerValueMax - LayerValueMin);
    }

    /// <summary>Sets pallete max according to percantage in 3 times wider range</summary>
    public void SetPalleteMaxAsPercentage()
    {
        PaletteMax = LayerValueMax + (LayerValueMax - LayerValueMin);
    }

    /// <summary> Get sizes of model </summary>
    public Vector3 getSizeOfModel()
    {
        float xSize = _VerticesXMax - _VerticesXMin;
        float ySize = _VerticesYMax - _VerticesYMin;
        float zSize = _VerticesZMax - _VerticesZMin;
        
        return new Vector3(xSize, ySize, zSize);
    }

    /// <summary> enable rendering 2nd model </summary>
    public void EnableModel()
    {
        GameObject model2 = GameObject.Find("Mesh Generator2");
        model2.GetComponent<Renderer>().enabled = true;
    }


    #endregion

    #region Export Model

    /// <summary>Get the path with type of file</summary>
    public void getSavePath()
    {
        string tempPath = "";
        string tempType = "";

        #if UNITY_STANDALONE_WIN
        var bp = new BrowserProperties();

        bp.filter = "XYZ file (*.XYZ)|*.XYZ|PTS file (*.PTS)|*.PTS|PLY file (*.PLY)|*.PLY";
        bp.filterIndex = 0;

        new FileBrowser().SaveFileBrowser(bp, "FileName", ".XYZ", path =>
        {
            tempPath = path;
        });
        #endif

        for(int i = 0; i < tempPath.Length; i++)
        {
            if(tempPath[i] == '.')
            {
                tempType = tempPath[i + 1].ToString() + tempPath[i+2].ToString() + tempPath[i+3].ToString();
                break;
            }
        }

        switch (tempType)
        {
            case "XYZ":
                SaveXYZ(tempPath);
                break;
            case "PLY":
                SavePLY(tempPath);
                break;
            case "PTS":
                SavePTS(tempPath);
                break;
            default:
                SaveXYZ(tempPath);
                break;
        }

    }

    #endregion

    #region simplify model

    /// <summary>get vertices from simplifed mesh </summary>
    /// from Whinarn
    /// https://github.com/Whinarn/UnityMeshSimplifier
    public void SimplifyMesh()
    {
        var meshSimplifier = new UnityMeshSimplifier.MeshSimplifier();
        meshSimplifier.Vertices = mesh.vertices;

        for (int i = 0; i < mesh.subMeshCount; i++)
        {
            meshSimplifier.AddSubMeshTriangles(mesh.GetTriangles(i));
        }

        // simplify
        meshSimplifier.SimplifyMesh(quality);

        simplVert = meshSimplifier.Vertices;

        mesh.RecalculateNormals();

        simplNorm = mesh.normals;
    }
    #endregion

    #region Measurement

    #region Mouse Clicks
    /// <summary>Processes clicking of the left button. The ray through the space defines the position of click.</summary>
    public void LeftMouseClick(Ray ray, RaycastHit[] hits)
    {
        int IDpoint = 0;
        bool meshHit = false, mirrorHit = false;
        int meshIndex = 0, mirrorIndex = 0;

        if (hits.Length == 3 || hits.Length == 2 || hits.Length == 4) //always 3 collidors: cube, mesh and mirror or 2: cube + mirror
        {
            for (int i = 0; i < hits.Length; i++ )
            {
                switch (hits[i].transform.name)
                {
                    case "Mesh Generator":
                        meshHit = true;
                        meshIndex = i;
                        break;

                    case "Mirror":
                        mirrorHit = true;
                        mirrorIndex = i;
                        break;

                    default:
                        break;
                }
            }

            if (meshHit)
            {
                IDpoint = NearestPointToRay(ray, hits[meshIndex]);
            }
            else
            {
                if (mirrorHit) // for backside of model
                {
                    Ray ray2 = new Ray(hits[mirrorIndex].point, Vector3.Reflect(ray.direction, ray.direction)); // creating new ray (from point of reflection and with 0 angle)
                    RaycastHit[] hits2;
                    hits2 = Physics.RaycastAll(ray2);  

                    if (hits2.Length == 2 || hits2.Length == 1 || hits2.Length == 3) // check if user clicked on the model
                    {
                        for (int i = 0; i < hits2.Length; i++)
                        {
                            if (hits2[i].transform.name == "Mesh Generator")
                            {
                                IDpoint = NearestPointToRay(ray2, hits2[i]);
                                break;
                            }
                        }
                    }
                    else 
                    {
                        IDpoint = -1;
                        Debug.Log("Model was missed");
                    }
                }
            }
        }
       
        if (IDpoint != -1) //if any point has been selected
           SelectMeasShapePoint(IDpoint);
    }

    /// <summary>Processes clicking of the right button. The ray through the space defines the position of click.</summary>
    public void RightMouseClick(Ray ray, RaycastHit[] hits)
    {
        int IDpoint = 0;
        bool meshHit = false, mirrorHit = false;
        int meshIndex = 0, mirrorIndex = 0;

        if (hits.Length == 3 || hits.Length == 2) //always 3 collidors: cube, mesh and mirror or 2: cube + mirror
        {
            for (int i = 0; i < hits.Length; i++)
            {
                switch (hits[i].transform.name)
                {
                    case "Mesh Generator":
                        meshHit = true;
                        meshIndex = i;
                        break;

                    case "Mirror":
                        mirrorHit = true;
                        mirrorIndex = i;
                        break;

                    default:
                        break;
                }
            }

            if (meshHit)
            {
                IDpoint = NearestPointToRay(ray, hits[meshIndex]);
            }
            else
            {
                if (mirrorHit) // for backside of model
                {
                    Ray ray2 = new Ray(hits[mirrorIndex].point, Vector3.Reflect(ray.direction, ray.direction)); // creating new ray (from point of reflection and with 0 angle)
                    RaycastHit[] hits2;
                    hits2 = Physics.RaycastAll(ray2);

                    if (hits2.Length == 2) // check if user clicked on the model
                    {
                        for (int i = 0; i < hits2.Length; i++)
                        {
                            if (hits2[i].transform.name == "Mesh Generator")
                            {
                                IDpoint = NearestPointToRay(ray2, hits2[i]);
                                break;
                            }
                        }
                    }
                    else
                    {
                        IDpoint = -1;
                        Debug.Log("Model was missed");
                    }
                }
            }
        }

        if (IDpoint != -1) //if any point has been selected
        {
            if (measState == "RED")
            {
                selectionPoint1 = IDpoint;
                CreateRightPoint(selectionPoint1);
                Measure_Recompute();
            }
            else
            {
                selectionPoint2 = IDpoint;
                CreateRightPoint(selectionPoint2);
                Measure_Recompute();
            }
        }
    }

    #endregion

    #region Delete methods
    /// <summary>Delete last point of measuring shape definition</summary>
    public void MeasureDeleteLastPoint()
    {
        if (measState == "RED")
        {
            if (measShape1Points.Count > 0)
            {
                measShape1Points.RemoveAt(measShape1Points.Count - 1);
                if (measShape1Points.Count > 0) //is still any point
                {
                    measShape1Poly.RemoveRange(measShape1Poly.Count - segmentLength1[segmentLength1.Count - 1], segmentLength1[segmentLength1.Count - 1]);
                    segmentLength1.RemoveAt(segmentLength1.Count - 1);
                    closingSegment1 = FindShortestPath(measShape1Points[measShape1Points.Count - 1], measShape1Points[0]);
                    ResetColorPoints(innerPoints1); //unhighlight measure area
                }
                CreateLines();
                DeletePoint();
                Measure_Recompute();
            }
        }
        else
        {
            if (measShape2Points.Count > 0)
            {
                measShape2Points.RemoveAt(measShape2Points.Count - 1);
                if (measShape2Points.Count > 0) //is still any point
                {
                    measShape2Poly.RemoveRange(measShape2Poly.Count - segmentLength2[segmentLength2.Count - 1], segmentLength2[segmentLength2.Count - 1]);
                    segmentLength2.RemoveAt(segmentLength2.Count - 1);
                    closingSegment2 = FindShortestPath(measShape2Points[measShape2Points.Count - 1], measShape2Points[0]);
                    ResetColorPoints(innerPoints2); //unhighlight measure area
                }
                CreateLines();
                DeletePoint();
                Measure_Recompute();
            }
        }
    }

    /// <summary>Delete every created line and points</summary>
    public void DeleteAllPointsLines()
    {
        // skip deleting if the mesh2 wasnt created
        if (this.name == "Mesh Generator2" && this.mesh == null)
            return;

        // left-click points
        for(int i = 1; i <= measShape1Points.Count; i++)
        {
            //cubes
            Destroy(GameObject.Find("cubeRED" + i.ToString() + this.name.ToString()));
            //crosshair
            Destroy(GameObject.Find("crosshairRED" + i.ToString()));
        }

        for (int j = 1; j <= measShape2Points.Count; j++)
        {
            //cube
            Destroy(GameObject.Find("cubeBLUE" + j.ToString() + this.name.ToString()));
            //crosshair
            Destroy(GameObject.Find("crosshairBLUE" + j.ToString()));
        }

        // right-click points
        Destroy(GameObject.Find("cubeREDSPACE"));
        Destroy(GameObject.Find("cubeBLUESPACE"));
        ResetColorPoints(innerPoints1);
        ResetColorPoints(innerPoints2);


        // lines
        closeSegment1.positionCount = 0;
        closeSegment2.positionCount = 0;
        openSegment1.positionCount = 0;
        openSegment2.positionCount = 0;

        //meshes of shapes
        cutInterShape1.GetComponent<MeshFilter>().mesh = null;
        cutInterShape2.GetComponent<MeshFilter>().mesh = null;

        ResetShape1();
        ResetShape2();
        Measure_Recompute();
    }

    /// <summary>Delete created line and points into SHAPE1</summary>
    public void DeleteShape1PointsLines()
    {
        // Left-click points
        for (int i = 1; i <= measShape1Points.Count; i++)
        {
            //cubes
            Destroy(GameObject.Find("cubeRED" + i.ToString() + this.name.ToString()));
            //crosshair
            Destroy(GameObject.Find("crosshairRED" + i.ToString() + this.name.ToString()));
        }

        // Red-click point
        Destroy(GameObject.Find("cubeREDSPACE"));
        ResetColorPoints(innerPoints1);

        //lines
        closeSegment1.positionCount = 0;
        openSegment1.positionCount = 0;

        //mesh of shape
        cutInterShape1.GetComponent<MeshFilter>().mesh = null;

        ResetShape1();
        Measure_Recompute();
    }

    /// <summary>Delete created line and points into SHAPE2</summary>
    public void DeleteShape2PointsLines()
    {
        // Left-click points
        for (int i = 1; i <= measShape2Points.Count; i++)
        {
            //cube
            Destroy(GameObject.Find("cubeBLUE" + i.ToString() + this.name.ToString()));
            //crosshair
            Destroy(GameObject.Find("crosshairBLUE" + i.ToString() + this.name.ToString()));
        }

        // Red-click point
        Destroy(GameObject.Find("cubeBLUESPACE"));
        ResetColorPoints(innerPoints2);

        //lines
        closeSegment2.positionCount = 0;
        openSegment2.positionCount = 0;

        //mesh of shape
        cutInterShape1.GetComponent<MeshFilter>().mesh = null;
        cutInterShape2.GetComponent<MeshFilter>().mesh = null;

        ResetShape2();
        Measure_Recompute();
    }

    /// <summary>Hide all created lines and points on model</summary>
    public void hideVisuals()
    {
        for (int i = 1; i <= measShape1Points.Count; i++)
        {
            //cubes
            Destroy(GameObject.Find("cubeRED" + i.ToString() + this.name.ToString()));
            //crosshair
            Destroy(GameObject.Find("crosshairRED" + i.ToString() + this.name.ToString()));
        }

        for (int i = 1; i <= measShape2Points.Count; i++)
        {
            //cube
            Destroy(GameObject.Find("cubeBLUE" + i.ToString() + this.name.ToString()));
            //crosshair
            Destroy(GameObject.Find("crosshairBLUE" + i.ToString() + this.name.ToString()));
        }

        // Red-click point
        Destroy(GameObject.Find("cubeREDSPACE"));
        Destroy(GameObject.Find("cubeBLUESPACE"));

        //lines
        closeSegment1.positionCount = 0;
        openSegment1.positionCount = 0;
        closeSegment2.positionCount = 0;
        openSegment2.positionCount = 0;
    }

    #endregion

    #region Load/Export Model

    /// <summary>Load model (with measurement)</summary>
    public void LoadModel()
    {
        // source: https://github.com/SrejonKhan/AnotherFileBrowser

        #if UNITY_STANDALONE_WIN
        var bp = new BrowserProperties();
        bp.filter = "xml files (*.xml)|*.xml";
        bp.filterIndex = 0;
        new FileBrowser().OpenFileBrowser(bp, tempPath =>
        {
            pathMeas = tempPath;
        });
        #endif

        if (pathMeas == null)
            return;
        else
            Measure_Load(pathMeas);

    }

    /// <summary>Export model (with measurement)</summary>
    public void ExportModel()
    {
        #if UNITY_STANDALONE_WIN

        var bp = new BrowserProperties();
        bp.filter = "xml files (*.xml)|*.xml";
        bp.filterIndex = 0;

        new FileBrowser().SaveFileBrowser(bp, "FileName", ".xml", tempPath =>
        {
            path = tempPath;
        });
        #endif

        if (path != null)
            Measure_Export(path);
        else
            return;
    }
    /// <summary>Load .3dmod model path </summary>
    private void Get3dmodPath()
    {
        // source: https://github.com/SrejonKhan/AnotherFileBrowser

    #if UNITY_STANDALONE_WIN
        var bp = new BrowserProperties();
        bp.filter = "3dmod files (*.3dmod)|*.3dmod";
        bp.filterIndex = 0;
        new FileBrowser().OpenFileBrowser(bp, tempPath =>
        {
            path = tempPath;
        });
        #endif

        if (path == null)
            return;
    }
    #endregion

    /// <summary>Create Line between points</summary>
    public void CreateLines()
    {
        bool changedShape = false;

        if (_shape != MeasShape)
        {
            changedShape = true;
            _shape = MeasShape;
        }

        DeleteLanes(changedShape);
        Measure_Recompute();

        //delete previous meshes of shapes
        cutInterShape1.GetComponent<MeshFilter>().mesh = null;
        cutInterShape2.GetComponent<MeshFilter>().mesh = null;

        switch (MeasShape)
        {
            case "SNAPPED_SHAPE_SURFACE":        //SNAPPED_SHAPE_SURFACE
                SnappedShapeLines(changedShape);
                changedShape = false;
                break;

            case "DIRECT_SHAPES_ONLY":           //DIRECT_SHAPES_ONLY
                DirectShapeLines(changedShape);
                changedShape = false;
                break;

            case "SNAPPED_SHAPE_VOLUME":         //SNAPPED_SHAPE_VOLUME
                SnappedShapeLines(changedShape);
                DrawCutInterSurface(cuttingSurface1, Color.red);
                DrawCutInterSurface(cuttingSurface2, Color.blue);
                changedShape = false;

                break;

            case "INTERSHAPE_VOLUME":            //INTERSHAPE_VOLUME
                SnappedShapeLines(changedShape);
                DrawCutInterSurface(intershape, Color.green);
                changedShape = false;
                break;

            case "SELECTED_POINTS_ONLY":         //SELECTED_POINTS_ONLY
                changedShape = false;
                break;

            default:                             //SNAPPED_SHAPE_SURFACE
                SnappedShapeLines(changedShape);
                changedShape = false;
                break;
        }
    }

    /// <summary>Reset All cubes or crosshairs when switched</summary>
    public void PointReset()
    {
        if (!crosshairPoints.isOn)
        {
            // replace crosshairs with cube
            for (int i = 0; i < measShape1Points.Count; i++)
            {
                Destroy(GameObject.Find("crosshairRED" + (i+1).ToString()));

                GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
                cube.transform.parent = this.transform;

                cube.transform.position = points[measShape1Points[i]].Vertex.Position;
                cube.transform.localScale = new Vector3(2, 2, 2);
                cube.name = "cubeRED" + (i+1).ToString() + this.name.ToString();
                cube.GetComponent<Renderer>().material.color = Color.red;
                Destroy(cube.GetComponent<BoxCollider>());
            }

            for (int j = 0; j < measShape2Points.Count; j++)
            {
                Destroy(GameObject.Find("crosshairBLUE" + (j+1).ToString()));

                GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
                cube.transform.parent = this.transform;

                cube.transform.position = points[measShape2Points[j]].Vertex.Position;
                cube.transform.localScale = new Vector3(2, 2, 2);
                cube.name = "cubeBLUE" + (j+1).ToString() + this.name.ToString();
                cube.GetComponent<Renderer>().material.color = Color.blue;
                Destroy(cube.GetComponent<BoxCollider>());
            }
        }
        else
        {
            // delete cubes if already exists
            for (int i = 0; i < measShape1Points.Count; i++)
            {
                Destroy(GameObject.Find("cubeRED" + (i+1).ToString() + this.name.ToString()));

                GameObject crosshair = new GameObject();
                GameObject lineX = new GameObject();
                GameObject lineY = new GameObject();
                GameObject lineZ = new GameObject();

                crosshair.transform.parent = this.transform;
                lineX.transform.parent = crosshair.transform;
                lineY.transform.parent = crosshair.transform;
                lineZ.transform.parent = crosshair.transform;
                lineX.AddComponent<LineRenderer>();
                lineY.AddComponent<LineRenderer>();
                lineZ.AddComponent<LineRenderer>();

                crosshair.transform.position = points[measShape1Points[i]].Vertex.Position;
                Vector3 crossPoint = crosshair.transform.position;
                crosshair.name = "crosshairRED" + (i+1).ToString();
                DrawCrosshair(crossPoint, lineX, lineY, lineZ, new Color(0.4716981f, 0, 0));
            }

            for (int j = 0; j < measShape2Points.Count; j++)
            {
                Destroy(GameObject.Find("cubeBLUE" + (j+1).ToString() + this.name.ToString()));

                GameObject crosshair = new GameObject();
                GameObject lineX = new GameObject();
                GameObject lineY = new GameObject();
                GameObject lineZ = new GameObject();

                crosshair.transform.parent = this.transform;
                lineX.transform.parent = crosshair.transform;
                lineY.transform.parent = crosshair.transform;
                lineZ.transform.parent = crosshair.transform;
                lineX.AddComponent<LineRenderer>();
                lineY.AddComponent<LineRenderer>();
                lineZ.AddComponent<LineRenderer>();

                crosshair.transform.position = points[measShape2Points[j]].Vertex.Position;
                Vector3 crossPoint = crosshair.transform.position;
                crosshair.name = "crosshairBLUE" + (j+1).ToString();
                DrawCrosshair(crossPoint, lineX, lineY, lineZ, new Color(0, 0, 0.4716981f));
            }
        }
    }

    /// <summary>Reset All cubes in alignment</summary>
    public void PointReset2()
    {
            // reset cubes
            for (int i = 0; i < measShape1Points.Count; i++)
            {
            Destroy(GameObject.Find("cubeRED" + (i + 1).ToString() + this.name.ToString()));

                GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
                cube.transform.parent = this.transform;

                cube.transform.position = vertices[measShape1Points[i]];
                cube.transform.localScale = new Vector3(2, 2, 2);
                cube.name = "cubeRED" + (i + 1).ToString() + this.name.ToString();
                cube.GetComponent<Renderer>().material.color = Color.red;
                Destroy(cube.GetComponent<BoxCollider>());
            }


            for (int j = 0; j < measShape2Points.Count; j++)
            {
            Destroy(GameObject.Find("cubeBLUE" + (j + 1).ToString() + this.name.ToString()));

                GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
                cube.transform.parent = this.transform;

                cube.transform.position = vertices[measShape2Points[j]];
                cube.transform.localScale = new Vector3(2, 2, 2);
                cube.name = "cubeBLUE" + (j + 1).ToString() + this.name.ToString();
                cube.GetComponent<Renderer>().material.color = Color.blue;
                Destroy(cube.GetComponent<BoxCollider>());
            }
    }


    /// <summary>Compute all measurements</summary>
    public void Measure_Recompute()
    {       
        if (!(manualRecompute.isOn || wasClicked)) // block the measurement 
            return;

        if (neighbourPointTable.Count <= 0)
            ComputeCommonPrerequisites(); //if not yet done, prepare table of neigbours atc.

        wasClicked = false;

        // Boundary length (without closing segment)
        string segments1 = "";
        float boundOpen1 = PolygonLength(measShape1Points, ref segments1);
        string segments2 = "";
        float boundOpen2 = PolygonLength(measShape2Points, ref segments2);

        // Boundary length (with closing segment)
        float boundClose1 = 0;
        if (measShape1Points.Count > 1)
        {
            float closeSeg1 = GetDistance(measShape1Points[measShape1Points.Count - 1], measShape1Points[0]);
            boundClose1 = boundOpen1 + closeSeg1;
            if (closeSeg1 > 0)
                segments1 += "(" + closeSeg1.ToString("N2") + " " + UnitName + " / " + GetAngle(measShape1Points[measShape1Points.Count - 1], measShape1Points[0], measShape1Points[1]).ToString("N2") + "°)";
        }
        float boundClose2 = 0;
        if (measShape2Points.Count > 1)
        {
            float closeSeg2 = GetDistance(measShape2Points[measShape2Points.Count - 1], measShape2Points[0]);
            boundClose2 = boundOpen2 + closeSeg2;
            if (closeSeg2 > 0)
                segments2 += "(" + closeSeg2.ToString("N2") + " " + UnitName + " / " + GetAngle(measShape2Points[measShape2Points.Count - 1], measShape2Points[0], measShape2Points[1]).ToString("N2") + "°)";
        }

        // Snapped boundary length (without closing segment)
        float snapOpen1 = PolygonLength(measShape1Poly);
        float snapOpen2 = PolygonLength(measShape2Poly);

        // snapped boundary length (with closing segment)
        float snapCloseSeg1 = PolygonLength(closingSegment1);
        float snapCloseSeg2 = PolygonLength(closingSegment2);
        float snapClose1 = snapOpen1 + snapCloseSeg1;
        float snapClose2 = snapOpen2 + snapCloseSeg2;

        // Length of each snapped boundary segment
        string snapSegments1 = "";
        int pPoint = 0;
        for (int i = 0; i < segmentLength1.Count; i++)
        {
            snapSegments1 += PolygonLength(measShape1Poly.GetRange(pPoint, segmentLength1[i])).ToString("N2") + " " + UnitName + Environment.NewLine;
            pPoint += segmentLength1[i];
        }
        if (snapCloseSeg1 > 0)
            snapSegments1 += "(" + snapCloseSeg1.ToString("N2") + " " + UnitName + ")";
        string snapSegments2 = "";
        pPoint = 0;
        for (int i = 0; i < segmentLength2.Count; i++)
        {
            snapSegments2 += PolygonLength(measShape2Poly.GetRange(pPoint, segmentLength2[i])).ToString("N2") + " " + UnitName + Environment.NewLine;
            pPoint += segmentLength2[i];
        }
        if (snapCloseSeg2 > 0)
            snapSegments2 += "(" + snapCloseSeg2.ToString("N2") + " " + UnitName + ")";

        // Snapped surface area and volume
        float shapeArea1;
        float shapeVolume1;
        float shapeTemperature1;
        List<int> boundary1 = new List<int>(measShape1Poly);
        boundary1.AddRange(closingSegment1); //total boundary
        innerPoints1 = FillShape(boundary1, selectionPoint1); //fill inner points
        cuttingSurface1 = SurfaceTriangulation(boundary1, segmentLength1, closingSegment1.Count, measShape1Points, selectionPoint1); // compute cutting surface

        if (selectionPoint1 != -1)
        {
            shapeArea1 = ComputeShapeArea(innerPoints1, boundary1);
            shapeVolume1 = ComputeShapeVolume(innerPoints1, cuttingSurface1, boundary1);
            shapeTemperature1 = ComputeShapeTemperature(innerPoints1);
        }
        else
        {
            shapeArea1 = 0;
            shapeVolume1 = 0;
            shapeTemperature1 = float.NaN;
        }

        float shapeArea2;
        float shapeVolume2;
        float shapeTemperature2;
        List<int> boundary2 = new List<int>(measShape2Poly);
        boundary2.AddRange(closingSegment2); //total boundary
        innerPoints2 = FillShape(boundary2, selectionPoint2); //fill inner points
        cuttingSurface2 = SurfaceTriangulation(boundary2, segmentLength2, closingSegment2.Count, measShape2Points, selectionPoint2); // compute cutting surface

        if (selectionPoint2 != -1)
        {
            shapeArea2 = ComputeShapeArea(innerPoints2, boundary2);
            shapeVolume2 = ComputeShapeVolume(innerPoints2, cuttingSurface2, boundary2);
            shapeTemperature2 = ComputeShapeTemperature(innerPoints2);
        }
        else
        {
            shapeArea2 = 0;
            shapeVolume2 = 0;
            shapeTemperature2 = float.NaN;
        }

        Measure_UpdateModel(); //highlight shapes

        // Intershape volume
        float intershapeVolume;
        if ((selectionPoint2 != -1) && (selectionPoint2 != -1))
        {
            intershape = IntershapeTriangulation(boundary1, boundary2);
            intershapeVolume = ComputeIntershapeVolume(innerPoints1, innerPoints2, intershape, boundary1, boundary2);
        }
        else
            intershapeVolume = 0;

        //Single point information
        string rightPoint1Text = "";
        string rightPoint2Text = "";

        if (!AvailableLayers.HasFlag(ModelLayers.DISTANCE))
        {
            if (selectionPoint1 >= 0) //is clicked
                rightPoint1Text +=
                    "X: " + points[selectionPoint1].Vertex.Position.x.ToString("N3") + Environment.NewLine +
                    "Y: " + points[selectionPoint1].Vertex.Position.y.ToString("N3") + Environment.NewLine +
                    "Z: " + points[selectionPoint1].Vertex.Position.z.ToString("N3") + Environment.NewLine +
                    "Color: " + points[selectionPoint1].Color.ToString() + Environment.NewLine +
                    (AvailableLayers.HasFlag(RoscanModelUnity.ModelLayers.REFLECTIVITY) ? "Reflectivity: " + points[selectionPoint1].Reflectivity + Environment.NewLine : "") +
                    (AvailableLayers.HasFlag(RoscanModelUnity.ModelLayers.REFL_WIDTH) ? "Refl. Width: " + points[selectionPoint1].Reflectivity + Environment.NewLine : "") +
                    (AvailableLayers.HasFlag(RoscanModelUnity.ModelLayers.TEMPERATURE) ? "Temperature: " + (points[selectionPoint1].Temperature) + " °C" + Environment.NewLine : "") +
                    (AvailableLayers.HasFlag(RoscanModelUnity.ModelLayers.COLOR) ? "Orig. Color: " + points[selectionPoint1].Color + Environment.NewLine : "") +
                    "";

            if (selectionPoint2 >= 0) //is clicked
                rightPoint2Text +=
                    "X: " + points[selectionPoint2].Vertex.Position.x.ToString("N3") + Environment.NewLine +
                    "Y: " + points[selectionPoint2].Vertex.Position.y.ToString("N3") + Environment.NewLine +
                    "Z: " + points[selectionPoint2].Vertex.Position.z.ToString("N3") + Environment.NewLine +
                    "Color: " + points[selectionPoint2].Color.ToString() + Environment.NewLine +
                    (AvailableLayers.HasFlag(RoscanModelUnity.ModelLayers.REFLECTIVITY) ? "Reflectivity: " + points[selectionPoint2].Reflectivity + Environment.NewLine : "") +
                    (AvailableLayers.HasFlag(RoscanModelUnity.ModelLayers.REFL_WIDTH) ? "Refl. Width: " + points[selectionPoint2].Reflectivity + Environment.NewLine : "") +
                    (AvailableLayers.HasFlag(RoscanModelUnity.ModelLayers.TEMPERATURE) ? "Temperature: " + (points[selectionPoint2].Temperature) + " °C" + Environment.NewLine : "") +
                    (AvailableLayers.HasFlag(RoscanModelUnity.ModelLayers.COLOR) ? "Orig. Color: " + points[selectionPoint2].Color + Environment.NewLine : "") +
                    "";
        }
        else
        {
            if (selectionPoint1 >= 0) //is clicked
                rightPoint1Text +=
                    "X: " + points[selectionPoint1].Vertex.Position.x.ToString("N3") + Environment.NewLine +
                    "Y: " + points[selectionPoint1].Vertex.Position.y.ToString("N3") + Environment.NewLine +
                    "Z: " + points[selectionPoint1].Vertex.Position.z.ToString("N3") + Environment.NewLine +
                    (AvailableLayers.HasFlag(RoscanModelUnity.ModelLayers.REFLECTIVITY) ? "Refl. Diff.: " + points[selectionPoint1].Reflectivity + Environment.NewLine : "") +
                    (AvailableLayers.HasFlag(RoscanModelUnity.ModelLayers.REFL_WIDTH) ? "Refl. W. Diff.: " + points[selectionPoint1].Reflectivity + Environment.NewLine : "") +
                    (AvailableLayers.HasFlag(RoscanModelUnity.ModelLayers.TEMPERATURE) ? "Temp. DIff.: " + (points[selectionPoint1].Temperature) + " °C" + Environment.NewLine : "") +
                    (AvailableLayers.HasFlag(RoscanModelUnity.ModelLayers.DISTANCE) ? "Distance: " + points[selectionPoint1].Distance + Environment.NewLine : "") +
                    "";

            if (selectionPoint2 >= 0) //is clicked
                rightPoint2Text +=
                    "X: " + points[selectionPoint2].Vertex.Position.x.ToString("N3") + Environment.NewLine +
                    "Y: " + points[selectionPoint2].Vertex.Position.y.ToString("N3") + Environment.NewLine +
                    "Z: " + points[selectionPoint2].Vertex.Position.z.ToString("N3") + Environment.NewLine +
                    (AvailableLayers.HasFlag(RoscanModelUnity.ModelLayers.REFLECTIVITY) ? "Refl. Diff.: " + points[selectionPoint2].Reflectivity + Environment.NewLine : "") +
                    (AvailableLayers.HasFlag(RoscanModelUnity.ModelLayers.REFL_WIDTH) ? "Refl. W. Diff.: " + points[selectionPoint2].Reflectivity + Environment.NewLine : "") +
                    (AvailableLayers.HasFlag(RoscanModelUnity.ModelLayers.TEMPERATURE) ? "Temp. DIff.: " + (points[selectionPoint2].Temperature) + " °C" + Environment.NewLine : "") +
                    (AvailableLayers.HasFlag(RoscanModelUnity.ModelLayers.DISTANCE) ? "Distance: " + points[selectionPoint2].Distance + Environment.NewLine : "") +
                    "";
        }

        // Composing content of result table

        MeasurementResults.Clear();
        //SURFACE
        MeasurementResults.Add( shapeArea1.ToString("N2") + " " + UnitName + "2" ); //snapped shape area 1
        MeasurementResults.Add( shapeArea2.ToString("N2") + " " + UnitName + "2" ); //snapped shape area 2
        MeasurementResults.Add( (entireModelArea / (((float)Unit) * ((float)Unit))).ToString("N2") + " " + UnitName + "2" ); //Entire model surface area
   
        if(float.IsNaN(shapeTemperature1))
        {
            MeasurementResults.Add("NaN °C"); //Average temperature 1
        }
        else 
        {
            MeasurementResults.Add(shapeTemperature1 + " °C"); //Average temperature 1
        }

        if (float.IsNaN(shapeTemperature2))
        {
            MeasurementResults.Add("NaN °C"); //Average temperature 2
        }
        else
        {
            MeasurementResults.Add(shapeTemperature2 + " °C"); //Average temperature 2
        }

        //VOLUME
        MeasurementResults.Add( shapeVolume1.ToString("N2") + " " + UnitName + "3" ); // Highlighted shape volume 1
        MeasurementResults.Add( shapeVolume2.ToString("N2") + " " + UnitName + "3" ); // Highlighted shape volume 2
        MeasurementResults.Add( intershapeVolume.ToString("N2") + " " + UnitName + "3" ); //Inter-shape volume
        //SINGLE POINT
        MeasurementResults.Add( rightPoint1Text ); // Selection point (right-clicked) 1
        MeasurementResults.Add( rightPoint2Text ); // Selection point (right-clicked) 2
        //DEFINED SHAPE
        MeasurementResults.Add( measShape1Points.Count.ToString() ); //Number of boundary points 1
        MeasurementResults.Add( measShape2Points.Count.ToString() ); //Number of boundary points 2
        MeasurementResults.Add( boundOpen1.ToString("N2") + " " + UnitName ); //Boundary length(without closing segment) 1
        MeasurementResults.Add( boundOpen2.ToString("N2") + " " + UnitName ); //Boundary length (without closing segment) 2
        MeasurementResults.Add( boundClose1.ToString("N2") + " " + UnitName ); //Boundary length (with closing segment) 1
        MeasurementResults.Add( boundClose2.ToString("N2") + " " + UnitName ); //Boundary length (with closing segment) 2
        MeasurementResults.Add( segments1 ); // Length and ending angle of each boundary segment 1
        MeasurementResults.Add( segments2 );// Length and ending angle of each boundary segment 2
        //SNAPPED SHAPE
        MeasurementResults.Add( measShape1Poly.Count.ToString() ); // Number of snapped points 1
        MeasurementResults.Add( measShape2Poly.Count.ToString() ); // Number of snapped points 2
        MeasurementResults.Add( snapOpen1.ToString("N2") + " " + UnitName ); // Boundary length (without closing segment) 1
        MeasurementResults.Add( snapOpen2.ToString("N2") + " " + UnitName ); // Boundary length (without closing segment) 2
        MeasurementResults.Add( snapClose1.ToString("N2") + " " + UnitName ); // Boundary length (with closing segment) 1
        MeasurementResults.Add( snapClose2.ToString("N2") + " " + UnitName ); // Boundary length (with closing segment) 2
        MeasurementResults.Add( snapSegments1 ); // Length of each boundary segment 1
        MeasurementResults.Add( snapSegments2 ); // Length of each boundary segment 2

        results.MeasInfo();
    }
    
    /// <summary>Recolor measuring shapes in model according to actual value of MeasurementViewMode</summary>
    public void Measure_UpdateModel()
    {
        if ((MeasShape == "SNAPPED_SHAPE_SURFACE") || (MeasShape == "SNAPPED_SHAPE_VOLUME") || (MeasShape == "INTERSHAPE_VOLUME"))
        {
            SetColorPoints(innerPoints1, Color.red*255); //highlight shapes
            SetColorPoints(innerPoints2, Color.blue*255); //highlight shapes

            MeshUpdate();
        }
        else
        {
            ResetColorPoints(innerPoints1); //unhiglight measuring shapes
            ResetColorPoints(innerPoints2);
        }
    }

    #endregion

    #endregion
}
