package PCG;

import com.sun.j3d.utils.geometry.*;
import javax.media.j3d.*;
import javax.vecmath.*;

/**
 * Title:        Implementierung eines Tetraeders für Java3D
 * Description:
 * Copyright:    Copyright (c) 2001
 * Company:
 * @author Fabian Wleklinski (Fabian@Wleklinski.de)
 * @version 1.0
 */

public class Tetrahedron extends Primitive {

        /**
         * Used to designate the left side of the tetrahedron when using
         * getShape().
         *
         * @see Tetrahedron#getShape
         */
        public static final int LEFT = 0;

        /**
         * Used to designate the right side of the tetrahedron when using
         * getShape().
         *
         * @see Tetrahedron#getShape
         */
        public static final int RIGHT = 1;

        /**
         * Used to designate the backward side of the tetrahedron when using
         * getShape().
         *
         * @see Tetrahedron#getShape
         */
        public static final int BACK = 2;

        /**
         * Used to designate the bottom side of the tetrahedron when using
         * getShape().
         *
         * @see Tetrahedron#getShape
         */
        public static final int BOTTOM = 3;

        float m_fLength;

        int m_iNumTexUnit = 1;

        int m_iPrimFlags = 0;

        /**
         * Constructs a default tetrahedron with all sides 1.0 long
         */
        public Tetrahedron() {
                this( 1.0f, GENERATE_NORMALS, null );
        }

        /**
         * Constructs a tetrahedron of a given dimension and appearance.
         *
         * @param fLength side length
         * @param oAppearance Appearance
         */
        public Tetrahedron( float fLength, Appearance oAppearance)
        {
                this( fLength, GENERATE_NORMALS, oAppearance );
        }

        /**
         * Constructs a tetrahedron of a given dimension, flags, and appearance.
         *
         * @param fLength side length
         * @param iPrimFlags primitive flags.
         * @param oAppearance Appearance
         */
        public Tetrahedron( float fLength, int iPrimFlags,
                Appearance oAppearance) {

                this( fLength, iPrimFlags, oAppearance, 1);
        }

        /**
         * Constructs a tetrahedron of a given dimension, flags, and appearance.
         *
         * @param fLength side length
         * @param iPrimFlags primitive flags.
         * @param oAppearance Appearance
         * @param iNumTexUnit
         */
        public Tetrahedron( float fLength, int iPrimFlags,
                Appearance oAppearance, int iNumTexUnit) {

                double dSign;

                m_fLength = fLength;

                m_iPrimFlags = iPrimFlags;
                m_iNumTexUnit = iNumTexUnit;

                m_aoPoints[ 0 ].x *= fLength;
                m_aoPoints[ 0 ].y *= fLength;
                m_aoPoints[ 0 ].z *= fLength;

                m_aoPoints[ 1 ].x *= fLength;
                m_aoPoints[ 1 ].y *= fLength;
                m_aoPoints[ 1 ].z *= fLength;

                m_aoPoints[ 2 ].x *= fLength;
                m_aoPoints[ 2 ].y *= fLength;
                m_aoPoints[ 2 ].z *= fLength;

                m_aoPoints[ 3 ].x *= fLength;
                m_aoPoints[ 3 ].y *= fLength;
                m_aoPoints[ 3 ].z *= fLength;

                //Depending on whether normal inward bit is set.
                if ((iPrimFlags & GENERATE_NORMALS_INWARD) != 0)
                        dSign = -1.0;
                else
                        dSign = 1.0;

                /* Fabian Wleklinski: Ein Instanz von Shape3D für jede Seite
                des Tetraeders erstellen */

                for (int i = LEFT; i <= BOTTOM; i++) {

                        TriangleArray oTriangleArray = new TriangleArray(
                                3,
                                TriangleArray.COORDINATES |
                                TriangleArray.NORMALS |
                                TriangleArray.TEXTURE_COORDINATE_2
                        );

                        oTriangleArray.setCoordinates(
                                0,
                                m_aaoVertFactors[ i ]
                        );

                        oTriangleArray.setTextureCoordinates(
                                0,
                                0,
                                m_aaoTextureCoordinates[ i ]
                        );

                        /* Fabian Wleklinski: calculate normal */
                        Vector3f oVectorNormal = new Vector3f();
                        Vector3f oVector1 = new Vector3f();
                        Vector3f oVector2 = new Vector3f();
                        oVector1.sub( m_aaoVertFactors[ i ][ 2 ], m_aaoVertFactors[ i ][ 1 ] );
                        oVector2.sub( m_aaoVertFactors[ i ][ 1 ], m_aaoVertFactors[ i ][ 0 ] );

                        oVectorNormal.cross( oVector2, oVector1 );
                        oVectorNormal.normalize();

                        oTriangleArray.setNormal(
                                0,
                                oVectorNormal
                        );
                        oTriangleArray.setNormal(
                                1,
                                oVectorNormal
                        );
                        oTriangleArray.setNormal(
                                2,
                                oVectorNormal
                        );

                        Shape3D oShape3D = new Shape3D( oTriangleArray );

                        if ((iPrimFlags & ENABLE_APPEARANCE_MODIFY) != 0) {
                                oShape3D.setCapability( Shape3D.ALLOW_APPEARANCE_READ );
                                oShape3D.setCapability( Shape3D.ALLOW_APPEARANCE_WRITE );
                        }

                        if ((iPrimFlags & ENABLE_GEOMETRY_PICKING) != 0) {
                                oShape3D.setCapability( Shape3D.ALLOW_GEOMETRY_READ );
                        }

                        this.addChild( oShape3D );
                }

                if (oAppearance == null) {
                        setAppearance();
                } else {
                        setAppearance( oAppearance );
                }
        }

