Package toxi.geom.mesh

Source Code of toxi.geom.mesh.Terrain

/*
*   __               .__       .__  ._____.          
* _/  |_  _______  __|__| ____ |  | |__\_ |__   ______
* \   __\/  _ \  \/  /  |/ ___\|  | |  || __ \ /  ___/
*  |  | (  <_> >    <|  \  \___|  |_|  || \_\ \\___ \
*  |__|  \____/__/\_ \__|\___  >____/__||___  /____  >
*                   \/       \/             \/     \/
*
* 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.IsectData3D;
import toxi.geom.Ray3D;
import toxi.geom.Triangle3D;
import toxi.geom.TriangleIntersector;
import toxi.geom.Vec2D;
import toxi.geom.Vec3D;
import toxi.math.Interpolation2D;
import toxi.math.MathUtils;

/**
* Implementation of a 2D grid based heightfield with basic intersection
* features and conversion to {@link TriangleMesh}. The terrain is always
* located in the XZ plane with the positive Y axis as up vector.
*/
public class Terrain {

    protected float[] elevation;
    protected Vec3D[] vertices;

    protected int width;

    protected int depth;
    protected Vec2D scale;

    /**
     * Constructs a new and initially flat terrain of the given size in the XZ
     * plane, centred around the world origin.
     *
     * @param width
     * @param depth
     * @param scale
     */
    public Terrain(int width, int depth, float scale) {
        this(width, depth, new Vec2D(scale, scale));
    }

    public Terrain(int width, int depth, Vec2D scale) {
        this.width = width;
        this.depth = depth;
        this.scale = scale;
        this.elevation = new float[width * depth];
        this.vertices = new Vec3D[elevation.length];
        Vec3D offset = new Vec3D(width / 2, 0, depth / 2);
        Vec3D scaleXZ = scale.to3DXZ();
        for (int z = 0, i = 0; z < depth; z++) {
            for (int x = 0; x < width; x++) {
                vertices[i++] = new Vec3D(x, 0, z).subSelf(offset).scaleSelf(
                        scaleXZ);
            }
        }
    }

    public Terrain clear() {
        for (int i = 0; i < elevation.length; i++) {
            elevation[i] = 0;
        }
        return updateElevation();
    }

    /**
     * @return number of grid cells along the Z axis.
     */
    public int getDepth() {
        return depth;
    }

    public float[] getElevation() {
        return elevation;
    }

    /**
     * @param x
     * @param z
     * @return the elevation at grid point
     */
    public float getHeightAtCell(int x, int z) {
        return elevation[getIndex(x, z)];
    }

    /**
     * Computes the elevation of the terrain at the given 2D world coordinate
     * (based on current terrain scale).
     *
     * @param x
     *            scaled world coord x
     * @param z
     *            scaled world coord z
     * @return interpolated elevation
     */
    public float getHeightAtPoint(float x, float z) {
        float xx = x / scale.x + width * 0.5f;
        float zz = z / scale.y + depth * 0.5f;
        float y = 0;
        if (xx >= 0 && xx < width && zz >= 0 && zz < depth) {
            int x2 = (int) MathUtils.min(xx + 1, width - 1);
            int z2 = (int) MathUtils.min(zz + 1, depth - 1);
            float a = getHeightAtCell((int) xx, (int) zz);
            float b = getHeightAtCell(x2, (int) zz);
            float c = getHeightAtCell((int) xx, z2);
            float d = getHeightAtCell(x2, z2);
            y = Interpolation2D.bilinear(xx, zz, (int) xx, (int) zz, x2, z2, a,
                    b, c, d);
        }
        return y;
    }

    /**
     * Computes the array index for the given cell coords & checks if they're in
     * bounds. If not an {@link IndexOutOfBoundsException} is thrown.
     *
     * @param x
     * @param z
     * @return array index
     */
    protected final int getIndex(int x, int z) {
        int idx = z * width + x;
        if (idx < 0 || idx > elevation.length) {
            throw new IndexOutOfBoundsException(
                    "the given terrain cell is invalid: " + x + ";" + z);
        }
        return idx;
    }

    /**
     * @return the scale
     */
    public Vec2D getScale() {
        return scale;
    }

