|
|
|
package com.jme3.gde.nmgen;
|
|
|
|
|
|
|
|
import com.jme3.export.InputCapsule;
|
|
|
|
import com.jme3.export.JmeExporter;
|
|
|
|
import com.jme3.export.JmeImporter;
|
|
|
|
import com.jme3.export.OutputCapsule;
|
|
|
|
import com.jme3.export.Savable;
|
|
|
|
import com.jme3.math.FastMath;
|
|
|
|
import com.jme3.math.Vector3f;
|
|
|
|
import com.jme3.scene.Mesh;
|
|
|
|
import com.jme3.scene.Node;
|
|
|
|
import com.jme3.scene.VertexBuffer.Type;
|
|
|
|
import com.jme3.scene.mesh.IndexBuffer;
|
|
|
|
import com.jme3.terrain.Terrain;
|
|
|
|
import java.io.IOException;
|
|
|
|
import java.nio.FloatBuffer;
|
|
|
|
import org.critterai.nmgen.IntermediateData;
|
|
|
|
import org.critterai.nmgen.NavmeshGenerator;
|
|
|
|
import org.critterai.nmgen.TriangleMesh;
|
|
|
|
import org.openide.DialogDisplayer;
|
|
|
|
import org.openide.NotifyDescriptor;
|
|
|
|
|
|
|
|
public class NavMeshGenerator implements Savable {
|
|
|
|
|
|
|
|
private org.critterai.nmgen.NavmeshGenerator nmgen;
|
|
|
|
private float cellSize = 1f;
|
|
|
|
private float cellHeight = 1.5f;
|
|
|
|
private float minTraversableHeight = 7.5f;
|
|
|
|
private float maxTraversableStep = 1f;
|
|
|
|
private float maxTraversableSlope = 48.0f;
|
|
|
|
private boolean clipLedges = false;
|
|
|
|
private float traversableAreaBorderSize = 1.2f;
|
|
|
|
private int smoothingThreshold = 2;
|
|
|
|
private boolean useConservativeExpansion = true;
|
|
|
|
private int minUnconnectedRegionSize = 3;
|
|
|
|
private int mergeRegionSize = 10;
|
|
|
|
private float maxEdgeLength = 0;
|
|
|
|
private float edgeMaxDeviation = 2.4f;
|
|
|
|
private int maxVertsPerPoly = 6;
|
|
|
|
private float contourSampleDistance = 25;
|
|
|
|
private float contourMaxDeviation = 25;
|
|
|
|
private IntermediateData intermediateData;
|
|
|
|
private int timeout = 10000;
|
|
|
|
|
|
|
|
public NavMeshGenerator() {
|
|
|
|
}
|
|
|
|
|
|
|
|
public void printParams() {
|
|
|
|
System.out.println("Cell Size: " + cellSize);
|
|
|
|
System.out.println("Cell Height: " + cellHeight);
|
|
|
|
System.out.println("Min Trav. Height: " + minTraversableHeight);
|
|
|
|
System.out.println("Max Trav. Step: " + maxTraversableStep);
|
|
|
|
System.out.println("Max Trav. Slope: " + maxTraversableSlope);
|
|
|
|
System.out.println("Clip Ledges: " + clipLedges);
|
|
|
|
System.out.println("Trav. Area Border Size: " + traversableAreaBorderSize);
|
|
|
|
System.out.println("Smooth Thresh.: " + smoothingThreshold);
|
|
|
|
System.out.println("Use Cons. Expansion: " + useConservativeExpansion);
|
|
|
|
System.out.println("Min Unconn. Region Size: " + minUnconnectedRegionSize);
|
|
|
|
System.out.println("Merge Region Size: " + mergeRegionSize);
|
|
|
|
System.out.println("Max Edge Length: " + maxEdgeLength);
|
|
|
|
System.out.println("Edge Max Dev.: " + edgeMaxDeviation);
|
|
|
|
System.out.println("Max Verts/Poly: " + maxVertsPerPoly);
|
|
|
|
System.out.println("Contour Sample Dist: " + contourSampleDistance);
|
|
|
|
System.out.println("Contour Max Dev.: " + contourMaxDeviation);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setIntermediateData(IntermediateData data) {
|
|
|
|
this.intermediateData = data;
|
|
|
|
}
|
|
|
|
|
|
|
|
public Mesh optimize(Mesh mesh) {
|
|
|
|
nmgen = new NavmeshGenerator(cellSize, cellHeight, minTraversableHeight,
|
|
|
|
maxTraversableStep, maxTraversableSlope,
|
|
|
|
clipLedges, traversableAreaBorderSize,
|
|
|
|
smoothingThreshold, useConservativeExpansion,
|
|
|
|
minUnconnectedRegionSize, mergeRegionSize,
|
|
|
|
maxEdgeLength, edgeMaxDeviation, maxVertsPerPoly,
|
|
|
|
contourSampleDistance, contourMaxDeviation);
|
|
|
|
|
|
|
|
FloatBuffer pb = mesh.getFloatBuffer(Type.Position);
|
|
|
|
IndexBuffer ib = mesh.getIndexBuffer();
|
|
|
|
|
|
|
|
// copy positions to float array
|
|
|
|
float[] positions = new float[pb.capacity()];
|
|
|
|
pb.clear();
|
|
|
|
pb.get(positions);
|
|
|
|
|
|
|
|
// generate int array of indices
|
|
|
|
int[] indices = new int[ib.size()];
|
|
|
|
for (int i = 0; i < indices.length; i++) {
|
|
|
|
indices[i] = ib.get(i);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
TriangleMesh triMesh = buildNavMesh(positions, indices, intermediateData);
|
|
|
|
if (triMesh == null) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
int[] indices2 = triMesh.indices;
|
|
|
|
float[] positions2 = triMesh.vertices;
|
|
|
|
|
|
|
|
Mesh mesh2 = new Mesh();
|
|
|
|
mesh2.setBuffer(Type.Position, 3, positions2);
|
|
|
|
mesh2.setBuffer(Type.Index, 3, indices2);
|
|
|
|
mesh2.updateBound();
|
|
|
|
mesh2.updateCounts();
|
|
|
|
|
|
|
|
return mesh2;
|
|
|
|
}
|
|
|
|
|
|
|
|
private TriangleMesh buildNavMesh(float[] positions, int[] indices, IntermediateData intermediateData) {
|
|
|
|
MeshBuildRunnable runnable = new MeshBuildRunnable(positions, indices, intermediateData);
|
|
|
|
try {
|
|
|
|
execute(runnable, timeout);
|
|
|
|
} catch (TimeoutException ex) {
|
|
|
|
DialogDisplayer.getDefault().notifyLater(new NotifyDescriptor.Message("NavMesh Generation timed out."));
|
|
|
|
}
|
|
|
|
return runnable.getTriMesh();
|
|
|
|
}
|
|
|
|
|
|
|
|
private static void execute(Thread task, long timeout) throws TimeoutException {
|
|
|
|
task.start();
|
|
|
|
try {
|
|
|
|
task.join(timeout);
|
|
|
|
} catch (InterruptedException e) {
|
|
|
|
}
|
|
|
|
if (task.isAlive()) {
|
|
|
|
// task.interrupt();
|
|
|
|
task.stop();
|
|
|
|
throw new TimeoutException();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static void execute(Runnable task, long timeout) throws TimeoutException {
|
|
|
|
Thread t = new Thread(task, "Timeout guard");
|
|
|
|
t.setDaemon(true);
|
|
|
|
execute(t, timeout);
|
|
|
|
}
|
|
|
|
|
|
|
|
public Mesh terrain2mesh(Terrain terr) {
|
|
|
|
float[] heights = terr.getHeightMap();
|
|
|
|
int length = heights.length;
|
|
|
|
int side = (int) FastMath.sqrt(heights.length);
|
|
|
|
float[] vertices = new float[length * 3];
|
|
|
|
int[] indices = new int[(side - 1) * (side - 1) * 6];
|
|
|
|
|
|
|
|
Vector3f scale = ((Node) terr).getWorldScale().clone();
|
|
|
|
Vector3f trans = ((Node) terr).getWorldTranslation().clone();
|
|
|
|
trans.x -= terr.getTerrainSize() / 2f;
|
|
|
|
trans.z -= terr.getTerrainSize() / 2f;
|
|
|
|
float offsetX = trans.x * scale.x;
|
|
|
|
float offsetZ = trans.z * scale.z;
|
|
|
|
|
|
|
|
// do vertices
|
|
|
|
int i = 0;
|
|
|
|
for (int z = 0; z < side; z++) {
|
|
|
|
for (int x = 0; x < side; x++) {
|
|
|
|
vertices[i++] = x + offsetX;
|
|
|
|
vertices[i++] = heights[z * side + x] * scale.y;
|
|
|
|
vertices[i++] = z + offsetZ;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// do indexes
|
|
|
|
i = 0;
|
|
|
|
for (int z = 0; z < side - 1; z++) {
|
|
|
|
for (int x = 0; x < side - 1; x++) {
|
|
|
|
// triangle 1
|
|
|
|
indices[i++] = z * side + x;
|
|
|
|
indices[i++] = (z + 1) * side + x;
|
|
|
|
indices[i++] = (z + 1) * side + x + 1;
|
|
|
|
// triangle 2
|
|
|
|
indices[i++] = z * side + x;
|
|
|
|
indices[i++] = (z + 1) * side + x + 1;
|
|
|
|
indices[i++] = z * side + x + 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Mesh mesh2 = new Mesh();
|
|
|
|
mesh2.setBuffer(Type.Position, 3, vertices);
|
|
|
|
mesh2.setBuffer(Type.Index, 3, indices);
|
|
|
|
mesh2.updateBound();
|
|
|
|
mesh2.updateCounts();
|
|
|
|
|
|
|
|
return mesh2;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return The height resolution used when sampling the source mesh. Value must be > 0.
|
|
|
|
*/
|
|
|
|
public float getCellHeight() {
|
|
|
|
return cellHeight;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param cellHeight - The height resolution used when sampling the source mesh. Value must be > 0.
|
|
|
|
*/
|
|
|
|
public void setCellHeight(float cellHeight) {
|
|
|
|
this.cellHeight = cellHeight;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return The width and depth resolution used when sampling the the source mesh.
|
|
|
|
*/
|
|
|
|
public float getCellSize() {
|
|
|
|
return cellSize;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param cellSize - The width and depth resolution used when sampling the the source mesh.
|
|
|
|
*/
|
|
|
|
public void setCellSize(float cellSize) {
|
|
|
|
this.cellSize = cellSize;
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean isClipLedges() {
|
|
|
|
return clipLedges;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setClipLedges(boolean clipLedges) {
|
|
|
|
this.clipLedges = clipLedges;
|
|
|
|
}
|
|
|
|
|
|
|
|
public float getContourMaxDeviation() {
|
|
|
|
return contourMaxDeviation;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setContourMaxDeviation(float contourMaxDeviation) {
|
|
|
|
this.contourMaxDeviation = contourMaxDeviation;
|
|
|
|
}
|
|
|
|
|
|
|
|
public float getContourSampleDistance() {
|
|
|
|
return contourSampleDistance;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setContourSampleDistance(float contourSampleDistance) {
|
|
|
|
this.contourSampleDistance = contourSampleDistance;
|
|
|
|
}
|
|
|
|
|
|
|
|
public float getEdgeMaxDeviation() {
|
|
|
|
return edgeMaxDeviation;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setEdgeMaxDeviation(float edgeMaxDeviation) {
|
|
|
|
this.edgeMaxDeviation = edgeMaxDeviation;
|
|
|
|
}
|
|
|
|
|
|
|
|
public float getMaxEdgeLength() {
|
|
|
|
return maxEdgeLength;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setMaxEdgeLength(float maxEdgeLength) {
|
|
|
|
this.maxEdgeLength = maxEdgeLength;
|
|
|
|
}
|
|
|
|
|
|
|
|
public float getMaxTraversableSlope() {
|
|
|
|
return maxTraversableSlope;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setMaxTraversableSlope(float maxTraversableSlope) {
|
|
|
|
this.maxTraversableSlope = maxTraversableSlope;
|
|
|
|
}
|
|
|
|
|
|
|
|
public float getMaxTraversableStep() {
|
|
|
|
return maxTraversableStep;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setMaxTraversableStep(float maxTraversableStep) {
|
|
|
|
this.maxTraversableStep = maxTraversableStep;
|
|
|
|
}
|
|
|
|
|
|
|
|
public int getMaxVertsPerPoly() {
|
|
|
|
return maxVertsPerPoly;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setMaxVertsPerPoly(int maxVertsPerPoly) {
|
|
|
|
this.maxVertsPerPoly = maxVertsPerPoly;
|
|
|
|
}
|
|
|
|
|
|
|
|
public int getMergeRegionSize() {
|
|
|
|
return mergeRegionSize;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setMergeRegionSize(int mergeRegionSize) {
|
|
|
|
this.mergeRegionSize = mergeRegionSize;
|
|
|
|
}
|
|
|
|
|
|
|
|
public float getMinTraversableHeight() {
|
|
|
|
return minTraversableHeight;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setMinTraversableHeight(float minTraversableHeight) {
|
|
|
|
this.minTraversableHeight = minTraversableHeight;
|
|
|
|
}
|
|
|
|
|
|
|
|
public int getMinUnconnectedRegionSize() {
|
|
|
|
return minUnconnectedRegionSize;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setMinUnconnectedRegionSize(int minUnconnectedRegionSize) {
|
|
|
|
this.minUnconnectedRegionSize = minUnconnectedRegionSize;
|
|
|
|
}
|
|
|
|
|
|
|
|
public NavmeshGenerator getNmgen() {
|
|
|
|
return nmgen;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setNmgen(NavmeshGenerator nmgen) {
|
|
|
|
this.nmgen = nmgen;
|
|
|
|
}
|
|
|
|
|
|
|
|
public int getSmoothingThreshold() {
|
|
|
|
return smoothingThreshold;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setSmoothingThreshold(int smoothingThreshold) {
|
|
|
|
this.smoothingThreshold = smoothingThreshold;
|
|
|
|
}
|
|
|
|
|
|
|
|
public float getTraversableAreaBorderSize() {
|
|
|
|
return traversableAreaBorderSize;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setTraversableAreaBorderSize(float traversableAreaBorderSize) {
|
|
|
|
this.traversableAreaBorderSize = traversableAreaBorderSize;
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean isUseConservativeExpansion() {
|
|
|
|
return useConservativeExpansion;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setUseConservativeExpansion(boolean useConservativeExpansion) {
|
|
|
|
this.useConservativeExpansion = useConservativeExpansion;
|
|
|
|
}
|
|
|
|
|
|
|
|
public int getTimeout() {
|
|
|
|
return timeout;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setTimeout(int timeout) {
|
|
|
|
this.timeout = timeout;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void write(JmeExporter ex) throws IOException {
|
|
|
|
OutputCapsule oc = ex.getCapsule(this);
|
|
|
|
oc.write(cellSize, "cellSize", 1f);
|
|
|
|
oc.write(cellHeight, "cellHeight", 1.5f);
|
|
|
|
oc.write(minTraversableHeight, "minTraversableHeight", 7.5f);
|
|
|
|
oc.write(maxTraversableStep, "maxTraversableStep", 1f);
|
|
|
|
oc.write(maxTraversableSlope, "maxTraversableSlope", 48f);
|
|
|
|
oc.write(clipLedges, "clipLedges", false);
|
|
|
|
oc.write(traversableAreaBorderSize, "traversableAreaBorderSize", 1.2f);
|
|
|
|
oc.write(smoothingThreshold, "smoothingThreshold", 2);
|
|
|
|
oc.write(useConservativeExpansion, "useConservativeExpansion", true);
|
|
|
|
oc.write(minUnconnectedRegionSize, "minUnconnectedRegionSize", 3);
|
|
|
|
oc.write(mergeRegionSize, "mergeRegionSize", 10);
|
|
|
|
oc.write(maxEdgeLength, "maxEdgeLength", 0);
|
|
|
|
oc.write(edgeMaxDeviation, "edgeMaxDeviation", 2.4f);
|
|
|
|
oc.write(maxVertsPerPoly, "maxVertsPerPoly", 6);
|
|
|
|
oc.write(contourSampleDistance, "contourSampleDistance", 25);
|
|
|
|
oc.write(contourMaxDeviation, "contourMaxDeviation", 25);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void read(JmeImporter im) throws IOException {
|
|
|
|
InputCapsule ic = im.getCapsule(this);
|
|
|
|
cellSize = ic.readFloat("cellSize", 1f);
|
|
|
|
cellHeight = ic.readFloat("cellHeight", 1.5f);
|
|
|
|
minTraversableHeight = ic.readFloat("minTraversableHeight", 7.5f);
|
|
|
|
maxTraversableStep = ic.readFloat("maxTraversableStep", 1f);
|
|
|
|
maxTraversableSlope = ic.readFloat("maxTraversableSlope", 48f);
|
|
|
|
clipLedges = ic.readBoolean("clipLedges", false);
|
|
|
|
traversableAreaBorderSize = ic.readFloat("traversableAreaBorderSize", 1.2f);
|
|
|
|
smoothingThreshold = (int) ic.readFloat("smoothingThreshold", 2);
|
|
|
|
useConservativeExpansion = ic.readBoolean("useConservativeExpansion", true);
|
|
|
|
minUnconnectedRegionSize = (int) ic.readFloat("minUnconnectedRegionSize", 3);
|
|
|
|
mergeRegionSize = (int) ic.readFloat("mergeRegionSize", 10);
|
|
|
|
maxEdgeLength = ic.readFloat("maxEdgeLength", 0);
|
|
|
|
edgeMaxDeviation = ic.readFloat("edgeMaxDeviation", 2.4f);
|
|
|
|
maxVertsPerPoly = (int) ic.readFloat("maxVertsPerPoly", 6);
|
|
|
|
contourSampleDistance = ic.readFloat("contourSampleDistance", 25);
|
|
|
|
contourMaxDeviation = ic.readFloat("contourMaxDeviation", 25);
|
|
|
|
}
|
|
|
|
|
|
|
|
private class MeshBuildRunnable implements Runnable {
|
|
|
|
|
|
|
|
private float[] positions;
|
|
|
|
private int[] indices;
|
|
|
|
private IntermediateData intermediateData;
|
|
|
|
private TriangleMesh triMesh;
|
|
|
|
|
|
|
|
public MeshBuildRunnable(float[] positions, int[] indices, IntermediateData intermediateData) {
|
|
|
|
this.positions = positions;
|
|
|
|
this.indices = indices;
|
|
|
|
this.intermediateData = intermediateData;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void run() {
|
|
|
|
triMesh = nmgen.build(positions, indices, intermediateData);
|
|
|
|
}
|
|
|
|
|
|
|
|
public TriangleMesh getTriMesh() {
|
|
|
|
return triMesh;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static class TimeoutException extends Exception {
|
|
|
|
|
|
|
|
/** Create an instance */
|
|
|
|
public TimeoutException() {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|