using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Runtime.Serialization;

namespace CSGBuilder {

    // A test egy lapját reprezentáló osztály
    [Serializable()]
    public class Face : ISerializable {
        // A lapot meghatározó csúcsok indexei
        int[] vertices;
        Color color;

        public static Color defaultColor = Color.White;

        public Face(int v1, int v2, int v3) {
            vertices = new int[3];
            vertices[0] = v1;
            vertices[1] = v2;
            vertices[2] = v3;
            color = defaultColor;
        }

        public int this[int i] {
            get { return vertices[i]; }
            set { vertices[i] = value; }
        }

        public Color Color {
            get { return color; }
            set { color = value; }
        }

        public override string ToString() {
            return "[" + vertices[0] + ", " + vertices[1] + ", " + vertices[2] + "]";
        }

        public Face Clone() {
            Face f = new Face(vertices[0], vertices[1], vertices[2]);
            f.Color = this.Color;
            return f;
        }

        #region ISerializable Members

        public void GetObjectData(SerializationInfo info, StreamingContext context) {
            info.AddValue("Vertices", vertices);
            info.AddValue("Color", color);
        }

        public Face(SerializationInfo info, StreamingContext context) {
            vertices = (int[])info.GetValue("Vertices", typeof(int[]));
            color = (Color)info.GetValue("Color", typeof(Color));
        }

        #endregion
    }

    // A test egy élét reprezentáló osztály
    [Serializable()]
    public class Edge : ISerializable {
        // Az él kezdő- és végpontja
        int sp, ep;
        Color color;

        public Edge(int v1, int v2) {
            sp = v1;
            ep = v2;
            color = Color.Empty;
        }

        #region Public properties
        public int Sp {
            get { return sp; }
            set { sp = value; }
        }

        public int Ep {
            get { return ep; }
            set { ep = value; }
        }

        public Color Color {
            get { return color; }
            set { color = value; }
        }
        #endregion

        public override string ToString() {
            return "[" + sp + ", " + ep + "]";
        }

        public Edge Clone() {
            Edge e = new Edge(sp, ep); e.Color = color;
            return e;
        }

        #region ISerializable Members

        public void GetObjectData(SerializationInfo info, StreamingContext context) {
            info.AddValue("Sp", sp);
            info.AddValue("Ep", ep);
            info.AddValue("Color", color);
        }

        public Edge(SerializationInfo info, StreamingContext context) {
            sp = info.GetInt32("Sp");
            ep = info.GetInt32("Ep");
            color = (Color)info.GetValue("Color", typeof(Color));
        }

        #endregion
    }

    // A testet leíró BRep adatszerkezetet megvalósító osztály
    [Serializable()]
    public class BRep : ISerializable {
        List<Point3D> vertices;    // 3D-s pontok listája
        List<Point> v2D;		    // 2D-s leképezett pontok listája
        List<Edge> edges;           // Élek listája
        List<Face> faces;		    // Lapok listája
        List<Face> visibleFaces;    // Látható lapok
        List<double> weights;		// Lapok súlypontjai
        List<double> lights;		// A lapok színei

        #region Public properties
        public List<Point3D> Vertices {
            get { return vertices; }
            set { vertices = value; }
        }

        public List<Point> V2D {
            get { return v2D; }
            set { v2D = value; }
        }

        public List<Edge> Edges {
            get { return edges; }
            set { edges = value; }
        }

        public List<Face> Faces {
            get { return faces; }
            set { faces = value; }
        }

        public List<Face> VisibleFaces {
            get { return visibleFaces; }
            set { visibleFaces = value; }
        }

        public List<double> Lights {
            get { return lights; }
        }
        #endregion

        //public double rc = 1.0, gc = 1.0, bc = 1.0;   // A színösszetevők súlyozása árnyaláshoz

        #region ISerializable Members