    protected Vec3D getVertexAtCell(int x, int z) {
        return vertices[getIndex(x, z)];
    }

    /**
     * @return number of grid cells along the X axis.
     */
    public int getWidth() {
        return width;
    }

    /**
     * Computes the 3D position (with elevation) and normal vector at the given
     * 2D location in the terrain. The position is in scaled world coordinates
     * based on the given terrain scale. The returned data is encapsulated in a
     * {@link toxi.geom.IsectData3D} instance.
     *
     * @param x
     * @param z
     * @return intersection data parcel
     */
    public IsectData3D intersectAtPoint(float x, float z) {
        float xx = x / scale.x + width * 0.5f;
        float zz = z / scale.y + depth * 0.5f;
        IsectData3D isec = new IsectData3D();
        if (xx >= 0 && xx < width && zz >= 0 && zz < depth) {
            int x2 = (int) MathUtils.min(xx + 1, width - 1);
            int z2 = (int) MathUtils.min(zz + 1, depth - 1);
            Vec3D a = getVertexAtCell((int) xx, (int) zz);
            Vec3D b = getVertexAtCell(x2, (int) zz);
            Vec3D c = getVertexAtCell(x2, z2);
            Vec3D d = getVertexAtCell((int) xx, z2);
            Ray3D r = new Ray3D(new Vec3D(x, 10000, z), new Vec3D(0, -1, 0));
            TriangleIntersector i = new TriangleIntersector(new Triangle3D(a,
                    b, d));
            if (i.intersectsRay(r)) {
                isec = i.getIntersectionData();
            } else {
                i.setTriangle(new Triangle3D(b, c, d));
                i.intersectsRay(r);
                isec = i.getIntersectionData();
            }
        }
        return isec;
    }

    /**
     * Sets the elevation of all cells to those of the given array values.
     *
     * @param elevation
     *            array of height values
     * @return itself
     */
    public Terrain setElevation(float[] elevation) {
        if (this.elevation.length == elevation.length) {
            this.elevation = elevation;
            updateElevation();
        } else {
            throw new IllegalArgumentException(
                    "the given elevation array size does not match existing terrain size");
        }
        return this;
    }

    /**
     * Sets the elevation for a single given grid cell.
     *
     * @param x
     * @param z
     * @param h
     *            new elevation value
     * @return itself
     */
    public Terrain setHeightAtCell(int x, int z, float h) {
        int index = getIndex(x, z);
        elevation[index] = h;
        vertices[index].y = h;
        return this;
    }

    public void setScale(float scale) {
        setScale(new Vec2D(scale, scale));
    }

    /**
     * @param scale
     *            the scale to set
     */
    public void setScale(Vec2D scale) {
        this.scale.set(scale);
        Vec3D offset = new Vec3D(width / 2, 0, depth / 2);
        for (int z = 0, i = 0; z < depth; z++) {
            for (int x = 0; x < width; x++, i++) {
                vertices[i].set((x - offset.x) * scale.x, vertices[i].y,
                        (z - offset.z) * scale.y);
            }
        }
    }

    public Mesh3D toMesh() {
        return toMesh(null);
    }

    public Mesh3D toMesh(float groundLevel) {
        return toMesh(null, groundLevel);
    }

    /**
     * Creates a {@link TriangleMesh} instance of the terrain surface or adds
     * its geometry to an existing mesh.
     *
     * @param mesh
     * @return mesh instance
     */
    public Mesh3D toMesh(Mesh3D mesh) {
        return toMesh(mesh, 0, 0, width, depth);
    }

    /**
     * Creates a {@link TriangleMesh} instance of the terrain and constructs
     * side panels and a bottom plane to form a fully enclosed mesh volume, e.g.
     * suitable for CNC fabrication or 3D printing. The bottom plane will be
     * created at the given ground level (can also be negative) and the sides
     * are extended downward to that level too.
     *
     * @param mesh
     *            existing mesh or null
     * @param groundLevel
     * @return mesh
     */
    public Mesh3D toMesh(Mesh3D mesh, float groundLevel) {
        return toMesh(mesh, 0, 0, width, depth, groundLevel);
    }

