/*
 * Copyright (c) 2009-2012 jMonkeyEngine
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.jme3.scene.plugins.blender.curves;

import java.util.logging.Logger;

import com.jme3.math.FastMath;
import com.jme3.math.Matrix4f;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.file.BlenderFileException;
import com.jme3.scene.plugins.blender.file.Structure;

/**
 * A class that is used in mesh calculations.
 * 
 * @author Marcin Roguski (Kaelthas)
 */
public class CurvesHelper extends AbstractBlenderHelper {
    private static final Logger LOGGER                      = Logger.getLogger(CurvesHelper.class.getName());

    /** Minimum basis U function degree for NURBS curves and surfaces. */
    protected int               minimumBasisUFunctionDegree = 4;
    /** Minimum basis V function degree for NURBS curves and surfaces. */
    protected int               minimumBasisVFunctionDegree = 4;

    /**
     * This constructor parses the given blender version and stores the result. Some functionalities may differ in
     * different blender versions.
     * @param blenderVersion
     *            the version read from the blend file
     * @param blenderContext
     *            the blender context
     */
    public CurvesHelper(String blenderVersion, BlenderContext blenderContext) {
        super(blenderVersion, blenderContext);
    }

    public CurvesTemporalMesh toCurve(Structure curveStructure, BlenderContext blenderContext) throws BlenderFileException {
        CurvesTemporalMesh result = new CurvesTemporalMesh(curveStructure, blenderContext);

        if (blenderContext.getBlenderKey().isLoadObjectProperties()) {
            LOGGER.fine("Reading custom properties.");
            result.setProperties(this.loadProperties(curveStructure, blenderContext));
        }

        return result;
    }

    /**
     * The method transforms the bevel along the curve.
     * 
     * @param bevel
     *            the bevel to be transformed
     * @param prevPos
     *            previous curve point
     * @param currPos
     *            current curve point (here the center of the new bevel will be
     *            set)
     * @param nextPos
     *            next curve point
     * @return points of transformed bevel
     */
    protected Vector3f[] transformBevel(Vector3f[] bevel, Vector3f prevPos, Vector3f currPos, Vector3f nextPos) {
        bevel = bevel.clone();

        // currPos and directionVector define the line in 3D space
        Vector3f directionVector = prevPos != null ? currPos.subtract(prevPos) : nextPos.subtract(currPos);
        directionVector.normalizeLocal();

        // plane is described by equation: Ax + By + Cz + D = 0 where planeNormal = [A, B, C] and D = -(Ax + By + Cz)
        Vector3f planeNormal = null;
        if (prevPos != null) {
            planeNormal = currPos.subtract(prevPos).normalizeLocal();
            if (nextPos != null) {
                planeNormal.addLocal(nextPos.subtract(currPos).normalizeLocal()).normalizeLocal();
            }
        } else {
            planeNormal = nextPos.subtract(currPos).normalizeLocal();
        }
        float D = -planeNormal.dot(currPos);// D = -(Ax + By + Cz)

        // now we need to compute paralell cast of each bevel point on the plane, the leading line is already known
        // parametric equation of a line: x = px + vx * t; y = py + vy * t; z = pz + vz * t
        // where p = currPos and v = directionVector
        // using x, y and z in plane equation we get value of 't' that will allow us to compute the point where plane and line cross
        float temp = planeNormal.dot(directionVector);
        for (int i = 0; i < bevel.length; ++i) {
            float t = -(planeNormal.dot(bevel[i]) + D) / temp;
            if (fixUpAxis) {
                bevel[i] = new Vector3f(bevel[i].x + directionVector.x * t, bevel[i].y + directionVector.y * t, bevel[i].z + directionVector.z * t);
            } else {
                bevel[i] = new Vector3f(bevel[i].x + directionVector.x * t, -bevel[i].z + directionVector.z * t, bevel[i].y + directionVector.y * t);
            }
        }
        return bevel;
    }

    /**
     * This method transforms the first line of the bevel points positioning it
     * on the first point of the curve.
     * 
     * @param startingLinePoints
     *            the vbevel shape points
     * @param firstCurvePoint
     *            the first curve's point
     * @param secondCurvePoint
     *            the second curve's point
     * @return points of transformed bevel
     */
    protected Vector3f[] transformToFirstLineOfBevelPoints(Vector3f[] startingLinePoints, Vector3f firstCurvePoint, Vector3f secondCurvePoint) {
        Vector3f planeNormal = secondCurvePoint.subtract(firstCurvePoint).normalizeLocal();

        float angle = FastMath.acos(planeNormal.dot(Vector3f.UNIT_X));
        Vector3f rotationVector = Vector3f.UNIT_X.cross(planeNormal).normalizeLocal();
        
        Matrix4f m = new Matrix4f();
        m.setRotationQuaternion(new Quaternion().fromAngleAxis(angle, rotationVector));
        m.setTranslation(firstCurvePoint);

        Vector3f temp = new Vector3f();
        Vector3f[] verts = new Vector3f[startingLinePoints.length];
        for (int i = 0; i < verts.length; ++i) {
            verts[i] = m.mult(startingLinePoints[i], temp).clone();
        }
        return verts;
    }
}