        public void GetObjectData(SerializationInfo info, StreamingContext context) {
            info.AddValue("Vertices", vertices);
            info.AddValue("Edges", edges);
            info.AddValue("Faces", faces);
        }

        public BRep(SerializationInfo info, StreamingContext context) {
            vertices = (List<Point3D>)info.GetValue("Vertices", typeof(List<Point3D>));
            edges = (List<Edge>)info.GetValue("Edges", typeof(List<Edge>));
            faces = (List<Face>)info.GetValue("Faces", typeof(List<Face>));
        }

        #endregion

        public BRep() {
            vertices = new List<Point3D>();
            edges = new List<Edge>();
            faces = new List<Face>();
            v2D = new List<Point>();
            visibleFaces = new List<Face>();
            weights = new List<double>();
            lights = new List<double>();
        }

        //public void SetLightWeights(double prc, double pgc, double pbc) { rc = prc; gc = pgc; bc = pbc; }

        // Törli a teljes adatstruktúrát
        public void Clear() {
            vertices.Clear();
            faces.Clear();
            visibleFaces.Clear();
            v2D.Clear();
            weights.Clear();
        }

        // A megvilágítás számítása konstans árnyaláshoz
        public void CalcLights(Point3D lamp) {
            for (int i = 0; i < visibleFaces.Count; i++) {
                Face f = (Face)visibleFaces[i];
                Point3D cp = (vertices[f[0]] + vertices[f[1]] + vertices[f[2]]) / 3;
                Point3D n = (vertices[f[1]] - vertices[f[0]]) % (vertices[f[2]] - vertices[f[0]]);
                Point3D v = lamp - cp;
                double d = (v * n) / (v.Norm() * n.Norm());
                if (d < 0) d = 0;
                lights.Add(d);
            }

        }

        // Csúcspontbeli fényintenzitások számítása Gouraud-árnyaláshoz
        public void CalcVectors2(Point3D lamp) {
            lights.Clear();
            for (int i = 0; i < vertices.Count; i++) {
                double m = 0.0;
                int k = 0;
                for (int j = 0; j < faces.Count; j++) {
                    if (faces[j][0] == i || faces[j][1] == i || faces[j][2] == i) {
                        Point3D n = (vertices[faces[j][1]] - vertices[faces[j][0]]) % (vertices[faces[j][2]] - vertices[faces[j][0]]);
                        Point3D cp = (vertices[faces[j][0]] + vertices[faces[j][1]] + vertices[faces[j][2]]) / 3;
                        Point3D v = lamp - cp;
                        double d = (v * n) / (v.Norm() * n.Norm());
                        if (d < 0.0) d = 0.0; 
                        m += d; k++;
                    }
                }
                if (k > 0) {
                    m /= k;
                    lights.Add(m);
                } else {
                    lights.Add(0.0);
                }
            }
        }

        public bool contains(List<Point3D> l, Point3D p) {
            double EPS = 0.00001;
            foreach (Point3D p0 in l)
                if (Math.Abs(p.X - p0.X) < EPS && Math.Abs(p.Y - p0.Y) < EPS && Math.Abs(p.Z - p0.Z) < EPS) return true;
            return false;
        }

