/*
!=======================================================================
!
!  PROGRAM  PHASE-Viewer  (PHASE-Viewer 2014.01 ver.3.3.0)
!
!  Created on 2006/07/08, 14:46
!  AUTHOR(S): KOGA, Junichiro
!  File : BoundPlane.java
!  
!  Contact address :  Phase System Consortium
!                     E-mail: phase_system@nims.go.jp URL https://azuma.nims.go.jp
!
!
!   Since 2002, this program set had been intensively developed as a part of the following 
!  national projects supported by the Ministry of Education, Culture, Sports, Science and
!  Technology (MEXT) of Japan; "Frontier Simulation Software for Industrial Science
!  (FSIS)" from 2002 to 2005, "Revolutionary Simulation Software (RSS21)" from 2006 to
!  2008. "Research and Development of Innovative Simulation Software (RISS)" from 2008
!  to 2013. These projects is lead by the Center for Research on Innovative Simulation 
!  Software (CISS), the Institute of Industrial Science (IIS), the University of Tokyo.
!   Since 2013, this program set has been further developed centering on PHASE System
!  Consortium. 
!   The activity of development of this program set has been supervised by Takahisa Ohno.
!
!=======================================================================
 */

package ciss.phase_viewer.acviewer.geom;

import java.util.Vector;

import javax.vecmath.Point3f;

import org.apache.log4j.Logger;

import ciss.phase_viewer.common.VectorOperations;

/**
 * 有限の面を表す. 有限の面には, 任意の数だけ頂点がある. また, 頂点がちゃんと並んでいればその配列から辺も作れる.
 * 
 * @author
 */
public class BoundPlane extends Plane {
    private Logger logger = Logger.getLogger(BoundPlane.class.getName());

    private Vector vertices = new Vector(); /* 頂点を格納するVector */

    float verySmall = 0.0001f;

    /**
     * デフォルト値で初期化. 原点: 0,0,0 法線ベクトル: 0,0,1
     */
    public BoundPlane() {
        super();
    }

    /**
     * 法線ベクトルを与えて初期化. 一応, 規格化されていることを想定.
     * 
     * @param normal
     *            法線ベクトル
     */
    public BoundPlane(Point3f normal) {
        super(normal);
    }

    /**
     * 法線ベクトルも原点も与えて初期化.
     * 
     * @param normal
     *            法線ベクトル
     * @param origin
     *            原点
     */
    public BoundPlane(Point3f normal, Point3f origin) {
        super(normal, origin);
    }

    /**
     * 頂点をセット(このメソッドでセットする場合, 頂点バッファーは初期化される!!) さらに, ソートも行う.
     * 
     * @param 頂点配列
     */
    public void setVertices(Point3f[] points) {
        vertices = new Vector();
        for (int i = 0; i < points.length; i++) {
            vertices.addElement(points[i]);
        }
        sortVertices();
    }

    /**
     * 頂点が三つ以上あるときは法線ベクトルを作成する. そうでなければ何もしない.
     */
    public void generateNormal() {
        if (vertices.size() < 3) {
            return;
        }
        Point3f p1 = (Point3f) vertices.get(0);
        Point3f p2 = (Point3f) vertices.get(1);
        Point3f p3 = (Point3f) vertices.get(2);
        Point3f ref1 = new Point3f();
        Point3f ref2 = new Point3f();
        ref1.add(p1);
        ref1.sub(p2);
        ref2.add(p3);
        ref2.sub(p2);
        float[] norm = VectorOperations.getNormalVector(new float[] { ref1.x,
                ref1.y, ref1.z }, new float[] { ref2.x, ref2.y, ref2.z });
        setNormalVector(new Point3f(norm));
    }

    /**
     * 「面の原点」の作成
     */
    public void generateOrigin() {
        if (vertices.size() == 0) {
            // logger.error("no vertices.");
            return;
        }
        setOrigin(new Point3f((Point3f) vertices.get(0)));
        //
        // Point3f sum = new Point3f();
        // for ( int i=0 ; i<vertices.size() ; i++ ) {
        // sum.x += ((Point3f) vertices.get(i)).x;
        // sum.y += ((Point3f) vertices.get(i)).y;
        // sum.z += ((Point3f) vertices.get(i)).z;
        // }
        // sum.x /= vertices.size();
        // sum.y /= vertices.size();
        // sum.z /= vertices.size();
        //
        // origin = new Point3f(sum);
    }

    /**
     * 頂点を足す. ただし重複して足すことはない.
     * 
     * @param vertex
     *            足したい頂点
     */
    public void addVertex(Point3f vertex) {
        for (int i = 0; i < vertices.size(); i++) {
            Point3f tmp = (Point3f) vertices.get(i);
            if (tmp.epsilonEquals(vertex, verySmall)) {
                return;
            }
        }
        vertices.addElement(vertex);
    }

