Texturierung von Voxel Terrain angepasst

This commit is contained in:
2026-08-20 16:10:06 +02:00
parent 919ef98137
commit 024a7ffaff
7 changed files with 358 additions and 28 deletions

View File

@@ -530,9 +530,9 @@ public class SculptedMeshEditorState extends BaseAppState {
}
}
com.jme3.texture.Texture2D a0 = tes.getSplatTex();
com.jme3.texture.Texture2D a1 = tes.getUpperSplatTex();
com.jme3.texture.Texture2D a2 = tes.getThirdSplatTex();
com.jme3.texture.Texture2D a0 = tes.getVoxelSplatTex();
com.jme3.texture.Texture2D a1 = tes.getVoxelUpperSplatTex();
com.jme3.texture.Texture2D a2 = tes.getVoxelThirdSplatTex();
if (a0 != null) { mat.setTexture("AlphaMap", a0); }
if (a1 != null) { mat.setTexture("AlphaMap_1", a1); }
if (a2 != null) { mat.setTexture("AlphaMap_2", a2); }

View File

@@ -142,6 +142,22 @@ public class TerrainEditorState extends BaseAppState {
private Image thirdSplatImage;
private Texture2D thirdSplatTex;
// ── Voxel-Splatmaps (Sculpted-Mesh, unabhängig vom Basis-Terrain) ─────────
private byte[] voxelSplatR, voxelSplatG, voxelSplatB, voxelSplatA;
private ByteBuffer voxelSplatBuf;
private Image voxelSplatImage;
private Texture2D voxelSplatTex;
private byte[] voxelUpperSplatR, voxelUpperSplatG, voxelUpperSplatB, voxelUpperSplatA;
private ByteBuffer voxelUpperSplatBuf;
private Image voxelUpperSplatImage;
private Texture2D voxelUpperSplatTex;
private byte[] voxelThirdSplatR, voxelThirdSplatG, voxelThirdSplatB, voxelThirdSplatA;
private ByteBuffer voxelThirdSplatBuf;
private Image voxelThirdSplatImage;
private Texture2D voxelThirdSplatTex;
// ── Kameraposition ────────────────────────────────────────────────────────
private static final float DEFAULT_CAM_Y = 50f;
private static final float DEFAULT_PITCH = (float) (Math.PI / 4); // intern +45° → Kamera schaut 45° nach unten (JME 3.9: dir.y = -sin(pitch))
@@ -476,6 +492,19 @@ public class TerrainEditorState extends BaseAppState {
input.thirdTexturePaths, 0, MapData.TEXTURE_SLOTS);
System.arraycopy(loadedMapData.thirdNormalMaps, 0,
input.thirdNormalMapPaths, 0, MapData.TEXTURE_SLOTS);
// Voxel-Splatmaps (Sculpted-Meshes, seit Version 17)
voxelSplatR = loadedMapData.voxelSplatR.clone();
voxelSplatG = loadedMapData.voxelSplatG.clone();
voxelSplatB = loadedMapData.voxelSplatB.clone();
voxelSplatA = loadedMapData.voxelSplatA.clone();
voxelUpperSplatR = loadedMapData.voxelUpperSplatR.clone();
voxelUpperSplatG = loadedMapData.voxelUpperSplatG.clone();
voxelUpperSplatB = loadedMapData.voxelUpperSplatB.clone();
voxelUpperSplatA = loadedMapData.voxelUpperSplatA.clone();
voxelThirdSplatR = loadedMapData.voxelThirdSplatR.clone();
voxelThirdSplatG = loadedMapData.voxelThirdSplatG.clone();
voxelThirdSplatB = loadedMapData.voxelThirdSplatB.clone();
voxelThirdSplatA = loadedMapData.voxelThirdSplatA.clone();
if (loadedMapData.normalMapOrder != null && loadedMapData.normalMapOrder.length == 12)
input.normalMapOrder = loadedMapData.normalMapOrder.clone();
if (loadedMapData.diffuseScales != null && loadedMapData.diffuseScales.length == 12)
@@ -507,6 +536,18 @@ public class TerrainEditorState extends BaseAppState {
thirdSplatG = new byte[SPLAT_SIZE * SPLAT_SIZE];
thirdSplatB = new byte[SPLAT_SIZE * SPLAT_SIZE];
thirdSplatA = new byte[SPLAT_SIZE * SPLAT_SIZE];
voxelSplatR = new byte[SPLAT_SIZE * SPLAT_SIZE];
voxelSplatG = new byte[SPLAT_SIZE * SPLAT_SIZE];
voxelSplatB = new byte[SPLAT_SIZE * SPLAT_SIZE];
voxelSplatA = new byte[SPLAT_SIZE * SPLAT_SIZE];
voxelUpperSplatR = new byte[SPLAT_SIZE * SPLAT_SIZE];
voxelUpperSplatG = new byte[SPLAT_SIZE * SPLAT_SIZE];
voxelUpperSplatB = new byte[SPLAT_SIZE * SPLAT_SIZE];
voxelUpperSplatA = new byte[SPLAT_SIZE * SPLAT_SIZE];
voxelThirdSplatR = new byte[SPLAT_SIZE * SPLAT_SIZE];
voxelThirdSplatG = new byte[SPLAT_SIZE * SPLAT_SIZE];
voxelThirdSplatB = new byte[SPLAT_SIZE * SPLAT_SIZE];
voxelThirdSplatA = new byte[SPLAT_SIZE * SPLAT_SIZE];
}
// A-Kanal aus alten Saves bereinigen (war für Fluss-Textur; nicht mehr verwendet)
@@ -545,6 +586,39 @@ public class TerrainEditorState extends BaseAppState {
thirdSplatTex.setMinFilter(Texture.MinFilter.BilinearNoMipMaps);
thirdSplatTex.setMagFilter(Texture.MagFilter.Bilinear);
voxelSplatBuf = BufferUtils.createByteBuffer(SPLAT_SIZE * SPLAT_SIZE * 4);
for (int i = 0; i < SPLAT_SIZE * SPLAT_SIZE; i++) {
voxelSplatBuf.put(voxelSplatR[i]).put(voxelSplatG[i]).put(voxelSplatB[i]).put(voxelSplatA[i]);
}
voxelSplatBuf.flip();
voxelSplatImage = new Image(Image.Format.RGBA8, SPLAT_SIZE, SPLAT_SIZE, voxelSplatBuf, ColorSpace.Linear);
voxelSplatTex = new Texture2D(voxelSplatImage);
voxelSplatTex.setWrap(Texture.WrapMode.EdgeClamp);
voxelSplatTex.setMinFilter(Texture.MinFilter.BilinearNoMipMaps);
voxelSplatTex.setMagFilter(Texture.MagFilter.Bilinear);
voxelUpperSplatBuf = BufferUtils.createByteBuffer(SPLAT_SIZE * SPLAT_SIZE * 4);
for (int i = 0; i < SPLAT_SIZE * SPLAT_SIZE; i++) {
voxelUpperSplatBuf.put(voxelUpperSplatR[i]).put(voxelUpperSplatG[i]).put(voxelUpperSplatB[i]).put(voxelUpperSplatA[i]);
}
voxelUpperSplatBuf.flip();
voxelUpperSplatImage = new Image(Image.Format.RGBA8, SPLAT_SIZE, SPLAT_SIZE, voxelUpperSplatBuf, ColorSpace.Linear);
voxelUpperSplatTex = new Texture2D(voxelUpperSplatImage);
voxelUpperSplatTex.setWrap(Texture.WrapMode.EdgeClamp);
voxelUpperSplatTex.setMinFilter(Texture.MinFilter.BilinearNoMipMaps);
voxelUpperSplatTex.setMagFilter(Texture.MagFilter.Bilinear);
voxelThirdSplatBuf = BufferUtils.createByteBuffer(SPLAT_SIZE * SPLAT_SIZE * 4);
for (int i = 0; i < SPLAT_SIZE * SPLAT_SIZE; i++) {
voxelThirdSplatBuf.put(voxelThirdSplatR[i]).put(voxelThirdSplatG[i]).put(voxelThirdSplatB[i]).put(voxelThirdSplatA[i]);
}
voxelThirdSplatBuf.flip();
voxelThirdSplatImage = new Image(Image.Format.RGBA8, SPLAT_SIZE, SPLAT_SIZE, voxelThirdSplatBuf, ColorSpace.Linear);
voxelThirdSplatTex = new Texture2D(voxelThirdSplatImage);
voxelThirdSplatTex.setWrap(Texture.WrapMode.EdgeClamp);
voxelThirdSplatTex.setMinFilter(Texture.MinFilter.BilinearNoMipMaps);
voxelThirdSplatTex.setMagFilter(Texture.MagFilter.Bilinear);
// loadedMapData wird ab hier nicht mehr gebraucht (letzter Verbraucher war
// dieser Merge). Freigeben, weil terrainHeight allein ~1 GB (16385² floats)
// belegt und sonst die ganze Session über unnötig im Heap hängt.
@@ -954,6 +1028,139 @@ public class TerrainEditorState extends BaseAppState {
}
}
/** Malt Voxel-Basis-Texturen (Slots 1-4) auf die Voxel-Splatmap. */
private void applyVoxelTexturePaint(Vector3f contact, float strength, int textureIndex) {
float radius = (float) input.textureTool.brushRadius.getValue();
float softEdge = radius + Math.min(radius * 0.5f, 2f * SPLAT_WE_PER_PX);
int centerPX = Math.round((contact.x + WORLD_HALF) / SPLAT_WE_PER_PX);
int centerPZ = (SPLAT_SIZE - 1) - Math.round((contact.z + WORLD_HALF) / SPLAT_WE_PER_PX);
int pixR = (int) Math.ceil(softEdge / SPLAT_WE_PER_PX);
boolean changed = false;
for (int dz = -pixR; dz <= pixR; dz++) {
int pz = centerPZ + dz;
if (pz < 0 || pz >= SPLAT_SIZE) continue;
for (int dx = -pixR; dx <= pixR; dx++) {
int px = centerPX + dx;
if (px < 0 || px >= SPLAT_SIZE) continue;
float distWE = FastMath.sqrt(dx * dx + dz * dz) * SPLAT_WE_PER_PX;
if (distWE >= softEdge) continue;
float t = distWE / softEdge;
float falloff = (1f + FastMath.cos(FastMath.PI * t)) * 0.5f;
float blend = (textureIndex == 0) ? falloff : strength * falloff;
int idx = pz * SPLAT_SIZE + px;
float curG = (voxelSplatG[idx] & 0xFF) / 255f;
float curB = (voxelSplatB[idx] & 0xFF) / 255f;
float curA = (voxelSplatA[idx] & 0xFF) / 255f;
float tgG = (textureIndex == 1) ? 1f : 0f;
float tgB = (textureIndex == 2) ? 1f : 0f;
float tgA = (textureIndex == 3) ? 1f : 0f;
voxelSplatR[idx] = (byte) 255;
voxelSplatG[idx] = (byte) Math.round((curG + (tgG - curG) * blend) * 255f);
voxelSplatB[idx] = (byte) Math.round((curB + (tgB - curB) * blend) * 255f);
voxelSplatA[idx] = (byte) Math.round((curA + (tgA - curA) * blend) * 255f);
int bi = idx * 4;
voxelSplatBuf.put(bi, voxelSplatR[idx]);
voxelSplatBuf.put(bi + 1, voxelSplatG[idx]);
voxelSplatBuf.put(bi + 2, voxelSplatB[idx]);
voxelSplatBuf.put(bi + 3, voxelSplatA[idx]);
changed = true;
}
}
if (changed) { voxelSplatBuf.rewind(); voxelSplatImage.setUpdateNeeded(); }
}
/** Malt Voxel-Slots 5-8 auf die Voxel-Upper-Splatmap. */
private void applyVoxelUpperTexturePaint(Vector3f contact, float strength, int textureIndex) {
float radius = (float) input.textureTool.brushRadius.getValue();
float softEdge = radius + Math.min(radius * 0.5f, 2f * SPLAT_WE_PER_PX);
int centerPX = Math.round((contact.x + WORLD_HALF) / SPLAT_WE_PER_PX);
int centerPZ = (SPLAT_SIZE - 1) - Math.round((contact.z + WORLD_HALF) / SPLAT_WE_PER_PX);
int pixR = (int) Math.ceil(softEdge / SPLAT_WE_PER_PX);
boolean changed = false;
for (int dz = -pixR; dz <= pixR; dz++) {
int pz = centerPZ + dz;
if (pz < 0 || pz >= SPLAT_SIZE) continue;
for (int dx = -pixR; dx <= pixR; dx++) {
int px = centerPX + dx;
if (px < 0 || px >= SPLAT_SIZE) continue;
float distWE = FastMath.sqrt(dx * dx + dz * dz) * SPLAT_WE_PER_PX;
if (distWE >= softEdge) continue;
float t = distWE / softEdge;
float falloff = (1f + FastMath.cos(FastMath.PI * t)) * 0.5f;
int idx = pz * SPLAT_SIZE + px;
if (textureIndex < 0) {
voxelUpperSplatR[idx] = (byte) Math.round((voxelUpperSplatR[idx] & 0xFF) / 255f * (1f - falloff) * 255f);
voxelUpperSplatG[idx] = (byte) Math.round((voxelUpperSplatG[idx] & 0xFF) / 255f * (1f - falloff) * 255f);
voxelUpperSplatB[idx] = (byte) Math.round((voxelUpperSplatB[idx] & 0xFF) / 255f * (1f - falloff) * 255f);
voxelUpperSplatA[idx] = (byte) Math.round((voxelUpperSplatA[idx] & 0xFF) / 255f * (1f - falloff) * 255f);
} else {
float blend = strength * falloff;
float curR = (voxelUpperSplatR[idx] & 0xFF) / 255f;
float curG = (voxelUpperSplatG[idx] & 0xFF) / 255f;
float curB = (voxelUpperSplatB[idx] & 0xFF) / 255f;
float curA = (voxelUpperSplatA[idx] & 0xFF) / 255f;
voxelUpperSplatR[idx] = (byte) Math.round((curR + ((textureIndex == 0 ? 1f : 0f) - curR) * blend) * 255f);
voxelUpperSplatG[idx] = (byte) Math.round((curG + ((textureIndex == 1 ? 1f : 0f) - curG) * blend) * 255f);
voxelUpperSplatB[idx] = (byte) Math.round((curB + ((textureIndex == 2 ? 1f : 0f) - curB) * blend) * 255f);
voxelUpperSplatA[idx] = (byte) Math.round((curA + ((textureIndex == 3 ? 1f : 0f) - curA) * blend) * 255f);
}
int bi = idx * 4;
voxelUpperSplatBuf.put(bi, voxelUpperSplatR[idx]);
voxelUpperSplatBuf.put(bi + 1, voxelUpperSplatG[idx]);
voxelUpperSplatBuf.put(bi + 2, voxelUpperSplatB[idx]);
voxelUpperSplatBuf.put(bi + 3, voxelUpperSplatA[idx]);
changed = true;
}
}
if (changed) { voxelUpperSplatBuf.rewind(); voxelUpperSplatImage.setUpdateNeeded(); }
}
/** Malt Voxel-Slots 9-12 auf die Voxel-Third-Splatmap. */
private void applyVoxelThirdTexturePaint(Vector3f contact, float strength, int textureIndex) {
float radius = (float) input.textureTool.brushRadius.getValue();
float softEdge = radius + Math.min(radius * 0.5f, 2f * SPLAT_WE_PER_PX);
int centerPX = Math.round((contact.x + WORLD_HALF) / SPLAT_WE_PER_PX);
int centerPZ = (SPLAT_SIZE - 1) - Math.round((contact.z + WORLD_HALF) / SPLAT_WE_PER_PX);
int pixR = (int) Math.ceil(softEdge / SPLAT_WE_PER_PX);
boolean changed = false;
for (int dz = -pixR; dz <= pixR; dz++) {
int pz = centerPZ + dz;
if (pz < 0 || pz >= SPLAT_SIZE) continue;
for (int dx = -pixR; dx <= pixR; dx++) {
int px = centerPX + dx;
if (px < 0 || px >= SPLAT_SIZE) continue;
float distWE = FastMath.sqrt(dx * dx + dz * dz) * SPLAT_WE_PER_PX;
if (distWE >= softEdge) continue;
float t = distWE / softEdge;
float falloff = (1f + FastMath.cos(FastMath.PI * t)) * 0.5f;
int idx = pz * SPLAT_SIZE + px;
if (textureIndex < 0) {
voxelThirdSplatR[idx] = (byte) Math.round((voxelThirdSplatR[idx] & 0xFF) / 255f * (1f - falloff) * 255f);
voxelThirdSplatG[idx] = (byte) Math.round((voxelThirdSplatG[idx] & 0xFF) / 255f * (1f - falloff) * 255f);
voxelThirdSplatB[idx] = (byte) Math.round((voxelThirdSplatB[idx] & 0xFF) / 255f * (1f - falloff) * 255f);
voxelThirdSplatA[idx] = (byte) Math.round((voxelThirdSplatA[idx] & 0xFF) / 255f * (1f - falloff) * 255f);
} else {
float blend = strength * falloff;
float curR = (voxelThirdSplatR[idx] & 0xFF) / 255f;
float curG = (voxelThirdSplatG[idx] & 0xFF) / 255f;
float curB = (voxelThirdSplatB[idx] & 0xFF) / 255f;
float curA = (voxelThirdSplatA[idx] & 0xFF) / 255f;
voxelThirdSplatR[idx] = (byte) Math.round((curR + ((textureIndex == 0 ? 1f : 0f) - curR) * blend) * 255f);
voxelThirdSplatG[idx] = (byte) Math.round((curG + ((textureIndex == 1 ? 1f : 0f) - curG) * blend) * 255f);
voxelThirdSplatB[idx] = (byte) Math.round((curB + ((textureIndex == 2 ? 1f : 0f) - curB) * blend) * 255f);
voxelThirdSplatA[idx] = (byte) Math.round((curA + ((textureIndex == 3 ? 1f : 0f) - curA) * blend) * 255f);
}
int bi = idx * 4;
voxelThirdSplatBuf.put(bi, voxelThirdSplatR[idx]);
voxelThirdSplatBuf.put(bi + 1, voxelThirdSplatG[idx]);
voxelThirdSplatBuf.put(bi + 2, voxelThirdSplatB[idx]);
voxelThirdSplatBuf.put(bi + 3, voxelThirdSplatA[idx]);
changed = true;
}
}
if (changed) { voxelThirdSplatBuf.rewind(); voxelThirdSplatImage.setUpdateNeeded(); }
}
// ── Update-Schleife ───────────────────────────────────────────────────────
@Override
@@ -1159,27 +1366,44 @@ public class TerrainEditorState extends BaseAppState {
Vector3f far = cam.getWorldCoordinates(new Vector2f(jmeX, jmeY), 1f);
com.jme3.math.Ray ray = new com.jme3.math.Ray(near, far.subtract(near).normalizeLocal());
Vector3f contact = raycastSurface(ray);
if (contact == null) continue;
if (ves != null && ves.terrainTypeAt(contact.x, contact.z) == VoxelEditorState.TerrainType.VOXEL_UNBAKED) continue;
SurfaceHit hit = raycastSurfaceWithType(ray);
if (hit == null) continue;
Vector3f contact = hit.point;
if (!hit.voxelMesh && ves != null && ves.terrainTypeAt(contact.x, contact.z) == VoxelEditorState.TerrainType.VOXEL_UNBAKED) continue;
int selIdx = input.textureTool.textureIndex.getSelectedIndex();
float str = (float) input.textureTool.brushStrength.getValue();
if (edit.action() > 0) {
// Linksklick: Slot malen
if (selIdx >= 8) {
applyThirdTexturePaint(contact, str, selIdx - 8); // 0=R(Slot9),1=G(Slot10),...
} else if (selIdx >= 4) {
applyUpperTexturePaint(contact, str, selIdx - 4); // 0=R(Slot5),1=G(Slot6),...
if (hit.voxelMesh) {
// Sculpted-Mesh-Treffer → Voxel-Splatmaps
if (edit.action() > 0) {
if (selIdx >= 8) {
applyVoxelThirdTexturePaint(contact, str, selIdx - 8);
} else if (selIdx >= 4) {
applyVoxelUpperTexturePaint(contact, str, selIdx - 4);
} else {
applyVoxelTexturePaint(contact, str, selIdx);
}
} else {
applyTexturePaint(contact, str, selIdx);
applyVoxelTexturePaint(contact, str, 0);
applyVoxelUpperTexturePaint(contact, str, -1);
applyVoxelThirdTexturePaint(contact, str, -1);
}
} else {
// Rechtsklick: immer auf Basis (Slot 1) zurücksetzen
applyTexturePaint(contact, str, 0); // Base → Slot 1 (Gras)
applyUpperTexturePaint(contact, str, -1); // Upper-Kanäle → 0
applyThirdTexturePaint(contact, str, -1); // Third-Kanäle → 0
// Basis-Terrain-Treffer → normale Splatmaps
if (edit.action() > 0) {
if (selIdx >= 8) {
applyThirdTexturePaint(contact, str, selIdx - 8);
} else if (selIdx >= 4) {
applyUpperTexturePaint(contact, str, selIdx - 4);
} else {
applyTexturePaint(contact, str, selIdx);
}
} else {
applyTexturePaint(contact, str, 0);
applyUpperTexturePaint(contact, str, -1);
applyThirdTexturePaint(contact, str, -1);
}
}
}
}
@@ -1194,22 +1418,38 @@ public class TerrainEditorState extends BaseAppState {
public com.jme3.texture.Texture2D getUpperSplatTex() { return upperSplatTex; }
public com.jme3.texture.Texture2D getThirdSplatTex() { return thirdSplatTex; }
public com.jme3.texture.Texture2D getVoxelSplatTex() { return voxelSplatTex; }
public com.jme3.texture.Texture2D getVoxelUpperSplatTex() { return voxelUpperSplatTex; }
public com.jme3.texture.Texture2D getVoxelThirdSplatTex() { return voxelThirdSplatTex; }
private static final class SurfaceHit {
final Vector3f point;
final boolean voxelMesh;
SurfaceHit(Vector3f p, boolean v) { point = p; voxelMesh = v; }
}
/**
* Raycast gegen Basis-Terrain, ungebackenes Voxel-Terrain und gebackene Sculpted-Meshes.
* Gibt den zur Kamera nächstgelegenen Treffer zurück, oder null wenn keines getroffen wurde.
*/
private Vector3f raycastSurface(com.jme3.math.Ray ray) {
SurfaceHit h = raycastSurfaceWithType(ray);
return h != null ? h.point : null;
}
private SurfaceHit raycastSurfaceWithType(com.jme3.math.Ray ray) {
CollisionResults hits = new CollisionResults();
terrain.collideWith(ray, hits);
Vector3f best = hits.size() > 0 ? hits.getClosestCollision().getContactPoint() : null;
float bestDistSq = best != null ? ray.getOrigin().distanceSquared(best) : Float.MAX_VALUE;
boolean bestIsVoxelMesh = false;
VoxelEditorState ves = getStateManager().getState(VoxelEditorState.class);
if (ves != null) {
Vector3f vp = ves.raycastVoxelGeometry(ray);
if (vp != null) {
float d = ray.getOrigin().distanceSquared(vp);
if (d < bestDistSq) { best = vp; bestDistSq = d; }
if (d < bestDistSq) { best = vp; bestDistSq = d; bestIsVoxelMesh = false; }
}
}
@@ -1218,10 +1458,10 @@ public class TerrainEditorState extends BaseAppState {
Vector3f sp = smes.raycastGeometry(ray);
if (sp != null) {
float d = ray.getOrigin().distanceSquared(sp);
if (d < bestDistSq) { best = sp; }
if (d < bestDistSq) { best = sp; bestIsVoxelMesh = true; }
}
}
return best;
return best != null ? new SurfaceHit(best, bestIsVoxelMesh) : null;
}
/** Gibt die Terrain-Höhe (Welt-Y) an der angegebenen Welt-XZ-Position zurück. */
@@ -1268,6 +1508,18 @@ public class TerrainEditorState extends BaseAppState {
final byte[] thG = thirdSplatG != null ? thirdSplatG.clone() : null;
final byte[] thB = thirdSplatB != null ? thirdSplatB.clone() : null;
final byte[] thA = thirdSplatA != null ? thirdSplatA.clone() : null;
final byte[] vxR = voxelSplatR != null ? voxelSplatR.clone() : null;
final byte[] vxG = voxelSplatG != null ? voxelSplatG.clone() : null;
final byte[] vxB = voxelSplatB != null ? voxelSplatB.clone() : null;
final byte[] vxA = voxelSplatA != null ? voxelSplatA.clone() : null;
final byte[] vxUR = voxelUpperSplatR != null ? voxelUpperSplatR.clone() : null;
final byte[] vxUG = voxelUpperSplatG != null ? voxelUpperSplatG.clone() : null;
final byte[] vxUB = voxelUpperSplatB != null ? voxelUpperSplatB.clone() : null;
final byte[] vxUA = voxelUpperSplatA != null ? voxelUpperSplatA.clone() : null;
final byte[] vxTR = voxelThirdSplatR != null ? voxelThirdSplatR.clone() : null;
final byte[] vxTG = voxelThirdSplatG != null ? voxelThirdSplatG.clone() : null;
final byte[] vxTB = voxelThirdSplatB != null ? voxelThirdSplatB.clone() : null;
final byte[] vxTA = voxelThirdSplatA != null ? voxelThirdSplatA.clone() : null;
final String[] texPaths = input.terrainTexturePaths.clone();
final String[] normalPaths = input.terrainNormalMapPaths.clone();
final String[] upperPaths = input.upperTexturePaths.clone();
@@ -1345,6 +1597,20 @@ public class TerrainEditorState extends BaseAppState {
System.arraycopy(thirdPaths, 0, data.thirdTextures, 0, MapData.TEXTURE_SLOTS);
System.arraycopy(thirdNormPaths, 0, data.thirdNormalMaps, 0, MapData.TEXTURE_SLOTS);
}
if (vxR != null) {
System.arraycopy(vxR, 0, data.voxelSplatR, 0, data.voxelSplatR.length);
System.arraycopy(vxG, 0, data.voxelSplatG, 0, data.voxelSplatG.length);
System.arraycopy(vxB, 0, data.voxelSplatB, 0, data.voxelSplatB.length);
System.arraycopy(vxA, 0, data.voxelSplatA, 0, data.voxelSplatA.length);
System.arraycopy(vxUR, 0, data.voxelUpperSplatR, 0, data.voxelUpperSplatR.length);
System.arraycopy(vxUG, 0, data.voxelUpperSplatG, 0, data.voxelUpperSplatG.length);
System.arraycopy(vxUB, 0, data.voxelUpperSplatB, 0, data.voxelUpperSplatB.length);
System.arraycopy(vxUA, 0, data.voxelUpperSplatA, 0, data.voxelUpperSplatA.length);
System.arraycopy(vxTR, 0, data.voxelThirdSplatR, 0, data.voxelThirdSplatR.length);
System.arraycopy(vxTG, 0, data.voxelThirdSplatG, 0, data.voxelThirdSplatG.length);
System.arraycopy(vxTB, 0, data.voxelThirdSplatB, 0, data.voxelThirdSplatB.length);
System.arraycopy(vxTA, 0, data.voxelThirdSplatA, 0, data.voxelThirdSplatA.length);
}
data.normalMapOrder = input.normalMapOrder.clone();
data.diffuseScales = diffScaleSnap.clone();
data.voxelFlatSlot = voxelFlatSlot;