package com.badlogic.gdx.graphics.g3d.shaders.graph;
+import java.util.Arrays;
+
import com.badlogic.gdx.graphics.g3d.shaders.graph.ShaderNode.ShaderNodeType;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.GdxRuntimeException;
public boolean forwardTarget = true;
/** Whether to update the target on scroll */
public boolean scrollTarget = false;
- public int forwardKey = Keys.UP;
+ public int forwardKey = Keys.W;
protected boolean forwardPressed;
- public int backwardKey = Keys.DOWN;
+ public int backwardKey = Keys.S;
protected boolean backwardPressed;
- public int rotateRightKey = Keys.RIGHT;
+ public int rotateRightKey = Keys.A;
protected boolean rotateRightPressed;
- public int rotateLeftKey = Keys.LEFT;
+ public int rotateLeftKey = Keys.D;
protected boolean rotateLeftPressed;
/** The camera. */
public Camera camera;
--- /dev/null
+package com.badlogic.gdx.graphics.g3d.utils;
+
+import com.badlogic.gdx.Gdx;
+import com.badlogic.gdx.Input.Keys;
+import com.badlogic.gdx.InputAdapter;
+import com.badlogic.gdx.graphics.Camera;
+import com.badlogic.gdx.math.Vector3;
+import com.badlogic.gdx.utils.IntIntMap;
+
+/**
+ * Takes a {@link Camera} instance and controlls it via w,a,s,d and
+ * mouse panning.
+ * @author badlogic
+ *
+ */
+public class FirstPersonCameraController extends InputAdapter {
+ private final Camera camera;
+ private final IntIntMap keys = new IntIntMap();
+ private int STRAFE_LEFT = Keys.A;
+ private int STRAFE_RIGHT = Keys.D;
+ private int FORWARD = Keys.W;
+ private int BACKWARD = Keys.S;
+ private int UP = Keys.Q;
+ private int DOWN = Keys.E;
+ private float velocity = 5;
+ private float degreesPerPixel = 0.5f;
+ private final Vector3 tmp = new Vector3();
+ private final Vector3 tmp2 = new Vector3();
+
+ public FirstPersonCameraController(Camera camera) {
+ this.camera = camera;
+ }
+
+ @Override
+ public boolean keyDown (int keycode) {
+ keys.put(keycode, keycode);
+ return true;
+ }
+
+ @Override
+ public boolean keyUp (int keycode) {
+ keys.remove(keycode, 0);
+ return true;
+ }
+
+ /**
+ * Sets the velocity in units per second for moving forward, backward and strafing left/right.
+ * @param velocity the velocity in units per second
+ */
+ public void setVelocity(float velocity) {
+ this.velocity = velocity;
+ }
+
+ /**
+ * Sets how many degrees to rotate per pixel the mouse moved.
+ * @param degreesPerPixel
+ */
+ public void setDegreesPerPixel(float degreesPerPixel) {
+ this.degreesPerPixel = degreesPerPixel;
+ }
+
+ @Override
+ public boolean touchDragged (int screenX, int screenY, int pointer) {
+ float deltaX = -Gdx.input.getDeltaX() * degreesPerPixel;
+ float deltaY = -Gdx.input.getDeltaY() * degreesPerPixel;
+ camera.direction.rotate(camera.up, deltaX);
+ tmp.set(camera.direction).crs(camera.up).nor();
+ camera.direction.rotate(tmp, deltaY);
+// camera.up.rotate(tmp, deltaY);
+ return true;
+ }
+
+ public void update() {
+ update(Gdx.graphics.getDeltaTime());
+ }
+
+ public void update(float deltaTime) {
+ if(keys.containsKey(FORWARD)) {
+ tmp.set(camera.direction).nor().scl(deltaTime * velocity);
+ camera.position.add(tmp);
+ }
+ if(keys.containsKey(BACKWARD)) {
+ tmp.set(camera.direction).nor().scl(-deltaTime * velocity);
+ camera.position.add(tmp);
+ }
+ if(keys.containsKey(STRAFE_LEFT)) {
+ tmp.set(camera.direction).crs(camera.up).nor().scl(-deltaTime * velocity);
+ camera.position.add(tmp);
+ }
+ if(keys.containsKey(STRAFE_RIGHT)) {
+ tmp.set(camera.direction).crs(camera.up).nor().scl(deltaTime * velocity);
+ camera.position.add(tmp);
+ }
+ if(keys.containsKey(UP)) {
+ tmp.set(camera.up).nor().scl(deltaTime * velocity);
+ camera.position.add(tmp);
+ }
+ if(keys.containsKey(DOWN)) {
+ tmp.set(camera.up).nor().scl(-deltaTime * velocity);
+ camera.position.add(tmp);
+ }
+ camera.update(true);
+ }
+}
import com.badlogic.gdx.tests.g3d.Basic3DTest;\r
import com.badlogic.gdx.tests.g3d.ModelLoaderTest;\r
import com.badlogic.gdx.tests.g3d.ModelTest;\r
+import com.badlogic.gdx.tests.g3d.voxel.VoxelTest;\r
import com.badlogic.gdx.tests.utils.GdxTest;\r
\r
public class LwjglDebugStarter {\r
// new SharedLibraryLoader("../../extensions/gdx-controllers/gdx-controllers-desktop/libs/gdx-controllers-desktop-natives.jar").load("gdx-controllers-desktop");\r
// new SharedLibraryLoader("../../gdx/libs/gdx-natives.jar").load("gdx");\r
\r
- GdxTest test = new MeshShaderTest();\r
+ GdxTest test = new VoxelTest();\r
LwjglApplicationConfiguration config = new LwjglApplicationConfiguration();\r
config.useGL20 = test.needsGL20();\r
config.width = 1024;\r
--- /dev/null
+
+package com.badlogic.gdx.tests.g3d.voxel;
+
+import com.badlogic.gdx.graphics.Color;
+import com.badlogic.gdx.graphics.Pixmap;
+import com.badlogic.gdx.graphics.Pixmap.Format;
+import com.badlogic.gdx.math.MathUtils;
+
+/** Adapted from <a href="http://devmag.org.za/2009/04/25/perlin-noise/">http://devmag.org.za/2009/04/25/perlin-noise/</a>
+ * @author badlogic */
+public class PerlinNoiseGenerator {
+ public static float[][] generateWhiteNoise (int width, int height) {
+ float[][] noise = new float[width][height];
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ noise[x][y] = MathUtils.random();
+ }
+ }
+ return noise;
+ }
+
+ public static float interpolate (float x0, float x1, float alpha) {
+ return x0 * (1 - alpha) + alpha * x1;
+ }
+
+ public static float[][] generateSmoothNoise (float[][] baseNoise, int octave) {
+ int width = baseNoise.length;
+ int height = baseNoise[0].length;
+ float[][] smoothNoise = new float[width][height];
+
+ int samplePeriod = 1 << octave; // calculates 2 ^ k
+ float sampleFrequency = 1.0f / samplePeriod;
+ for (int i = 0; i < width; i++) {
+ int sample_i0 = (i / samplePeriod) * samplePeriod;
+ int sample_i1 = (sample_i0 + samplePeriod) % width; // wrap around
+ float horizontal_blend = (i - sample_i0) * sampleFrequency;
+
+ for (int j = 0; j < height; j++) {
+ int sample_j0 = (j / samplePeriod) * samplePeriod;
+ int sample_j1 = (sample_j0 + samplePeriod) % height; // wrap around
+ float vertical_blend = (j - sample_j0) * sampleFrequency;
+ float top = interpolate(baseNoise[sample_i0][sample_j0], baseNoise[sample_i1][sample_j0], horizontal_blend);
+ float bottom = interpolate(baseNoise[sample_i0][sample_j1], baseNoise[sample_i1][sample_j1], horizontal_blend);
+ smoothNoise[i][j] = interpolate(top, bottom, vertical_blend);
+ }
+ }
+
+ return smoothNoise;
+ }
+
+ public static float[][] generatePerlinNoise (float[][] baseNoise, int octaveCount) {
+ int width = baseNoise.length;
+ int height = baseNoise[0].length;
+ float[][][] smoothNoise = new float[octaveCount][][]; // an array of 2D arrays containing
+ float persistance = 0.7f;
+
+ for (int i = 0; i < octaveCount; i++) {
+ smoothNoise[i] = generateSmoothNoise(baseNoise, i);
+ }
+
+ float[][] perlinNoise = new float[width][height]; // an array of floats initialised to 0
+
+ float amplitude = 1.0f;
+ float totalAmplitude = 0.0f;
+
+ for (int octave = octaveCount - 1; octave >= 0; octave--) {
+ amplitude *= persistance;
+ totalAmplitude += amplitude;
+
+ for (int i = 0; i < width; i++) {
+ for (int j = 0; j < height; j++) {
+ perlinNoise[i][j] += smoothNoise[octave][i][j] * amplitude;
+ }
+ }
+ }
+
+ for (int i = 0; i < width; i++) {
+ for (int j = 0; j < height; j++) {
+ perlinNoise[i][j] /= totalAmplitude;
+ }
+ }
+
+ return perlinNoise;
+ }
+
+ public static float[][] generatePerlinNoise (int width, int height, int octaveCount) {
+ float[][] baseNoise = generateWhiteNoise(width, height);
+ return generatePerlinNoise(baseNoise, octaveCount);
+ }
+
+ public static byte[] generateHeightMap(int width, int height, int min, int max, int octaveCount) {
+ float[][] baseNoise = generateWhiteNoise(width, height);
+ float[][] noise = generatePerlinNoise(baseNoise, octaveCount);
+ byte[] bytes = new byte[baseNoise.length * baseNoise[0].length];
+ int idx = 0;
+ int range = max - min;
+ for(int y = 0; y < height; y++) {
+ for(int x = 0; x < width; x++) {
+ bytes[idx++] = (byte)(noise[x][y] * range + min);
+ }
+ }
+ return bytes;
+ }
+
+ public static Pixmap generatePixmap(int width, int height, int min, int max, int octaveCount) {
+ byte[] bytes = generateHeightMap(width, height, min, max, octaveCount);
+ Pixmap pixmap = new Pixmap(width, height, Format.RGBA8888);
+ for(int i = 0, idx = 0; i < bytes.length; i++) {
+ byte val = bytes[i];
+ pixmap.getPixels().put(idx++, val);
+ pixmap.getPixels().put(idx++, val);
+ pixmap.getPixels().put(idx++, val);
+ pixmap.getPixels().put(idx++, (byte)255);
+ }
+ return pixmap;
+ }
+
+ public static void generateVoxels(VoxelWorld voxelWorld, int min, int max, int octaveCount) {
+ byte[] heightMap = PerlinNoiseGenerator.generateHeightMap(voxelWorld.voxelsX, voxelWorld.voxelsZ, min, max, 8);
+ int idx = 0;
+ for(int z = 0; z < voxelWorld.voxelsZ; z++) {
+ for(int x = 0; x < voxelWorld.voxelsX; x++) {
+ voxelWorld.setColumn(x, heightMap[idx++], z, (byte)1);
+// voxelWorld.set(x, heightMap[idx++], z, (byte)1);
+ }
+ }
+ }
+}
--- /dev/null
+package com.badlogic.gdx.tests.g3d.voxel;
+
+import com.badlogic.gdx.math.Vector3;
+
+public class VoxelChunk {
+ public static final int VERTEX_SIZE = 6;
+ public final byte[] voxels;
+ public final int width;
+ public final int height;
+ public final int depth;
+ public final Vector3 offset = new Vector3();
+ private final int widthTimesHeight;
+ private final int topOffset;
+ private final int bottomOffset;
+ private final int leftOffset;
+ private final int rightOffset;
+ private final int frontOffset;
+ private final int backOffset;
+
+ public VoxelChunk(int width, int height, int depth) {
+ this.voxels = new byte[width * height * depth];
+ this.width = width;
+ this.height = height;
+ this.depth = depth;
+ this.topOffset = width * depth;
+ this.bottomOffset = -width * depth;
+ this.leftOffset = -1;
+ this.rightOffset = 1;
+ this.frontOffset = - width;
+ this.backOffset = width;
+ this.widthTimesHeight = width * height;
+ }
+
+ public byte get(int x, int y, int z) {
+ if(x < 0 || x >= width) return 0;
+ if(y < 0 || y >= height) return 0;
+ if(z < 0 || z >= depth) return 0;
+ return getFast(x, y, z);
+ }
+
+ public byte getFast(int x, int y, int z) {
+ return voxels[x + z * width + y * widthTimesHeight];
+ }
+
+ public void set(int x, int y, int z, byte voxel) {
+ if(x < 0 || x >= width) return;
+ if(y < 0 || y >= height) return;
+ if(z < 0 || z >= depth) return;
+ setFast(x, y, z, voxel);
+ }
+
+ public void setFast(int x, int y, int z, byte voxel) {
+ voxels[x + z * width + y * widthTimesHeight] = voxel;
+ }
+
+ /**
+ * Creates a mesh out of the chunk, returning the number of
+ * indices produced
+ * @return the number of vertices produced
+ */
+ public int calculateVertices(float[] vertices) {
+ int i = 0;
+ int vertexOffset = 0;
+ for(int y = 0; y < height; y++) {
+ for(int z = 0; z < depth; z++) {
+ for(int x = 0; x < width; x++, i++) {
+ byte voxel = voxels[i];
+ if(voxel == 0) continue;
+
+ if(y < height - 1) {
+ if(voxels[i+topOffset] == 0) vertexOffset = createTop(offset, x, y, z, vertices, vertexOffset);
+ } else {
+ vertexOffset = createTop(offset, x, y, z, vertices, vertexOffset);
+ }
+ if(y > 0) {
+ if(voxels[i+bottomOffset] == 0) vertexOffset = createBottom(offset, x, y, z, vertices, vertexOffset);
+ } else {
+ vertexOffset = createBottom(offset, x, y, z, vertices, vertexOffset);
+ }
+ if(x > 0) {
+ if(voxels[i+leftOffset] == 0) vertexOffset = createLeft(offset, x, y, z, vertices, vertexOffset);
+ } else {
+ vertexOffset = createLeft(offset, x, y, z, vertices, vertexOffset);
+ }
+ if(x < width - 1) {
+ if(voxels[i+rightOffset] == 0) vertexOffset = createRight(offset, x, y, z, vertices, vertexOffset);
+ } else {
+ vertexOffset = createRight(offset, x, y, z, vertices, vertexOffset);
+ }
+ if(z > 0) {
+ if(voxels[i+frontOffset] == 0) vertexOffset = createFront(offset, x, y, z, vertices, vertexOffset);
+ } else {
+ vertexOffset = createFront(offset, x, y, z, vertices, vertexOffset);
+ }
+ if(z < depth - 1) {
+ if(voxels[i+backOffset] == 0) vertexOffset = createBack(offset, x, y, z, vertices, vertexOffset);
+ } else {
+ vertexOffset = createBack(offset, x, y, z, vertices, vertexOffset);
+ }
+ }
+ }
+ }
+ return vertexOffset / VERTEX_SIZE;
+ }
+
+ public static int createTop(Vector3 offset, int x, int y, int z, float[] vertices, int vertexOffset) {
+ vertices[vertexOffset++] = offset.x + x;
+ vertices[vertexOffset++] = offset.y + y + 1;
+ vertices[vertexOffset++] = offset.z + z;
+ vertices[vertexOffset++] = 0;
+ vertices[vertexOffset++] = 1;
+ vertices[vertexOffset++] = 0;
+
+ vertices[vertexOffset++] = offset.x + x + 1;
+ vertices[vertexOffset++] = offset.y + y + 1;
+ vertices[vertexOffset++] = offset.z + z;
+ vertices[vertexOffset++] = 0;
+ vertices[vertexOffset++] = 1;
+ vertices[vertexOffset++] = 0;
+
+ vertices[vertexOffset++] = offset.x + x + 1;
+ vertices[vertexOffset++] = offset.y + y + 1;
+ vertices[vertexOffset++] = offset.z + z + 1;
+ vertices[vertexOffset++] = 0;
+ vertices[vertexOffset++] = 1;
+ vertices[vertexOffset++] = 0;
+
+ vertices[vertexOffset++] = offset.x + x;
+ vertices[vertexOffset++] = offset.y + y + 1;
+ vertices[vertexOffset++] = offset.z + z + 1;
+ vertices[vertexOffset++] = 0;
+ vertices[vertexOffset++] = 1;
+ vertices[vertexOffset++] = 0;
+ return vertexOffset;
+ }
+
+ public static int createBottom(Vector3 offset, int x, int y, int z, float[] vertices, int vertexOffset) {
+ vertices[vertexOffset++] = offset.x + x;
+ vertices[vertexOffset++] = offset.y + y;
+ vertices[vertexOffset++] = offset.z + z;
+ vertices[vertexOffset++] = 0;
+ vertices[vertexOffset++] = -1;
+ vertices[vertexOffset++] = 0;
+
+ vertices[vertexOffset++] = offset.x + x;
+ vertices[vertexOffset++] = offset.y + y;
+ vertices[vertexOffset++] = offset.z + z + 1;
+ vertices[vertexOffset++] = 0;
+ vertices[vertexOffset++] = -1;
+ vertices[vertexOffset++] = 0;
+
+ vertices[vertexOffset++] = offset.x + x + 1;
+ vertices[vertexOffset++] = offset.y + y;
+ vertices[vertexOffset++] = offset.z + z + 1;
+ vertices[vertexOffset++] = 0;
+ vertices[vertexOffset++] = -1;
+ vertices[vertexOffset++] = 0;
+
+ vertices[vertexOffset++] = offset.x + x + 1;
+ vertices[vertexOffset++] = offset.y + y;
+ vertices[vertexOffset++] = offset.z + z;
+ vertices[vertexOffset++] = 0;
+ vertices[vertexOffset++] = -1;
+ vertices[vertexOffset++] = 0;
+ return vertexOffset;
+ }
+
+ public static int createLeft(Vector3 offset, int x, int y, int z, float[] vertices, int vertexOffset) {
+ vertices[vertexOffset++] = offset.x + x;
+ vertices[vertexOffset++] = offset.y + y;
+ vertices[vertexOffset++] = offset.z + z;
+ vertices[vertexOffset++] = -1;
+ vertices[vertexOffset++] = 0;
+ vertices[vertexOffset++] = 0;
+
+ vertices[vertexOffset++] = offset.x + x;
+ vertices[vertexOffset++] = offset.y + y + 1;
+ vertices[vertexOffset++] = offset.z + z;
+ vertices[vertexOffset++] = -1;
+ vertices[vertexOffset++] = 0;
+ vertices[vertexOffset++] = 0;
+
+ vertices[vertexOffset++] = offset.x + x;
+ vertices[vertexOffset++] = offset.y + y + 1;
+ vertices[vertexOffset++] = offset.z + z + 1;
+ vertices[vertexOffset++] = -1;
+ vertices[vertexOffset++] = 0;
+ vertices[vertexOffset++] = 0;
+
+ vertices[vertexOffset++] = offset.x + x;
+ vertices[vertexOffset++] = offset.y + y;
+ vertices[vertexOffset++] = offset.z + z + 1;
+ vertices[vertexOffset++] = -1;
+ vertices[vertexOffset++] = 0;
+ vertices[vertexOffset++] = 0;
+ return vertexOffset;
+ }
+
+ public static int createRight(Vector3 offset, int x, int y, int z, float[] vertices, int vertexOffset) {
+ vertices[vertexOffset++] = offset.x + x + 1;
+ vertices[vertexOffset++] = offset.y + y;
+ vertices[vertexOffset++] = offset.z + z;
+ vertices[vertexOffset++] = 0;
+ vertices[vertexOffset++] = 1;
+ vertices[vertexOffset++] = 0;
+
+ vertices[vertexOffset++] = offset.x + x + 1;
+ vertices[vertexOffset++] = offset.y + y;
+ vertices[vertexOffset++] = offset.z + z + 1;
+ vertices[vertexOffset++] = 0;
+ vertices[vertexOffset++] = 1;
+ vertices[vertexOffset++] = 0;
+
+ vertices[vertexOffset++] = offset.x + x + 1;
+ vertices[vertexOffset++] = offset.y + y + 1;
+ vertices[vertexOffset++] = offset.z + z + 1;
+ vertices[vertexOffset++] = 0;
+ vertices[vertexOffset++] = 1;
+ vertices[vertexOffset++] = 0;
+
+ vertices[vertexOffset++] = offset.x + x + 1;
+ vertices[vertexOffset++] = offset.y + y + 1;
+ vertices[vertexOffset++] = offset.z + z;
+ vertices[vertexOffset++] = 0;
+ vertices[vertexOffset++] = 1;
+ vertices[vertexOffset++] = 0;
+ return vertexOffset;
+ }
+
+ public static int createFront(Vector3 offset, int x, int y, int z, float[] vertices, int vertexOffset) {
+ vertices[vertexOffset++] = offset.x + x;
+ vertices[vertexOffset++] = offset.y + y;
+ vertices[vertexOffset++] = offset.z + z;
+ vertices[vertexOffset++] = 0;
+ vertices[vertexOffset++] = 0;
+ vertices[vertexOffset++] = 1;
+
+ vertices[vertexOffset++] = offset.x + x + 1;
+ vertices[vertexOffset++] = offset.y + y;
+ vertices[vertexOffset++] = offset.z + z;
+ vertices[vertexOffset++] = 0;
+ vertices[vertexOffset++] = 0;
+ vertices[vertexOffset++] = 1;
+
+ vertices[vertexOffset++] = offset.x + x + 1;
+ vertices[vertexOffset++] = offset.y + y + 1;
+ vertices[vertexOffset++] = offset.z + z;
+ vertices[vertexOffset++] = 0;
+ vertices[vertexOffset++] = 0;
+ vertices[vertexOffset++] = 1;
+
+ vertices[vertexOffset++] = offset.x + x;
+ vertices[vertexOffset++] = offset.y + y + 1;
+ vertices[vertexOffset++] = offset.z + z;
+ vertices[vertexOffset++] = 0;
+ vertices[vertexOffset++] = 0;
+ vertices[vertexOffset++] = 1;
+ return vertexOffset;
+ }
+
+ public static int createBack(Vector3 offset, int x, int y, int z, float[] vertices, int vertexOffset) {
+ vertices[vertexOffset++] = offset.x + x;
+ vertices[vertexOffset++] = offset.y + y;
+ vertices[vertexOffset++] = offset.z + z + 1;
+ vertices[vertexOffset++] = 0;
+ vertices[vertexOffset++] = 0;
+ vertices[vertexOffset++] = -1;
+
+ vertices[vertexOffset++] = offset.x + x;
+ vertices[vertexOffset++] = offset.y + y + 1;
+ vertices[vertexOffset++] = offset.z + z + 1;
+ vertices[vertexOffset++] = 0;
+ vertices[vertexOffset++] = 0;
+ vertices[vertexOffset++] = -1;
+
+ vertices[vertexOffset++] = offset.x + x + 1;
+ vertices[vertexOffset++] = offset.y + y + 1;
+ vertices[vertexOffset++] = offset.z + z + 1;
+ vertices[vertexOffset++] = 0;
+ vertices[vertexOffset++] = 0;
+ vertices[vertexOffset++] = -1;
+
+ vertices[vertexOffset++] = offset.x + x + 1;
+ vertices[vertexOffset++] = offset.y + y;
+ vertices[vertexOffset++] = offset.z + z + 1;
+ vertices[vertexOffset++] = 0;
+ vertices[vertexOffset++] = 0;
+ vertices[vertexOffset++] = -1;
+ return vertexOffset;
+ }
+}
\ No newline at end of file
--- /dev/null
+package com.badlogic.gdx.tests.g3d.voxel;
+
+import com.badlogic.gdx.Gdx;
+import com.badlogic.gdx.graphics.Color;
+import com.badlogic.gdx.graphics.GL10;
+import com.badlogic.gdx.graphics.GL20;
+import com.badlogic.gdx.graphics.Mesh;
+import com.badlogic.gdx.graphics.PerspectiveCamera;
+import com.badlogic.gdx.graphics.Texture;
+import com.badlogic.gdx.graphics.VertexAttribute;
+import com.badlogic.gdx.graphics.VertexAttributes.Usage;
+import com.badlogic.gdx.graphics.g2d.BitmapFont;
+import com.badlogic.gdx.graphics.g2d.SpriteBatch;
+import com.badlogic.gdx.graphics.g2d.TextureRegion;
+import com.badlogic.gdx.graphics.g3d.Model;
+import com.badlogic.gdx.graphics.g3d.ModelBatch;
+import com.badlogic.gdx.graphics.g3d.ModelInstance;
+import com.badlogic.gdx.graphics.g3d.lights.DirectionalLight;
+import com.badlogic.gdx.graphics.g3d.lights.Lights;
+import com.badlogic.gdx.graphics.g3d.materials.ColorAttribute;
+import com.badlogic.gdx.graphics.g3d.materials.Material;
+import com.badlogic.gdx.graphics.g3d.shaders.DefaultShader;
+import com.badlogic.gdx.graphics.g3d.utils.CameraInputController;
+import com.badlogic.gdx.graphics.g3d.utils.FirstPersonCameraController;
+import com.badlogic.gdx.graphics.g3d.utils.ModelBuilder;
+import com.badlogic.gdx.math.MathUtils;
+import com.badlogic.gdx.math.Vector3;
+import com.badlogic.gdx.tests.utils.GdxTest;
+import com.badlogic.gdx.tests.utils.PerspectiveCamController;
+
+public class VoxelTest extends GdxTest {
+ SpriteBatch spriteBatch;
+ BitmapFont font;
+ ModelBatch modelBatch;
+ PerspectiveCamera camera;
+ Lights lights;
+ FirstPersonCameraController controller;
+ VoxelWorld voxelWorld;
+
+ @Override
+ public void create () {
+ spriteBatch = new SpriteBatch();
+ font = new BitmapFont();
+ modelBatch = new ModelBatch();
+ DefaultShader.defaultCullFace = GL20.GL_FRONT;
+ camera = new PerspectiveCamera(67, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
+ camera.near = 1;
+ camera.far = 1000;
+ controller = new FirstPersonCameraController(camera);
+ Gdx.input.setInputProcessor(controller);
+
+ lights = new Lights();
+ lights.ambientLight.set(0.4f, 0.4f, 0.4f, 1f);
+ lights.add(new DirectionalLight().set(1, 1, 1, -0.7f, -0.8f, -0.2f));
+
+ Texture texture = new Texture(Gdx.files.internal("data/g3d/tiles.png"));
+ TextureRegion[][] tiles = TextureRegion.split(texture, 32, 32);
+
+ MathUtils.random.setSeed(0);
+ voxelWorld = new VoxelWorld(tiles[0], 20, 4, 20);
+ PerlinNoiseGenerator.generateVoxels(voxelWorld, 0, 63, 3);
+// voxelWorld.setCube(0, 0, 0, 16, 16, 16, (byte)1);
+ float camX = voxelWorld.voxelsX / 2f;
+ float camZ = voxelWorld.voxelsZ / 2f;
+ float camY = voxelWorld.getHighest(camX, camZ);
+ camera.position.set(camX, camY, camZ);
+ }
+
+ @Override
+ public void render () {
+ Gdx.gl.glClearColor(0.4f, 0.4f, 0.4f, 1f);
+ Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);
+ modelBatch.begin(camera);
+ modelBatch.render(voxelWorld, lights);
+ modelBatch.end();
+ controller.update();
+
+ spriteBatch.begin();
+ font.draw(spriteBatch, "fps: " + Gdx.graphics.getFramesPerSecond(), 0, 20);
+ spriteBatch.end();
+ }
+
+ @Override
+ public void resize (int width, int height) {
+ spriteBatch.getProjectionMatrix().setToOrtho2D(0, 0, width, height);
+ camera.viewportWidth = width;
+ camera.viewportHeight = height;
+ camera.update();
+ }
+
+ @Override
+ public boolean needsGL20 () {
+ return true;
+ }
+}
--- /dev/null
+package com.badlogic.gdx.tests.g3d.voxel;
+
+import com.badlogic.gdx.graphics.Color;
+import com.badlogic.gdx.graphics.GL10;
+import com.badlogic.gdx.graphics.GL20;
+import com.badlogic.gdx.graphics.Mesh;
+import com.badlogic.gdx.graphics.VertexAttribute;
+import com.badlogic.gdx.graphics.g2d.TextureRegion;
+import com.badlogic.gdx.graphics.g3d.Renderable;
+import com.badlogic.gdx.graphics.g3d.RenderableProvider;
+import com.badlogic.gdx.graphics.g3d.materials.ColorAttribute;
+import com.badlogic.gdx.graphics.g3d.materials.Material;
+import com.badlogic.gdx.math.MathUtils;
+import com.badlogic.gdx.utils.Array;
+import com.badlogic.gdx.utils.Pool;
+
+public class VoxelWorld implements RenderableProvider {
+ public static final int CHUNK_SIZE_X = 16;
+ public static final int CHUNK_SIZE_Y = 16;
+ public static final int CHUNK_SIZE_Z = 16;
+
+ public final VoxelChunk[] chunks;
+ public final Mesh[] meshes;
+ public final Material[] materials;
+ public final boolean[] dirty;
+ public final int[] numVertices;
+ public float[] vertices;
+ public final int chunksX;
+ public final int chunksY;
+ public final int chunksZ;
+ public final int voxelsX;
+ public final int voxelsY;
+ public final int voxelsZ;
+ private final TextureRegion[] tiles;
+
+ public VoxelWorld(TextureRegion[] tiles, int chunksX, int chunksY, int chunksZ) {
+ this.tiles = tiles;
+ this.chunks = new VoxelChunk[chunksX * chunksY * chunksZ];
+ this.chunksX = chunksX;
+ this.chunksY = chunksY;
+ this.chunksZ = chunksZ;
+ this.voxelsX = chunksX * CHUNK_SIZE_X;
+ this.voxelsY = chunksY * CHUNK_SIZE_Y;
+ this.voxelsZ = chunksZ * CHUNK_SIZE_Z;
+ int i = 0;
+ for(int y = 0; y < chunksY; y++) {
+ for(int z = 0; z < chunksZ; z++) {
+ for(int x = 0; x < chunksX; x++) {
+ VoxelChunk chunk = new VoxelChunk(CHUNK_SIZE_X, CHUNK_SIZE_Y, CHUNK_SIZE_Z);
+ chunk.offset.set(x * CHUNK_SIZE_X, y * CHUNK_SIZE_Y, z * CHUNK_SIZE_Z);
+ chunks[i++] = chunk;
+ }
+ }
+ }
+ int len = CHUNK_SIZE_X * CHUNK_SIZE_Y * CHUNK_SIZE_Z * 6 * 6 / 3;
+ short[] indices = new short[len];
+ short j = 0;
+ for (i = 0; i < len; i += 6, j += 4) {
+ indices[i + 0] = (short)(j + 0);
+ indices[i + 1] = (short)(j + 1);
+ indices[i + 2] = (short)(j + 2);
+ indices[i + 3] = (short)(j + 2);
+ indices[i + 4] = (short)(j + 3);
+ indices[i + 5] = (short)(j + 0);
+ }
+ this.meshes = new Mesh[chunksX * chunksY * chunksZ];
+ for(i = 0; i < meshes.length; i++) {
+ meshes[i] = new Mesh(true,
+ CHUNK_SIZE_X * CHUNK_SIZE_Y * CHUNK_SIZE_Z * 6 * 4,
+ CHUNK_SIZE_X * CHUNK_SIZE_Y * CHUNK_SIZE_Z * 36 / 3,
+ VertexAttribute.Position(), VertexAttribute.Normal());
+ meshes[i].setIndices(indices);
+ }
+ this.dirty = new boolean[chunksX * chunksY * chunksZ];
+ for(i = 0; i < dirty.length; i++) dirty[i] = true;
+
+ this.numVertices = new int[chunksX * chunksY * chunksZ];
+ for(i = 0; i < numVertices.length; i++) numVertices[i] = 0;
+
+ this.vertices = new float[VoxelChunk.VERTEX_SIZE * 6 * CHUNK_SIZE_X * CHUNK_SIZE_Y * CHUNK_SIZE_Z];
+ this.materials = new Material[chunksX * chunksY * chunksZ];
+ for(i = 0; i < materials.length; i++) {
+ materials[i] = new Material(new ColorAttribute(ColorAttribute.Diffuse, MathUtils.random(0.5f, 1f), MathUtils.random(0.5f, 1f), MathUtils.random(0.5f, 1f), 1));
+ }
+ }
+
+ public void set(float x, float y, float z, byte voxel) {
+ int ix = (int)x;
+ int iy = (int)y;
+ int iz = (int)z;
+ int chunkX = ix / CHUNK_SIZE_X;
+ if(chunkX < 0 || chunkX >= chunksX) return;
+ int chunkY = iy / CHUNK_SIZE_Y;
+ if(chunkY < 0 || chunkY >= chunksY) return;
+ int chunkZ = iz / CHUNK_SIZE_Z;
+ if(chunkZ < 0 || chunkZ >= chunksZ) return;
+ chunks[chunkX + chunkZ * chunksX + chunkY * chunksX * chunksZ].set(ix % CHUNK_SIZE_X, iy % CHUNK_SIZE_Y, iz % CHUNK_SIZE_Z, voxel);
+ }
+
+ public byte get(float x, float y, float z) {
+ int ix = (int)x;
+ int iy = (int)y;
+ int iz = (int)z;
+ int chunkX = ix / CHUNK_SIZE_X;
+ if(chunkX < 0 || chunkX >= chunksX) return 0;
+ int chunkY = iy / CHUNK_SIZE_Y;
+ if(chunkY < 0 || chunkY >= chunksY) return 0;
+ int chunkZ = iz / CHUNK_SIZE_Z;
+ if(chunkZ < 0 || chunkZ >= chunksZ) return 0;
+ return chunks[chunkX + chunkZ * chunksX + chunkY * chunksX * chunksZ].get(ix % CHUNK_SIZE_X, iy % CHUNK_SIZE_Y, iz % CHUNK_SIZE_Z);
+ }
+
+ public float getHighest (float x, float z) {
+ int ix = (int)x;
+ int iz = (int)z;
+ if(ix < 0 || ix >= voxelsX) return 0;
+ if(iz < 0 || iz >= voxelsZ) return 0;
+ // FIXME optimize
+ for(int y = voxelsY - 1; y > 0; y--) {
+ if(get(ix, y, iz) > 0) return y + 1;
+ }
+ return 0;
+ }
+
+ public void setColumn(float x, float y, float z, byte voxel) {
+ int ix = (int)x;
+ int iy = (int)y;
+ int iz = (int)z;
+ if(ix < 0 || ix >= voxelsX) return;
+ if(iy < 0 || iy >= voxelsY) return;
+ if(iz < 0 || iz >= voxelsZ) return;
+ // FIXME optimize
+ for(; iy > 0; iy--) {
+ set(ix, iy, iz, voxel);
+ }
+ }
+
+ public void setCube(float x, float y, float z, float width, float height, float depth, byte voxel) {
+ int ix = (int)x;
+ int iy = (int)y;
+ int iz = (int)z;
+ int iwidth = (int)width;
+ int iheight = (int)height;
+ int idepth = (int)depth;
+ int startX = Math.max(ix, 0);
+ int endX = Math.min(voxelsX, ix + iwidth);
+ int startY = Math.max(iy, 0);
+ int endY = Math.min(voxelsY, iy + iheight);
+ int startZ = Math.max(iz, 0);
+ int endZ = Math.min(voxelsZ, iz + idepth);
+ // FIXME optimize
+ for(iy = startY; iy < endY; iy++) {
+ for(iz = startZ; iz < endZ; iz++) {
+ for(ix = startX; ix < endX; ix++) {
+ set(ix, iy, iz, voxel);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void getRenderables (Array<Renderable> renderables, Pool<Renderable> pool) {
+ for(int i = 0; i < chunks.length; i++) {
+ VoxelChunk chunk = chunks[i];
+ Mesh mesh = meshes[i];
+ if(dirty[i]) {
+ int numVerts = chunk.calculateVertices(vertices);
+ numVertices[i] = numVerts / 4 * 6;
+ mesh.setVertices(vertices, 0, numVerts * VoxelChunk.VERTEX_SIZE);
+ dirty[i] = false;
+ }
+ Renderable renderable = pool.obtain();
+ renderable.material = materials[i];
+ renderable.mesh = mesh;
+ renderable.meshPartOffset = 0;
+ renderable.meshPartSize = numVertices[i];
+ renderable.primitiveType = GL20.GL_TRIANGLES;
+ renderables.add(renderable);
+ }
+ }
+}