Skip to content

Instantly share code, notes, and snippets.

@devent
Created August 23, 2020 15:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save devent/65f08bbf06f97217a3ad11fe70d7cd22 to your computer and use it in GitHub Desktop.
Save devent/65f08bbf06f97217a3ad11fe70d7cd22 to your computer and use it in GitHub Desktop.
/**
* Copyright © 2020 Erwin Müller (erwin@muellerpublic.de)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.anrisoftware.dwarfhustle.gamemainmap.terrain.states;
import static java.lang.String.format;
import static org.apache.commons.math3.util.FastMath.round;
import java.nio.FloatBuffer;
import java.util.List;
import org.eclipse.collections.api.list.primitive.MutableIntList;
import org.eclipse.collections.api.map.primitive.MutableObjectIntMap;
import org.eclipse.collections.impl.factory.Lists;
import org.eclipse.collections.impl.factory.primitive.IntLists;
import org.eclipse.collections.impl.factory.primitive.ObjectIntMaps;
import org.eclipse.collections.impl.map.mutable.primitive.ObjectIntHashMap;
import com.jme3.bounding.BoundingBox;
import com.jme3.collision.CollisionResults;
import com.jme3.math.Ray;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.util.BufferUtils;
import lombok.AllArgsConstructor;
import lombok.Data;
public class TerrainBox {
@Data
@AllArgsConstructor
private static class QuadVertex {
Vector3f ver;
Vector3f nor;
Vector2f tex;
int x;
int y;
}
private final int size;
private final List<QuadVertex> quads;
private final MutableIntList inds;
private final Geometry geometry;
private final Mesh mesh;
private final Vector3f pos;
private final int[] indices = new int[30];
private final int x;
private final int y;
private final BoundingBox boundingBox;
private TerrainBox a;
private TerrainBox b;
private TerrainBox c;
private TerrainBox d;
private FloatBuffer verbuff;
public TerrainBox(int size) {
this(new Geometry(), new Mesh(), size, Lists.mutable.ofInitialCapacity(calcSize(size)),
IntLists.mutable.empty(), new Vector3f(-size / 2 + 1, 0, -size / 2 + 1),
new BoundingBox(new Vector3f(1, 0, 1), size / 2f, size / 2f, 1f), 0, 0);
}
private static int calcSize(int size) {
return size * size + 2 * size + 1;
}
public TerrainBox(Geometry geometry, Mesh mesh, int size, List<QuadVertex> quads, MutableIntList inds, Vector3f pos,
BoundingBox boundingBox, int x, int y) {
this.geometry = geometry;
this.mesh = mesh;
this.size = size;
this.quads = quads;
this.inds = inds;
this.pos = pos;
this.boundingBox = boundingBox;
this.x = x;
this.y = y;
}
public TerrainBox build() {
buildQuads(null, size / 2 - 1, 1f / size);
buildMesh();
return this;
}
public TerrainBox collideWith(Ray other, CollisionResults results) {
if (isLeaf()) {
return this;
}
if (a.boundingBox.collideWith(other, results) != 0) {
return a.collideWith(other, results);
}
if (b.boundingBox.collideWith(other, results) != 0) {
return b.collideWith(other, results);
}
if (c.boundingBox.collideWith(other, results) != 0) {
return c.collideWith(other, results);
}
if (d.boundingBox.collideWith(other, results) != 0) {
return d.collideWith(other, results);
}
return null;
}
public void setHeights(int[] xs, int[] ys, float height) {
for (int i = 0; i < xs.length; i++) {
var q = getPatch(xs[i] - (size / 2 - 1), ys[i] - (size / 2 - 1));
setHeight(q.indices, height);
}
}
public void setHeight(int x, int y, float height) {
var q = getPatch(x - (size / 2 - 1), y - (size / 2 - 1));
setHeight(q.indices, height);
}
public void setHeights(float[][] heights) {
for (int x = 0; x < heights.length; x++) {
for (int y = 0; y < heights[x].length; y++) {
var q = getPatch(x - (size / 2 - 1), y - (size / 2 - 1));
setHeight(q.indices, heights[x][y]);
}
}
}
public void setHeight(int[] indices, float height) {
for (int i = 0; i < indices.length; i++) {
int ix = indices[i];
var q = quads.get(indices[i]);
float y = q.ver.y + height;
verbuff.put(ix * 3 + 1, y);
}
mesh.getBuffer(Type.Position).setUpdateNeeded();
}
public boolean isLeaf() {
return a == null && b == null && c == null && d == null;
}
public Mesh getMesh() {
return mesh;
}
public Geometry getGeometry() {
return geometry;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public TerrainBox getPatch(float x, float z) {
int h = size / 2;
if (h == 0) {
return this;
}
if (a.pos.x <= x && x < (h + a.pos.x) && a.pos.z <= z && z < (h + a.pos.z)) {
return a.getPatch(x, z);
}
if (b.pos.x <= x && x < (h + b.pos.x) && b.pos.z <= z && z < (h + b.pos.z)) {
return b.getPatch(x, z);
}
if (c.pos.x <= x && x < (h + c.pos.x) && c.pos.z <= z && z < (h + c.pos.z)) {
return c.getPatch(x, z);
}
if (d.pos.x <= x && x < (h + d.pos.x) && d.pos.z <= z && z < (h + d.pos.z)) {
return d.getPatch(x, z);
}
return null;
}
@Override
public String toString() {
if (isLeaf()) {
return format("%s[x=%d,y=%d,p=%s,b=%s]", getClass().getSimpleName(), x, y, pos, boundingBox);
}
return format("%s[%s,x=%d,y=%d,p=%s,b=%s,a=%s,b=%s,c=%s,d=%s]", getClass().getSimpleName(), geometry, x, y, pos,
boundingBox, a, b, c, d);
}
private void buildMesh() {
Vector3f[] v = new Vector3f[quads.size()];
Vector2f[] t = new Vector2f[quads.size()];
Vector3f[] n = new Vector3f[quads.size()];
for (int i = 0; i < quads.size(); i++) {
v[i] = quads.get(i).ver;
t[i] = quads.get(i).tex;
n[i] = quads.get(i).nor;
}
int[] i = inds.toArray();
verbuff = BufferUtils.createFloatBuffer(v);
mesh.setBuffer(Type.Position, 3, verbuff);
mesh.setBuffer(Type.Normal, 3, BufferUtils.createFloatBuffer(n));
mesh.setBuffer(Type.TexCoord, 2, BufferUtils.createFloatBuffer(t));
mesh.setBuffer(Type.Index, 1, BufferUtils.createIntBuffer(i));
mesh.updateBound();
geometry.setMesh(mesh);
}
private TerrainBox buildQuads(QuadVertex quad, int sizeh, float texf) {
if (size > 1) {
float h = size / 2f;
float h2 = size / 4f;
var posa = pos.add(0, 0, 0);
a = new TerrainBox(geometry, mesh, size / 2, quads, inds, posa,
new BoundingBox(pos.add(h2, 0, h2), h2, 0, h2), round(sizeh + posa.x), round(sizeh + posa.z))
.buildQuads(null, sizeh, texf);
var posb = pos.add(h, 0, 0);
b = new TerrainBox(geometry, mesh, size / 2, quads, inds, posb,
new BoundingBox(pos.add(h2 + h2 + h2, 0, h2), h2, 0, h2), round(sizeh + posb.x),
round(sizeh + posb.z)).buildQuads(null, sizeh, texf);
Vector3f posc = pos.add(0, 0, h);
c = new TerrainBox(geometry, mesh, size / 2, quads, inds, posc,
new BoundingBox(pos.add(h2, 0, h2 + h2 + h2), h2, 0, h2), round(sizeh + posc.x),
round(sizeh + posc.z)).buildQuads(null, sizeh, texf);
Vector3f posd = pos.add(h, 0, h);
d = new TerrainBox(geometry, mesh, size / 2, quads, inds, posd,
new BoundingBox(pos.add(h2 + h2 + h2, 0, h2 + h2 + h2), h2, 0, h2), round(sizeh + posd.x),
round(sizeh + posd.z)).buildQuads(null, sizeh, texf);
}
if (size == 1) {
MutableObjectIntMap<QuadVertex> quadIndices = ObjectIntMaps.mutable.ofInitialCapacity(30);
setupIndicesTop(quadIndices, sizeh, texf, 0);
setupIndicesNorth(quadIndices, sizeh, texf, 6, -0.001f);
setupIndicesEast(quadIndices, sizeh, texf, 12, -0.001f);
setupIndicesSouth(quadIndices, sizeh, texf, 18, -0.001f);
setupIndicesWest(quadIndices, sizeh, texf, 24, -0.001f);
}
return this;
}
private void setupIndicesTop(MutableObjectIntMap<QuadVertex> quadIndices, int sizeh, float texf, int i) {
int h = size;
var posa = pos.add(0, 0, 0);
var nora = new Vector3f(0, 1, 0);
var texa = new Vector2f((sizeh + posa.x) * texf, 1f - (sizeh + posa.z) * texf);
var qa = new QuadVertex(posa, nora, texa, x, y);
var ia = addQuad(qa, quadIndices);
var posb = pos.add(0, 0, h);
var norb = new Vector3f(0, 1, 0);
var texb = new Vector2f((sizeh + posb.x) * texf, 1f - (sizeh + posb.z) * texf);
var qb = new QuadVertex(posb, norb, texb, x, y);
var ib = addQuad(qb, quadIndices);
var posc = pos.add(h, 0, h);
var norc = new Vector3f(0, 1, 0);
var texc = new Vector2f((sizeh + posc.x) * texf, 1f - (sizeh + posc.z) * texf);
var qc = new QuadVertex(posc, norc, texc, x, y);
var ic = addQuad(qc, quadIndices);
var posd = pos.add(h, 0, 0);
var nord = new Vector3f(0, 1, 0);
var texd = new Vector2f((sizeh + posd.x) * texf, 1f - (sizeh + posd.z) * texf);
var qd = new QuadVertex(posd, nord, texd, x, y);
var id = addQuad(qd, quadIndices);
inds.addAll(ia, ib, ic, ic, id, ia);
indices[i++] = ia;
indices[i++] = ib;
indices[i++] = ic;
indices[i++] = ic;
indices[i++] = id;
indices[i++] = ia;
}
private void setupIndicesNorth(MutableObjectIntMap<QuadVertex> quadIndices, int sizeh, float texf, int i, float b) {
int h = size;
var posa = pos.add(0, 0, 0);
var nora = new Vector3f(0, 0, 1);
var texa = new Vector2f((sizeh + posa.x) * texf, 1f - (sizeh + posa.z) * texf);
var qa = new QuadVertex(posa, nora, texa, x, y);
var ia = addQuad(qa, quadIndices);
var posb = pos.add(h, 0, 0);
var norb = new Vector3f(0, 0, 1);
var texb = new Vector2f((sizeh + posb.x) * texf, 1f - (sizeh + posb.z) * texf);
var qb = new QuadVertex(posb, norb, texb, x, y);
var ib = addQuad(qb, quadIndices);
var posc = pos.add(h, b, 0);
var norc = new Vector3f(0, 0, 1);
var texc = new Vector2f((sizeh + posc.x) * texf, 1f - (sizeh + posc.z) * texf);
var qc = new QuadVertex(posc, norc, texc, x, y);
var ic = addQuad(qc, quadIndices);
var posd = pos.add(0, b, 0);
var nord = new Vector3f(0, 0, 1);
var texd = new Vector2f((sizeh + posd.x) * texf, 1f - (sizeh + posd.z) * texf);
var qd = new QuadVertex(posd, nord, texd, x, y);
var id = addQuad(qd, quadIndices);
inds.addAll(ia, ib, id, id, ib, ic);
indices[i++] = ia;
indices[i++] = ib;
indices[i++] = id;
indices[i++] = id;
indices[i++] = ib;
indices[i++] = ic;
}
private void setupIndicesEast(MutableObjectIntMap<QuadVertex> quadIndices, int sizeh, float texf, int i, float b) {
int h = size;
var posa = pos.add(h, 0, 0);
var nora = new Vector3f(0, 0, 1);
var texa = new Vector2f((sizeh + posa.x) * texf, 1f - (sizeh + posa.z) * texf);
var qa = new QuadVertex(posa, nora, texa, x, y);
var ia = addQuad(qa, quadIndices);
var posb = pos.add(h, 0, h);
var norb = new Vector3f(0, 0, 1);
var texb = new Vector2f((sizeh + posb.x) * texf, 1f - (sizeh + posb.z) * texf);
var qb = new QuadVertex(posb, norb, texb, x, y);
var ib = addQuad(qb, quadIndices);
var posc = pos.add(h, b, h);
var norc = new Vector3f(0, 0, 1);
var texc = new Vector2f((sizeh + posc.x) * texf, 1f - (sizeh + posc.z) * texf);
var qc = new QuadVertex(posc, norc, texc, x, y);
var ic = addQuad(qc, quadIndices);
var posd = pos.add(h, b, 0);
var nord = new Vector3f(0, 0, 1);
var texd = new Vector2f((sizeh + posd.x) * texf, 1f - (sizeh + posd.z) * texf);
var qd = new QuadVertex(posd, nord, texd, x, y);
var id = addQuad(qd, quadIndices);
inds.addAll(ib, id, ia, ib, ic, id);
indices[i++] = ib;
indices[i++] = id;
indices[i++] = ia;
indices[i++] = ib;
indices[i++] = ic;
indices[i++] = id;
}
private void setupIndicesSouth(MutableObjectIntMap<QuadVertex> quadIndices, int sizeh, float texf, int i, float b) {
int h = size;
var posa = pos.add(0, 0, h);
var nora = new Vector3f(0, 0, -1);
var texa = new Vector2f((sizeh + posa.x) * texf, 1f - (sizeh + posa.z) * texf);
var qa = new QuadVertex(posa, nora, texa, x, y);
var ia = addQuad(qa, quadIndices);
var posb = pos.add(0, b, h);
var norb = new Vector3f(0, 0, -1);
var texb = new Vector2f((sizeh + posb.x) * texf, 1f - (sizeh + posb.z) * texf);
var qb = new QuadVertex(posb, norb, texb, x, y);
var ib = addQuad(qb, quadIndices);
var posc = pos.add(h, b, h);
var norc = new Vector3f(0, 0, -1);
var texc = new Vector2f((sizeh + posc.x) * texf, 1f - (sizeh + posc.z) * texf);
var qc = new QuadVertex(posc, norc, texc, x, y);
var ic = addQuad(qc, quadIndices);
var posd = pos.add(h, 0, h);
var nord = new Vector3f(0, 0, -1);
var texd = new Vector2f((sizeh + posd.x) * texf, 1f - (sizeh + posd.z) * texf);
var qd = new QuadVertex(posd, nord, texd, x, y);
var id = addQuad(qd, quadIndices);
inds.addAll(ia, ib, ic, ia, ic, id);
indices[i++] = ia;
indices[i++] = ib;
indices[i++] = ic;
indices[i++] = ia;
indices[i++] = ic;
indices[i++] = id;
}
private void setupIndicesWest(MutableObjectIntMap<QuadVertex> quadIndices, int sizeh, float texf, int i, float b) {
int h = size;
var posa = pos.add(0, b, 0);
var nora = new Vector3f(-1, 0, 0);
var texa = new Vector2f((sizeh + posa.x) * texf, 1f - (sizeh + posa.z) * texf);
var qa = new QuadVertex(posa, nora, texa, x, y);
var ia = addQuad(qa, quadIndices);
var posb = pos.add(0, b, h);
var norb = new Vector3f(-1, 0, 0);
var texb = new Vector2f((sizeh + posb.x) * texf, 1f - (sizeh + posb.z) * texf);
var qb = new QuadVertex(posb, norb, texb, x, y);
var ib = addQuad(qb, quadIndices);
var posc = pos.add(0, 0, h);
var norc = new Vector3f(-1, 0, 0);
var texc = new Vector2f((sizeh + posc.x) * texf, 1f - (sizeh + posc.z) * texf);
var qc = new QuadVertex(posc, norc, texc, x, y);
var ic = addQuad(qc, quadIndices);
var posd = pos.add(0, 0, 0);
var nord = new Vector3f(-1, 0, 0);
var texd = new Vector2f((sizeh + posd.x) * texf, 1f - (sizeh + posd.z) * texf);
var qd = new QuadVertex(posd, nord, texd, x, y);
var id = addQuad(qd, quadIndices);
inds.addAll(id, ib, ic, ib, id, ia);
indices[i++] = id;
indices[i++] = ib;
indices[i++] = ic;
indices[i++] = ib;
indices[i++] = id;
indices[i++] = ia;
}
private int addQuad(QuadVertex qa, MutableObjectIntMap<QuadVertex> quadIndices) {
var i = quadIndices.get(qa);
if (i == ObjectIntHashMap.EMPTY_VALUE) {
i = quads.size();
quadIndices.put(qa, i);
quads.add(qa);
}
return i;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment