/*
!=======================================================================
!
!  PROGRAM  PHASE-Viewer  (PHASE-Viewer 2014.01 ver.3.3.0)
!
!  Created on ----
!  AUTHOR(S): KOGA, Junichiro
!  File : CylinderCreator.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;

import javax.media.j3d.Appearance;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Material;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.vecmath.Color3f;
import javax.vecmath.Matrix4d;
import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;

import ciss.phase_viewer.acviewer.scenegraphelements.CylinderCreatorTG;

import com.sun.j3d.utils.geometry.Cone;
import com.sun.j3d.utils.geometry.Cylinder;

public class CylinderCreator {

    int edges;

    /**
     * edge(解像度)のデフォルト値(7)を設定.
     */
    public CylinderCreator() {
        edges = 7;
    }

    /**
     * 解像度を設定する.
     * 
     * @param e
     *            edgeの数.
     */

    public void setResolution(int e) {
        edges = e;
    }

    /**
     * 指定の向きの円柱を作成するメソッド. 矢印の場合こっちを呼ぶ. Appearance はデフォルトのものが採用される(青っぽい感じ)
     * 
     * @param b
     *            円柱の下(ないし上)の座標
     * @param a
     *            円柱の上(ないし下)の座標
     * @param radius
     *            円柱の半径
     * @param arrowHeadRadius
     *            矢じりの半径
     * @param arrowHeadHeight
     *            矢じりの高さ
     * @return 指定の向きの円柱
     */
    public BranchGroup create(Point3d b, Point3d a, double radius,
            float arrowHeadRadius, float arrowHeadHeight) {
        Appearance app = new Appearance();
        Material material = new Material();
        float[] color_wk = new float[3];
        color_wk[0] = 0.2f;
        color_wk[1] = 0.2f;
        color_wk[2] = 0.5f;
        material.setDiffuseColor(new Color3f(color_wk));
        material.setShininess(120.0f);
        app.setMaterial(material);

        return create(b, a, radius, arrowHeadRadius, arrowHeadHeight, app);
    }

    /**
     * 指定の向きの円柱を作成するメソッド. 矢印の場合こっちを呼ぶ.
     * 
     * @param b
     *            円柱の下(ないし上)の座標
     * @param a
     *            円柱の上(ないし下)の座標
     * @param radius
     *            円柱の半径
     * @param arrowHeadRadius
     *            矢じりの半径
     * @param arrowHeadHeight
     *            矢じりの高さ
     * @param cylApp
     *            円柱のAppearance.
     * @return 指定の向きの円柱
     */
    public BranchGroup create(Point3d b, Point3d a, double radius,
            float arrowHeadRadius, float arrowHeadHeight, Appearance cylApp) {

        Vector3d base = new Vector3d();
        base.x = b.x;
        base.y = b.y;
        base.z = b.z;
        Vector3d apex = new Vector3d();
        apex.x = a.x;
        apex.y = a.y;
        apex.z = a.z;

        // calculate center of object
        Vector3d center = new Vector3d();
        center.x = (apex.x - base.x) / 2.0 + base.x;
        center.y = (apex.y - base.y) / 2.0 + base.y;
        center.z = (apex.z - base.z) / 2.0 + base.z;

        // calculate height of object and unit vector along cylinder axis
        Vector3d unit = new Vector3d();
        unit.sub(apex, base); // unit = apex - base;
        double height = unit.length();
        unit.normalize();

        /*
         * A Java3D cylinder is created lying on the Y axis by default. The idea
         * here is to take the desired cylinder's orientation and perform a
         * tranformation on it to get it ONTO the Y axis. Then this
         * transformation matrix is inverted and used on a newly-instantiated
         * Java 3D cylinder.
         */

        // calculate vectors for rotation matrix
        // rotate object in any orientation, onto Y axis (exception handled
        // below)
        // (see page 418 of _Computer Graphics_ by Hearn and Baker)
        Vector3d uX = new Vector3d();
        Vector3d uY = new Vector3d();
        Vector3d uZ = new Vector3d();
        double magX;
        Transform3D rotateFix = new Transform3D();

        uY = new Vector3d(unit);
        uX.cross(unit, new Vector3d(0, 0, 1));
        magX = uX.length();
        // magX == 0 if object's axis is parallel to Z axis
        if (magX != 0) {
            uX.z = uX.z / magX;
            uX.x = uX.x / magX;
            uX.y = uX.y / magX;
            uZ.cross(uX, uY);
        } else {
            // formula doesn't work if object's axis is parallel to Z axis
            // so rotate object onto X axis first, then back to Y at end
            double magZ;
            // (switched z -> y, y -> x, x -> z from code above)
            uX = new Vector3d(unit);
            uZ.cross(unit, new Vector3d(0, 1, 0));
            magZ = uZ.length();
            uZ.x = uZ.x / magZ;
            uZ.y = uZ.y / magZ;
            uZ.z = uZ.z / magZ;
            uY.cross(uZ, uX);
            // rotate object 90 degrees CCW around Z axis--from X onto Y
            rotateFix.rotZ(-Math.PI / 2.0);
        }

        // create the rotation matrix
        Transform3D transMatrix = new Transform3D();
        Transform3D rotateMatrix = new Transform3D(new Matrix4d(uX.x, uX.y,
                uX.z, 0, uY.x, uY.y, uY.z, 0, uZ.x, uZ.y, uZ.z, 0, 0, 0, 0, 1));
        // invert the matrix; need to rotate it off of the Z axis
        try {
            rotateMatrix.invert();
        } catch (Exception exc) {
            exc.printStackTrace();
            return new BranchGroup();
        }
        // rotate the cylinder into correct orientation
        transMatrix.mul(rotateMatrix);
        transMatrix.mul(rotateFix);
        // translate the cylinder away
        transMatrix.setTranslation(center);
        // create the transform group
        TransformGroup tg = new TransformGroup(transMatrix);

        Cylinder cyl = new Cylinder((float) radius, (float) height,
                Cylinder.GENERATE_NORMALS, edges, 1, cylApp);

        CylinderCreatorTG.setAllowIntersect(cyl);

        if (arrowHeadHeight != 0.f && arrowHeadRadius != 0) {
            Cone cone = new Cone(arrowHeadRadius, arrowHeadHeight, cylApp);
            CylinderCreatorTG.setAllowIntersect(cone);
            Transform3D translateCone = new Transform3D();
            Vector3d vCone = new Vector3d();
            vCone.y = ciss.phase_viewer.common.VectorOperations
                    .norm(new double[] { b.x - a.x, b.y - a.y, b.z - a.z }) / 2.d;
            translateCone.set(vCone);
            TransformGroup tgCone = new TransformGroup(translateCone);
            tgCone.addChild(cone);
            tg.addChild(tgCone);
        }

        tg.addChild(cyl);
        BranchGroup cylBg = new BranchGroup();
        cylBg.addChild(tg);
        cylBg.setCapability(BranchGroup.ALLOW_DETACH);

        return cylBg;

    }

    /**
     * 指定の向きの円柱を作成するメソッド.
     * 
     * @param b
     *            円柱の下(ないし上)の座標
     * @param a
     *            円柱の上(ないし下)の座標
     * @param radius
     *            円柱の半径
     * @param cylApp
     *            円柱のAppearance.
     * @return 指定の向きの円柱
     */
    public BranchGroup create(Point3d b, Point3d a, double radius,
            Appearance cylApp) {
        return create(b, a, radius, 0.f, 0.f, cylApp);
    }

    /**
     * 指定の向きの円柱を作成するメソッド. Appearanceは青っぽいやつが使われる.
     * 
     * @param b
     *            円柱の下(ないし上)の座標
     * @param a
     *            円柱の上(ないし下)の座標
     * @param radius
     *            円柱の半径
     * @return 指定の向きの円柱
     */
    public BranchGroup create(Point3d b, Point3d a, double radius) {
        Appearance app = new Appearance();
        Material material = new Material();
        float[] color_wk = new float[3];
        color_wk[0] = 0.2f;
        color_wk[1] = 0.2f;
        color_wk[2] = 0.5f;
        material.setDiffuseColor(new Color3f(color_wk));
        material.setShininess(120.0f);
        app.setMaterial(material);

        return create(b, a, radius, 0.f, 0.f, app);
    }
}
