Async mesh reloading for planets

This commit is contained in:
Anuken
2025-05-29 20:46:17 -04:00
parent 1e621259d5
commit 82a801b1c0
14 changed files with 218 additions and 196 deletions

View File

@@ -32,7 +32,7 @@ public class LoadRenderer implements Disposable{
private float testprogress = 0f;
private StringBuilder assetText = new StringBuilder();
private Bar[] bars;
private Mesh mesh = MeshBuilder.buildLineHex(colorRed, 2);
private Mesh mesh = MeshBuilder.buildPlanetGrid(PlanetGrid.create(2), colorRed, 1f);
private Camera3D cam = new Camera3D();
private int lastLength = -1;
private FxProcessor fx;

View File

@@ -21,7 +21,7 @@ public class HexMesh extends PlanetMesh{
@Override
public void preRender(PlanetParams params){
Shaders.planet.planet = planet;
Shaders.planet.emissive = planet.generator != null && planet.generator.hasEmissive();
Shaders.planet.emissive = planet.generator != null;
Shaders.planet.lightDir.set(planet.solarSystem.position).sub(planet.position).rotate(Vec3.Y, planet.getRotation()).nor();
Shaders.planet.ambientColor.set(planet.solarSystem.lightColor);
}

View File

@@ -6,15 +6,16 @@ import arc.math.geom.*;
/** Defines color and height for a planet mesh. */
public interface HexMesher{
float getHeight(Vec3 position);
Color getColor(Vec3 position);
default Color getEmissiveColor(Vec3 position){
return Color.clear;
default float getHeight(Vec3 position){
return 0f;
}
default boolean hasEmissive(){
return false;
default void getColor(Vec3 position, Color out){
}
default void getEmissiveColor(Vec3 position, Color out){
}
default boolean skip(Vec3 position){

View File

@@ -21,8 +21,8 @@ public class HexSkyMesh extends PlanetMesh{
}
@Override
public Color getColor(Vec3 position){
return color;
public void getColor(Vec3 position, Color out){
out.set(color);
}
@Override

View File

@@ -8,65 +8,39 @@ import mindustry.graphics.g3d.PlanetGrid.*;
import mindustry.maps.generators.*;
public class MeshBuilder{
private static final Vec3 v1 = new Vec3(), v2 = new Vec3(), v3 = new Vec3(), v4 = new Vec3();
private static final boolean gl30 = Core.gl30 != null;
private static final float[] floats = new float[3 + (gl30 ? 1 : 3) + 1], emissiveFloats = new float[floats.length + 1];
private static final short[] shorts = new short[3];
private static float[] tmpHeights = new float[14580]; //highest amount of corners in vanilla
private static Mesh mesh;
public static Mesh buildIcosphere(int divisions, float radius, Color color){
begin(20 * (2 << (2 * divisions - 1)) * 3, 0, false);
float col = color.toFloatBits();
private static volatile float[] tmpHeights = new float[14580]; //highest amount of corners in vanilla
/** Note that the resulting icosphere does not have normals or a color. */
public static Mesh buildIcosphere(int divisions, float radius){
MeshResult result = Icosphere.create(divisions);
for(int i = 0; i < result.indices.size; i+= 3){
v1.set(result.vertices.items, result.indices.items[i] * 3).setLength(radius);
v2.set(result.vertices.items, result.indices.items[i + 1] * 3).setLength(radius);
v3.set(result.vertices.items, result.indices.items[i + 2] * 3).setLength(radius);
verts(v1, v3, v2, normal(v1, v2, v3).scl(-1f), col, 0f);
Mesh mesh = begin(result.vertices.size / 3, result.indices.size, false, false);
if(result.vertices.size >= 65535) throw new RuntimeException("Due to index size limits, only meshes with a maximum of 65535 vertices are supported. If you want more than that, make your own non-indexed mesh builder.");
float[] items = result.vertices.items;
for(int i = 0; i < result.vertices.size; i ++){
items[i] *= radius;
}
return end();
}
mesh.getVerticesBuffer().put(items, 0, result.vertices.size);
public static Mesh buildIcosphere(int divisions, float radius){
return buildIcosphere(divisions, radius, Color.white);
short[] indices = new short[result.indices.size];
for(int i = 0; i < result.indices.size; i++){
indices[i] = (short)result.indices.items[i];
}
mesh.getIndicesBuffer().put(indices);
return end(mesh);
}
public static Mesh buildPlanetGrid(PlanetGrid grid, Color color, float scale){
int total = 0;
for(Ptile tile : grid.tiles){
total += tile.corners.length * 2;
}
float col = color.toFloatBits();
begin(total, 0, false);
for(Ptile tile : grid.tiles){
Corner[] c = tile.corners;
for(int i = 0; i < c.length; i++){
Vec3 a = v1.set(c[i].v).scl(scale);
Vec3 b = v2.set(c[(i + 1) % c.length].v).scl(scale);
vert(a, Vec3.Z, col, 0f);
vert(b, Vec3.Z, col, 0f);
}
}
return end();
}
public static Mesh buildLineHex(Color color, int divisions){
PlanetGrid grid = PlanetGrid.create(divisions);
begin(grid.tiles.length * 12, 0, false);
Vec3 nor = v4.set(1f, 1f, 1f);
Mesh mesh = begin(grid.tiles.length * 12, 0, false, false);
float col = color.toFloatBits();
float[] floats = new float[8];
for(Ptile tile : grid.tiles){
Corner[] c = tile.corners;
@@ -75,12 +49,21 @@ public class MeshBuilder{
Vec3 v1 = c[i].v;
Vec3 v2 = c[(i + 1) % c.length].v;
vert(v1, nor, col, 0f);
vert(v2, nor, col, 0f);
floats[0] = v1.x * scale;
floats[1] = v1.y * scale;
floats[2] = v1.z * scale;
floats[3] = col;
floats[4] = v2.x * scale;
floats[5] = v2.y * scale;
floats[6] = v2.z * scale;
floats[7] = col;
mesh.getVerticesBuffer().put(floats);
}
}
return end();
return end(mesh);
}
public static Mesh buildHex(Color color, int divisions, float radius){
@@ -91,29 +74,24 @@ public class MeshBuilder{
}
@Override
public Color getColor(Vec3 position){
return color;
public void getColor(Vec3 position, Color out){
out.set(color);
}
}, divisions, radius, 0);
}
//TODO: make this thread safe
public static Mesh buildHex(HexMesher mesher, int divisions, float radius, float intensity){
//TODO: in principle this should not be synchronized, but I would rather not realloc tmpHeights every time, and it is unlikely that two planets will be reloading at the same time
public static synchronized Mesh buildHex(HexMesher mesher, int divisions, float radius, float intensity){
PlanetGrid grid = PlanetGrid.create(divisions);
//TODO: this is NOT thread safe, but in practice, it should never cause a problem
if(mesher instanceof PlanetGenerator generator){
generator.seed = generator.baseSeed;
}
boolean emit = mesher.hasEmissive();
if(grid.tiles.length * 6 >= 65535) throw new RuntimeException("Due to index size limits, only meshes with a maximum of 65535 vertices are supported. If you want more than that, make your own non-indexed mesh builder.");
boolean indexed = grid.tiles.length * 6 < 65535;
if(indexed){
begin(grid.tiles.length * 6, grid.tiles.length * 4 * 3, emit);
}else{
begin(grid.tiles.length * 12, 0, emit);
}
Mesh mesh = begin(grid.tiles.length * 6, grid.tiles.length * 4 * 3, true, true);
float[] heights;
@@ -130,6 +108,12 @@ public class MeshBuilder{
int position = 0;
short[] shorts = new short[12];
float[] floats = new float[3 + (gl30 ? 1 : 3) + 1 + 1];
Vec3 nor = new Vec3();
Color tmpCol = new Color();
for(Ptile tile : grid.tiles){
if(mesher.skip(tile.v)){
continue;
@@ -137,60 +121,77 @@ public class MeshBuilder{
Corner[] c = tile.corners;
for(Corner corner : c){
corner.v.scl((1f + heights[corner.id] * intensity) * radius);
float
h1 = (1f + heights[c[0].id] * intensity) * radius,
h2 = (1f + heights[c[2].id] * intensity) * radius,
h3 = (1f + heights[c[4].id] * intensity) * radius;
Vec3
v1 = c[0].v,
v2 = c[2].v,
v3 = c[4].v;
normal(
v1.x * h1, v1.y * h1, v1.z * h1,
v2.x * h2, v2.y * h2, v2.z * h2,
v3.x * h3, v3.y * h3, v3.z * h3,
nor);
tmpCol.set(1f, 1f, 1f, 1f);
mesher.getColor(tile.v, tmpCol);
float color = tmpCol.toFloatBits();
tmpCol.set(0f, 0f, 0f, 0f);
mesher.getEmissiveColor(tile.v, tmpCol);
float emissive = tmpCol.toFloatBits();
for(var corner : c){
float height = (1f + heights[corner.id] * intensity) * radius;
vert(mesh, floats, corner.v.x * height, corner.v.y * height, corner.v.z * height, nor, color, emissive);
}
Vec3 nor = normal(c[0].v, c[2].v, c[4].v);
float color = mesher.getColor(tile.v).toFloatBits();
float emissive = emit ? mesher.getEmissiveColor(tile.v).toFloatBits() : 0f;
shorts[0] = (short)(position);
shorts[1] = (short)(position + 1);
shorts[2] = (short)(position + 2);
if(indexed){
for(var corner : c){
vert(corner.v, nor, color, emissive);
}
shorts[3] = (short)(position);
shorts[4] = (short)(position + 2);
shorts[5] = (short)(position + 3);
indices(position, position + 1, position + 2);
indices(position, position + 2, position + 3);
indices(position, position + 3, position + 4);
if(c.length > 5){
indices(position, position + 4, position + 5);
}
shorts[6] = (short)(position);
shorts[7] = (short)(position + 3);
shorts[8] = (short)(position + 4);
position += c.length;
}else{
verts(c[0].v, c[1].v, c[2].v, nor, color, emissive);
verts(c[0].v, c[2].v, c[3].v, nor, color, emissive);
verts(c[0].v, c[3].v, c[4].v, nor, color, emissive);
if(c.length > 5){
verts(c[0].v, c[4].v, c[5].v, nor, color, emissive);
}
if(c.length > 5){
shorts[9] = (short)(position);
shorts[10] = (short)(position + 4);
shorts[11] = (short)(position + 5);
}
//restore mutated corners
for(Corner corner : c){
corner.v.nor();
}
mesh.getIndicesBuffer().put(shorts, 0, c.length > 5 ? 12 : 9);
position += c.length;
}
return end();
return end(mesh);
}
private static void begin(int vertices, int indices, boolean emissive){
private static Mesh begin(int vertices, int indices, boolean normal, boolean emissive){
Seq<VertexAttribute> attributes = Seq.with(
VertexAttribute.position3,
//only GL30 supports GL_INT_2_10_10_10_REV
gl30 ? VertexAttribute.packedNormal : VertexAttribute.normal,
VertexAttribute.color
VertexAttribute.position3
);
if(normal){
//only GL30 supports GL_INT_2_10_10_10_REV
attributes.add(gl30 ? VertexAttribute.packedNormal : VertexAttribute.normal);
}
attributes.add(VertexAttribute.color);
if(emissive){
attributes.add(new VertexAttribute(4, GL20.GL_UNSIGNED_BYTE, true, "a_emissive"));
}
mesh = new Mesh(true, vertices, indices, attributes.toArray(VertexAttribute.class));
Mesh mesh = new Mesh(true, vertices, indices, attributes.toArray(VertexAttribute.class));
mesh.getVerticesBuffer().limit(mesh.getVerticesBuffer().capacity());
mesh.getVerticesBuffer().position(0);
@@ -199,56 +200,70 @@ public class MeshBuilder{
mesh.getIndicesBuffer().limit(mesh.getIndicesBuffer().capacity());
mesh.getIndicesBuffer().position(0);
}
return mesh;
}
private static Mesh end(){
Mesh last = mesh;
last.getVerticesBuffer().limit(last.getVerticesBuffer().position());
if(last.getNumIndices() > 0){
last.getIndicesBuffer().limit(last.getIndicesBuffer().position());
private static Mesh end(Mesh mesh){
mesh.getVerticesBuffer().limit(mesh.getVerticesBuffer().position());
if(mesh.getNumIndices() > 0){
mesh.getIndicesBuffer().limit(mesh.getIndicesBuffer().position());
}
mesh = null;
return last;
return mesh;
}
private static Vec3 normal(Vec3 v1, Vec3 v2, Vec3 v3){
return v4.set(v2).sub(v1).crs(v3.x - v1.x, v3.y - v1.y, v3.z - v1.z).nor();
private static void normal(Vec3 v1, Vec3 v2, Vec3 v3, Vec3 out){
float
x = v2.x - v1.x,
y = v2.y - v1.y,
z = v2.z - v1.z,
vx = v3.x - v1.x,
vy = v3.y - v1.y,
vz = v3.z - v1.z;
float
cx = y * vz - z * vy,
cy = z * vx - x * vz,
cz = x * vy - y * vx;
out.set(cx, cy, cz).nor();
}
private static void indices(int a, int b, int c){
shorts[0] = (short)a;
shorts[1] = (short)b;
shorts[2] = (short)c;
mesh.getIndicesBuffer().put(shorts);
private static void normal(float v1x, float v1y, float v1z, float v2x, float v2y, float v2z, float v3x, float v3y, float v3z, Vec3 out){
float
x = v2x - v1x,
y = v2y - v1y,
z = v2z - v1z,
vx = v3x - v1x,
vy = v3y - v1y,
vz = v3z - v1z;
float
cx = y * vz - z * vy,
cy = z * vx - x * vz,
cz = x * vy - y * vx;
out.set(cx, cy, cz).nor();
}
private static void verts(Vec3 a, Vec3 b, Vec3 c, Vec3 normal, float color, float emissive){
vert(a, normal, color, emissive);
vert(b, normal, color, emissive);
vert(c, normal, color, emissive);
}
private static void vert(Vec3 a, Vec3 normal, float color, float emissive){
boolean emit = mesh.getVertexSize() == emissiveFloats.length*4;
float[] floats = emit ? emissiveFloats : MeshBuilder.floats;
floats[0] = a.x;
floats[1] = a.y;
floats[2] = a.z;
private static void vert(Mesh mesh, float[] floats, float x, float y, float z, Vec3 normal, float color, float emissive){
floats[0] = x;
floats[1] = y;
floats[2] = z;
if(gl30){
floats[3] = packNormals(normal.x, normal.y, normal.z);
floats[4] = color;
if(emit) floats[5] = emissive;
floats[5] = emissive;
}else{
floats[3] = normal.x;
floats[4] = normal.x;
floats[5] = normal.x;
floats[6] = color;
if(emit) floats[7] = emissive;
floats[7] = emissive;
}
mesh.getVerticesBuffer().put(floats);

View File

@@ -18,8 +18,8 @@ public class NoiseMesh extends HexMesh{
}
@Override
public Color getColor(Vec3 position){
return color;
public void getColor(Vec3 position, Color out){
out.set(color);
}
}, divisions, radius, 0.2f);
}
@@ -35,8 +35,8 @@ public class NoiseMesh extends HexMesh{
}
@Override
public Color getColor(Vec3 position){
return Simplex.noise3d(8 + seed, coct, cper, cscl, 5f + position.x, 5f + position.y, 5f + position.z) > cthresh ? color2 : color1;
public void getColor(Vec3 position, Color out){
out.set(Simplex.noise3d(8 + seed, coct, cper, cscl, 5f + position.x, 5f + position.y, 5f + position.z) > cthresh ? color2 : color1);
}
}, divisions, radius, 0.2f);
}

View File

@@ -47,7 +47,7 @@ public class PlanetGrid{
}
}
public static PlanetGrid create(int size){
public static synchronized PlanetGrid create(int size){
//cache grids between calls, since only ~5 different grids total are needed
if(size < cache.length && cache[size] != null){
return cache[size];

View File

@@ -3,7 +3,6 @@ package mindustry.graphics.g3d;
import arc.graphics.*;
import arc.math.*;
import arc.math.geom.*;
import arc.util.*;
import arc.util.noise.*;
import mindustry.graphics.*;
import mindustry.type.*;
@@ -19,9 +18,9 @@ public class SunMesh extends HexMesh{
}
@Override
public Color getColor(Vec3 position){
public void getColor(Vec3 position, Color out){
double height = Math.pow(Simplex.noise3d(0, octaves, persistence, scl, position.x, position.y, position.z), pow) * mag;
return Tmp.c1.set(colors[Mathf.clamp((int)(height * colors.length), 0, colors.length - 1)]).mul(colorScale);
out.set(colors[Mathf.clamp((int)(height * colors.length), 0, colors.length - 1)]).mul(colorScale);
}
}, divisions, Shaders.unlit);
}