aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ardor3d-craft/LICENSE24
-rw-r--r--ardor3d-craft/NiceDataGenerator_Map.acrbin0 -> 3488388 bytes
-rw-r--r--ardor3d-craft/README.md25
-rw-r--r--ardor3d-craft/build.gradle10
-rw-r--r--ardor3d-craft/pom.xml42
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/collision/HitTester.java14
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/collision/IntersectionResult.java37
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/collision/Tracer.java217
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/control/ButtonSet.java82
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/control/FlyControl.java315
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/control/ITriggerGroup.java33
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/control/SomeButtonsDownPredicate.java53
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/control/UprightFPSMoveConfig.java118
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/control/UprightFlyControlTriggers.java340
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/control/WalkControl.java318
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/control/WalkControlTriggers.java352
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/data/Direction.java11
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/data/Pos.java89
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/file/WorldFile.java313
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/file/WorldFileViewer.java96
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/generators/DataGenerator.java37
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/generators/DefaultDataGenerator.java218
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/objects/QuadBox.java333
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/objects/QuadQuad.java114
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/objects/SkyDome.java350
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/objects/Skybox.java257
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/AStarHeuristic.java15
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/ConstrainedAStar.java180
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/MoveCalculator.java14
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/MoveData.java17
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/PathNode.java103
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/PathResult.java32
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/calculator/DefaultAStarCalculator.java89
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/heuristics/ClosestHeuristic.java23
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/heuristics/ClosestSquaredHeuristic.java22
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/heuristics/ManhattanHeuristic.java23
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/player/PlayerBase.java61
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/util/BlockUtil.java497
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/util/ColorUtil.java83
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/util/DoubleBufferedList.java56
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/util/DoubleBufferedSet.java50
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/util/FontHandler.java67
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/util/ImprovedNoise.java197
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/util/MouseButtonHeldCondition.java54
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/util/SimplexNoise.java706
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/util/geometryproducers/BoxProducer.java494
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/util/geometryproducers/GeometryProducer.java35
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/util/geometryproducers/MeshProducer.java130
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/util/queue/Coords.java60
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/util/queue/Task.java132
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/util/queue/Worker.java106
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/util/queue/WorkerManager.java19
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/voxel/MeshVoxelationContext.java92
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/voxel/TriangleAABBIntersection.java268
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/voxel/Voxel.java11
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/voxel/VoxelateMesh.java288
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/voxel/VoxelationContext.java64
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/voxel/VoxelationListener.java13
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/voxel/Voxelator.java81
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/world/BlockEditData.java34
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/world/BlockProvider.java43
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/world/BlockSide.java19
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/world/BlockType.java15
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/world/BlockWorld.java1979
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/world/Chunk.java25
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/world/ChunkModifier.java22
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/world/GeometryHandler.java217
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/world/IServerConnection.java25
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/world/LightProvider.java13
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/world/WorldModifier.java15
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/world/WorldSettings.java227
-rw-r--r--ardor3d-craft/src/main/java/com/ardorcraft/world/utils/ChunkDistanceComparator.java32
-rw-r--r--ardor3d-craft/world.acrbin0 -> 2074380 bytes
-rw-r--r--ardor3d-examples/build.gradle1
-rw-r--r--ardor3d-examples/pom.xml5
-rw-r--r--pom.xml1
-rw-r--r--settings.gradle4
77 files changed, 10556 insertions, 1 deletions
diff --git a/ardor3d-craft/LICENSE b/ardor3d-craft/LICENSE
new file mode 100644
index 0000000..fcc7425
--- /dev/null
+++ b/ardor3d-craft/LICENSE
@@ -0,0 +1,24 @@
+Ardor3D is licensed as follows:
+
+/**
+ * Copyright (c) 2008-2014 Ardor Labs, Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ *
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ *
+ * 3. This notice may not be removed or altered from any source
+ * distribution.
+ */ \ No newline at end of file
diff --git a/ardor3d-craft/NiceDataGenerator_Map.acr b/ardor3d-craft/NiceDataGenerator_Map.acr
new file mode 100644
index 0000000..a76d88b
--- /dev/null
+++ b/ardor3d-craft/NiceDataGenerator_Map.acr
Binary files differ
diff --git a/ardor3d-craft/README.md b/ardor3d-craft/README.md
new file mode 100644
index 0000000..eade830
--- /dev/null
+++ b/ardor3d-craft/README.md
@@ -0,0 +1,25 @@
+# ArdorCraft API
+
+## Try it
+[Try running one of the examples!](https://jogamp.org/cgit/ardor3d.git/tree/ardor3d-examples/src/main/java/com/ardor3d/example/craft)
+
+## Learn it
+[Features](http://tinyurl.com/mpuu8or)
+
+[Documentation](http://jogamp.org/deployment/ardor3d/javadoc/)
+
+
+Built with the best open-source 3D engine [Ardor3D](https://jogamp.org/cgit/ardor3d.git)
+
+## Screenshots from some of the examples you can build on:
+
+**http://www.youtube.com/watch?v=FZKYiNBafUc**
+
+![Screenshot](http://i.imgur.com/qf5Z3.jpg)
+![Screenshot](http://i.imgur.com/9RpZF.png)
+![Screenshot](http://i.imgur.com/lv30u.jpg)
+![Screenshot](http://i.imgur.com/PxlNx.png)
+![Screenshot](http://i.imgur.com/nEEjS.png)
+![Screenshot](http://i.imgur.com/WfVXB.jpg)
+![Screenshot](http://i.imgur.com/3Nwac.png)
+![Screenshot](http://i.imgur.com/cTuYb.png)
diff --git a/ardor3d-craft/build.gradle b/ardor3d-craft/build.gradle
new file mode 100644
index 0000000..0ef8d8c
--- /dev/null
+++ b/ardor3d-craft/build.gradle
@@ -0,0 +1,10 @@
+
+description = 'Ardor 3D Craft'
+dependencies {
+ compile project(':ardor3d-awt')
+ compile project(':ardor3d-core')
+ compile group: 'com.sun.xml.bind', name: 'jaxb-core', version:'2.3.0.1'
+ compile group: 'javax.xml.bind', name: 'jaxb-api', version:'2.3.1'
+ compile group: 'com.sun.xml.bind', name: 'jaxb-impl', version:'2.3.1'
+}
+
diff --git a/ardor3d-craft/pom.xml b/ardor3d-craft/pom.xml
new file mode 100644
index 0000000..9118608
--- /dev/null
+++ b/ardor3d-craft/pom.xml
@@ -0,0 +1,42 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.jogamp.ardor3d</groupId>
+ <artifactId>ardor3d</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+
+ <artifactId>ardor3d-craft</artifactId>
+ <packaging>bundle</packaging>
+ <name>Ardor 3D Craft</name>
+
+ <dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>ardor3d-core</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>ardor3d-awt</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.sun.xml.bind</groupId>
+ <artifactId>jaxb-core</artifactId>
+ <version>2.3.0.1</version>
+ </dependency>
+ <dependency>
+ <groupId>javax.xml.bind</groupId>
+ <artifactId>jaxb-api</artifactId>
+ <version>2.3.1</version>
+ </dependency>
+ <dependency>
+ <groupId>com.sun.xml.bind</groupId>
+ <artifactId>jaxb-impl</artifactId>
+ <version>2.3.1</version>
+ </dependency>
+ </dependencies>
+
+</project> \ No newline at end of file
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/collision/HitTester.java b/ardor3d-craft/src/main/java/com/ardorcraft/collision/HitTester.java
new file mode 100644
index 0000000..c0c642f
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/collision/HitTester.java
@@ -0,0 +1,14 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.collision;
+
+/**
+ * A HitTester allows for user configurable picking.
+ */
+public interface HitTester {
+ boolean isHit(int blockId);
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/collision/IntersectionResult.java b/ardor3d-craft/src/main/java/com/ardorcraft/collision/IntersectionResult.java
new file mode 100644
index 0000000..b13da86
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/collision/IntersectionResult.java
@@ -0,0 +1,37 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.collision;
+
+import com.ardorcraft.data.Pos;
+
+/**
+ * The result of a picking/tracing operation.
+ * <ul>
+ * <li>hit = was there a hit during this intersection test
+ * <li>oldPos = the last block grid position before a hit (which is the position in front of the hit block face)
+ * <li>pos = the block grid position that was hit
+ * <li>length = the length of the ray from the source to the hit position
+ * </ul>
+ */
+public class IntersectionResult {
+ public boolean hit;
+ public Pos oldPos = new Pos();
+ public Pos pos = new Pos();
+ public double length;
+
+ public void set(final IntersectionResult result) {
+ hit = result.hit;
+ oldPos = new Pos(result.oldPos);
+ pos = new Pos(result.pos);
+ length = result.length;
+ }
+
+ @Override
+ public String toString() {
+ return "IntersectionResult [hit=" + hit + ", length=" + length + ", oldPos=" + oldPos + ", pos=" + pos + "]";
+ }
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/collision/Tracer.java b/ardor3d-craft/src/main/java/com/ardorcraft/collision/Tracer.java
new file mode 100644
index 0000000..9dad657
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/collision/Tracer.java
@@ -0,0 +1,217 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.collision;
+
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardorcraft.world.BlockProvider;
+
+/**
+ * Ray tracing for a block world.
+ */
+public class Tracer {
+ private double mult = 1;
+ private final Vector3 tmax = new Vector3();
+ private final Vector3 tdelta = new Vector3();
+ private final Vector3 newPos = new Vector3();
+
+ private static final class DefaultHitTester implements HitTester {
+ @Override
+ public boolean isHit(final int blockId) {
+ return blockId != 0;
+ }
+ }
+
+ private final int maxHeight;
+ private final HitTester hitTester;
+ private final BlockProvider provider;
+
+ public Tracer(final BlockProvider provider) {
+ this.provider = provider;
+ hitTester = new DefaultHitTester();
+ maxHeight = -1;
+ }
+
+ public Tracer(final BlockProvider provider, final HitTester hitTester) {
+ this.provider = provider;
+ this.hitTester = hitTester;
+ maxHeight = -1;
+ }
+
+ public Tracer(final BlockProvider provider, final HitTester hitTester, final int maxHeight) {
+ this.provider = provider;
+ this.hitTester = hitTester;
+ this.maxHeight = maxHeight;
+ }
+
+ public void traceCollision(ReadOnlyVector3 curpos, ReadOnlyVector3 raydir, final int iterations,
+ final IntersectionResult result) {
+ tmax.set(0, 0, 0);
+ tdelta.set(0, 0, 0);
+
+ mult = 1;
+ boolean back = false;
+
+ result.hit = false;
+ result.length = 0;
+
+ if (curpos.getY() < 0) {
+ return;
+ }
+ if (maxHeight > 0 && curpos.getY() >= maxHeight - 1 && raydir.getY() >= 0) {
+ return;
+ }
+
+ if (maxHeight > 0 && curpos.getY() >= maxHeight - 1) {
+ final double diff = maxHeight - 1 - curpos.getY();
+ final double t = diff / raydir.getY();
+ newPos.set(raydir).multiplyLocal(t).addLocal(curpos);
+ curpos = newPos;
+ }
+
+ int X = (int) MathUtils.floor(curpos.getX());
+ int Y = (int) MathUtils.floor(curpos.getY());
+ int Z = (int) MathUtils.floor(curpos.getZ());
+
+ final int block1 = provider.getBlock(X, Y, Z);
+ if (block1 != 0 && hitTester.isHit(block1)) {
+ raydir = new Vector3(raydir).negateLocal();
+ mult = -1;
+ back = true;
+ }
+
+ int stepX, stepY, stepZ;
+ double cbx, cby, cbz;
+
+ if (raydir.getX() > 0.0) {
+ stepX = 1;
+ cbx = X + 1;
+ } else {
+ stepX = -1;
+ cbx = X;
+ }
+ if (raydir.getY() > 0.0) {
+ stepY = 1;
+ cby = Y + 1;
+ } else {
+ stepY = -1;
+ cby = Y;
+ }
+ if (raydir.getZ() > 0.0) {
+ stepZ = 1;
+ cbz = Z + 1;
+ } else {
+ stepZ = -1;
+ cbz = Z;
+ }
+
+ if (raydir.getX() != 0) {
+ final double rxr = 1.0 / raydir.getX();
+ tmax.setX((cbx - curpos.getX()) * rxr);
+ tdelta.setX(stepX * rxr);
+ } else {
+ tmax.setX(1000000);
+ }
+ if (raydir.getY() != 0) {
+ final double ryr = 1.0 / raydir.getY();
+ tmax.setY((cby - curpos.getY()) * ryr);
+ tdelta.setY(stepY * ryr);
+ } else {
+ tmax.setY(1000000);
+ }
+ if (raydir.getZ() != 0) {
+ final double rzr = 1.0 / raydir.getZ();
+ tmax.setZ((cbz - curpos.getZ()) * rzr);
+ tdelta.setZ(stepZ * rzr);
+ } else {
+ tmax.setZ(1000000);
+ }
+
+ int oldX = X, oldY = Y, oldZ = Z;
+
+ for (int i = 0; i < iterations; i++) {
+ if (tmax.getX() < tmax.getY()) {
+ if (tmax.getX() < tmax.getZ()) {
+ X = X + stepX;
+ final int block = provider.getBlock(X, Y, Z);
+ final boolean isHit = block != 0 && hitTester.isHit(block);
+ if (back && !isHit || !back && isHit) {
+ gatherMin(result, tmax, X, Y, Z, oldX, oldY, oldZ);
+ result.hit = true;
+ return;
+ }
+ tmax.setX(tmax.getX() + tdelta.getX());
+ } else {
+ Z = Z + stepZ;
+ final int block = provider.getBlock(X, Y, Z);
+ final boolean isHit = block != 0 && hitTester.isHit(block);
+ if (back && !isHit || !back && isHit) {
+ gatherMin(result, tmax, X, Y, Z, oldX, oldY, oldZ);
+ result.hit = true;
+ return;
+ }
+ tmax.setZ(tmax.getZ() + tdelta.getZ());
+ }
+ } else {
+ if (tmax.getY() < tmax.getZ()) {
+ Y = Y + stepY;
+ if (maxHeight > 0 && Y >= maxHeight - 1) {
+ return;
+ }
+ final int block = provider.getBlock(X, Y, Z);
+ final boolean isHit = block != 0 && hitTester.isHit(block);
+ if (back && !isHit || !back && isHit) {
+ gatherMin(result, tmax, X, Y, Z, oldX, oldY, oldZ);
+ result.hit = true;
+ return;
+ }
+ tmax.setY(tmax.getY() + tdelta.getY());
+ } else {
+ Z = Z + stepZ;
+ final int block = provider.getBlock(X, Y, Z);
+ final boolean isHit = block != 0 && hitTester.isHit(block);
+ if (back && !isHit || !back && isHit) {
+ gatherMin(result, tmax, X, Y, Z, oldX, oldY, oldZ);
+ result.hit = true;
+ return;
+ }
+ tmax.setZ(tmax.getZ() + tdelta.getZ());
+ }
+ }
+
+ oldX = X;
+ oldY = Y;
+ oldZ = Z;
+ }
+ }
+
+ private void gatherMin(final IntersectionResult result, final Vector3 tmax, final int X, final int Y, final int Z,
+ final int oldX, final int oldY, final int oldZ) {
+ result.oldPos.set(oldX, oldY, oldZ);
+ result.pos.set(X, Y, Z);
+
+ double min = tmax.getX();
+ if (tmax.getY() < min) {
+ min = tmax.getY();
+ }
+ if (tmax.getZ() < min) {
+ min = tmax.getZ();
+ }
+
+ final double epsilon = 0.0001;
+
+ double length = min * mult;
+ if (length > 0) {
+ length = Math.max(length - epsilon, 0.0);
+ } else if (length < 0) {
+ length = Math.min(length - epsilon, 0.0);
+ }
+
+ result.length = length;
+ }
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/control/ButtonSet.java b/ardor3d-craft/src/main/java/com/ardorcraft/control/ButtonSet.java
new file mode 100644
index 0000000..4197e18
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/control/ButtonSet.java
@@ -0,0 +1,82 @@
+/**
+ *
+ */
+package com.ardorcraft.control;
+
+import com.ardor3d.input.Key;
+import com.ardor3d.input.MouseButton;
+
+import java.util.Collection;
+import java.util.TreeSet;
+
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+
+/**
+ * @author Nathan
+ *
+ * A set of keys or mouse buttons to map to a function
+ * for creating configurable controls.
+ */
+public class ButtonSet
+{
+ /**
+ * The set of keys in the set.
+ */
+ @XmlElementWrapper(name="Keys")
+ @XmlElement(name="Key")
+ public TreeSet<Key> keys;
+
+ /**
+ * The set of mouse buttons in the set.
+ */
+ @XmlElementWrapper(name="MouseButtons")
+ @XmlElement(name="Button")
+ public TreeSet<MouseButton> mouseButtons;
+
+ /**
+ * Default constructor just creates
+ * an empty set.
+ */
+ public ButtonSet()
+ {
+ keys = new TreeSet<>();
+ mouseButtons = new TreeSet<>();
+ }
+
+ /**
+ * Create a union of a number of
+ * button sets.
+ * @param buttonSets
+ * The buttonsets to combine
+ */
+ public ButtonSet(ButtonSet...buttonSets)
+ {
+ keys = new TreeSet<>();
+ mouseButtons = new TreeSet<>();
+
+ for(ButtonSet set : buttonSets)
+ {
+ keys.addAll(set.keys);
+ mouseButtons.addAll(set.mouseButtons);
+ }
+ }
+
+ /**
+ * Constructs a set containing the
+ * union of the sets passed in.
+ * @param buttonSets
+ * The sets to combine.
+ */
+ public ButtonSet(Collection<ButtonSet> buttonSets)
+ {
+ keys = new TreeSet<>();
+ mouseButtons = new TreeSet<>();
+
+ for(ButtonSet set : buttonSets)
+ {
+ keys.addAll(set.keys);
+ mouseButtons.addAll(set.mouseButtons);
+ }
+ }
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/control/FlyControl.java b/ardor3d-craft/src/main/java/com/ardorcraft/control/FlyControl.java
new file mode 100644
index 0000000..237df9f
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/control/FlyControl.java
@@ -0,0 +1,315 @@
+/**
+ * Copyright (c) 2008-2010 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardorcraft.control;
+
+import java.util.function.Predicate;
+
+import com.ardor3d.framework.Canvas;
+//import com.ardor3d.input.Key;
+//import com.ardor3d.input.KeyboardState;
+import com.ardor3d.input.MouseState;
+import com.ardor3d.input.logical.InputTrigger;
+import com.ardor3d.input.logical.LogicalLayer;
+import com.ardor3d.input.logical.TriggerAction;
+import com.ardor3d.input.logical.TriggerConditions;
+import com.ardor3d.input.logical.TwoInputStates;
+import com.ardor3d.math.Matrix3;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardorcraft.player.PlayerBase;
+
+public class FlyControl {
+
+ private final Vector3 _upAxis = new Vector3();
+ private double _mouseRotateSpeed = .005;
+ private double _moveSpeed = 15;
+ private double _keyRotateSpeed = 2.25;
+ private final Matrix3 _workerMatrix = new Matrix3();
+ private final Vector3 _workerStoreA = new Vector3();
+ private InputTrigger _mouseTrigger;
+ private InputTrigger _keyTrigger;
+ private final LogicalLayer layer;
+ private final PlayerBase player;
+
+ private SomeButtonsDownPredicate moveLeftPredicate;
+ private SomeButtonsDownPredicate moveRightPredicate;
+ private SomeButtonsDownPredicate moveForwardPredicate;
+ private SomeButtonsDownPredicate moveBackPredicate;
+ private SomeButtonsDownPredicate turnLeftPredicate;
+ private SomeButtonsDownPredicate turnRightPredicate;
+ private SomeButtonsDownPredicate turnUpPredicate;
+ private SomeButtonsDownPredicate turnDownPredicate;
+ private UprightFPSMoveConfig moveConfigMember;
+
+ public FlyControl(
+ final PlayerBase player,
+ final LogicalLayer layer,
+ final ReadOnlyVector3 upAxis,
+ UprightFPSMoveConfig moveConfigParam) {
+ _upAxis.set(upAxis);
+ this.layer = layer;
+ this.player = player;
+ moveConfigMember = moveConfigParam;
+ moveLeftPredicate =
+ new SomeButtonsDownPredicate(moveConfigMember.MoveLeft);
+ moveRightPredicate =
+ new SomeButtonsDownPredicate(moveConfigMember.MoveRight);
+ moveForwardPredicate =
+ new SomeButtonsDownPredicate(moveConfigMember.MoveForward);
+ moveBackPredicate =
+ new SomeButtonsDownPredicate(moveConfigMember.MoveBack);
+ turnLeftPredicate =
+ new SomeButtonsDownPredicate(moveConfigMember.TurnLeft);
+ turnRightPredicate =
+ new SomeButtonsDownPredicate(moveConfigMember.TurnRight);
+ turnUpPredicate =
+ new SomeButtonsDownPredicate(moveConfigMember.TurnUp);
+ turnDownPredicate =
+ new SomeButtonsDownPredicate(moveConfigMember.TurnDown);
+ }
+
+ public ReadOnlyVector3 getUpAxis() {
+ return _upAxis;
+ }
+
+ public void setUpAxis(final ReadOnlyVector3 upAxis) {
+ _upAxis.set(upAxis);
+ }
+
+ public double getMouseRotateSpeed() {
+ return _mouseRotateSpeed;
+ }
+
+ public void setMouseRotateSpeed(final double speed) {
+ _mouseRotateSpeed = speed;
+ }
+
+ public double getMoveSpeed() {
+ return _moveSpeed;
+ }
+
+ public void setMoveSpeed(final double speed) {
+ _moveSpeed = speed;
+ }
+
+ public double getKeyRotateSpeed() {
+ return _keyRotateSpeed;
+ }
+
+ public void setKeyRotateSpeed(final double speed) {
+ _keyRotateSpeed = speed;
+ }
+
+ protected void move(
+ final TwoInputStates state,
+ final double tpf)
+ {
+ // MOVEMENT
+ int moveFB = 0, strafeLR = 0;
+ if (moveForwardPredicate.test(state)) {
+ moveFB += 1;
+ }
+ if (moveBackPredicate.test(state)) {
+ moveFB -= 1;
+ }
+ if (moveLeftPredicate.test(state)) {
+ strafeLR += 1;
+ }
+ if (moveRightPredicate.test(state)) {
+ strafeLR -= 1;
+ }
+
+ if (moveFB != 0 || strafeLR != 0) {
+ final Vector3 loc = _workerStoreA.zero();
+ if (moveFB == 1) {
+ loc.addLocal(player.getDirection());
+ } else if (moveFB == -1) {
+ loc.subtractLocal(player.getDirection());
+ }
+ if (strafeLR == 1) {
+ loc.addLocal(player.getLeft());
+ } else if (strafeLR == -1) {
+ loc.subtractLocal(player.getLeft());
+ }
+ loc.normalizeLocal().multiplyLocal(_moveSpeed * tpf)
+ .addLocal(player.getPosition());
+ player.getPosition().set(loc);
+ }
+
+ // ROTATION
+ int rotX = 0, rotY = 0;
+ if (turnUpPredicate.test(state)) {
+ rotY -= 1;
+ }
+ if (turnDownPredicate.test(state)) {
+ rotY += 1;
+ }
+ if (turnLeftPredicate.test(state)) {
+ rotX += 1;
+ }
+ if (turnRightPredicate.test(state)) {
+ rotX -= 1;
+ }
+ if (rotX != 0 || rotY != 0) {
+ rotate(rotX * _keyRotateSpeed / _mouseRotateSpeed * tpf, rotY
+ * _keyRotateSpeed / _mouseRotateSpeed * tpf);
+ }
+ }
+
+ protected void rotate(final double dx, final double dy) {
+
+ if (dx != 0) {
+ _workerMatrix.fromAngleNormalAxis(_mouseRotateSpeed * dx,
+ _upAxis != null ? _upAxis : player.getUp());
+ _workerMatrix.applyPost(player.getLeft(), _workerStoreA);
+ player.getLeft().set(_workerStoreA);
+ _workerMatrix.applyPost(player.getDirection(), _workerStoreA);
+ player.getDirection().set(_workerStoreA);
+ _workerMatrix.applyPost(player.getUp(), _workerStoreA);
+ player.getUp().set(_workerStoreA);
+ }
+
+ if (dy != 0) {
+ _workerMatrix.fromAngleNormalAxis(_mouseRotateSpeed * dy,
+ player.getLeft());
+ _workerMatrix.applyPost(player.getLeft(), _workerStoreA);
+ player.getLeft().set(_workerStoreA);
+ _workerMatrix.applyPost(player.getDirection(), _workerStoreA);
+ player.getDirection().set(_workerStoreA);
+ _workerMatrix.applyPost(player.getUp(), _workerStoreA);
+ player.getUp().set(_workerStoreA);
+ }
+
+ player.normalize();
+ }
+
+ /**
+ * @param layer
+ * the logical layer to register with
+ * @param upAxis
+ * the up axis of the camera
+ * @param dragOnly
+ * if true, mouse input will only rotate the camera if one of the
+ * mouse buttons (left, center or right) is down.
+ * @return a new FlyControl object
+ */
+ public static FlyControl setupTriggers(final PlayerBase player,
+ final LogicalLayer layer, final ReadOnlyVector3 upAxis,
+ final boolean dragOnly) {
+
+ final FlyControl control =
+ new FlyControl(
+ player,
+ layer,
+ upAxis,
+ new UprightFPSMoveConfig(UprightFPSMoveConfig.defaultControls.RightHanded));
+ control.setupKeyboardTriggers(layer);
+ control.setupMouseTriggers(layer, dragOnly);
+ return control;
+ }
+
+ /**
+ * Deregister the triggers of the given FlyControl from the given
+ * LogicalLayer.
+ *
+ * @param layer
+ * @param control
+ */
+ public static void removeTriggers(final LogicalLayer layer,
+ final FlyControl control) {
+ if (control._mouseTrigger != null) {
+ layer.deregisterTrigger(control._mouseTrigger);
+ }
+ if (control._keyTrigger != null) {
+ layer.deregisterTrigger(control._keyTrigger);
+ }
+ }
+
+ public void setupMouseTriggers(final LogicalLayer layer,
+ final boolean dragOnly) {
+ final FlyControl control = this;
+ // Mouse look
+ final Predicate<TwoInputStates> someMouseDown =
+ TriggerConditions.leftButtonDown().or(TriggerConditions.rightButtonDown()).or(TriggerConditions.middleButtonDown());
+ final Predicate<TwoInputStates> dragged = TriggerConditions.mouseMoved().and(someMouseDown);
+ final TriggerAction dragAction = new TriggerAction() {
+
+ // Test boolean to allow us to ignore first mouse event. First event
+ // can wildly vary based on platform.
+ private boolean firstPing = true;
+
+ @Override
+ public void perform(final Canvas source,
+ final TwoInputStates inputStates, final double tpf) {
+ final MouseState mouse = inputStates.getCurrent()
+ .getMouseState();
+ if (mouse.getDx() != 0 || mouse.getDy() != 0) {
+ if (!firstPing) {
+ control.rotate(-mouse.getDx(), -mouse.getDy());
+ } else {
+ firstPing = false;
+ }
+ }
+ }
+ };
+
+ _mouseTrigger = new InputTrigger(dragOnly ? dragged
+ : TriggerConditions.mouseMoved(), dragAction);
+ layer.registerTrigger(_mouseTrigger);
+ }
+
+ public Predicate<TwoInputStates> setupKeyboardTriggers(
+ final LogicalLayer layer) {
+
+ final FlyControl control = this;
+
+ // WASD control
+ final Predicate<TwoInputStates> keysHeld =
+ moveConfigMember.createSinglePredicate(moveConfigMember.createKeysDownPredicates());
+
+ final TriggerAction moveAction = new TriggerAction() {
+ @Override
+ public void perform(final Canvas source,
+ final TwoInputStates inputStates, final double tpf) {
+ control.move(inputStates, tpf);
+ }
+ };
+ _keyTrigger = new InputTrigger(keysHeld, moveAction);
+ layer.registerTrigger(_keyTrigger);
+ return keysHeld;
+ }
+
+ public InputTrigger getKeyTrigger() {
+ return _keyTrigger;
+ }
+
+ public InputTrigger getMouseTrigger() {
+ return _mouseTrigger;
+ }
+
+ public void enable() {
+ if (!layer.getTriggers().contains(_keyTrigger)) {
+ layer.registerTrigger(_keyTrigger);
+ }
+ if (!layer.getTriggers().contains(_mouseTrigger)) {
+ layer.registerTrigger(_mouseTrigger);
+ }
+ }
+
+ public void disable() {
+ if (layer.getTriggers().contains(_keyTrigger)) {
+ layer.deregisterTrigger(_keyTrigger);
+ }
+ if (layer.getTriggers().contains(_mouseTrigger)) {
+ layer.deregisterTrigger(_mouseTrigger);
+ }
+ }
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/control/ITriggerGroup.java b/ardor3d-craft/src/main/java/com/ardorcraft/control/ITriggerGroup.java
new file mode 100644
index 0000000..3669669
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/control/ITriggerGroup.java
@@ -0,0 +1,33 @@
+/**
+ *
+ */
+package com.ardorcraft.control;
+
+import com.ardor3d.input.logical.LogicalLayer;
+
+/**
+ * @author Nathan
+ *
+ */
+public interface ITriggerGroup
+{
+
+ /**
+ * Add the triggers to the specified
+ * logical layer.
+ * @param layer
+ * The logical layer to add
+ * the triggers to.
+ */
+ void AddToLayer(LogicalLayer layer);
+
+ /**
+ * Removes the trigger from the
+ * specified logival layer
+ * @param layer
+ * The logical layer to
+ * remove the triggers from.
+ */
+ void RemoveFromLayer(LogicalLayer layer);
+
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/control/SomeButtonsDownPredicate.java b/ardor3d-craft/src/main/java/com/ardorcraft/control/SomeButtonsDownPredicate.java
new file mode 100644
index 0000000..be7003f
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/control/SomeButtonsDownPredicate.java
@@ -0,0 +1,53 @@
+/**
+ *
+ */
+package com.ardorcraft.control;
+
+import com.ardor3d.input.InputState;
+import com.ardor3d.input.Key;
+import com.ardor3d.input.MouseButton;
+
+import java.util.function.Predicate;
+
+import com.ardor3d.input.ButtonState;
+import com.ardor3d.input.logical.TwoInputStates;
+
+/**
+ * @author nathan
+ *
+ */
+public class SomeButtonsDownPredicate implements
+ Predicate<TwoInputStates>
+{
+ private ButtonSet buttons;
+
+ public SomeButtonsDownPredicate(ButtonSet buttonsParam)
+ {
+ buttons = buttonsParam;
+ }
+
+ @Override
+ public boolean test(TwoInputStates states)
+ {
+ InputState current = states.getCurrent();
+
+ for(Key currentKey : buttons.keys)
+ {
+ if(current.getKeyboardState().isDown(currentKey))
+ {
+ return true;
+ }
+ }
+
+ for(MouseButton currentButton : buttons.mouseButtons)
+ {
+ if(current.getMouseState().getButtonState(currentButton) == ButtonState.DOWN)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/control/UprightFPSMoveConfig.java b/ardor3d-craft/src/main/java/com/ardorcraft/control/UprightFPSMoveConfig.java
new file mode 100644
index 0000000..d1186d2
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/control/UprightFPSMoveConfig.java
@@ -0,0 +1,118 @@
+package com.ardorcraft.control;
+
+import com.ardor3d.input.Key;
+import java.util.ArrayList;
+import java.util.function.Predicate;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+import com.ardor3d.input.logical.TriggerConditions;
+import com.ardor3d.input.logical.TwoInputStates;
+
+@XmlRootElement(name="Controls")
+public class UprightFPSMoveConfig
+{
+ public enum defaultControls
+ {
+ LeftHanded,
+ RightHanded
+ }
+
+ public ButtonSet MoveLeft = new ButtonSet();
+ public ButtonSet MoveRight = new ButtonSet();
+ public ButtonSet MoveForward = new ButtonSet();
+ public ButtonSet MoveBack = new ButtonSet();
+ public ButtonSet TurnLeft = new ButtonSet();
+ public ButtonSet TurnRight = new ButtonSet();
+ public ButtonSet TurnUp = new ButtonSet();
+ public ButtonSet TurnDown = new ButtonSet();
+
+ public UprightFPSMoveConfig()
+ {
+
+ }
+
+ public UprightFPSMoveConfig(defaultControls config)
+ {
+ TurnLeft.keys.add(Key.LEFT);
+ TurnRight.keys.add(Key.RIGHT);
+ TurnUp.keys.add(Key.UP);
+ TurnDown.keys.add(Key.DOWN);
+
+ if(config == defaultControls.LeftHanded)
+ {
+ MoveLeft.keys.add(Key.J);
+ MoveRight.keys.add(Key.L);
+ MoveForward.keys.add(Key.I);
+ MoveBack.keys.add(Key.K);
+ }
+ else
+ {
+ MoveForward.keys.add(Key.W);
+ MoveLeft.keys.add(Key.A);
+ MoveBack.keys.add(Key.S);
+ MoveRight.keys.add(Key.D);
+ }
+ }
+
+ ArrayList<Predicate<TwoInputStates> >
+ createSomeMoveKeysDownPredicates()
+ {
+ ArrayList<Predicate<TwoInputStates> >
+ tempPredicates = new ArrayList<>();
+
+ tempPredicates.add(
+ new SomeButtonsDownPredicate(MoveForward));
+ tempPredicates.add(
+ new SomeButtonsDownPredicate(MoveLeft));
+ tempPredicates.add(
+ new SomeButtonsDownPredicate(MoveBack));
+ tempPredicates.add(
+ new SomeButtonsDownPredicate(MoveRight));
+
+ return tempPredicates;
+ }
+
+ ArrayList<Predicate<TwoInputStates> >
+ createSomeTurnKeyPredicates()
+ {
+ ArrayList<Predicate<TwoInputStates> >
+ tempPredicates =
+ new ArrayList<>();
+
+ tempPredicates.add(
+ new SomeButtonsDownPredicate(TurnUp));
+ tempPredicates.add(
+ new SomeButtonsDownPredicate(TurnLeft));
+ tempPredicates.add(
+ new SomeButtonsDownPredicate(TurnDown));
+ tempPredicates.add(
+ new SomeButtonsDownPredicate(TurnRight));
+
+ return tempPredicates;
+ }
+
+ ArrayList<Predicate<TwoInputStates>>
+ createKeysDownPredicates()
+ {
+ ArrayList<Predicate<TwoInputStates> > moveKeys =
+ createSomeMoveKeysDownPredicates();
+ ArrayList<Predicate<TwoInputStates> > turnKeys =
+ createSomeTurnKeyPredicates();
+
+ ArrayList<Predicate<TwoInputStates> > result =
+ new ArrayList<>();
+ result.addAll(moveKeys);
+ result.addAll(turnKeys);
+
+ return result;
+ }
+
+ Predicate<TwoInputStates> createSinglePredicate(final Iterable<Predicate<TwoInputStates>> predicates) {
+ Predicate<TwoInputStates> result = TriggerConditions.alwaysFalse();
+ for (final Predicate<TwoInputStates> predicate : predicates) {
+ result = result.or(predicate);
+ }
+ return result;
+ }
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/control/UprightFlyControlTriggers.java b/ardor3d-craft/src/main/java/com/ardorcraft/control/UprightFlyControlTriggers.java
new file mode 100644
index 0000000..8aa05a4
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/control/UprightFlyControlTriggers.java
@@ -0,0 +1,340 @@
+package com.ardorcraft.control;
+
+import java.util.function.Predicate;
+
+import com.ardor3d.framework.Canvas;
+import com.ardor3d.input.MouseState;
+import com.ardor3d.input.logical.InputTrigger;
+import com.ardor3d.input.logical.LogicalLayer;
+import com.ardor3d.input.logical.TriggerAction;
+import com.ardor3d.input.logical.TriggerConditions;
+import com.ardor3d.input.logical.TwoInputStates;
+import com.ardor3d.math.Matrix3;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardorcraft.player.PlayerBase;
+
+/**
+ *
+ * Triggers for flying that enforce
+ * an upright position.
+ * @author nathan
+ *
+ */
+public class UprightFlyControlTriggers implements ITriggerGroup
+{
+
+ private final Vector3 upAxis = new Vector3();
+ private double mouseRotateSpeed = .005;
+ private double moveSpeed = 15;
+ private double keyRotateSpeed = 2.25;
+ private final Matrix3 workerMatrix = new Matrix3();
+ private final Vector3 workerStoreA = new Vector3();
+ private final InputTrigger moveTrigger;
+ private final InputTrigger turnTrigger;
+ private final PlayerBase player;
+
+ // The first mouse state is pretty random,
+ // so arrange to ignore it. I'm not sure
+ // this should be there, but I'm not sure
+ // if Ardor3D allows not firing the
+ // triggers on the first update.
+ private boolean firstPing = true;
+
+ private Predicate<TwoInputStates> mouseTurnPredicate;
+
+ private SomeButtonsDownPredicate moveLeftPredicate;
+ private SomeButtonsDownPredicate moveRightPredicate;
+ private SomeButtonsDownPredicate moveForwardPredicate;
+ private SomeButtonsDownPredicate moveBackPredicate;
+ private SomeButtonsDownPredicate turnLeftPredicate;
+ private SomeButtonsDownPredicate turnRightPredicate;
+ private SomeButtonsDownPredicate turnUpPredicate;
+ private SomeButtonsDownPredicate turnDownPredicate;
+ private UprightFPSMoveConfig moveConfig;
+
+ /**
+ * Creates a trigger set for flying
+ * in an upright first person shooter.
+ * @param playerParam
+ * The player object to update
+ * @param upAxisParam
+ * The up axis for the player
+ * @param mouseLookDragOnly
+ * Whether the player needs to
+ * press a mouse button to
+ * turn.
+ * @param moveConfigParam
+ * The keys and possibly mouse
+ * buttons to manage movement.
+ */
+ public UprightFlyControlTriggers(
+ final PlayerBase playerParam,
+ final ReadOnlyVector3 upAxisParam,
+ final boolean mouseLookDragOnly,
+ final UprightFPSMoveConfig moveConfigParam)
+ {
+ player = playerParam;
+ upAxis.set(upAxisParam);
+ moveConfig = moveConfigParam;
+
+ moveLeftPredicate =
+ new SomeButtonsDownPredicate(moveConfig.MoveLeft);
+ moveRightPredicate =
+ new SomeButtonsDownPredicate(moveConfig.MoveRight);
+ moveForwardPredicate =
+ new SomeButtonsDownPredicate(moveConfig.MoveForward);
+ moveBackPredicate =
+ new SomeButtonsDownPredicate(moveConfig.MoveBack);
+ turnLeftPredicate =
+ new SomeButtonsDownPredicate(moveConfig.TurnLeft);
+ turnRightPredicate =
+ new SomeButtonsDownPredicate(moveConfig.TurnRight);
+ turnUpPredicate =
+ new SomeButtonsDownPredicate(moveConfig.TurnUp);
+ turnDownPredicate =
+ new SomeButtonsDownPredicate(moveConfig.TurnDown);
+
+ moveTrigger = CreateMoveTrigger();
+
+ mouseTurnPredicate = CreateMouseTurnPredicate(
+ mouseLookDragOnly);
+
+ turnTrigger = CreateTurnTrigger();
+ }
+
+ /**
+ * Create a trigger to map move keys
+ * to the move the player
+ * @return
+ * The trigger.
+ */
+ private InputTrigger CreateMoveTrigger()
+ {
+
+ final UprightFlyControlTriggers control = this;
+
+ final Predicate<TwoInputStates> keysHeld =
+ moveConfig.createSinglePredicate(moveConfig.createSomeMoveKeysDownPredicates());
+
+ final TriggerAction moveAction = new TriggerAction() {
+ @Override
+ public void perform(final Canvas source,
+ final TwoInputStates inputStates, final double tpf) {
+ control.move(inputStates, tpf);
+ }
+ };
+
+ return new InputTrigger(keysHeld, moveAction);
+ }
+
+ /**
+ * Create a predicate for mouse movements
+ * @param mouseTurnDragOnly
+ * True if looking requres
+ * dragging
+ * @return
+ * The predicate for mouse turning.
+ */
+ private Predicate<TwoInputStates> CreateMouseTurnPredicate(
+ boolean mouseTurnDragOnly)
+ {
+
+ // Mouse look
+ final Predicate<TwoInputStates> someMouseDown =
+ TriggerConditions.leftButtonDown().or(TriggerConditions.rightButtonDown()).or(TriggerConditions.middleButtonDown());
+ final Predicate<TwoInputStates> dragged = TriggerConditions.mouseMoved().and(someMouseDown);
+
+ final Predicate<TwoInputStates> mouseLookUpdate =
+ mouseTurnDragOnly
+ ? dragged
+ : TriggerConditions.mouseMoved();
+
+ return mouseLookUpdate;
+
+ }
+
+ /**
+ * Creates a trigger for managing looking
+ * and turning.
+ * @param mouseLookDragOnly
+ * @return
+ * The trigger for the player
+ * to look and turn.
+ */
+ private InputTrigger CreateTurnTrigger()
+ {
+
+ final UprightFlyControlTriggers control = this;
+
+ final Predicate<TwoInputStates> keysHeld =
+ moveConfig.createSinglePredicate(moveConfig.createSomeTurnKeyPredicates());
+
+ final Predicate<TwoInputStates> turnPredicate = keysHeld.or(mouseTurnPredicate);
+
+ final TriggerAction turnAction = new TriggerAction() {
+
+ @Override
+ public void perform(final Canvas source,
+ final TwoInputStates inputStates, final double tpf) {
+ control.turn(inputStates, tpf);
+ }
+ };
+
+ return new InputTrigger(turnPredicate, turnAction);
+
+ }
+
+ /**
+ * Add the triggers to the logical layer
+ */
+ @Override
+ public void AddToLayer(LogicalLayer layer)
+ {
+ layer.registerTrigger(moveTrigger);
+ layer.registerTrigger(turnTrigger);
+ }
+
+ /**
+ * Remove the triggers from the logical layer
+ */
+ @Override
+ public void RemoveFromLayer(LogicalLayer layer)
+ {
+ layer.deregisterTrigger(moveTrigger);
+ layer.deregisterTrigger(turnTrigger);
+ }
+
+ /**
+ * Move the player according to the
+ * triggers
+ * @param state
+ * The input state.
+ * @param tpf
+ * The time per frame.
+ */
+ protected void move(
+ final TwoInputStates state,
+ final double tpf)
+ {
+ // MOVEMENT
+ int moveFB = 0, strafeLR = 0;
+ if (moveForwardPredicate.test(state)) {
+ moveFB += 1;
+ }
+ if (moveBackPredicate.test(state)) {
+ moveFB -= 1;
+ }
+ if (moveLeftPredicate.test(state)) {
+ strafeLR += 1;
+ }
+ if (moveRightPredicate.test(state)) {
+ strafeLR -= 1;
+ }
+
+ if (moveFB != 0 || strafeLR != 0) {
+ final Vector3 loc = new Vector3(Vector3.ZERO);
+ if (moveFB == 1) {
+ loc.addLocal(player.getDirection());
+ } else if (moveFB == -1) {
+ loc.subtractLocal(player.getDirection());
+ }
+ if (strafeLR == 1) {
+ loc.addLocal(player.getLeft());
+ } else if (strafeLR == -1) {
+ loc.subtractLocal(player.getLeft());
+ }
+ loc.normalizeLocal().multiplyLocal(moveSpeed * tpf)
+ .addLocal(player.getPosition());
+ player.getPosition().set(loc);
+ }
+ }
+
+ /**
+ * Turns the player
+ * @param state
+ * The input state for the
+ * turn
+ * @param tpf
+ * The time per frame for
+ * the last frame.
+ */
+ protected void turn(
+ final TwoInputStates state,
+ final double tpf)
+ {
+
+ final double keyboardRotationFactor =
+ keyRotateSpeed / mouseRotateSpeed * tpf;
+
+ // ROTATION
+ double rotX = 0, rotY = 0;
+
+ if (turnUpPredicate.test(state)) {
+ rotY -= keyboardRotationFactor;
+ }
+ if (turnDownPredicate.test(state)) {
+ rotY += keyboardRotationFactor;
+ }
+ if (turnLeftPredicate.test(state)) {
+ rotX += keyboardRotationFactor;
+ }
+ if (turnRightPredicate.test(state)) {
+ rotX -= keyboardRotationFactor;
+ }
+
+ if(mouseTurnPredicate.test(state))
+ {
+ if(firstPing)
+ {
+ firstPing = false;
+ }
+ else
+ {
+ final MouseState mouse = state.getCurrent()
+ .getMouseState();
+ rotX -= mouse.getDx();
+ rotY -= mouse.getDy();
+ }
+ }
+
+ rotate(rotX, rotY);
+ }
+
+ /**
+ * Rotate the player by the desired
+ * amounts
+ * @param dx
+ * The x angle to rotate
+ * @param dy
+ * The y angle to rotate
+ */
+ protected void rotate(final double dx, final double dy) {
+
+ if (dx != 0) {
+ workerMatrix.fromAngleNormalAxis(mouseRotateSpeed * dx,
+ upAxis != null ? upAxis : player.getUp());
+ workerMatrix.applyPost(player.getLeft(), workerStoreA);
+ player.getLeft().set(workerStoreA);
+ workerMatrix.applyPost(player.getDirection(), workerStoreA);
+ player.getDirection().set(workerStoreA);
+ workerMatrix.applyPost(player.getUp(), workerStoreA);
+ player.getUp().set(workerStoreA);
+ }
+
+ if (dy != 0) {
+ workerMatrix.fromAngleNormalAxis(mouseRotateSpeed * dy,
+ player.getLeft());
+ workerMatrix.applyPost(player.getLeft(), workerStoreA);
+ player.getLeft().set(workerStoreA);
+ workerMatrix.applyPost(player.getDirection(), workerStoreA);
+ player.getDirection().set(workerStoreA);
+ workerMatrix.applyPost(player.getUp(), workerStoreA);
+ player.getUp().set(workerStoreA);
+ }
+
+ player.normalize();
+
+ }
+
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/control/WalkControl.java b/ardor3d-craft/src/main/java/com/ardorcraft/control/WalkControl.java
new file mode 100644
index 0000000..cc77dbd
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/control/WalkControl.java
@@ -0,0 +1,318 @@
+/**
+ * Copyright (c) 2008-2010 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardorcraft.control;
+
+import java.util.function.Predicate;
+
+import com.ardor3d.framework.Canvas;
+//import com.ardor3d.input.Key;
+//import com.ardor3d.input.KeyboardState;
+import com.ardor3d.input.MouseState;
+import com.ardor3d.input.logical.InputTrigger;
+import com.ardor3d.input.logical.LogicalLayer;
+import com.ardor3d.input.logical.TriggerAction;
+import com.ardor3d.input.logical.TriggerConditions;
+import com.ardor3d.input.logical.TwoInputStates;
+import com.ardor3d.math.Matrix3;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardorcraft.player.PlayerBase;
+
+public class WalkControl {
+
+ private final Vector3 _upAxis = new Vector3();
+ private double _mouseRotateSpeed = .003;
+ private double _moveSpeed = 0.8;
+ private double _keyRotateSpeed = 2.25;
+ private final Matrix3 _workerMatrix = new Matrix3();
+ private final Vector3 _workerStoreA = new Vector3();
+ private InputTrigger _mouseTrigger;
+ private InputTrigger _keyTrigger;
+ private final LogicalLayer layer;
+ private final PlayerBase player;
+
+ private SomeButtonsDownPredicate moveLeftPredicate;
+ private SomeButtonsDownPredicate moveRightPredicate;
+ private SomeButtonsDownPredicate moveForwardPredicate;
+ private SomeButtonsDownPredicate moveBackPredicate;
+ private SomeButtonsDownPredicate turnLeftPredicate;
+ private SomeButtonsDownPredicate turnRightPredicate;
+ private SomeButtonsDownPredicate turnUpPredicate;
+ private SomeButtonsDownPredicate turnDownPredicate;
+ private UprightFPSMoveConfig moveConfigMember;
+
+ public WalkControl(final PlayerBase player, final LogicalLayer layer,
+ final ReadOnlyVector3 upAxis, UprightFPSMoveConfig moveConfigParam) {
+ _upAxis.set(upAxis);
+ this.layer = layer;
+ this.player = player;
+
+ moveConfigMember = moveConfigParam;
+ moveLeftPredicate =
+ new SomeButtonsDownPredicate(moveConfigMember.MoveLeft);
+ moveRightPredicate =
+ new SomeButtonsDownPredicate(moveConfigMember.MoveRight);
+ moveForwardPredicate =
+ new SomeButtonsDownPredicate(moveConfigMember.MoveForward);
+ moveBackPredicate =
+ new SomeButtonsDownPredicate(moveConfigMember.MoveBack);
+ turnLeftPredicate =
+ new SomeButtonsDownPredicate(moveConfigMember.TurnLeft);
+ turnRightPredicate =
+ new SomeButtonsDownPredicate(moveConfigMember.TurnRight);
+ turnUpPredicate =
+ new SomeButtonsDownPredicate(moveConfigMember.TurnUp);
+ turnDownPredicate =
+ new SomeButtonsDownPredicate(moveConfigMember.TurnDown);
+ }
+
+ public ReadOnlyVector3 getUpAxis() {
+ return _upAxis;
+ }
+
+ public void setUpAxis(final ReadOnlyVector3 upAxis) {
+ _upAxis.set(upAxis);
+ }
+
+ public double getMouseRotateSpeed() {
+ return _mouseRotateSpeed;
+ }
+
+ public void setMouseRotateSpeed(final double speed) {
+ _mouseRotateSpeed = speed;
+ }
+
+ public double getMoveSpeed() {
+ return _moveSpeed;
+ }
+
+ public void setMoveSpeed(final double speed) {
+ _moveSpeed = speed;
+ }
+
+ public double getKeyRotateSpeed() {
+ return _keyRotateSpeed;
+ }
+
+ public void setKeyRotateSpeed(final double speed) {
+ _keyRotateSpeed = speed;
+ }
+
+ protected void move(
+ final TwoInputStates state,
+ final double tpf) {
+ // MOVEMENT
+ int moveFB = 0, strafeLR = 0;
+ if (moveForwardPredicate.test(state)) {
+ moveFB += 1;
+ }
+ if (moveBackPredicate.test(state)) {
+ moveFB -= 1;
+ }
+ if (moveLeftPredicate.test(state)) {
+ strafeLR += 1;
+ }
+ if (moveRightPredicate.test(state)) {
+ strafeLR -= 1;
+ }
+
+ if (moveFB != 0 || strafeLR != 0) {
+ final Vector3 loc = _workerStoreA.zero();
+ if (moveFB == 1) {
+ loc.addLocal(player.getDirection());
+ } else if (moveFB == -1) {
+ loc.subtractLocal(player.getDirection());
+ }
+ if (strafeLR == 1) {
+ loc.addLocal(player.getLeft());
+ } else if (strafeLR == -1) {
+ loc.subtractLocal(player.getLeft());
+ }
+ loc.setY(0);
+ loc.normalizeLocal();
+ player.getAcceleration().set(loc).multiplyLocal(_moveSpeed);
+
+ // TODO
+ // player.getTargetVelocity().x = loc.getX() * 80 *
+ // PhysicsSystem.TIMESTEP;
+ // player.getTargetVelocity().z = loc.getZ() * 80 *
+ // PhysicsSystem.TIMESTEP;
+ }
+
+ // ROTATION
+ int rotX = 0, rotY = 0;
+ if (turnUpPredicate.test(state)) {
+ rotY -= 1;
+ }
+ if (turnDownPredicate.test(state)) {
+ rotY += 1;
+ }
+ if (turnLeftPredicate.test(state)) {
+ rotX += 1;
+ }
+ if (turnRightPredicate.test(state)) {
+ rotX -= 1;
+ }
+ if (rotX != 0 || rotY != 0) {
+ rotate(rotX * _keyRotateSpeed / _mouseRotateSpeed * tpf, rotY
+ * _keyRotateSpeed / _mouseRotateSpeed * tpf);
+ }
+ }
+
+ protected void rotate(final double dx, final double dy) {
+
+ if (dx != 0) {
+ _workerMatrix.fromAngleNormalAxis(_mouseRotateSpeed * dx,
+ _upAxis != null ? _upAxis : player.getUp());
+ _workerMatrix.applyPost(player.getLeft(), _workerStoreA);
+ player.getLeft().set(_workerStoreA);
+ _workerMatrix.applyPost(player.getDirection(), _workerStoreA);
+ player.getDirection().set(_workerStoreA);
+ _workerMatrix.applyPost(player.getUp(), _workerStoreA);
+ player.getUp().set(_workerStoreA);
+ }
+
+ if (dy != 0) {
+ _workerMatrix.fromAngleNormalAxis(_mouseRotateSpeed * dy,
+ player.getLeft());
+ _workerMatrix.applyPost(player.getLeft(), _workerStoreA);
+ player.getLeft().set(_workerStoreA);
+ _workerMatrix.applyPost(player.getDirection(), _workerStoreA);
+ player.getDirection().set(_workerStoreA);
+ _workerMatrix.applyPost(player.getUp(), _workerStoreA);
+ player.getUp().set(_workerStoreA);
+ }
+
+ player.normalize();
+ }
+
+ /**
+ * @param layer
+ * the logical layer to register with
+ * @param upAxis
+ * the up axis of the camera
+ * @param dragOnly
+ * if true, mouse input will only rotate the camera if one of the
+ * mouse buttons (left, center or right) is down.
+ * @return a new FlyControl object
+ */
+ public static WalkControl setupTriggers(final PlayerBase player,
+ final LogicalLayer layer, final ReadOnlyVector3 upAxis,
+ final boolean dragOnly) {
+
+ final WalkControl control =
+ new WalkControl(
+ player,
+ layer,
+ upAxis,
+ new UprightFPSMoveConfig(UprightFPSMoveConfig.defaultControls.RightHanded));
+ control.setupKeyboardTriggers(layer);
+ control.setupMouseTriggers(layer, dragOnly);
+ return control;
+ }
+
+ /**
+ * Deregister the triggers of the given FlyControl from the given
+ * LogicalLayer.
+ *
+ * @param layer
+ * @param control
+ */
+ public static void removeTriggers(final LogicalLayer layer,
+ final WalkControl control) {
+ if (control._mouseTrigger != null) {
+ layer.deregisterTrigger(control._mouseTrigger);
+ }
+ if (control._keyTrigger != null) {
+ layer.deregisterTrigger(control._keyTrigger);
+ }
+ }
+
+ public void setupMouseTriggers(final LogicalLayer layer,
+ final boolean dragOnly) {
+ final WalkControl control = this;
+ // Mouse look
+ final Predicate<TwoInputStates> someMouseDown =
+ TriggerConditions.leftButtonDown().or(TriggerConditions.rightButtonDown().or(TriggerConditions.middleButtonDown()));
+ final Predicate<TwoInputStates> dragged = TriggerConditions.mouseMoved().and(someMouseDown);
+ final TriggerAction dragAction = new TriggerAction() {
+
+ // Test boolean to allow us to ignore first mouse event. First event
+ // can wildly vary based on platform.
+ private boolean firstPing = true;
+
+ @Override
+ public void perform(final Canvas source,
+ final TwoInputStates inputStates, final double tpf) {
+ final MouseState mouse = inputStates.getCurrent()
+ .getMouseState();
+ if (mouse.getDx() != 0 || mouse.getDy() != 0) {
+ if (!firstPing) {
+ control.rotate(-mouse.getDx(), -mouse.getDy());
+ } else {
+ firstPing = false;
+ }
+ }
+ }
+ };
+
+ _mouseTrigger = new InputTrigger(dragOnly ? dragged
+ : TriggerConditions.mouseMoved(), dragAction);
+ layer.registerTrigger(_mouseTrigger);
+ }
+
+ public Predicate<TwoInputStates> setupKeyboardTriggers(
+ final LogicalLayer layer) {
+
+ final WalkControl control = this;
+
+ // WASD control
+ final Predicate<TwoInputStates> keysHeld =
+ moveConfigMember.createSinglePredicate(moveConfigMember.createKeysDownPredicates());
+
+ final TriggerAction moveAction = new TriggerAction() {
+ @Override
+ public void perform(final Canvas source,
+ final TwoInputStates inputStates, final double tpf) {
+ control.move(inputStates, tpf);
+ }
+ };
+ _keyTrigger = new InputTrigger(keysHeld, moveAction);
+ layer.registerTrigger(_keyTrigger);
+ return keysHeld;
+ }
+
+ public InputTrigger getKeyTrigger() {
+ return _keyTrigger;
+ }
+
+ public InputTrigger getMouseTrigger() {
+ return _mouseTrigger;
+ }
+
+ public void enable() {
+ if (!layer.getTriggers().contains(_keyTrigger)) {
+ layer.registerTrigger(_keyTrigger);
+ }
+ if (!layer.getTriggers().contains(_mouseTrigger)) {
+ layer.registerTrigger(_mouseTrigger);
+ }
+ }
+
+ public void disable() {
+ if (layer.getTriggers().contains(_keyTrigger)) {
+ layer.deregisterTrigger(_keyTrigger);
+ }
+ if (layer.getTriggers().contains(_mouseTrigger)) {
+ layer.deregisterTrigger(_mouseTrigger);
+ }
+ }
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/control/WalkControlTriggers.java b/ardor3d-craft/src/main/java/com/ardorcraft/control/WalkControlTriggers.java
new file mode 100644
index 0000000..0483105
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/control/WalkControlTriggers.java
@@ -0,0 +1,352 @@
+/**
+ *
+ */
+package com.ardorcraft.control;
+
+import java.util.function.Predicate;
+
+import com.ardor3d.framework.Canvas;
+import com.ardor3d.input.MouseState;
+import com.ardor3d.input.logical.InputTrigger;
+import com.ardor3d.input.logical.LogicalLayer;
+import com.ardor3d.input.logical.TriggerAction;
+import com.ardor3d.input.logical.TriggerConditions;
+import com.ardor3d.input.logical.TwoInputStates;
+import com.ardor3d.math.Matrix3;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardorcraft.player.PlayerBase;
+
+/**
+ * Set of triggers to use for walking.
+ * @author nathan
+ *
+ */
+public class WalkControlTriggers implements ITriggerGroup
+{
+ private final Vector3 upAxis = new Vector3();
+ private double mouseRotateSpeed = .003;
+ private double moveSpeed = 0.8;
+ private double keyRotateSpeed = 2.25;
+ private final Matrix3 workerMatrix = new Matrix3();
+ private final Vector3 workerStoreA = new Vector3();
+ private InputTrigger turnTrigger;
+ private InputTrigger moveTrigger;
+ private final PlayerBase player;
+
+ // The first mouse state is pretty random,
+ // so arrange to ignore it. I'm not sure
+ // this should be there, but I'm not sure
+ // if Ardor3D allows not firing the
+ // triggers on the first update.
+ private boolean firstPing = true;
+
+ private Predicate<TwoInputStates> mouseTurnPredicate;
+
+ private SomeButtonsDownPredicate moveLeftPredicate;
+ private SomeButtonsDownPredicate moveRightPredicate;
+ private SomeButtonsDownPredicate moveForwardPredicate;
+ private SomeButtonsDownPredicate moveBackPredicate;
+ private SomeButtonsDownPredicate turnLeftPredicate;
+ private SomeButtonsDownPredicate turnRightPredicate;
+ private SomeButtonsDownPredicate turnUpPredicate;
+ private SomeButtonsDownPredicate turnDownPredicate;
+ private UprightFPSMoveConfig moveConfig;
+
+ /**
+ * Constructor for the WalkControlTriggers
+ * @param playerParam
+ * The player for the triggers
+ * to update
+ * @param upAxisParam
+ * The up axis of the player
+ * @param mouseLookDragOnly
+ * True if mouse look requires
+ * draging
+ * @param moveConfigParam
+ * The keys for for moving and turning.
+ */
+ public WalkControlTriggers(
+ final PlayerBase playerParam,
+ final ReadOnlyVector3 upAxisParam,
+ final boolean mouseLookDragOnly,
+ final UprightFPSMoveConfig moveConfigParam)
+ {
+ this.player = playerParam;
+ upAxis.set(upAxisParam);
+ moveConfig = moveConfigParam;
+
+ moveLeftPredicate =
+ new SomeButtonsDownPredicate(moveConfig.MoveLeft);
+ moveRightPredicate =
+ new SomeButtonsDownPredicate(moveConfig.MoveRight);
+ moveForwardPredicate =
+ new SomeButtonsDownPredicate(moveConfig.MoveForward);
+ moveBackPredicate =
+ new SomeButtonsDownPredicate(moveConfig.MoveBack);
+ turnLeftPredicate =
+ new SomeButtonsDownPredicate(moveConfig.TurnLeft);
+ turnRightPredicate =
+ new SomeButtonsDownPredicate(moveConfig.TurnRight);
+ turnUpPredicate =
+ new SomeButtonsDownPredicate(moveConfig.TurnUp);
+ turnDownPredicate =
+ new SomeButtonsDownPredicate(moveConfig.TurnDown);
+
+ moveTrigger = CreateMoveTrigger();
+
+ mouseTurnPredicate = CreateMouseTurnPredicate(
+ mouseLookDragOnly);
+
+ turnTrigger = CreateTurnTrigger();
+ }
+
+ /**
+ * Create a trigger to map move keys
+ * to the move the player
+ * @return
+ * The trigger.
+ */
+ private InputTrigger CreateMoveTrigger()
+ {
+
+ final WalkControlTriggers control = this;
+
+ final Predicate<TwoInputStates> keysHeld =
+ moveConfig.createSinglePredicate(moveConfig.createSomeMoveKeysDownPredicates());
+
+ final TriggerAction moveAction = new TriggerAction() {
+ @Override
+ public void perform(final Canvas source,
+ final TwoInputStates inputStates, final double tpf) {
+ control.move(inputStates, tpf);
+ }
+ };
+
+ return new InputTrigger(keysHeld, moveAction);
+ }
+
+ /**
+ * Create a predicate for mouse movements
+ * @param mouseTurnDragOnly
+ * True if looking requres
+ * dragging
+ * @return
+ * The predicate for mouse turning.
+ */
+ private Predicate<TwoInputStates> CreateMouseTurnPredicate(
+ boolean mouseTurnDragOnly)
+ {
+
+ // Mouse look
+ final Predicate<TwoInputStates> someMouseDown =
+ TriggerConditions.leftButtonDown().or(TriggerConditions.rightButtonDown().or(TriggerConditions.middleButtonDown()));
+ final Predicate<TwoInputStates> dragged = TriggerConditions.mouseMoved().and(someMouseDown);
+
+ final Predicate<TwoInputStates> mouseLookUpdate =
+ mouseTurnDragOnly
+ ? dragged
+ : TriggerConditions.mouseMoved();
+
+ return mouseLookUpdate;
+
+ }
+
+ /**
+ * Creates a trigger for managing looking
+ * and turning.
+ * @param mouseLookDragOnly
+ * @return
+ * The trigger for the player
+ * to look and turn.
+ */
+ private InputTrigger CreateTurnTrigger()
+ {
+
+ final WalkControlTriggers control = this;
+
+ final Predicate<TwoInputStates> keysHeld =
+ moveConfig.createSinglePredicate(moveConfig.createSomeTurnKeyPredicates());
+
+ final Predicate<TwoInputStates> turnPredicate =
+ keysHeld.or(mouseTurnPredicate);
+
+ final TriggerAction turnAction = new TriggerAction() {
+
+ @Override
+ public void perform(final Canvas source,
+ final TwoInputStates inputStates, final double tpf) {
+ control.turn(inputStates, tpf);
+ }
+ };
+
+ return new InputTrigger(turnPredicate, turnAction);
+
+ }
+
+ /**
+ * Move in response to the controls
+ * @param state
+ * The input state
+ * @param tpf
+ * The time per frame.
+ */
+ protected void move(TwoInputStates state, double tpf)
+ {
+ // MOVEMENT
+ int moveFB = 0, strafeLR = 0;
+ if (moveForwardPredicate.test(state))
+ {
+ moveFB += 1;
+ }
+ if (moveBackPredicate.test(state))
+ {
+ moveFB -= 1;
+ }
+ if (moveLeftPredicate.test(state))
+ {
+ strafeLR += 1;
+ }
+ if (moveRightPredicate.test(state))
+ {
+ strafeLR -= 1;
+ }
+
+ if (moveFB != 0 || strafeLR != 0)
+ {
+ final Vector3 loc = workerStoreA.zero();
+ if (moveFB == 1)
+ {
+ loc.addLocal(player.getDirection());
+ }
+ else if (moveFB == -1)
+ {
+ loc.subtractLocal(player.getDirection());
+ }
+
+ if (strafeLR == 1)
+ {
+ loc.addLocal(player.getLeft());
+ }
+ else if (strafeLR == -1)
+ {
+ loc.subtractLocal(player.getLeft());
+ }
+ loc.setY(0);
+ loc.normalizeLocal();
+ loc.multiplyLocal(moveSpeed);
+ player.getAcceleration().set(loc);
+
+ // TODO
+ // player.getTargetVelocity().x = loc.getX() * 80 *
+ // PhysicsSystem.TIMESTEP;
+ // player.getTargetVelocity().z = loc.getZ() * 80 *
+ // PhysicsSystem.TIMESTEP;
+ }
+ }
+
+ /**
+ * Turn the player in response to
+ * the input state.
+ * @param state
+ * The input state
+ * @param tpf
+ * The time per frame.
+ */
+ protected void turn(TwoInputStates state, double tpf)
+ {
+
+ final double keyboardRotationFactor =
+ keyRotateSpeed / mouseRotateSpeed * tpf;
+
+ // ROTATION
+ double rotX = 0, rotY = 0;
+
+ if (turnUpPredicate.test(state)) {
+ rotY -= keyboardRotationFactor;
+ }
+ if (turnDownPredicate.test(state)) {
+ rotY += keyboardRotationFactor;
+ }
+ if (turnLeftPredicate.test(state)) {
+ rotX += keyboardRotationFactor;
+ }
+ if (turnRightPredicate.test(state)) {
+ rotX -= keyboardRotationFactor;
+ }
+
+ if(mouseTurnPredicate.test(state))
+ {
+ if(firstPing)
+ {
+ firstPing = false;
+ }
+ else
+ {
+ final MouseState mouse = state.getCurrent()
+ .getMouseState();
+ rotX -= mouse.getDx();
+ rotY -= mouse.getDy();
+ }
+ }
+
+ rotate(rotX, rotY);
+ }
+
+ /**
+ * Rotate the player by the desired
+ * amounts
+ * @param dx
+ * The x angle to rotate
+ * @param dy
+ * The y angle to rotate
+ */
+ protected void rotate(final double dx, final double dy) {
+
+ if (dx != 0) {
+ workerMatrix.fromAngleNormalAxis(mouseRotateSpeed * dx,
+ upAxis != null ? upAxis : player.getUp());
+ workerMatrix.applyPost(player.getLeft(), workerStoreA);
+ player.getLeft().set(workerStoreA);
+ workerMatrix.applyPost(player.getDirection(), workerStoreA);
+ player.getDirection().set(workerStoreA);
+ workerMatrix.applyPost(player.getUp(), workerStoreA);
+ player.getUp().set(workerStoreA);
+ }
+
+ if (dy != 0) {
+ workerMatrix.fromAngleNormalAxis(mouseRotateSpeed * dy,
+ player.getLeft());
+ workerMatrix.applyPost(player.getLeft(), workerStoreA);
+ player.getLeft().set(workerStoreA);
+ workerMatrix.applyPost(player.getDirection(), workerStoreA);
+ player.getDirection().set(workerStoreA);
+ workerMatrix.applyPost(player.getUp(), workerStoreA);
+ player.getUp().set(workerStoreA);
+ }
+
+ player.normalize();
+
+ }
+
+ /* (non-Javadoc)
+ * @see com.ardorcraft.control.ITriggerGroup#AddToLayer(com.ardor3d.input.logical.LogicalLayer)
+ */
+ @Override
+ public void AddToLayer(LogicalLayer layer)
+ {
+ layer.registerTrigger(moveTrigger);
+ layer.registerTrigger(turnTrigger);
+ }
+
+ /* (non-Javadoc)
+ * @see com.ardorcraft.control.ITriggerGroup#RemoveFromLayer(com.ardor3d.input.logical.LogicalLayer)
+ */
+ @Override
+ public void RemoveFromLayer(LogicalLayer layer)
+ {
+ layer.deregisterTrigger(moveTrigger);
+ layer.deregisterTrigger(turnTrigger);
+ }
+
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/data/Direction.java b/ardor3d-craft/src/main/java/com/ardorcraft/data/Direction.java
new file mode 100644
index 0000000..50520dd
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/data/Direction.java
@@ -0,0 +1,11 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.data;
+
+public enum Direction {
+ X, Y, Z
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/data/Pos.java b/ardor3d-craft/src/main/java/com/ardorcraft/data/Pos.java
new file mode 100644
index 0000000..a9e8feb
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/data/Pos.java
@@ -0,0 +1,89 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.data;
+
+/**
+ * Integer 3D coordinate
+ */
+public class Pos {
+ public int x, y, z;
+
+ public Pos() {
+
+ }
+
+ public Pos(final Pos pos) {
+ this(pos.x, pos.y, pos.z);
+ }
+
+ public Pos(final int x, final int y, final int z) {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + x;
+ result = prime * result + y;
+ result = prime * result + z;
+ return result;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof Pos)) {
+ return false;
+ }
+ final Pos other = (Pos) obj;
+ if (x != other.x) {
+ return false;
+ }
+ if (y != other.y) {
+ return false;
+ }
+ if (z != other.z) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "Pos [x=" + x + ", y=" + y + ", z=" + z + "]";
+ }
+
+ public void set(final int x, final int y, final int z) {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ }
+
+ public void set(final Pos pos) {
+ set(pos.x, pos.y, pos.z);
+ }
+
+ public void add(final int x, final int y, final int z) {
+ this.x += x;
+ this.y += y;
+ this.z += z;
+ }
+
+ public void subtract(final int x, final int y, final int z) {
+ this.x -= x;
+ this.y -= y;
+ this.z -= z;
+ }
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/file/WorldFile.java b/ardor3d-craft/src/main/java/com/ardorcraft/file/WorldFile.java
new file mode 100644
index 0000000..28c8330
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/file/WorldFile.java
@@ -0,0 +1,313 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.file;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.logging.Logger;
+import java.util.zip.DataFormatException;
+import java.util.zip.Deflater;
+import java.util.zip.Inflater;
+
+/**
+ * ArdorCraft map file format
+ * <p>
+ *
+ * Layout of each chunk data in the file:
+ * <ul>
+ * <li>byte 0 = this key (long, 8)
+ * <li>byte 8 = next key pos (long, 8)
+ * <li>byte 16 = this pos (long, 8 (0 = +4, >0 = to start of next "this pos"))
+ * <li>byte 24 = data size (int, 4)
+ * <li>byte 28 = compressed data (byte[])
+ *
+ */
+public final class WorldFile {
+ private static final Logger logger = Logger.getLogger(WorldFile.class.getName());
+
+ private final RandomAccessFile worldFile;
+ private final Map<Long, Long> mapping = new HashMap<>();
+ private final Lock lock = new ReentrantLock();
+ private final Deflater deflator = new Deflater();
+ private final Inflater inflator = new Inflater();
+ private final byte[] buf = new byte[1024 * 1024];
+ private static final int IDENTIFIER = "ArdorCraft Map".hashCode();
+ private static final int VERSION = 1;
+
+ public WorldFile(final File file) throws Exception {
+ worldFile = new RandomAccessFile(file, "rw");
+ parse();
+ }
+
+ public void close() throws IOException {
+ lock.lock();
+ try {
+ deflator.end();
+ inflator.end();
+ worldFile.close();
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ public long size() throws IOException {
+ return worldFile.length();
+ }
+
+ public boolean contains(final int x, final int z) {
+ lock.lock();
+ try {
+ return mapping.containsKey(getKey(x, z));
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ private void parse() throws IOException {
+ lock.lock();
+ try {
+ final long endPos = worldFile.length();
+
+ worldFile.seek(0);
+ if (endPos == 0) {
+ worldFile.writeInt(IDENTIFIER);
+ worldFile.writeInt(VERSION);
+ return;
+ }
+ if (endPos >= 4) {
+ final boolean isMapFile = worldFile.readInt() == IDENTIFIER;
+ if (!isMapFile) {
+ throw new RuntimeException("This is not an ArdorCraft map file!");
+ }
+ }
+ if (endPos >= 8) {
+ final int version = worldFile.readInt();
+ if (version < VERSION) {
+ throw new RuntimeException("Map file was created with an older version of the ArdorCraft API ("
+ + version + " < " + VERSION + ")");
+ }
+ logger.info("Map file version: " + version);
+ }
+
+ long pos = 8;
+ while (true) {
+ if (pos >= endPos) {
+ logger.info("Map file contains " + mapping.keySet().size() + " chunks");
+ return;
+ }
+ worldFile.seek(pos);
+ final long key = worldFile.readLong();
+ final long nextKeyPos = worldFile.readLong();
+
+ if (nextKeyPos < 0 || nextKeyPos > endPos) {
+ logger.warning("Corrupt mapping, nextKeyPos: " + nextKeyPos);
+ return;
+ }
+
+ if (mapping.containsKey(key)) {
+ pos = nextKeyPos;
+ continue;
+ }
+
+ long thisPos = worldFile.readLong();
+ while (thisPos > 0) {
+ worldFile.seek(thisPos);
+ thisPos = worldFile.readLong();
+ }
+
+ mapping.put(key, worldFile.getFilePointer() - 24);
+ pos = nextKeyPos;
+
+ // final int x = WorldFile.getCoordinateX(key);
+ // final int z = WorldFile.getCoordinateZ(key);
+ // System.out.println("parsed: " + x + "," + z + " = " + (worldFile.getFilePointer() - 24));
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ public void remap() throws IOException {
+ lock.lock();
+ try {
+ // File file = File.createTempFile("foo", null);
+ final File file = new File("tmpFile.tmp");
+ if (file.exists()) {
+ file.delete();
+ }
+ try (final RandomAccessFile tmpFile = new RandomAccessFile(file, "rw")) {
+
+ tmpFile.writeInt(IDENTIFIER);
+ tmpFile.writeInt(VERSION);
+ for (final Entry<Long, Long> entry : mapping.entrySet()) {
+ final long key = entry.getKey();
+ final long pos = entry.getValue();
+
+ worldFile.seek(pos + 24);
+ final int size = worldFile.readInt();
+
+ final long endPos = tmpFile.getFilePointer();
+
+ tmpFile.writeLong(key);
+ tmpFile.writeLong(endPos + 28 + size);
+ tmpFile.writeLong(0);
+ tmpFile.writeInt(size);
+
+ long transfered = 0;
+ while (transfered < size) {
+ transfered += worldFile.getChannel().transferTo(pos + 28 + transfered, size - transfered,
+ tmpFile.getChannel());
+ }
+ tmpFile.seek(endPos + 28 + size);
+ }
+
+ worldFile.seek(0);
+ final long size = tmpFile.length();
+ long transfered = 0;
+ while (transfered < size) {
+ transfered += tmpFile.getChannel().transferTo(transfered, size - transfered, worldFile.getChannel());
+ }
+ worldFile.setLength(size);
+
+ } finally {
+ file.delete();
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ public void save(final int x, final int z, final byte[] dataSource) throws IOException {
+ lock.lock();
+ try {
+ final long newKey = getKey(x, z);
+ final long endPosition = worldFile.length();
+
+ final byte[] data = compress(dataSource);
+ final int length = data.length;
+
+ if (!mapping.containsKey(newKey)) {
+ worldFile.seek(endPosition);
+ worldFile.writeLong(newKey);
+ worldFile.writeLong(endPosition + 28 + length);
+ worldFile.writeLong(0);
+ worldFile.writeInt(length);
+ worldFile.write(data);
+
+ mapping.put(newKey, endPosition);
+ } else {
+ final long pos = mapping.get(newKey);
+
+ // read size
+ worldFile.seek(pos + 24);
+ final int size = worldFile.readInt();
+ if (size < length) {
+ worldFile.seek(pos + 16);
+ worldFile.writeLong(endPosition + 16);
+
+ worldFile.seek(endPosition);
+ worldFile.writeLong(newKey);
+ worldFile.writeLong(endPosition + 28 + length);
+ worldFile.writeLong(0);
+ worldFile.writeInt(length);
+ worldFile.write(data);
+
+ mapping.put(newKey, endPosition);
+ } else {
+ worldFile.seek(pos + 24);
+ worldFile.writeInt(length);
+ worldFile.write(data);
+ }
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ public byte[] load(final int x, final int z) throws IOException {
+ final long newKey = getKey(x, z);
+ lock.lock();
+ try {
+ if (!mapping.containsKey(newKey)) {
+ logger.severe("No data found for coords: " + x + "," + z);
+ return null;
+ }
+ long pos = mapping.get(newKey);
+
+ pos += 24; // go to size
+ worldFile.seek(pos);
+ final int size = worldFile.readInt();
+ final byte[] data = new byte[size];
+ worldFile.read(data);
+
+ return decompress(data);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ public static long getKey(final int x, final int z) {
+ long r = x;
+ r <<= 32;
+ r |= z & 0XFFFFFFFFL;
+ return r;
+ }
+
+ public static int getCoordinateX(final long key) {
+ return (int) (key >> 32 & 0XFFFFFFFF);
+ }
+
+ public static int getCoordinateZ(final long key) {
+ return (int) (key & 0XFFFFFFFF);
+ }
+
+ private byte[] compress(final byte[] data) {
+ deflator.reset();
+ deflator.setInput(data);
+ deflator.finish();
+ final ByteArrayOutputStream bos = new ByteArrayOutputStream(data.length);
+ while (!deflator.finished()) {
+ final int count = deflator.deflate(buf);
+ if (count > 0) {
+ bos.write(buf, 0, count);
+ }
+ }
+ return bos.toByteArray();
+ }
+
+ private byte[] decompress(final byte[] input) {
+ inflator.reset();
+ inflator.setInput(input);
+ final ByteArrayOutputStream bos = new ByteArrayOutputStream(input.length);
+ try {
+ while (true) {
+ final int count = inflator.inflate(buf);
+ if (count > 0) {
+ bos.write(buf, 0, count);
+ } else if (count == 0 && inflator.finished()) {
+ break;
+ } else {
+ throw new RuntimeException("bad zip data, size:" + input.length);
+ }
+ }
+ } catch (final DataFormatException t) {
+ throw new RuntimeException(t);
+ }
+ return bos.toByteArray();
+ }
+
+ Map<Long, Long> getMapping() {
+ return mapping;
+ }
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/file/WorldFileViewer.java b/ardor3d-craft/src/main/java/com/ardorcraft/file/WorldFileViewer.java
new file mode 100644
index 0000000..6cf474a
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/file/WorldFileViewer.java
@@ -0,0 +1,96 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.file;
+
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.io.File;
+import java.util.Map;
+import java.util.Random;
+
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+
+import com.ardor3d.math.ColorRGBA;
+
+public class WorldFileViewer {
+ private final Graphics2D g2;
+ private final ColorRGBA[] colors;
+ private final ColorRGBA[] heightCol;
+ private final Color[] heightCol2;
+
+ private final int subMeshSize;
+ private final int height;
+ private final int subsize = 1;
+ private final int size = 16 * subsize;
+
+ private final WorldFile worldFile;
+
+ public WorldFileViewer(final File file, final int subMeshSize, final int height) throws Exception {
+ worldFile = new WorldFile(file);
+ this.subMeshSize = subMeshSize;
+ this.height = height;
+
+ final JPanel panel = new JPanel();
+ final JFrame frame = new JFrame("WorldFile Viewer");
+ frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ frame.getContentPane().add(panel);
+ frame.setBounds(10, 10, 1000, 1000);
+ frame.setVisible(true);
+ g2 = (Graphics2D) panel.getGraphics();
+
+ final Random rand = new Random(1337);
+ colors = new ColorRGBA[255];
+ for (int i = 0; i < 255; i++) {
+ colors[i] = new ColorRGBA(rand.nextFloat(), rand.nextFloat(), rand.nextFloat(), 1);
+ }
+
+ heightCol = new ColorRGBA[100];
+ heightCol2 = new Color[100];
+ for (int i = 0; i < 100; i++) {
+ final float v = i / 100.0f * 0.9f + 0.1f;
+ heightCol[i] = new ColorRGBA(v, v, v, 1);
+ heightCol2[i] = new Color(v, v, v);
+ }
+ }
+
+ public void view() throws Exception {
+ final Map<Long, Long> mapping = worldFile.getMapping();
+ for (final Long key : mapping.keySet()) {
+ final int x = WorldFile.getCoordinateX(key);
+ final int z = WorldFile.getCoordinateZ(key);
+ final byte[] dataSource = worldFile.load(x, z);
+ paintChunk(x, z, dataSource);
+ }
+ }
+
+ private void paintChunk(final int x, final int z, final byte[] dataSource) {
+ final int xPos = x * size + 400;
+ final int yPos = z * size + 400;
+
+ for (int xx = 0; xx < subMeshSize; xx++) {
+ for (int zz = 0; zz < subMeshSize; zz++) {
+ for (int yy = height - 1; yy >= 0; yy--) {
+ final int b = dataSource[xx + (yy + zz * height) * subMeshSize] & 0xff;
+
+ if (b != 0) {
+ // g2.setColor(colors[b]);
+ g2.setColor(heightCol2[yy]);
+
+ // final ColorRGBA c1 = colors[b];
+ // final ColorRGBA c2 = heightCol[yy];
+ // g2.setColor(new Color(c1.getRed() * c2.getRed(), c1.getGreen() * c2.getGreen(), c1.getBlue()
+ // * c2.getBlue()));
+
+ g2.fillRect(xPos + xx * subsize, yPos + zz * subsize, subsize, subsize);
+ break;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/generators/DataGenerator.java b/ardor3d-craft/src/main/java/com/ardorcraft/generators/DataGenerator.java
new file mode 100644
index 0000000..49d8b32
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/generators/DataGenerator.java
@@ -0,0 +1,37 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.generators;
+
+import com.ardorcraft.world.WorldModifier;
+
+/**
+ * A DataGenerator is responsible for generating the terrain in the world. It HAS to be thread safe!
+ */
+public interface DataGenerator {
+
+ /**
+ * Method to implement for custom terrain generation.
+ *
+ * @param xStart
+ * Starting point of generation in X
+ * @param zStart
+ * Starting point of generation in ZZ
+ * @param xEnd
+ * Ending point of generation in X
+ * @param zEnd
+ * Ending point of generation in Z
+ * @param spacing
+ * Space between sample data points (for faster zoom outs in map viewers etc)
+ * @param height
+ * Height of data to generate in Y
+ * @param proxy
+ * WorldModifier to build the world against (setBlock etc).
+ */
+ void generateChunk(final int xStart, final int zStart, final int xEnd, final int zEnd, int spacing,
+ final int height, final WorldModifier proxy);
+
+} \ No newline at end of file
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/generators/DefaultDataGenerator.java b/ardor3d-craft/src/main/java/com/ardorcraft/generators/DefaultDataGenerator.java
new file mode 100644
index 0000000..c4d40fe
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/generators/DefaultDataGenerator.java
@@ -0,0 +1,218 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.generators;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+import com.ardor3d.math.MathUtils;
+import com.ardorcraft.data.Pos;
+import com.ardorcraft.util.ImprovedNoise;
+import com.ardorcraft.world.BlockWorld;
+import com.ardorcraft.world.WorldModifier;
+
+public class DefaultDataGenerator implements DataGenerator {
+ private final int waterHeight = 20;
+
+ @Override
+ public void generateChunk(final int xStart, final int zStart, final int xEnd, final int zEnd, final int spacing,
+ final int height, final WorldModifier blockScene) {
+
+ final List<Pos> treePositions = new ArrayList<>();
+ final List<Integer> treeHeights = new ArrayList<>();
+ final Random rand = new Random(xStart * 10000 + zStart);
+
+ for (int x = xStart; x < xEnd; x += spacing) {
+ for (int z = zStart; z < zEnd; z += spacing) {
+ generateColumn(x, z, height, blockScene, xStart, zStart, xEnd, zEnd, treePositions, treeHeights, rand);
+ }
+ }
+
+ int index = 0;
+ for (final Pos pos : treePositions) {
+ final int treeHeight = treeHeights.get(index++);
+ addTree(blockScene, pos, treeHeight, rand);
+ }
+ }
+
+ private final int[] height = new int[] {
+ 5, 7, 9
+ };
+
+ private void addTree(final WorldModifier blockScene, final Pos pos, final int treeHeight, final Random rand) {
+ for (int y = 0; y < treeHeight; y++) {
+ blockScene.setBlock(pos.x, pos.y + y, pos.z, 17);
+ }
+
+ for (int x = 0; x < treeHeight; x++) {
+ for (int z = 0; z < treeHeight; z++) {
+ for (int y = 0; y < treeHeight; y++) {
+ final int xx = x - (treeHeight - 1) / 2;
+ final int yy = y - (treeHeight - 1) / 2;
+ final int zz = z - (treeHeight - 1) / 2;
+ if (xx == 0 && zz == 0 && yy <= 0) {
+ continue;
+ }
+ final double test = MathUtils.sqrt((double) xx * xx + yy * yy + zz * zz);
+ if (test < (treeHeight - 1.0) / 2.0) {
+ if (rand.nextDouble() < 0.8) {
+ blockScene.setBlock(pos.x + xx, pos.y + yy + treeHeight - 1, pos.z + zz, 18);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private void generateColumn(final int x, final int z, final int height, final WorldModifier blockScene,
+ final int xStart, final int zStart, final int xEnd, final int zEnd, final List<Pos> treePositions,
+ final List<Integer> treeHeights, final Random rand) {
+ final double gen = 5;
+
+ int localHeight = 0;
+
+ localHeight = generateLayer(x, z, gen + 0, 0.1, 0, 0.4f * height, 7, 0.2f, height, blockScene, rand);
+ localHeight = generateLayer(x, z, gen + 0.2, 1.5, localHeight, 0.08f * height + 1.5f * (localHeight - 10), 1,
+ 0.8f, height, blockScene, rand);
+ localHeight = generateLayer(x, z, gen + 0.5, 2.0, localHeight, 0.06f * height + 0.3f * (localHeight - 5), 3,
+ 0.6f, height, blockScene, rand);
+
+ // mountain
+ final double noise1 = ImprovedNoise.noise(x * 0.01, 20, z * 0.01) + 0.5;
+ final double noise3 = ImprovedNoise.noise(x * 0.05, 20, z * 0.05) + 0.5;
+ final double noise2 = ImprovedNoise.noise(x * 0.05, 100, z * 0.05);
+ double mul = (localHeight + height / 2.0) / height;
+ mul = 10.0 * MathUtils.clamp(mul, 0.0, 1.0);
+ int val = (int) (mul * noise1 * noise3 * (noise2 > 0.2 ? 1.0 : 0.0));
+ val = Math.max(0, val);
+ int type = 1;
+ for (int y = localHeight; y < localHeight + val; y++) {
+ if (y <= waterHeight + 1) {
+ type = 12;
+ }
+ final double scaleY = (Math.abs(y - height / 5) + 10.0) / height * 3.5;
+ final double scale = 0.05;
+ final double noise4 = ImprovedNoise.noise(x * scale, y * scale * 2.5, z * scale);
+ if (noise4 < scaleY) {
+ if (rand.nextDouble() < 0.1) {
+ blockScene.setBlock(x, y, z, 13);
+ } else {
+ blockScene.setBlock(x, y, z, type);
+ }
+ } else {
+ blockScene.setBlock(x, y, z, 0);
+ }
+ }
+ localHeight += val;
+
+ // sediment
+ final int block = blockScene.getBlock(x, localHeight - 1, z);
+ if (block == 3) {
+ if (localHeight - 1 <= waterHeight + 1) {
+ blockScene.setBlock(x, localHeight - 1, z, 12);
+ } else {
+ blockScene.setBlock(x, localHeight - 1, z, 2);
+ final boolean addedTree = checkAddTree(x, z, xStart, zStart, xEnd, zEnd, localHeight, treePositions,
+ treeHeights, rand);
+
+ final double noiseVegetation = ImprovedNoise.noise(x * 0.5, 0, z * 0.5) * Math.abs(noise3);
+ if (!addedTree && noiseVegetation > (double) (localHeight - waterHeight) / height) {
+ blockScene.setBlock(x, localHeight, z, 100 + MathUtils.rand.nextInt(8));
+ localHeight++;
+ }
+ }
+
+ if (noise2 < -0.4) {
+ final double noiseTree = ImprovedNoise.noise(x * 0.2, localHeight * 0.2, z * 0.2);
+ if (noiseTree > 0.4) {
+ final int mountainHeight = (int) ((noiseTree - 0.4) * 10);
+ for (int y = localHeight; y < localHeight + mountainHeight; y++) {
+ blockScene.setBlock(x, y, z, 1);
+ }
+ localHeight += mountainHeight;
+ }
+ }
+ }
+
+ if (localHeight < waterHeight) {
+ for (; localHeight < waterHeight; localHeight++) {
+ blockScene.setBlock(x, localHeight, z, BlockWorld.WATER);
+ }
+ }
+
+ for (int y = localHeight; y < height; y++) {
+ blockScene.setBlock(x, y, z, 0);
+ }
+ }
+
+ private boolean checkAddTree(final int x, final int z, final int xStart, final int zStart, final int xEnd,
+ final int zEnd, final int localHeight, final List<Pos> treePositions, final List<Integer> treeHeights,
+ final Random rand) {
+ final int treeHeight = height[rand.nextInt(height.length)];
+ final int testHeight = (treeHeight - 1) / 2;
+
+ if (x >= xStart + testHeight && x < xEnd - testHeight && z >= zStart + testHeight && z < zEnd - testHeight) {
+ final double noiseTree = (ImprovedNoise.noise(x * 0.01, localHeight * 0.005, z * 0.01) + 0.3) * 0.2;
+ final double r = rand.nextDouble();
+ if (noiseTree > r) {
+ treePositions.add(new Pos(x, localHeight, z));
+ treeHeights.add(treeHeight);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private int generateLayer(final int x, final int z, final double noiseVal, final double noiseScale,
+ final int startheight, float layerheight, final int type, final float adder, final int height,
+ final WorldModifier blockScene, final Random rand) {
+ layerheight = Math.max(0.0f, layerheight);
+
+ double noise = ImprovedNoise.noise(x * 0.01 * noiseScale, noiseVal, z * 0.01 * noiseScale) + adder;
+ double noise2 = ImprovedNoise.noise(x * 0.05 * noiseScale, noiseVal, z * 0.05 * noiseScale) + adder;
+
+ double phatnoise = ImprovedNoise.noise(x * 0.004, noiseVal, z * 0.004);
+ phatnoise = MathUtils.clamp(Math.abs(phatnoise) + 0.6, 0.0, 1.0);
+ noise2 *= phatnoise;
+ noise *= phatnoise;
+
+ int localHeight = (int) (noise * layerheight + noise2 * layerheight * 0.35);
+ localHeight = Math.max(0, localHeight);
+
+ for (int y = startheight; y < startheight + localHeight; y++) {
+ if (y <= 1) {
+ blockScene.setBlock(x, y, z, 12);
+ }
+ final double scaleY = (Math.abs(y - height / 3) + 15.0) / height * 1.5;
+ final double scale = 0.05;
+ final double noise3 = ImprovedNoise.noise(x * scale, y * scale * 2.0, z * scale);
+ if (noise3 < scaleY) {
+ if (type == 1) {
+ final int r = rand.nextInt(100);
+ if (r < 3) {
+ blockScene.setBlock(x, y, z, 14 + r);
+ } else {
+ blockScene.setBlock(x, y, z, type);
+ }
+ } else {
+ blockScene.setBlock(x, y, z, type);
+ }
+ } else if (noise3 - scaleY < 0.02) {
+ blockScene.setBlock(x, y, z, 1);
+ } else {
+ if (y < waterHeight) {
+ blockScene.setBlock(x, y, z, BlockWorld.WATER);
+ } else {
+ blockScene.setBlock(x, y, z, 0);
+ }
+ }
+ }
+
+ return startheight + localHeight;
+ }
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/objects/QuadBox.java b/ardor3d-craft/src/main/java/com/ardorcraft/objects/QuadBox.java
new file mode 100644
index 0000000..6d3469d
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/objects/QuadBox.java
@@ -0,0 +1,333 @@
+/**
+ * Copyright (c) 2008-2010 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardorcraft.objects;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.renderer.IndexMode;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.geom.BufferUtils;
+
+/**
+ * <code>QuadBox</code> is an axis-aligned rectangular prism defined by a center point and x, y, and z extents from that
+ * center (essentially radii.)
+ */
+public class QuadBox extends Mesh {
+
+ private double _xExtent, _yExtent, _zExtent;
+
+ private final Vector3 _center = new Vector3(0, 0, 0);
+
+ /**
+ * Constructs a new 1x1x1 <code>QuadBox</code>.
+ */
+ public QuadBox() {
+ this("unnamed QuadBox");
+ }
+
+ /**
+ * Constructs a new 1x1x1 <code>QuadBox</code> with the given name.
+ *
+ * @param name
+ * the name to give this new box. This is required for identification and comparison purposes.
+ */
+ public QuadBox(final String name) {
+ super(name);
+ setData(Vector3.ZERO, 0.5, 0.5, 0.5);
+ }
+
+ /**
+ * Constructs a new <code>QuadBox</code> object using the given two points as opposite corners of the box. These two
+ * points may be in any order.
+ *
+ * @param name
+ * the name to give this new box. This is required for identification and comparison purposes.
+ * @param pntA
+ * the first point
+ * @param pntB
+ * the second point.
+ */
+ public QuadBox(final String name, final ReadOnlyVector3 pntA, final ReadOnlyVector3 pntB) {
+ super(name);
+ setData(pntA, pntB);
+ }
+
+ /**
+ * Constructs a new <code>QuadBox</code> object using the given center and extents. Since the extents represent the
+ * distance from the center of the box to the edge, the full length of a side is actually 2 * extent.
+ *
+ * @param name
+ * the name to give this new box. This is required for identification and comparison purposes.
+ * @param center
+ * Center of the box.
+ * @param xExtent
+ * x extent of the box
+ * @param yExtent
+ * y extent of the box
+ * @param zExtent
+ * z extent of the box
+ */
+ public QuadBox(final String name, final ReadOnlyVector3 center, final double xExtent, final double yExtent,
+ final double zExtent) {
+ super(name);
+ setData(center, xExtent, yExtent, zExtent);
+ }
+
+ /**
+ * @return the current center of this box.
+ */
+ public ReadOnlyVector3 getCenter() {
+ return _center;
+ }
+
+ /**
+ * @return the current X extent of this box.
+ */
+ public double getXExtent() {
+ return _xExtent;
+ }
+
+ /**
+ * @return the current Y extent of this box.
+ */
+ public double getYExtent() {
+ return _yExtent;
+ }
+
+ /**
+ * @return the current Z extent of this box.
+ */
+ public double getZExtent() {
+ return _zExtent;
+ }
+
+ /**
+ * Updates the center point and extents of this box to match an axis-aligned box defined by the two given opposite
+ * corners.
+ *
+ * @param pntA
+ * the first point
+ * @param pntB
+ * the second point.
+ */
+ public void setData(final ReadOnlyVector3 pntA, final ReadOnlyVector3 pntB) {
+ _center.set(pntB).addLocal(pntA).multiplyLocal(0.5);
+
+ final double x = Math.abs(pntB.getX() - _center.getX());
+ final double y = Math.abs(pntB.getY() - _center.getY());
+ final double z = Math.abs(pntB.getZ() - _center.getZ());
+ setData(_center, x, y, z);
+ }
+
+ /**
+ * Updates the center point and extents of this box using the defined values.
+ *
+ * @param center
+ * The center of the box.
+ * @param xExtent
+ * x extent of the box
+ * @param yExtent
+ * y extent of the box
+ * @param zExtent
+ * z extent of the box
+ */
+ public void setData(final ReadOnlyVector3 center, final double xExtent, final double yExtent, final double zExtent) {
+ if (center != null) {
+ _center.set(center);
+ }
+
+ _xExtent = xExtent;
+ _yExtent = yExtent;
+ _zExtent = zExtent;
+
+ setVertexData();
+ setNormalData();
+ setTextureData();
+ setIndexData();
+
+ }
+
+ /**
+ * <code>setVertexData</code> sets the vertex positions that define the box using the center point and defined
+ * extents.
+ */
+ protected void setVertexData() {
+ if (_meshData.getVertexBuffer() == null) {
+ _meshData.setVertexBuffer(BufferUtils.createVector3Buffer(24));
+ }
+
+ final Vector3[] vert = computeVertices(); // returns 8
+
+ // Back
+ BufferUtils.setInBuffer(vert[0], _meshData.getVertexBuffer(), 0);
+ BufferUtils.setInBuffer(vert[1], _meshData.getVertexBuffer(), 1);
+ BufferUtils.setInBuffer(vert[2], _meshData.getVertexBuffer(), 2);
+ BufferUtils.setInBuffer(vert[3], _meshData.getVertexBuffer(), 3);
+
+ // Right
+ BufferUtils.setInBuffer(vert[1], _meshData.getVertexBuffer(), 4);
+ BufferUtils.setInBuffer(vert[4], _meshData.getVertexBuffer(), 5);
+ BufferUtils.setInBuffer(vert[6], _meshData.getVertexBuffer(), 6);
+ BufferUtils.setInBuffer(vert[2], _meshData.getVertexBuffer(), 7);
+
+ // Front
+ BufferUtils.setInBuffer(vert[4], _meshData.getVertexBuffer(), 8);
+ BufferUtils.setInBuffer(vert[5], _meshData.getVertexBuffer(), 9);
+ BufferUtils.setInBuffer(vert[7], _meshData.getVertexBuffer(), 10);
+ BufferUtils.setInBuffer(vert[6], _meshData.getVertexBuffer(), 11);
+
+ // Left
+ BufferUtils.setInBuffer(vert[5], _meshData.getVertexBuffer(), 12);
+ BufferUtils.setInBuffer(vert[0], _meshData.getVertexBuffer(), 13);
+ BufferUtils.setInBuffer(vert[3], _meshData.getVertexBuffer(), 14);
+ BufferUtils.setInBuffer(vert[7], _meshData.getVertexBuffer(), 15);
+
+ // Top
+ BufferUtils.setInBuffer(vert[2], _meshData.getVertexBuffer(), 16);
+ BufferUtils.setInBuffer(vert[6], _meshData.getVertexBuffer(), 17);
+ BufferUtils.setInBuffer(vert[7], _meshData.getVertexBuffer(), 18);
+ BufferUtils.setInBuffer(vert[3], _meshData.getVertexBuffer(), 19);
+
+ // Bottom
+ BufferUtils.setInBuffer(vert[0], _meshData.getVertexBuffer(), 20);
+ BufferUtils.setInBuffer(vert[5], _meshData.getVertexBuffer(), 21);
+ BufferUtils.setInBuffer(vert[4], _meshData.getVertexBuffer(), 22);
+ BufferUtils.setInBuffer(vert[1], _meshData.getVertexBuffer(), 23);
+ }
+
+ /**
+ * <code>setNormalData</code> sets the normals of each of the box's planes.
+ */
+ private void setNormalData() {
+ if (_meshData.getNormalBuffer() == null) {
+ _meshData.setNormalBuffer(BufferUtils.createVector3Buffer(24));
+
+ // back
+ for (int i = 0; i < 4; i++) {
+ _meshData.getNormalBuffer().put(0).put(0).put(-1);
+ }
+
+ // right
+ for (int i = 0; i < 4; i++) {
+ _meshData.getNormalBuffer().put(1).put(0).put(0);
+ }
+
+ // front
+ for (int i = 0; i < 4; i++) {
+ _meshData.getNormalBuffer().put(0).put(0).put(1);
+ }
+
+ // left
+ for (int i = 0; i < 4; i++) {
+ _meshData.getNormalBuffer().put(-1).put(0).put(0);
+ }
+
+ // top
+ for (int i = 0; i < 4; i++) {
+ _meshData.getNormalBuffer().put(0).put(1).put(0);
+ }
+
+ // bottom
+ for (int i = 0; i < 4; i++) {
+ _meshData.getNormalBuffer().put(0).put(-1).put(0);
+ }
+ }
+ }
+
+ /**
+ * <code>setTextureData</code> sets the points that define the texture of the box. It's a one-to-one ratio, where
+ * each plane of the box has it's own copy of the texture. That is, the texture is repeated one time for each six
+ * faces.
+ */
+ private void setTextureData() {
+ if (_meshData.getTextureCoords(0) == null) {
+ _meshData.setTextureBuffer(BufferUtils.createVector2Buffer(24), 0);
+ final FloatBuffer tex = _meshData.getTextureBuffer(0);
+
+ for (int i = 0; i < 6; i++) {
+ tex.put(1).put(0);
+ tex.put(0).put(0);
+ tex.put(0).put(1);
+ tex.put(1).put(1);
+ }
+ }
+ }
+
+ /**
+ * <code>setIndexData</code> sets the indices into the list of vertices, defining all triangles that constitute the
+ * box.
+ */
+ private void setIndexData() {
+ if (_meshData.getIndexBuffer() == null) {
+ final byte[] indices = {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23
+ };
+ final ByteBuffer buf = BufferUtils.createByteBuffer(indices.length);
+ buf.put(indices);
+ buf.rewind();
+ _meshData.setIndexBuffer(buf);
+ _meshData.setIndexMode(IndexMode.Quads);
+ }
+ }
+
+ /**
+ * <code>clone</code> creates a new QuadBox object containing the same data as this one.
+ *
+ * @return the new QuadBox
+ */
+ @Override
+ public QuadBox clone() {
+ final QuadBox rVal = new QuadBox(getName() + "_clone", _center.clone(), _xExtent, _yExtent, _zExtent);
+ return rVal;
+ }
+
+ /**
+ * @return a size 8 array of Vectors representing the 8 points of the box.
+ */
+ public Vector3[] computeVertices() {
+
+ final Vector3 rVal[] = new Vector3[8];
+ rVal[0] = _center.add(-_xExtent, -_yExtent, -_zExtent, null);
+ rVal[1] = _center.add(_xExtent, -_yExtent, -_zExtent, null);
+ rVal[2] = _center.add(_xExtent, _yExtent, -_zExtent, null);
+ rVal[3] = _center.add(-_xExtent, _yExtent, -_zExtent, null);
+ rVal[4] = _center.add(_xExtent, -_yExtent, _zExtent, null);
+ rVal[5] = _center.add(-_xExtent, -_yExtent, _zExtent, null);
+ rVal[6] = _center.add(_xExtent, _yExtent, _zExtent, null);
+ rVal[7] = _center.add(-_xExtent, _yExtent, _zExtent, null);
+ return rVal;
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_xExtent, "xExtent", 0);
+ capsule.write(_yExtent, "yExtent", 0);
+ capsule.write(_zExtent, "zExtent", 0);
+ capsule.write(_center, "center", new Vector3(Vector3.ZERO));
+
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _xExtent = capsule.readDouble("xExtent", 0);
+ _yExtent = capsule.readDouble("yExtent", 0);
+ _zExtent = capsule.readDouble("zExtent", 0);
+ _center.set((Vector3) capsule.readSavable("center", new Vector3(Vector3.ZERO)));
+ }
+} \ No newline at end of file
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/objects/QuadQuad.java b/ardor3d-craft/src/main/java/com/ardorcraft/objects/QuadQuad.java
new file mode 100644
index 0000000..ad2f247
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/objects/QuadQuad.java
@@ -0,0 +1,114 @@
+/**
+ * Copyright (c) 2008-2010 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardorcraft.objects;
+
+import java.nio.ByteBuffer;
+
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.renderer.IndexMode;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.util.geom.BufferUtils;
+
+/**
+ * <code>QuadQuad</code> defines a four sided, two dimensional shape. The local height of the <code>QuadQuad</code>
+ * defines it's size about the y-axis, while the width defines the x-axis. The z-axis will always be 0.
+ */
+public class QuadQuad extends Mesh {
+
+ protected double _width = 0;
+ protected double _height = 0;
+
+ public QuadQuad() {
+
+ }
+
+ /**
+ * Constructor creates a new <code>Quade</code> object with the provided width and height.
+ *
+ * @param name
+ * the name of the <code>QuadQuad</code>.
+ * @param width
+ * the width of the <code>QuadQuad</code>.
+ * @param height
+ * the height of the <code>QuadQuad</code>.
+ */
+ public QuadQuad(final String name, final double width, final double height, final int mode) {
+ super(name);
+ initialize(width, height, mode);
+ }
+
+ /**
+ * <code>resize</code> changes the width and height of the given quad by altering its vertices.
+ *
+ * @param width
+ * the new width of the <code>QuadQuad</code>.
+ * @param height
+ * the new height of the <code>QuadQuad</code>.
+ */
+ public void resize(final double width, final double height, final int mode) {
+ _width = width;
+ _height = height;
+
+ _meshData.getVertexBuffer().clear();
+
+ if (mode == 0) {
+ _meshData.getVertexBuffer().put(0).put((float) (-width / 2)).put((float) (height / 2));
+ _meshData.getVertexBuffer().put(0).put((float) (-width / 2)).put((float) (-height / 2));
+ _meshData.getVertexBuffer().put(0).put((float) (width / 2)).put((float) (-height / 2));
+ _meshData.getVertexBuffer().put(0).put((float) (width / 2)).put((float) (height / 2));
+ } else if (mode == 1) {
+ _meshData.getVertexBuffer().put((float) (-width / 2)).put(0).put((float) (height / 2));
+ _meshData.getVertexBuffer().put((float) (-width / 2)).put(0).put((float) (-height / 2));
+ _meshData.getVertexBuffer().put((float) (width / 2)).put(0).put((float) (-height / 2));
+ _meshData.getVertexBuffer().put((float) (width / 2)).put(0).put((float) (height / 2));
+ } else if (mode == 2) {
+ _meshData.getVertexBuffer().put((float) (-width / 2)).put((float) (height / 2)).put(0);
+ _meshData.getVertexBuffer().put((float) (-width / 2)).put((float) (-height / 2)).put(0);
+ _meshData.getVertexBuffer().put((float) (width / 2)).put((float) (-height / 2)).put(0);
+ _meshData.getVertexBuffer().put((float) (width / 2)).put((float) (height / 2)).put(0);
+ }
+ }
+
+ /**
+ * <code>initialize</code> builds the data for the <code>QuadQuad</code> object.
+ *
+ * @param width
+ * the width of the <code>QuadQuad</code>.
+ * @param height
+ * the height of the <code>QuadQuad</code>.
+ */
+ private void initialize(final double width, final double height, final int mode) {
+ final int verts = 4;
+ _meshData.setVertexBuffer(BufferUtils.createVector3Buffer(verts));
+
+ final byte[] indices = {
+ 0, 1, 2, 3
+ };
+ final ByteBuffer buf = BufferUtils.createByteBuffer(indices.length);
+ buf.put(indices);
+ buf.rewind();
+ _meshData.setIndexBuffer(buf);
+
+ _meshData.setIndexMode(IndexMode.Quads);
+
+ resize(width, height, mode);
+
+ setDefaultColor(ColorRGBA.BLACK);
+ }
+
+ public double getWidth() {
+ return _width;
+ }
+
+ public double getHeight() {
+ return _height;
+ }
+} \ No newline at end of file
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/objects/SkyDome.java b/ardor3d-craft/src/main/java/com/ardorcraft/objects/SkyDome.java
new file mode 100644
index 0000000..3baca8f
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/objects/SkyDome.java
@@ -0,0 +1,350 @@
+/**
+ * Copyright (c) 2008-2010 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardorcraft.objects;
+
+import java.io.IOException;
+
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.renderer.queue.RenderBucketType;
+import com.ardor3d.renderer.state.CullState;
+import com.ardor3d.renderer.state.CullState.Face;
+import com.ardor3d.renderer.state.FogState;
+import com.ardor3d.renderer.state.ZBufferState;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.scenegraph.hint.CullHint;
+import com.ardor3d.scenegraph.hint.LightCombineMode;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+import com.ardor3d.util.geom.BufferUtils;
+
+/**
+ * A half sphere.
+ */
+public class SkyDome extends Mesh {
+
+ private int _planes;
+
+ private int _radialSamples;
+
+ /** The radius of the dome */
+ private double _radius;
+
+ private final ColorRGBA midColor = new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f);
+ private final ColorRGBA topColor = new ColorRGBA(0.5f, 0.6f, 1.0f, 1.0f);
+ private final ColorRGBA tmpColor = new ColorRGBA();
+
+ public SkyDome() {}
+
+ /**
+ * Constructs a dome. By default the dome has not geometry data or center.
+ *
+ * @param name
+ * The name of the dome.
+ */
+ public SkyDome(final String name) {
+ super(name);
+ }
+
+ /**
+ * Constructs a dome with center at the origin. For details, see the other constructor.
+ *
+ * @param name
+ * Name of dome.
+ * @param planes
+ * The number of planes along the Z-axis.
+ * @param radialSamples
+ * The samples along the radial.
+ * @param radius
+ * Radius of the dome.
+ */
+ public SkyDome(final String name, final int planes, final int radialSamples, final double radius) {
+ this(name, new Vector3(0, 0, 0), planes, radialSamples, radius);
+ }
+
+ /**
+ * Constructs a dome. All geometry data buffers are updated automatically. Both planes and radialSamples increase
+ * the quality of the generated dome.
+ *
+ * @param name
+ * Name of the dome.
+ * @param center
+ * Center of the dome.
+ * @param planes
+ * The number of planes along the Z-axis.
+ * @param radialSamples
+ * The number of samples along the radial.
+ * @param radius
+ * The radius of the dome.
+ */
+ public SkyDome(final String name, final Vector3 center, final int planes, final int radialSamples,
+ final double radius) {
+
+ super(name);
+ setData(center, planes, radialSamples, radius, true, false);
+ }
+
+ /**
+ * Constructs a dome. All geometry data buffers are updated automatically. Both planes and radialSamples increase
+ * the quality of the generated dome.
+ *
+ * @param name
+ * Name of the dome.
+ * @param center
+ * Center of the dome.
+ * @param planes
+ * The number of planes along the Z-axis.
+ * @param radialSamples
+ * The number of samples along the radial.
+ * @param radius
+ * The radius of the dome.
+ * @param outsideView
+ * If true, the triangles will be connected for a view outside of the dome.
+ */
+ public SkyDome(final String name, final Vector3 center, final int planes, final int radialSamples,
+ final double radius, final boolean outsideView) {
+ super(name);
+ setData(center, planes, radialSamples, radius, true, outsideView);
+ }
+
+ /**
+ * Changes the information of the dome into the given values. The boolean at the end signals if buffer data should
+ * be updated as well. If the dome is to be rendered, then that value should be true.
+ *
+ * @param center
+ * The new center of the dome.
+ * @param planes
+ * The number of planes along the Z-axis.
+ * @param radialSamples
+ * The new number of radial samples of the dome.
+ * @param radius
+ * The new radius of the dome.
+ * @param updateBuffers
+ * If true, buffer information is updated as well.
+ * @param outsideView
+ * If true, the triangles will be connected for a view outside of the dome.
+ */
+ public void setData(final Vector3 center, final int planes, final int radialSamples, final double radius,
+ final boolean updateBuffers, final boolean outsideView) {
+ _planes = planes;
+ _radialSamples = radialSamples;
+ _radius = radius;
+
+ getSceneHints().setLightCombineMode(LightCombineMode.Off);
+ getSceneHints().setCullHint(CullHint.Never);
+ getSceneHints().setRenderBucketType(RenderBucketType.Skip);
+
+ final ZBufferState zbuff = new ZBufferState();
+ zbuff.setEnabled(false);
+ setRenderState(zbuff);
+
+ final FogState fs = new FogState();
+ fs.setEnabled(false);
+ setRenderState(fs);
+
+ final CullState cs = new CullState();
+ cs.setCullFace(Face.Front);
+ setRenderState(cs);
+
+ if (updateBuffers) {
+ setGeometryData(outsideView, center);
+ setIndexData();
+ }
+ }
+
+ /**
+ * Generates the vertices of the dome
+ *
+ * @param outsideView
+ * If the dome should be viewed from the outside (if not zbuffer is used)
+ * @param center
+ */
+ private void setGeometryData(final boolean outsideView, final Vector3 center) {
+ final Vector3 tempVa = Vector3.fetchTempInstance();
+ final Vector3 tempVb = Vector3.fetchTempInstance();
+ final Vector3 tempVc = Vector3.fetchTempInstance();
+
+ // allocate vertices, we need one extra in each radial to get the
+ // correct texture coordinates
+ final int verts = (_planes - 1) * (_radialSamples + 1) + 1;
+ _meshData.setVertexBuffer(BufferUtils.createVector3Buffer(verts));
+
+ // allocate normals
+ _meshData.setColorBuffer(BufferUtils.createColorBuffer(verts));
+
+ // generate geometry
+ final double fInvRS = 1.0 / _radialSamples;
+ final double fYFactor = 1.0 / (_planes - 1);
+
+ // Generate points on the unit circle to be used in computing the mesh
+ // points on a dome slice.
+ final double[] afSin = new double[_radialSamples];
+ final double[] afCos = new double[_radialSamples];
+ for (int iR = 0; iR < _radialSamples; iR++) {
+ final double fAngle = MathUtils.TWO_PI * fInvRS * iR;
+ afCos[iR] = MathUtils.cos(fAngle);
+ afSin[iR] = MathUtils.sin(fAngle);
+ }
+
+ // generate the dome itself
+ int i = 0;
+ for (int iY = 0; iY < _planes - 1; iY++) {
+ final double fYFraction = fYFactor * iY; // in (0,1)
+ final double fY = _radius * fYFraction;
+ // compute center of slice
+ final Vector3 kSliceCenter = tempVb.set(center);
+ kSliceCenter.addLocal(0, fY, 0);
+
+ // compute radius of slice
+ final double fSliceRadius = Math.sqrt(Math.abs(_radius * _radius - fY * fY));
+
+ // compute slice vertices
+ final int iSave = i;
+ for (int iR = 0; iR < _radialSamples; iR++) {
+ final Vector3 kRadial = tempVc.set(afCos[iR], 0, afSin[iR]);
+ kRadial.multiply(fSliceRadius, tempVa);
+ _meshData.getVertexBuffer().put((float) (kSliceCenter.getX() + tempVa.getX()))
+ .put((float) (kSliceCenter.getY() + tempVa.getY()))
+ .put((float) (kSliceCenter.getZ() + tempVa.getZ()));
+
+ BufferUtils.populateFromBuffer(tempVa, _meshData.getVertexBuffer(), i);
+
+ ColorRGBA.lerp(midColor, topColor, (float) Math.sqrt(fYFraction), tmpColor);
+ _meshData.getColorBuffer().put(tmpColor.getRed()).put(tmpColor.getGreen()).put(tmpColor.getBlue())
+ .put(1);
+
+ i++;
+ }
+
+ BufferUtils.copyInternalVector3(_meshData.getVertexBuffer(), iSave, i);
+ BufferUtils.copyInternalColor(_meshData.getColorBuffer(), iSave, i);
+
+ i++;
+ }
+
+ // pole
+ _meshData.getVertexBuffer().put((float) center.getX()).put((float) (center.getY() + _radius))
+ .put((float) center.getZ());
+
+ _meshData.getColorBuffer().put(topColor.getRed()).put(topColor.getGreen()).put(topColor.getBlue()).put(1);
+
+ Vector3.releaseTempInstance(tempVa);
+ Vector3.releaseTempInstance(tempVb);
+ Vector3.releaseTempInstance(tempVc);
+ }
+
+ public void updateColors() {
+ final Vector3 tempVa = Vector3.fetchTempInstance();
+ final Vector3 tempVb = Vector3.fetchTempInstance();
+ final Vector3 tempVc = Vector3.fetchTempInstance();
+
+ final double fYFactor = 1.0 / (_planes - 1);
+
+ _meshData.getColorBuffer().clear();
+
+ // generate the dome itself
+ int i = 0;
+ for (int iY = 0; iY < _planes - 1; iY++) {
+ final double fYFraction = fYFactor * iY; // in (0,1)
+
+ // compute slice vertices
+ final int iSave = i;
+ for (int iR = 0; iR < _radialSamples; iR++) {
+ ColorRGBA.lerp(midColor, topColor, (float) Math.sqrt(fYFraction), tmpColor);
+ _meshData.getColorBuffer().put(tmpColor.getRed()).put(tmpColor.getGreen()).put(tmpColor.getBlue())
+ .put(1);
+ i++;
+ }
+
+ BufferUtils.copyInternalVector3(_meshData.getVertexBuffer(), iSave, i);
+ BufferUtils.copyInternalColor(_meshData.getColorBuffer(), iSave, i);
+
+ i++;
+ }
+
+ _meshData.getColorBuffer().put(topColor.getRed()).put(topColor.getGreen()).put(topColor.getBlue()).put(1);
+
+ Vector3.releaseTempInstance(tempVa);
+ Vector3.releaseTempInstance(tempVb);
+ Vector3.releaseTempInstance(tempVc);
+ }
+
+ /**
+ * Generates the connections
+ */
+ private void setIndexData() {
+ // allocate connectivity
+ final int verts = (_planes - 1) * (_radialSamples + 1) + 1;
+ final int tris = (_planes - 2) * _radialSamples * 2 + _radialSamples;
+ _meshData.setIndices(BufferUtils.createIndexBufferData(3 * tris, verts - 1));
+
+ // Generate only for middle planes
+ for (int plane = 1; plane < _planes - 1; plane++) {
+ final int bottomPlaneStart = (plane - 1) * (_radialSamples + 1);
+ final int topPlaneStart = plane * (_radialSamples + 1);
+ for (int sample = 0; sample < _radialSamples; sample++) {
+ _meshData.getIndices().put(bottomPlaneStart + sample);
+ _meshData.getIndices().put(topPlaneStart + sample);
+ _meshData.getIndices().put(bottomPlaneStart + sample + 1);
+ _meshData.getIndices().put(bottomPlaneStart + sample + 1);
+ _meshData.getIndices().put(topPlaneStart + sample);
+ _meshData.getIndices().put(topPlaneStart + sample + 1);
+ }
+ }
+
+ // pole triangles
+ final int bottomPlaneStart = (_planes - 2) * (_radialSamples + 1);
+ for (int samples = 0; samples < _radialSamples; samples++) {
+ _meshData.getIndices().put(bottomPlaneStart + samples);
+ _meshData.getIndices().put(_meshData.getVertexCount() - 1);
+ _meshData.getIndices().put(bottomPlaneStart + samples + 1);
+ }
+ }
+
+ public int getPlanes() {
+ return _planes;
+ }
+
+ public int getRadialSamples() {
+ return _radialSamples;
+ }
+
+ public double getRadius() {
+ return _radius;
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_planes, "planes", 0);
+ capsule.write(_radialSamples, "radialSamples", 0);
+ capsule.write(_radius, "radius", 0);
+
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _planes = capsule.readInt("planes", 0);
+ _radialSamples = capsule.readInt("radialSamples", 0);
+ _radius = capsule.readDouble("radius", 0);
+
+ }
+
+ public ColorRGBA getMidColor() {
+ return midColor;
+ }
+
+ public ColorRGBA getTopColor() {
+ return topColor;
+ }
+} \ No newline at end of file
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/objects/Skybox.java b/ardor3d-craft/src/main/java/com/ardorcraft/objects/Skybox.java
new file mode 100644
index 0000000..528cef5
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/objects/Skybox.java
@@ -0,0 +1,257 @@
+/**
+ * Copyright (c) 2008-2010 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardorcraft.objects;
+
+import java.io.IOException;
+
+import com.ardor3d.image.Texture;
+import com.ardor3d.image.Texture.WrapMode;
+import com.ardor3d.math.Matrix3;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.renderer.queue.RenderBucketType;
+import com.ardor3d.renderer.state.FogState;
+import com.ardor3d.renderer.state.RenderState;
+import com.ardor3d.renderer.state.RenderState.StateType;
+import com.ardor3d.renderer.state.TextureState;
+import com.ardor3d.renderer.state.ZBufferState;
+import com.ardor3d.scenegraph.Node;
+import com.ardor3d.scenegraph.hint.CullHint;
+import com.ardor3d.scenegraph.hint.LightCombineMode;
+import com.ardor3d.scenegraph.hint.TextureCombineMode;
+import com.ardor3d.scenegraph.shape.Quad;
+import com.ardor3d.util.export.CapsuleUtils;
+import com.ardor3d.util.export.InputCapsule;
+import com.ardor3d.util.export.OutputCapsule;
+
+/**
+ * A Box made of textured quads that simulate having a sky, horizon and so forth around your scene. Either attach to a
+ * camera node or update on each frame to set this skybox at the camera's position.
+ */
+public class Skybox extends Node {
+
+ public enum Face {
+ /** The +Z side of the skybox. */
+ North,
+ /** The -Z side of the skybox. */
+ South,
+ /** The -X side of the skybox. */
+ East,
+ /** The +X side of the skybox. */
+ West,
+ /** The +Y side of the skybox. */
+ Up,
+ /** The -Y side of the skybox. */
+ Down;
+ }
+
+ private float _xExtent;
+ private float _yExtent;
+ private float _zExtent;
+
+ private Quad[] _skyboxQuads;
+
+ public Skybox() {}
+
+ /**
+ * Creates a new skybox. The size of the skybox and name is specified here. By default, no textures are set.
+ *
+ * @param name
+ * The name of the skybox.
+ * @param xExtent
+ * The x size of the skybox in both directions from the center.
+ * @param yExtent
+ * The y size of the skybox in both directions from the center.
+ * @param zExtent
+ * The z size of the skybox in both directions from the center.
+ */
+ public Skybox(final String name, final float xExtent, final float yExtent, final float zExtent) {
+ super(name);
+
+ _xExtent = xExtent;
+ _yExtent = yExtent;
+ _zExtent = zExtent;
+
+ initialize();
+ }
+
+ /**
+ * Set the texture to be displayed on the given face of the skybox. Replaces any existing texture on that face.
+ *
+ * @param face
+ * the face to set
+ * @param texture
+ * The texture for that side to assume.
+ * @throws IllegalArgumentException
+ * if face is null.
+ */
+ public void setTexture(final Face face, final Texture texture) {
+ if (face == null) {
+ throw new IllegalArgumentException("Face can not be null.");
+ }
+
+ _skyboxQuads[face.ordinal()].clearRenderState(RenderState.StateType.Texture);
+ setTexture(face, texture, 0);
+ }
+
+ /**
+ * Set the texture to be displayed on the given side of the skybox. Only replaces the texture at the index specified
+ * by textureUnit.
+ *
+ * @param face
+ * the face to set
+ * @param texture
+ * The texture for that side to assume.
+ * @param textureUnit
+ * The texture unite of the given side's TextureState the texture will assume.
+ */
+ public void setTexture(final Face face, final Texture texture, final int textureUnit) {
+ // Validate
+ if (face == null) {
+ throw new IllegalArgumentException("Face can not be null.");
+ }
+
+ TextureState ts = (TextureState) _skyboxQuads[face.ordinal()]
+ .getLocalRenderState(RenderState.StateType.Texture);
+ if (ts == null) {
+ ts = new TextureState();
+ }
+
+ // Initialize the texture state
+ ts.setTexture(texture, textureUnit);
+ ts.setEnabled(true);
+
+ texture.setWrap(WrapMode.EdgeClamp);
+
+ // Set the texture to the quad
+ _skyboxQuads[face.ordinal()].setRenderState(ts);
+
+ return;
+ }
+
+ public Texture getTexture(final Face face) {
+ if (face == null) {
+ throw new IllegalArgumentException("Face can not be null.");
+ }
+ return ((TextureState) _skyboxQuads[face.ordinal()].getLocalRenderState(RenderState.StateType.Texture))
+ .getTexture();
+ }
+
+ public void initialize() {
+
+ // Skybox consists of 6 sides
+ _skyboxQuads = new Quad[6];
+
+ // Create each of the quads
+ _skyboxQuads[Face.North.ordinal()] = new Quad("north", _xExtent * 2, _yExtent * 2);
+ _skyboxQuads[Face.North.ordinal()].setRotation(new Matrix3().fromAngles(0, Math.toRadians(180), 0));
+ _skyboxQuads[Face.North.ordinal()].setTranslation(new Vector3(0, 0, _zExtent));
+ _skyboxQuads[Face.South.ordinal()] = new Quad("south", _xExtent * 2, _yExtent * 2);
+ _skyboxQuads[Face.South.ordinal()].setTranslation(new Vector3(0, 0, -_zExtent));
+ _skyboxQuads[Face.East.ordinal()] = new Quad("east", _zExtent * 2, _yExtent * 2);
+ _skyboxQuads[Face.East.ordinal()].setRotation(new Matrix3().fromAngles(0, Math.toRadians(90), 0));
+ _skyboxQuads[Face.East.ordinal()].setTranslation(new Vector3(-_xExtent, 0, 0));
+ _skyboxQuads[Face.West.ordinal()] = new Quad("west", _zExtent * 2, _yExtent * 2);
+ _skyboxQuads[Face.West.ordinal()].setRotation(new Matrix3().fromAngles(0, Math.toRadians(270), 0));
+ _skyboxQuads[Face.West.ordinal()].setTranslation(new Vector3(_xExtent, 0, 0));
+ _skyboxQuads[Face.Up.ordinal()] = new Quad("up", _xExtent * 2, _zExtent * 2);
+ _skyboxQuads[Face.Up.ordinal()]
+ .setRotation(new Matrix3().fromAngles(Math.toRadians(90), Math.toRadians(270), 0));
+ _skyboxQuads[Face.Up.ordinal()].setTranslation(new Vector3(0, _yExtent, 0));
+ _skyboxQuads[Face.Down.ordinal()] = new Quad("down", _xExtent * 2, _zExtent * 2);
+ _skyboxQuads[Face.Down.ordinal()].setRotation(new Matrix3().fromAngles(Math.toRadians(270),
+ Math.toRadians(270), 0));
+ _skyboxQuads[Face.Down.ordinal()].setTranslation(new Vector3(0, -_yExtent, 0));
+
+ // We don't want the light to effect our skybox
+ getSceneHints().setLightCombineMode(LightCombineMode.Off);
+
+ getSceneHints().setTextureCombineMode(TextureCombineMode.Replace);
+
+ final ZBufferState zbuff = new ZBufferState();
+ zbuff.setEnabled(false);
+ setRenderState(zbuff);
+
+ final FogState fs = new FogState();
+ fs.setEnabled(false);
+ setRenderState(fs);
+
+ // We don't want it making our skybox disapear, so force view
+ getSceneHints().setCullHint(CullHint.Never);
+
+ for (int i = 0; i < 6; i++) {
+ // Make sure texture is only what is set.
+ _skyboxQuads[i].getSceneHints().setTextureCombineMode(TextureCombineMode.Replace);
+
+ // Make sure no lighting on the skybox
+ _skyboxQuads[i].getSceneHints().setLightCombineMode(LightCombineMode.Off);
+
+ // Make sure the quad is viewable
+ _skyboxQuads[i].getSceneHints().setCullHint(CullHint.Never);
+
+ // Add to the prebucket.
+ _skyboxQuads[i].getSceneHints().setRenderBucketType(RenderBucketType.Skip);
+
+ // And attach the skybox as a child
+ attachChild(_skyboxQuads[i]);
+ }
+ }
+
+ /**
+ * Retrieve the quad indicated by the given side.
+ *
+ * @param face
+ * One of Skybox.Face.North, Skybox.Face.South, and so on...
+ * @return The Quad that makes up that side of the Skybox.
+ */
+ public Quad getFace(final Face face) {
+ return _skyboxQuads[face.ordinal()];
+ }
+
+ public void preloadTexture(final Face face, final Renderer r) {
+ final TextureState ts = (TextureState) _skyboxQuads[face.ordinal()]
+ .getLocalRenderState(RenderState.StateType.Texture);
+ if (ts != null) {
+ r.applyState(StateType.Texture, ts);
+ }
+ }
+
+ /**
+ * Force all of the textures to load. This prevents pauses later during the application as you pan around the world.
+ */
+ public void preloadTextures(final Renderer r) {
+ for (int x = 0; x < 6; x++) {
+ final TextureState ts = (TextureState) _skyboxQuads[x].getLocalRenderState(RenderState.StateType.Texture);
+ if (ts != null) {
+ r.applyState(StateType.Texture, ts);
+ }
+ }
+
+ }
+
+ @Override
+ public void write(final OutputCapsule capsule) throws IOException {
+ super.write(capsule);
+ capsule.write(_xExtent, "xExtent", 0);
+ capsule.write(_yExtent, "yExtent", 0);
+ capsule.write(_zExtent, "zExtent", 0);
+ capsule.write(_skyboxQuads, "skyboxQuads", null);
+ }
+
+ @Override
+ public void read(final InputCapsule capsule) throws IOException {
+ super.read(capsule);
+ _xExtent = capsule.readFloat("xExtent", 0);
+ _yExtent = capsule.readFloat("yExtent", 0);
+ _zExtent = capsule.readFloat("zExtent", 0);
+ _skyboxQuads = CapsuleUtils.asArray(capsule.readSavableArray("skyboxQuads", null), Quad.class);
+ }
+} \ No newline at end of file
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/AStarHeuristic.java b/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/AStarHeuristic.java
new file mode 100644
index 0000000..70babce
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/AStarHeuristic.java
@@ -0,0 +1,15 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.pathfinding;
+
+
+/**
+ * Use to define your own heuristic for use with the {@link ConstrainedAStar}.
+ */
+public interface AStarHeuristic {
+ public float getCost(int x, int y, int z, int tx, int ty, int tz);
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/ConstrainedAStar.java b/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/ConstrainedAStar.java
new file mode 100644
index 0000000..8cf8584
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/ConstrainedAStar.java
@@ -0,0 +1,180 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.pathfinding;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.PriorityQueue;
+import java.util.Set;
+
+import com.ardorcraft.pathfinding.calculator.DefaultAStarCalculator;
+import com.ardorcraft.pathfinding.heuristics.ClosestHeuristic;
+import com.ardorcraft.world.BlockProvider;
+
+/**
+ * AStar pathfinding with pluggable cost calculator (since that is game specific).
+ * <p>
+ * Typical usage:
+ * <p>
+ *
+ * <pre>
+ * ConstrainedAStar pathFinder = new ConstrainedAStar(blockWorld, 40);
+ * ...
+ * PathResult pathResult = pathFinder.findPath(source.x, source.y, source.z, target.x, target.y, target.z);
+ * </pre>
+ */
+public class ConstrainedAStar {
+ private final Set<PathNode> closed = new HashSet<>();
+ private final PriorityQueue<PathNode> open = new PriorityQueue<>();
+ private final Map<PathNode, PathNode> nodeTable = new HashMap<>();
+
+ private final int maxSearchDistance;
+ private final AStarHeuristic heuristic;
+ private final MoveCalculator moveCalculator;
+ private final BlockProvider map;
+ private final float heuristicMultiplier = 0.2f;
+
+ public ConstrainedAStar(final BlockProvider map, final int maxSearchDistance) {
+ this(map, maxSearchDistance, new ClosestHeuristic(), new DefaultAStarCalculator());
+ }
+
+ public ConstrainedAStar(final BlockProvider map, final int maxSearchDistance, final AStarHeuristic heuristic,
+ final MoveCalculator moveCalculator) {
+ this.heuristic = heuristic;
+ this.map = map;
+ this.maxSearchDistance = maxSearchDistance;
+ this.moveCalculator = moveCalculator;
+ }
+
+ public PathResult findPath(final int sx, final int sy, final int sz, final int tx, final int ty, final int tz) {
+ closed.clear();
+ open.clear();
+ nodeTable.clear();
+ final MoveData moveData = new MoveData();
+
+ final PathNode startNode = new PathNode(sx, sy, sz);
+ final PathNode endNode = new PathNode(tx, ty, tz);
+
+ if (startNode.equals(endNode)) {
+ return new PathResult(null, true);
+ }
+
+ startNode.heuristic = getHeuristicCost(startNode, tx, ty, tz) * heuristicMultiplier;
+ open.add(startNode);
+ nodeTable.put(startNode, startNode);
+ nodeTable.put(endNode, endNode);
+
+ PathNode current = null;
+ int maxDepth = 0;
+ while (maxDepth < maxSearchDistance && !open.isEmpty()) {
+ current = open.poll();
+ if (current == null || current.equals(endNode)) {
+ break;
+ }
+
+ Thread.yield();
+
+ closed.add(current);
+
+ for (int x = -1; x < 2; x++) {
+ for (int z = -1; z < 2; z++) {
+ if (x == 0 && z == 0) {
+ continue;
+ }
+
+ moveCalculator.calculateMove(map, moveData, current, x, z);
+ if (moveData.canMove) {
+ final float nextStepCost = current.cost + moveData.cost;
+
+ PathNode neighbour = new PathNode(current.x + x, moveData.newY, current.z + z);
+ if (nodeTable.containsKey(neighbour)) {
+ neighbour = nodeTable.get(neighbour);
+ } else {
+ nodeTable.put(neighbour, neighbour);
+ }
+
+ if (nextStepCost < neighbour.cost) {
+ if (open.contains(neighbour)) {
+ open.remove(neighbour);
+ }
+ if (closed.contains(neighbour)) {
+ closed.remove(neighbour);
+ }
+ }
+
+ if (!open.contains(neighbour) && !closed.contains(neighbour)) {
+ neighbour.cost = nextStepCost;
+ neighbour.heuristic = getHeuristicCost(neighbour, tx, ty, tz) * heuristicMultiplier;
+ maxDepth = Math.max(maxDepth, neighbour.setParent(current));
+ open.add(neighbour);
+ }
+ }
+ }
+ }
+ }
+
+ if (current == null) {
+ return new PathResult(null, false);
+ }
+ if (!current.equals(endNode)) {
+ PathNode minNode = null;
+ float minHeuristic = Float.MAX_VALUE;
+ for (final PathNode node : nodeTable.keySet()) {
+ if (node != endNode && node.heuristic + node.cost < minHeuristic) {
+ minHeuristic = node.heuristic + node.cost;
+ minNode = node;
+ }
+ }
+
+ if (minNode == null) {
+ return new PathResult(null, false);
+ }
+ if (minNode.equals(startNode)) {
+ final List<PathNode> result = new ArrayList<>();
+ result.add(minNode);
+ return new PathResult(result, false);
+ }
+
+ final List<PathNode> result = new ArrayList<>();
+ PathNode target = minNode;
+ while (target != null && target != startNode) {
+ result.add(target);
+ target = target.parent;
+ }
+ Collections.reverse(result);
+
+ closed.clear();
+ open.clear();
+ nodeTable.clear();
+
+ return new PathResult(result, false);
+ }
+
+ final List<PathNode> result = new ArrayList<>();
+ PathNode target = endNode;
+ while (target != null && target != startNode) {
+ result.add(target);
+ target = target.parent;
+ }
+ Collections.reverse(result);
+
+ closed.clear();
+ open.clear();
+ nodeTable.clear();
+
+ return new PathResult(result, true);
+ }
+
+ private float getHeuristicCost(final PathNode node, final int tx, final int ty, final int tz) {
+ return heuristic.getCost(node.getX(), node.getY(), node.getZ(), tx, ty, tz);
+ }
+
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/MoveCalculator.java b/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/MoveCalculator.java
new file mode 100644
index 0000000..1ee4e64
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/MoveCalculator.java
@@ -0,0 +1,14 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.pathfinding;
+
+import com.ardorcraft.world.BlockProvider;
+
+public interface MoveCalculator {
+ void calculateMove(BlockProvider map, final MoveData moveData, final PathNode current, final int xChange,
+ final int zChange);
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/MoveData.java b/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/MoveData.java
new file mode 100644
index 0000000..070f8c0
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/MoveData.java
@@ -0,0 +1,17 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.pathfinding;
+
+public class MoveData {
+ MoveData() {
+
+ }
+
+ public boolean canMove = false;
+ public float cost = 0;
+ public int newY = 0;
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/PathNode.java b/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/PathNode.java
new file mode 100644
index 0000000..1537dfe
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/PathNode.java
@@ -0,0 +1,103 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.pathfinding;
+
+public class PathNode implements Comparable<PathNode> {
+ public final int x;
+ public final int y;
+ public final int z;
+
+ PathNode parent;
+
+ float cost;
+ float heuristic;
+ int depth;
+
+ public PathNode(final int x, final int y, final int z) {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ }
+
+ public int setParent(final PathNode parent) {
+ depth = parent.depth + 1;
+ this.parent = parent;
+
+ return depth;
+ }
+
+ @Override
+ public int compareTo(final PathNode other) {
+ final PathNode o = other;
+
+ final float f = heuristic + cost;
+ final float of = o.heuristic + o.cost;
+
+ if (f < of) {
+ return -1;
+ } else if (f > of) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + x;
+ result = prime * result + y;
+ result = prime * result + z;
+ return result;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof PathNode)) {
+ return false;
+ }
+ final PathNode other = (PathNode) obj;
+ if (x != other.x) {
+ return false;
+ }
+ if (y != other.y) {
+ return false;
+ }
+ if (z != other.z) {
+ return false;
+ }
+ return true;
+ }
+
+ public int getX() {
+ return x;
+ }
+
+ public int getY() {
+ return y;
+ }
+
+ public int getZ() {
+ return z;
+ }
+
+ public float getCost() {
+ return cost;
+ }
+
+ @Override
+ public String toString() {
+ return "PathNode [x=" + x + ", y=" + y + ", z=" + z + ", cost=" + cost + ", heuristic=" + heuristic + "]";
+ }
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/PathResult.java b/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/PathResult.java
new file mode 100644
index 0000000..e72e729
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/PathResult.java
@@ -0,0 +1,32 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.pathfinding;
+
+import java.util.List;
+
+public class PathResult {
+ private final List<PathNode> path;
+ private final boolean isFullPath;
+
+ public PathResult(final List<PathNode> path, final boolean isFullPath) {
+ this.path = path;
+ this.isFullPath = isFullPath;
+ }
+
+ public List<PathNode> getPath() {
+ return path;
+ }
+
+ public boolean isFullPath() {
+ return isFullPath;
+ }
+
+ @Override
+ public String toString() {
+ return "PathResult [path=" + path + ", isFullPath=" + isFullPath + "]";
+ }
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/calculator/DefaultAStarCalculator.java b/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/calculator/DefaultAStarCalculator.java
new file mode 100644
index 0000000..7af7815
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/calculator/DefaultAStarCalculator.java
@@ -0,0 +1,89 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.pathfinding.calculator;
+
+import com.ardorcraft.pathfinding.MoveCalculator;
+import com.ardorcraft.pathfinding.MoveData;
+import com.ardorcraft.pathfinding.PathNode;
+import com.ardorcraft.world.BlockProvider;
+
+public class DefaultAStarCalculator implements MoveCalculator {
+ @Override
+ public void calculateMove(final BlockProvider map, final MoveData moveData, final PathNode current,
+ final int xChange, final int zChange) {
+ moveData.canMove = false;
+ moveData.cost = 0.01f;
+ moveData.newY = current.y;
+
+ // Special handling of diagonal movement
+ if (xChange != 0 && zChange != 0) {
+ final int block1 = map.getBlock(current.x + xChange, current.y, current.z);
+ final int block2 = map.getBlock(current.x + xChange, current.y + 1, current.z);
+
+ final int block3 = map.getBlock(current.x, current.y, current.z + zChange);
+ final int block4 = map.getBlock(current.x, current.y + 1, current.z + zChange);
+
+ final int block5 = map.getBlock(current.x + xChange, current.y, current.z + zChange);
+ final int block6 = map.getBlock(current.x + xChange, current.y + 1, current.z + zChange);
+
+ if (block1 == 0 && block2 == 0 && block3 == 0 && block4 == 0 && block5 == 0 && block6 == 0) {
+ moveData.canMove = true;
+ moveData.cost = 0.05f * 1.42f;
+
+ for (int y = moveData.newY - 1; y >= 0; y--) {
+ final int block7 = map.getBlock(current.x + xChange, y, current.z + zChange);
+ if (block7 != 0) {
+ moveData.newY = y + 1;
+ final int height = Math.abs(moveData.newY - current.y);
+ moveData.cost = 0.05f * 1.42f + 0.05f * height;
+ if (height > 3) {
+ moveData.cost *= 10.0f;
+ }
+ if (height > 5) {
+ moveData.canMove = false;
+ }
+ break;
+ }
+ }
+ }
+ return;
+ }
+
+ final int block1 = map.getBlock(current.x + xChange, current.y, current.z + zChange);
+ final int block2 = map.getBlock(current.x + xChange, current.y + 1, current.z + zChange);
+ // Walk straight on
+ if (block1 == 0 && block2 == 0) {
+ moveData.canMove = true;
+
+ for (int y = moveData.newY - 1; y >= 0; y--) {
+ final int block3 = map.getBlock(current.x + xChange, y, current.z + zChange);
+ if (block3 != 0) {
+ moveData.newY = y + 1;
+ final int height = Math.abs(moveData.newY - current.y);
+ moveData.cost = 0.05f + 0.05f * height;
+ if (height > 3) {
+ moveData.cost *= 10.0f;
+ }
+ if (height > 5) {
+ moveData.canMove = false;
+ }
+ break;
+ }
+ }
+ }
+ // Climb
+ else if (block1 != 0 && block2 == 0) {
+ final int block3 = map.getBlock(current.x, current.y + 2, current.z);
+ final int block4 = map.getBlock(current.x + xChange, current.y + 2, current.z + zChange);
+ if (block3 == 0 && block4 == 0) {
+ moveData.canMove = true;
+ moveData.cost = 0.1f;
+ moveData.newY += 1;
+ }
+ }
+ }
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/heuristics/ClosestHeuristic.java b/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/heuristics/ClosestHeuristic.java
new file mode 100644
index 0000000..d37bdb0
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/heuristics/ClosestHeuristic.java
@@ -0,0 +1,23 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.pathfinding.heuristics;
+
+import com.ardorcraft.pathfinding.AStarHeuristic;
+
+public class ClosestHeuristic implements AStarHeuristic {
+ @Override
+ public float getCost(final int x, final int y, final int z, final int tx, final int ty, final int tz) {
+ final float dx = tx - x;
+ final float dy = ty - y;
+ final float dz = tz - z;
+
+ final float result = (float) Math.sqrt(dx * dx + dy * dy + dz * dz);
+
+ return result;
+ }
+
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/heuristics/ClosestSquaredHeuristic.java b/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/heuristics/ClosestSquaredHeuristic.java
new file mode 100644
index 0000000..7d7c1f6
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/heuristics/ClosestSquaredHeuristic.java
@@ -0,0 +1,22 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.pathfinding.heuristics;
+
+import com.ardorcraft.pathfinding.AStarHeuristic;
+
+public class ClosestSquaredHeuristic implements AStarHeuristic {
+
+ @Override
+ public float getCost(final int x, final int y, final int z, final int tx, final int ty, final int tz) {
+ final float dx = tx - x;
+ final float dy = ty - y;
+ final float dz = tz - z;
+
+ return dx * dx + dy * dy + dz * dz;
+ }
+
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/heuristics/ManhattanHeuristic.java b/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/heuristics/ManhattanHeuristic.java
new file mode 100644
index 0000000..4846b52
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/heuristics/ManhattanHeuristic.java
@@ -0,0 +1,23 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.pathfinding.heuristics;
+
+import com.ardorcraft.pathfinding.AStarHeuristic;
+
+public class ManhattanHeuristic implements AStarHeuristic {
+ private final int minimumCost;
+
+ public ManhattanHeuristic(final int minimumCost) {
+ this.minimumCost = minimumCost;
+ }
+
+ @Override
+ public float getCost(final int x, final int y, final int z, final int tx, final int ty, final int tz) {
+ return minimumCost * (Math.abs(x - tx) + Math.abs(y - ty) + Math.abs(z - tz));
+ }
+
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/player/PlayerBase.java b/ardor3d-craft/src/main/java/com/ardorcraft/player/PlayerBase.java
new file mode 100644
index 0000000..8e98c1f
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/player/PlayerBase.java
@@ -0,0 +1,61 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.player;
+
+import com.ardor3d.math.Vector3;
+
+/**
+ * Useful baseclass for player handling
+ */
+public class PlayerBase {
+ protected final Vector3 position;
+ protected final Vector3 direction;
+ protected final Vector3 up;
+ protected final Vector3 left;
+ protected final Vector3 velocity;
+ protected final Vector3 acceleration;
+
+ public PlayerBase() {
+ position = new Vector3(0, 0, 0);
+ direction = new Vector3(Vector3.UNIT_Z);
+ up = new Vector3(Vector3.UNIT_Y);
+ left = new Vector3(Vector3.UNIT_X);
+ velocity = new Vector3();
+ acceleration = new Vector3();
+ }
+
+ public Vector3 getPosition() {
+ return position;
+ }
+
+ public Vector3 getDirection() {
+ return direction;
+ }
+
+ public Vector3 getUp() {
+ return up;
+ }
+
+ public Vector3 getVelocity() {
+ return velocity;
+ }
+
+ public Vector3 getLeft() {
+ return left;
+ }
+
+ public Vector3 getAcceleration() {
+ return acceleration;
+ }
+
+ public void normalize() {
+ left.normalizeLocal();
+ up.normalizeLocal();
+ direction.normalizeLocal();
+ }
+
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/util/BlockUtil.java b/ardor3d-craft/src/main/java/com/ardorcraft/util/BlockUtil.java
new file mode 100644
index 0000000..9baf07c
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/util/BlockUtil.java
@@ -0,0 +1,497 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.util;
+
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Logger;
+
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Quaternion;
+import com.ardor3d.math.Vector2;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyVector2;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.scenegraph.Node;
+import com.ardor3d.scenegraph.shape.Quad;
+import com.ardor3d.util.geom.MeshCombiner;
+import com.ardorcraft.util.geometryproducers.BoxProducer;
+import com.ardorcraft.util.geometryproducers.GeometryProducer;
+import com.ardorcraft.util.geometryproducers.MeshProducer;
+import com.ardorcraft.world.BlockSide;
+import com.ardorcraft.world.BlockType;
+import com.ardorcraft.world.BlockWorld;
+
+/**
+ * Main class to use for setting up properties for all block types.
+ */
+public final class BlockUtil {
+ private static final Logger logger = Logger.getLogger(BlockUtil.class.getName());
+
+ private final int totalTextureSizeWidth;
+ private final int totalTextureSizeHeight;
+ private final int textureSize;
+ private final float textureReciprocalX;
+ private final float textureReciprocalY;
+ private final float offsetWidth;
+ private final float offsetHeight;
+ private final int xCount;
+ private final int yCount;
+
+ private final Map<Integer, EnumMap<BlockSide, Vector2>> blockTextureCoords = new HashMap<>();
+ private final BlockType[] types = new BlockType[256];
+ private final boolean[] isLocalLightMap = new boolean[256];
+ private final GeometryProducer[] geometryProducers = new GeometryProducer[256];
+ private final boolean[] isCollidable = new boolean[256];
+ private final boolean[] isPickable = new boolean[256];
+ private final boolean[] isSemiTransparent = new boolean[256];
+
+ public BlockUtil(final int textureWidth, final int textureHeight, final int subTextureSize) {
+ totalTextureSizeWidth = textureWidth;
+ totalTextureSizeHeight = textureHeight;
+ textureSize = subTextureSize;
+
+ textureReciprocalX = (float) textureSize / (float) totalTextureSizeWidth;
+ textureReciprocalY = (float) textureSize / (float) totalTextureSizeHeight;
+
+ xCount = totalTextureSizeWidth / textureSize;
+ yCount = totalTextureSizeHeight / textureSize;
+
+ offsetWidth = 0.1f / totalTextureSizeWidth;
+ offsetHeight = 0.1f / totalTextureSizeHeight;
+
+ setupDefaultBlocks();
+ }
+
+ private void setupDefaultBlocks() {
+ final GeometryProducer producer = new BoxProducer();
+ for (int x = 0; x < 16; x++) {
+ for (int y = 0; y < 16; y++) {
+ final int index = y * 16 + x + 1;
+ if (index > 255) {
+ break;
+ }
+ setBlockMapping(index, x, y);
+ setBlockType(index, BlockType.Solid);
+ setGeometryProducer(index, producer);
+ setIsCollidable(index, true);
+ setIsPickable(index, true);
+ setIsLocalLightSource(index, false);
+ }
+ }
+
+ final Mesh vegetationMesh = createVegetationMesh();
+ final MeshProducer vegetationMeshProducer = new MeshProducer(vegetationMesh);
+ vegetationMeshProducer.setTransformTextureCoords(true);
+
+ // setBlockType(BlockWorld.WATER, BlockType.Transparent);
+ setIsSemiTransparent(BlockWorld.WATER, true);
+ setIsPickable(BlockWorld.WATER, false);
+ setIsCollidable(BlockWorld.WATER, false);
+
+ setBlockMapping(1, 1, 0); // stone
+ setBlockMapping(2, 0, 0, 3, 0, 2, 0); // grass
+ setBlockMapping(3, 2, 0); // dirt
+ setBlockMapping(4, 0, 1); // cobblestone
+ setBlockMapping(5, 4, 0); // wooden plank
+
+ setBlockMapping(6, 15, 0); // sapling
+ setBlockType(6, BlockType.Transparent);
+ setGeometryProducer(6, vegetationMeshProducer);
+ setIsCollidable(6, false);
+
+ setBlockMapping(7, 1, 1); // bedrock
+ setBlockMapping(8, 13, 12); // water
+ setBlockMapping(9, 13, 12); // stationary water
+ setBlockMapping(10, 13, 14); // lava
+ setBlockMapping(11, 13, 14); // stationary lava
+ setBlockMapping(12, 2, 1); // sand
+ setBlockMapping(13, 3, 1); // gravel
+ setBlockMapping(14, 0, 2); // gold ore
+ setBlockMapping(15, 1, 2); // iron ore
+ setBlockMapping(16, 2, 2); // coal ore
+ setBlockMapping(17, 4, 1); // wood
+
+ setBlockMapping(18, 4, 3); // leaves
+ setBlockType(18, BlockType.Transparent);
+
+ setBlockMapping(19, 0, 3); // sponge
+
+ setBlockMapping(20, 1, 3); // glass
+ setBlockType(20, BlockType.Transparent);
+
+ setBlockMapping(21, 0, 10); // lapis ore
+ setBlockMapping(22, 0, 9); // lapis block
+ setBlockMapping(23, 14, 3, 13, 2, 14, 3); // dispenser
+ setBlockMapping(24, 0, 12); // sandstone
+
+ setBlockMapping(37, 13, 0); // dandelion
+ setBlockType(37, BlockType.Transparent);
+ setGeometryProducer(37, vegetationMeshProducer);
+ setIsCollidable(37, false);
+ setBlockMapping(38, 12, 0); // rose
+ setBlockType(38, BlockType.Transparent);
+ setGeometryProducer(38, vegetationMeshProducer);
+ setIsCollidable(38, false);
+ setBlockMapping(39, 12, 1); // brown mushroom
+ setBlockType(39, BlockType.Transparent);
+ setGeometryProducer(39, vegetationMeshProducer);
+ setIsCollidable(39, false);
+ setBlockMapping(40, 13, 1); // red mushroom
+ setBlockType(40, BlockType.Transparent);
+ setGeometryProducer(40, vegetationMeshProducer);
+ setIsCollidable(40, false);
+
+ setBlockMapping(41, 7, 1); // gold block
+ setBlockMapping(42, 6, 1); // iron block
+ setBlockMapping(43, 5, 0); // double slabs
+ setBlockMapping(44, 5, 0); // slabs
+ setBlockMapping(45, 7, 0); // brick block
+ setBlockMapping(46, 8, 0); // tnt
+ setBlockMapping(47, 4, 0, 3, 2, 4, 0, 4, 0, 4, 0, 4, 0); // bookshelf
+ setBlockMapping(48, 4, 2); // moss stone
+ setBlockMapping(49, 5, 2); // obsidian
+
+ setBlockMapping(50, 0, 5); // torch
+ setBlockType(50, BlockType.Transparent);
+ setGeometryProducer(50, vegetationMeshProducer);
+ setIsCollidable(50, false);
+ setIsLocalLightSource(50, true);
+
+ setBlockMapping(51, 0, 5); // fire
+ setBlockType(51, BlockType.Transparent);
+ setIsCollidable(51, false);
+ setIsLocalLightSource(51, true);
+
+ setBlockMapping(52, 1, 4); // monster spawner
+ setBlockType(52, BlockType.Transparent);
+
+ setBlockMapping(53, 4, 0); // stairs
+ setBlockMapping(54, 9, 1, 10, 1, 11, 1); // chest
+
+ setBlockMapping(55, 4, 10); // redstone wire
+ setBlockType(55, BlockType.Transparent);
+
+ setBlockMapping(56, 2, 3); // diamond ore
+ setBlockMapping(57, 8, 1); // diamond block
+ setBlockMapping(58, 11, 2, 11, 3, 11, 2); // crafting table
+
+ setBlockMapping(59, 15, 5); // seeds
+ setBlockType(59, BlockType.Transparent);
+
+ setBlockMapping(60, 7, 5); // farmland
+ setBlockMapping(61, 12, 2); // furnace
+ setBlockMapping(62, 13, 3); // burning furnace
+ setBlockMapping(63, 4, 0); // sign post
+
+ setBlockMapping(64, 1, 5); // door
+ setBlockType(64, BlockType.Transparent);
+ setBlockMapping(65, 3, 5); // ladders
+ setBlockType(65, BlockType.Transparent);
+ setBlockMapping(66, 0, 8); // rails
+ setBlockType(66, BlockType.Transparent);
+
+ setBlockMapping(67, 0, 1); // cobblestone stairs
+ setBlockMapping(68, 4, 0); // wallsign
+ setBlockMapping(69, 4, 8); // lever
+ setBlockMapping(70, 6, 0); // stone pressure plate
+ setBlockMapping(71, 2, 5); // iron door
+ setBlockMapping(72, 4, 0); // wooden pressure plate
+ setBlockMapping(73, 3, 3); // redstone ore
+ setBlockMapping(74, 3, 3); // glowing redstone ore
+
+ setBlockMapping(75, 0, 6); // redstone torch off
+ setBlockType(75, BlockType.Transparent);
+
+ setBlockMapping(76, 0, 5); // restone torch on
+ setBlockType(76, BlockType.Transparent);
+ setIsLocalLightSource(76, true);
+
+ setBlockMapping(77, 0, 1); // stone button
+ setBlockMapping(78, 2, 4); // snow
+ setBlockMapping(79, 2, 4); // ice
+ setBlockMapping(80, 2, 4); // snow block
+
+ setBlockMapping(81, 5, 4, 6, 4, 5, 4); // cactus
+ setBlockType(81, BlockType.Transparent);
+
+ setBlockMapping(82, 3, 1); // clay block
+
+ setBlockMapping(83, 9, 4); // sugar cane
+ setBlockType(83, BlockType.Transparent);
+
+ setBlockMapping(84, 11, 4, 10, 4, 10, 4); // jukebox
+
+ setBlockMapping(85, 5, 9); // fence
+ setBlockType(85, BlockType.Transparent);
+
+ setBlockMapping(86, 6, 6, 7, 7, 6, 7); // pumpkin
+ setBlockMapping(87, 7, 6); // netherrack
+ setBlockMapping(88, 8, 6); // soul sand
+ setBlockMapping(89, 9, 6); // glowstone block
+ setBlockMapping(90, 1, 12); // portal
+ setBlockMapping(91, 1, 12); // jack-o-lantern
+
+ setBlockMapping(92, 9, 7, 10, 7, 12, 7); // cake block
+ setBlockType(92, BlockType.Transparent);
+
+ setBlockMapping(93, 7, 6); // redstone repeater off
+ setBlockMapping(94, 7, 6); // redstone repeater on
+ setBlockMapping(95, 9, 1, 11, 1, 10, 1, 10, 1, 10, 1, 9, 1); // locked chest
+
+ // Some grass stuff
+ for (int i = 0; i < 8; i++) {
+ setBlockType(100 + i, BlockType.Transparent);
+ setBlockMapping(100 + i, 8 + i, 5);
+ setIsCollidable(100 + i, false);
+ setIsPickable(100 + i, false);
+ setGeometryProducer(100 + i, vegetationMeshProducer);
+ }
+ }
+
+ /**
+ * Set if this block should emit light
+ *
+ * @param blockId
+ * @param value
+ */
+ public void setIsLocalLightSource(final int blockId, final boolean value) {
+ isLocalLightMap[blockId] = value;
+ }
+
+ /**
+ * Setup texture tiles to use for this block
+ *
+ * @param blockId
+ * @param x
+ * @param y
+ */
+ public void setBlockMapping(final int blockId, final int x, final int y) {
+ setBlockMapping(blockId, x, y, x, y, x, y);
+ }
+
+ /**
+ * Setup texture tiles to use for this block
+ *
+ * @param blockId
+ * @param xTop
+ * @param yTop
+ * @param xSide
+ * @param ySide
+ * @param xBottom
+ * @param yBottom
+ */
+ public void setBlockMapping(final int blockId, final int xTop, final int yTop, final int xSide, final int ySide,
+ final int xBottom, final int yBottom) {
+ setBlockMapping(blockId, xTop, yTop, xSide, ySide, xSide, ySide, xSide, ySide, xSide, ySide, xBottom, yBottom);
+ }
+
+ /**
+ * Setup texture tiles to use for this block
+ *
+ * @param blockId
+ * @param xTop
+ * @param yTop
+ * @param xFront
+ * @param yFront
+ * @param xBack
+ * @param yBack
+ * @param xLeft
+ * @param yLeft
+ * @param xRight
+ * @param yRight
+ * @param xBottom
+ * @param yBottom
+ */
+ public void setBlockMapping(final int blockId, final int xTop, final int yTop, final int xFront, final int yFront,
+ final int xBack, final int yBack, final int xLeft, final int yLeft, final int xRight, final int yRight,
+ final int xBottom, final int yBottom) {
+ setBlockMapping(blockId, BlockSide.Top, xTop, yTop);
+ setBlockMapping(blockId, BlockSide.Front, xFront, yFront);
+ setBlockMapping(blockId, BlockSide.Back, xBack, yBack);
+ setBlockMapping(blockId, BlockSide.Left, xLeft, yLeft);
+ setBlockMapping(blockId, BlockSide.Right, xRight, yRight);
+ setBlockMapping(blockId, BlockSide.Bottom, xBottom, yBottom);
+ }
+
+ /**
+ * Setup texture tiles to use for this block
+ *
+ * @param blockId
+ * @param side
+ * @param x
+ * @param y
+ */
+ public void setBlockMapping(final int blockId, final BlockSide side, final int x, final int y) {
+ EnumMap<BlockSide, Vector2> coord = blockTextureCoords.get(blockId);
+ if (coord == null) {
+ coord = new EnumMap<>(BlockSide.class);
+ blockTextureCoords.put(blockId, coord);
+ }
+ fillCoord(coord, side, (yCount - y - 1) * xCount + x);
+ }
+
+ /**
+ * Set if this block is see-through or not (if not, then hidden surface removal can be done)
+ *
+ * @param blockId
+ * @param type
+ */
+ public void setBlockType(final int blockId, final BlockType type) {
+ types[blockId] = type;
+ }
+
+ public BlockType getBlockType(final int blockId) {
+ return types[blockId];
+ }
+
+ public boolean isLocalLight(final int block) {
+ return isLocalLightMap[block];
+ }
+
+ private void fillCoord(final EnumMap<BlockSide, Vector2> coord, final BlockSide blockSide, final int pos) {
+ final double x = pos % xCount * textureReciprocalX;
+ final double y = pos / yCount * textureReciprocalY;
+
+ coord.put(blockSide, new Vector2(x, y));
+ }
+
+ /**
+ * Get start coordinates for a specific side of a block.
+ *
+ * @param blockId
+ * @param blockSide
+ * @return
+ */
+ public ReadOnlyVector2 getBlockTextureCoord(final int blockId, final BlockSide blockSide) {
+ if (!blockTextureCoords.containsKey(blockId)) {
+ logger.info("No mapping found for blockId: " + blockId);
+ return Vector2.ZERO;
+ }
+ return blockTextureCoords.get(blockId).get(blockSide);
+ }
+
+ /**
+ * Get offset to fix the artifact issue of texture bleeding.
+ *
+ * @return
+ */
+ public float getOffsetWidth() {
+ return offsetWidth;
+ }
+
+ /**
+ * Get offset to fix the artifact issue of texture bleeding.
+ *
+ * @return
+ */
+ public float getOffsetHeight() {
+ return offsetHeight;
+ }
+
+ /**
+ * Get the width of each tile in the texture atlas.
+ *
+ * @return
+ */
+ public float getTileWidth() {
+ return textureReciprocalX;
+ }
+
+ /**
+ * Get the height of each tile in the texture atlas.
+ *
+ * @return
+ */
+ public float getTileHeight() {
+ return textureReciprocalY;
+ }
+
+ /**
+ * Set the geometry producer to use for this block id.
+ *
+ * @param blockId
+ * @param handler
+ */
+ public void setGeometryProducer(final int blockId, final GeometryProducer handler) {
+ geometryProducers[blockId] = handler;
+ }
+
+ public GeometryProducer getGeometryProducer(final int blockId) {
+ return geometryProducers[blockId];
+ }
+
+ /**
+ * Set if the block should be collidable.
+ *
+ * @param blockId
+ * @param value
+ */
+ public void setIsCollidable(final int blockId, final boolean value) {
+ isCollidable[blockId] = value;
+ }
+
+ public boolean getIsCollidable(final int blockId) {
+ return isCollidable[blockId];
+ }
+
+ /**
+ * Set if the block should be pickable.
+ *
+ * @param blockId
+ * @param value
+ */
+ public void setIsPickable(final int blockId, final boolean value) {
+ isPickable[blockId] = value;
+ }
+
+ public boolean getIsPickable(final int blockId) {
+ return isPickable[blockId];
+ }
+
+ /**
+ * Set if block is semi-transparent like water and should not generate sides when next to itself.
+ *
+ * @param blockId
+ * @param value
+ */
+ public void setIsSemiTransparent(final int blockId, final boolean value) {
+ isSemiTransparent[blockId] = value;
+ }
+
+ public boolean getIsSemiTransparent(final int blockId) {
+ return isSemiTransparent[blockId];
+ }
+
+ private Mesh createVegetationMesh() {
+ final Node combineNode = new Node();
+
+ final double sizeX = 1;
+ final double sizeY = 1;
+
+ Quad q = new Quad("quad", sizeX, sizeY);
+ q.setRotation(new Quaternion().fromAngleAxis(MathUtils.HALF_PI * 0.5, Vector3.UNIT_Y));
+ combineNode.attachChild(q);
+
+ q = new Quad("quad", sizeX, sizeY);
+ q.setRotation(new Quaternion().fromAngleAxis(-MathUtils.HALF_PI * 0.5, Vector3.UNIT_Y));
+ combineNode.attachChild(q);
+
+ q = new Quad("quad", sizeX, sizeY);
+ q.setRotation(new Quaternion().fromAngleAxis(MathUtils.HALF_PI * 1.5, Vector3.UNIT_Y));
+ combineNode.attachChild(q);
+
+ q = new Quad("quad", sizeX, sizeY);
+ q.setRotation(new Quaternion().fromAngleAxis(-MathUtils.HALF_PI * 1.5, Vector3.UNIT_Y));
+ combineNode.attachChild(q);
+
+ combineNode.updateWorldTransform(true);
+
+ return MeshCombiner.combine(combineNode);
+ }
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/util/ColorUtil.java b/ardor3d-craft/src/main/java/com/ardorcraft/util/ColorUtil.java
new file mode 100644
index 0000000..b73c050
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/util/ColorUtil.java
@@ -0,0 +1,83 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.util;
+
+import java.awt.Color;
+import java.awt.Rectangle;
+import java.awt.image.BufferedImage;
+import java.util.List;
+
+/**
+ * Utility class for tinting areas of an image (for example tint black/white grass patch for biomes)
+ */
+public class ColorUtil {
+ public static BufferedImage tintAreas(final BufferedImage input, final List<Rectangle> areas,
+ final int sourceTintX, final int sourceTintY) throws Exception {
+ BufferedImage image = input;
+ switch (input.getType()) {
+ case BufferedImage.TYPE_INT_ARGB:
+ case BufferedImage.TYPE_INT_RGB:
+ break;
+ case BufferedImage.TYPE_4BYTE_ABGR:
+ image = new BufferedImage(input.getWidth(), input.getHeight(), BufferedImage.TYPE_INT_ARGB);
+ image.getGraphics().drawImage(input, 0, 0, null);
+ break;
+ default:
+ throw new Exception("Input terrain texture, unhandled image data format: " + input.getType());
+ }
+
+ boolean alpha = false;
+ final int col = image.getRGB(sourceTintX, sourceTintY);
+ final int red = col >> 16 & 0xff;
+ final int green = col >> 8 & 0xff;
+ final int blue = col & 0xff;
+ final float[] hsb = Color.RGBtoHSB(red, green, blue, null);
+ final float hue = hsb[0];
+ final float saturation = hsb[1];// * 0.9f;
+
+ switch (image.getType()) {
+ case BufferedImage.TYPE_INT_ARGB:
+ alpha = true;
+ // Falls through on purpose.
+ case BufferedImage.TYPE_INT_RGB:
+ for (final Rectangle area : areas) {
+ tintArea(image, area, alpha, hue, saturation);
+ }
+ break;
+ default:
+ throw new Exception("Tinting texture, unhandled image data format: " + image.getType());
+ }
+ return image;
+ }
+
+ private static void tintArea(final BufferedImage input, final Rectangle area, final boolean alpha, final float hue,
+ final float saturation) {
+ for (int x = area.x; x < area.x + area.width; x++) {
+ for (int y = area.y; y < area.y + area.height; y++) {
+ final int col = input.getRGB(x, y);
+
+ // Specify 3 RGB values
+ final int red = col >> 16 & 0xff;
+ final int green = col >> 8 & 0xff;
+ final int blue = col & 0xff;
+
+ final float[] hsb = Color.RGBtoHSB(red, green, blue, null);
+ final float brightness = hsb[2];
+
+ int argb = Color.HSBtoRGB(hue, saturation, brightness) & 0xffffff;
+
+ // add alpha, if applicable
+ if (alpha) {
+ argb |= col & 0xff000000;
+ }
+
+ // apply to image
+ input.setRGB(x, y, argb);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/util/DoubleBufferedList.java b/ardor3d-craft/src/main/java/com/ardorcraft/util/DoubleBufferedList.java
new file mode 100644
index 0000000..2704807
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/util/DoubleBufferedList.java
@@ -0,0 +1,56 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.util;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Utility class used by the mailbox update system.
+ *
+ * @param <T>
+ */
+public final class DoubleBufferedList<T> {
+ private List<T> frontList = new ArrayList<>();
+ private List<T> backList = new ArrayList<>();
+
+ /**
+ * The add method can be called at any point.
+ *
+ * @param t
+ */
+ public void add(final T t) {
+ if (t != null) {
+ synchronized (backList) {
+ backList.add(t);
+ }
+ }
+ }
+
+ /**
+ * The switchAndGet call and it's returned list has to be accessed sequencially.
+ *
+ * @return The list
+ */
+ public List<T> switchAndGet() {
+ if (backList.isEmpty()) {
+ return Collections.emptyList();
+ }
+ synchronized (backList) {
+ final List<T> tmp = backList;
+ backList = frontList;
+ frontList = tmp;
+ backList.clear();
+ return frontList;
+ }
+ }
+
+ public boolean isEmpty() {
+ return backList.isEmpty();
+ }
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/util/DoubleBufferedSet.java b/ardor3d-craft/src/main/java/com/ardorcraft/util/DoubleBufferedSet.java
new file mode 100644
index 0000000..64315b9
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/util/DoubleBufferedSet.java
@@ -0,0 +1,50 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.util;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Utility class used by the mailbox update system.
+ *
+ * @param <T>
+ */
+public class DoubleBufferedSet<T> {
+ private Set<T> frontSet = new HashSet<>();
+ private Set<T> backSet = new HashSet<>();
+
+ /**
+ * The add method can be called at any point.
+ *
+ * @param t
+ */
+ public void add(final T t) {
+ synchronized (backSet) {
+ backSet.add(t);
+ }
+ }
+
+ /**
+ * The switchAndGet call and it's returned list has to be accessed sequencially.
+ *
+ * @return The Set
+ */
+ public Set<T> switchAndGet() {
+ if (backSet.isEmpty()) {
+ return Collections.emptySet();
+ }
+ synchronized (backSet) {
+ final Set<T> tmp = backSet;
+ backSet = frontSet;
+ frontSet = tmp;
+ backSet.clear();
+ return frontSet;
+ }
+ }
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/util/FontHandler.java b/ardor3d-craft/src/main/java/com/ardorcraft/util/FontHandler.java
new file mode 100644
index 0000000..2339166
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/util/FontHandler.java
@@ -0,0 +1,67 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.util;
+
+import java.io.IOException;
+
+import com.ardor3d.image.Texture.MagnificationFilter;
+import com.ardor3d.image.Texture.MinificationFilter;
+import com.ardor3d.renderer.queue.RenderBucketType;
+import com.ardor3d.renderer.state.BlendState;
+import com.ardor3d.ui.text.BMFont;
+import com.ardor3d.ui.text.BMText.Align;
+import com.ardor3d.ui.text.BasicText;
+import com.ardor3d.util.resource.ResourceLocatorTool;
+import com.ardor3d.util.resource.URLResourceSource;
+
+public class FontHandler {
+ public static BMFont MINI_FONT;
+ public static int MINI_FONT_SIZE = 8;
+ public static BMFont BIG_FONT;
+ public static int BIG_FONT_SIZE = 15;
+ public static BMFont SMALL_FONT;
+ public static int SMALL_FONT_SIZE = 12;
+ public static BMFont MEDIUM_FONT;
+ public static int MEDIUM_FONT_SIZE = 16;
+
+ static {
+ try {
+ MINI_FONT = new BMFont(new URLResourceSource(ResourceLocatorTool.getClassPathResource(FontHandler.class,
+ "com/ardorcraft/resources/mini.fnt")), true);
+ MINI_FONT.getPageTexture().setMinificationFilter(MinificationFilter.NearestNeighborNoMipMaps);
+ MINI_FONT.getPageTexture().setMagnificationFilter(MagnificationFilter.NearestNeighbor);
+ BIG_FONT = new BMFont(new URLResourceSource(ResourceLocatorTool.getClassPathResource(FontHandler.class,
+ "com/ardorcraft/resources/big.fnt")), true);
+ BIG_FONT.getPageTexture().setMinificationFilter(MinificationFilter.NearestNeighborNoMipMaps);
+ BIG_FONT.getPageTexture().setMagnificationFilter(MagnificationFilter.NearestNeighbor);
+ SMALL_FONT = new BMFont(new URLResourceSource(ResourceLocatorTool.getClassPathResource(FontHandler.class,
+ "com/ardor3d/extension/ui/font/arial-12-regular.fnt")), true);
+ MEDIUM_FONT = new BMFont(new URLResourceSource(ResourceLocatorTool.getClassPathResource(FontHandler.class,
+ "com/ardor3d/extension/ui/font/arial-16-bold-regular.fnt")), true);
+ } catch (final IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private static BlendState bs = new BlendState();
+ static {
+ bs.setBlendEnabled(true);
+ }
+
+ public static BasicText createText(final String text) {
+ return createText(text, FontHandler.SMALL_FONT, FontHandler.SMALL_FONT_SIZE);
+ }
+
+ public static BasicText createText(final String text, final BMFont font, final int size) {
+ final BasicText itemLabel = new BasicText("Test", text, font, size);
+ itemLabel.getSceneHints().setRenderBucketType(RenderBucketType.Skip);
+ itemLabel.setAlign(Align.East);
+ itemLabel.setRenderState(bs);
+ itemLabel.updateGeometricState(0, true);
+ return itemLabel;
+ }
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/util/ImprovedNoise.java b/ardor3d-craft/src/main/java/com/ardorcraft/util/ImprovedNoise.java
new file mode 100644
index 0000000..c2345fe
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/util/ImprovedNoise.java
@@ -0,0 +1,197 @@
+/**
+ * Copyright (c) 2008-2010 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardorcraft.util;
+
+import com.ardor3d.math.MathUtils;
+
+/**
+ * <code>ImprovedNoise</code> Fast perlin noise.
+ *
+ * @author Ken Perlin
+ */
+public final class ImprovedNoise {
+ public static double noise(double x, double y, double z) {
+ final int X = (int) MathUtils.floor(x) & 255, // FIND UNIT CUBE THAT
+ Y = (int) MathUtils.floor(y) & 255, // CONTAINS POINT.
+ Z = (int) MathUtils.floor(z) & 255;
+ x -= MathUtils.floor(x); // FIND RELATIVE X,Y,Z
+ y -= MathUtils.floor(y); // OF POINT IN CUBE.
+ z -= MathUtils.floor(z);
+ final double u = fade(x), // COMPUTE FADE CURVES
+ v = fade(y), // FOR EACH OF X,Y,Z.
+ w = fade(z);
+ final int A = p[X] + Y, AA = p[A] + Z, AB = p[A + 1] + Z, // HASH COORDINATES OF
+ B = p[X + 1] + Y, BA = p[B] + Z, BB = p[B + 1] + Z; // THE 8 CUBE CORNERS,
+
+ return lerp(w, lerp(v, lerp(u, grad(p[AA], x, y, z), // AND ADD
+ grad(p[BA], x - 1, y, z)), // BLENDED
+ lerp(u, grad(p[AB], x, y - 1, z), // RESULTS
+ grad(p[BB], x - 1, y - 1, z))),// FROM 8
+ lerp(v, lerp(u, grad(p[AA + 1], x, y, z - 1), // CORNERS
+ grad(p[BA + 1], x - 1, y, z - 1)), // OF CUBE
+ lerp(u, grad(p[AB + 1], x, y - 1, z - 1), grad(p[BB + 1], x - 1, y - 1, z - 1))));
+ }
+
+ private static double fade(final double t) {
+ return t * t * t * (t * (t * 6 - 15) + 10);
+ }
+
+ private static double lerp(final double t, final double a, final double b) {
+ return a + t * (b - a);
+ }
+
+ private static double grad(final int hash, final double x, final double y, final double z) {
+ final int h = hash & 15; // CONVERT LO 4 BITS OF HASH CODE
+ final double u = h < 8 ? x : y, // INTO 12 GRADIENT DIRECTIONS.
+ v = h < 4 ? y : h == 12 || h == 14 ? x : z;
+ return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);
+ }
+
+ private static final int p[] = new int[512], permutation[] = {
+ 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99,
+ 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32,
+ 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27,
+ 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102,
+ 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116,
+ 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126,
+ 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152,
+ 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224,
+ 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81,
+ 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45,
+ 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156,
+ 180
+ };
+
+ static {
+ for (int i = 0; i < 256; i++) {
+ p[256 + i] = p[i] = permutation[i];
+ }
+ }
+
+ public static double noise2d(final double x, final double y, final int nbOctave) {
+ int result = 0;
+ final int frequence256 = 256;
+ int sx = (int) (x * frequence256);
+ int sy = (int) (y * frequence256);
+ int octave = nbOctave;
+ while (octave != 0) {
+ final int bX = sx & 0xFF;
+ final int bY = sy & 0xFF;
+
+ final int sxp = sx >> 8;
+ final int syp = sy >> 8;
+
+ // Compute noise for each corner of current cell
+ final int Y1376312589_00 = syp * 1376312589;
+ final int Y1376312589_01 = Y1376312589_00 + 1376312589;
+
+ final int XY1376312589_00 = sxp + Y1376312589_00;
+ final int XY1376312589_10 = XY1376312589_00 + 1;
+ final int XY1376312589_01 = sxp + Y1376312589_01;
+ final int XY1376312589_11 = XY1376312589_01 + 1;
+
+ final int XYBASE_00 = XY1376312589_00 << 13 ^ XY1376312589_00;
+ final int XYBASE_10 = XY1376312589_10 << 13 ^ XY1376312589_10;
+ final int XYBASE_01 = XY1376312589_01 << 13 ^ XY1376312589_01;
+ final int XYBASE_11 = XY1376312589_11 << 13 ^ XY1376312589_11;
+
+ int alt1 = XYBASE_00 * (XYBASE_00 * XYBASE_00 * 15731 + 789221) + 1376312589;
+ int alt2 = XYBASE_10 * (XYBASE_10 * XYBASE_10 * 15731 + 789221) + 1376312589;
+ int alt3 = XYBASE_01 * (XYBASE_01 * XYBASE_01 * 15731 + 789221) + 1376312589;
+ int alt4 = XYBASE_11 * (XYBASE_11 * XYBASE_11 * 15731 + 789221) + 1376312589;
+
+ /*
+ * NOTE : on for true grandiant noise uncomment following block for true gradiant we need to perform scalar
+ * product here, gradiant vector are created/deducted using the above pseudo random values (alt1...alt4) :
+ * by cutting thoses values in twice values to get for each a fixed x,y vector gradX1= alt1&0xFF gradY1=
+ * (alt1&0xFF00)>>8
+ *
+ * the last part of the PRN (alt1&0xFF0000)>>8 is used as an offset to correct one of the gradiant problem
+ * wich is zero on cell edge
+ *
+ * source vector (sXN;sYN) for scalar product are computed using (bX,bY)
+ *
+ * each four values must be replaced by the result of the following altN=(gradXN;gradYN) scalar (sXN;sYN)
+ *
+ * all the rest of the code (interpolation+accumulation) is identical for value & gradiant noise
+ */
+
+ /* START BLOCK FOR TRUE GRADIANT NOISE */
+
+ final int grad1X = (alt1 & 0xFF) - 128;
+ final int grad1Y = (alt1 >> 8 & 0xFF) - 128;
+ final int grad2X = (alt2 & 0xFF) - 128;
+ final int grad2Y = (alt2 >> 8 & 0xFF) - 128;
+ final int grad3X = (alt3 & 0xFF) - 128;
+ final int grad3Y = (alt3 >> 8 & 0xFF) - 128;
+ final int grad4X = (alt4 & 0xFF) - 128;
+ final int grad4Y = (alt4 >> 8 & 0xFF) - 128;
+
+ final int sX1 = bX >> 1;
+ final int sY1 = bY >> 1;
+ final int sX2 = 128 - sX1;
+ final int sY2 = sY1;
+ final int sX3 = sX1;
+ final int sY3 = 128 - sY1;
+ final int sX4 = 128 - sX1;
+ final int sY4 = 128 - sY1;
+ alt1 = grad1X * sX1 + grad1Y * sY1 + 16384 + ((alt1 & 0xFF0000) >> 9); // to avoid seams to be 0 we use an
+ // offset
+ alt2 = grad2X * sX2 + grad2Y * sY2 + 16384 + ((alt2 & 0xFF0000) >> 9);
+ alt3 = grad3X * sX3 + grad3Y * sY3 + 16384 + ((alt3 & 0xFF0000) >> 9);
+ alt4 = grad4X * sX4 + grad4Y * sY4 + 16384 + ((alt4 & 0xFF0000) >> 9);
+
+ /* END BLOCK FOR TRUE GRADIANT NOISE */
+
+ /* START BLOCK FOR VALUE NOISE */
+ /*
+ * alt1&=0xFFFF; alt2&=0xFFFF; alt3&=0xFFFF; alt4&=0xFFFF;
+ */
+ /* END BLOCK FOR VALUE NOISE */
+
+ /* START BLOCK FOR LINEAR INTERPOLATION */
+ // BiLinear interpolation
+ /*
+ * int f24=(bX*bY)>>8; int f23=bX-f24; int f14=bY-f24; int f13=256-f14-f23-f24;
+ *
+ * int val=(alt1*f13+alt2*f23+alt3*f14+alt4*f24);
+ */
+ /* END BLOCK FOR LINEAR INTERPOLATION */
+
+ // BiCubic interpolation ( in the form alt(bX) = alt[n] - (3*bX^2 - 2*bX^3) * (alt[n] - alt[n+1]) )
+ /* START BLOCK FOR BICUBIC INTERPOLATION */
+ final int bX2 = bX * bX >> 8;
+ final int bX3 = bX2 * bX >> 8;
+ final int _3bX2 = 3 * bX2;
+ final int _2bX3 = 2 * bX3;
+ final int alt12 = alt1 - ((_3bX2 - _2bX3) * (alt1 - alt2) >> 8);
+ final int alt34 = alt3 - ((_3bX2 - _2bX3) * (alt3 - alt4) >> 8);
+
+ final int bY2 = bY * bY >> 8;
+ final int bY3 = bY2 * bY >> 8;
+ final int _3bY2 = 3 * bY2;
+ final int _2bY3 = 2 * bY3;
+ int val = alt12 - ((_3bY2 - _2bY3) * (alt12 - alt34) >> 8);
+
+ val *= 256;
+ /* END BLOCK FOR BICUBIC INTERPOLATION */
+
+ // Accumulate in result
+ result += val << octave;
+
+ octave--;
+ sx <<= 1;
+ sy <<= 1;
+
+ }
+ return (result >>> 16 + nbOctave + 1) / 255.0 - 0.5;
+ }
+} \ No newline at end of file
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/util/MouseButtonHeldCondition.java b/ardor3d-craft/src/main/java/com/ardorcraft/util/MouseButtonHeldCondition.java
new file mode 100644
index 0000000..4df8f38
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/util/MouseButtonHeldCondition.java
@@ -0,0 +1,54 @@
+/**
+ * Copyright (c) 2008-2010 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardorcraft.util;
+
+import java.util.function.Predicate;
+
+import com.ardor3d.annotation.Immutable;
+import com.ardor3d.input.ButtonState;
+import com.ardor3d.input.InputState;
+import com.ardor3d.input.MouseButton;
+import com.ardor3d.input.logical.TwoInputStates;
+
+/**
+ * A condition that is true if a given button was pressed when going from the previous input state to the current one.
+ */
+@Immutable
+public final class MouseButtonHeldCondition implements Predicate<TwoInputStates> {
+ private final MouseButton _button;
+
+ /**
+ * Construct a new MouseButtonHeldCondition.
+ *
+ * @param button
+ * the button that should be pressed to trigger this condition
+ * @throws NullPointerException
+ * if the button is null
+ */
+ public MouseButtonHeldCondition(final MouseButton button) {
+ if (button == null) {
+ throw new NullPointerException();
+ }
+
+ _button = button;
+ }
+
+ @Override
+ public boolean test(final TwoInputStates states) {
+ final InputState currentState = states.getCurrent();
+
+ if (currentState == null || !currentState.getMouseState().hasButtonState(ButtonState.DOWN)) {
+ return false;
+ }
+
+ return currentState.getMouseState().getButtonState(_button) == ButtonState.DOWN;
+ }
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/util/SimplexNoise.java b/ardor3d-craft/src/main/java/com/ardorcraft/util/SimplexNoise.java
new file mode 100644
index 0000000..e3f2ee4
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/util/SimplexNoise.java
@@ -0,0 +1,706 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.util;
+
+/**
+ * Simplex Noise in 2D, 3D and 4D. Based on the example code of this paper:
+ * http://staffwww.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf
+ *
+ * @author Stefan Gustavson, Linkping University, Sweden (stegu at itn dot liu dot se)
+ *
+ * Slight optimizations & restructuring by
+ * @author Karsten Schmidt (info at toxi dot co dot uk)
+ *
+ */
+
+public class SimplexNoise {
+
+ private static final double SQRT3 = Math.sqrt(3.0);
+ private static final double SQRT5 = Math.sqrt(5.0);
+
+ /**
+ * Skewing and unskewing factors for 2D, 3D and 4D, some of them pre-multiplied.
+ */
+ private static final double F2 = 0.5 * (SQRT3 - 1.0);
+ private static final double G2 = (3.0 - SQRT3) / 6.0;
+ private static final double G22 = G2 * 2.0 - 1;
+
+ private static final double F3 = 1.0 / 3.0;
+ private static final double G3 = 1.0 / 6.0;
+
+ private static final double F4 = (SQRT5 - 1.0) / 4.0;
+ private static final double G4 = (5.0 - SQRT5) / 20.0;
+ private static final double G42 = G4 * 2.0;
+ private static final double G43 = G4 * 3.0;
+ private static final double G44 = G4 * 4.0 - 1.0;
+
+ /**
+ * Gradient vectors for 3D (pointing to mid points of all edges of a unit cube)
+ */
+ private static final int[][] grad3 = {
+ {
+ 1, 1, 0
+ }, {
+ -1, 1, 0
+ }, {
+ 1, -1, 0
+ }, {
+ -1, -1, 0
+ }, {
+ 1, 0, 1
+ }, {
+ -1, 0, 1
+ }, {
+ 1, 0, -1
+ }, {
+ -1, 0, -1
+ }, {
+ 0, 1, 1
+ }, {
+ 0, -1, 1
+ }, {
+ 0, 1, -1
+ }, {
+ 0, -1, -1
+ }
+ };
+
+ /**
+ * Gradient vectors for 4D (pointing to mid points of all edges of a unit 4D hypercube)
+ */
+ private static final int[][] grad4 = {
+ {
+ 0, 1, 1, 1
+ }, {
+ 0, 1, 1, -1
+ }, {
+ 0, 1, -1, 1
+ }, {
+ 0, 1, -1, -1
+ }, {
+ 0, -1, 1, 1
+ }, {
+ 0, -1, 1, -1
+ }, {
+ 0, -1, -1, 1
+ }, {
+ 0, -1, -1, -1
+ }, {
+ 1, 0, 1, 1
+ }, {
+ 1, 0, 1, -1
+ }, {
+ 1, 0, -1, 1
+ }, {
+ 1, 0, -1, -1
+ }, {
+ -1, 0, 1, 1
+ }, {
+ -1, 0, 1, -1
+ }, {
+ -1, 0, -1, 1
+ }, {
+ -1, 0, -1, -1
+ }, {
+ 1, 1, 0, 1
+ }, {
+ 1, 1, 0, -1
+ }, {
+ 1, -1, 0, 1
+ }, {
+ 1, -1, 0, -1
+ }, {
+ -1, 1, 0, 1
+ }, {
+ -1, 1, 0, -1
+ }, {
+ -1, -1, 0, 1
+ }, {
+ -1, -1, 0, -1
+ }, {
+ 1, 1, 1, 0
+ }, {
+ 1, 1, -1, 0
+ }, {
+ 1, -1, 1, 0
+ }, {
+ 1, -1, -1, 0
+ }, {
+ -1, 1, 1, 0
+ }, {
+ -1, 1, -1, 0
+ }, {
+ -1, -1, 1, 0
+ }, {
+ -1, -1, -1, 0
+ }
+ };
+
+ /**
+ * Permutation table
+ */
+ private static final int[] p = {
+ 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99,
+ 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32,
+ 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27,
+ 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102,
+ 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116,
+ 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126,
+ 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152,
+ 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224,
+ 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81,
+ 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45,
+ 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156,
+ 180
+ };
+
+ /**
+ * To remove the need for index wrapping, double the permutation table length
+ */
+ private static int[] perm = new int[0x200];
+ /**
+ * A lookup table to traverse the simplex around a given point in 4D. Details can be found where this table is used,
+ * in the 4D noise method.
+ */
+ private static final int[][] simplex = {
+ {
+ 0, 1, 2, 3
+ }, {
+ 0, 1, 3, 2
+ }, {
+ 0, 0, 0, 0
+ }, {
+ 0, 2, 3, 1
+ }, {
+ 0, 0, 0, 0
+ }, {
+ 0, 0, 0, 0
+ }, {
+ 0, 0, 0, 0
+ }, {
+ 1, 2, 3, 0
+ }, {
+ 0, 2, 1, 3
+ }, {
+ 0, 0, 0, 0
+ }, {
+ 0, 3, 1, 2
+ }, {
+ 0, 3, 2, 1
+ }, {
+ 0, 0, 0, 0
+ }, {
+ 0, 0, 0, 0
+ }, {
+ 0, 0, 0, 0
+ }, {
+ 1, 3, 2, 0
+ }, {
+ 0, 0, 0, 0
+ }, {
+ 0, 0, 0, 0
+ }, {
+ 0, 0, 0, 0
+ }, {
+ 0, 0, 0, 0
+ }, {
+ 0, 0, 0, 0
+ }, {
+ 0, 0, 0, 0
+ }, {
+ 0, 0, 0, 0
+ }, {
+ 0, 0, 0, 0
+ }, {
+ 1, 2, 0, 3
+ }, {
+ 0, 0, 0, 0
+ }, {
+ 1, 3, 0, 2
+ }, {
+ 0, 0, 0, 0
+ }, {
+ 0, 0, 0, 0
+ }, {
+ 0, 0, 0, 0
+ }, {
+ 2, 3, 0, 1
+ }, {
+ 2, 3, 1, 0
+ }, {
+ 1, 0, 2, 3
+ }, {
+ 1, 0, 3, 2
+ }, {
+ 0, 0, 0, 0
+ }, {
+ 0, 0, 0, 0
+ }, {
+ 0, 0, 0, 0
+ }, {
+ 2, 0, 3, 1
+ }, {
+ 0, 0, 0, 0
+ }, {
+ 2, 1, 3, 0
+ }, {
+ 0, 0, 0, 0
+ }, {
+ 0, 0, 0, 0
+ }, {
+ 0, 0, 0, 0
+ }, {
+ 0, 0, 0, 0
+ }, {
+ 0, 0, 0, 0
+ }, {
+ 0, 0, 0, 0
+ }, {
+ 0, 0, 0, 0
+ }, {
+ 0, 0, 0, 0
+ }, {
+ 2, 0, 1, 3
+ }, {
+ 0, 0, 0, 0
+ }, {
+ 0, 0, 0, 0
+ }, {
+ 0, 0, 0, 0
+ }, {
+ 3, 0, 1, 2
+ }, {
+ 3, 0, 2, 1
+ }, {
+ 0, 0, 0, 0
+ }, {
+ 3, 1, 2, 0
+ }, {
+ 2, 1, 0, 3
+ }, {
+ 0, 0, 0, 0
+ }, {
+ 0, 0, 0, 0
+ }, {
+ 0, 0, 0, 0
+ }, {
+ 3, 1, 0, 2
+ }, {
+ 0, 0, 0, 0
+ }, {
+ 3, 2, 0, 1
+ }, {
+ 3, 2, 1, 0
+ }
+ };
+
+ static {
+ for (int i = 0; i < 0x200; i++) {
+ perm[i] = p[i & 0xff];
+ }
+ }
+
+ /**
+ * Computes dot product in 2D.
+ *
+ * @param g
+ * 2-vector (grid offset)
+ * @param x
+ * @param y
+ * @return dot product
+ */
+ private static double dot(final int g[], final double x, final double y) {
+ return g[0] * x + g[1] * y;
+ }
+
+ /**
+ * Computes dot product in 3D.
+ *
+ * @param g
+ * 3-vector (grid offset)
+ * @param x
+ * @param y
+ * @param z
+ * @return dot product
+ */
+ private static double dot(final int g[], final double x, final double y, final double z) {
+ return g[0] * x + g[1] * y + g[2] * z;
+ }
+
+ /**
+ * Computes dot product in 4D.
+ *
+ * @param g
+ * 4-vector (grid offset)
+ * @param x
+ * @param y
+ * @param z
+ * @param w
+ * @return dot product
+ */
+ private static double dot(final int g[], final double x, final double y, final double z, final double w) {
+ return g[0] * x + g[1] * y + g[2] * z + g[3] * w;
+ }
+
+ /**
+ * This method is a *lot* faster than using (int)Math.floor(x).
+ *
+ * @param x
+ * value to be floored
+ * @return
+ */
+ private static final int fastfloor(final double x) {
+ return x >= 0 ? (int) x : (int) x - 1;
+ }
+
+ /**
+ * Computes 2D Simplex Noise.
+ *
+ * @param x
+ * coordinate
+ * @param y
+ * coordinate
+ * @return noise value in range -1 ... +1.
+ */
+ public static double noise(final double x, final double y) {
+ double n0 = 0, n1 = 0, n2 = 0; // Noise contributions from the three
+ // corners
+ // Skew the input space to determine which simplex cell we're in
+ final double s = (x + y) * F2; // Hairy factor for 2D
+ final int i = fastfloor(x + s);
+ final int j = fastfloor(y + s);
+ final double t = (i + j) * G2;
+ final double x0 = x - (i - t); // The x,y distances from the cell origin
+ final double y0 = y - (j - t);
+ // For the 2D case, the simplex shape is an equilateral triangle.
+ // Determine which simplex we are in.
+ int i1, j1; // Offsets for second (middle) corner of simplex in (i,j)
+ if (x0 > y0) {
+ i1 = 1;
+ j1 = 0;
+ } // lower triangle, XY order: (0,0)->(1,0)->(1,1)
+ else {
+ i1 = 0;
+ j1 = 1;
+ } // upper triangle, YX order: (0,0)->(0,1)->(1,1)
+ // A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and
+ // a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where
+ // c = (3-sqrt(3))/6
+ final double x1 = x0 - i1 + G2; // Offsets for middle corner in (x,y) unskewed
+ final double y1 = y0 - j1 + G2;
+ final double x2 = x0 + G22; // Offsets for last corner in (x,y) unskewed
+ final double y2 = y0 + G22;
+ // Work out the hashed gradient indices of the three simplex corners
+ final int ii = i & 0xff;
+ final int jj = j & 0xff;
+ // Calculate the contribution from the three corners
+ double t0 = 0.5 - x0 * x0 - y0 * y0;
+ if (t0 > 0) {
+ t0 *= t0;
+ final int gi0 = perm[ii + perm[jj]] % 12;
+ n0 = t0 * t0 * dot(grad3[gi0], x0, y0); // (x,y) of grad3 used for
+ // 2D gradient
+ }
+ double t1 = 0.5 - x1 * x1 - y1 * y1;
+ if (t1 > 0) {
+ t1 *= t1;
+ final int gi1 = perm[ii + i1 + perm[jj + j1]] % 12;
+ n1 = t1 * t1 * dot(grad3[gi1], x1, y1);
+ }
+ double t2 = 0.5 - x2 * x2 - y2 * y2;
+ if (t2 > 0) {
+ t2 *= t2;
+ final int gi2 = perm[ii + 1 + perm[jj + 1]] % 12;
+ n2 = t2 * t2 * dot(grad3[gi2], x2, y2);
+ }
+ // Add contributions from each corner to get the final noise value.
+ // The result is scaled to return values in the interval [-1,1].
+ return 70.0 * (n0 + n1 + n2);
+ }
+
+ /**
+ * Computes 3D Simplex Noise.
+ *
+ * @param x
+ * coordinate
+ * @param y
+ * coordinate
+ * @param z
+ * coordinate
+ * @return noise value in range -1 ... +1
+ */
+ public static double noise(final double x, final double y, final double z) {
+ double n0 = 0, n1 = 0, n2 = 0, n3 = 0;
+ // Noise contributions from the
+ // four corners
+ // Skew the input space to determine which simplex cell we're in
+ // final double F3 = 1.0 / 3.0;
+ final double s = (x + y + z) * F3; // Very nice and simple skew factor
+ // for 3D
+ final int i = fastfloor(x + s);
+ final int j = fastfloor(y + s);
+ final int k = fastfloor(z + s);
+ // final double G3 = 1.0 / 6.0; // Very nice and simple unskew factor,
+ // too
+ final double t = (i + j + k) * G3;
+ final double x0 = x - (i - t); // The x,y,z distances from the cell origin
+ final double y0 = y - (j - t);
+ final double z0 = z - (k - t);
+ // For the 3D case, the simplex shape is a slightly irregular
+ // tetrahedron.
+ // Determine which simplex we are in.
+ int i1, j1, k1; // Offsets for second corner of simplex in (i,j,k)
+ // coords
+ int i2, j2, k2; // Offsets for third corner of simplex in (i,j,k) coords
+ if (x0 >= y0) {
+ if (y0 >= z0) {
+ i1 = 1;
+ j1 = 0;
+ k1 = 0;
+ i2 = 1;
+ j2 = 1;
+ k2 = 0;
+ } // X Y Z order
+ else if (x0 >= z0) {
+ i1 = 1;
+ j1 = 0;
+ k1 = 0;
+ i2 = 1;
+ j2 = 0;
+ k2 = 1;
+ } // X Z Y order
+ else {
+ i1 = 0;
+ j1 = 0;
+ k1 = 1;
+ i2 = 1;
+ j2 = 0;
+ k2 = 1;
+ } // Z X Y order
+ } else { // x0<y0
+ if (y0 < z0) {
+ i1 = 0;
+ j1 = 0;
+ k1 = 1;
+ i2 = 0;
+ j2 = 1;
+ k2 = 1;
+ } // Z Y X order
+ else if (x0 < z0) {
+ i1 = 0;
+ j1 = 1;
+ k1 = 0;
+ i2 = 0;
+ j2 = 1;
+ k2 = 1;
+ } // Y Z X order
+ else {
+ i1 = 0;
+ j1 = 1;
+ k1 = 0;
+ i2 = 1;
+ j2 = 1;
+ k2 = 0;
+ } // Y X Z order
+ }
+ // A step of (1,0,0) in (i,j,k) means a step of (1-c,-c,-c) in (x,y,z),
+ // a step of (0,1,0) in (i,j,k) means a step of (-c,1-c,-c) in (x,y,z),
+ // and
+ // a step of (0,0,1) in (i,j,k) means a step of (-c,-c,1-c) in (x,y,z),
+ // where
+ // c = 1/6.
+ final double x1 = x0 - i1 + G3; // Offsets for second corner in (x,y,z) coords
+ final double y1 = y0 - j1 + G3;
+ final double z1 = z0 - k1 + G3;
+
+ final double x2 = x0 - i2 + F3; // Offsets for third corner in (x,y,z)
+ final double y2 = y0 - j2 + F3;
+ final double z2 = z0 - k2 + F3;
+
+ final double x3 = x0 - 0.5; // Offsets for last corner in (x,y,z)
+ final double y3 = y0 - 0.5;
+ final double z3 = z0 - 0.5;
+ // Work out the hashed gradient indices of the four simplex corners
+ final int ii = i & 0xff;
+ final int jj = j & 0xff;
+ final int kk = k & 0xff;
+
+ // Calculate the contribution from the four corners
+ double t0 = 0.6 - x0 * x0 - y0 * y0 - z0 * z0;
+ if (t0 > 0) {
+ t0 *= t0;
+ final int gi0 = perm[ii + perm[jj + perm[kk]]] % 12;
+ n0 = t0 * t0 * dot(grad3[gi0], x0, y0, z0);
+ }
+ double t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1;
+ if (t1 > 0) {
+ t1 *= t1;
+ final int gi1 = perm[ii + i1 + perm[jj + j1 + perm[kk + k1]]] % 12;
+ n1 = t1 * t1 * dot(grad3[gi1], x1, y1, z1);
+ }
+ double t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2;
+ if (t2 > 0) {
+ t2 *= t2;
+ final int gi2 = perm[ii + i2 + perm[jj + j2 + perm[kk + k2]]] % 12;
+ n2 = t2 * t2 * dot(grad3[gi2], x2, y2, z2);
+ }
+ double t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3;
+ if (t3 > 0) {
+ t3 *= t3;
+ final int gi3 = perm[ii + 1 + perm[jj + 1 + perm[kk + 1]]] % 12;
+ n3 = t3 * t3 * dot(grad3[gi3], x3, y3, z3);
+ }
+ // Add contributions from each corner to get the final noise value.
+ // The result is scaled to stay just inside [-1,1]
+ return 32.0 * (n0 + n1 + n2 + n3);
+ }
+
+ /**
+ * Computes 4D Simplex Noise.
+ *
+ * @param x
+ * coordinate
+ * @param y
+ * coordinate
+ * @param z
+ * coordinate
+ * @param w
+ * coordinate
+ * @return noise value in range -1 ... +1
+ */
+ public static double noise(final double x, final double y, final double z, final double w) {
+ // The skewing and unskewing factors are hairy again for the 4D case
+ double n0 = 0, n1 = 0, n2 = 0, n3 = 0, n4 = 0; // Noise contributions
+ // from the five corners
+ // Skew the (x,y,z,w) space to determine which cell of 24 simplices
+ final double s = (x + y + z + w) * F4; // Factor for 4D skewing
+ final int i = fastfloor(x + s);
+ final int j = fastfloor(y + s);
+ final int k = fastfloor(z + s);
+ final int l = fastfloor(w + s);
+ final double t = (i + j + k + l) * G4; // Factor for 4D unskewing
+ final double x0 = x - (i - t); // The x,y,z,w distances from the cell origin
+ final double y0 = y - (j - t);
+ final double z0 = z - (k - t);
+ final double w0 = w - (l - t);
+ // For the 4D case, the simplex is a 4D shape I won't even try to
+ // describe.
+ // To find out which of the 24 possible simplices we're in, we need to
+ // determine the magnitude ordering of x0, y0, z0 and w0.
+ // The method below is a good way of finding the ordering of x,y,z,w and
+ // then find the correct traversal order for the simplex were in.
+ // First, six pair-wise comparisons are performed between each possible
+ // pair of the four coordinates, and the results are used to add up
+ // binary bits for an integer index.
+ int c = 0;
+ if (x0 > y0) {
+ c = 0x20;
+ }
+ if (x0 > z0) {
+ c |= 0x10;
+ }
+ if (y0 > z0) {
+ c |= 0x08;
+ }
+ if (x0 > w0) {
+ c |= 0x04;
+ }
+ if (y0 > w0) {
+ c |= 0x02;
+ }
+ if (z0 > w0) {
+ c |= 0x01;
+ }
+ int i1, j1, k1, l1; // The integer offsets for the second simplex corner
+ int i2, j2, k2, l2; // The integer offsets for the third simplex corner
+ int i3, j3, k3, l3; // The integer offsets for the fourth simplex corner
+ // simplex[c] is a 4-vector with the numbers 0, 1, 2 and 3 in some
+ // order. Many values of c will never occur, since e.g. x>y>z>w makes
+ // x<z, y<w and x<w impossible. Only the 24 indices which have non-zero
+ // entries make any sense. We use a thresholding to set the coordinates
+ // in turn from the largest magnitude. The number 3 in the "simplex"
+ // array is at the position of the largest coordinate.
+ final int[] sc = simplex[c];
+ i1 = sc[0] >= 3 ? 1 : 0;
+ j1 = sc[1] >= 3 ? 1 : 0;
+ k1 = sc[2] >= 3 ? 1 : 0;
+ l1 = sc[3] >= 3 ? 1 : 0;
+ // The number 2 in the "simplex" array is at the second largest
+ // coordinate.
+ i2 = sc[0] >= 2 ? 1 : 0;
+ j2 = sc[1] >= 2 ? 1 : 0;
+ k2 = sc[2] >= 2 ? 1 : 0;
+ l2 = sc[3] >= 2 ? 1 : 0;
+ // The number 1 in the "simplex" array is at the second smallest
+ // coordinate.
+ i3 = sc[0] >= 1 ? 1 : 0;
+ j3 = sc[1] >= 1 ? 1 : 0;
+ k3 = sc[2] >= 1 ? 1 : 0;
+ l3 = sc[3] >= 1 ? 1 : 0;
+ // The fifth corner has all coordinate offsets = 1, so no need to look
+ // that up.
+ final double x1 = x0 - i1 + G4; // Offsets for second corner in (x,y,z,w)
+ final double y1 = y0 - j1 + G4;
+ final double z1 = z0 - k1 + G4;
+ final double w1 = w0 - l1 + G4;
+
+ final double x2 = x0 - i2 + G42; // Offsets for third corner in (x,y,z,w)
+ final double y2 = y0 - j2 + G42;
+ final double z2 = z0 - k2 + G42;
+ final double w2 = w0 - l2 + G42;
+
+ final double x3 = x0 - i3 + G43; // Offsets for fourth corner in (x,y,z,w)
+ final double y3 = y0 - j3 + G43;
+ final double z3 = z0 - k3 + G43;
+ final double w3 = w0 - l3 + G43;
+
+ final double x4 = x0 + G44; // Offsets for last corner in (x,y,z,w)
+ final double y4 = y0 + G44;
+ final double z4 = z0 + G44;
+ final double w4 = w0 + G44;
+
+ // Work out the hashed gradient indices of the five simplex corners
+ final int ii = i & 0xff;
+ final int jj = j & 0xff;
+ final int kk = k & 0xff;
+ final int ll = l & 0xff;
+
+ // Calculate the contribution from the five corners
+ double t0 = 0.6 - x0 * x0 - y0 * y0 - z0 * z0 - w0 * w0;
+ if (t0 > 0) {
+ t0 *= t0;
+ final int gi0 = perm[ii + perm[jj + perm[kk + perm[ll]]]] % 32;
+ n0 = t0 * t0 * dot(grad4[gi0], x0, y0, z0, w0);
+ }
+ double t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1 - w1 * w1;
+ if (t1 > 0) {
+ t1 *= t1;
+ final int gi1 = perm[ii + i1 + perm[jj + j1 + perm[kk + k1 + perm[ll + l1]]]] % 32;
+ n1 = t1 * t1 * dot(grad4[gi1], x1, y1, z1, w1);
+ }
+ double t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2 - w2 * w2;
+ if (t2 > 0) {
+ t2 *= t2;
+ final int gi2 = perm[ii + i2 + perm[jj + j2 + perm[kk + k2 + perm[ll + l2]]]] % 32;
+ n2 = t2 * t2 * dot(grad4[gi2], x2, y2, z2, w2);
+ }
+ double t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3 - w3 * w3;
+ if (t3 > 0) {
+ t3 *= t3;
+ final int gi3 = perm[ii + i3 + perm[jj + j3 + perm[kk + k3 + perm[ll + l3]]]] % 32;
+ n3 = t3 * t3 * dot(grad4[gi3], x3, y3, z3, w3);
+ }
+ double t4 = 0.6 - x4 * x4 - y4 * y4 - z4 * z4 - w4 * w4;
+ if (t4 > 0) {
+ t4 *= t4;
+ final int gi4 = perm[ii + 1 + perm[jj + 1 + perm[kk + 1 + perm[ll + 1]]]] % 32;
+ n4 = t4 * t4 * dot(grad4[gi4], x4, y4, z4, w4);
+ }
+ // Sum up and scale the result to cover the range [-1,1]
+ return 27.0 * (n0 + n1 + n2 + n3 + n4);
+ }
+} \ No newline at end of file
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/util/geometryproducers/BoxProducer.java b/ardor3d-craft/src/main/java/com/ardorcraft/util/geometryproducers/BoxProducer.java
new file mode 100644
index 0000000..1101594
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/util/geometryproducers/BoxProducer.java
@@ -0,0 +1,494 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.util.geometryproducers;
+
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.type.ReadOnlyVector2;
+import com.ardorcraft.data.Direction;
+import com.ardorcraft.util.BlockUtil;
+import com.ardorcraft.world.BlockProvider;
+import com.ardorcraft.world.BlockSide;
+import com.ardorcraft.world.BlockType;
+import com.ardorcraft.world.GeometryHandler;
+
+/**
+ * Main blockworld geometry producer. Produces standard 1x1x1 boxes with hidden surface removal.
+ */
+public final class BoxProducer implements GeometryProducer {
+ private final int[] checkDirs = new int[] {//
+ -1, 0, 0, //
+ 1, 0, 0, //
+ 0, -1, 0, //
+ 0, 1, 0, //
+ 0, 0, -1, //
+ 0, 0, 1, //
+ };
+ private final boolean[] sides = new boolean[] {//
+ false, true,//
+ true, false,//
+ false, true
+ //
+ };
+ private final Direction[] directions = new Direction[] {//
+ Direction.X, Direction.X, //
+ Direction.Y, Direction.Y, //
+ Direction.Z, Direction.Z, //
+ };
+
+ private final float MOD = 0.12f;
+ private final float creaseAmount = 0.3f;
+
+ private final BlockSide[][] orientations = new BlockSide[][] {
+ { // Front
+ BlockSide.Front, BlockSide.Back, BlockSide.Left, BlockSide.Right, BlockSide.Top, BlockSide.Bottom
+ }, { // Back
+ BlockSide.Back, BlockSide.Front, BlockSide.Right, BlockSide.Left, BlockSide.Top, BlockSide.Bottom
+ }, { // Left
+ BlockSide.Right, BlockSide.Left, BlockSide.Back, BlockSide.Front, BlockSide.Top, BlockSide.Bottom
+ }, { // Right
+ BlockSide.Left, BlockSide.Right, BlockSide.Front, BlockSide.Back, BlockSide.Top, BlockSide.Bottom
+ }, { // Top
+ BlockSide.Front, BlockSide.Back, BlockSide.Left, BlockSide.Right, BlockSide.Top, BlockSide.Bottom
+ }, { // Bottom
+ BlockSide.Front, BlockSide.Back, BlockSide.Left, BlockSide.Right, BlockSide.Top, BlockSide.Bottom
+ }
+ };
+
+ public BoxProducer() {}
+
+ @Override
+ public void generateBlock(final int blockId, final GeometryHandler geometryHandler, final BlockProvider provider,
+ final BlockUtil blockUtil, final int x, final int y, final int z) {
+ int startIndex = 0;
+
+ final boolean isTransparent = blockUtil.getIsSemiTransparent(blockId);
+ final BlockSide orientation = BlockSide.values()[provider.getBlockExtra(x, y, z)];
+
+ for (int face = 0; face < 6; face++) {
+ final int xChange = checkDirs[face * 3 + 0];
+ final int yChange = checkDirs[face * 3 + 1];
+ final int zChange = checkDirs[face * 3 + 2];
+ final boolean side = sides[face];
+ final Direction direction = directions[face];
+
+ final int neighbourBlock = provider.getBlock(x + xChange, y + yChange, z + zChange);
+ final boolean neighbourSolid = blockUtil.getBlockType(neighbourBlock) == BlockType.Solid;
+ final boolean isNeighbourTransparent = blockUtil.getIsSemiTransparent(neighbourBlock);
+
+ if (neighbourBlock == 0 || !neighbourSolid || !isTransparent && isNeighbourTransparent) {
+ createBlockSide(geometryHandler, provider, blockUtil, orientation, x + Math.max(0, xChange),
+ y + Math.max(0, yChange), z + Math.max(0, zChange), blockId, side, direction, startIndex);
+ startIndex++;
+ }
+ }
+ geometryHandler.setIndexCount(startIndex * 6);
+ geometryHandler.setVertexCount(startIndex * 4);
+ }
+
+ private void createBlockSide(final GeometryHandler geometryHandler, final BlockProvider provider,
+ final BlockUtil blockUtil, final BlockSide orientation, final int x, final int y, final int z,
+ final int block, final boolean first, final Direction dir, final int startIndex) {
+ if (dir == Direction.X) {
+ if (geometryHandler.hasVertices()) {
+ geometryHandler.setVertex(startIndex * 4 + 0, x, y, z);
+ geometryHandler.setVertex(startIndex * 4 + 1, x, y + 1, z);
+ geometryHandler.setVertex(startIndex * 4 + 2, x, y + 1, z + 1);
+ geometryHandler.setVertex(startIndex * 4 + 3, x, y, z + 1);
+ }
+
+ if (geometryHandler.hasTextureCoords()) {
+ if (first) {
+ final ReadOnlyVector2 coord = blockUtil.getBlockTextureCoord(block,
+ getSide(orientation, BlockSide.Left));
+
+ geometryHandler.setTextureCoord(startIndex * 4 + 0, coord.getXf() + blockUtil.getTileWidth()
+ - blockUtil.getOffsetWidth(), coord.getYf() + blockUtil.getOffsetHeight());
+ geometryHandler.setTextureCoord(startIndex * 4 + 1, coord.getXf() + blockUtil.getTileWidth()
+ - blockUtil.getOffsetWidth(),
+ coord.getYf() + blockUtil.getTileHeight() - blockUtil.getOffsetHeight());
+ geometryHandler.setTextureCoord(startIndex * 4 + 2, coord.getXf() + blockUtil.getOffsetWidth(),
+ coord.getYf() + blockUtil.getTileHeight() - blockUtil.getOffsetHeight());
+ geometryHandler.setTextureCoord(startIndex * 4 + 3, coord.getXf() + blockUtil.getOffsetWidth(),
+ coord.getYf() + blockUtil.getOffsetHeight());
+ } else {
+ final ReadOnlyVector2 coord = blockUtil.getBlockTextureCoord(block,
+ getSide(orientation, BlockSide.Right));
+
+ geometryHandler.setTextureCoord(startIndex * 4 + 0, coord.getXf() + blockUtil.getOffsetWidth(),
+ coord.getYf() + blockUtil.getOffsetHeight());
+ geometryHandler.setTextureCoord(startIndex * 4 + 1, coord.getXf() + blockUtil.getOffsetWidth(),
+ coord.getYf() + blockUtil.getTileHeight() - blockUtil.getOffsetHeight());
+ geometryHandler.setTextureCoord(startIndex * 4 + 2, coord.getXf() + blockUtil.getTileWidth()
+ - blockUtil.getOffsetWidth(),
+ coord.getYf() + blockUtil.getTileHeight() - blockUtil.getOffsetHeight());
+ geometryHandler.setTextureCoord(startIndex * 4 + 3, coord.getXf() + blockUtil.getTileWidth()
+ - blockUtil.getOffsetWidth(), coord.getYf() + blockUtil.getOffsetHeight());
+ }
+ }
+
+ if (geometryHandler.hasColors()) {
+ float sideModifier = 0.04f;
+ if (first) {
+ sideModifier = MOD;
+ }
+
+ float globalLighting, localLighting, miniShade;
+
+ miniShade = miniShadeX(provider, blockUtil, x, y, z, 0, first);
+ globalLighting = MathUtils.clamp(geometryHandler.getGlobalLighting(x, y, z) - sideModifier, 0f, 1f)
+ * miniShade;
+ localLighting = MathUtils.clamp(geometryHandler.getLocalLighting(x, y, z) - sideModifier, 0f, 1f)
+ * miniShade;
+ geometryHandler.setColor(startIndex * 4 + 0, globalLighting, localLighting);
+
+ miniShade = miniShadeX(provider, blockUtil, x, y, z, 1, first);
+ globalLighting = MathUtils.clamp(geometryHandler.getGlobalLighting(x, y + 1, z) - sideModifier, 0f, 1f)
+ * miniShade;
+ localLighting = MathUtils.clamp(geometryHandler.getLocalLighting(x, y + 1, z) - sideModifier, 0f, 1f)
+ * miniShade;
+ geometryHandler.setColor(startIndex * 4 + 1, globalLighting, localLighting);
+
+ miniShade = miniShadeX(provider, blockUtil, x, y, z, 2, first);
+ globalLighting = MathUtils.clamp(geometryHandler.getGlobalLighting(x, y + 1, z + 1) - sideModifier, 0f,
+ 1f) * miniShade;
+ localLighting = MathUtils.clamp(geometryHandler.getLocalLighting(x, y + 1, z + 1) - sideModifier, 0f,
+ 1f) * miniShade;
+ geometryHandler.setColor(startIndex * 4 + 2, globalLighting, localLighting);
+
+ miniShade = miniShadeX(provider, blockUtil, x, y, z, 3, first);
+ globalLighting = MathUtils.clamp(geometryHandler.getGlobalLighting(x, y, z + 1) - sideModifier, 0f, 1f)
+ * miniShade;
+ localLighting = MathUtils.clamp(geometryHandler.getLocalLighting(x, y, z + 1) - sideModifier, 0f, 1f)
+ * miniShade;
+ geometryHandler.setColor(startIndex * 4 + 3, globalLighting, localLighting);
+ }
+
+ if (geometryHandler.hasIndices()) {
+ if (first) {
+ geometryHandler.setIndex(startIndex * 6 + 0, startIndex * 4 + 0);
+ geometryHandler.setIndex(startIndex * 6 + 1, startIndex * 4 + 1);
+ geometryHandler.setIndex(startIndex * 6 + 2, startIndex * 4 + 3);
+ geometryHandler.setIndex(startIndex * 6 + 3, startIndex * 4 + 1);
+ geometryHandler.setIndex(startIndex * 6 + 4, startIndex * 4 + 2);
+ geometryHandler.setIndex(startIndex * 6 + 5, startIndex * 4 + 3);
+ } else {
+ geometryHandler.setIndex(startIndex * 6 + 0, startIndex * 4 + 3);
+ geometryHandler.setIndex(startIndex * 6 + 1, startIndex * 4 + 2);
+ geometryHandler.setIndex(startIndex * 6 + 2, startIndex * 4 + 1);
+ geometryHandler.setIndex(startIndex * 6 + 3, startIndex * 4 + 1);
+ geometryHandler.setIndex(startIndex * 6 + 4, startIndex * 4 + 0);
+ geometryHandler.setIndex(startIndex * 6 + 5, startIndex * 4 + 3);
+ }
+ }
+ } else if (dir == Direction.Y) {
+ if (geometryHandler.hasVertices()) {
+ geometryHandler.setVertex(startIndex * 4 + 0, x, y, z);
+ geometryHandler.setVertex(startIndex * 4 + 1, x + 1, y, z);
+ geometryHandler.setVertex(startIndex * 4 + 2, x + 1, y, z + 1);
+ geometryHandler.setVertex(startIndex * 4 + 3, x, y, z + 1);
+ }
+
+ if (geometryHandler.hasTextureCoords()) {
+ if (first) {
+ final ReadOnlyVector2 coord = blockUtil.getBlockTextureCoord(block,
+ getSide(orientation, BlockSide.Bottom));
+
+ geometryHandler.setTextureCoord(startIndex * 4 + 0, coord.getXf() + blockUtil.getTileWidth()
+ - blockUtil.getOffsetWidth(), coord.getYf() + blockUtil.getOffsetHeight());
+ geometryHandler.setTextureCoord(startIndex * 4 + 1, coord.getXf() + blockUtil.getTileWidth()
+ - blockUtil.getOffsetWidth(),
+ coord.getYf() + blockUtil.getTileHeight() - blockUtil.getOffsetHeight());
+ geometryHandler.setTextureCoord(startIndex * 4 + 2, coord.getXf() + blockUtil.getOffsetWidth(),
+ coord.getYf() + blockUtil.getTileHeight() - blockUtil.getOffsetHeight());
+ geometryHandler.setTextureCoord(startIndex * 4 + 3, coord.getXf() + blockUtil.getOffsetWidth(),
+ coord.getYf() + blockUtil.getOffsetHeight());
+ } else {
+ final ReadOnlyVector2 coord = blockUtil.getBlockTextureCoord(block,
+ getSide(orientation, BlockSide.Top));
+
+ geometryHandler.setTextureCoord(startIndex * 4 + 0, coord.getXf() + blockUtil.getOffsetWidth(),
+ coord.getYf() + blockUtil.getOffsetHeight());
+ geometryHandler.setTextureCoord(startIndex * 4 + 1, coord.getXf() + blockUtil.getOffsetWidth(),
+ coord.getYf() + blockUtil.getTileHeight() - blockUtil.getOffsetHeight());
+ geometryHandler.setTextureCoord(startIndex * 4 + 2, coord.getXf() + blockUtil.getTileWidth()
+ - blockUtil.getOffsetWidth(),
+ coord.getYf() + blockUtil.getTileHeight() - blockUtil.getOffsetHeight());
+ geometryHandler.setTextureCoord(startIndex * 4 + 3, coord.getXf() + blockUtil.getTileWidth()
+ - blockUtil.getOffsetWidth(), coord.getYf() + blockUtil.getOffsetHeight());
+ }
+ }
+
+ if (geometryHandler.hasColors()) {
+ float sideModifier = 0.0f;
+ if (first) {
+ sideModifier = MOD;
+ }
+
+ float globalLighting, localLighting, miniShade;
+
+ miniShade = miniShadeY(provider, blockUtil, x, y, z, 0, first);
+ globalLighting = MathUtils.clamp(geometryHandler.getGlobalLighting(x, y, z) - sideModifier, 0f, 1f)
+ * miniShade;
+ localLighting = MathUtils.clamp(geometryHandler.getLocalLighting(x, y, z) - sideModifier, 0f, 1f)
+ * miniShade;
+ geometryHandler.setColor(startIndex * 4 + 0, globalLighting, localLighting);
+
+ miniShade = miniShadeY(provider, blockUtil, x, y, z, 1, first);
+ globalLighting = MathUtils.clamp(geometryHandler.getGlobalLighting(x + 1, y, z) - sideModifier, 0f, 1f)
+ * miniShade;
+ localLighting = MathUtils.clamp(geometryHandler.getLocalLighting(x + 1, y, z) - sideModifier, 0f, 1f)
+ * miniShade;
+ geometryHandler.setColor(startIndex * 4 + 1, globalLighting, localLighting);
+
+ miniShade = miniShadeY(provider, blockUtil, x, y, z, 2, first);
+ globalLighting = MathUtils.clamp(geometryHandler.getGlobalLighting(x + 1, y, z + 1) - sideModifier, 0f,
+ 1f) * miniShade;
+ localLighting = MathUtils.clamp(geometryHandler.getLocalLighting(x + 1, y, z + 1) - sideModifier, 0f,
+ 1f) * miniShade;
+ geometryHandler.setColor(startIndex * 4 + 2, globalLighting, localLighting);
+
+ miniShade = miniShadeY(provider, blockUtil, x, y, z, 3, first);
+ globalLighting = MathUtils.clamp(geometryHandler.getGlobalLighting(x, y, z + 1) - sideModifier, 0f, 1f)
+ * miniShade;
+ localLighting = MathUtils.clamp(geometryHandler.getLocalLighting(x, y, z + 1) - sideModifier, 0f, 1f)
+ * miniShade;
+ geometryHandler.setColor(startIndex * 4 + 3, globalLighting, localLighting);
+ }
+
+ if (geometryHandler.hasIndices()) {
+ if (first) {
+ geometryHandler.setIndex(startIndex * 6 + 0, startIndex * 4 + 0);
+ geometryHandler.setIndex(startIndex * 6 + 1, startIndex * 4 + 1);
+ geometryHandler.setIndex(startIndex * 6 + 2, startIndex * 4 + 3);
+ geometryHandler.setIndex(startIndex * 6 + 3, startIndex * 4 + 1);
+ geometryHandler.setIndex(startIndex * 6 + 4, startIndex * 4 + 2);
+ geometryHandler.setIndex(startIndex * 6 + 5, startIndex * 4 + 3);
+ } else {
+ geometryHandler.setIndex(startIndex * 6 + 0, startIndex * 4 + 3);
+ geometryHandler.setIndex(startIndex * 6 + 1, startIndex * 4 + 2);
+ geometryHandler.setIndex(startIndex * 6 + 2, startIndex * 4 + 1);
+ geometryHandler.setIndex(startIndex * 6 + 3, startIndex * 4 + 1);
+ geometryHandler.setIndex(startIndex * 6 + 4, startIndex * 4 + 0);
+ geometryHandler.setIndex(startIndex * 6 + 5, startIndex * 4 + 3);
+ }
+ }
+ } else if (dir == Direction.Z) {
+ if (geometryHandler.hasVertices()) {
+ geometryHandler.setVertex(startIndex * 4 + 0, x, y, z);
+ geometryHandler.setVertex(startIndex * 4 + 1, x + 1, y, z);
+ geometryHandler.setVertex(startIndex * 4 + 2, x + 1, y + 1, z);
+ geometryHandler.setVertex(startIndex * 4 + 3, x, y + 1, z);
+ }
+
+ if (geometryHandler.hasTextureCoords()) {
+ if (first) {
+ final ReadOnlyVector2 coord = blockUtil.getBlockTextureCoord(block,
+ getSide(orientation, BlockSide.Front));
+
+ geometryHandler.setTextureCoord(startIndex * 4 + 0, coord.getXf() + blockUtil.getOffsetWidth(),
+ coord.getYf() + blockUtil.getOffsetHeight());
+ geometryHandler.setTextureCoord(startIndex * 4 + 1, coord.getXf() + blockUtil.getTileWidth()
+ - blockUtil.getOffsetWidth(), coord.getYf() + blockUtil.getOffsetHeight());
+ geometryHandler.setTextureCoord(startIndex * 4 + 2, coord.getXf() + blockUtil.getTileWidth()
+ - blockUtil.getOffsetWidth(),
+ coord.getYf() + blockUtil.getTileHeight() - blockUtil.getOffsetHeight());
+ geometryHandler.setTextureCoord(startIndex * 4 + 3, coord.getXf() + blockUtil.getOffsetWidth(),
+ coord.getYf() + blockUtil.getTileHeight() - blockUtil.getOffsetHeight());
+ } else {
+ final ReadOnlyVector2 coord = blockUtil.getBlockTextureCoord(block,
+ getSide(orientation, BlockSide.Back));
+
+ geometryHandler.setTextureCoord(startIndex * 4 + 0, coord.getXf() + blockUtil.getTileWidth()
+ - blockUtil.getOffsetWidth(), coord.getYf() + blockUtil.getOffsetHeight());
+ geometryHandler.setTextureCoord(startIndex * 4 + 1, coord.getXf() + blockUtil.getOffsetWidth(),
+ coord.getYf() + blockUtil.getOffsetHeight());
+ geometryHandler.setTextureCoord(startIndex * 4 + 2, coord.getXf() + blockUtil.getOffsetWidth(),
+ coord.getYf() + blockUtil.getTileHeight() - blockUtil.getOffsetHeight());
+ geometryHandler.setTextureCoord(startIndex * 4 + 3, coord.getXf() + blockUtil.getTileWidth()
+ - blockUtil.getOffsetWidth(),
+ coord.getYf() + blockUtil.getTileHeight() - blockUtil.getOffsetHeight());
+ }
+ }
+
+ if (geometryHandler.hasColors()) {
+ float sideModifier = 0.04f;
+ if (first) {
+ sideModifier = MOD;
+ }
+
+ float globalLighting, localLighting, miniShade;
+
+ miniShade = miniShadeZ(provider, blockUtil, x, y, z, 0, first);
+ globalLighting = MathUtils.clamp(geometryHandler.getGlobalLighting(x, y, z) - sideModifier, 0f, 1f)
+ * miniShade;
+ localLighting = MathUtils.clamp(geometryHandler.getLocalLighting(x, y, z) - sideModifier, 0f, 1f)
+ * miniShade;
+ geometryHandler.setColor(startIndex * 4 + 0, globalLighting, localLighting);
+
+ miniShade = miniShadeZ(provider, blockUtil, x, y, z, 1, first);
+ globalLighting = MathUtils.clamp(geometryHandler.getGlobalLighting(x + 1, y, z) - sideModifier, 0f, 1f)
+ * miniShade;
+ localLighting = MathUtils.clamp(geometryHandler.getLocalLighting(x + 1, y, z) - sideModifier, 0f, 1f)
+ * miniShade;
+ geometryHandler.setColor(startIndex * 4 + 1, globalLighting, localLighting);
+
+ miniShade = miniShadeZ(provider, blockUtil, x, y, z, 2, first);
+ globalLighting = MathUtils.clamp(geometryHandler.getGlobalLighting(x + 1, y + 1, z) - sideModifier, 0f,
+ 1f) * miniShade;
+ localLighting = MathUtils.clamp(geometryHandler.getLocalLighting(x + 1, y + 1, z) - sideModifier, 0f,
+ 1f) * miniShade;
+ geometryHandler.setColor(startIndex * 4 + 2, globalLighting, localLighting);
+
+ miniShade = miniShadeZ(provider, blockUtil, x, y, z, 3, first);
+ globalLighting = MathUtils.clamp(geometryHandler.getGlobalLighting(x, y + 1, z) - sideModifier, 0f, 1f)
+ * miniShade;
+ localLighting = MathUtils.clamp(geometryHandler.getLocalLighting(x, y + 1, z) - sideModifier, 0f, 1f)
+ * miniShade;
+ geometryHandler.setColor(startIndex * 4 + 3, globalLighting, localLighting);
+ }
+
+ if (geometryHandler.hasIndices()) {
+ if (first) {
+ geometryHandler.setIndex(startIndex * 6 + 0, startIndex * 4 + 0);
+ geometryHandler.setIndex(startIndex * 6 + 1, startIndex * 4 + 1);
+ geometryHandler.setIndex(startIndex * 6 + 2, startIndex * 4 + 3);
+ geometryHandler.setIndex(startIndex * 6 + 3, startIndex * 4 + 1);
+ geometryHandler.setIndex(startIndex * 6 + 4, startIndex * 4 + 2);
+ geometryHandler.setIndex(startIndex * 6 + 5, startIndex * 4 + 3);
+ } else {
+ geometryHandler.setIndex(startIndex * 6 + 0, startIndex * 4 + 3);
+ geometryHandler.setIndex(startIndex * 6 + 1, startIndex * 4 + 2);
+ geometryHandler.setIndex(startIndex * 6 + 2, startIndex * 4 + 1);
+ geometryHandler.setIndex(startIndex * 6 + 3, startIndex * 4 + 1);
+ geometryHandler.setIndex(startIndex * 6 + 4, startIndex * 4 + 0);
+ geometryHandler.setIndex(startIndex * 6 + 5, startIndex * 4 + 3);
+ }
+ }
+ }
+ }
+
+ private BlockSide getSide(final BlockSide orientation, final BlockSide side) {
+ return orientations[orientation.ordinal()][side.ordinal()];
+ }
+
+ private float miniShadeX(final BlockProvider provider, final BlockUtil blockUtil, final int x, final int y,
+ final int z, final int index, final boolean first) {
+ final int xx = first ? x + 0 : x - 1;
+ final int yy = y;
+ final int zz = z;
+
+ boolean b1, b2, b3;
+
+ if (index == 0) {
+ b1 = isSolid(provider, blockUtil, xx, yy - 1, zz);
+ b2 = isSolid(provider, blockUtil, xx, yy, zz - 1);
+ b3 = isSolid(provider, blockUtil, xx, yy - 1, zz - 1);
+ return getShade(b1, b2, b3);
+ } else if (index == 1) {
+ b1 = isSolid(provider, blockUtil, xx, yy + 1, zz);
+ b2 = isSolid(provider, blockUtil, xx, yy, zz - 1);
+ b3 = isSolid(provider, blockUtil, xx, yy + 1, zz - 1);
+ return getShade(b1, b2, b3);
+ } else if (index == 2) {
+ b1 = isSolid(provider, blockUtil, xx, yy + 1, zz);
+ b2 = isSolid(provider, blockUtil, xx, yy, zz + 1);
+ b3 = isSolid(provider, blockUtil, xx, yy + 1, zz + 1);
+ return getShade(b1, b2, b3);
+ } else if (index == 3) {
+ b1 = isSolid(provider, blockUtil, xx, yy - 1, zz);
+ b2 = isSolid(provider, blockUtil, xx, yy, zz + 1);
+ b3 = isSolid(provider, blockUtil, xx, yy - 1, zz + 1);
+ return getShade(b1, b2, b3);
+ }
+
+ return 1f;
+ }
+
+ private float miniShadeY(final BlockProvider provider, final BlockUtil blockUtil, final int x, final int y,
+ final int z, final int index, final boolean first) {
+ final int xx = x;
+ final int yy = first ? y - 1 : y + 0;
+ final int zz = z;
+
+ boolean b1, b2, b3;
+
+ if (index == 0) {
+ b1 = isSolid(provider, blockUtil, xx - 1, yy, zz);
+ b2 = isSolid(provider, blockUtil, xx, yy, zz - 1);
+ b3 = isSolid(provider, blockUtil, xx - 1, yy, zz - 1);
+ return getShade(b1, b2, b3);
+ } else if (index == 1) {
+ b1 = isSolid(provider, blockUtil, xx + 1, yy, zz);
+ b2 = isSolid(provider, blockUtil, xx, yy, zz - 1);
+ b3 = isSolid(provider, blockUtil, xx + 1, yy, zz - 1);
+ return getShade(b1, b2, b3);
+ } else if (index == 2) {
+ b1 = isSolid(provider, blockUtil, xx + 1, yy, zz);
+ b2 = isSolid(provider, blockUtil, xx, yy, zz + 1);
+ b3 = isSolid(provider, blockUtil, xx + 1, yy, zz + 1);
+ return getShade(b1, b2, b3);
+ } else if (index == 3) {
+ b1 = isSolid(provider, blockUtil, xx - 1, yy, zz);
+ b2 = isSolid(provider, blockUtil, xx, yy, zz + 1);
+ b3 = isSolid(provider, blockUtil, xx - 1, yy, zz + 1);
+ return getShade(b1, b2, b3);
+ }
+
+ return 1f;
+ }
+
+ private float miniShadeZ(final BlockProvider provider, final BlockUtil blockUtil, final int x, final int y,
+ final int z, final int index, final boolean first) {
+ final int xx = x;
+ final int yy = y;
+ final int zz = first ? z + 0 : z - 1;
+
+ boolean b1, b2, b3;
+
+ if (index == 0) {
+ b1 = isSolid(provider, blockUtil, xx, yy - 1, zz);
+ b2 = isSolid(provider, blockUtil, xx - 1, yy, zz);
+ b3 = isSolid(provider, blockUtil, xx - 1, yy - 1, zz);
+ return getShade(b1, b2, b3);
+ } else if (index == 1) {
+ b1 = isSolid(provider, blockUtil, xx, yy - 1, zz);
+ b2 = isSolid(provider, blockUtil, xx + 1, yy, zz);
+ b3 = isSolid(provider, blockUtil, xx + 1, yy - 1, zz);
+ return getShade(b1, b2, b3);
+ } else if (index == 2) {
+ b1 = isSolid(provider, blockUtil, xx, yy + 1, zz);
+ b2 = isSolid(provider, blockUtil, xx + 1, yy, zz);
+ b3 = isSolid(provider, blockUtil, xx + 1, yy + 1, zz);
+ return getShade(b1, b2, b3);
+ } else if (index == 3) {
+ b1 = isSolid(provider, blockUtil, xx, yy + 1, zz);
+ b2 = isSolid(provider, blockUtil, xx - 1, yy, zz);
+ b3 = isSolid(provider, blockUtil, xx - 1, yy + 1, zz);
+ return getShade(b1, b2, b3);
+ }
+
+ return 1f;
+ }
+
+ // TODO: add solid amount
+ private boolean isSolid(final BlockProvider provider, final BlockUtil util, final int x, final int y, final int z) {
+ final int blockId = provider.getBlock(x, y, z);
+ if (blockId == 0) {
+ return false;
+ }
+ return util.getBlockType(blockId) == BlockType.Solid;
+ }
+
+ private float getShade(final boolean b1, final boolean b2, final boolean b3) {
+ if (b1 && b2) {
+ return 1f - creaseAmount * 2.0f;
+ } else if (b1 || b2 || b3) {
+ return 1f - creaseAmount;
+ }
+ return 1f;
+ }
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/util/geometryproducers/GeometryProducer.java b/ardor3d-craft/src/main/java/com/ardorcraft/util/geometryproducers/GeometryProducer.java
new file mode 100644
index 0000000..5941ce8
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/util/geometryproducers/GeometryProducer.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.util.geometryproducers;
+
+import com.ardorcraft.util.BlockUtil;
+import com.ardorcraft.world.BlockProvider;
+import com.ardorcraft.world.GeometryHandler;
+
+/**
+ * Pluggable interface to create new geometry in block generation
+ */
+public interface GeometryProducer {
+ /**
+ * @param blockId
+ * ID of block to generate geometry for
+ * @param geometryHandler
+ * geometry handler to use for setting geometry data
+ * @param blockProvider
+ * blockprovider can give access to neighbouring blocks etc
+ * @param blockUtil
+ * block util gives information about a block, tile texture coords etc
+ * @param x
+ * world space x position
+ * @param y
+ * world space y position
+ * @param z
+ * world space z position
+ */
+ void generateBlock(final int blockId, final GeometryHandler geometryHandler, final BlockProvider blockProvider,
+ final BlockUtil blockUtil, final int x, final int y, final int z);
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/util/geometryproducers/MeshProducer.java b/ardor3d-craft/src/main/java/com/ardorcraft/util/geometryproducers/MeshProducer.java
new file mode 100644
index 0000000..252e532
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/util/geometryproducers/MeshProducer.java
@@ -0,0 +1,130 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.util.geometryproducers;
+
+import java.nio.FloatBuffer;
+import java.util.EnumMap;
+import java.util.Map;
+
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Quaternion;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyVector2;
+import com.ardor3d.renderer.IndexMode;
+import com.ardor3d.scenegraph.IndexBufferData;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.scenegraph.MeshData;
+import com.ardorcraft.util.BlockUtil;
+import com.ardorcraft.world.BlockProvider;
+import com.ardorcraft.world.BlockSide;
+import com.ardorcraft.world.GeometryHandler;
+
+/**
+ * GeometryProducer that can take an Ardor3D Mesh as input.
+ */
+public final class MeshProducer implements GeometryProducer {
+
+ private final Map<BlockSide, MeshData> meshDatas = new EnumMap<>(BlockSide.class);
+ private boolean transformCoords;
+
+ public MeshProducer(final Mesh mesh) {
+ final MeshData data = mesh.getMeshData();
+ for (final BlockSide side : BlockSide.values()) {
+ meshDatas.put(side, data);
+ }
+
+ if (data.getIndexMode(0) != IndexMode.Triangles) {
+ throw new IllegalArgumentException("Only meshes with IndexMode.Triangles allowed");
+ }
+ }
+
+ public void createOrientations() {
+ MeshData copy = meshDatas.get(BlockSide.Bottom).makeCopy();
+
+ copy.rotatePoints(new Quaternion().fromAngleAxis(MathUtils.HALF_PI, Vector3.UNIT_X));
+ meshDatas.put(BlockSide.Front, copy);
+
+ final Quaternion rotation = new Quaternion().fromAngleAxis(-MathUtils.HALF_PI, Vector3.UNIT_Y);
+
+ copy = copy.makeCopy();
+ copy.rotatePoints(rotation);
+ meshDatas.put(BlockSide.Left, copy);
+
+ copy = copy.makeCopy();
+ copy.rotatePoints(rotation);
+ meshDatas.put(BlockSide.Back, copy);
+
+ copy = copy.makeCopy();
+ copy.rotatePoints(rotation);
+ meshDatas.put(BlockSide.Right, copy);
+
+ copy = copy.makeCopy();
+ copy.rotatePoints(new Quaternion().fromAngleAxis(-MathUtils.HALF_PI, Vector3.UNIT_Z));
+ meshDatas.put(BlockSide.Top, copy);
+ }
+
+ public void setTransformTextureCoords(final boolean value) {
+ transformCoords = value;
+ }
+
+ @Override
+ public void generateBlock(final int blockId, final GeometryHandler geometryHandler,
+ final BlockProvider blockProvider, final BlockUtil blockUtil, final int x, final int y, final int z) {
+
+ final BlockSide orientation = BlockSide.values()[blockProvider.getBlockExtra(x, y, z)];
+ final MeshData data = meshDatas.get(orientation);
+
+ if (geometryHandler.hasVertices()) {
+ final FloatBuffer vertices = data.getVertexBuffer();
+ for (int i = 0; i < data.getVertexCount(); i++) {
+ geometryHandler.setVertex(i, vertices.get(i * 3 + 0) + x + 0.5f, vertices.get(i * 3 + 1) + y + 0.5f,
+ vertices.get(i * 3 + 2) + z + 0.5f);
+ }
+ }
+
+ if (geometryHandler.hasTextureCoords()) {
+ final FloatBuffer texcoords = data.getTextureBuffer(0);
+ if (!transformCoords) {
+ for (int i = 0; i < data.getVertexCount(); i++) {
+ geometryHandler.setTextureCoord(i, texcoords.get(i * 2 + 0), texcoords.get(i * 2 + 1));
+ }
+ } else {
+ final ReadOnlyVector2 coord = blockUtil.getBlockTextureCoord(blockId, BlockSide.Front);
+
+ for (int i = 0; i < data.getVertexCount(); i++) {
+ geometryHandler.setTextureCoord(
+ i,
+ coord.getXf() + blockUtil.getOffsetWidth() + texcoords.get(i * 2 + 0)
+ * (blockUtil.getTileWidth() - blockUtil.getOffsetWidth() * 2),
+ coord.getYf() + blockUtil.getOffsetHeight() + texcoords.get(i * 2 + 1)
+ * (blockUtil.getTileHeight() - blockUtil.getOffsetHeight() * 2));
+ }
+ }
+ }
+
+ if (geometryHandler.hasColors()) {
+ final float globalLighting = geometryHandler.getGlobalLighting(x, y + 1, z);
+ final float localLighting = geometryHandler.getLocalLighting(x, y + 1, z);
+
+ for (int i = 0; i < data.getVertexCount(); i++) {
+ geometryHandler.setColor(i, globalLighting, localLighting);
+ }
+ }
+
+ int indexCount = 0;
+ if (geometryHandler.hasIndices()) {
+ final IndexBufferData<?> indices = data.getIndices();
+ indexCount = indices.limit();
+ for (int i = 0; i < indexCount; i++) {
+ geometryHandler.setIndex(i, indices.get(i));
+ }
+ }
+
+ geometryHandler.setVertexCount(data.getVertexCount());
+ geometryHandler.setIndexCount(indexCount);
+ }
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/util/queue/Coords.java b/ardor3d-craft/src/main/java/com/ardorcraft/util/queue/Coords.java
new file mode 100644
index 0000000..83b9dc8
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/util/queue/Coords.java
@@ -0,0 +1,60 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.util.queue;
+
+/**
+ * 2D coordinates
+ */
+public class Coords {
+ private int x, z;
+
+ public int getX() {
+ return x;
+ }
+
+ public void setX(final int x) {
+ this.x = x;
+ }
+
+ public int getZ() {
+ return z;
+ }
+
+ public void setZ(final int z) {
+ this.z = z;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + x;
+ result = prime * result + z;
+ return result;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof Coords)) {
+ return false;
+ }
+ final Coords other = (Coords) obj;
+ if (x != other.x) {
+ return false;
+ }
+ if (z != other.z) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/util/queue/Task.java b/ardor3d-craft/src/main/java/com/ardorcraft/util/queue/Task.java
new file mode 100644
index 0000000..8b60558
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/util/queue/Task.java
@@ -0,0 +1,132 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.util.queue;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+import com.ardor3d.renderer.Renderer;
+
+/**
+ * A prioritized Task for the Worker. Sorted based on bucket, distance and time.
+ */
+public abstract class Task implements Comparable<Task> {
+ public abstract void execute(Renderer renderer);
+
+ private static AtomicLong atomicTime = new AtomicLong();
+ private Coords playerCoords;
+
+ private final int bucket;
+ private final int x;
+ private final int z;
+ private final long time;
+
+ public Task(final int bucket, final int x, final int z) {
+ this.bucket = bucket;
+ this.x = x;
+ this.z = z;
+ time = atomicTime.incrementAndGet();
+ }
+
+ public int compareToTest(final Task o2) {
+ if (bucket < 2 && o2.bucket >= 2 || bucket >= 2 && o2.bucket < 2) {
+ final int bucketDiff = bucket - o2.bucket;
+ if (bucketDiff != 0) {
+ return bucketDiff;
+ }
+ }
+
+ final int diffX1 = x - playerCoords.getX();
+ final int diffZ1 = z - playerCoords.getZ();
+ final int manhattanDistance1 = Math.abs(diffX1) + Math.abs(diffZ1);
+ final int diffX2 = o2.x - playerCoords.getX();
+ final int diffZ2 = o2.z - playerCoords.getZ();
+ final int manhattanDistance2 = Math.abs(diffX2) + Math.abs(diffZ2);
+ if (manhattanDistance1 != manhattanDistance2) {
+ return manhattanDistance1 - manhattanDistance2;
+ }
+
+ final int bucketDiff = bucket - o2.bucket;
+ if (bucketDiff != 0) {
+ return bucketDiff;
+ }
+
+ return (int) (time - o2.time);
+ }
+
+ @Override
+ public int compareTo(final Task o2) {
+ final int bucketDiff = bucket - o2.bucket;
+ if (bucketDiff != 0) {
+ return bucketDiff;
+ }
+
+ final int diffX1 = x - playerCoords.getX();
+ final int diffZ1 = z - playerCoords.getZ();
+ final int manhattanDistance1 = Math.abs(diffX1) + Math.abs(diffZ1);
+ final int diffX2 = o2.x - playerCoords.getX();
+ final int diffZ2 = o2.z - playerCoords.getZ();
+ final int manhattanDistance2 = Math.abs(diffX2) + Math.abs(diffZ2);
+ if (manhattanDistance1 != manhattanDistance2) {
+ return manhattanDistance1 - manhattanDistance2;
+ }
+
+ return (int) (time - o2.time);
+ }
+
+ public void setPlayerCoords(final Coords playerCoords) {
+ this.playerCoords = playerCoords;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("Task [bucket=");
+ builder.append(bucket);
+ builder.append(", x=");
+ builder.append(x);
+ builder.append(", z=");
+ builder.append(z);
+ builder.append(", time=");
+ builder.append(time);
+ builder.append("]");
+ return builder.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + x;
+ result = prime * result + z;
+ return result;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final Task other = (Task) obj;
+ if (x != other.x) {
+ return false;
+ }
+ if (z != other.z) {
+ return false;
+ }
+ return true;
+ }
+
+ public int getBucket() {
+ return bucket;
+ }
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/util/queue/Worker.java b/ardor3d-craft/src/main/java/com/ardorcraft/util/queue/Worker.java
new file mode 100644
index 0000000..deab168
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/util/queue/Worker.java
@@ -0,0 +1,106 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.util.queue;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.locks.ReentrantLock;
+
+import com.ardor3d.renderer.Renderer;
+
+/**
+ * The Worker handles the chunk update Tasks, making sure they are prioritized, and doesnt spend too much time per
+ * frame.
+ */
+public class Worker {
+ private final List<Task> taskList = new LinkedList<>();
+ private final ReentrantLock lock = new ReentrantLock();
+ private long executionTime = 10;
+ private boolean needsSorting = false;
+ private final Coords playerCoords = new Coords();
+
+ public void enqueue(final Task task) {
+ task.setPlayerCoords(playerCoords);
+
+ lock.lock();
+ try {
+ taskList.add(task);
+ needsSorting = true;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ private final Set<Task> taskCleaner = new HashSet<>();
+
+ public void execute(final Renderer renderer) {
+ final long beginTime = System.currentTimeMillis();
+ long elapsedTime = 0;
+
+ int size = 0;
+
+ lock.lock();
+ try {
+ size = taskList.size();
+ if (size > 0 && needsSorting) {
+ Collections.sort(taskList);
+ needsSorting = false;
+
+ taskCleaner.clear();
+ final Iterator<Task> iterator = taskList.iterator();
+ while (iterator.hasNext()) {
+ final Task task = iterator.next();
+ final int bucket = task.getBucket();
+ if ((bucket == 2 || bucket == 3) && taskCleaner.contains(task)) {
+ iterator.remove();
+ } else if (bucket == 2 || bucket == 3) {
+ taskCleaner.add(task);
+ }
+ }
+ size = taskList.size();
+ }
+ } finally {
+ lock.unlock();
+ }
+
+ if (size > 0) {
+ int tasksFinished = 0;
+ for (int i = 0; i < size && elapsedTime < executionTime; i++) {
+ taskList.get(i).execute(renderer);
+ // System.out.println(taskList.get(i));
+ tasksFinished++;
+ elapsedTime = System.currentTimeMillis() - beginTime;
+ }
+
+ lock.lock();
+ try {
+ for (int i = 0; i < tasksFinished; i++) {
+ taskList.remove(0);
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+ }
+
+ public long getExecutionTime() {
+ return executionTime;
+ }
+
+ public void setExecutionTime(final long executionTime) {
+ this.executionTime = executionTime;
+ }
+
+ public void setPlayerCoords(final int playerX, final int playerZ) {
+ playerCoords.setX(playerX);
+ playerCoords.setZ(playerZ);
+ }
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/util/queue/WorkerManager.java b/ardor3d-craft/src/main/java/com/ardorcraft/util/queue/WorkerManager.java
new file mode 100644
index 0000000..25490b0
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/util/queue/WorkerManager.java
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.util.queue;
+
+public final class WorkerManager {
+ private static final Worker worker = new Worker();
+
+ private WorkerManager() {
+
+ }
+
+ public static Worker getWorker() {
+ return worker;
+ }
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/voxel/MeshVoxelationContext.java b/ardor3d-craft/src/main/java/com/ardorcraft/voxel/MeshVoxelationContext.java
new file mode 100644
index 0000000..23e3483
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/voxel/MeshVoxelationContext.java
@@ -0,0 +1,92 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.voxel;
+
+import java.util.Arrays;
+
+import com.ardor3d.math.Transform;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyVector3;
+
+public class MeshVoxelationContext extends VoxelationContext {
+ public final Mesh mesh = new Mesh();
+ public final Triangle triangle = new Triangle();
+ public final Transform transform = new Transform();
+
+ public int[] _sectionVoxelCount = new int[3];
+ public int[] _sectionCount = new int[3];
+
+ public float EDGE_EPSILON = 0.01f;
+
+ public Section[][][] sections;
+
+ public MeshVoxelationContext(final ReadOnlyVector3 center, final ReadOnlyVector3 voxelSize,
+ final int[] sectionCount, final int[] sectionVoxelCount) {
+ super(center, voxelSize);
+
+ System.arraycopy(sectionVoxelCount, 0, _sectionVoxelCount, 0, 3);
+ System.arraycopy(sectionCount, 0, _sectionCount, 0, 3);
+
+ extent[0] = sectionCount[0] * sectionVoxelCount[0];
+ extent[1] = sectionCount[1] * sectionVoxelCount[1];
+ extent[2] = sectionCount[2] * sectionVoxelCount[2];
+
+ sections = new Section[sectionCount[0]][sectionCount[1]][sectionCount[2]];
+ for (int i = 0; i < sectionCount[0]; i++) {
+ for (int j = 0; j < sectionCount[1]; j++) {
+ for (int k = 0; k < sectionCount[2]; k++) {
+ sections[i][j][k] = new Section(sectionVoxelCount);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void init() {}
+
+ public static class Section {
+ public final byte[][][] voxel;
+
+ public Section(final int[] sectionVoxelCount) {
+ voxel = new byte[sectionVoxelCount[0]][sectionVoxelCount[1]][sectionVoxelCount[2]];
+ }
+
+ public void addVoxel(final byte mask, final int i, final int j, final int k) {
+ voxel[i][j][k] |= mask;
+ }
+
+ public void clear() {
+ for (final byte[][] b1 : voxel) {
+ for (final byte[] b2 : b1) {
+ Arrays.fill(b2, (byte) 0);
+ }
+ }
+ }
+
+ }
+
+ public class Mesh {
+ public final Vector3 extent = new Vector3();
+ public final Vector3 center = new Vector3();
+ }
+
+ public class Triangle {
+ public final Vector3[] vertex = new Vector3[] { new Vector3(), new Vector3(), new Vector3() };
+ public final Vector3 normal = new Vector3();
+ }
+
+ public void clearVoxelArea() {
+ for (final Section[][] s1 : sections) {
+ for (final Section[] s2 : s1) {
+ for (int i = 0; i < s2.length; i++) {
+ s2[i].clear();
+ }
+ }
+ }
+ }
+
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/voxel/TriangleAABBIntersection.java b/ardor3d-craft/src/main/java/com/ardorcraft/voxel/TriangleAABBIntersection.java
new file mode 100644
index 0000000..f32e45f
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/voxel/TriangleAABBIntersection.java
@@ -0,0 +1,268 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.voxel;
+
+import static java.lang.Math.abs;
+import static java.lang.Math.max;
+import static java.lang.Math.min;
+
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardorcraft.voxel.MeshVoxelationContext.Triangle;
+
+/**
+ * ported from: http://jgt.akpeters.com/papers/AkenineMoller01/tribox.html
+ */
+public class TriangleAABBIntersection {
+ public static final boolean intersect(final ReadOnlyVector3 center, final ReadOnlyVector3 extent,
+ final Triangle triangle) {
+ return intersect(center.getX(), center.getY(), center.getZ(), extent, triangle);
+ }
+
+ /**
+ * Test if Triangle and AABB instersects Will also set triangle.normal
+ *
+ * @param cx
+ * - AABB center x value
+ * @param cy
+ * - AABB center x value
+ * @param cz
+ * - AABB center x value
+ * @param extent
+ * - AABB extent of sides
+ * @param triangle
+ * @return true if intersecting
+ */
+ public static final boolean intersect(final double cx, final double cy, final double cz,
+ final ReadOnlyVector3 extent, final Triangle triangle) {
+ double v0x = triangle.vertex[0].getX(), v0y = triangle.vertex[0].getY(), v0z = triangle.vertex[0].getZ(), v1x = triangle.vertex[1]
+ .getX(), v1y = triangle.vertex[1].getY(), v1z = triangle.vertex[1].getZ(), v2x = triangle.vertex[2]
+ .getX(), v2y = triangle.vertex[2].getY(), v2z = triangle.vertex[2].getZ();
+ final double xExt = extent.getX(), yExt = extent.getY(), zExt = extent.getZ();
+
+ // Move everything so that the AABB center is in (0,0,0)
+ v0x -= cx;
+ v0y -= cy;
+ v0z -= cz;
+ v1x -= cx;
+ v1y -= cy;
+ v1z -= cz;
+ v2x -= cx;
+ v2y -= cy;
+ v2z -= cz;
+
+ // Triangle edges
+ final double e0x = v1x - v0x, e0y = v1y - v0y, e0z = v1z - v0z, e1x = v2x - v1x, e1y = v2y - v1y, e1z = v2z
+ - v1z, e2x = v0x - v2x, e2y = v0y - v2y, e2z = v0z - v2z;
+
+ // Test A:
+ // Use separating plane(axis) theorem to test overlap between triangle and box
+ // need to test for overlap in these directions:
+ // Cross product(edge from tri, {x,y,z}-direction)
+ // this gives 3x3=9 more tests
+ // test the 9 tests first (this was faster)
+ double min, max;
+ double p0, p1, p2, rad;
+ double fex = abs(e0x), fey = abs(e0y), fez = abs(e0z);
+
+ // AXISTEST_X01(e0[Z], e0[Y], fez, fey);
+ p0 = e0z * v0y - e0y * v0z;
+ p2 = e0z * v2y - e0y * v2z;
+ min = min(p0, p2);
+ max = max(p0, p2);
+ rad = fez * yExt + fey * zExt;
+ if (min > rad || max < -rad) {
+ return false;
+ }
+
+ // AXISTEST_Y02(e0[Z], e0[X], fez, fex);
+ p0 = -e0z * v0x + e0x * v0z;
+ p2 = -e0z * v2x + e0x * v2z;
+ min = min(p0, p2);
+ max = max(p0, p2);
+ rad = fez * xExt + fex * zExt;
+ if (min > rad || max < -rad) {
+ return false;
+ }
+
+ // AXISTEST_Z12(e0[Y], e0[X], fey, fex);
+ p1 = e0y * v1x - e0x * v1y;
+ p2 = e0y * v2x - e0x * v2y;
+ min = min(p1, p2);
+ max = max(p1, p2);
+ rad = fey * xExt + fex * yExt;
+ if (min > rad || max < -rad) {
+ return false;
+ }
+
+ fex = abs(e1x);
+ fey = abs(e1y);
+ fez = abs(e1z);
+
+ // AXISTEST_X01(e1[Z], e1[Y], fez, fey);
+ p0 = e1z * v0y - e1y * v0z;
+ p2 = e1z * v2y - e1y * v2z;
+ min = min(p0, p2);
+ max = max(p0, p2);
+ rad = fez * yExt + fey * zExt;
+ if (min > rad || max < -rad) {
+ return false;
+ }
+
+ // AXISTEST_Y02(e1[Z], e1[X], fez, fex);
+ p0 = -e1z * v0x + e1x * v0z;
+ p2 = -e1z * v2x + e1x * v2z;
+ min = min(p0, p2);
+ max = max(p0, p2);
+ rad = fez * xExt + fex * zExt;
+ if (min > rad || max < -rad) {
+ return false;
+ }
+
+ // AXISTEST_Z0(e1[Y], e1[X], fey, fex);
+ p0 = e1y * v0x - e1x * v0y;
+ p1 = e1y * v1x - e1x * v1y;
+ min = min(p0, p1);
+ max = max(p0, p1);
+ rad = fey * xExt + fex * yExt;
+ if (min > rad || max < -rad) {
+ return false;
+ }
+
+ fex = abs(e2x);
+ fey = abs(e2y);
+ fez = abs(e2z);
+
+ // AXISTEST_X2(e2[Z], e2[Y], fez, fey);
+ p0 = e2z * v0y - e2y * v0z;
+ p1 = e2z * v1y - e2y * v1z;
+ min = min(p0, p1);
+ max = max(p0, p1);
+ rad = fez * yExt + fey * zExt;
+ if (min > rad || max < -rad) {
+ return false;
+ }
+
+ // AXISTEST_Y1(e2[Z], e2[X], fez, fex);
+ p0 = -e2z * v0x + e2x * v0z;
+ p1 = -e2z * v1x + e2x * v1z;
+ min = min(p0, p1);
+ max = max(p0, p1);
+ rad = fez * xExt + fex * yExt;
+ if (min > rad || max < -rad) {
+ return false;
+ }
+
+ // AXISTEST_Z12(e2[Y], e2[X], fey, fex);
+ p1 = e2y * v1x - e2x * v1y;
+ p2 = e2y * v2x - e2x * v2y;
+ min = min(p1, p2);
+ max = max(p1, p2);
+ rad = fey * xExt + fex * yExt;
+ if (min > rad || max < -rad) {
+ return false;
+ }
+
+ // === Test 2 ===
+ // check if a minimal AABB around the triangle intersect the given AABB
+
+ double minX = v0x, maxX = v0x, minY = v0y, maxY = v0y, minZ = v0z, maxZ = v0z;
+
+ // test in X-direction
+ if (v1x < minX) {
+ minX = v1x;
+ }
+ if (v2x < minX) {
+ minX = v2x;
+ }
+ if (v1x > maxX) {
+ maxX = v1x;
+ }
+ if (v2x > maxX) {
+ maxX = v2x;
+ }
+ if (minX > xExt || maxX < -xExt) {
+ return false;
+ }
+
+ // test in Y-direction
+ if (v1y < minY) {
+ minY = v1y;
+ }
+ if (v2y < minY) {
+ minY = v2y;
+ }
+ if (v1y > maxY) {
+ maxY = v1y;
+ }
+ if (v2y > maxY) {
+ maxY = v2y;
+ }
+ if (minY > yExt || maxY < -yExt) {
+ return false;
+ }
+
+ // test in Z-direction
+ if (v1z < minZ) {
+ minZ = v1z;
+ }
+ if (v2z < minZ) {
+ minZ = v2z;
+ }
+ if (v1z > maxZ) {
+ maxZ = v1z;
+ }
+ if (v2z > maxZ) {
+ maxZ = v2z;
+ }
+ if (minZ > zExt || maxZ < -zExt) {
+ return false;
+ }
+
+ // === Test 3 ===
+ // Check if the AABB intersects the plane of the triangle
+ // This is done by checking if the farthest and closest vertex
+ // of the AABB is on the same side of the plane
+ // normal = e0 x e1
+ final double nx = e1y * e0z - e1z * e0y, ny = e1z * e0x - e1x * e0z, nz = e1x * e0y - e1y * e0x, d = nx * v0x
+ + ny * v0y + nz * v0z;
+
+ triangle.normal.set(nx, ny, nz).normalizeLocal();
+
+ if (nx > 0) {
+ minX = -xExt;
+ maxX = xExt;
+ } else {
+ minX = xExt;
+ maxX = -xExt;
+ }
+
+ if (ny > 0) {
+ minY = -yExt;
+ maxY = yExt;
+ } else {
+ minY = yExt;
+ maxY = -yExt;
+ }
+
+ if (nz > 0) {
+ minZ = -zExt;
+ maxZ = zExt;
+ } else {
+ minZ = zExt;
+ maxZ = -zExt;
+ }
+
+ if (nx * minX + ny * minY + nz * minZ > d) {
+ return false;
+ }
+ if (nx * maxX + ny * maxY + nz * maxZ < d) {
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/voxel/Voxel.java b/ardor3d-craft/src/main/java/com/ardorcraft/voxel/Voxel.java
new file mode 100644
index 0000000..1bf3357
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/voxel/Voxel.java
@@ -0,0 +1,11 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.voxel;
+
+public class Voxel {
+ public int x, y, z;
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/voxel/VoxelateMesh.java b/ardor3d-craft/src/main/java/com/ardorcraft/voxel/VoxelateMesh.java
new file mode 100644
index 0000000..86d411b
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/voxel/VoxelateMesh.java
@@ -0,0 +1,288 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.voxel;
+
+import java.nio.FloatBuffer;
+
+import com.ardor3d.math.Vector3;
+import com.ardor3d.renderer.IndexMode;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.scenegraph.MeshData;
+import com.ardorcraft.voxel.MeshVoxelationContext.Section;
+import com.ardorcraft.voxel.MeshVoxelationContext.Triangle;
+
+public class VoxelateMesh {
+ public static final byte EDGE_DIRECTION_RIGHT = 0x01;
+ public static final byte EDGE_DIRECTION_LEFT = 0x02;
+
+ private final static int X = 0;
+ private final static int Y = 1;
+ private final static int Z = 2;
+
+ public static void surface(final Mesh mesh, final MeshVoxelationContext vcx) {
+ voxelate(mesh, vcx, false);
+ }
+
+ public static void solid(final Mesh mesh, final MeshVoxelationContext vcx) {
+ voxelate(mesh, vcx, true);
+ }
+
+ private static void voxelate(final Mesh mesh, final MeshVoxelationContext vcx, final boolean isSolid) {
+ if (mesh.getMeshData().getVertexBuffer() == null) {
+ return;
+ }
+
+ vcx.clearVoxelArea();
+
+ calculateMeshTransform(mesh, vcx);
+
+ final MeshData md = mesh.getMeshData();
+ final int sectionCount = md.getSectionCount();
+ int primitiveCount;
+ if (md.getIndexMode(0) == IndexMode.Triangles) {
+ final Vector3[] vertexes = vcx.triangle.vertex;
+ for (int j = 0; j < sectionCount; j++) {
+ primitiveCount = md.getPrimitiveCount(j);
+ for (int i = 0; i < primitiveCount; i++) {
+ md.getPrimitiveVertices(i, j, vertexes);
+ voxelateTriangle(vcx);
+ }
+ }
+ }
+
+ if (isSolid) {
+ fill(vcx);
+ } else {
+ final Section section = vcx.sections[0][0][0];
+ final int[] sectionIndex = new int[] {
+ 0, 0, 0
+ };
+ final int[] voxelIndex = new int[3];
+ byte[][] slice;
+ byte[] line;
+ byte mask;
+ for (int i = 0; i < vcx._sectionVoxelCount[0]; i++) {
+ slice = section.voxel[i];
+ for (int j = 0; j < vcx._sectionVoxelCount[1]; j++) {
+ line = slice[j];
+ for (int k = 0; k < vcx._sectionVoxelCount[2]; k++) {
+ mask = line[k];
+ if (mask == 0) {
+ voxelIndex[0] = i;
+ voxelIndex[1] = j;
+ voxelIndex[2] = k;
+ vcx.addSpace(sectionIndex, voxelIndex);
+ continue;
+ }
+ voxelIndex[0] = i;
+ voxelIndex[1] = j;
+ voxelIndex[2] = k;
+ vcx.addVoxel(sectionIndex, voxelIndex);
+ }
+ }
+ }
+ }
+ }
+
+ private static void fill(final MeshVoxelationContext vcx) {
+ final Section section = vcx.sections[0][0][0];
+ final int[] sectionIndex = new int[] {
+ 0, 0, 0
+ };
+ final int[] voxelIndex = new int[3];
+ byte[][] slice;
+ byte[] line;
+ byte mask;
+ boolean fill = false;
+ for (int i = 0; i < vcx._sectionVoxelCount[0]; i++) {
+ slice = section.voxel[i];
+ for (int j = 0; j < vcx._sectionVoxelCount[1]; j++) {
+ line = slice[j];
+ for (int k = 0; k < vcx._sectionVoxelCount[2]; k++) {
+ mask = line[k];
+ if (mask == 0) {
+ if (!fill) {
+ voxelIndex[0] = i;
+ voxelIndex[1] = j;
+ voxelIndex[2] = k;
+ vcx.addSpace(sectionIndex, voxelIndex);
+ continue;
+ }
+ } else if ((mask & EDGE_DIRECTION_LEFT) != 0) {
+ fill = true;
+ } else if ((mask & EDGE_DIRECTION_RIGHT) != 0) {
+ fill = false;
+ }
+ voxelIndex[0] = i;
+ voxelIndex[1] = j;
+ voxelIndex[2] = k;
+ vcx.addVoxel(sectionIndex, voxelIndex);
+ }
+ }
+ }
+ }
+
+ private static void voxelateTriangle(final MeshVoxelationContext vcx) {
+ final Triangle triangle = vcx.triangle;
+ Section section;
+ boolean isIntersecting;
+ int minX, maxX, minY, maxY, minZ, maxZ;
+ final Vector3 center = new Vector3();
+ final Voxel vox = new Voxel();
+ byte mask;
+
+ final Vector3 v1 = triangle.vertex[0];
+ final Vector3 v2 = triangle.vertex[1];
+ final Vector3 v3 = triangle.vertex[2];
+
+ // If allowed it would be smarter to do the conversion in the mesh
+ // vertexes instead of for every primitive
+ v1.subtractLocal(vcx.mesh.center);
+ v1.multiplyLocal(vcx.transform.getScale());
+ v1.addLocal(vcx.transform.getTranslation());
+
+ v2.subtractLocal(vcx.mesh.center);
+ v2.multiplyLocal(vcx.transform.getScale());
+ v2.addLocal(vcx.transform.getTranslation());
+
+ v3.subtractLocal(vcx.mesh.center);
+ v3.multiplyLocal(vcx.transform.getScale());
+ v3.addLocal(vcx.transform.getTranslation());
+
+ // Get the bounding box for this triangle
+ vcx.positionToVoxel(v1, vox);
+ minX = maxX = vox.x;
+ minY = maxY = vox.y;
+ minZ = maxZ = vox.z;
+
+ vcx.positionToVoxel(v2, vox);
+ if (vox.x < minX) {
+ minX = vox.x;
+ } else if (vox.x > maxX) {
+ maxX = vox.x;
+ }
+ if (vox.y < minY) {
+ minY = vox.y;
+ } else if (vox.y > maxY) {
+ maxY = vox.y;
+ }
+ if (vox.z < minZ) {
+ minZ = vox.z;
+ } else if (vox.z > maxZ) {
+ maxZ = vox.z;
+ }
+
+ vcx.positionToVoxel(v3, vox);
+ if (vox.x < minX) {
+ minX = vox.x;
+ } else if (vox.x > maxX) {
+ maxX = vox.x;
+ }
+ if (vox.y < minY) {
+ minY = vox.y;
+ } else if (vox.y > maxY) {
+ maxY = vox.y;
+ }
+ if (vox.z < minZ) {
+ minZ = vox.z;
+ } else if (vox.z > maxZ) {
+ maxZ = vox.z;
+ }
+
+ // XXX: we should be able to skip checking two voxels but probably minor
+ // performance increase, the voxels of the triangle points
+ for (int i = minX; i <= maxX; i++) {
+ for (int j = minY; j <= maxY; j++) {
+ for (int k = minZ; k <= maxZ; k++) {
+ vcx.voxelToPosition(i, j, k, center);
+ isIntersecting = TriangleAABBIntersection.intersect(center, vcx.voxelSize, triangle);
+ if (isIntersecting) {
+ // Check if the projection of the triangle normal is
+ // positive or negative with regards to the slice axis(Z by default)
+ // TODO: Think i need to improve this fill algorithm it isn't 100% robust
+ final double d = triangle.normal.dot(Vector3.UNIT_Z);
+ // XXX: don't know the best limit of this EPSILON. to low and we miss fill stops
+ if (d > vcx.EDGE_EPSILON) {
+ mask = EDGE_DIRECTION_LEFT;
+ } else {
+ mask = EDGE_DIRECTION_RIGHT;
+ }
+
+ // TODO: Decide which section this voxel belongs to
+ section = vcx.sections[0][0][0];
+ section.addVoxel(mask, i, j, k);
+ }
+ }
+ }
+ }
+ }
+
+ private static void calculateMeshTransform(final Mesh mesh, final MeshVoxelationContext vcx) {
+ float x, y, z, minX, maxX, minY, maxY, minZ, maxZ;
+ final FloatBuffer vBuf = mesh.getMeshData().getVertexBuffer();
+
+ minX = maxX = vBuf.get(X);
+ minY = maxY = vBuf.get(Y);
+ minZ = maxZ = vBuf.get(Z);
+
+ final int vCount = 3 * mesh.getMeshData().getVertexCount();
+ for (int i = 0; i < vCount; i += 3) {
+ x = vBuf.get(i);
+ y = vBuf.get(i + 1);
+ z = vBuf.get(i + 2);
+
+ if (x < minX) {
+ minX = x;
+ } else if (x > maxX) {
+ maxX = x;
+ }
+
+ if (y < minY) {
+ minY = y;
+ } else if (y > maxY) {
+ maxY = y;
+ }
+
+ if (z < minZ) {
+ minZ = z;
+ } else if (z > maxZ) {
+ maxZ = z;
+ }
+ }
+
+ vcx.mesh.extent.set(maxX - minX, maxY - minY, maxZ - minZ);
+ vcx.mesh.center.set((minX + maxX) / 2, (minY + maxY) / 2, (minZ + maxZ) / 2);
+
+ // -1 because we want the mesh to be scaled a tiny bit smaller than the entire VoxelationArea
+ final double xScale = vcx.voxelSize.getX() * (vcx._sectionCount[X] * vcx._sectionVoxelCount[X] - 1)
+ / vcx.mesh.extent.getX();
+ final double yScale = vcx.voxelSize.getY() * (vcx._sectionCount[Y] * vcx._sectionVoxelCount[Y] - 1)
+ / vcx.mesh.extent.getY();
+ final double zScale = vcx.voxelSize.getZ() * (vcx._sectionCount[Z] * vcx._sectionVoxelCount[Z] - 1)
+ / vcx.mesh.extent.getZ();
+
+ // Setup transform so that Mesh is strictly in +X,+Y,+Z quadrant and scaled so that it
+ // fits inside the VoxelationArea
+ double scale;
+ if (xScale < yScale) {
+ if (xScale < zScale) {
+ scale = xScale;
+ } else {
+ scale = zScale;
+ }
+ } else {
+ if (yScale < zScale) {
+ scale = yScale;
+ } else {
+ scale = zScale;
+ }
+ }
+ vcx.transform.setScale(scale);
+ vcx.transform.setTranslation(0.5 * vcx.mesh.extent.getX() * scale, 0.5 * vcx.mesh.extent.getY() * scale, 0.5
+ * vcx.mesh.extent.getZ() * scale);
+ }
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/voxel/VoxelationContext.java b/ardor3d-craft/src/main/java/com/ardorcraft/voxel/VoxelationContext.java
new file mode 100644
index 0000000..e8eb08f
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/voxel/VoxelationContext.java
@@ -0,0 +1,64 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.voxel;
+
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyVector3;
+
+public abstract class VoxelationContext {
+ // The center position of voxel 0,0,0
+ public final Vector3 center = new Vector3();
+ // The size of one voxel
+ public final Vector3 voxelSize = new Vector3();
+
+ public int[] extent = new int[] {
+ 32, 32, 32
+ };
+
+ public VoxelationListener listener;
+
+ public VoxelationContext(final ReadOnlyVector3 center, final ReadOnlyVector3 voxelSize) {
+ this.center.set(center);
+ this.voxelSize.set(voxelSize);
+ }
+
+ public abstract void init();
+
+ public void positionToVoxel(final ReadOnlyVector3 pos, final Voxel store) {
+ store.x = (int) Math.round((pos.getX() - center.getX()) / voxelSize.getX());
+ store.y = (int) Math.round((pos.getY() - center.getY()) / voxelSize.getY());
+ store.z = (int) Math.round((pos.getZ() - center.getZ()) / voxelSize.getZ());
+ }
+
+ public void voxelToPosition(final Voxel voxel, final Vector3 store) {
+ store.set(voxel.x * voxelSize.getX() + center.getX(), voxel.y * voxelSize.getY() + center.getY(), voxel.z
+ * voxelSize.getZ() + center.getZ());
+ }
+
+ public void voxelToPosition(final int[] voxel, final Vector3 store) {
+ store.set(voxel[0] * voxelSize.getX() + center.getX(), voxel[1] * voxelSize.getY() + center.getY(), voxel[2]
+ * voxelSize.getZ() + center.getZ());
+ }
+
+ public void voxelToPosition(final int x, final int y, final int z, final Vector3 store) {
+ store.set(x * voxelSize.getX() + center.getX(), y * voxelSize.getY() + center.getY(), z * voxelSize.getZ()
+ + center.getZ());
+ }
+
+ public void addVoxel(final int[] section, final int[] voxel) {
+ if (listener != null) {
+ listener.addVoxel(this, section, voxel);
+ }
+ }
+
+ public void addSpace(final int[] section, final int[] voxel) {
+ if (listener != null) {
+ listener.addSpace(this, section, voxel);
+ }
+ }
+
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/voxel/VoxelationListener.java b/ardor3d-craft/src/main/java/com/ardorcraft/voxel/VoxelationListener.java
new file mode 100644
index 0000000..593a460
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/voxel/VoxelationListener.java
@@ -0,0 +1,13 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.voxel;
+
+public interface VoxelationListener {
+ public void addVoxel(VoxelationContext vcx, int[] section, int[] voxel);
+
+ public void addSpace(VoxelationContext vcx, int[] section, int[] voxel);
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/voxel/Voxelator.java b/ardor3d-craft/src/main/java/com/ardorcraft/voxel/Voxelator.java
new file mode 100644
index 0000000..aee5f37
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/voxel/Voxelator.java
@@ -0,0 +1,81 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.voxel;
+
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardorcraft.data.Pos;
+import com.ardorcraft.world.ChunkModifier;
+
+public class Voxelator {
+ private MeshVoxelationContext vcx;
+ private final ChunkModifier blockScene;
+ private final Pos position = new Pos();
+ private int blockType;
+
+ private final int xSize;
+ private final int ySize;
+ private final int zSize;
+
+ public Voxelator(final ChunkModifier blockScene, final int xSize, final int ySize, final int zSize) {
+ this.blockScene = blockScene;
+ this.xSize = xSize;
+ this.ySize = ySize;
+ this.zSize = zSize;
+ }
+
+ public void voxelate(final Vector3 position, final Mesh mesh, final float edgeEpsilon, final int blockType) {
+ final int X = MathUtils.floor(position.getXf());
+ final int Y = MathUtils.floor(position.getYf());
+ final int Z = MathUtils.floor(position.getZf());
+
+ voxelate(new Pos(X, Y, Z), mesh, edgeEpsilon, blockType);
+ }
+
+ public void voxelate(final Pos pos, final Mesh mesh, final float edgeEpsilon, final int blockType) {
+ if (vcx == null) {
+ init();
+ }
+
+ this.blockType = blockType;
+ position.set(pos.x - xSize / 2, pos.y, pos.z - zSize / 2);
+
+ vcx.EDGE_EPSILON = edgeEpsilon;
+
+ VoxelateMesh.solid(mesh, vcx);
+ }
+
+ protected void init() {
+ vcx = new MeshVoxelationContext(Vector3.ZERO, new Vector3(1, 1, 1), new int[] {
+ 1, 1, 1
+ }, new int[] {
+ xSize, ySize, zSize
+ });
+
+ vcx.listener = new VoxelationListener() {
+ Vector3 pos = new Vector3();
+
+ @Override
+ public void addVoxel(final VoxelationContext context, final int[] section, final int[] voxel) {
+ vcx.voxelToPosition(voxel, pos);
+
+ final int X = MathUtils.floor(pos.getXf());
+ final int Y = MathUtils.floor(pos.getYf());
+ final int Z = MathUtils.floor(pos.getZf());
+
+ blockScene.setBlock(X + position.x, Y + position.y, Z + position.z, blockType);
+ }
+
+ @Override
+ public void addSpace(final VoxelationContext vcx, final int[] section, final int[] voxel) {
+ // TODO Auto-generated method stub
+
+ }
+ };
+ }
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/world/BlockEditData.java b/ardor3d-craft/src/main/java/com/ardorcraft/world/BlockEditData.java
new file mode 100644
index 0000000..b37f544
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/world/BlockEditData.java
@@ -0,0 +1,34 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.world;
+
+import com.ardorcraft.data.Pos;
+
+public class BlockEditData {
+ public Pos pos;
+ public int type;
+ public BlockSide orientation;
+
+ public BlockEditData(final Pos pos, final int type, final BlockSide orientation) {
+ this.pos = pos;
+ this.type = type;
+ this.orientation = orientation;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("BlockEditData [pos=");
+ builder.append(pos);
+ builder.append(", type=");
+ builder.append(type);
+ builder.append(", orientation=");
+ builder.append(orientation);
+ builder.append("]");
+ return builder.toString();
+ }
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/world/BlockProvider.java b/ardor3d-craft/src/main/java/com/ardorcraft/world/BlockProvider.java
new file mode 100644
index 0000000..075cc61
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/world/BlockProvider.java
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.world;
+
+/**
+ * Proxy for serving up block data.
+ */
+public interface BlockProvider {
+
+ /**
+ * Get block id for world coordinate x, y, z
+ *
+ * @param x
+ * X coordinate
+ * @param y
+ * Y coordinate
+ * @param z
+ * Z coordinate
+ * @return Block id at position
+ */
+ int getBlock(int x, int y, int z);
+
+ /**
+ * Get block extra data for world coordinate x, y, z.<br>
+ * bits 0,1,2 = orientation<br>
+ * bits 3,4,5,6 = age (or something else)<br>
+ * bit 7 = whatever
+ * <p>
+ *
+ * @param x
+ * X coordinate
+ * @param y
+ * Y coordinate
+ * @param z
+ * Z coordinate
+ * @return Block id at position
+ */
+ int getBlockExtra(int x, final int y, int z);
+} \ No newline at end of file
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/world/BlockSide.java b/ardor3d-craft/src/main/java/com/ardorcraft/world/BlockSide.java
new file mode 100644
index 0000000..ac51f43
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/world/BlockSide.java
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.world;
+
+/**
+ * Used to identify the various sides of a block.
+ */
+public enum BlockSide {
+ Front,
+ Back,
+ Left,
+ Right,
+ Top,
+ Bottom
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/world/BlockType.java b/ardor3d-craft/src/main/java/com/ardorcraft/world/BlockType.java
new file mode 100644
index 0000000..a6de24d
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/world/BlockType.java
@@ -0,0 +1,15 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.world;
+
+/**
+ * Identifies if blocks are see through or covers the entire 1x1x1 box (which then allows for hidden surface removal).
+ */
+public enum BlockType {
+ Solid,
+ Transparent
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/world/BlockWorld.java b/ardor3d-craft/src/main/java/com/ardorcraft/world/BlockWorld.java
new file mode 100644
index 0000000..f05e1df
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/world/BlockWorld.java
@@ -0,0 +1,1979 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.world;
+
+import java.awt.Rectangle;
+import java.awt.image.BufferedImage;
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.imageio.ImageIO;
+
+import com.ardor3d.bounding.BoundingBox;
+import com.ardor3d.image.Image;
+import com.ardor3d.image.Texture;
+import com.ardor3d.image.Texture.ApplyMode;
+import com.ardor3d.image.Texture.MagnificationFilter;
+import com.ardor3d.image.Texture.WrapMode;
+import com.ardor3d.image.Texture2D;
+import com.ardor3d.image.TextureStoreFormat;
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyColorRGBA;
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardor3d.renderer.ContextManager;
+import com.ardor3d.renderer.IndexMode;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.renderer.state.BlendState;
+import com.ardor3d.renderer.state.CullState;
+import com.ardor3d.renderer.state.TextureState;
+import com.ardor3d.scenegraph.AbstractBufferData.VBOAccessMode;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.scenegraph.MeshData;
+import com.ardor3d.scenegraph.Node;
+import com.ardor3d.scenegraph.hint.CullHint;
+import com.ardor3d.scenegraph.hint.DataMode;
+import com.ardor3d.scenegraph.hint.LightCombineMode;
+import com.ardor3d.util.ReadOnlyTimer;
+import com.ardor3d.util.TextureManager;
+import com.ardor3d.util.geom.BufferUtils;
+
+import com.ardor3d.image.util.awt.AWTImageLoader;
+
+import com.ardorcraft.collision.HitTester;
+import com.ardorcraft.collision.IntersectionResult;
+import com.ardorcraft.collision.Tracer;
+import com.ardorcraft.data.Pos;
+import com.ardorcraft.util.BlockUtil;
+import com.ardorcraft.util.ColorUtil;
+import com.ardorcraft.util.DoubleBufferedList;
+import com.ardorcraft.util.queue.Coords;
+import com.ardorcraft.util.queue.Task;
+import com.ardorcraft.util.queue.WorkerManager;
+import com.ardorcraft.world.utils.ChunkDistanceComparator;
+
+/**
+ * BlockWorld is the main class when it comes to building and rendering the block world. It handles asynchronous
+ * updating and rebuilding of the chunks after receiving chunk or block updates. It also handles light calculations.
+ * <p>
+ * In the simplest setup, the blockworld is just initialized with a {@link WorldSettings} object. Then the main node for
+ * rendering the world is extracted and added to your scenegraph. Finally when you have everything else setup, all the
+ * threads are kicked off to start processing.
+ * <p>
+ * <code>
+ * blockWorld = new BlockWorld(settings);<br>
+ * root.attachChild(blockWorld.getWorldNode());<br>
+ * ...<br>
+ * blockWorld.startThreads();
+ * </code>
+ */
+public final class BlockWorld implements LightProvider, ChunkModifier {
+ private static final Logger logger = Logger.getLogger(BlockWorld.class.getName());
+
+ public static int WATER = 223;
+
+ private static final int CHUNK = 0;
+ private static final int BLOCKS = 1;
+ private static final int REBUILD = 2;
+ private static final int LIGHT = 3;
+
+ private final int width;
+ private final int height;
+ private final int subMeshSize;
+
+ private static final byte MAX_LIGHT = 15;
+ private static final byte LIGHT_STEP = 2;
+ private float globalLight = 1f;
+ private boolean lightingChanged = false;
+ private boolean updateLighting = true;
+ private boolean firstRender = true;
+
+ private final ColorRGBA caveLight = new ColorRGBA(0.08f, 0.07f, 0.07f, 1.0f);
+ private final ColorRGBA nightLight = new ColorRGBA(0.1f, 0.16f, 0.2f, 1.0f);
+ private final ColorRGBA dayLight = new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f);
+ private final ColorRGBA torchLight = new ColorRGBA(1.0f, 0.8f, 0.5f, 1.0f);
+
+ private ByteBuffer lightingBuffer;
+
+ private final byte blocks[];
+ // Extra data
+ // 0,1,2 = orientation
+ // 3,4,5,6 = age (or something else)
+ // 7 = whatever
+ private final byte blockExtra[];
+
+ private BlockUtil blockUtil;
+
+ private final short lightHeightmap[];
+ private final byte lightingWrite[];
+ private final byte localLightDataWrite[];
+
+ private final BitSet isLightSolid;
+
+ private final Set<Pos> currentPos = new LinkedHashSet<>();
+ private int oldX = Integer.MAX_VALUE;
+ private int oldZ = Integer.MAX_VALUE;
+ private int playerPositionX = Integer.MIN_VALUE;
+ private int playerPositionZ = Integer.MIN_VALUE;
+
+ private Texture lightTexture;
+ private TextureState terrainTextureState;
+ private BlendState terrainBlendState;
+ // private TextureState waterTextureState;
+ // private Texture waterTexture;
+
+ private BlendState transparentState;
+ private CullState cullState;
+ private CullState windowCullState;
+
+ private Node worldNode;
+ private Node solidNode;
+ private Node transparentNode;
+
+ private final IServerConnection serverConnection;
+
+ private final Map<Pos, Mesh> meshCache = new HashMap<>();
+ private final Map<Pos, Mesh> meshCacheTransparent = new HashMap<>();
+ private final Set<Pos> isLoaded = Collections.synchronizedSet(new HashSet<Pos>());
+
+ private final DoubleBufferedList<Pos> lightUpdateBox = new DoubleBufferedList<>();
+ private final DoubleBufferedList<UpdateMessage> chunkMailBox = new DoubleBufferedList<>();
+
+ private final int gridSize;
+ private int currentTileX;
+ private int currentTileZ;
+ private final int gridUnitSize;
+
+ private final CountDownLatch exitLatch = new CountDownLatch(2);
+ private boolean threadsStarted = false;
+ private boolean exit = false;
+
+ private final WorldSettings settings;
+ private boolean hasVBOSupport = false;
+ private boolean doDefaultTint = true;
+
+ private final GeometryHandler geometryHandler;
+ private final float[] tmpVertices;
+ private final float[] tmpTexcoords;
+ private final float[] tmpColors;
+ private final int[] tmpIndices;
+
+ private final GeometryHandler geometryHandlerTransparent;
+ private final float[] tmpVerticesTransparent;
+ private final float[] tmpTexcoordsTransparent;
+ private final float[] tmpColorsTransparent;
+ private final int[] tmpIndicesTransparent;
+
+ /**
+ * Create a new BlockWorld instance based on the provided settings.
+ *
+ * @param settings
+ */
+ public BlockWorld(final WorldSettings settings) {
+ Objects.requireNonNull(settings);
+ Objects.requireNonNull(settings.getServerConnection());
+
+ this.settings = new WorldSettings(settings);
+ width = settings.getGridSize() * settings.getTileSize();
+ height = settings.getTileHeight();
+ subMeshSize = settings.getTileSize();
+
+ doDefaultTint = settings.isDoDefaultTint();
+
+ hasVBOSupport = settings.isUseVBO() && ContextManager.getCurrentContext().getCapabilities().isVBOSupported();
+ if (hasVBOSupport) {
+ logger.info("Using VBO");
+ }
+
+ WorkerManager.getWorker().setExecutionTime(8);
+
+ gridUnitSize = settings.getGridSize();
+ gridSize = width / 2;
+
+ geometryHandler = new GeometryHandler(BlockWorld.this);
+ tmpVertices = new float[subMeshSize * height * subMeshSize * 24 * 3 / 2];
+ tmpTexcoords = new float[subMeshSize * height * subMeshSize * 24 * 2 / 2];
+ tmpColors = new float[subMeshSize * height * subMeshSize * 24 * 2 / 2];
+ tmpIndices = new int[subMeshSize * height * subMeshSize * 36 * 1 / 2];
+
+ geometryHandlerTransparent = new GeometryHandler(BlockWorld.this);
+ tmpVerticesTransparent = new float[subMeshSize * height * subMeshSize * 24 * 3 / 2];
+ tmpTexcoordsTransparent = new float[subMeshSize * height * subMeshSize * 24 * 2 / 2];
+ tmpColorsTransparent = new float[subMeshSize * height * subMeshSize * 24 * 2 / 2];
+ tmpIndicesTransparent = new int[subMeshSize * height * subMeshSize * 36 * 1 / 2];
+
+ blocks = new byte[width * height * width];
+ blockExtra = new byte[width * height * width];
+
+ lightingWrite = new byte[width * height * width];
+ lightHeightmap = new short[width * width];
+ localLightDataWrite = new byte[width * height * width];
+
+ Arrays.fill(lightingWrite, (byte) 255);
+
+ isLightSolid = new BitSet();
+
+ serverConnection = settings.getServerConnection();
+ serverConnection.getModifier(this);
+
+ createWorld(settings);
+ }
+
+ private void createWorld(final WorldSettings settings) {
+ terrainTextureState = new TextureState();
+
+ final int blockSize = settings.getTerrainTextureTileSize();
+ try {
+ BufferedImage img = ImageIO.read(settings.getTerrainTexture().openStream());
+
+ if (doDefaultTint) {
+ final List<Rectangle> areas = new ArrayList<>();
+ areas.add(new Rectangle(0, 0, blockSize, blockSize));
+ areas.add(new Rectangle(4 * blockSize, 3 * blockSize, blockSize, blockSize));
+ try {
+ img = ColorUtil.tintAreas(img, areas, blockSize * 4 - 1, 0);
+ } catch (final Exception e) {
+ logger.log(Level.WARNING, "Tinting failed!", e);
+ }
+ }
+ final Image image = AWTImageLoader.makeArdor3dImage(img, true);
+
+ final Texture texture1 = TextureManager.loadFromImage(image,
+ Texture.MinificationFilter.NearestNeighborLinearMipMap, TextureStoreFormat.GuessNoCompressedFormat);
+ texture1.setMagnificationFilter(MagnificationFilter.NearestNeighbor);
+ terrainTextureState.setTexture(texture1, 0);
+
+ final int tw = texture1.getImage().getWidth();
+ final int th = texture1.getImage().getHeight();
+ logger.info("Texture size: " + tw + "x" + th);
+
+ final int numLevels = (int) Math.floor(Math.log(blockSize) / Math.log(2));
+ texture1.setTextureMaxLevel(numLevels);
+
+ blockUtil = new BlockUtil(tw, th, blockSize);
+ } catch (final Exception e) {
+ throw new IllegalArgumentException(e);
+ }
+
+ final BufferedImage image = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB);
+ final ColorRGBA outLight = ColorRGBA.lerp(nightLight, dayLight, globalLight, new ColorRGBA());
+ final ColorRGBA tmp1 = new ColorRGBA();
+ for (int x = 0; x < 16; x++) {
+ for (int y = 0; y < 16; y++) {
+ final float global = x / 15f;
+ final float local = y / 15f;
+
+ ColorRGBA.lerp(caveLight, outLight, global, tmp1);
+ tmp1.lerpLocal(torchLight, local);
+
+ image.setRGB(x, y, tmp1.asIntARGB());
+ }
+ }
+ final Image lightImage = AWTImageLoader.makeArdor3dImage(image, false);
+ lightTexture = TextureManager.loadFromImage(lightImage, Texture.MinificationFilter.BilinearNoMipMaps);
+ lightTexture.setApply(ApplyMode.Modulate);
+ lightTexture.setWrap(WrapMode.EdgeClamp);
+ terrainTextureState.setTexture(lightTexture, 1);
+ lightingBuffer = BufferUtils.createByteBuffer(16 * 16 * 4);
+
+ // waterTextureState = new TextureState();
+ // waterTexture = TextureManager.load(settings.getWaterTexture(),
+ // Texture.MinificationFilter.NearestNeighborLinearMipMap, TextureStoreFormat.GuessNoCompressedFormat,
+ // true);
+ // waterTexture.setMagnificationFilter(MagnificationFilter.NearestNeighbor);
+ // waterTextureState.setTexture(waterTexture, 0);
+
+ terrainBlendState = new BlendState();
+ terrainBlendState.setTestEnabled(true);
+ terrainBlendState.setBlendEnabled(false);
+ terrainBlendState.setReference(0.5f);
+
+ transparentState = new BlendState();
+ transparentState.setBlendEnabled(true);
+
+ cullState = new CullState();
+ cullState.setCullFace(CullState.Face.Back);
+ cullState.setEnabled(true);
+
+ windowCullState = new CullState();
+ windowCullState.setCullFace(CullState.Face.None);
+ windowCullState.setEnabled(true);
+
+ worldNode = new WorldNode("WorldModifier");
+ worldNode.getSceneHints().setCullHint(CullHint.Never);
+
+ solidNode = new Node("Solid");
+ solidNode.getSceneHints().setCullHint(CullHint.Never);
+
+ transparentNode = new Node("Water");
+ transparentNode.getSceneHints().setCullHint(CullHint.Never);
+
+ worldNode.attachChild(solidNode);
+ worldNode.attachChild(transparentNode);
+ }
+
+ private class WorldNode extends Node {
+ public WorldNode(final String name) {
+ super(name);
+ }
+
+ @Override
+ public void draw(final Renderer renderer) {
+ if (!firstRender && updateLighting) {
+ updateLightingTexture(renderer);
+ updateLighting = false;
+ }
+ if (firstRender) {
+ firstRender = false;
+ }
+
+ solidNode.draw(renderer);
+ transparentNode.draw(renderer);
+ }
+
+ private final ColorRGBA resultColorFirst = new ColorRGBA();
+ private final ColorRGBA resultColorFinal = new ColorRGBA();
+
+ private void updateLightingTexture(final Renderer renderer) {
+ ColorRGBA.lerp(nightLight, dayLight, globalLight, resultColorFirst);
+
+ for (int x = 0; x < 16; x++) {
+ for (int y = 0; y < 16; y++) {
+ final float global = y / 15f;
+ final float local = x / 15f;
+
+ ColorRGBA.lerp(caveLight, resultColorFirst, global, resultColorFinal);
+ resultColorFinal.lerpLocal(torchLight, local);
+
+ lightingBuffer.put((byte) (resultColorFinal.getRed() * 255));
+ lightingBuffer.put((byte) (resultColorFinal.getGreen() * 255));
+ lightingBuffer.put((byte) (resultColorFinal.getBlue() * 255));
+ lightingBuffer.put((byte) 255);
+ }
+ }
+
+ lightingBuffer.flip();
+ renderer.updateTexture2DSubImage((Texture2D) lightTexture, 0, 0, 16, 16, lightingBuffer, 0, 0, 16);
+ }
+
+ @Override
+ public void onDraw(final Renderer renderer) {
+ draw(renderer);
+ }
+ }
+
+ /**
+ * Start all processing threads. Currently a chunk updater and a light updater.
+ */
+ public void startThreads() {
+ if (!threadsStarted) {
+ exit = false;
+
+ final Thread chunkUpdater = new Thread(new ChunkUpdater(), "ChunkUpdater");
+ chunkUpdater.setDaemon(true);
+ chunkUpdater.start();
+
+ final Thread lightUpdater = new Thread(new LightUpdater(), "LightUpdater");
+ lightUpdater.setDaemon(true);
+ lightUpdater.start();
+
+ threadsStarted = true;
+ }
+ }
+
+ /**
+ * Get the Node used for rendering the block world.
+ *
+ * @return Node with block world
+ */
+ public Node getWorldNode() {
+ return worldNode;
+ }
+
+ // private final Matrix4 textureMatrix = new Matrix4();
+
+ /**
+ * Issue world updates based on currently set player position/direction.
+ *
+ * @param timer
+ */
+ public void update(final ReadOnlyTimer timer) {
+ // textureMatrix.setValue(3, 0, timer.getTimeInSeconds() * 0.2);
+ // waterTexture.setTextureMatrix(textureMatrix);
+
+ if (playerPositionX == oldX && playerPositionZ == oldZ) {
+ if (lightingChanged) {
+ for (final Pos pos : currentPos) {
+ chunkMailBox.add(new LightMessage(pos.x, pos.z));
+ }
+ lightingChanged = false;
+ }
+ return;
+ }
+ // final int diffX = playerPositionX - oldX;
+ // final int diffZ = playerPositionZ - oldZ;
+ oldX = playerPositionX;
+ oldZ = playerPositionZ;
+
+ // TODO
+ serverConnection.update(playerPositionX, playerPositionZ);
+
+ final Set<Pos> newPos = new LinkedHashSet<>();
+ for (int x = 0; x < gridUnitSize; x++) {
+ for (int z = 0; z < gridUnitSize; z++) {
+ final int xx = playerPositionX + x - gridUnitSize / 2;
+ final int zz = playerPositionZ + z - gridUnitSize / 2;
+ newPos.add(new Pos(xx, 0, zz));
+ }
+ }
+
+ final Iterator<Pos> tileIterator = currentPos.iterator();
+ while (tileIterator.hasNext()) {
+ final Pos pos = tileIterator.next();
+
+ if (!newPos.contains(pos)) {
+ isLoaded.remove(pos);
+ tileIterator.remove();
+ } else {
+ newPos.remove(pos);
+ }
+ }
+
+ if (!newPos.isEmpty()) {
+ final List<Pos> sortedPos = new ArrayList<>();
+ sortedPos.addAll(newPos);
+ Collections.sort(sortedPos, new ChunkDistanceComparator(playerPositionX, playerPositionZ));
+
+ // TODO
+ for (final Pos coll : sortedPos) {
+ serverConnection.requestChunk(coll.x, coll.z);
+ }
+ }
+
+ if (lightingChanged) {
+ for (final Pos pos : currentPos) {
+ chunkMailBox.add(new LightMessage(pos.x, pos.z));
+ }
+ lightingChanged = false;
+ }
+
+ currentPos.addAll(newPos);
+ }
+
+ @Override
+ public void postChunk(final int x, final int z, final Chunk chunk) {
+ chunkMailBox.add(new ChunkMessage(x, z, chunk.getBlocks(), chunk.getExtra()));
+ }
+
+ /**
+ * Let the block world know the current player position and direction.
+ *
+ * @param location
+ * @param direction
+ */
+ public void updatePlayer(final ReadOnlyVector3 location, final ReadOnlyVector3 direction) {
+ serverConnection.updatePlayerPosition(location, direction);
+
+ playerPositionX = MathUtils.floor(location.getXf() / subMeshSize);
+ playerPositionZ = MathUtils.floor(location.getZf() / subMeshSize);
+
+ WorkerManager.getWorker().setPlayerCoords(playerPositionX, playerPositionZ);
+
+ currentTileX = playerPositionX * subMeshSize;
+ currentTileZ = playerPositionZ * subMeshSize;
+ }
+
+ /**
+ * Reload all chunks.
+ */
+ public void reloadAll() {
+ currentPos.clear();
+ oldX = Integer.MAX_VALUE;
+ oldZ = Integer.MAX_VALUE;
+ }
+
+ private final class LightUpdater implements Runnable {
+ private final Set<Pos> updates = new LinkedHashSet<>();
+
+ @Override
+ public void run() {
+ while (!exit) {
+ final List<Pos> lightList = lightUpdateBox.switchAndGet();
+
+ if (!lightList.isEmpty()) {
+ updates.clear();
+
+ for (final Pos pos : lightList) {
+ if (pos == null) {
+ continue;
+ }
+
+ final Pos gridpos = new Pos(MathUtils.floor((float) pos.x / subMeshSize), 0,
+ MathUtils.floor((float) pos.z / subMeshSize));
+
+ if (isChunkValid(gridpos.x, gridpos.z)) {
+ updates.add(gridpos);
+ }
+
+ final int xUpdate = (float) MathUtils.moduloPositive(pos.x, subMeshSize) / (float) subMeshSize > 0.5f ? gridpos.x + 1
+ : gridpos.x - 1;
+ final int zUpdate = (float) MathUtils.moduloPositive(pos.z, subMeshSize) / (float) subMeshSize > 0.5f ? gridpos.z + 1
+ : gridpos.z - 1;
+ if (isChunkValid(xUpdate, gridpos.z)) {
+ updates.add(new Pos(xUpdate, 0, gridpos.z));
+ }
+ if (isChunkValid(gridpos.x, zUpdate)) {
+ updates.add(new Pos(gridpos.x, 0, zUpdate));
+ }
+ if (isChunkValid(xUpdate, zUpdate)) {
+ updates.add(new Pos(xUpdate, 0, zUpdate));
+ }
+ }
+
+ final Set<Pos> localLightOpenList = new LinkedHashSet<>();
+ for (final Pos pos : updates) {
+ // initiateLocalLightList(localLightOpenList, pos.x * subMeshSize, pos.z * subMeshSize, pos.x
+ // * subMeshSize + subMeshSize, pos.z * subMeshSize + subMeshSize, height);
+ initiateLighting(localLightOpenList, pos.x * subMeshSize, pos.z * subMeshSize, pos.x
+ * subMeshSize + subMeshSize, pos.z * subMeshSize + subMeshSize, height);
+ Thread.yield();
+ }
+
+ for (final Pos pos : updates) {
+ fillQueueLocal(localLightDataWrite, localLightOpenList, pos.x * subMeshSize + subMeshSize,
+ pos.z * subMeshSize, pos.x * subMeshSize + subMeshSize + 1, pos.z * subMeshSize
+ + subMeshSize, height);
+ fillQueueLocal(localLightDataWrite, localLightOpenList, pos.x * subMeshSize - 1, pos.z
+ * subMeshSize, pos.x * subMeshSize, pos.z * subMeshSize + subMeshSize, height);
+
+ fillQueueLocal(localLightDataWrite, localLightOpenList, pos.x * subMeshSize, pos.z
+ * subMeshSize + subMeshSize, pos.x * subMeshSize + subMeshSize, pos.z * subMeshSize
+ + subMeshSize + 1, height);
+ fillQueueLocal(localLightDataWrite, localLightOpenList, pos.x * subMeshSize, pos.z
+ * subMeshSize - 1, pos.x * subMeshSize + subMeshSize, pos.z * subMeshSize, height);
+ Thread.yield();
+ }
+
+ djikstraLight(localLightDataWrite, localLightOpenList);
+ Thread.yield();
+
+ final Set<Pos> openList = new LinkedHashSet<>();
+ for (final Pos pos : updates) {
+ openList.clear();
+ floodLighting(openList, pos.x * subMeshSize, pos.z * subMeshSize, pos.x * subMeshSize
+ + subMeshSize, pos.z * subMeshSize + subMeshSize, height);
+
+ fillQueue(lightingWrite, openList, pos.x * subMeshSize + subMeshSize, pos.z * subMeshSize,
+ pos.x * subMeshSize + subMeshSize + 1, pos.z * subMeshSize + subMeshSize, height);
+ fillQueue(lightingWrite, openList, pos.x * subMeshSize - 1, pos.z * subMeshSize, pos.x
+ * subMeshSize, pos.z * subMeshSize + subMeshSize, height);
+
+ fillQueue(lightingWrite, openList, pos.x * subMeshSize, pos.z * subMeshSize + subMeshSize,
+ pos.x * subMeshSize + subMeshSize, pos.z * subMeshSize + subMeshSize + 1, height);
+ fillQueue(lightingWrite, openList, pos.x * subMeshSize, pos.z * subMeshSize - 1, pos.x
+ * subMeshSize + subMeshSize, pos.z * subMeshSize, height);
+ Thread.yield();
+
+ djikstraLight(lightingWrite, openList);
+
+ Thread.yield();
+ }
+
+ for (final Pos pos : updates) {
+ buildLightReadData(lightingWrite, localLightDataWrite, pos.x * subMeshSize,
+ pos.z * subMeshSize, pos.x * subMeshSize + subMeshSize, pos.z * subMeshSize
+ + subMeshSize, height);
+ Thread.yield();
+ }
+
+ for (final Pos pos : updates) {
+ chunkMailBox.add(new LightMessage(pos.x, pos.z));
+ }
+
+ try {
+ Thread.sleep(5);
+ } catch (final InterruptedException e) {
+ e.printStackTrace();
+ }
+ } else {
+ try {
+ Thread.sleep(5);
+ } catch (final InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ exitLatch.countDown();
+ }
+ }
+
+ private final class ChunkUpdater implements Runnable {
+ @Override
+ public void run() {
+ final List<UpdateMessage> dataUpdates = new ArrayList<>();
+ final List<BlocksMessage> blockDataUpdates = new ArrayList<>();
+ final List<UpdateMessage> rebuildUpdates = new ArrayList<>();
+ final List<UpdateMessage> lightUpdates = new ArrayList<>();
+
+ while (!exit) {
+ final List<UpdateMessage> list = chunkMailBox.switchAndGet();
+
+ if (!list.isEmpty()) {
+ dataUpdates.clear();
+ blockDataUpdates.clear();
+ rebuildUpdates.clear();
+ lightUpdates.clear();
+
+ for (final UpdateMessage updateMessage : list) {
+ if (updateMessage == null) {
+ continue;
+ }
+
+ if (isChunkOutside(updateMessage.chunkX, updateMessage.chunkZ)) {
+ continue;
+ }
+
+ if (updateMessage instanceof ChunkMessage) {
+ dataUpdates.add(updateMessage);
+
+ final RebuildMessage rebuildMessageX = new RebuildMessage(updateMessage.chunkX,
+ updateMessage.chunkZ);
+ if (!rebuildUpdates.contains(rebuildMessageX)) {
+ rebuildUpdates.add(rebuildMessageX);
+ }
+
+ if (!isChunkOutside(updateMessage.chunkX - 1, updateMessage.chunkZ)) {
+ final RebuildMessage rebuildMessage = new RebuildMessage(updateMessage.chunkX - 1,
+ updateMessage.chunkZ);
+ if (!rebuildUpdates.contains(rebuildMessage)) {
+ rebuildUpdates.add(rebuildMessage);
+ }
+ }
+ if (!isChunkOutside(updateMessage.chunkX + 1, updateMessage.chunkZ)) {
+ final RebuildMessage rebuildMessage = new RebuildMessage(updateMessage.chunkX + 1,
+ updateMessage.chunkZ);
+ if (!rebuildUpdates.contains(rebuildMessage)) {
+ rebuildUpdates.add(rebuildMessage);
+ }
+ }
+ if (!isChunkOutside(updateMessage.chunkX, updateMessage.chunkZ - 1)) {
+ final RebuildMessage rebuildMessage = new RebuildMessage(updateMessage.chunkX,
+ updateMessage.chunkZ - 1);
+ if (!rebuildUpdates.contains(rebuildMessage)) {
+ rebuildUpdates.add(rebuildMessage);
+ }
+ }
+ if (!isChunkOutside(updateMessage.chunkX, updateMessage.chunkZ + 1)) {
+ final RebuildMessage rebuildMessage = new RebuildMessage(updateMessage.chunkX,
+ updateMessage.chunkZ + 1);
+ if (!rebuildUpdates.contains(rebuildMessage)) {
+ rebuildUpdates.add(rebuildMessage);
+ }
+ }
+
+ } else if (updateMessage instanceof BlocksMessage) {
+ final BlocksMessage message = (BlocksMessage) updateMessage;
+
+ if (blockDataUpdates.contains(message)) {
+ final BlocksMessage messageOld = blockDataUpdates
+ .get(blockDataUpdates.indexOf(message));
+ messageOld.blockUpdates.addAll(message.blockUpdates);
+ } else {
+ blockDataUpdates.add((BlocksMessage) updateMessage);
+
+ final RebuildMessage rebuildMessageX = new RebuildMessage(updateMessage.chunkX,
+ updateMessage.chunkZ);
+ if (!rebuildUpdates.contains(rebuildMessageX)) {
+ rebuildUpdates.add(rebuildMessageX);
+ }
+ }
+
+ for (final BlockEditData data : message.blockUpdates) {
+ addBlockEditDatas(rebuildUpdates, updateMessage, data.pos);
+ }
+ } else if (updateMessage instanceof RebuildMessage) {
+ if (!rebuildUpdates.contains(updateMessage)) {
+ rebuildUpdates.add(updateMessage);
+ }
+ }
+ }
+
+ for (final UpdateMessage updateMessage : list) {
+ if (updateMessage instanceof LightMessage) {
+ if (!rebuildUpdates.contains(updateMessage) && !lightUpdates.contains(updateMessage)) {
+ lightUpdates.add(updateMessage);
+ }
+ }
+ }
+
+ for (final UpdateMessage updateMessage : dataUpdates) {
+ if (updateMessage instanceof ChunkMessage) {
+ final ChunkMessage message = (ChunkMessage) updateMessage;
+
+ WorkerManager.getWorker().enqueue(
+ new Task(CHUNK, updateMessage.chunkX, updateMessage.chunkZ) {
+ @Override
+ public void execute(final Renderer renderer) {
+ final byte[] data = message.data;
+ final byte[] extra = message.extra;
+
+ final int x = message.chunkX;
+ final int z = message.chunkZ;
+
+ if (isChunkOutside(x, z)) {
+ return;
+ }
+
+ for (int xx = 0; xx < subMeshSize; xx++) {
+ for (int yy = 1; yy < height; yy++) {
+ for (int zz = 0; zz < subMeshSize; zz++) {
+ final int index = xx + (yy + zz * height) * subMeshSize;
+ final int blockId = data[index] & 0xff;
+ final BlockSide orientation = extra == null ? BlockSide.Front
+ : BlockSide.values()[extra[index] & 0xff];
+
+ setBlockInternal(xx + x * subMeshSize, yy,
+ zz + z * subMeshSize, blockId, orientation);
+
+ // TODO: optimize
+ setLightMax(lightingWrite, xx + x * subMeshSize, yy, zz + z
+ * subMeshSize);
+ }
+ }
+ }
+
+ // initiateLightingOnly(x * subMeshSize, z * subMeshSize, x * subMeshSize
+ // + subMeshSize, z * subMeshSize + subMeshSize, height);
+
+ isLoaded.add(new Pos(x, 0, z));
+
+ lightUpdateBox.add(new Pos(x * subMeshSize, 0, z * subMeshSize));
+ lightUpdateBox.add(new Pos(x * subMeshSize + subMeshSize - 1, 0, z
+ * subMeshSize));
+ lightUpdateBox.add(new Pos(x * subMeshSize, 0, z * subMeshSize
+ + subMeshSize - 1));
+ lightUpdateBox.add(new Pos(x * subMeshSize + subMeshSize - 1, 0, z
+ * subMeshSize + subMeshSize - 1));
+ }
+
+ private void setLightMax(final byte[] buffer, int x, int y, int z) {
+ x = MathUtils.moduloPositive(x, width);
+ y = MathUtils.clamp(y, 0, height - 1);
+ z = MathUtils.moduloPositive(z, width);
+
+ buffer[x + (y + z * height) * width] = (byte) 0xff;
+ }
+ });
+ }
+ }
+
+ for (final UpdateMessage updateMessage : blockDataUpdates) {
+ if (updateMessage instanceof BlocksMessage) {
+ final BlocksMessage message = (BlocksMessage) updateMessage;
+
+ WorkerManager.getWorker().enqueue(
+ new Task(BLOCKS, updateMessage.chunkX, updateMessage.chunkZ) {
+ @Override
+ public void execute(final Renderer renderer) {
+ final int x = message.chunkX;
+ final int z = message.chunkZ;
+
+ if (!isChunkValid(x, z)) {
+ return;
+ }
+
+ final List<BlockEditData> blockUpdates = message.blockUpdates;
+ for (final BlockEditData blockEditData : blockUpdates) {
+ final Pos pos = blockEditData.pos;
+ setBlockInternal(pos.x, pos.y, pos.z, blockEditData.type,
+ blockEditData.orientation);
+ lightUpdateBox.add(pos);
+ serverConnection.setBlock(pos.x, pos.y, pos.z, blockEditData.type,
+ blockEditData.orientation);
+ }
+
+ // initiateLightingOnly(x * subMeshSize, z * subMeshSize, x * subMeshSize
+ // + subMeshSize, z * subMeshSize + subMeshSize, height);
+ }
+ });
+ }
+ }
+
+ for (final UpdateMessage updateMessage : rebuildUpdates) {
+ if (updateMessage instanceof RebuildMessage) {
+ WorkerManager.getWorker().enqueue(
+ new Task(REBUILD, updateMessage.chunkX, updateMessage.chunkZ) {
+ @Override
+ public void execute(final Renderer renderer) {
+ if (!isChunkValid(updateMessage.chunkX, updateMessage.chunkZ)) {
+ return;
+ }
+
+ createSubMesh(renderer, updateMessage.chunkX * subMeshSize, 0,
+ updateMessage.chunkZ * subMeshSize, subMeshSize, height,
+ subMeshSize, ALL);
+ }
+ });
+ }
+ }
+
+ for (final UpdateMessage updateMessage : lightUpdates) {
+ if (updateMessage instanceof LightMessage) {
+ WorkerManager.getWorker().enqueue(
+ new Task(LIGHT, updateMessage.chunkX, updateMessage.chunkZ) {
+ @Override
+ public void execute(final Renderer renderer) {
+ final int x = updateMessage.chunkX;
+ final int z = updateMessage.chunkZ;
+
+ if (!isChunkValid(x, z)) {
+ return;
+ }
+
+ createSubMesh(renderer, x * subMeshSize, 0, z * subMeshSize, subMeshSize,
+ height, subMeshSize, COLORS);
+ }
+ });
+ }
+ }
+ }
+
+ try {
+ Thread.sleep(5);
+ } catch (final InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ exitLatch.countDown();
+ }
+
+ private void addBlockEditDatas(final List<UpdateMessage> rebuildUpdates, final UpdateMessage updateMessage,
+ final Pos pos) {
+ final int xx = MathUtils.moduloPositive(pos.x, subMeshSize);
+ final int zz = MathUtils.moduloPositive(pos.z, subMeshSize);
+ if (xx == 0 && isChunkValid(updateMessage.chunkX - 1, updateMessage.chunkZ)) {
+ final RebuildMessage rebuildMessage = new RebuildMessage(updateMessage.chunkX - 1, updateMessage.chunkZ);
+ if (!rebuildUpdates.contains(rebuildMessage)) {
+ rebuildUpdates.add(rebuildMessage);
+ }
+ } else if (xx == subMeshSize - 1 && isChunkValid(updateMessage.chunkX + 1, updateMessage.chunkZ)) {
+ final RebuildMessage rebuildMessage = new RebuildMessage(updateMessage.chunkX + 1, updateMessage.chunkZ);
+ if (!rebuildUpdates.contains(rebuildMessage)) {
+ rebuildUpdates.add(rebuildMessage);
+ }
+ }
+ if (zz == 0 && isChunkValid(updateMessage.chunkX, updateMessage.chunkZ - 1)) {
+ final RebuildMessage rebuildMessage = new RebuildMessage(updateMessage.chunkX, updateMessage.chunkZ - 1);
+ if (!rebuildUpdates.contains(rebuildMessage)) {
+ rebuildUpdates.add(rebuildMessage);
+ }
+ } else if (zz == subMeshSize - 1 && isChunkValid(updateMessage.chunkX, updateMessage.chunkZ + 1)) {
+ final RebuildMessage rebuildMessage = new RebuildMessage(updateMessage.chunkX, updateMessage.chunkZ + 1);
+ if (!rebuildUpdates.contains(rebuildMessage)) {
+ rebuildUpdates.add(rebuildMessage);
+ }
+ }
+
+ if (xx == 0 && zz == 0 && isChunkValid(updateMessage.chunkX - 1, updateMessage.chunkZ - 1)) {
+ final RebuildMessage rebuildMessage = new RebuildMessage(updateMessage.chunkX - 1,
+ updateMessage.chunkZ - 1);
+ if (!rebuildUpdates.contains(rebuildMessage)) {
+ rebuildUpdates.add(rebuildMessage);
+ }
+ } else if (xx == subMeshSize - 1 && zz == 0
+ && isChunkValid(updateMessage.chunkX + 1, updateMessage.chunkZ - 1)) {
+ final RebuildMessage rebuildMessage = new RebuildMessage(updateMessage.chunkX + 1,
+ updateMessage.chunkZ - 1);
+ if (!rebuildUpdates.contains(rebuildMessage)) {
+ rebuildUpdates.add(rebuildMessage);
+ }
+ } else if (xx == subMeshSize - 1 && zz == subMeshSize - 1
+ && isChunkValid(updateMessage.chunkX + 1, updateMessage.chunkZ + 1)) {
+ final RebuildMessage rebuildMessage = new RebuildMessage(updateMessage.chunkX + 1,
+ updateMessage.chunkZ + 1);
+ if (!rebuildUpdates.contains(rebuildMessage)) {
+ rebuildUpdates.add(rebuildMessage);
+ }
+ } else if (xx == 0 && zz == subMeshSize - 1
+ && isChunkValid(updateMessage.chunkX - 1, updateMessage.chunkZ + 1)) {
+ final RebuildMessage rebuildMessage = new RebuildMessage(updateMessage.chunkX - 1,
+ updateMessage.chunkZ + 1);
+ if (!rebuildUpdates.contains(rebuildMessage)) {
+ rebuildUpdates.add(rebuildMessage);
+ }
+ }
+ }
+
+ }
+
+ private boolean isChunkValid(final int chunkX, final int chunkZ) {
+ if (!isLoaded.contains(new Pos(chunkX, 0, chunkZ))) {
+ return false;
+ }
+ return !isChunkOutside(chunkX, chunkZ);
+ }
+
+ private boolean isChunkOutside(final int chunkX, final int chunkZ) {
+ final int diffX1 = playerPositionX - chunkX;
+ final int diffZ1 = playerPositionZ - chunkZ;
+ if (diffX1 > gridUnitSize / 2 || diffX1 < -gridUnitSize / 2 + 1 || diffZ1 > gridUnitSize / 2
+ || diffZ1 < -gridUnitSize / 2 + 1) {
+ return true;
+ }
+ return false;
+ }
+
+ private static int VERTICES = 1 << 0;
+ private static int TEXCOORDS = 1 << 1;
+ private static int COLORS = 1 << 2;
+ private static int INDICES = 1 << 3;
+ private static int ALL = VERTICES | TEXCOORDS | COLORS | INDICES;
+
+ private void createSubMesh(final Renderer renderer, final int xStart, final int yStart, final int zStart,
+ final int xWidth, final int yWidth, final int zWidth, final int creationType) {
+ final int modxStart = MathUtils.moduloPositive(xStart, width);
+ final int modyStart = MathUtils.moduloPositive(yStart, height);
+ final int modzStart = MathUtils.moduloPositive(zStart, width);
+
+ final Pos pos = new Pos(modxStart / subMeshSize, modyStart / height, modzStart / subMeshSize);
+ int minY = Integer.MAX_VALUE, maxY = Integer.MIN_VALUE;
+
+ final float[] vertices = (creationType & VERTICES) == VERTICES ? tmpVertices : null;
+ final float[] texcoords = (creationType & TEXCOORDS) == TEXCOORDS ? tmpTexcoords : null;
+ final float[] colors = (creationType & COLORS) == COLORS ? tmpColors : null;
+ final int[] indices = (creationType & INDICES) == INDICES ? tmpIndices : null;
+ geometryHandler.setVertices(vertices);
+ geometryHandler.setTexcoords(texcoords);
+ geometryHandler.setColors(colors);
+ geometryHandler.setIndices(indices);
+
+ final float[] verticesTransparent = (creationType & VERTICES) == VERTICES ? tmpVerticesTransparent : null;
+ final float[] texcoordsTransparent = (creationType & TEXCOORDS) == TEXCOORDS ? tmpTexcoordsTransparent : null;
+ final float[] colorsTransparent = (creationType & COLORS) == COLORS ? tmpColorsTransparent : null;
+ final int[] indicesTransparent = (creationType & INDICES) == INDICES ? tmpIndicesTransparent : null;
+ geometryHandlerTransparent.setVertices(verticesTransparent);
+ geometryHandlerTransparent.setTexcoords(texcoordsTransparent);
+ geometryHandlerTransparent.setColors(colorsTransparent);
+ geometryHandlerTransparent.setIndices(indicesTransparent);
+
+ // long t = System.currentTimeMillis();
+ int vertexCount = 0;
+ int indexCount = 0;
+ int vertexCountTransparent = 0;
+ int indexCountTransparent = 0;
+
+ for (int x = xStart; x < xStart + xWidth; x++) {
+ for (int y = yStart; y < yStart + yWidth; y++) {
+ for (int z = zStart; z < zStart + zWidth; z++) {
+ final int block = getBlock(x, y, z);
+ if (block == 0) {
+ continue;
+ }
+
+ if (y < minY) {
+ minY = y;
+ }
+ if (y >= maxY) {
+ maxY = y;
+ }
+
+ final boolean isTransparent = blockUtil.getIsSemiTransparent(block);
+ final GeometryHandler currentGeometryHandler = isTransparent ? geometryHandlerTransparent
+ : geometryHandler;
+
+ if (!isTransparent) {
+ currentGeometryHandler.setStartIndexCount(indexCount);
+ currentGeometryHandler.setStartVertexCount(vertexCount);
+ } else {
+ currentGeometryHandler.setStartIndexCount(indexCountTransparent);
+ currentGeometryHandler.setStartVertexCount(vertexCountTransparent);
+ }
+ blockUtil.getGeometryProducer(block).generateBlock(block, currentGeometryHandler, this, blockUtil,
+ x, y, z);
+ if (!isTransparent) {
+ indexCount += currentGeometryHandler.getIndexCount();
+ vertexCount += currentGeometryHandler.getVertexCount();
+ } else {
+ indexCountTransparent += currentGeometryHandler.getIndexCount();
+ vertexCountTransparent += currentGeometryHandler.getVertexCount();
+ }
+ }
+ }
+ }
+
+ setMeshData(renderer, xStart, yStart, zStart, pos, indexCount, vertexCount, vertices, texcoords, colors,
+ indices, minY, maxY);
+ setMeshDataTransparent(renderer, xStart, yStart, zStart, pos, indexCountTransparent, vertexCountTransparent,
+ verticesTransparent, texcoordsTransparent, colorsTransparent, indicesTransparent, minY, maxY);
+ }
+
+ private void setMeshData(final Renderer renderer, final int xStart, final int yStart, final int zStart,
+ final Pos pos, final int indexCount, final int vertexCount, final float[] vertices,
+ final float[] texcoords, final float[] colors, final int[] indices, final int minY, final int maxY) {
+ if (indexCount == 0) {
+ if (meshCache.containsKey(pos)) {
+ final Mesh mesh = meshCache.get(pos);
+ if (solidNode.hasChild(mesh)) {
+ solidNode.detachChild(mesh);
+ }
+ }
+ return;
+ }
+
+ Mesh mesh;
+ if (meshCache.containsKey(pos)) {
+ mesh = meshCache.get(pos);
+ if (!solidNode.hasChild(mesh)) {
+ solidNode.attachChild(mesh);
+ }
+ } else {
+ if (indices == null) {
+ return;
+ }
+
+ mesh = new Mesh(xStart + "," + yStart + "," + zStart);
+ mesh.getSceneHints().setLightCombineMode(LightCombineMode.Off);
+
+ mesh.setModelBound(new BoundingBox(new Vector3(), subMeshSize / 2, height / 2, subMeshSize / 2));
+ mesh.getSceneHints().setCullHint(CullHint.Dynamic);
+
+ mesh.setRenderState(terrainTextureState);
+ mesh.setRenderState(cullState);
+ mesh.setRenderState(terrainBlendState);
+
+ if (hasVBOSupport) {
+ mesh.getSceneHints().setDataMode(DataMode.VBO);
+ }
+
+ solidNode.attachChild(mesh);
+ meshCache.put(pos, mesh);
+ mesh.getMeshData().setIndexMode(IndexMode.Triangles);
+ }
+
+ updateBuffers(renderer, xStart, zStart, indexCount, vertexCount, vertices, texcoords, colors, indices, minY,
+ maxY, mesh);
+ }
+
+ private void setMeshDataTransparent(final Renderer renderer, final int xStart, final int yStart, final int zStart,
+ final Pos pos, final int indexCount, final int vertexCount, final float[] vertices,
+ final float[] texcoords, final float[] colors, final int[] indices, final int minY, final int maxY) {
+ if (indexCount == 0) {
+ if (meshCacheTransparent.containsKey(pos)) {
+ final Mesh mesh = meshCacheTransparent.get(pos);
+ if (transparentNode.hasChild(mesh)) {
+ transparentNode.detachChild(mesh);
+ }
+ }
+ return;
+ }
+
+ Mesh mesh;
+ if (meshCacheTransparent.containsKey(pos)) {
+ mesh = meshCacheTransparent.get(pos);
+ if (!transparentNode.hasChild(mesh)) {
+ transparentNode.attachChild(mesh);
+ }
+ } else {
+ if (indices == null) {
+ return;
+ }
+
+ mesh = new Mesh(xStart + "," + yStart + "," + zStart);
+ mesh.getSceneHints().setLightCombineMode(LightCombineMode.Off);
+
+ mesh.setModelBound(new BoundingBox(new Vector3(), subMeshSize / 2, height / 2, subMeshSize / 2));
+ mesh.getSceneHints().setCullHint(CullHint.Dynamic);
+
+ mesh.setRenderState(terrainTextureState);
+ mesh.setRenderState(windowCullState);
+ mesh.setRenderState(transparentState);
+
+ if (hasVBOSupport) {
+ mesh.getSceneHints().setDataMode(DataMode.VBO);
+ }
+
+ transparentNode.attachChild(mesh);
+ meshCacheTransparent.put(pos, mesh);
+ mesh.getMeshData().setIndexMode(IndexMode.Triangles);
+ }
+
+ updateBuffers(renderer, xStart, zStart, indexCount, vertexCount, vertices, texcoords, colors, indices, minY,
+ maxY, mesh);
+ }
+
+ private void updateBuffers(final Renderer renderer, final int xStart, final int zStart, final int indexCount,
+ final int vertexCount, final float[] vertices, final float[] texcoords, final float[] colors,
+ final int[] indices, final int minY, final int maxY, final Mesh mesh) {
+ final int height = (maxY - minY) / 2 + 1;
+ final int posY = (maxY + minY) / 2 + 1;
+ final BoundingBox boundingBox = (BoundingBox) mesh.getModelBound();
+ boundingBox.setYExtent(height);
+ boundingBox.setCenter(xStart + subMeshSize / 2, posY, zStart + subMeshSize / 2);
+ mesh.updateWorldBound(false);
+
+ final MeshData meshData = mesh.getMeshData();
+
+ if (vertices != null) {
+ FloatBuffer vertexBuffer = meshData.getVertexBuffer();
+ if (vertexBuffer != null && vertexBuffer.capacity() >= vertexCount * 3) {
+ vertexBuffer.clear();
+ if (hasVBOSupport) {
+ meshData.getVertexCoords().setNeedsRefresh(true);
+ // meshData.getVertexCoords().removeVBOID(ContextManager.getCurrentContext().getGlContextRep());
+ }
+ } else {
+ if (hasVBOSupport && vertexBuffer != null) {
+ renderer.deleteVBOs(meshData.getVertexCoords());
+ }
+ vertexBuffer = BufferUtils.createFloatBuffer(vertexCount * 3);
+ meshData.setVertexBuffer(vertexBuffer);
+ if (hasVBOSupport) {
+ meshData.getVertexCoords().setVboAccessMode(VBOAccessMode.DynamicDraw);
+ }
+ }
+ vertexBuffer.put(vertices, 0, vertexCount * 3);
+ }
+
+ if (texcoords != null) {
+ FloatBuffer textureBuffer = meshData.getTextureBuffer(0);
+ if (textureBuffer != null && textureBuffer.capacity() >= vertexCount * 2) {
+ textureBuffer.clear();
+ if (hasVBOSupport) {
+ meshData.getTextureCoords(0).setNeedsRefresh(true);
+ // meshData.getTextureCoords(0).removeVBOID(ContextManager.getCurrentContext().getGlContextRep());
+ }
+ } else {
+ if (hasVBOSupport && textureBuffer != null) {
+ renderer.deleteVBOs(meshData.getTextureCoords(0));
+ }
+ textureBuffer = BufferUtils.createFloatBuffer(vertexCount * 2);
+ meshData.setTextureBuffer(textureBuffer, 0);
+ if (hasVBOSupport) {
+ meshData.getTextureCoords(0).setVboAccessMode(VBOAccessMode.DynamicDraw);
+ }
+ }
+ textureBuffer.put(texcoords, 0, vertexCount * 2);
+ }
+
+ if (colors != null) {
+ FloatBuffer colorBuffer = meshData.getTextureBuffer(1);
+ if (colorBuffer != null && colorBuffer.capacity() >= vertexCount * 2) {
+ colorBuffer.clear();
+ if (hasVBOSupport) {
+ meshData.getTextureCoords(1).setNeedsRefresh(true);
+ // meshData.getColorCoords().removeVBOID(ContextManager.getCurrentContext().getGlContextRep());
+ }
+ } else {
+ if (hasVBOSupport && colorBuffer != null) {
+ renderer.deleteVBOs(meshData.getTextureCoords(1));
+ }
+ colorBuffer = BufferUtils.createFloatBuffer(vertexCount * 2);
+ meshData.setTextureBuffer(colorBuffer, 1);
+ if (hasVBOSupport) {
+ meshData.getTextureCoords(1).setVboAccessMode(VBOAccessMode.DynamicDraw);
+ }
+ }
+ colorBuffer.put(colors, 0, vertexCount * 2);
+ }
+
+ if (indices != null) {
+ IntBuffer indexBuffer = (IntBuffer) meshData.getIndexBuffer();
+ if (indexBuffer != null && indexBuffer.capacity() >= indexCount) {
+ final int oldLimit = indexBuffer.limit();
+ indexBuffer.clear();
+ indexBuffer.limit(indexCount);
+ if (hasVBOSupport) {
+ if (indexCount <= oldLimit) {
+ meshData.getIndices().setNeedsRefresh(true);
+ } else {
+ renderer.deleteVBOs(meshData.getIndices());
+ }
+ }
+ } else {
+ if (hasVBOSupport && indexBuffer != null) {
+ renderer.deleteVBOs(meshData.getIndices());
+ }
+ indexBuffer = BufferUtils.createIntBuffer(indexCount);
+ meshData.setIndexBuffer(indexBuffer);
+ if (hasVBOSupport) {
+ meshData.getIndices().setVboAccessMode(VBOAccessMode.DynamicDraw);
+ }
+ }
+ indexBuffer.put(indices, 0, indexCount);
+ }
+ }
+
+ /**
+ * Get the exact float used global lighting.
+ *
+ * @param lighting
+ * @return float
+ */
+ public float lookupLighting(final int lighting) {
+ final int clampedLight = MathUtils.clamp(lighting, 0, 15);
+ return clampedLight / 15f;
+ }
+
+ @Override
+ public int getBlock(int x, final int y, int z) {
+ if (testOutsideBounds(x, y, z)) {
+ return 1;
+ }
+
+ x = MathUtils.moduloPositive(x, width);
+ z = MathUtils.moduloPositive(z, width);
+
+ return blocks[x + (y + z * height) * width] & 0xff;
+ }
+
+ private int getBlockFast(final int x, final int y, final int z) {
+ return blocks[x + (y + z * height) * width] & 0xff;
+ }
+
+ @Override
+ public void setBlock(final int x, final int y, final int z, final int blockId) {
+ setBlock(x, y, z, blockId, BlockSide.Front);
+ }
+
+ @Override
+ public void setBlock(final int x, final int y, final int z, final int blockId, final BlockSide orientation) {
+ if (testOutsideBounds(x, y, z)) {
+ return;
+ }
+
+ final BlockEditData data = new BlockEditData(new Pos(x, y, z), blockId, orientation);
+
+ final int chunkX = MathUtils.floor((float) x / subMeshSize);
+ final int chunkZ = MathUtils.floor((float) z / subMeshSize);
+ chunkMailBox.add(new BlocksMessage(chunkX, chunkZ, data));
+ }
+
+ @Override
+ public void setBlocks(final List<BlockEditData> blockList) {
+ final Map<Coords, List<BlockEditData>> chunks = new HashMap<>();
+ for (final BlockEditData data : blockList) {
+ final int xx = data.pos.x;
+ final int zz = data.pos.z;
+ final int chunkX = MathUtils.floor((float) xx / subMeshSize);
+ final int chunkZ = MathUtils.floor((float) zz / subMeshSize);
+ final Coords coord = new Coords();
+ coord.setX(chunkX);
+ coord.setZ(chunkZ);
+
+ List<BlockEditData> subData = chunks.get(coord);
+ if (subData == null) {
+ subData = new ArrayList<>();
+ chunks.put(coord, subData);
+ }
+ subData.add(data);
+ }
+ for (final Entry<Coords, List<BlockEditData>> entry : chunks.entrySet()) {
+ final Coords coord = entry.getKey();
+ final List<BlockEditData> list = entry.getValue();
+ chunkMailBox.add(new BlocksMessage(coord.getX(), coord.getZ(), list));
+ }
+ }
+
+ /**
+ * Get block orientation at coordinate x, y, z.
+ *
+ * @param x
+ * @param y
+ * @param z
+ * @return Orientation
+ */
+ public BlockSide getBlockOrientation(final int x, final int y, final int z) {
+ return BlockSide.values()[getBlockExtra(x, y, z)];
+ }
+
+ @Override
+ public int getBlockExtra(int x, final int y, int z) {
+ if (testOutsideBounds(x, y, z)) {
+ return 0;
+ }
+
+ x = MathUtils.moduloPositive(x, width);
+ z = MathUtils.moduloPositive(z, width);
+
+ return blockExtra[x + (y + z * height) * width] & 0x7;
+ }
+
+ private void setBlockInternal(final int xx, final int yy, final int zz, final int data, final BlockSide orientation) {
+ if (testOutsideBounds(xx, yy, zz)) {
+ return;
+ }
+
+ final int x = MathUtils.moduloPositive(xx, width);
+ final int y = yy;
+ final int z = MathUtils.moduloPositive(zz, width);
+
+ blocks[x + (y + z * height) * width] = (byte) data;
+ blockExtra[x + (y + z * height) * width] = (byte) orientation.ordinal();
+ }
+
+ private void setLightSolid(int x, final int y, int z, final boolean val) {
+ x = MathUtils.moduloPositive(x, width);
+ z = MathUtils.moduloPositive(z, width);
+ isLightSolid.set(x + (y + z * height) * width, val);
+ }
+
+ private boolean isLightSolid(int x, final int y, int z) {
+ x = MathUtils.moduloPositive(x, width);
+ z = MathUtils.moduloPositive(z, width);
+ return isLightSolid.get(x + (y + z * height) * width);
+ }
+
+ private boolean testOutsideBounds(final int x, final int y, final int z) {
+ return y < 1 || y >= height || x < currentTileX - gridSize || x >= currentTileX + gridSize
+ || z < currentTileZ - gridSize || z >= currentTileZ + gridSize;
+ }
+
+ private byte getLight(final byte[] buffer, int x, int y, int z) {
+ x = MathUtils.moduloPositive(x, width);
+ y = MathUtils.clamp(y, 0, height - 1);
+ z = MathUtils.moduloPositive(z, width);
+
+ return (byte) (buffer[x + (y + z * height) * width] & 0x0f);
+ }
+
+ private void setLight(final byte[] buffer, int x, int y, int z, final int data) {
+ x = MathUtils.moduloPositive(x, width);
+ y = MathUtils.clamp(y, 0, height - 1);
+ z = MathUtils.moduloPositive(z, width);
+
+ buffer[x + (y + z * height) * width] = (byte) (buffer[x + (y + z * height) * width] & 0xf0 | data & 0xf);
+ }
+
+ private byte getLightRead(final byte[] buffer, int x, int y, int z) {
+ x = MathUtils.moduloPositive(x, width);
+ y = MathUtils.clamp(y, 0, height - 1);
+ z = MathUtils.moduloPositive(z, width);
+
+ return (byte) (buffer[x + (y + z * height) * width] >> 4 & 0xf);
+ }
+
+ private void setLightRead(final byte[] buffer, int x, int y, int z, final int data) {
+ x = MathUtils.moduloPositive(x, width);
+ y = MathUtils.clamp(y, 0, height - 1);
+ z = MathUtils.moduloPositive(z, width);
+
+ buffer[x + (y + z * height) * width] = (byte) (buffer[x + (y + z * height) * width] & 0x0f | (data & 0xf) << 4);
+ }
+
+ private int getLightHeightmap(int x, int z) {
+ if (testOutsideBounds(x, 5, z)) {
+ return height;
+ }
+
+ x = MathUtils.moduloPositive(x, width);
+ z = MathUtils.moduloPositive(z, width);
+
+ return lightHeightmap[x + z * width];
+ }
+
+ private void setLightHeightmap(final int x, final int z, final short data) {
+ lightHeightmap[x + z * width] = data;
+ }
+
+ private void initiateLighting(final Set<Pos> localLightOpenList, final int xStart, final int zStart,
+ final int xEnd, final int zEnd, final int height) {
+ for (int x = xStart; x < xEnd; x++) {
+ for (int z = zStart; z < zEnd; z++) {
+ final int modx = MathUtils.moduloPositive(x, width);
+ final int modz = MathUtils.moduloPositive(z, width);
+
+ int currentLight = MAX_LIGHT;
+ for (int y = height - 1; y >= 0; y--) {
+ final int block = getBlockFast(modx, y, modz);
+ final boolean solid = blockUtil.getBlockType(block) == BlockType.Solid;
+ setLightSolid(x, y, z, false);
+ if (currentLight > 0) {
+ if (block != 0 && block != WATER && solid) {
+ setLightHeightmap(modx, modz, (short) y);
+ setLight(lightingWrite, x, y, z, 0);
+ setLight(localLightDataWrite, x, y, z, 0);
+ setLightSolid(x, y, z, true);
+ currentLight = 0;
+ } else {
+ setLight(lightingWrite, x, y, z, currentLight);
+ setLight(localLightDataWrite, x, y, z, 0);
+ }
+ if (block == WATER) {
+ currentLight -= 2;
+ if (currentLight < 0) {
+ currentLight = 0;
+ }
+ } else if (block != 0 && !solid) {
+ currentLight -= 1;
+ if (currentLight < 0) {
+ currentLight = 0;
+ }
+ }
+ } else if (block == 0 || block == WATER || !solid) {
+ setLight(lightingWrite, x, y, z, 0);
+ setLight(localLightDataWrite, x, y, z, 0);
+ } else {
+ setLight(lightingWrite, x, y, z, 0);
+ setLight(localLightDataWrite, x, y, z, 0);
+ setLightSolid(x, y, z, true);
+ }
+
+ if (blockUtil.isLocalLight(block)) {
+ setLight(localLightDataWrite, x, y, z, MAX_LIGHT);
+ localLightOpenList.add(new Pos(x, y, z));
+ }
+ }
+ }
+ }
+ }
+
+// private void initiateLocalLightList(final Set<Pos> localLightOpenList, final int xStart, final int zStart,
+// final int xEnd, final int zEnd, final int height) {
+// for (int x = xStart; x < xEnd; x++) {
+// for (int z = zStart; z < zEnd; z++) {
+// final int modx = MathUtils.moduloPositive(x, width);
+// final int modz = MathUtils.moduloPositive(z, width);
+//
+// for (int y = height - 1; y >= 0; y--) {
+// final int block = getBlockFast(modx, y, modz);
+// if (blockUtil.isLocalLight(block)) {
+// localLightOpenList.add(new Pos(x, y, z));
+// }
+// }
+// }
+// }
+// }
+
+// private void initiateLightingOnly(final int xStart, final int zStart, final int xEnd, final int zEnd,
+// final int height) {
+// for (int x = xStart; x < xEnd; x++) {
+// for (int z = zStart; z < zEnd; z++) {
+// final int modx = MathUtils.moduloPositive(x, width);
+// final int modz = MathUtils.moduloPositive(z, width);
+//
+// int currentLight = MAX_LIGHT;
+// for (int y = height - 1; y >= 0; y--) {
+// final int block = getBlockFast(modx, y, modz);
+// final boolean solid = blockUtil.getBlockType(block) == BlockType.Solid;
+// setLightSolid(x, y, z, false);
+// if (currentLight > 0) {
+// if (block != 0 && block != WATER && solid) {
+// setLightHeightmap(modx, modz, (short) y);
+// setLight(lightingWrite, x, y, z, 0);
+// setLight(localLightDataWrite, x, y, z, 0);
+// setLightSolid(x, y, z, true);
+// currentLight = 0;
+// } else {
+// setLight(lightingWrite, x, y, z, currentLight);
+// setLight(localLightDataWrite, x, y, z, 0);
+// }
+// if (block == WATER) {
+// currentLight -= 2;
+// if (currentLight < 0) {
+// currentLight = 0;
+// }
+// } else if (block != 0 && !solid) {
+// currentLight -= 1;
+// if (currentLight < 0) {
+// currentLight = 0;
+// }
+// }
+// } else if (block == 0 || block == WATER || !solid) {
+// setLight(lightingWrite, x, y, z, 0);
+// setLight(localLightDataWrite, x, y, z, 0);
+// } else {
+// setLight(lightingWrite, x, y, z, 0);
+// setLight(localLightDataWrite, x, y, z, 0);
+// setLightSolid(x, y, z, true);
+// }
+// }
+// }
+// }
+// }
+
+ private void fillQueue(final byte[] write, final Set<Pos> openList, final int xStart, final int zStart,
+ final int xEnd, final int zEnd, final int height) {
+ if (testOutsideBounds(xStart, 5, zStart)) {
+ return;
+ }
+ for (int x = xStart; x < xEnd; x++) {
+ for (int z = zStart; z < zEnd; z++) {
+ final int yStart = getLightHeightmap(x, z) - 1;
+ for (int y = yStart; y >= 0; y--) {
+ final byte currentLight = getLight(write, x, y, z);
+ if (currentLight <= LIGHT_STEP) {
+ continue;
+ }
+ addToLightQueue(openList, x, y, z);
+ }
+ }
+ }
+ }
+
+ private void fillQueueLocal(final byte[] write, final Set<Pos> openList, final int xStart, final int zStart,
+ final int xEnd, final int zEnd, final int height) {
+ if (testOutsideBounds(xStart, 5, zStart)) {
+ return;
+ }
+ for (int x = xStart; x < xEnd; x++) {
+ for (int z = zStart; z < zEnd; z++) {
+ for (int y = height - 1; y >= 0; y--) {
+ final byte currentLight = getLight(write, x, y, z);
+ if (currentLight <= LIGHT_STEP) {
+ continue;
+ }
+ addToLightQueue(openList, x, y, z);
+ }
+ }
+ }
+ }
+
+ private void floodLighting(final Set<Pos> openList, final int xStart, final int zStart, final int xEnd,
+ final int zEnd, final int height) {
+ for (int x = xStart; x < xEnd; x++) {
+ for (int z = zStart; z < zEnd; z++) {
+ final int yStart = getLightHeightmap(x, z) - 1;
+
+ if (testOutsideBounds(x, 5, z)) {
+ continue;
+ }
+ final int modx = MathUtils.moduloPositive(x, width);
+ final int modz = MathUtils.moduloPositive(z, width);
+
+ for (int y = yStart; y >= 0; y--) {
+ final int block = getBlockFast(modx, y, modz);
+ final boolean solid = blockUtil.getBlockType(block) == BlockType.Solid;
+ if (block != 0 && solid) {
+ continue;
+ }
+
+ int neighbourHeight;
+
+ // if (!testOutsideBounds(x + 1, 5, z)) {
+ neighbourHeight = getLightHeightmap(x + 1, z);
+ if (neighbourHeight < y) {
+ affectLightNode(lightingWrite, openList, x, y, z, getLight(lightingWrite, x + 1, y, z));
+ continue;
+ }
+ // }
+ // if (!testOutsideBounds(x - 1, 5, z)) {
+ neighbourHeight = getLightHeightmap(x - 1, z);
+ if (neighbourHeight < y) {
+ affectLightNode(lightingWrite, openList, x, y, z, getLight(lightingWrite, x - 1, y, z));
+ continue;
+ }
+ // }
+ // if (!testOutsideBounds(x, 5, z + 1)) {
+ neighbourHeight = getLightHeightmap(x, z + 1);
+ if (neighbourHeight < y) {
+ affectLightNode(lightingWrite, openList, x, y, z, getLight(lightingWrite, x, y, z + 1));
+ continue;
+ }
+ // }
+ // if (!testOutsideBounds(x, 5, z - 1)) {
+ neighbourHeight = getLightHeightmap(x, z - 1);
+ if (neighbourHeight < y) {
+ affectLightNode(lightingWrite, openList, x, y, z, getLight(lightingWrite, x, y, z - 1));
+ continue;
+ }
+ // }
+ }
+ }
+ }
+ }
+
+ private void addToLightQueue(final Set<Pos> openList, final int x, final int y, final int z) {
+ openList.add(new Pos(x, y, z));
+ }
+
+ private void djikstraLight(final byte[] write, Set<Pos> openList) {
+ int iterations = 0;
+ Set<Pos> backupList = new LinkedHashSet<>();
+ while (!openList.isEmpty()) {
+ final Iterator<Pos> iterator = openList.iterator();
+ while (iterator.hasNext()) {
+ final Pos openPos = iterator.next();
+ iterator.remove();
+
+ final int x = openPos.x;
+ final int y = openPos.y;
+ final int z = openPos.z;
+
+ final byte currentLight = getLight(write, x, y, z);
+ if (currentLight <= LIGHT_STEP) {
+ continue;
+ }
+
+ if (testLightExpand(write, x - 1, y, z, currentLight)) {
+ affectLightNode(write, backupList, x - 1, y, z, currentLight);
+ }
+ if (testLightExpand(write, x + 1, y, z, currentLight)) {
+ affectLightNode(write, backupList, x + 1, y, z, currentLight);
+ }
+ if (testLightExpand(write, x, y - 1, z, currentLight)) {
+ affectLightNode(write, backupList, x, y - 1, z, currentLight);
+ }
+ if (testLightExpand(write, x, y + 1, z, currentLight)) {
+ affectLightNode(write, backupList, x, y + 1, z, currentLight);
+ }
+ if (testLightExpand(write, x, y, z - 1, currentLight)) {
+ affectLightNode(write, backupList, x, y, z - 1, currentLight);
+ }
+ if (testLightExpand(write, x, y, z + 1, currentLight)) {
+ affectLightNode(write, backupList, x, y, z + 1, currentLight);
+ }
+
+ if (iterations++ > 50000) {
+ logger.warning("Too many light calcs, skipping: " + iterations + ", " + openList.size());
+ return;
+ }
+ if (iterations == 10000) {
+ logger.warning("Lots of light calcs: " + iterations + ", " + openList.size());
+ }
+ if (iterations == 30000) {
+ logger.warning("Scary amount of light calcs: " + iterations + ", " + openList.size());
+ }
+ }
+ final Set<Pos> tmp = openList;
+ openList = backupList;
+ backupList = tmp;
+ }
+ }
+
+ private void affectLightNode(final byte[] write, final Set<Pos> openList, final int x, final int y, final int z,
+ final byte currentLight) {
+ addToLightQueue(openList, x, y, z);
+ final int light = currentLight - LIGHT_STEP;
+ setLight(write, x, y, z, light < 0 ? 0 : light);
+ }
+
+ private boolean testLightExpand(final byte[] write, final int x, final int y, final int z, final byte currentLight) {
+ if (testOutsideBounds(x, y, z)) {
+ return false;
+ }
+
+ final int block = getBlock(x, y, z);
+ final boolean solid = blockUtil.getBlockType(block) == BlockType.Solid;
+
+ return (block == 0 || block == WATER || !solid) && getLight(write, x, y, z) < currentLight - LIGHT_STEP;
+ }
+
+ private void buildLightReadData(final byte[] writeGlobal, final byte[] writeLocal, final int xStart,
+ final int zStart, final int xEnd, final int zEnd, final int height) {
+ for (int x = xStart; x < xEnd + 1; x++) {
+ for (int y = 0; y < height; y++) {
+ for (int z = zStart; z < zEnd + 1; z++) {
+ float dataGlobal = 0;
+ float dataLocal = 0;
+ float divider = 0;
+
+ if (!isLightSolid(x, y, z)) {
+ dataGlobal += getLight(writeGlobal, x, y, z);
+ dataLocal += getLight(writeLocal, x, y, z);
+ divider++;
+ }
+ if (!isLightSolid(x - 1, y, z)) {
+ dataGlobal += getLight(writeGlobal, x - 1, y, z);
+ dataLocal += getLight(writeLocal, x - 1, y, z);
+ divider++;
+ }
+ if (!isLightSolid(x, y, z - 1)) {
+ dataGlobal += getLight(writeGlobal, x, y, z - 1);
+ dataLocal += getLight(writeLocal, x, y, z - 1);
+ divider++;
+ }
+ if (!isLightSolid(x - 1, y, z - 1)) {
+ dataGlobal += getLight(writeGlobal, x - 1, y, z - 1);
+ dataLocal += getLight(writeLocal, x - 1, y, z - 1);
+ divider++;
+ }
+ if (y > 0) {
+ if (!isLightSolid(x, y - 1, z)) {
+ dataGlobal += getLight(writeGlobal, x, y - 1, z);
+ dataLocal += getLight(writeLocal, x, y - 1, z);
+ divider++;
+ }
+ if (!isLightSolid(x - 1, y - 1, z)) {
+ dataGlobal += getLight(writeGlobal, x - 1, y - 1, z);
+ dataLocal += getLight(writeLocal, x - 1, y - 1, z);
+ divider++;
+ }
+ if (!isLightSolid(x, y - 1, z - 1)) {
+ dataGlobal += getLight(writeGlobal, x, y - 1, z - 1);
+ dataLocal += getLight(writeLocal, x, y - 1, z - 1);
+ divider++;
+ }
+ if (!isLightSolid(x - 1, y - 1, z - 1)) {
+ dataGlobal += getLight(writeGlobal, x - 1, y - 1, z - 1);
+ dataLocal += getLight(writeLocal, x - 1, y - 1, z - 1);
+ divider++;
+ }
+ }
+
+ if (divider > 0) {
+ setLightRead(writeGlobal, x, y, z, (int) (dataGlobal / divider));
+ setLightRead(writeLocal, x, y, z, (int) (dataLocal / divider));
+ } else {
+ setLightRead(writeGlobal, x, y, z, 0);
+ setLightRead(writeLocal, x, y, z, 0);
+ }
+ }
+ }
+ }
+ }
+
+ public int getHeight() {
+ return height;
+ }
+
+ private final HitTester defaultPickingTester = new HitTester() {
+ @Override
+ public boolean isHit(final int blockId) {
+ return blockUtil.getIsPickable(blockId);
+ }
+ };
+ private final HitTester defaultHitTester = new HitTester() {
+ @Override
+ public boolean isHit(final int blockId) {
+ return blockUtil.getIsCollidable(blockId);
+ }
+ };
+
+ public void tracePicking(final ReadOnlyVector3 curpos, final ReadOnlyVector3 raydir, final int maxIterations,
+ final IntersectionResult result) {
+ new Tracer(this, defaultPickingTester, height).traceCollision(curpos, raydir, maxIterations, result);
+ }
+
+ public void traceCollision(final ReadOnlyVector3 curpos, final ReadOnlyVector3 raydir, final int maxIterations,
+ final IntersectionResult result) {
+ new Tracer(this, defaultHitTester, height).traceCollision(curpos, raydir, maxIterations, result);
+ }
+
+ /**
+ * Stop all processing threads, and wait for them to finish.
+ */
+ public void stopThreads() {
+ if (threadsStarted) {
+ exit = true;
+
+ logger.info("Stopping threads...");
+
+ boolean wait = false;
+ try {
+ wait = exitLatch.await(20, TimeUnit.SECONDS);
+ } catch (final InterruptedException e1) {
+ e1.printStackTrace();
+ }
+
+ chunkMailBox.switchAndGet();
+ chunkMailBox.switchAndGet();
+ lightUpdateBox.switchAndGet();
+ lightUpdateBox.switchAndGet();
+
+ threadsStarted = false;
+
+ logger.info("All threads done: " + wait);
+ }
+ }
+
+ public float getGlobalLight() {
+ return globalLight;
+ }
+
+ /**
+ * Set global light value (0-15) for day/night simulation.
+ *
+ * @param globalLight
+ */
+ public void setGlobalLight(final float globalLight) {
+ this.globalLight = MathUtils.clamp(globalLight, 0f, 1f);
+ updateLighting = true;
+ }
+
+ public WorldSettings getSettings() {
+ return settings;
+ }
+
+ public BlockUtil getBlockUtil() {
+ return blockUtil;
+ }
+
+ @Override
+ public float getGlobalLighting(final int x, final int y, final int z) {
+ final int lighting = getLightRead(lightingWrite, x, y, z);
+ return lookupLighting(lighting);
+ }
+
+ @Override
+ public float getLocalLighting(final int x, final int y, final int z) {
+ final int lighting = getLightRead(localLightDataWrite, x, y, z);
+ return lookupLighting(lighting);
+ }
+
+ public Texture getLightTexture() {
+ return lightTexture;
+ }
+
+ public void setLightTexture(final Texture lightTexture) {
+ this.lightTexture = lightTexture;
+ }
+
+ public ReadOnlyColorRGBA getCaveLight() {
+ return caveLight;
+ }
+
+ public void setCaveLight(final ColorRGBA caveLight) {
+ this.caveLight.set(caveLight);
+ updateLighting = true;
+ }
+
+ public ReadOnlyColorRGBA getNightLight() {
+ return nightLight;
+ }
+
+ public void setNightLight(final ColorRGBA nightLight) {
+ this.nightLight.set(nightLight);
+ updateLighting = true;
+ }
+
+ public ReadOnlyColorRGBA getDayLight() {
+ return dayLight;
+ }
+
+ public void setDayLight(final ColorRGBA dayLight) {
+ this.dayLight.set(dayLight);
+ updateLighting = true;
+ }
+
+ public ReadOnlyColorRGBA getTorchLight() {
+ return torchLight;
+ }
+
+ public void setTorchLight(final ColorRGBA torchLight) {
+ this.torchLight.set(torchLight);
+ updateLighting = true;
+ }
+
+ public int getCurrentlyLoadedChunksCount() {
+ return isLoaded.size();
+ }
+
+ private abstract class UpdateMessage {
+ int chunkX;
+ int chunkZ;
+
+ @Override
+ public String toString() {
+ return "UpdateMessage [chunkX=" + chunkX + ", chunkZ=" + chunkZ + "]";
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + chunkX;
+ result = prime * result + chunkZ;
+ return result;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final UpdateMessage other = (UpdateMessage) obj;
+ if (chunkX != other.chunkX) {
+ return false;
+ }
+ if (chunkZ != other.chunkZ) {
+ return false;
+ }
+ return true;
+ }
+ }
+
+ private class BlocksMessage extends UpdateMessage {
+ List<BlockEditData> blockUpdates;
+
+ public BlocksMessage(final int chunkX, final int chunkZ, final List<BlockEditData> blockUpdates) {
+ this.chunkX = chunkX;
+ this.chunkZ = chunkZ;
+ this.blockUpdates = blockUpdates;
+ }
+
+ public BlocksMessage(final int chunkX, final int chunkZ, final BlockEditData singleBlockUpdate) {
+ this.chunkX = chunkX;
+ this.chunkZ = chunkZ;
+ blockUpdates = new ArrayList<>();
+ blockUpdates.add(singleBlockUpdate);
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("BlocksMessage [chunkX=");
+ builder.append(chunkX);
+ builder.append(", chunkZ=");
+ builder.append(chunkZ);
+ builder.append(", blockUpdates=");
+ builder.append(blockUpdates);
+ builder.append("]");
+ return builder.toString();
+ }
+ }
+
+ private class ChunkMessage extends UpdateMessage {
+ byte[] data;
+ byte[] extra;
+
+ public ChunkMessage(final int chunkX, final int chunkZ, final byte[] data, final byte[] extra) {
+ this.chunkX = chunkX;
+ this.chunkZ = chunkZ;
+ this.data = data;
+ this.extra = extra;
+ }
+ }
+
+ private class LightMessage extends UpdateMessage {
+ public LightMessage(final int chunkX, final int chunkZ) {
+ this.chunkX = chunkX;
+ this.chunkZ = chunkZ;
+ }
+ }
+
+ private class RebuildMessage extends UpdateMessage {
+ public RebuildMessage(final int chunkX, final int chunkZ) {
+ this.chunkX = chunkX;
+ this.chunkZ = chunkZ;
+ }
+ }
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/world/Chunk.java b/ardor3d-craft/src/main/java/com/ardorcraft/world/Chunk.java
new file mode 100644
index 0000000..bca52a2
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/world/Chunk.java
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.world;
+
+public class Chunk {
+ private final byte[] blocks;
+ private final byte[] extra;
+
+ public Chunk(final byte[] blocks, final byte[] extra) {
+ this.blocks = blocks;
+ this.extra = extra;
+ }
+
+ public byte[] getBlocks() {
+ return blocks;
+ }
+
+ public byte[] getExtra() {
+ return extra;
+ }
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/world/ChunkModifier.java b/ardor3d-craft/src/main/java/com/ardorcraft/world/ChunkModifier.java
new file mode 100644
index 0000000..4ed268d
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/world/ChunkModifier.java
@@ -0,0 +1,22 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.world;
+
+import java.util.List;
+
+/**
+ * Interface for classes that allows block editing.
+ */
+public interface ChunkModifier extends BlockProvider {
+ void postChunk(final int x, final int z, final Chunk chunk);
+
+ void setBlock(final int x, final int y, final int z, final int blockId);
+
+ void setBlock(final int x, final int y, final int z, final int blockId, final BlockSide orientation);
+
+ void setBlocks(final List<BlockEditData> blockList);
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/world/GeometryHandler.java b/ardor3d-craft/src/main/java/com/ardorcraft/world/GeometryHandler.java
new file mode 100644
index 0000000..0738fde
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/world/GeometryHandler.java
@@ -0,0 +1,217 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.world;
+
+import com.ardorcraft.util.geometryproducers.GeometryProducer;
+
+/**
+ * This is the class that {@link GeometryProducer}s use to set geometry into the world.
+ */
+public final class GeometryHandler {
+ private float[] vertices;
+ private float[] texcoords;
+ private float[] colors;
+ private int vertexCount;
+ private int startVertexCount;
+ private int[] indices;
+ private int indexCount;
+ private int startIndexCount;
+
+ private final LightProvider lightProvider;
+
+ GeometryHandler(final LightProvider lightProvider) {
+ this.lightProvider = lightProvider;
+ }
+
+ void setStartIndexCount(final int index) {
+ startIndexCount = index;
+ }
+
+ void setStartVertexCount(final int index) {
+ startVertexCount = index;
+ }
+
+ /**
+ * Sets the index count.
+ *
+ * @param count
+ * the new index count
+ */
+ public void setIndexCount(final int count) {
+ indexCount = count;
+ }
+
+ /**
+ * Sets the vertex count.
+ *
+ * @param count
+ * the new vertex count
+ */
+ public void setVertexCount(final int count) {
+ vertexCount = count;
+ }
+
+ /**
+ * Gets the index count.
+ *
+ * @return the index count
+ */
+ public int getIndexCount() {
+ return indexCount;
+ }
+
+ /**
+ * Gets the vertex count.
+ *
+ * @return the vertex count
+ */
+ public int getVertexCount() {
+ return vertexCount;
+ }
+
+ /**
+ * Checks for vertices.
+ *
+ * @return true, if successful
+ */
+ public boolean hasVertices() {
+ return vertices != null;
+ }
+
+ /**
+ * Sets the vertex.
+ *
+ * @param index
+ * the index
+ * @param x
+ * the x
+ * @param y
+ * the y
+ * @param z
+ * the z
+ */
+ public void setVertex(final int index, final float x, final float y, final float z) {
+ vertices[startVertexCount * 3 + index * 3 + 0] = x;
+ vertices[startVertexCount * 3 + index * 3 + 1] = y;
+ vertices[startVertexCount * 3 + index * 3 + 2] = z;
+ }
+
+ /**
+ * Checks for texture coords.
+ *
+ * @return true, if successful
+ */
+ public boolean hasTextureCoords() {
+ return texcoords != null;
+ }
+
+ /**
+ * Sets the texture coord.
+ *
+ * @param index
+ * the index
+ * @param u
+ * the u
+ * @param v
+ * the v
+ */
+ public void setTextureCoord(final int index, final float u, final float v) {
+ texcoords[startVertexCount * 2 + index * 2 + 0] = u;
+ texcoords[startVertexCount * 2 + index * 2 + 1] = v;
+ }
+
+ /**
+ * Checks for colors.
+ *
+ * @return true, if successful
+ */
+ public boolean hasColors() {
+ return colors != null;
+ }
+
+ /**
+ * Sets the color.
+ *
+ * @param index
+ * the index
+ * @param globalLight
+ * the global light
+ * @param localLight
+ * the local light
+ */
+ public void setColor(final int index, final float globalLight, final float localLight) {
+ colors[startVertexCount * 2 + index * 2 + 0] = globalLight;
+ colors[startVertexCount * 2 + index * 2 + 1] = localLight;
+ }
+
+ /**
+ * Checks for indices.
+ *
+ * @return true, if successful
+ */
+ public boolean hasIndices() {
+ return indices != null;
+ }
+
+ /**
+ * Sets the index.
+ *
+ * @param index
+ * the index
+ * @param value
+ * the value
+ */
+ public void setIndex(final int index, final int value) {
+ indices[startIndexCount + index] = startVertexCount + value;
+ }
+
+ /**
+ * Gets the global lighting.
+ *
+ * @param x
+ * the x
+ * @param y
+ * the y
+ * @param z
+ * the z
+ * @return the global lighting
+ */
+ public float getGlobalLighting(final int x, final int y, final int z) {
+ return lightProvider.getGlobalLighting(x, y, z);
+ }
+
+ /**
+ * Gets the local lighting.
+ *
+ * @param x
+ * the x
+ * @param y
+ * the y
+ * @param z
+ * the z
+ * @return the local lighting
+ */
+ public float getLocalLighting(final int x, final int y, final int z) {
+ return lightProvider.getLocalLighting(x, y, z);
+ }
+
+ void setVertices(final float[] vertices) {
+ this.vertices = vertices;
+ }
+
+ void setTexcoords(final float[] texcoords) {
+ this.texcoords = texcoords;
+ }
+
+ void setColors(final float[] colors) {
+ this.colors = colors;
+ }
+
+ void setIndices(final int[] indices) {
+ this.indices = indices;
+ }
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/world/IServerConnection.java b/ardor3d-craft/src/main/java/com/ardorcraft/world/IServerConnection.java
new file mode 100644
index 0000000..cda7c25
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/world/IServerConnection.java
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.world;
+
+import com.ardor3d.math.type.ReadOnlyVector3;
+
+public interface IServerConnection {
+ void getModifier(ChunkModifier chunkModifier);
+
+ void update(final int x, final int z);
+
+ void requestChunk(final int x, final int z);
+
+ void setBlock(final int x, final int y, final int z, final int blockId, final BlockSide orientation);
+
+ void updatePlayerPosition(final ReadOnlyVector3 location, ReadOnlyVector3 direction);
+
+ void connect(final String address);
+
+ void close();
+} \ No newline at end of file
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/world/LightProvider.java b/ardor3d-craft/src/main/java/com/ardorcraft/world/LightProvider.java
new file mode 100644
index 0000000..097b64e
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/world/LightProvider.java
@@ -0,0 +1,13 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.world;
+
+public interface LightProvider {
+ float getGlobalLighting(final int x, final int y, final int z);
+
+ float getLocalLighting(final int x, final int y, final int z);
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/world/WorldModifier.java b/ardor3d-craft/src/main/java/com/ardorcraft/world/WorldModifier.java
new file mode 100644
index 0000000..7e58be1
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/world/WorldModifier.java
@@ -0,0 +1,15 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.world;
+
+public interface WorldModifier {
+
+ void setBlock(final int x, final int y, final int z, final int data);
+
+ int getBlock(final int x, final int y, final int z);
+
+} \ No newline at end of file
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/world/WorldSettings.java b/ardor3d-craft/src/main/java/com/ardorcraft/world/WorldSettings.java
new file mode 100644
index 0000000..6f83e4a
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/world/WorldSettings.java
@@ -0,0 +1,227 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.world;
+
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.util.resource.ResourceSource;
+
+/**
+ * Settings class for setting up the BlockWorld object.
+ * <p>
+ * terrainTexture, terrainTextureTileSize, waterTexture and serverConnection has to be setup.
+ */
+public class WorldSettings {
+ private int tileSize = 16;
+ private int tileHeight = 128;
+ private int gridSize = 16;
+
+ private ResourceSource terrainTexture;
+ private int terrainTextureTileSize;
+ private ResourceSource waterTexture;
+
+ private boolean useVBO = true;
+ private boolean doDefaultTint = true;
+
+ private IServerConnection serverConnection;
+
+ public WorldSettings() {
+
+ }
+
+ /**
+ * Copy constructor for WorldSettings.
+ *
+ * @param settings
+ * WorldSettings to copy
+ */
+ public WorldSettings(final WorldSettings settings) {
+ tileSize = settings.tileSize;
+ tileHeight = settings.tileHeight;
+ gridSize = settings.gridSize;
+
+ terrainTexture = settings.terrainTexture;
+ terrainTextureTileSize = settings.terrainTextureTileSize;
+ waterTexture = settings.waterTexture;
+
+ useVBO = settings.useVBO;
+ doDefaultTint = settings.doDefaultTint;
+
+ serverConnection = settings.serverConnection;
+ }
+
+ /**
+ * Get chunk size in blocks.
+ *
+ * @return int
+ */
+ public int getTileSize() {
+ return tileSize;
+ }
+
+ /**
+ * Set size of chunks in blocks.
+ *
+ * @param tileSize
+ * int
+ */
+ public void setTileSize(final int tileSize) {
+ this.tileSize = MathUtils.nearestPowerOfTwo(tileSize);
+ }
+
+ /**
+ * Get height of chunks in blocks.
+ *
+ * @return int
+ */
+ public int getTileHeight() {
+ return tileHeight;
+ }
+
+ /**
+ * Set height of chunks in blocks.
+ *
+ * @param tileHeight
+ * int
+ */
+ public void setTileHeight(final int tileHeight) {
+ this.tileHeight = tileHeight;
+ }
+
+ /**
+ * Get length of world side, in chunks
+ *
+ * @return int
+ */
+ public int getGridSize() {
+ return gridSize;
+ }
+
+ /**
+ * Set length of world side, in chunks
+ *
+ * @param gridSize
+ * int
+ */
+ public void setGridSize(final int gridSize) {
+ this.gridSize = gridSize + gridSize % 2;
+ }
+
+ /**
+ * Get texture atlas used for terrain.
+ *
+ * @return ResourceSource
+ */
+ public ResourceSource getTerrainTexture() {
+ return terrainTexture;
+ }
+
+ /**
+ * Set texture atlas used for terrain.
+ *
+ * @param terrainTexture
+ * ResourceSource
+ */
+ public void setTerrainTexture(final ResourceSource terrainTexture) {
+ this.terrainTexture = terrainTexture;
+ }
+
+ /**
+ * Get size in pixels of each sub-texture tile in the texture atlas.
+ *
+ * @return int
+ */
+ public int getTerrainTextureTileSize() {
+ return terrainTextureTileSize;
+ }
+
+ /**
+ * Set size in pixels of each sub-texture tile in the texture atlas. (typically 16x16, 32x32 etc)
+ *
+ * @param terrainTextureTileSize
+ * int
+ */
+ public void setTerrainTextureTileSize(final int terrainTextureTileSize) {
+ this.terrainTextureTileSize = terrainTextureTileSize;
+ }
+
+ /**
+ * Get texture used for water.
+ *
+ * @return ResourceSource
+ */
+ public ResourceSource getWaterTexture() {
+ return waterTexture;
+ }
+
+ /**
+ * Set texture used for water. (current not used)
+ *
+ * @param waterTexture
+ * ResourceSource
+ */
+ public void setWaterTexture(final ResourceSource waterTexture) {
+ this.waterTexture = waterTexture;
+ }
+
+ /**
+ * Get server connection.
+ *
+ * @return IServerConnection
+ */
+ public IServerConnection getServerConnection() {
+ return serverConnection;
+ }
+
+ /**
+ * Set server connection.
+ *
+ * @param serverConnection
+ * IServerConnection
+ */
+ public void setServerConnection(final IServerConnection serverConnection) {
+ this.serverConnection = serverConnection;
+ }
+
+ /**
+ * Get if the world should use VBO (vertex buffer objects) for rendering.
+ *
+ * @return boolean
+ */
+ public boolean isUseVBO() {
+ return useVBO;
+ }
+
+ /**
+ * Set if the world should use VBO (vertex buffer objects) for rendering. Default is true.
+ *
+ * @param useVBO
+ * boolean
+ */
+ public void setUseVBO(final boolean useVBO) {
+ this.useVBO = useVBO;
+ }
+
+ /**
+ * Get if default minecraft texture tinting should be done. This will be turned into something completely
+ * configurable later on.
+ *
+ * @return boolean
+ */
+ public boolean isDoDefaultTint() {
+ return doDefaultTint;
+ }
+
+ /**
+ * Set if default minecraft texture tinting should be done. This will be turned into something completely
+ * configurable later on.
+ *
+ * @param doDefaultTint
+ */
+ public void setDoDefaultTint(final boolean doDefaultTint) {
+ this.doDefaultTint = doDefaultTint;
+ }
+}
diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/world/utils/ChunkDistanceComparator.java b/ardor3d-craft/src/main/java/com/ardorcraft/world/utils/ChunkDistanceComparator.java
new file mode 100644
index 0000000..9ea4de2
--- /dev/null
+++ b/ardor3d-craft/src/main/java/com/ardorcraft/world/utils/ChunkDistanceComparator.java
@@ -0,0 +1,32 @@
+/**
+ * Copyright (c) 2011-2012 Ardor Labs AB.
+ *
+ * This file is part of the ArdorCraft API, developed by Rikard Herlitz.
+ */
+
+package com.ardorcraft.world.utils;
+
+import java.util.Comparator;
+
+import com.ardorcraft.data.Pos;
+
+public class ChunkDistanceComparator implements Comparator<Pos> {
+ private final int X;
+ private final int Z;
+
+ public ChunkDistanceComparator(final int X, final int Z) {
+ this.X = X;
+ this.Z = Z;
+ }
+
+ @Override
+ public int compare(final Pos o1, final Pos o2) {
+ int xDist = X - o1.x;
+ int zDist = Z - o1.z;
+ final int l1 = xDist * xDist + zDist * zDist;
+ xDist = X - o2.x;
+ zDist = Z - o2.z;
+ final int l2 = xDist * xDist + zDist * zDist;
+ return l1 - l2;
+ }
+}
diff --git a/ardor3d-craft/world.acr b/ardor3d-craft/world.acr
new file mode 100644
index 0000000..ebac185
--- /dev/null
+++ b/ardor3d-craft/world.acr
Binary files differ
diff --git a/ardor3d-examples/build.gradle b/ardor3d-examples/build.gradle
index f07954b..7a6306c 100644
--- a/ardor3d-examples/build.gradle
+++ b/ardor3d-examples/build.gradle
@@ -12,4 +12,5 @@ dependencies {
compile project(':ardor3d-collada')
compile project(':ardor3d-terrain')
compile project(':ardor3d-ui')
+ compile project(':ardor3d-craft')
}
diff --git a/ardor3d-examples/pom.xml b/ardor3d-examples/pom.xml
index 62b3bf0..c9c25f1 100644
--- a/ardor3d-examples/pom.xml
+++ b/ardor3d-examples/pom.xml
@@ -162,6 +162,11 @@
<artifactId>ardor3d-ui</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>ardor3d-craft</artifactId>
+ <version>${project.version}</version>
+ </dependency>
</dependencies>
</project>
diff --git a/pom.xml b/pom.xml
index c2b3fba..70be635 100644
--- a/pom.xml
+++ b/pom.xml
@@ -63,6 +63,7 @@
<module>ardor3d-examples</module>
<module>ardor3d-distribution</module>
<module>ardor3d-performance</module>
+ <module>ardor3d-craft</module>
</modules>
<reporting>
diff --git a/settings.gradle b/settings.gradle
index 82cea1a..4b20feb 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -17,6 +17,7 @@ include ':ardor3d-terrain'
include ':ardor3d-examples'
include ':ardor3d-performance'
include ':distribution'
+include ':ardor3d-craft'
project(':ardor3d-savable').projectDir = "$rootDir/ardor3d-savable" as File
project(':ardor3d-math').projectDir = "$rootDir/ardor3d-math" as File
@@ -35,4 +36,5 @@ project(':ardor3d-ui').projectDir = "$rootDir/ardor3d-ui" as File
project(':ardor3d-terrain').projectDir = "$rootDir/ardor3d-terrain" as File
project(':ardor3d-examples').projectDir = "$rootDir/ardor3d-examples" as File
project(':ardor3d-performance').projectDir = "$rootDir/ardor3d-performance" as File
-project(':distribution').projectDir = "$rootDir/ardor3d-distribution" as File \ No newline at end of file
+project(':distribution').projectDir = "$rootDir/ardor3d-distribution" as File
+project(':ardor3d-craft').projectDir = "$rootDir/ardor3d-craft" as File \ No newline at end of file