kilgoar2024/Assets/MapEditor/Scripts/WorldConverter.cs

464 lines
16 KiB
C#

using System;
using UnityEngine;
using UnityEditor;
using System.Security.Cryptography;
using System.Text;
using System.IO;
using System.Threading.Tasks;
using System.Collections.Generic;
using RustMapEditor.Variables;
using static RustMapEditor.Maths.Array;
using static AreaManager;
using static TerrainManager;
using static WorldSerialization;
using static ModManager;
public static class WorldConverter
{
public struct MapInfo
{
public int terrainRes;
public int splatRes;
public Vector3 size;
public float[,,] splatMap;
public float[,,] biomeMap;
public bool[,] alphaMap;
public TerrainInfo land;
public TerrainInfo water;
public TerrainMap<int> topology;
public PrefabData[] prefabData;
public PathData[] pathData;
public CircuitDataHolder circuitDataHolder;
public CircuitData[] circuitData;
public NPCData[] npcData;
public ModifierData modifierData;
}
public struct TerrainInfo
{
public float[,] heights;
}
public static MapInfo EmptyMap(int size, float landHeight, TerrainSplat.Enum ground = TerrainSplat.Enum.Grass, TerrainBiome.Enum biome = TerrainBiome.Enum.Temperate)
{
MapInfo terrains = new MapInfo();
int splatRes = Mathf.Clamp(Mathf.NextPowerOfTwo((int)(size * 0.50f)), 16, 2048);
List<PathData> paths = new List<PathData>();
List<PrefabData> prefabs = new List<PrefabData>();
List<CircuitData> circuits = new List<CircuitData>();
terrains.pathData = paths.ToArray();
terrains.prefabData = prefabs.ToArray();
terrains.circuitData = circuits.ToArray();
terrains.terrainRes = Mathf.NextPowerOfTwo((int)(size * 0.50f)) + 1;
terrains.size = new Vector3(size, 1000, size);
terrains.land.heights = SetValues(new float[terrains.terrainRes, terrains.terrainRes], landHeight / 1000f, new Area(0, terrains.terrainRes, 0, terrains.terrainRes));
terrains.water.heights = SetValues(new float[terrains.terrainRes, terrains.terrainRes], 500f / 1000f, new Area(0, terrains.terrainRes, 0, terrains.terrainRes));
terrains.splatRes = splatRes;
terrains.splatMap = new float[splatRes, splatRes, 8];
int gndIdx = TerrainSplat.TypeToIndex((int)ground);
Parallel.For(0, splatRes, i =>
{
for (int j = 0; j < splatRes; j++)
terrains.splatMap[i, j, gndIdx] = 1f;
});
terrains.biomeMap = new float[splatRes, splatRes, 4];
int biomeIdx = TerrainBiome.TypeToIndex((int)biome);
Parallel.For(0, splatRes, i =>
{
for (int j = 0; j < splatRes; j++)
terrains.biomeMap[i, j, biomeIdx] = 1f;
});
terrains.alphaMap = new bool[splatRes, splatRes];
Parallel.For(0, splatRes, i =>
{
for (int j = 0; j < splatRes; j++)
terrains.alphaMap[i, j] = true;
});
terrains.topology = new TerrainMap<int>(new byte[(int)Mathf.Pow(splatRes, 2) * 4 * 1], 1);
return terrains;
}
/// <summary>Converts the MapInfo and TerrainMaps into a Unity map format.</summary>
public static MapInfo ConvertMaps(MapInfo terrains, TerrainMap<byte> splatMap, TerrainMap<byte> biomeMap, TerrainMap<byte> alphaMap)
{
terrains.splatMap = new float[splatMap.res, splatMap.res, 8];
terrains.biomeMap = new float[biomeMap.res, biomeMap.res, 4];
terrains.alphaMap = new bool[alphaMap.res, alphaMap.res];
var groundTask = Task.Run(() =>
{
Parallel.For(0, terrains.splatRes, i =>
{
for (int j = 0; j < terrains.splatRes; j++)
for (int k = 0; k < 8; k++)
terrains.splatMap[i, j, k] = BitUtility.Byte2Float(splatMap[k, i, j]);
});
});
var biomeTask = Task.Run(() =>
{
Parallel.For(0, terrains.splatRes, i =>
{
for (int j = 0; j < terrains.splatRes; j++)
for (int k = 0; k < 4; k++)
terrains.biomeMap[i, j, k] = BitUtility.Byte2Float(biomeMap[k, i, j]);
});
});
var alphaTask = Task.Run(() =>
{
Parallel.For(0, terrains.splatRes, i =>
{
for (int j = 0; j < terrains.splatRes; j++)
{
if (alphaMap[0, i, j] > 0)
terrains.alphaMap[i, j] = true;
else
terrains.alphaMap[i, j] = false;
}
});
});
Task.WaitAll(groundTask, biomeTask, alphaTask);
return terrains;
}
/// <summary>Parses World Serialization and converts into MapInfo struct.</summary>
/// <param name="world">Serialization of the map file to parse.</param>
public static MapInfo WorldToTerrain(WorldSerialization world)
{
MapInfo terrains = new MapInfo();
var terrainSize = new Vector3(world.world.size, 1000, world.world.size);
var terrainMap = new TerrainMap<short>(world.GetMap("terrain").data, 1);
var heightMap = new TerrainMap<short>(world.GetMap("height").data, 1);
var waterMap = new TerrainMap<short>(world.GetMap("water").data, 1);
var splatMap = new TerrainMap<byte>(world.GetMap("splat").data, 8);
var topologyMap = new TerrainMap<int>(world.GetMap("topology").data, 1);
var biomeMap = new TerrainMap<byte>(world.GetMap("biome").data, 4);
var alphaMap = new TerrainMap<byte>(world.GetMap("alpha").data, 1);
terrains.topology = topologyMap;
terrains.pathData = world.world.paths.ToArray();
terrains.prefabData = world.world.prefabs.ToArray();
terrains.terrainRes = heightMap.res;
terrains.splatRes = splatMap.res;
terrains.size = terrainSize;
// Clear existing modding data and populate from world
ModManager.ClearModdingData();
foreach (var name in ModManager.GetKnownDataNames())
{
if (name == "buildingblocks"){
WorldSerialization.MapData buildData = world.GetMap(name);
ModManager.AddOrUpdateModdingData(name, buildData.data);
continue;
}
string hashedName = ModManager.MapDataName(world.world.prefabs.Count, name);
WorldSerialization.MapData mapData = world.GetMap(hashedName);
if (mapData != null)
{
mapData.name = name; // Correct name
ModManager.AddOrUpdateModdingData(name, mapData.data);
}
}
var heightTask = Task.Run(() => ShortMapToFloatArray(heightMap));
var waterTask = Task.Run(() => ShortMapToFloatArray(waterMap));
terrains = ConvertMaps(terrains, splatMap, biomeMap, alphaMap);
Task.WaitAll(heightTask, waterTask);
terrains.land.heights = heightTask.Result;
terrains.water.heights = waterTask.Result;
//terrains.land.heights = ShortMapToFloatArray(heightMap);
//terrains.water.heights = ShortMapToFloatArray(waterMap);
terrains = ConvertMaps(terrains, splatMap, biomeMap, alphaMap);
return terrains;
}
public static WorldSerialization CollectionToREPrefab(Transform parent)
{
WorldSerialization world = new WorldSerialization();
try
{
if (parent == null)
{
Debug.LogError("Parent Transform is null; no prefabs can be processed.");
return world;
}
// Collect all PrefabDataHolder components in the hierarchy (flattening nesting)
List<PrefabDataHolder> prefabHolders = new List<PrefabDataHolder>();
CollectPrefabDataHolders(parent, prefabHolders);
// Process each PrefabDataHolder and convert to local space relative to the parent
foreach (PrefabDataHolder holder in prefabHolders)
{
if (holder.prefabData != null)
{
// Create a copy of the prefab data
PrefabData localPrefab = holder.prefabData;
// Get the world position and rotation from the holder's transform
Vector3 worldPosition = holder.transform.position;
Quaternion worldRotation = holder.transform.rotation;
// Convert to local space relative to the parent
localPrefab.position = parent.InverseTransformPoint(worldPosition);
localPrefab.rotation = Quaternion.Inverse(parent.rotation) * worldRotation;
// Add the adjusted prefab to the REPrefab data
world.rePrefab.prefabs.Add(localPrefab);
}
}
// Initialize empty collections for other REPrefab components
world.rePrefab.electric.circuitData = new List<CircuitData>();
world.rePrefab.npcs.bots = new List<NPCData>();
world.rePrefab.modifiers = new ModifierData();
return world;
}
catch (NullReferenceException err)
{
Debug.LogError("Error during prefab conversion: " + err.Message);
return world;
}
}
/// <summary>Recursively collects all PrefabDataHolder components from a Transform and its children.</summary>
/// <param name="current">Current Transform to inspect.</param>
/// <param name="holders">List to store collected PrefabDataHolder components.</param>
private static void CollectPrefabDataHolders(Transform current, List<PrefabDataHolder> holders)
{
// Check if the current object has a PrefabDataHolder
PrefabDataHolder holder = current.GetComponent<PrefabDataHolder>();
if (holder != null)
{
holders.Add(holder);
}
// Recursively process all children
foreach (Transform child in current)
{
CollectPrefabDataHolders(child, holders);
}
}
/// <summary>Converts Unity terrains to WorldSerialization.</summary>
public static WorldSerialization TerrainToWorld(Terrain land, Terrain water, (int prefab, int path, int terrain) ID = default)
{
WorldSerialization world = new WorldSerialization();
world.world.size = (uint) land.terrainData.size.x;
var textureResolution = SplatMapRes;
byte[] splatBytes = new byte[textureResolution * textureResolution * 8];
var splatMap = new TerrainMap<byte>(splatBytes, 8);
var splatTask = Task.Run(() =>
{
Parallel.For(0, 8, i =>
{
for (int j = 0; j < textureResolution; j++)
for (int k = 0; k < textureResolution; k++)
splatMap[i, j, k] = BitUtility.Float2Byte(Ground[j, k, i]);
});
splatBytes = splatMap.ToByteArray();
});
byte[] biomeBytes = new byte[textureResolution * textureResolution * 4];
var biomeMap = new TerrainMap<byte>(biomeBytes, 4);
var biomeTask = Task.Run(() =>
{
Parallel.For(0, 4, i =>
{
for (int j = 0; j < textureResolution; j++)
for (int k = 0; k < textureResolution; k++)
biomeMap[i, j, k] = BitUtility.Float2Byte(Biome[j, k, i]);
});
biomeBytes = biomeMap.ToByteArray();
});
byte[] alphaBytes = new byte[textureResolution * textureResolution * 1];
var alphaMap = new TerrainMap<byte>(alphaBytes, 1);
bool[,] terrainHoles = GetAlphaMap();
var alphaTask = Task.Run(() =>
{
Parallel.For(0, textureResolution, i =>
{
for (int j = 0; j < textureResolution; j++)
alphaMap[0, i, j] = BitUtility.Bool2Byte(terrainHoles[i, j]);
});
alphaBytes = alphaMap.ToByteArray();
});
var topologyTask = Task.Run(() => TopologyData.SaveTopologyLayers());
foreach (PrefabDataHolder p in PrefabManager.CurrentMapPrefabs)
{
if (p.prefabData != null)
{
p.UpdatePrefabData(); // Updates the prefabdata before saving.
p.AlwaysBreakPrefabs();
world.world.prefabs.Insert(0, p.prefabData);
}
}
#if UNITY_EDITOR
Progress.Report(ID.prefab, 0.99f, "Saved " + PrefabManager.CurrentMapPrefabs.Length + " prefabs.");
#endif
foreach (PathDataHolder p in PathManager.CurrentMapPaths)
{
if (p.pathData != null)
{
p.pathData.nodes = new VectorData[p.transform.childCount];
for (int i = 0; i < p.transform.childCount; i++)
{
Transform g = p.transform.GetChild(i);
p.pathData.nodes[i] = g.position - MapOffset;
}
world.world.paths.Insert(0, p.pathData);
}
}
#if UNITY_EDITOR
Progress.Report(ID.path, 0.99f, "Saved " + PathManager.CurrentMapPaths.Length + " paths.");
#endif
byte[] landHeightBytes = FloatArrayToByteArray(land.terrainData.GetHeights(0, 0, HeightMapRes, HeightMapRes));
byte[] waterHeightBytes = FloatArrayToByteArray(water.terrainData.GetHeights(0, 0, HeightMapRes, HeightMapRes));
Task.WaitAll(splatTask, biomeTask, alphaTask, topologyTask);
#if UNITY_EDITOR
Progress.Report(ID.terrain, 0.99f, "Saved " + TerrainSize.x + " size map.");
#endif
// Add modding data from ModManager
var moddingData = ModManager.GetModdingData();
if (moddingData != null && moddingData.Count > 0)
{
foreach (var md in moddingData)
{
if (md.name == "buildingblocks")
{
world.AddMap(md.name, md.data);
}
else
{
// Normal case: Hash the name
string hashedName = ModManager.MapDataName(world.world.prefabs.Count, md.name);
world.AddMap(hashedName, md.data);
}
}
}
else
{
}
world.AddMap("terrain", landHeightBytes);
world.AddMap("height", landHeightBytes);
world.AddMap("water", waterHeightBytes);
world.AddMap("splat", splatBytes);
world.AddMap("biome", biomeBytes);
world.AddMap("alpha", alphaBytes);
world.AddMap("topology", TopologyData.GetTerrainMap().ToByteArray());
return world;
}
public static MapInfo WorldToREPrefab(WorldSerialization world)
{
MapInfo refab = new MapInfo();
refab.prefabData = world.rePrefab.prefabs.ToArray();
refab.circuitData = world.rePrefab.electric.circuitData.ToArray();
refab.npcData = world.rePrefab.npcs.bots.ToArray();
refab.modifierData = world.rePrefab.modifiers;
for (int k = 0; k < refab.circuitData.Length; k++)
{
refab.circuitData[k].connectionsIn = refab.circuitData[k].branchIn.ToArray();
refab.circuitData[k].connectionsOut = refab.circuitData[k].branchOut.ToArray();
}
return refab;
}
public static WorldSerialization TerrainToCustomPrefab((int prefab, int circuit) ID)
{
WorldSerialization world = new WorldSerialization();
try
{
if (PrefabManager.CurrentModifiers?.modifierData!= null)
world.rePrefab.modifiers = PrefabManager.CurrentModifiers.modifierData;
foreach(NPCDataHolder p in PrefabManager.CurrentMapNPCs)
{
if (p.bots != null)
{
world.rePrefab.npcs.bots.Insert(0, p.bots);
}
}
foreach (PrefabDataHolder p in PrefabManager.CurrentMapPrefabs)
{
if (p.prefabData != null)
{
//p.UpdatePrefabData();
p.AlwaysBreakPrefabs(); // Updates the prefabdata before saving.
world.rePrefab.prefabs.Add(p.prefabData);
}
}
foreach (CircuitDataHolder p in PrefabManager.CurrentMapElectrics)
{
if (p.circuitData != null)
{
p.UpdateCircuitData(); // Updates the circuitdata before saving.
world.rePrefab.electric.circuitData.Insert(0, p.circuitData);
}
}
#if UNITY_EDITOR
Progress.Report(ID.prefab, 0.99f, "Saved " + PrefabManager.CurrentMapPrefabs.Length + " prefabs.");
Progress.Report(ID.circuit, 0.99f, "Saved " + PrefabManager.CurrentMapPrefabs.Length + " circuits.");
#endif
return world;
}
catch(NullReferenceException err)
{
Debug.LogError(err.Message);
return world;
}
}
}