        public void CalcVectors(Point3D lamp) {
            lights.Clear();            
            for (int i = 0; i < vertices.Count; i++)
                lights.Add(-1.0);
            for (int k = 0; k < visibleFaces.Count; k++) {
                for (int l = 0; l < 3; l++) {
                    int i = visibleFaces[k][l];
                    if (lights[i] != -1.0) continue;
                    List<Point3D> f = new List<Point3D>();
                    // A kapcsolódó lapok normálvektorait összegyűjti egy listába
                    for (int j = 0; j < faces.Count; j++) {
                        if (faces[j][0] == i || faces[j][1] == i || faces[j][2] == i) {
                            Point3D v0 = (vertices[faces[j][1]] - vertices[faces[j][0]]);
                            Point3D v1 = (vertices[faces[j][2]] - vertices[faces[j][0]]);
                            //v0.Normal(); v1.Normal();
                            Point3D v = v0 % v1;
                            f.Add(v);
                        }
                    }
                    // majd ezen vektorok kiátlagolásával számolható a csúcspontbeli normális
                    if (f.Count > 0) {
                        Point3D n = f[0];
                        for (int j = 1; j < f.Count; j++) n += f[j];
                        n /= f.Count; 
                        // A fényforrás irányába mutató vektor v
                        Point3D p = vertices[i];
                        Point3D v = lamp - p; 
                        // A közbezárt szög koszinusza
                        double d = (v * n) / (v.Norm() * n.Norm());
                        if (d < 0.0) d = 0.0;
                        // Ez lesz az adott csúcspontbeli fényintenzitás
                        lights[i] = d;
                    } else lights[i] = 0.0;
                }
            }
        }

        // Backface culling - Hátsó (nem látható) lapok eltávolítása
        public void BFC(Point3D viewPoint) {
            Point3D vp = viewPoint.Clone(); vp.Normal();
            visibleFaces.Clear();
            weights.Clear();
            for (int i = 0; i < faces.Count; i++) {
                Face f = (Face)faces[i];
                Point3D cp = ((vertices[f[0]] + vertices[f[1]] + vertices[f[2]]) / 3);
                Point3D n = (vertices[f[1]] - vertices[f[0]]) % (vertices[f[2]] - vertices[f[0]]);
                double d = (double)((vp - cp) * n); // (double)((viewPoint - cp).Norm() * n.Norm());
                if (d >= 0.0) {
                    visibleFaces.Add(f);
                    weights.Add(System.Math.Sqrt(Math.Pow(vp.X - cp.X, 2) +
                        Math.Pow(vp.Y - cp.Y, 2) +
                        Math.Pow(vp.Z - cp.Z, 2)));
                }
            }

        }

        // Mélységi rendezés
        public void DepthSort() {
            for (int i = 0; i < visibleFaces.Count - 1; ++i) {
                int min = i;
                for (int j = i + 1; j < visibleFaces.Count; ++j)
                    if (weights[j] < weights[min]) min = j;
//                if (min != i) {
                    Face temp = visibleFaces[i];
                    visibleFaces[i] = visibleFaces[min];
                    visibleFaces[min] = temp;
                    double w = weights[i];
                    weights[i] = weights[min];
                    weights[min] = w;
//                }
            }

        }

        // Az összes pontot transzformálja a megadott mátrixszal
        public void Transform(Matrix tr) {
            for (int i = 0; i < vertices.Count; i++)
                vertices[i] = ((tr * vertices[i]).ToPoint3Dh());
        }

        // A 3d-s pontokat leképezi 2D-be, a megadott projekciós mátrixszal
        public void Projection(Matrix pr) {
            v2D.Clear();
            double x, y;
            for (int i = 0; i < vertices.Count; i++) {
                x = (vertices[i].X * pr[0, 0]);
                y = (vertices[i].Y * pr[1, 1]);
                v2D.Add(new Point((int)x, (int)y));
            }
        }

        // Kirajzolja a lapokat színesen
        public void DrawFaces(System.Windows.Forms.PaintEventArgs e, Color cframe, Color cfill, Point dp) {
            Point[] p = new Point[3];
            SolidBrush sb = new SolidBrush(cfill);
            Pen pen = new Pen(cframe);
            for (int i = visibleFaces.Count - 1; i >= 0; i--) {
                Face f = visibleFaces[i];
                p[0].X = v2D[f[0]].X + dp.X; p[0].Y = v2D[f[0]].Y + dp.Y;
                p[1].X = v2D[f[1]].X + dp.X; p[1].Y = v2D[f[1]].Y + dp.Y;
                p[2].X = v2D[f[2]].X + dp.X; p[2].Y = v2D[f[2]].Y + dp.Y;
                e.Graphics.FillPolygon(sb, p);
                e.Graphics.DrawPolygon(pen, p);
            }
        }

