/*
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOUSE. See the GNU
 * General Public License for more details.
 *
 * You should have recieved a copy of the GNU General Public License
 * along with this program; if not write to the Free Software
 * Foundation, inc., 59 Temple Place, Suite 330, Boston MA 02111-1307
 * USA
 */
/* 
 *
 * Nurbs.java
 *
 * author   Timothy F. Rohaly
 * version  1.0, 01/15/97
 */

package j3d.nurbs;
import j3d.*;


/**
 * NURBS curve and surface
 * @author   Timothy F.  Rohaly
 * @version  1.0, 1/15/97
 */
public class Nurbs implements Cloneable {

    protected Knot       u;
    protected Knot       v;
    protected ControlNet controlNet;


    /**
     * NURBS Constructor.
     */
    Nurbs() {
        // Use of this form not recommended since it
        // bypasses consistency checks.
        // It is left here solely as an ease to development.
    }


    /**
     * NURBS Constructor.
     * @param u the U knot sequence
     * @param v the V knot sequence
     * @param controlNet the array of Point4 control points
     * @exception java.lang.IllegalArgumentException when the
     */
    public Nurbs(Knot u, Knot v, ControlNet controlNet) {
        //
        // Check for consistency
        //
        String errorString = getClass().getName() + ":  " +
                             "Number of knots in array must equal " +
                             "order + number of control points";
        if (u.numControlPoints != controlNet.numUControlPoints ||
            v.numControlPoints != controlNet.numVControlPoints   )
            throw new IllegalArgumentException(errorString);

        this.u          = u;
        this.v          = v;
        this.controlNet = controlNet;
    }


    /**
     * Performs a deep copy of this NURBS.
     * @return a new instance of Nurbs
     */
    public Object clone() {
        try {
            Nurbs n      = (Nurbs) super.clone();
            n.u          = (Knot) u.clone();
            n.v          = (Knot) v.clone();
            n.controlNet = (ControlNet) controlNet.clone();
            return n;
        }
        catch (CloneNotSupportedException e) {
            String errorString = getClass().getName() + ":  " +
                                 "CloneNotSupportedException " +
                                 "for a Cloneable class";
            throw new InternalError(errorString);  // this should never happen
        }
    }


    /**
     * Access the number of U knots.
     * @return the number of U knots
     */
    public int getNumUKnots() {
        return u.numKnots;
    }


    /**
     * Access the number of V knots.
     * @return the number of V knots
     */
    public int getNumVKnots() {
        return v.numKnots;
    }


    /**
     * Access the NURB order in U.
     * @return the order in U of the NURB
     */
    public int getUOrder() {
        return u.order;
    }


    /**
     * Access the NURB order in V.
     * @return the order in V of the NURB
     */
    public int getVOrder() {
        return v.order;
    }


    /**
     * Access the number of U control points.
     * @return the number of U control points
     */
    public int getNumUControlPoints() {
        return controlNet.numUControlPoints;
    }


    /**
     * Access the number of V control points.
     * @return the number of V control points
     */
    public int getNumVControlPoints() {
        return controlNet.numVControlPoints;
    }


    /**
     * Access the U knot sequence.
     * @return the U knot sequence
     */
    public Knot getUKnot() {
        return u;
    }


    /**
     * Access the V knot sequence.
     * @return the V knot sequence
     */
    public Knot getVKnot() {
        return v;
    }


    /**
     * Set the U knot sequence.
     * @param u the new U knot sequence
     */
    public void setUKnot(Knot u) {
        this.u = u;
    }


    /**
     * Set the V knot sequence.
     * @param v the new V knot sequence
     */
    public void setVKnot(Knot v) {
        this.v = v;
    }


    /**
     * Set the ControlNet.
     * @param controlNet the new ControlNet
     */
    public void setControlNet(ControlNet controlNet) {
        this.controlNet = controlNet;
    }


    /**
     * Scale this NURBS uniformly.
     * @param scale the scale
     */
    public void scale(float scale) {
        controlNet.scale(scale);
    }


    /**
     * Scale this NURBS non-uniformly.
     * @param scale the scale
     */
    public void scale(float xscale, float yscale, float zscale) {
        controlNet.scale(xscale, yscale, zscale);
    }


    /**
     * Translate this NURBS.
     * @param x the translation in X
     * @param y the translation in Y
     * @param z the translation in Z
     */
    public void translate(float x, float y, float z) {
        controlNet.translate(x, y, z);
    }


