/*
 *   __               .__       .__  ._____.           
 * _/  |_  _______  __|__| ____ |  | |__\_ |__   ______
 * \   __\/  _ \  \/  /  |/ ___\|  | |  || __ \ /  ___/
 *  |  | (  <_> >    <|  \  \___|  |_|  || \_\ \\___ \ 
 *  |__|  \____/__/\_ \__|\___  >____/__||___  /____  >
 *                   \/       \/             \/     \/ 
 *
 * Copyright (c) 2006-2011 Karsten Schmidt
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * http://creativecommons.org/licenses/LGPL/2.1/
 * 
 * This library 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 PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

package toxi.geom.mesh;

import toxi.geom.Vec2D;
import toxi.geom.Vec3D;

/**
 * An extensible builder class for {@link TriangleMesh}es based on 3D surface
 * functions using spherical coordinates. In order to create a mesh, you'll need
 * to supply a {@link SurfaceFunction} implementation to the builder.
 */
public class SurfaceMeshBuilder {

    /**
     *
     */
    protected SurfaceFunction function;

    /**
     *
     * @param function
     */
    public SurfaceMeshBuilder(SurfaceFunction function) {
        this.function = function;
    }

    /**
     *
     * @param res
     * @return
     */
    public Mesh3D createMesh(int res) {
        return createMesh(null, res, 1);
    }

    /**
     *
     * @param mesh
     * @param res
     * @param size
     * @return
     */
    public Mesh3D createMesh(Mesh3D mesh, int res, float size) {
        return createMesh(mesh, res, size, true);
    }

    /**
     *
     * @param mesh
     * @param res
     * @param size
     * @param isClosed
     * @return
     */
    public Mesh3D createMesh(Mesh3D mesh, int res, float size, boolean isClosed) {
        if (mesh == null) {
            mesh = new TriangleMesh();
        }
        Vec3D a = new Vec3D();
        Vec3D b = new Vec3D();
        Vec3D pa = new Vec3D(), pb = new Vec3D();
        Vec3D a0 = new Vec3D(), b0 = new Vec3D();
        int phiRes = function.getPhiResolutionLimit(res);
        float phiRange = function.getPhiRange();
        int thetaRes = function.getThetaResolutionLimit(res);
        float thetaRange = function.getThetaRange();
        float pres = 1f / phiRes;
        float tres = 1f / thetaRes;
        float ires = 1f / res;
        Vec2D pauv = new Vec2D();
        Vec2D pbuv = new Vec2D();
        Vec2D auv = new Vec2D();
        Vec2D buv = new Vec2D();
        for (int p = 0; p < phiRes; p++) {
            float phi = p * phiRange * ires;
            float phiNext = (p + 1) * phiRange * ires;
            for (int t = 0; t <= thetaRes; t++) {
                float theta;
                theta = t * thetaRange * ires;
                a = function.computeVertexFor(a, phiNext, theta)
                        .scaleSelf(size);
                auv.set(t * tres, 1 - (p + 1) * pres);
                b = function.computeVertexFor(b, phi, theta).scaleSelf(size);
                buv.set(t * tres, 1 - p * pres);
                if (b.equalsWithTolerance(a, 0.0001f)) {
                    b.set(a);
                }
                if (t > 0) {
                    if (t == thetaRes && isClosed) {
                        a.set(a0);
                        b.set(b0);
                    }
                    mesh.addFace(pa, pb, a, pauv.copy(), pbuv.copy(),
                            auv.copy());
                    mesh.addFace(pb, b, a, pbuv.copy(), buv.copy(), auv.copy());
                } else {
                    a0.set(a);
                    b0.set(b);
                }
                pa.set(a);
                pb.set(b);
                pauv.set(auv);
                pbuv.set(buv);
            }
        }
        return mesh;
    }

    /**
     * @return the function
     */
    public SurfaceFunction getFunction() {
        return function;
    }

    /**
     * @param function
     *            the function to set
     */
    public void setFunction(SurfaceFunction function) {
        this.function = function;
    }
}