        /**
         * Gets one of the faces (Shape3D) from the tetrahedron that contains the
         * geometry and appearance. This allows users to modify the
         * appearance or geometry of individual parts.
         * @param partId The part to return.
         * @return The Shape3D object associated with the partID.  If an
         * invalid partId is passed in, null is returned.
         */
        public Shape3D getShape( int iPartId ) {
                if ((iPartId >= LEFT) && (iPartId <= BOTTOM)) {
                        return (Shape3D) getChild( iPartId );
                }

                return null;
        }

        /**
         *  Sets appearance of the tetrahedron. This will set each face of the
         *  tetrahedron to the same appearance. To set each face's appearance
         *  separately, use getShape(iPartId) to get the
         *  individual shape and call shape.setAppearance(ap).
         */
        public void setAppearance( Appearance oAppearance ){
                ((Shape3D) getChild(LEFT)).setAppearance( oAppearance );
                ((Shape3D) getChild(RIGHT)).setAppearance( oAppearance );
                ((Shape3D) getChild(BACK)).setAppearance( oAppearance );
                ((Shape3D) getChild(BOTTOM)).setAppearance( oAppearance );
        }

        /**
         * Gets the appearance of the specified part of the tetrahedron.
         *
         * @param iPartId identifier for a given subpart of the tetrahedron
         *
         * @return The appearance object associated with the iPartId.  If an
         * invalid iPartId is passed in, null is returned.
         */
        public Appearance getAppearance( int iPartId ) {
                if (iPartId > BOTTOM || iPartId < LEFT) {
                        return null;
                }

                return getShape( iPartId ).getAppearance();
        }


        // sqrt( 0.75 ) = 0.86602540378443864676372317075294
        // sqrt( 0.75 ) / 2 = sqrt( 0.1875 ) = 0.43301270189221932338186158537647
        private static final float SQRT_075 = 0.86602540378443864676372317075294f;
        private static final float SQRT_01875 = SQRT_075 / 2;

        private static final Point3f[] m_aoPoints = {
                new Point3f(-0.5f, -0.375f, -SQRT_01875),
                new Point3f(+0.5f, -0.375f, -SQRT_01875),
                new Point3f(+0.0f, -0.375f, +SQRT_01875),
                new Point3f(+0.0f, +0.375f, +0.0f)
        };