    /**
     * Rotate this NURBS.
     * @param x the X component of the rotation axis
     * @param y the Y component of the rotation axis
     * @param z the Z component of the rotation axis
     * @param theta the rotation in radians
     */
    public void rotate(float x, float y, float z, float theta) {
        controlNet.rotate(x, y, z, theta);
    }


    /**
     * Transpose this NURBS by swapping U and V.
     */
    public void transpose() {
        Knot temp;

        temp = u;
        u    = v;
        v    = temp;

        controlNet.transpose();
    }


    /**
     * Refine this NURBS in the U and V directions by inserting
     * new knots and control points.
     * Employs recursive subdivision using the Oslo algorithm.
     * Refinement is done through recursive subdivision by adding
     * uSegments knots in the U direction and vSegments knots in
     * the V direction.  (This also adds uSegments*vSegments
     * control points since the order remains constant.)
     * @param uSegments the number of new control points
     * @param vSegments the number of new control points
     * @return the refined Nurbs
     */
    public Nurbs tessellate(int uSegments, int vSegments) {
        //
        // Declare the return Nurbs.  It initially refers
        // to "this" to account for the case where
        // uSegments == vSegments == 0
        //
        Nurbs temp = this;

        //
        // Tesselate in U direction first
        //
        if (uSegments > 0) {
            temp = this.tessellateU(uSegments);
//          System.err.println("After Tesselate: " + temp);
        }

        if (vSegments > 0) {
            //
            // If this is a surface, transpose (swap U, V),
            // then tessellate in new U direction (= old V direction)
            //
            if (uSegments <=0)
                temp = (Nurbs) this.clone();
            temp.transpose();

            Nurbs temp2 = temp.tessellateU(vSegments);

            //
            // Transpose back to its original configuration
            // and return
            //
            temp2.transpose();
            return temp2;
       }
       else {
            //
            // This is a curve, so we're done
            //
            return temp;
       }
    }


    /**
     * Refine this NURBS in the U direction by inserting new knots
     * and control points.
     * Refinement is done via recursive subdivision by adding
     * segments new knots.
     * @param uSegments the number of new control points
     * @return the refined Nurbs
     */
    private Nurbs tessellateU(int uSegments) {
        Nurbs temp    = new Nurbs();

        //
        // First add new knots to U knot sequence
        //
        int uOrder    = this.getUOrder();
        int numUKnots = Math.max(uSegments, this.getUOrder()*2 +1);
        int numUControlPoints = numUKnots - this.getUOrder();

        temp.u = new Knot(uOrder, numUControlPoints);
        temp.u.makeKnots(u.knot[0], u.knot[u.numKnots-1]);
        temp.u = temp.u.unionKnots(this.u);

        //
        // Copy the V knot sequence
        //
        temp.v        = (Knot) v.clone();

//      System.err.println("tessellate:  New U Knot Vector = " +
//                                      temp.u.toString());
//      System.err.println("tessellate:  New V Knot Vector = " +
//                                      temp.v.toString());

        //
        // Second create the new ControlNet
        //
        Point4[][] tmppoints = new Point4[temp.u.numControlPoints]
                                         [temp.v.numControlPoints];
        for (int i=0; i<temp.u.numControlPoints; i++) {
            for (int j=0; j<temp.v.numControlPoints; j++) {
                // Creates the Point4 objects and initializes
                // them to zero
                tmppoints[i][j] = new Point4();
            }
        }
        ControlNet tmpcontrol = new ControlNet(temp.u.numControlPoints,
                                               temp.v.numControlPoints,
                                               tmppoints);
        temp.setControlNet(tmpcontrol);

        //
        // Now refine in U direction
        //
//      System.err.println("loop over " + temp.u.numControlPoints);
        //
        // should this be over numKnots?  no, since
        // numControlPoints = numKnots - order, and since we
        // know that the last order knots are unchanged, we don't
        // have to deal with those.
        //
        for (int i=0; i<temp.u.numControlPoints; i++) {
//          System.err.println("in refine " + i);
            // knotIndex tells us where this particular new knot lies
            // in the old knot series
//          System.err.println("new knot=" + temp.u.knot[i]);
            int knotIndex = Knot.interval(this.u.knot, temp.u.knot[i]);
//          System.err.println("Index knotIndex = " + knotIndex +
//                             " out of " + getNumUKnots());
            this.subdivide(temp, knotIndex, i);
        }
        return temp;
    }