    /**
     * 頂点を差し替える
     * 
     * @param vertex
     *            差し替える頂点
     * @param i
     *            差し替える場所
     */
    public void setVertexAt(Point3f vertex, int i) {
        vertices.setElementAt(vertex, i);
    }

    /**
     * 頂点を削除する
     * 
     * @param vertex
     *            削除したいvertex
     */
    public void removeVertex(Point3f vertex) {
        for (int i = 0; i < vertices.size(); i++) {
            Point3f p3f = (Point3f) vertices.get(i);
            if (p3f.epsilonEquals(vertex, verySmall)) {
                vertices.removeElementAt(i);
            }
        }
    }

    /**
     * 頂点を差し替える. 見つけられなかった場合, 最後尾に足す.
     * 
     * @param from
     *            "差し替え元"
     * @param to
     *            "差し替え先"
     */
    public void replaceVertex(Point3f from, Point3f to) {
        for (int i = 0; i < vertices.size(); i++) {
            Point3f p3f = (Point3f) vertices.get(i);
            if (p3f.epsilonEquals(from, verySmall)) {
                vertices.set(i, to);
                return;
            }
        }
        vertices.add(to);
    }

    /**
     * 頂点を削除する
     * 
     * @param i
     *            削除したいvertexのindex
     */
    public void removeVertex(int i) {
        vertices.remove(i);
    }

    /**
     * 頂点を取得.
     * 
     * @return 頂点の配列
     */
    public Point3f[] getVertices() {
        Point3f[] ret = new Point3f[vertices.size()];
        vertices.copyInto(ret);
        return ret;
    }

    /**
     * 頂点を, "カノニカルな順序"にならびかえる. 辺を取得する前に一度は呼ぶこと. カノニカルな順序とは, 時計回りないし反時計周り.
     */
    public void sortVertices() {
        if (vertices == null || vertices.size() <= 2) {
            return;
        }
        vertices.trimToSize();
        Point3f[] verts = new Point3f[vertices.size()];
        vertices.copyInto(verts);
        Point3f[] ordered = Sorter.radiallyClockWise(verts);
        if (ordered != null && ordered.length >= 2) {
            for (int i = 0; i < ordered.length; i++) {
                vertices.set(i, ordered[i]);
            }
        } else {
            logger.error("invalid vertices!!");
        }
    }

    /**
     * 指定のSideを持っているか否か判定
     * 
     * @param side
     *            テストしたい辺
     * @return sideを持っている場合true
     */
    public boolean hasSide(Side side) {
        Point3f sp1 = side.getPoint1();
        Point3f sp2 = side.getPoint2();
        for (int i = 0; i < vertices.size() - 1; i++) {
            for (int j = i + 1; j < vertices.size(); j++) {
                Point3f lp1 = (Point3f) vertices.get(i);
                Point3f lp2 = (Point3f) vertices.get(j);

                if ((lp1.epsilonEquals(sp1, verySmall) && lp2.epsilonEquals(
                        sp2, verySmall))
                        || lp2.epsilonEquals(sp1, verySmall)
                        && lp1.epsilonEquals(sp2, verySmall)) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * この面が持っている"辺"の配列を取得するメソッド. このメソッドを呼ぶ前に, 必ずsortVertices()を呼ぶこと. また,
     * 頂点の数が0か1の場合はnullを返す.
     * 
     * @return この面が持っている"辺"の配列
     */
    public Side[] getSides() {
        if (vertices == null || vertices.size() < 2) {
            return null;
        }
        Vector sides = new Vector();
        for (int i = 0; i < vertices.size(); i++) {
            int i2 = i + 1;
            if (i2 == vertices.size()) {
                i2 = 0;
            }
            Point3f p3f1 = (Point3f) vertices.get(i);
            Point3f p3f2 = (Point3f) vertices.get(i2);
            Side side = new Side(p3f1, p3f2);
            sides.addElement(side);
        }
        Side[] ret = new Side[sides.size()];
        sides.copyInto(ret);

        return ret;
    }

    /**
     * 指定のバーテックスがこの面に存在するかどうかを判定
     * 
     * @param v
     *            テストしたいバーテックス
     * @return 存在するならtrue
     */
    public boolean hasVertex(Point3f v) {
        for (int i = 0; i < vertices.size(); i++) {
            Point3f p = (Point3f) vertices.get(i);
            if (p.epsilonEquals(v, verySmall)) {
                return true;
            }
        }
        return false;
    }

    public String toString() {
        String ret = "";
        ret += "vertices: ";
        for (int i = 0; i < vertices.size(); i++) {
            ret += System.getProperty("line.separator")
                    + vertices.get(i).toString();
        }
        return ret;
    }

}