        private static final Point3f[][] m_aaoVertFactors = {
                {
                        // left face
                        m_aoPoints[ 2 ],
                        m_aoPoints[ 3 ],
                        m_aoPoints[ 0 ]
                },
                {
                        // right face
                        m_aoPoints[ 1 ],
                        m_aoPoints[ 3 ],
                        m_aoPoints[ 2 ]
                },
                {
                        // back face
                        m_aoPoints[ 0 ],
                        m_aoPoints[ 3 ],
                        m_aoPoints[ 1 ]
                },
                {
                        // bottom face
                        m_aoPoints[ 1 ],
                        m_aoPoints[ 2 ],
                        m_aoPoints[ 0 ]
                }
        };

        // the texturecoordinates
        private static final TexCoord2f[] m_aoTexturePoints = {
                new TexCoord2f(0.0f, 0.0f),
                new TexCoord2f(0.5f, (float) SQRT_075),
                new TexCoord2f(1.0f, 0.0f)
        };

        // then I certainly need some texturecoordinates
        private TexCoord2f[][] m_aaoTextureCoordinates = {
                {
                        m_aoTexturePoints[ 0 ],
                        m_aoTexturePoints[ 1 ],
                        m_aoTexturePoints[ 2 ]
                },
                {
                        m_aoTexturePoints[ 0 ],
                        m_aoTexturePoints[ 1 ],
                        m_aoTexturePoints[ 2 ]
                },
                {
                        m_aoTexturePoints[ 0 ],
                        m_aoTexturePoints[ 1 ],
                        m_aoTexturePoints[ 2 ]
                },
                {
                        m_aoTexturePoints[ 0 ],
                        m_aoTexturePoints[ 1 ],
                        m_aoTexturePoints[ 2 ]
                }
        };

        /**
         * Used to create a new instance of the node.  This routine is called
         * by <code>cloneTree</code> to duplicate the current node.
         * <code>cloneNode</code> should be overridden by any user subclassed
         * objects.  All subclasses must have their <code>cloneNode</code>
         * method consist of the following lines:
         * <P><blockquote><pre>
         *     public Node cloneNode(boolean forceDuplicate) {
         *         UserSubClass usc = new UserSubClass();
         *         usc.duplicateNode(this, forceDuplicate);
         *         return usc;
         *     }
         * </pre></blockquote>
         * @param forceDuplicate when set to <code>true</code>, causes the
         *  <code>duplicateOnCloneTree</code> flag to be ignored.  When
         *  <code>false</code>, the value of each node's
         *  <code>duplicateOnCloneTree</code> variable determines whether
         *  NodeComponent data is duplicated or copied.
         *
         * @see Node#cloneTree
         * @see Node#duplicateNode
         * @see NodeComponent#setDuplicateOnCloneTree
         */
        public Node cloneNode( boolean bForceDuplicate ) {
                Tetrahedron oTetrahedron = new Tetrahedron( m_fLength, m_iPrimFlags, getAppearance() );
                oTetrahedron.duplicateNode( this, bForceDuplicate );
                return oTetrahedron;
        }

        /**
         * Copies all node information from <code>originalNode</code> into
         * the current node.  This method is called from the
         * <code>cloneNode</code> method which is, in turn, called by the
         * <code>cloneTree</code> method.
         * <P>
         * For any <i>NodeComponent</i> objects
         * contained by the object being duplicated, each <i>NodeComponent</i>
         * object's <code>duplicateOnCloneTree</code> value is used to determine
         * whether the <i>NodeComponent</i> should be duplicated in the new node
         * or if just a reference to the current node should be placed in the
         * new node.  This flag can be overridden by setting the
         * <code>forceDuplicate</code> parameter in the <code>cloneTree</code>
         * method to <code>true</code>.
         *
         * @param originalNode the original node to duplicate.
         * @param forceDuplicate when set to <code>true</code>, causes the
         *  <code>duplicateOnCloneTree</code> flag to be ignored.  When
         *  <code>false</code>, the value of each node's
         *  <code>duplicateOnCloneTree</code> variable determines whether
         *  NodeComponent data is duplicated or copied.
         *
         * @see Node#cloneTree
         * @see Node#cloneNode
         * @see NodeComponent#setDuplicateOnCloneTree
         */
        public void duplicateNode( Node oOriginalNode, boolean bForceDuplicate ) {
                super.duplicateNode( oOriginalNode, bForceDuplicate );
        }

        /**
         * Returns the side length of the Tetrahedron
         */
        public float getLength() {
                return m_fLength;
        }
}