    /**
     * Calculates the control points corresponding to new knots.
     * This is called once for each new knot and creates
     * numVControlPoints new control points.
     * @param temp the Nurbs which will contain the new control points
     * @param oldKnotIndex  the index of the old knot
     * @param newKnotIndex the index of the new knot
     */
    protected void subdivide(Nurbs temp, int oldKnotIndex, int newKnotIndex) {

        int ttsize = getUOrder();
        Point4[][] tmppoints = new Point4[getUOrder()][oldKnotIndex+1];

        //
        // Refine U
        //
//      System.err.println("subdivide:  kk loop over " + getNumVControlPoints());
        for (int kk=0; kk<getNumVControlPoints(); kk++) {
//          System.err.println("subdivide:  ii loop over mu=" +
//                              oldKnotIndex + " nrb1.k=" + getUOrder());
            for (int j=oldKnotIndex-getUOrder()+1; j<oldKnotIndex+1; j++) {
                tmppoints[0][j] = this.controlNet.controlPoints[j][kk];
//              System.err.println("subdivide:  ttpts=" + tmppoints[0][j]);
            }

//          System.err.println("subdivide:  ir loop over " + this.u.order);
            for (int i=1; i<getUOrder(); i++) {
//              System.err.println("subdivide:  ii(2) loop over mu=" +
//                            oldKnotIndex + " nrb1.k=" + u.order + " ir=" + i);
                for (int j=oldKnotIndex-getUOrder()+1+i; j<oldKnotIndex+1; j++) {
                    float t1 = temp.u.knot[newKnotIndex  + getUOrder() - i] -
                               this.u.knot[j];
                    float t2 = this.u.knot[j + getUOrder() - i] -
                               temp.u.knot[newKnotIndex  + getUOrder() - i];
                    float tmul = 1.0f / (t1 + t2);
//                  System.err.println("subdivide:  t1=" + t1 +
//                                     " t2=" + t2 + " tmul=" + tmul);
                    // interpolate new control points using old
                    // control points
//                  System.err.println(" i = " + i + " j = " + j);
                    tmppoints[i][j] = new Point4(
                    (t1*tmppoints[i-1][j].x + t2*tmppoints[i-1][j-1].x) * tmul,
                    (t1*tmppoints[i-1][j].y + t2*tmppoints[i-1][j-1].y) * tmul,
                    (t1*tmppoints[i-1][j].z + t2*tmppoints[i-1][j-1].z) * tmul,
                    (t1*tmppoints[i-1][j].w + t2*tmppoints[i-1][j-1].w) * tmul);
                }
            }
//          System.err.println("FINAL = " + tmppoints[getUOrder()-1][oldKnotIndex]);
            temp.controlNet.controlPoints[newKnotIndex][kk] =
                                tmppoints[getUOrder()-1][oldKnotIndex];
        }
    }


    /**
     * Split this NURBS into two at the given location.
     * @param dottedline the knot parameter at which to cut
     * @return an array of two Nurbs objects
     */
    protected Nurbs[] split(float dottedline) {
        //
        // Not fully implemented yet

        //
        // Find where in the knot sequence dottedline lies
        //
        int knotIndex = Knot.interval(u.knot, dottedline);
        System.err.println("Index knotIndex = " + knotIndex +
                           " out of " + getNumUKnots());

        //
        // First create the Knots
        //
        int order = 0;
        // need order more for the end of the sequence where the split is.
        int numKnots = getNumUKnots() + getUOrder();
        int numControlPoints = 0;

        //
        // Copy first portion of knot sequence
        //
        float[] tmpknot = new float[numKnots];
        for (int i=0; i<knotIndex; i++) {
            tmpknot[i] = this.u.knot[i];
        }

        int ir = getUOrder();
        for (int i=0; i<getUOrder(); i++) {
            if (dottedline == u.knot[knotIndex - i])
                ir--; 
        }

        int iin = knotIndex;
        for (int i=0; i<ir; i++) {
            tmpknot[iin] = dottedline;
            iin++;
        }

        //
        // Copy second portion of knot sequence
        //
        for (int i=knotIndex; i<getNumUKnots(); i++) {
            tmpknot[i] = this.u.knot[i];
        }

        Knot kk = new Knot(order, numControlPoints);
        kk.setKnots(tmpknot);

        System.err.println("split:  Old U Knot Vector = " +
                                    u.toString());
        System.err.println("split:  New U Knot Vector = " +
                                    kk.toString());

//OSLO
        //
        // Second create the new ControlNet
        //
Nurbs tmp = new Nurbs();
        Point4[][] tmppoints = new Point4[tmp.u.numControlPoints]
                                         [tmp.v.numControlPoints];
        for (int i=0; i<tmp.u.numControlPoints; i++) {
            for (int j=0; j<tmp.v.numControlPoints; j++) {
                // Creates the Point4 objects and initializes
                // them to zero
                tmppoints[i][j] = new Point4();
            }
        }
        ControlNet tmpcontrol = new ControlNet(tmp.u.numControlPoints,
                                               tmp.v.numControlPoints,
                                               tmppoints);
        tmp.setControlNet(tmpcontrol);

        //
        // Now refine in U direction
        //
        // should this be over numKnots?  no, since
        // numControlPoints = numKnots - order, and since we
        // know that the last order knots are unchanged, we don't
        // have to deal with those.
        //
        for (int i=0; i<tmp.u.numControlPoints; i++) {
            // knotIndex tells us where this particular new knot lies
            // in the old knot series
            knotIndex = Knot.interval(this.u.knot, tmp.u.knot[i]);
            this.subdivide(tmp, knotIndex, i);
        }

       // blah blah


        Nurbs[] temp = new Nurbs[2];
        temp[0] = new Nurbs();
//      temp[0] = new Nurbs(kk, (Knot) v.clone(), controlNet);
        temp[1] = new Nurbs();

        return temp;
    }