        // Kirajzolja a lapokat vonalakból
        public void DrawFaceWire(System.Windows.Forms.PaintEventArgs e, Color c, Point dp) {
            Pen p = new Pen(c);
            Point p1 = new Point();
            Point p2 = new Point();
            foreach (Edge ed in edges) {
                p1.X = v2D[ed.Sp].X + dp.X; p1.Y = v2D[ed.Sp].Y + dp.Y;
                p2.X = v2D[ed.Ep].X + dp.X; p2.Y = v2D[ed.Ep].Y + dp.Y;
                e.Graphics.DrawLine(p, p1, p2);
            }
        }

        // Kirajzolja a lapokat konstans árnyalással
        public void DrawFlatShaded(System.Windows.Forms.PaintEventArgs e, Point dp) {
            Point[] p = new Point[3];
            for (int i = visibleFaces.Count - 1; i >= 0; i--) {
                Face f = visibleFaces[i];
                p[0].X = v2D[f[0]].X + dp.X; p[0].Y = v2D[f[0]].Y + dp.Y;
                p[1].X = v2D[f[1]].X + dp.X; p[1].Y = v2D[f[1]].Y + dp.Y;
                p[2].X = v2D[f[2]].X + dp.X; p[2].Y = v2D[f[2]].Y + dp.Y;
                Color c = Color.FromArgb(
                    (int)(lights[i] * 255.0),
                    (int)(lights[i] * 255.0),
                    (int)(lights[i] * 255.0));
                e.Graphics.FillPolygon(new SolidBrush(c), p);
                e.Graphics.DrawPolygon(new Pen(c), p);
            }
        }

        // Kirajzolja a lapokat Gouraud-árnyalással
        public void DrawGouraudShaded(System.Windows.Forms.PaintEventArgs e, Point dp) {
            for (int i = visibleFaces.Count - 1; i >= 0; i--) {
                Face f = visibleFaces[i];
                Point[] p = new Point[3];
                p[0] = new Point(v2D[f[0]].X + dp.X, v2D[f[0]].Y + dp.Y);
                p[1] = new Point(v2D[f[1]].X + dp.X, v2D[f[1]].Y + dp.Y);
                p[2] = new Point(v2D[f[2]].X + dp.X, v2D[f[2]].Y + dp.Y);
                Color c = Color.FromArgb(
                    (int)(lights[i] * 255.0),
                    (int)(lights[i] * 255.0),
                    (int)(lights[i] * 255.0));
                e.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
                e.Graphics.FillPolygon(new System.Drawing.Drawing2D.LinearGradientBrush(p[1], p[2], c, Color.Blue), p);
                e.Graphics.DrawPolygon(new Pen(c), p);
            }
        }

        // Összeköti a pontokat a megadott sorrendben
        public void DrawWireFrame(System.Windows.Forms.PaintEventArgs e, Color c, Point dp, int[] order) {
            Pen p = new Pen(c);
            for (int i = 0; i < order.Length - 1; i++) {
                if (((order[i]) == -1) || ((order[i + 1]) == -1)) continue;
                e.Graphics.DrawLine(p,
                    v2D[order[i]].X + dp.X, v2D[order[i]].Y + dp.Y,
                    v2D[order[i + 1]].X + dp.X, v2D[order[i + 1]].Y + dp.Y);
            }
        }

        // Kirajzolja a pontokat
        public void DrawPoints(System.Windows.Forms.PaintEventArgs e, Color c, Point dp) {
            Pen p = new Pen(c);
            for (int i = 0; i < v2D.Count; i++) 
                e.Graphics.DrawEllipse(p, v2D[i].X + dp.X, v2D[i].Y + dp.Y, 2, 2);            
        }

    }
}
