我正在寻找创建一个Terrain编辑器。我有一个地形类,我用它来制作地形块,它可以相互加载。我还有一个高度图类来加载地形的高度图,而不是现在至少。最重要的是我有鼠标框选择器。
因为地形是一个网格,而我的鼠标框选择器只适用于一个网格,它选择整个地形而不是一个图块。有什么方法可以解决这个问题吗?
这是Terrain Class
package org.lwjglb.engine.items;
import de.matthiasmann.twl.utils.PNGDecoder;
import java.nio.ByteBuffer;
import org.joml.Vector3f;
import org.lwjglb.engine.graph.HeightMapMesh;
public class Terrain {
private final GameItem[] gameItems;
private final int terrainSize;
private final int verticesPerCol;
private final int verticesPerRow;
private final HeightMapMesh heightMapMesh;
/**
* It will hold the bounding box for each terrain block
*/
private final Box2D[][] boundingBoxes;
/**
* A Terrain is composed by blocks, each block is a GameItem constructed
* from a HeightMap.
*
* @param terrainSize The number of blocks will be terrainSize * terrainSize
* @param scale The scale to be applied to each terrain block
* @param minY The minimum y value, before scaling, of each terrain block
* @param maxY The maximum y value, before scaling, of each terrain block
* @param heightMapFile
* @param textureFile
* @param textInc
* @throws Exception
*/
public Terrain(int terrainSize, float scale, float minY, float maxY, String heightMapFile, String textureFile, int textInc) throws Exception {
this.terrainSize = terrainSize;
gameItems = new GameItem[terrainSize * terrainSize];
PNGDecoder decoder = new PNGDecoder(getClass().getResourceAsStream(heightMapFile));
int height = decoder.getHeight();
int width = decoder.getWidth();
ByteBuffer buf = ByteBuffer.allocateDirect(
4 * decoder.getWidth() * decoder.getHeight());
decoder.decode(buf, decoder.getWidth() * 4, PNGDecoder.Format.RGBA);
buf.flip();
// The number of vertices per column and row
verticesPerCol = width - 1;
verticesPerRow = height - 1;
heightMapMesh = new HeightMapMesh(minY, maxY, buf, width, height, textureFile, textInc);
boundingBoxes = new Box2D[terrainSize][terrainSize];
for (int row = 0; row < terrainSize; row++) {
for (int col = 0; col < terrainSize; col++) {
float xDisplacement = (col - ((float) terrainSize - 1) / (float) 2) * scale * HeightMapMesh.getXLength();
float zDisplacement = (row - ((float) terrainSize - 1) / (float) 2) * scale * HeightMapMesh.getZLength();
GameItem terrainBlock = new GameItem(heightMapMesh.getMesh());
terrainBlock.setScale(scale);
terrainBlock.setPosition(xDisplacement, 0, zDisplacement);
gameItems[row * terrainSize + col] = terrainBlock;
boundingBoxes[row][col] = getBoundingBox(terrainBlock);
}
}
}
public float getHeight(Vector3f position) {
float result = Float.MIN_VALUE;
// For each terrain block we get the bounding box, translate it to view coodinates
// and check if the position is contained in that bounding box
Box2D boundingBox = null;
boolean found = false;
GameItem terrainBlock = null;
for (int row = 0; row < terrainSize && !found; row++) {
for (int col = 0; col < terrainSize && !found; col++) {
terrainBlock = gameItems[row * terrainSize + col];
boundingBox = boundingBoxes[row][col];
found = boundingBox.contains(position.x, position.z);
//System.out.println(terrainBlock.getMeshes().length);
}
}
// If we have found a terrain block that contains the position we need
// to calculate the height of the terrain on that position
if (found) {
Vector3f[] triangle = getTriangle(position, boundingBox, terrainBlock);
result = interpolateHeight(triangle[0], triangle[1], triangle[2], position.x, position.z);
}
return result;
}
protected Vector3f[] getTriangle(Vector3f position, Box2D boundingBox, GameItem terrainBlock) {
// Get the column and row of the heightmap associated to the current position
float cellWidth = boundingBox.width / (float) verticesPerCol;
float cellHeight = boundingBox.height / (float) verticesPerRow;
int col = (int) ((position.x - boundingBox.x) / cellWidth);
int row = (int) ((position.z - boundingBox.y) / cellHeight);
Vector3f[] triangle = new Vector3f[3];
triangle[1] = new Vector3f(
boundingBox.x + col * cellWidth,
getWorldHeight(row + 1, col, terrainBlock),
boundingBox.y + (row + 1) * cellHeight);
triangle[2] = new Vector3f(
boundingBox.x + (col + 1) * cellWidth,
getWorldHeight(row, col + 1, terrainBlock),
boundingBox.y + row * cellHeight);
if (position.z < getDiagonalZCoord(triangle[1].x, triangle[1].z, triangle[2].x, triangle[2].z, position.x)) {
triangle[0] = new Vector3f(
boundingBox.x + col * cellWidth,
getWorldHeight(row, col, terrainBlock),
boundingBox.y + row * cellHeight);
} else {
triangle[0] = new Vector3f(
boundingBox.x + (col + 1) * cellWidth,
getWorldHeight(row + 2, col + 1, terrainBlock),
boundingBox.y + (row + 1) * cellHeight);
}
return triangle;
}
protected float getDiagonalZCoord(float x1, float z1, float x2, float z2, float x) {
float z = ((z1 - z2) / (x1 - x2)) * (x - x1) + z1;
return z;
}
protected float getWorldHeight(int row, int col, GameItem gameItem) {
float y = heightMapMesh.getHeight(row, col);
return y * gameItem.getScale() + gameItem.getPosition().y;
}
protected float interpolateHeight(Vector3f pA, Vector3f pB, Vector3f pC, float x, float z) {
// Plane equation ax+by+cz+d=0
float a = (pB.y - pA.y) * (pC.z - pA.z) - (pC.y - pA.y) * (pB.z - pA.z);
float b = (pB.z - pA.z) * (pC.x - pA.x) - (pC.z - pA.z) * (pB.x - pA.x);
float c = (pB.x - pA.x) * (pC.y - pA.y) - (pC.x - pA.x) * (pB.y - pA.y);
float d = -(a * pA.x + b * pA.y + c * pA.z);
// y = (-d -ax -cz) / b
float y = (-d - a * x - c * z) / b;
return y;
}
/**
* Gets the bounding box of a terrain block
*
* @param terrainBlock A GameItem instance that defines the terrain block
* @return The boundingg box of the terrain block
*/
private Box2D getBoundingBox(GameItem terrainBlock) {
float scale = terrainBlock.getScale();
Vector3f position = terrainBlock.getPosition();
float topLeftX = HeightMapMesh.STARTX * scale + position.x;
float topLeftZ = HeightMapMesh.STARTZ * scale + position.z;
float width = Math.abs(HeightMapMesh.STARTX * 2) * scale;
float height = Math.abs(HeightMapMesh.STARTZ * 2) * scale;
Box2D boundingBox = new Box2D(topLeftX, topLeftZ, width, height);
return boundingBox;
}
public GameItem[] getGameItems() {
return gameItems;
}
static class Box2D {
public float x;
public float y;
public float width;
public float height;
public Box2D(float x, float y, float width, float height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
public boolean contains(float x2, float y2) {
return x2 >= x
&& y2 >= y
&& x2 < x + width
&& y2 < y + height;
}
}
}
这是HeightMap文件
package org.lwjglb.engine.graph;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import org.joml.Vector3f;
import org.lwjglb.engine.Utils;
public class HeightMapMesh {
private static final int MAX_COLOUR = 255 * 255 * 255;
public static final float STARTX = -0.5f;
public static final float STARTZ = -0.5f;
private final float minY;
private final float maxY;
private final Mesh mesh;
private final float[][] heightArray;
public HeightMapMesh(float minY, float maxY, ByteBuffer heightMapImage, int width, int height, String textureFile, int textInc) throws Exception {
this.minY = minY;
this.maxY = maxY;
heightArray = new float[height][width];
Texture texture = new Texture(textureFile);
float incx = getXLength() / (width - 1);
float incz = getZLength() / (height - 1);
List<Float> positions = new ArrayList();
List<Float> textCoords = new ArrayList();
List<Integer> indices = new ArrayList();
int tiles = 0;
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
// Create vertex for current position
positions.add(STARTX + col * incx); // x
float currentHeight = getHeight(col, row, width, heightMapImage);
heightArray[row][col] = currentHeight;
positions.add(currentHeight); //y
positions.add(STARTZ + row * incz); //z
// Set texture coordinates
textCoords.add((float) textInc * (float) col / (float) width);
textCoords.add((float) textInc * (float) row / (float) height);
// Create indices
if (col < width - 1 && row < height - 1) {
int leftTop = row * width + col;
int leftBottom = (row + 1) * width + col;
int rightBottom = (row + 1) * width + col + 1;
int rightTop = row * width + col + 1;
indices.add(leftTop);
indices.add(leftBottom);
indices.add(rightTop);
indices.add(rightTop);
indices.add(leftBottom);
indices.add(rightBottom);
}
tiles++;
}
}
float[] posArr = Utils.listToArray(positions);
int[] indicesArr = indices.stream().mapToInt(i -> i).toArray();
float[] textCoordsArr = Utils.listToArray(textCoords);
float[] normalsArr = calcNormals(posArr, width, height);
this.mesh = new Mesh(posArr, textCoordsArr, normalsArr, indicesArr);
Material material = new Material(texture, 0.0f);
mesh.setMaterial(material);
System.out.println(tiles + " Tiles created");
}
public Mesh getMesh() {
return mesh;
}
public float getHeight(int row, int col) {
float result = 0;
if ( row >= 0 && row < heightArray.length ) {
if ( col >= 0 && col < heightArray[row].length ) {
result = heightArray[row][col];
}
}
return result;
}
public static float getXLength() {
return Math.abs(-STARTX*2);
}
public static float getZLength() {
return Math.abs(-STARTZ*2);
}
private float[] calcNormals(float[] posArr, int width, int height) {
Vector3f v0 = new Vector3f();
Vector3f v1 = new Vector3f();
Vector3f v2 = new Vector3f();
Vector3f v3 = new Vector3f();
Vector3f v4 = new Vector3f();
Vector3f v12 = new Vector3f();
Vector3f v23 = new Vector3f();
Vector3f v34 = new Vector3f();
Vector3f v41 = new Vector3f();
List<Float> normals = new ArrayList<>();
Vector3f normal = new Vector3f();
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
if (row > 0 && row < height -1 && col > 0 && col < width -1) {
int i0 = row*width*3 + col*3;
v0.x = posArr[i0];
v0.y = posArr[i0 + 1];
v0.z = posArr[i0 + 2];
int i1 = row*width*3 + (col-1)*3;
v1.x = posArr[i1];
v1.y = posArr[i1 + 1];
v1.z = posArr[i1 + 2];
v1 = v1.sub(v0);
int i2 = (row+1)*width*3 + col*3;
v2.x = posArr[i2];
v2.y = posArr[i2 + 1];
v2.z = posArr[i2 + 2];
v2 = v2.sub(v0);
int i3 = (row)*width*3 + (col+1)*3;
v3.x = posArr[i3];
v3.y = posArr[i3 + 1];
v3.z = posArr[i3 + 2];
v3 = v3.sub(v0);
int i4 = (row-1)*width*3 + col*3;
v4.x = posArr[i4];
v4.y = posArr[i4 + 1];
v4.z = posArr[i4 + 2];
v4 = v4.sub(v0);
v1.cross(v2, v12);
v12.normalize();
v2.cross(v3, v23);
v23.normalize();
v3.cross(v4, v34);
v34.normalize();
v4.cross(v1, v41);
v41.normalize();
normal = v12.add(v23).add(v34).add(v41);
normal.normalize();
} else {
normal.x = 0;
normal.y = 1;
normal.z = 0;
}
normal.normalize();
normals.add(normal.x);
normals.add(normal.y);
normals.add(normal.z);
}
}
return Utils.listToArray(normals);
}
private float getHeight(int x, int z, int width, ByteBuffer buffer) {
int argb = getRGB(x, z, width, buffer);
return this.minY + Math.abs(this.maxY - this.minY) * ((float) argb / (float) MAX_COLOUR);
}
public static int getRGB(int x, int z, int width, ByteBuffer buffer) {
byte r = buffer.get(x * 4 + 0 + z * 4 * width);
byte g = buffer.get(x * 4 + 1 + z * 4 * width);
byte b = buffer.get(x * 4 + 2 + z * 4 * width);
byte a = buffer.get(x * 4 + 3 + z * 4 * width);
int argb = ((0xFF & a) << 24) | ((0xFF & r) << 16)
| ((0xFF & g) << 8) | (0xFF & b);
return argb;
}
}
鼠标选择器
package org.lwjglb.game;
import org.joml.Intersectionf;
import org.joml.Matrix4f;
import org.joml.Vector2d;
import org.joml.Vector2f;
import org.joml.Vector3f;
import org.joml.Vector4f;
import org.lwjglb.engine.Window;
import org.lwjglb.engine.graph.Camera;
import org.lwjglb.engine.items.GameItem;
public class MouseBoxSelectionDetector extends CameraBoxSelectionDetector {
private final Matrix4f invProjectionMatrix;
private final Matrix4f invViewMatrix;
private final Vector3f mouseDir;
private final Vector4f tmpVec;
public MouseBoxSelectionDetector() {
super();
invProjectionMatrix = new Matrix4f();
invViewMatrix = new Matrix4f();
mouseDir = new Vector3f();
tmpVec = new Vector4f();
}
public boolean selectGameItem(GameItem[] gameItems, Window window, Vector2d mousePos, Camera camera) {
// Transform mouse coordinates into normalized spaze [-1, 1]
int wdwWitdh = window.getWidth();
int wdwHeight = window.getHeight();
float x = (float)(2 * mousePos.x) / (float)wdwWitdh - 1.0f;
float y = 1.0f - (float)(2 * mousePos.y) / (float)wdwHeight;
float z = -1.0f;
invProjectionMatrix.set(window.getProjectionMatrix());
invProjectionMatrix.invert();
tmpVec.set(x, y, z, 1.0f);
tmpVec.mul(invProjectionMatrix);
tmpVec.z = -1.0f;
tmpVec.w = 0.0f;
Matrix4f viewMatrix = camera.getViewMatrix();
invViewMatrix.set(viewMatrix);
invViewMatrix.invert();
tmpVec.mul(invViewMatrix);
mouseDir.set(tmpVec.x, tmpVec.y, tmpVec.z);
return selectGameItem(gameItems, camera.getPosition(), mouseDir);
}
}
以防万一你需要它
package org.lwjglb.game;
import java.awt.geom.Point2D;
import org.joml.Intersectionf;
import org.joml.Vector2f;
import org.joml.Vector3f;
import org.lwjglb.engine.graph.Camera;
import org.lwjglb.engine.items.GameItem;
public class CameraBoxSelectionDetector {
private final Vector3f max;
private final Vector3f min;
private final Vector2f nearFar;
private Vector3f dir;
private double brushSize = 1.0;
private double maxBrushSize = 10.0;
private int maxSelected = 500;
public CameraBoxSelectionDetector() {
dir = new Vector3f();
min = new Vector3f();
max = new Vector3f();
nearFar = new Vector2f();
}
public void selectGameItem(GameItem[] gameItems, Camera camera) {
dir = camera.getViewMatrix().positiveZ(dir).negate();
selectGameItem(gameItems, camera.getPosition(), dir);
}
protected boolean selectGameItem(GameItem[] gameItems, Vector3f center, Vector3f dir) {
boolean selected = false;
GameItem selectedGameItem[] = new GameItem[maxSelected]; //Amount to select
float closestDistance = Float.POSITIVE_INFINITY;
outterloop:
for (GameItem gameItem : gameItems) {
gameItem.setSelected(false);
min.set(gameItem.getPosition());
max.set(gameItem.getPosition());
min.add(-gameItem.getScale(), -gameItem.getScale(), -gameItem.getScale());
max.add(gameItem.getScale(), gameItem.getScale(), gameItem.getScale());
if (Intersectionf.intersectRayAab(center, dir, min, max, nearFar) && nearFar.x < closestDistance) {
closestDistance = nearFar.x;
selectedGameItem[0] = gameItem;
}
else if (selectedGameItem[0] != null && selectedGameItem[1] == null) {
setCircleSelect(gameItems, selectedGameItem);
}
}
for(int i = 0; i < selectedGameItem.length; i++) {
if (selectedGameItem[i] != null) {
//System.out.println("SELECTED: " + i);
selectedGameItem[i].setSelected(true);
selectedGameItem[i] = null;
selected = true;
}
}
return selected;
}
private void setCircleSelect(GameItem[] gameItems, GameItem[] selectedGameItem) {
for(GameItem gameItem : gameItems) {
//double distance = Math.sqrt(Math.pow((gameItem.getPosition().x - selectedGameItem[0].getPosition().x), 2) + Math.pow((gameItem.getPosition().y - selectedGameItem[0].getPosition().y), 2));
double x1 = selectedGameItem[0].getPosition().x;
double z1 = selectedGameItem[0].getPosition().z;
double x2 = gameItem.getPosition().x;
double z2 = gameItem.getPosition().z;
double distance = Point2D.distance(x1, z1, x2, z2);
//System.out.println(distance);
if(distance <= brushSize) {
//Poop
//System.out.println("GOT X:" + gameItem.getPosition().x + " Y:" + gameItem.getPosition().y + "Z:" + gameItem.getPosition().z);
for(int i = 0; i < selectedGameItem.length; i++) {
if (selectedGameItem[i] == null) {
//System.out.println("i: " + i);
selectedGameItem[i] = gameItem;
break;
}
}
}
}
}
public void setBrushSize(double brushSize) {
this.brushSize = brushSize;
}
public double getBrushSize() {
return brushSize;
}
public double getMaxBrushSize() {
return maxBrushSize;
}
}
感谢您的帮助!这让我发疯了。 只是一个额外的问题,在我开始工作之后,有人可以指出我正确的方向,如何将其添加到另一个Java opengl程序中的自定义文件中。