    /**
     * Perform a linear extrusion of this NURBS along the vector w
     * to produce a new NURBS.  Leaves this instance unchanged.
     * If this Nurbs instance is a surface, extrude the v.knot[0]
     * curve only.
     * @oaram w a vector for the extrusion.
     * @return a Nurbs surface.
     */
    public Nurbs extrude(Point4 w) {
        //
        // Curve and Surface Construction using Rational B-splines
        // Piegl and Tiller CAD Vol 19 #9 November 1987 pp 485-498
        // 
        int order            = 2;   // linear extrusion
        int numControlPoints = 2;
        int numKnots         = 4;

        //
        // First create the Knots
        //
        float[] temp = new float[numKnots];
        temp[0] = 0.0f;
        temp[1] = 0.0f;
        temp[2] = 1.0f;
        temp[3] = 1.0f;

        Knot tmpVKnot = new Knot(order, numControlPoints);
        tmpVKnot.setKnots(temp);

        //
        // Second create the ControlNet
        //
        Point4[][] tmppoints = new Point4[u.numControlPoints]
                                         [tmpVKnot.numControlPoints];
        for (int i=0; i<u.numControlPoints; i++) {
            for (int j=0; j<tmpVKnot.numControlPoints; j++) {
                // Creates the Point4 objects and initializes
                // them to zero
                tmppoints[i][j] = new Point4();
            }
        }

        for (int i=0; i<u.numControlPoints; i++) {
            for (int j=0; j<tmpVKnot.numControlPoints; j++) {

                /* Change added 11/02/90 Steve Larkin :
                 * Have multiplied the term wcoord to the extrusion vector
                 * before adding to the curve coordinates. Not really sure
                 * this is the correct fix, but it works !
                 */
                tmppoints[i][j].x = this.controlNet.controlPoints[i][0].x +
                                    j*w.x;
                tmppoints[i][j].y = this.controlNet.controlPoints[i][0].y +
                                    j*w.y;
                tmppoints[i][j].z = this.controlNet.controlPoints[i][0].z +
                                    j*w.z;
                tmppoints[i][j].w = this.controlNet.controlPoints[i][0].w;
            }
        }
        ControlNet tmpcontrol = new ControlNet(u.numControlPoints,
                                               tmpVKnot.numControlPoints,
                                               tmppoints);
        return new Nurbs((Knot) u.clone(), tmpVKnot, tmpcontrol);
    }