    public Mesh3D toMesh(Mesh3D mesh, int minX, int minZ, int maxX, int maxZ) {
        if (mesh == null) {
            mesh = new TriangleMesh("terrain", vertices.length,
                    vertices.length * 2);
        }
        minX = MathUtils.clip(minX, 0, width - 1);
        maxX = MathUtils.clip(maxX, 0, width);
        minZ = MathUtils.clip(minZ, 0, depth - 1);
        maxZ = MathUtils.clip(maxZ, 0, depth);
        minX++;
        minZ++;
        for (int z = minZ, idx = minX * width; z < maxZ; z++, idx += width) {
            for (int x = minX; x < maxX; x++) {
                mesh.addFace(vertices[idx - width + x - 1], vertices[idx
                        - width + x], vertices[idx + x - 1]);
                mesh.addFace(vertices[idx - width + x], vertices[idx + x],
                        vertices[idx + x - 1]);
            }
        }
        return mesh;
    }

    public Mesh3D toMesh(Mesh3D mesh, int mix, int miz, int mxx, int mxz,
            float groundLevel) {
        mesh = toMesh(mesh, mix, miz, mxx, mxz);
        mix = MathUtils.clip(mix, 0, width - 1);
        mxx = MathUtils.clip(mxx, 0, width);
        miz = MathUtils.clip(miz, 0, depth - 1);
        mxz = MathUtils.clip(mxz, 0, depth);
        Vec3D offset = new Vec3D(width, 0, depth).scaleSelf(0.5f);
        float minX = (mix - offset.x) * scale.x;
        float minZ = (miz - offset.z) * scale.y;
        float maxX = (mxx - offset.x) * scale.x;
        float maxZ = (mxz - offset.z) * scale.y;
        for (int z = miz + 1; z < mxz; z++) {
            Vec3D a = new Vec3D(minX, groundLevel, (z - 1 - offset.z) * scale.y);
            Vec3D b = new Vec3D(minX, groundLevel, (z - offset.z) * scale.y);
            // left
            mesh.addFace(getVertexAtCell(mix, z - 1), getVertexAtCell(mix, z),
                    a);
            mesh.addFace(getVertexAtCell(mix, z), b, a);
            // right
            a.x = b.x = maxX - scale.x;
            mesh.addFace(getVertexAtCell(mxx - 1, z),
                    getVertexAtCell(mxx - 1, z - 1), b);
            mesh.addFace(getVertexAtCell(mxx - 1, z - 1), a, b);
        }
        for (int x = mix + 1; x < mxx; x++) {
            Vec3D a = new Vec3D((x - 1 - offset.x) * scale.x, groundLevel, minZ);
            Vec3D b = new Vec3D((x - offset.x) * scale.x, groundLevel, minZ);
            // back
            mesh.addFace(getVertexAtCell(x, miz), getVertexAtCell(x - 1, miz),
                    b);
            mesh.addFace(getVertexAtCell(x - 1, miz), a, b);
            // front
            a.z = b.z = maxZ - scale.y;
            mesh.addFace(getVertexAtCell(x - 1, mxz - 1),
                    getVertexAtCell(x, mxz - 1), a);
            mesh.addFace(getVertexAtCell(x, mxz - 1), b, a);
        }
        // bottom plane
        for (int z = miz + 1; z < mxz; z++) {
            for (int x = mix + 1; x < mxx; x++) {
                Vec3D a = getVertexAtCell(x - 1, z - 1).copy();
                Vec3D b = getVertexAtCell(x, z - 1).copy();
                Vec3D c = getVertexAtCell(x - 1, z).copy();
                Vec3D d = getVertexAtCell(x, z).copy();
                a.y = groundLevel;
                b.y = groundLevel;
                c.y = groundLevel;
                d.y = groundLevel;
                mesh.addFace(a, c, d);
                mesh.addFace(a, d, b);
            }
        }
        return mesh;
    }

    public Terrain updateElevation() {
        for (int i = 0; i < elevation.length; i++) {
            vertices[i].y = elevation[i];
        }
        return this;
    }
}
TOP

Related Classes of toxi.geom.mesh.Terrain

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.