1
2package com.badlogic.gdx.tests.g3d;
3
4import java.nio.ByteBuffer;
5
6import com.badlogic.gdx.graphics.Color;
7import com.badlogic.gdx.graphics.Mesh;
8import com.badlogic.gdx.graphics.Pixmap;
9import com.badlogic.gdx.graphics.Pixmap.Format;
10import com.badlogic.gdx.graphics.VertexAttributes.Usage;
11import com.badlogic.gdx.graphics.VertexAttributes;
12import com.badlogic.gdx.graphics.g3d.utils.MeshBuilder;
13import com.badlogic.gdx.graphics.g3d.utils.MeshPartBuilder;
14import com.badlogic.gdx.graphics.g3d.utils.MeshPartBuilder.VertexInfo;
15import com.badlogic.gdx.math.Vector2;
16import com.badlogic.gdx.math.Vector3;
17import com.badlogic.gdx.utils.Disposable;
18import 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 */
66public 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