    /**
     * Do a surface of revolution about the Y axis.
     * Assumes curve is in X-Y plane
     * The profile NURBS will first be projected onto the Z=0 plane,
     * the resulting projection will be revolved about the Y axis.
     * Currently, thetamin and thetamax are ignored and 0, 2*PI is
     * used in all cases.
     * @param thetamin the angle at which to start the revolution
     * @param thetamax the angle at which to end the revolution
     * @return a Nurbs surface.
     */
    public Nurbs revolve(float thetamin, float thetamax) {
        //
        // Curve and Surface Construction using Rational B-splines
        // Piegl and Tiller CAD Vol 19 #9 November 1987 pp 485-498
        //
        int order            =  3;
        int numControlPoints =  9;
        int numKnots         = 12;

        //
        // First create the Knots for the circular cross section
        // If we want to limit this to an arc , would be done here
        // using the createArc() code rather than the createCircle()
        // code.
        //
        float[] temp = { 0.00f, 0.00f, 0.00f,
                         0.25f, 0.25f,
                         0.50f, 0.50f,
                         0.75f, 0.75f,
                         1.00f, 1.00f, 1.00f };

        Knot tmpUKnot = new Knot(order, numControlPoints);
        tmpUKnot.setKnots(temp);

        //
        // Second create the ControlNet
        //
        Point4[][] tmppoints = new Point4[tmpUKnot.numControlPoints]
                                         [u.numControlPoints];
        for (int i=0; i<tmpUKnot.numControlPoints; i++) {
            for (int j=0; j<u.numControlPoints; j++) {
                tmppoints[i][j] = new Point4();
            }
        }

        //
        // Copy profile curve.
        //
        for (int i=0; i<u.numControlPoints; i++) {
            tmppoints[0][i].x = controlNet.controlPoints[i][0].x;
            tmppoints[0][i].y = controlNet.controlPoints[i][0].y;
            tmppoints[0][i].z = 0.0f;            // Project onto Z=0 plane
            tmppoints[0][i].w = controlNet.controlPoints[i][0].w;
        }

        float ww = (float) Math.sqrt(2.0)/2.0f;

        //
        // Sweep about Y axis.  The V direction will be
        // a circle around the Y axis.
        //
        for (int i=0; i<u.numControlPoints; i++) {
            for (int j=1; j<tmpUKnot.numControlPoints; j++) {
                tmppoints[j][i].y = tmppoints[0][i].y;
            }

            float radius = tmppoints[0][i].x;
            float weight = tmppoints[0][i].w;

            int jj = 1;
            for (int j=0; j<4; j++) {
                tmppoints[jj  ][i].y *= ww;
                tmppoints[jj  ][i].w  = ww * weight;
                tmppoints[jj+1][i].w  = weight;
                jj += 2;
            }

            tmppoints[1][i].x =  radius * ww;
            tmppoints[1][i].z =  radius * ww;
            tmppoints[7][i].x =  radius * ww;
            tmppoints[7][i].z = -radius * ww;
            tmppoints[2][i].x =  0.0f;
            tmppoints[2][i].z =  radius;
            tmppoints[6][i].x =  0.0f;
            tmppoints[6][i].z = -radius;
            tmppoints[3][i].x = -radius * ww;
            tmppoints[3][i].z =  radius * ww;
            tmppoints[5][i].x = -radius * ww;
            tmppoints[5][i].z = -radius * ww;
            tmppoints[4][i].x = -radius;
            tmppoints[4][i].z =  0.0f;
            tmppoints[8][i].x =  radius;
            tmppoints[8][i].z =  0.0f;
        }
        ControlNet tmpcontrol = new ControlNet(tmpUKnot.numControlPoints,
                                               u.numControlPoints,
                                               tmppoints);
        return new Nurbs(tmpUKnot, (Knot) u.clone(), tmpcontrol);
    }


    /**
     * Increase the order of this NURBS
     * @return the elevated Nurbs
     */
    public Nurbs elevate() {
        //
        // Not implemented yet
        //
        return new Nurbs();
    }


    /**
     * Creates a String representation for the NURBS
     * in the form of a VRML Coordinate node.
     * @return a string representation of the Nurbs
     */
    public String toVRMLString() {
        return controlNet.toVRMLString();
    }


    /**
     * Creates a String representation for the NURBS
     * in the form of a VRML Coordinate node.
     * @return a string representation of the Nurbs
     */
    public _Point[][] toCoordinateNode() {
        return controlNet.toCoordinateNode();
    }


    /**
     * Creates a String representation for the NURBS
     * in the form of a VRML coordIndex field.
     * @return a string representation of the Nurbs
     */
    public int[] toVRMLCoordIndex() {
        return controlNet.toVRMLCoordIndex();
    }


    /**
     * Creates a String representation for the NURBS
     * @return a string representation of the Nurbs
     */
    public String toString() {
        // Reserve enough space initially for a "typical" curve
        // in order to minimize buffer resizing. 
        StringBuffer buffer = new StringBuffer(1024);
        buffer.append(getClass().getName() + ": ");
        buffer.append("uOrder=" + getUOrder() + ", " +
                      "vOrder=" + getVOrder() + "\n");
        buffer.append("U Knot Sequence: " + u.toString() + "\n");
        buffer.append("V Knot Sequence: " + v.toString() + "\n");
        buffer.append("Control Points: "  + controlNet.toString());
        return buffer.toString();
    }
}
