Home | History | Annotate | Download | only in g3d
      1 
      2 package com.badlogic.gdx.tests.g3d;
      3 
      4 import java.nio.ByteBuffer;
      5 
      6 import com.badlogic.gdx.graphics.Color;
      7 import com.badlogic.gdx.graphics.Mesh;
      8 import com.badlogic.gdx.graphics.Pixmap;
      9 import com.badlogic.gdx.graphics.Pixmap.Format;
     10 import com.badlogic.gdx.graphics.VertexAttributes.Usage;
     11 import com.badlogic.gdx.graphics.VertexAttributes;
     12 import com.badlogic.gdx.graphics.g3d.utils.MeshBuilder;
     13 import com.badlogic.gdx.graphics.g3d.utils.MeshPartBuilder;
     14 import com.badlogic.gdx.graphics.g3d.utils.MeshPartBuilder.VertexInfo;
     15 import com.badlogic.gdx.math.Vector2;
     16 import com.badlogic.gdx.math.Vector3;
     17 import com.badlogic.gdx.utils.Disposable;
     18 import com.badlogic.gdx.utils.GdxRuntimeException;
     19 
     20 /** This is a test class, showing how one could implement a height field. See also {@link HeightMapTest}. Do not expect this to be
     21  * a fully supported and implemented height field class.
     22  * <p />
     23  * Represents a HeightField, which is an evenly spaced grid of values, where each value defines the height on that position of the
     24  * grid, so forming a 3D shape. Typically used for (relatively simple) terrains and such. See <a
     25  * href="http://en.wikipedia.org/wiki/Heightmap">wikipedia</a> for more information.
     26  * <p />
     27  * A height field has a width and height, specifying the width and height of the grid. Points on this grid are specified using
     28  * integer values, named "x" and "y". Do not confuse these with the x, y and z floating point values representing coordinates in
     29  * world space.
     30  * <p />
     31  * The values of the heightfield are normalized. Meaning that they typically range from 0 to 1 (but they can be negative or more
     32  * than one). The plane of the heightfield can be specified using the {@link #corner00}, {@link #corner01}, {@link #corner10} and
     33  * {@link #corner11} members. Where `corner00` is the location on the grid at x:0, y;0, `corner01` at x:0, y:height-1, `corner10`
     34  * at x:width-1, y:0 and `corner11` the location on the grid at x:width-1, y:height-1.
     35  * <p />
     36  * The height and direction of the field can be set using the {@link #magnitude} vector. Typically this should be the vector
     37  * perpendicular to the heightfield. E.g. if the field is on the XZ plane, then the magnitude is typically pointing on the Y axis.
     38  * The length of the `magnitude` specifies the height of the height field. In other words, the word coordinate of a point on the
     39  * grid is specified as:
     40  * <p />
     41  * base[y * width + x] + magnitude * value[y * width + x]
     42  * <p />
     43  * Use the {@link #getPositionAt(Vector3, int, int)} method to get the coordinate of a specific point on the grid.
     44  * <p />
     45  * You can set this heightfield using the constructor or one of the `set` methods. E.g. by specifying an array of values or a
     46  * {@link Pixmap}. The latter can be used to load a HeightMap, which is an image loaded from disc of which each texel is used to
     47  * specify the value for each point on the field. Be aware that the total number of vertices cannot exceed 32k. Using a large
     48  * height map will result in unpredicted results.
     49  * <p />
     50  * You can also manually modify the heightfield by directly accessing the {@link #data} member. The index within this array can be
     51  * calculates as: `y * width + x`. E.g. `field.data[y * field.width + x] = value;`. When you modify the data then you can update
     52  * the {@link #mesh} using the {@link #update()} method.
     53  * <p />
     54  * The {@link #mesh} member can be used to render the height field. The vertex attributes this mesh contains are specified in the
     55  * constructor. There are two ways for generating the mesh: smooth and sharp.
     56  * <p />
     57  * Smooth can be forced by specifying `true` for the `smooth` argument of the constructor. Otherwise it will be based on whether
     58  * the specified vertex attributes contains a normal attribute. If there is no normal attribute then the mesh will always be
     59  * smooth (even when you specify `false` in the constructor). In this case the number of vertices is the same as the amount of
     60  * grid points. Causing vertices to be shared amongst multiple faces.
     61  * <p />
     62  * Sharp will be used if the vertex attributes contains a normal attribute and you didnt specify `true` for the `smooth` argument
     63  * of the constructor. This will cause the number of vertices to be around four times the amount grid points and each normal is
     64  * estimated for each face instead of each point.
     65  * @author Xoppa */
     66 public class HeightField implements Disposable {
     67 	public final Vector2 uvOffset = new Vector2(0, 0);
     68 	public final Vector2 uvScale = new Vector2(1, 1);
     69 	public final Color color00 = new Color(Color.WHITE);
     70 	public final Color color10 = new Color(Color.WHITE);
     71 	public final Color color01 = new Color(Color.WHITE);
     72 	public final Color color11 = new Color(Color.WHITE);
     73 	public final Vector3 corner00 = new Vector3(0, 0, 0);
     74 	public final Vector3 corner10 = new Vector3(1, 0, 0);
     75 	public final Vector3 corner01 = new Vector3(0, 0, 1);
     76 	public final Vector3 corner11 = new Vector3(1, 0, 1);
     77 	public final Vector3 magnitude = new Vector3(0, 1, 0);
     78 
     79 	public final float[] data;
     80 	public final int width;
     81 	public final int height;
     82 	public final boolean smooth;
     83 	public final Mesh mesh;
     84 
     85 	private final float vertices[];
     86 	private final int stride;
     87 
     88 	private final int posPos;
     89 	private final int norPos;
     90 	private final int uvPos;
     91 	private final int colPos;
     92 
     93 	private final MeshPartBuilder.VertexInfo vertex00 = new MeshPartBuilder.VertexInfo();
     94 	private final MeshPartBuilder.VertexInfo vertex10 = new MeshPartBuilder.VertexInfo();
     95 	private final MeshPartBuilder.VertexInfo vertex01 = new MeshPartBuilder.VertexInfo();
     96 	private final MeshPartBuilder.VertexInfo vertex11 = new MeshPartBuilder.VertexInfo();
     97 
     98 	private final Vector3 tmpV1 = new Vector3();
     99 	private final Vector3 tmpV2 = new Vector3();
    100 	private final Vector3 tmpV3 = new Vector3();
    101 	private final Vector3 tmpV4 = new Vector3();
    102 	private final Vector3 tmpV5 = new Vector3();
    103 	private final Vector3 tmpV6 = new Vector3();
    104 	private final Vector3 tmpV7 = new Vector3();
    105 	private final Vector3 tmpV8 = new Vector3();
    106 	private final Vector3 tmpV9 = new Vector3();
    107 	private final Color tmpC = new Color();
    108 
    109 	public HeightField (boolean isStatic, final Pixmap map, boolean smooth, int attributes) {
    110 		this(isStatic, map.getWidth(), map.getHeight(), smooth, attributes);
    111 		set(map);
    112 	}
    113 
    114 	public HeightField (boolean isStatic, final ByteBuffer colorData, final Pixmap.Format format, int width, int height,
    115 		boolean smooth, int attributes) {
    116 		this(isStatic, width, height, smooth, attributes);
    117 		set(colorData, format);
    118 	}
    119 
    120 	public HeightField (boolean isStatic, final float[] data, int width, int height, boolean smooth, int attributes) {
    121 		this(isStatic, width, height, smooth, attributes);
    122 		set(data);
    123 	}
    124 
    125 	public HeightField (boolean isStatic, int width, int height, boolean smooth, int attributes) {
    126 		this(isStatic, width, height, smooth, MeshBuilder.createAttributes(attributes));
    127 	}
    128 
    129 	public HeightField (boolean isStatic, int width, int height, boolean smooth, VertexAttributes attributes) {
    130 		this.posPos = attributes.getOffset(Usage.Position, -1);
    131 		this.norPos = attributes.getOffset(Usage.Normal, -1);
    132 		this.uvPos = attributes.getOffset(Usage.TextureCoordinates, -1);
    133 		this.colPos = attributes.getOffset(Usage.ColorUnpacked, -1);
    134 		smooth = smooth || (norPos < 0); // cant have sharp edges without normals
    135 
    136 		this.width = width;
    137 		this.height = height;
    138 		this.smooth = smooth;
    139 		this.data = new float[width * height];
    140 
    141 		this.stride = attributes.vertexSize / 4;
    142 
    143 		final int numVertices = smooth ? width * height : (width - 1) * (height - 1) * 4;
    144 		final int numIndices = (width - 1) * (height - 1) * 6;
    145 
    146 		this.mesh = new Mesh(isStatic, numVertices, numIndices, attributes);
    147 		this.vertices = new float[numVertices * stride];
    148 
    149 		setIndices();
    150 	}
    151 
    152 	private void setIndices () {
    153 		final int w = width - 1;
    154 		final int h = height - 1;
    155 		short indices[] = new short[w * h * 6];
    156 		int i = -1;
    157 		for (int y = 0; y < h; ++y) {
    158 			for (int x = 0; x < w; ++x) {
    159 				final int c00 = smooth ? (y * width + x) : (y * 2 * w + x * 2);
    160 				final int c10 = c00 + 1;
    161 				final int c01 = c00 + (smooth ? width : w * 2);
    162 				final int c11 = c10 + (smooth ? width : w * 2);
    163 				indices[++i] = (short)c11;
    164 				indices[++i] = (short)c10;
    165 				indices[++i] = (short)c00;
    166 				indices[++i] = (short)c00;
    167 				indices[++i] = (short)c01;
    168 				indices[++i] = (short)c11;
    169 			}
    170 		}
    171 		mesh.setIndices(indices);
    172 	}
    173 
    174 	public void update () {
    175 		if (smooth) {
    176 			if (norPos < 0)
    177 				updateSimple();
    178 			else
    179 				updateSmooth();
    180 		} else
    181 			updateSharp();
    182 	}
    183 
    184 	private void updateSmooth () {
    185 		for (int x = 0; x < width; ++x) {
    186 			for (int y = 0; y < height; ++y) {
    187 				VertexInfo v = getVertexAt(vertex00, x, y);
    188 				getWeightedNormalAt(v.normal, x, y);
    189 				setVertex(y * width + x, v);
    190 			}
    191 		}
    192 		mesh.setVertices(vertices);
    193 	}
    194 
    195 	private void updateSimple () {
    196 		for (int x = 0; x < width; ++x) {
    197 			for (int y = 0; y < height; ++y) {
    198 				setVertex(y * width + x, getVertexAt(vertex00, x, y));
    199 			}
    200 		}
    201 		mesh.setVertices(vertices);
    202 	}
    203 
    204 	private void updateSharp () {
    205 		final int w = width - 1;
    206 		final int h = height - 1;
    207 		for (int y = 0; y < h; ++y) {
    208 			for (int x = 0; x < w; ++x) {
    209 				final int c00 = (y * 2 * w + x * 2);
    210 				final int c10 = c00 + 1;
    211 				final int c01 = c00 + w * 2;
    212 				final int c11 = c10 + w * 2;
    213 				VertexInfo v00 = getVertexAt(vertex00, x, y);
    214 				VertexInfo v10 = getVertexAt(vertex10, x + 1, y);
    215 				VertexInfo v01 = getVertexAt(vertex01, x, y + 1);
    216 				VertexInfo v11 = getVertexAt(vertex11, x + 1, y + 1);
    217 				v01.normal.set(v01.position).sub(v00.position).nor().crs(tmpV1.set(v11.position).sub(v01.position).nor());
    218 				v10.normal.set(v10.position).sub(v11.position).nor().crs(tmpV1.set(v00.position).sub(v10.position).nor());
    219 				v00.normal.set(v01.normal).lerp(v10.normal, .5f);
    220 				v11.normal.set(v00.normal);
    221 
    222 				setVertex(c00, v00);
    223 				setVertex(c10, v10);
    224 				setVertex(c01, v01);
    225 				setVertex(c11, v11);
    226 			}
    227 		}
    228 		mesh.setVertices(vertices);
    229 	}
    230 
    231 	/** Does not set the normal member! */
    232 	protected VertexInfo getVertexAt (final VertexInfo out, int x, int y) {
    233 		final float dx = (float)x / (float)(width - 1);
    234 		final float dy = (float)y / (float)(height - 1);
    235 		final float a = data[y * width + x];
    236 		out.position.set(corner00).lerp(corner10, dx).lerp(tmpV1.set(corner01).lerp(corner11, dx), dy);
    237 		out.position.add(tmpV1.set(magnitude).scl(a));
    238 		out.color.set(color00).lerp(color10, dx).lerp(tmpC.set(color01).lerp(color11, dx), dy);
    239 		out.uv.set(dx, dy).scl(uvScale).add(uvOffset);
    240 		return out;
    241 	}
    242 
    243 	public Vector3 getPositionAt (Vector3 out, int x, int y) {
    244 		final float dx = (float)x / (float)(width - 1);
    245 		final float dy = (float)y / (float)(height - 1);
    246 		final float a = data[y * width + x];
    247 		out.set(corner00).lerp(corner10, dx).lerp(tmpV1.set(corner01).lerp(corner11, dx), dy);
    248 		out.add(tmpV1.set(magnitude).scl(a));
    249 		return out;
    250 	}
    251 
    252 	public Vector3 getWeightedNormalAt (Vector3 out, int x, int y) {
    253 // This commented code is based on http://www.flipcode.com/archives/Calculating_Vertex_Normals_for_Height_Maps.shtml
    254 // Note that this approach only works for a heightfield on the XZ plane with a magnitude on the y axis
    255 // float sx = data[(x < width - 1 ? x + 1 : x) + y * width] + data[(x > 0 ? x-1 : x) + y * width];
    256 // if (x == 0 || x == (width - 1))
    257 // sx *= 2f;
    258 // float sy = data[(y < height - 1 ? y + 1 : y) * width + x] + data[(y > 0 ? y-1 : y) * width + x];
    259 // if (y == 0 || y == (height - 1))
    260 // sy *= 2f;
    261 // float xScale = (corner11.x - corner00.x) / (width - 1f);
    262 // float zScale = (corner11.z - corner00.z) / (height - 1f);
    263 // float yScale = magnitude.len();
    264 // out.set(-sx * yScale, 2f * xScale, sy*yScale*xScale / zScale).nor();
    265 // return out;
    266 
    267 // The following approach weights the normal of the four triangles (half quad) surrounding the position.
    268 // A more accurate approach would be to weight the normal of the actual triangles.
    269 		int faces = 0;
    270 		out.set(0, 0, 0);
    271 
    272 		Vector3 center = getPositionAt(tmpV2, x, y);
    273 		Vector3 left = x > 0 ? getPositionAt(tmpV3, x - 1, y) : null;
    274 		Vector3 right = x < (width - 1) ? getPositionAt(tmpV4, x + 1, y) : null;
    275 		Vector3 bottom = y > 0 ? getPositionAt(tmpV5, x, y - 1) : null;
    276 		Vector3 top = y < (height - 1) ? getPositionAt(tmpV6, x, y + 1) : null;
    277 		if (top != null && left != null) {
    278 			out.add(tmpV7.set(top).sub(center).nor().crs(tmpV8.set(center).sub(left).nor()).nor());
    279 			faces++;
    280 		}
    281 		if (left != null && bottom != null) {
    282 			out.add(tmpV7.set(left).sub(center).nor().crs(tmpV8.set(center).sub(bottom).nor()).nor());
    283 			faces++;
    284 		}
    285 		if (bottom != null && right != null) {
    286 			out.add(tmpV7.set(bottom).sub(center).nor().crs(tmpV8.set(center).sub(right).nor()).nor());
    287 			faces++;
    288 		}
    289 		if (right != null && top != null) {
    290 			out.add(tmpV7.set(right).sub(center).nor().crs(tmpV8.set(center).sub(top).nor()).nor());
    291 			faces++;
    292 		}
    293 		if (faces != 0)
    294 			out.scl(1f / (float)faces);
    295 		else
    296 			out.set(magnitude).nor();
    297 		return out;
    298 	}
    299 
    300 	protected void setVertex (int index, VertexInfo info) {
    301 		index *= stride;
    302 		if (posPos >= 0) {
    303 			vertices[index + posPos + 0] = info.position.x;
    304 			vertices[index + posPos + 1] = info.position.y;
    305 			vertices[index + posPos + 2] = info.position.z;
    306 		}
    307 		if (norPos >= 0) {
    308 			vertices[index + norPos + 0] = info.normal.x;
    309 			vertices[index + norPos + 1] = info.normal.y;
    310 			vertices[index + norPos + 2] = info.normal.z;
    311 		}
    312 		if (uvPos >= 0) {
    313 			vertices[index + uvPos + 0] = info.uv.x;
    314 			vertices[index + uvPos + 1] = info.uv.y;
    315 		}
    316 		if (colPos >= 0) {
    317 			vertices[index + colPos + 0] = info.color.r;
    318 			vertices[index + colPos + 1] = info.color.g;
    319 			vertices[index + colPos + 2] = info.color.b;
    320 			vertices[index + colPos + 3] = info.color.a;
    321 		}
    322 	}
    323 
    324 	public void set (final Pixmap map) {
    325 		if (map.getWidth() != width || map.getHeight() != height) throw new GdxRuntimeException("Incorrect map size");
    326 		set(map.getPixels(), map.getFormat());
    327 	}
    328 
    329 	public void set (final ByteBuffer colorData, final Pixmap.Format format) {
    330 		set(heightColorsToMap(colorData, format, width, height));
    331 	}
    332 
    333 	public void set (float[] data) {
    334 		set(data, 0);
    335 	}
    336 
    337 	public void set (float[] data, int offset) {
    338 		if (this.data.length > (data.length - offset)) throw new GdxRuntimeException("Incorrect data size");
    339 		System.arraycopy(data, offset, this.data, 0, this.data.length);
    340 		update();
    341 	}
    342 
    343 	@Override
    344 	public void dispose () {
    345 		mesh.dispose();
    346 	}
    347 
    348 	/** Simply creates an array containing only all the red components of the data. */
    349 	public static float[] heightColorsToMap (final ByteBuffer data, final Pixmap.Format format, int width, int height) {
    350 		final int bytesPerColor = (format == Format.RGB888 ? 3 : (format == Format.RGBA8888 ? 4 : 0));
    351 		if (bytesPerColor == 0) throw new GdxRuntimeException("Unsupported format, should be either RGB8 or RGBA8");
    352 		if (data.remaining() < (width * height * bytesPerColor)) throw new GdxRuntimeException("Incorrect map size");
    353 
    354 		final int startPos = data.position();
    355 		byte[] source = null;
    356 		int sourceOffset = 0;
    357 		if (data.hasArray() && !data.isReadOnly()) {
    358 			source = data.array();
    359 			sourceOffset = data.arrayOffset() + startPos;
    360 		} else {
    361 			source = new byte[width * height * bytesPerColor];
    362 			data.get(source);
    363 			data.position(startPos);
    364 		}
    365 
    366 		float[] dest = new float[width * height];
    367 		for (int i = 0; i < dest.length; ++i) {
    368 			int v = source[sourceOffset + i * bytesPerColor];
    369 			v = v < 0 ? 256 + v : v;
    370 			dest[i] = (float)v / 255f;
    371 		}
    372 
    373 		return dest;
    374 	}
    375 }
    376