Fortsetzung des angefangenen U5.3-Stands vom 2. Oktober.
- Fünf neue Sätze aus bake_pbr.py (metal, cloth, hide, bone, wood) für
Türme, Ancients, Grotte und die Schlundkrone. Ringborkes Rinde (RB_Bark)
auf ihren eigenen UVs mit dem 4k-Satz (Rauigkeit zu ORM gepackt),
Normalen im Tangentenraum des Meshes. Aufnahmen aller drei Lanes.
- Befund: Seit U5 war die Welt halb so hell und ohne Grün (Mid-Lane
Helligkeit 0,27, Grün 0 %). Ursachen und Korrekturen:
- Die PBR-Albedos wurden über die Godot-Farben multipliziert (Rasen
etwa x0,05). Jetzt teilt der Shader jede Karte durch ihren Mittel-
wert; die Godot-Farbe bleibt der Durchschnitt, die Karte liefert
Struktur. Ebenso die Rauigkeit, wo das Material eine eigene hat.
- Normal- und ORM-Karten waren als sRGB-Farbe importiert. Jetzt
Normalkarte bzw. linear.
- bake_pbr.py verzerrte alle Muster 10:1 (Regenstreifen). Stein, Erde,
Laub, Wasser und die neuen Sätze ohne Maserung neu gebacken.
- Der Treffer-Flash ersetzte die Grundfarbe aller Materialplätze; im
Nahkampf waren die Armeen weiß und der Boss gelb. Jetzt Tönung der
eigenen Farbe je Platz, wenig Emission, 16 Stufen.
- Nebel 0,0085 verschleierte die Kartenansicht zu 35-50 %; jetzt 0,0045.
Ergebnis Mid-Lane: Helligkeit 0,41-0,44, Grün 31-39 % (U3: 0,45 / 27 %).
- Tests: EditMode 29/29, PlayMode 13/13, Verify 718/0, Kerntests .NET
457/457, Fingerabdrücke unverändert (DCE95C96556F3A1A, 7B8E43AEFC0D60C5).
- Leistung: A/B mit einem U5.2-Player unter gleicher Last, kein messbarer
Unterschied (91,4 gegen 93,8 fps Karte, 124,9 gegen 125,3 fps Mid).
- Messwerkzeug tools/port/png_stats.py; Doku U5-VISUAL (U5.3),
PORT-STATUS, PORT-QA; Bilder screenshots/u5.3/ als JPEG.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
561 lines
27 KiB
C#
561 lines
27 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using Luneraria.Core;
|
||
using Luneraria.View;
|
||
using Newtonsoft.Json.Linq;
|
||
using UnityEditor;
|
||
using UnityEditor.SceneManagement;
|
||
using UnityEngine;
|
||
using UnityEngine.Rendering;
|
||
using UnityEngine.Rendering.Universal;
|
||
|
||
namespace Luneraria.EditorTools
|
||
{
|
||
/// <summary>
|
||
/// Builds the G1 map scene (U2B) reproducibly from three sources:
|
||
/// the world bake of the Godot generator (terrain, lanes, platform, bridge, bases, landmarks,
|
||
/// vegetation, veins, grotto stairs, deployment areas, lights),
|
||
/// the FBX models of the Blender pipeline (towers, Ancients, grotto, props),
|
||
/// the C# core's map (building ids, positions and facing).
|
||
/// Unity -batchmode -projectPath . -executeMethod Luneraria.EditorTools.LuneWorldBuilder.BuildScene
|
||
/// The scene owns no rules; buildings carry views that the match binding (U2C) drives.
|
||
/// </summary>
|
||
public static class LuneWorldBuilder
|
||
{
|
||
public const string ScenePath = "Assets/Luneraria/Scenes/G1_Map.unity";
|
||
public const string GeneratedFolder = "Assets/Luneraria/World/Generated";
|
||
public const string GroundPath = "Assets/Luneraria/World/LuneGround.asset";
|
||
public const string VolumePath = "Assets/Luneraria/Settings/Rendering/LuneVolume.asset";
|
||
const string Art = "Assets/Luneraria/Art/";
|
||
|
||
public static void BuildScene()
|
||
{
|
||
int code = 0;
|
||
try
|
||
{
|
||
Build();
|
||
}
|
||
catch (Exception error)
|
||
{
|
||
Debug.LogError("LUNE_WORLD failed: " + error);
|
||
code = 1;
|
||
}
|
||
EditorApplication.Exit(code);
|
||
}
|
||
|
||
public static void Build()
|
||
{
|
||
LuneProjectSetup.Configure();
|
||
LuneWorldBake bake = LuneWorldBake.Load();
|
||
LuneMaterialSet materials = LuneMaterialFactory.Build(bake, out Dictionary<int, Material> worldMaterials);
|
||
LuneGround ground = BuildGround(bake);
|
||
Directory.CreateDirectory(GeneratedFolder);
|
||
_written.Clear();
|
||
Directory.CreateDirectory(Path.GetDirectoryName(ScenePath));
|
||
AssetDatabase.Refresh();
|
||
|
||
var scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
|
||
Environment(bake);
|
||
var world = new GameObject("World").transform;
|
||
Terrain(bake, world, worldMaterials);
|
||
int meshes = Nodes(bake, world, worldMaterials, materials);
|
||
var buildings = Buildings(materials, ground);
|
||
LuneUnitLibrary library = LuneUnitLibraryBuilder.Build(materials, ground);
|
||
var camera = new GameObject("Main Camera");
|
||
camera.tag = "MainCamera";
|
||
var unityCamera = camera.AddComponent<Camera>();
|
||
unityCamera.fieldOfView = LuneCameraMath.FieldOfView;
|
||
unityCamera.nearClipPlane = .5f;
|
||
unityCamera.farClipPlane = 700f;
|
||
camera.AddComponent<UniversalAdditionalCameraData>().renderPostProcessing = true;
|
||
LuneCameraMath.Pose(new Vector3(0f, ground.StandHeight(new Vec2(0f, 0f)), 0f), LuneCameraMath.OverviewZoom, 0f, out Vector3 position, out Quaternion rotation);
|
||
camera.transform.SetPositionAndRotation(position, rotation);
|
||
var rig = camera.AddComponent<LuneCameraRig>();
|
||
rig.Ground = ground;
|
||
rig.Zoom = LuneCameraMath.OverviewZoom;
|
||
// The match on the host and its views; the game controller with the HUD (U3) lets the
|
||
// human seat play. Tests and the benchmark start bot matches themselves.
|
||
var matchObject = new GameObject("Match");
|
||
var driver = matchObject.AddComponent<LuneMatchDriver>();
|
||
var view = matchObject.AddComponent<LuneMatchView>();
|
||
view.Driver = driver;
|
||
view.Library = library;
|
||
view.ViewCamera = unityCamera;
|
||
var game = matchObject.AddComponent<Luneraria.UI.LuneGame>();
|
||
game.Driver = driver;
|
||
game.View = view;
|
||
game.Rig = rig;
|
||
game.UiAssets = AssetDatabase.LoadAssetAtPath<Luneraria.UI.LuneUiAssets>(LuneUiAssetsBuilder.AssetPath);
|
||
var benchmark = matchObject.AddComponent<LuneBenchmark>();
|
||
benchmark.Driver = driver;
|
||
benchmark.View = view;
|
||
benchmark.Rig = rig;
|
||
// Meshes of an earlier build that this build no longer produces.
|
||
foreach (string stale in AssetDatabase.FindAssets("t:Mesh", new[] { GeneratedFolder }).Select(AssetDatabase.GUIDToAssetPath).Where(p => !_written.Contains(p)).ToList())
|
||
AssetDatabase.DeleteAsset(stale);
|
||
EditorSceneManager.SaveScene(scene, ScenePath);
|
||
var scenes = EditorBuildSettings.scenes.Where(s => s.path != ScenePath).ToList();
|
||
scenes.Insert(0, new EditorBuildSettingsScene(ScenePath, true));
|
||
EditorBuildSettings.scenes = scenes.ToArray();
|
||
AssetDatabase.SaveAssets();
|
||
Debug.Log(string.Format("LUNE_WORLD built {0}: {1} generated meshes, {2} buildings", ScenePath, meshes, buildings));
|
||
}
|
||
|
||
// ------------------------------------------------------------ ground heights
|
||
static LuneGround BuildGround(LuneWorldBake bake)
|
||
{
|
||
var ground = AssetDatabase.LoadAssetAtPath<LuneGround>(GroundPath);
|
||
if (ground == null)
|
||
{
|
||
ground = ScriptableObject.CreateInstance<LuneGround>();
|
||
AssetDatabase.CreateAsset(ground, GroundPath);
|
||
}
|
||
JToken inner = bake.Data["terrain"]["inner"];
|
||
ground.WalkExtent = (float)inner["extent"];
|
||
ground.WalkStep = (float)inner["step"];
|
||
ground.WalkSize = (int)inner["size"];
|
||
ground.Walk = bake.Floats(inner["walk"]);
|
||
JToken stand = bake.Data["stand"];
|
||
ground.StandExtent = (float)stand["extent"];
|
||
ground.StandStep = (float)stand["step"];
|
||
ground.StandSize = (int)stand["size"];
|
||
ground.Stand = bake.Floats(stand["heights"]);
|
||
EditorUtility.SetDirty(ground);
|
||
return ground;
|
||
}
|
||
|
||
// ------------------------------------------------------------ environment
|
||
static void Environment(LuneWorldBake bake)
|
||
{
|
||
JToken lights = bake.Data["lights"];
|
||
JProperty sunEntry = ((JObject)lights).Properties().First(p => p.Name != "fill" && p.Name != "environment");
|
||
Light sun = DirectionalLight("Sun", sunEntry.Value, true);
|
||
sun.shadowStrength = 1f;
|
||
// U5: crisp cascades over the Mid-Lane slice (was Unity's default 2).
|
||
sun.shadowBias = .02f;
|
||
sun.shadowNormalBias = .35f;
|
||
sun.shadowNearPlane = .2f;
|
||
DirectionalLight("Fill", lights["fill"], false);
|
||
JToken environment = lights["environment"];
|
||
RenderSettings.ambientMode = AmbientMode.Flat;
|
||
RenderSettings.ambientLight = LuneMaterialFactory.GammaEncoded(LuneMaterialFactory.ColorOf(environment["ambient_color"]), (float)environment["ambient_energy"] * 1.6f);
|
||
// U5: aerial depth over the valley; the Mid-Lane reads further away than the towers.
|
||
// U5.3: density .0085 veiled the map view (zoom 64–70, 70–90 m away) by 35–50 % in
|
||
// the fog colour and took the colour out of the armies; .0045 keeps 10–15 % there.
|
||
RenderSettings.fog = true;
|
||
RenderSettings.fogMode = FogMode.ExponentialSquared;
|
||
RenderSettings.fogColor = LuneMaterialFactory.Hex("6a7f96");
|
||
RenderSettings.fogDensity = .0045f;
|
||
RenderSettings.sun = sun;
|
||
const string skyPath = "Assets/Luneraria/Settings/Rendering/LuneSky.mat";
|
||
var sky = AssetDatabase.LoadAssetAtPath<Material>(skyPath);
|
||
if (sky == null)
|
||
{
|
||
sky = new Material(Shader.Find("Skybox/Procedural"));
|
||
AssetDatabase.CreateAsset(sky, skyPath);
|
||
}
|
||
sky.SetColor("_SkyTint", LuneMaterialFactory.Hex("2a3d58"));
|
||
sky.SetColor("_GroundColor", LuneMaterialFactory.Hex("1a1c24"));
|
||
sky.SetFloat("_Exposure", 1.05f);
|
||
sky.SetFloat("_AtmosphereThickness", .8f);
|
||
EditorUtility.SetDirty(sky);
|
||
RenderSettings.skybox = sky;
|
||
RenderSettings.defaultReflectionMode = DefaultReflectionMode.Skybox;
|
||
RenderSettings.reflectionIntensity = 1f;
|
||
var volume = new GameObject("PostProcess").AddComponent<Volume>();
|
||
volume.isGlobal = true;
|
||
volume.sharedProfile = VolumeProfile();
|
||
}
|
||
|
||
/// <summary>Godot light: rotation_degrees (Euler YXZ), shining along its local -Z.</summary>
|
||
static Light DirectionalLight(string name, JToken entry, bool shadows)
|
||
{
|
||
var degrees = entry["rotation_degrees"];
|
||
float x = (float)degrees[0] * Mathf.Deg2Rad;
|
||
float y = (float)degrees[1] * Mathf.Deg2Rad;
|
||
// Ry(y) * Rx(x) * (0, 0, -1)
|
||
var afterX = new Vector3(0f, Mathf.Sin(x), -Mathf.Cos(x));
|
||
var godotDirection = new Vector3(afterX.x * Mathf.Cos(y) + afterX.z * Mathf.Sin(y), afterX.y, -afterX.x * Mathf.Sin(y) + afterX.z * Mathf.Cos(y));
|
||
var light = new GameObject(name).AddComponent<Light>();
|
||
light.type = LightType.Directional;
|
||
light.transform.rotation = Quaternion.LookRotation(LuneSpace.FromGodot(godotDirection), Vector3.up);
|
||
light.color = LuneMaterialFactory.ColorOf(entry["color"]);
|
||
light.intensity = (float)entry["energy"];
|
||
light.shadows = shadows ? LightShadows.Soft : LightShadows.None;
|
||
return light;
|
||
}
|
||
|
||
static T Override<T>(VolumeProfile profile) where T : VolumeComponent
|
||
{
|
||
if (profile.TryGet(out T component))
|
||
return component;
|
||
component = profile.Add<T>(true);
|
||
AssetDatabase.AddObjectToAsset(component, profile);
|
||
return component;
|
||
}
|
||
|
||
/// <summary>ACES tone mapping, glow and colour adjustment of scripts/presentation/lighting.gd (U5 Mid-Lane).</summary>
|
||
static VolumeProfile VolumeProfile()
|
||
{
|
||
var profile = AssetDatabase.LoadAssetAtPath<VolumeProfile>(VolumePath);
|
||
if (profile == null)
|
||
{
|
||
profile = ScriptableObject.CreateInstance<VolumeProfile>();
|
||
AssetDatabase.CreateAsset(profile, VolumePath);
|
||
}
|
||
// Updated in place: recreating the overrides gave them new file ids on every build.
|
||
var tonemapping = Override<Tonemapping>(profile);
|
||
tonemapping.mode.Override(TonemappingMode.ACES);
|
||
var bloom = Override<Bloom>(profile);
|
||
bloom.threshold.Override(1.15f);
|
||
bloom.intensity.Override(.62f);
|
||
bloom.scatter.Override(.68f);
|
||
var colors = Override<ColorAdjustments>(profile);
|
||
colors.contrast.Override(14f);
|
||
colors.saturation.Override(16f);
|
||
// U5: a cool valley shadow, warm sun on stone — stylized realism of the reference MOBAs.
|
||
var white = Override<WhiteBalance>(profile);
|
||
white.temperature.Override(8f);
|
||
white.tint.Override(-4f);
|
||
var vignette = Override<Vignette>(profile);
|
||
vignette.intensity.Override(.18f);
|
||
vignette.smoothness.Override(.4f);
|
||
EditorUtility.SetDirty(profile);
|
||
return profile;
|
||
}
|
||
|
||
// ------------------------------------------------------------ terrain
|
||
static void Terrain(LuneWorldBake bake, Transform world, Dictionary<int, Material> materials)
|
||
{
|
||
Material terrain = AssetDatabase.LoadAssetAtPath<Material>(LuneMaterialFactory.Folder + "/World/Terrain.mat");
|
||
Material mountains = AssetDatabase.LoadAssetAtPath<Material>(LuneMaterialFactory.Folder + "/World/TerrainEdge.mat") ?? terrain;
|
||
var group = new GameObject("Terrain").transform;
|
||
group.SetParent(world, false);
|
||
foreach (string key in new[] { "inner", "outer" })
|
||
{
|
||
JToken grid = bake.Data["terrain"][key];
|
||
Mesh mesh = GridMesh(key == "inner" ? "Ground" : "Mountains", bake.Floats(grid["ground"]), bake.Floats(grid["colors"], 4),
|
||
(int)grid["size"], (float)grid["extent"], (float)grid["step"], (float)grid["hole"]);
|
||
string label = mesh.name;
|
||
mesh = Save(mesh);
|
||
// Weg 2: the walkable ground is the lane tier, the surrounding mountains the edge tier.
|
||
var go = Place(label, group, mesh, new[] { key == "inner" ? terrain : mountains }, key == "inner");
|
||
go.isStatic = true;
|
||
}
|
||
}
|
||
|
||
/// <summary>Port of LuneTerrain._mesh: grid with alternating diagonals, normals from neighbours.</summary>
|
||
static Mesh GridMesh(string name, float[] heights, float[] colors, int n, float extent, float step, float hole)
|
||
{
|
||
var vertices = new Vector3[n * n];
|
||
var normals = new Vector3[n * n];
|
||
var tints = new Color[n * n];
|
||
for (int j = 0; j < n; j++)
|
||
{
|
||
for (int i = 0; i < n; i++)
|
||
{
|
||
int index = j * n + i;
|
||
vertices[index] = LuneSpace.FromGodot(new Vector3(-extent + i * step, heights[index], -extent + j * step));
|
||
float left = heights[j * n + Math.Max(0, i - 1)];
|
||
float right = heights[j * n + Math.Min(n - 1, i + 1)];
|
||
float up = heights[Math.Max(0, j - 1) * n + i];
|
||
float down = heights[Math.Min(n - 1, j + 1) * n + i];
|
||
normals[index] = LuneSpace.FromGodot(new Vector3(left - right, 2f * step, up - down).normalized);
|
||
tints[index] = new Color(colors[index * 4], colors[index * 4 + 1], colors[index * 4 + 2], colors[index * 4 + 3]);
|
||
}
|
||
}
|
||
var indices = new List<int>((n - 1) * (n - 1) * 6);
|
||
for (int j = 0; j < n - 1; j++)
|
||
{
|
||
for (int i = 0; i < n - 1; i++)
|
||
{
|
||
if (hole > 0f)
|
||
{
|
||
float cx = Mathf.Abs(-extent + (i + .5f) * step);
|
||
float cz = Mathf.Abs(-extent + (j + .5f) * step);
|
||
if (Mathf.Max(cx, cz) < hole)
|
||
continue;
|
||
}
|
||
int a = j * n + i;
|
||
int b = a + 1;
|
||
int c = a + n;
|
||
int d = c + 1;
|
||
if ((i + j) % 2 == 0)
|
||
indices.AddRange(new[] { a, b, d, a, d, c });
|
||
else
|
||
indices.AddRange(new[] { a, b, c, b, d, c });
|
||
}
|
||
}
|
||
var mesh = new Mesh { name = name, indexFormat = IndexFormat.UInt32 };
|
||
mesh.SetVertices(vertices);
|
||
mesh.SetNormals(normals);
|
||
mesh.SetColors(tints);
|
||
mesh.SetTriangles(indices, 0);
|
||
mesh.RecalculateBounds();
|
||
return mesh;
|
||
}
|
||
|
||
// ------------------------------------------------------------ baked nodes
|
||
static int Nodes(LuneWorldBake bake, Transform world, Dictionary<int, Material> worldMaterials, LuneMaterialSet set)
|
||
{
|
||
var meshes = new Dictionary<int, Mesh>();
|
||
var bakedMeshes = (JArray)bake.Data["meshes"];
|
||
var props = PropLibrary(set);
|
||
var map = new LuneMap(new LuneBalance());
|
||
foreach (JObject node in bake.Data["nodes"])
|
||
{
|
||
string name = (string)node["name"];
|
||
if (name.StartsWith("@"))
|
||
name = "Part";
|
||
Mesh mesh;
|
||
Material[] materials;
|
||
if (node["prop"] != null)
|
||
{
|
||
var prop = props[(string)node["prop"]];
|
||
mesh = prop.Key;
|
||
materials = prop.Value;
|
||
}
|
||
else
|
||
{
|
||
int index = (int)node["mesh"];
|
||
var baked = (JObject)bakedMeshes[index];
|
||
if (!meshes.TryGetValue(index, out mesh))
|
||
{
|
||
mesh = Save(BakedMesh(bake, baked, name + "_" + index));
|
||
meshes[index] = mesh;
|
||
}
|
||
var surfaces = (JArray)baked["surfaces"];
|
||
materials = new Material[surfaces.Count];
|
||
for (int s = 0; s < surfaces.Count; s++)
|
||
{
|
||
JToken reference = node["material"] ?? surfaces[s]["material"];
|
||
materials[s] = reference != null && worldMaterials.TryGetValue((int)reference, out Material found) ? found : null;
|
||
}
|
||
if (name.StartsWith("Vein_"))
|
||
{
|
||
JToken parameters = node["instance_params"];
|
||
materials = new[] { LuneMaterialFactory.VeinMaterial(name, LuneMaterialFactory.ColorOf(parameters["tint"]), (float)parameters["seed"]) };
|
||
}
|
||
}
|
||
var transforms = (JArray)node["transforms"];
|
||
bool shadows = (bool)node["cast_shadow"];
|
||
bool visible = (bool)node["visible"];
|
||
var group = new GameObject(name).transform;
|
||
group.SetParent(world, false);
|
||
for (int k = 0; k < transforms.Count; k++)
|
||
{
|
||
float[] t = LuneWorldBake.Transform(transforms[k]);
|
||
LuneSpace.FromGodot(t, 0, out Vector3 position, out Quaternion rotation, out Vector3 scale);
|
||
GameObject go = transforms.Count == 1 ? group.gameObject : new GameObject(name + "_" + k);
|
||
if (transforms.Count > 1)
|
||
go.transform.SetParent(group, false);
|
||
go.transform.localPosition = position;
|
||
go.transform.localRotation = rotation;
|
||
go.transform.localScale = scale;
|
||
AddRenderer(go, mesh, materials, shadows);
|
||
ApplyTier(map, go);
|
||
go.isStatic = visible;
|
||
}
|
||
group.gameObject.SetActive(visible);
|
||
}
|
||
return meshes.Count + 2;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Weg 2: a node beyond the lane corridor keeps the edge tier (Surface _Quality 0),
|
||
/// so the mountains and rim vegetation skip the PBR maps the slice pays for.
|
||
/// </summary>
|
||
static void ApplyTier(LuneMap map, GameObject go)
|
||
{
|
||
float quality = LuneZones.QualityOfWorld(map, go.transform.position);
|
||
if (quality > .5f)
|
||
return;
|
||
var block = new MaterialPropertyBlock();
|
||
block.SetFloat("_Quality", 0f);
|
||
foreach (Renderer renderer in go.GetComponentsInChildren<Renderer>(true))
|
||
renderer.SetPropertyBlock(block);
|
||
}
|
||
|
||
/// <summary>Prop meshes of map_props.fbx by name, with their surfaces dressed (ENV_* materials).</summary>
|
||
static Dictionary<string, KeyValuePair<Mesh, Material[]>> PropLibrary(LuneMaterialSet set)
|
||
{
|
||
var result = new Dictionary<string, KeyValuePair<Mesh, Material[]>>();
|
||
var root = AssetDatabase.LoadAssetAtPath<GameObject>(Art + "Environment/map_props.fbx");
|
||
foreach (MeshFilter filter in root.GetComponentsInChildren<MeshFilter>(true))
|
||
{
|
||
var renderer = filter.GetComponent<MeshRenderer>();
|
||
Material[] materials = renderer.sharedMaterials.Select(m => set.Get(LuneMaterialSet.KeyOf(m), 2) ?? m).ToArray();
|
||
result[filter.gameObject.name] = new KeyValuePair<Mesh, Material[]>(filter.sharedMesh, materials);
|
||
}
|
||
return result;
|
||
}
|
||
|
||
/// <summary>A baked Godot mesh in Unity: x mirrored (with the handedness change the picture and
|
||
/// the clockwise front faces are kept, so the index order stays).</summary>
|
||
static Mesh BakedMesh(LuneWorldBake bake, JObject baked, string name)
|
||
{
|
||
var vertices = new List<Vector3>();
|
||
var normals = new List<Vector3>();
|
||
var uvs = new List<Vector2>();
|
||
var colors = new List<Color>();
|
||
var submeshes = new List<int[]>();
|
||
bool hasNormals = true;
|
||
bool hasUvs = true;
|
||
bool hasColors = true;
|
||
foreach (JObject surface in baked["surfaces"])
|
||
{
|
||
int start = vertices.Count;
|
||
float[] v = bake.Floats(surface["vertices"], 3);
|
||
int count = v.Length / 3;
|
||
for (int i = 0; i < count; i++)
|
||
vertices.Add(LuneSpace.FromGodot(new Vector3(v[i * 3], v[i * 3 + 1], v[i * 3 + 2])));
|
||
if (surface["normals"] != null)
|
||
{
|
||
float[] n = bake.Floats(surface["normals"], 3);
|
||
for (int i = 0; i < count; i++)
|
||
normals.Add(LuneSpace.FromGodot(new Vector3(n[i * 3], n[i * 3 + 1], n[i * 3 + 2])));
|
||
}
|
||
else
|
||
{
|
||
hasNormals = false;
|
||
}
|
||
if (surface["uvs"] != null)
|
||
{
|
||
float[] uv = bake.Floats(surface["uvs"], 2);
|
||
for (int i = 0; i < count; i++)
|
||
uvs.Add(new Vector2(uv[i * 2], uv[i * 2 + 1]));
|
||
}
|
||
else
|
||
{
|
||
hasUvs = false;
|
||
for (int i = 0; i < count; i++)
|
||
uvs.Add(Vector2.zero);
|
||
}
|
||
if (surface["colors"] != null)
|
||
{
|
||
float[] c = bake.Floats(surface["colors"], 4);
|
||
for (int i = 0; i < count; i++)
|
||
colors.Add(new Color(c[i * 4], c[i * 4 + 1], c[i * 4 + 2], c[i * 4 + 3]));
|
||
}
|
||
else
|
||
{
|
||
hasColors = false;
|
||
for (int i = 0; i < count; i++)
|
||
colors.Add(Color.white);
|
||
}
|
||
int[] indices = surface["indices"] != null ? bake.Ints(surface["indices"]) : Enumerable.Range(0, count).ToArray();
|
||
submeshes.Add(indices.Select(i => i + start).ToArray());
|
||
}
|
||
var mesh = new Mesh { name = name, indexFormat = vertices.Count > 65000 ? IndexFormat.UInt32 : IndexFormat.UInt16 };
|
||
mesh.SetVertices(vertices);
|
||
if (hasUvs)
|
||
mesh.SetUVs(0, uvs);
|
||
if (hasColors)
|
||
mesh.SetColors(colors);
|
||
mesh.subMeshCount = submeshes.Count;
|
||
for (int s = 0; s < submeshes.Count; s++)
|
||
mesh.SetTriangles(submeshes[s], s);
|
||
if (hasNormals && normals.Count == vertices.Count)
|
||
mesh.SetNormals(normals);
|
||
else
|
||
mesh.RecalculateNormals();
|
||
mesh.RecalculateBounds();
|
||
return mesh;
|
||
}
|
||
|
||
static readonly HashSet<string> _written = new HashSet<string>();
|
||
|
||
/// <summary>Writes a generated mesh; an existing asset is updated in place so its GUID (and the
|
||
/// scene's reference) stays the same from build to build.</summary>
|
||
static Mesh Save(Mesh mesh)
|
||
{
|
||
string path = GeneratedFolder + "/" + mesh.name + ".asset";
|
||
if (!_written.Add(path))
|
||
throw new InvalidOperationException("duplicate generated mesh " + path);
|
||
var existing = AssetDatabase.LoadAssetAtPath<Mesh>(path);
|
||
if (existing == null)
|
||
{
|
||
AssetDatabase.CreateAsset(mesh, path);
|
||
return mesh;
|
||
}
|
||
EditorUtility.CopySerialized(mesh, existing);
|
||
EditorUtility.SetDirty(existing);
|
||
UnityEngine.Object.DestroyImmediate(mesh);
|
||
return existing;
|
||
}
|
||
|
||
static GameObject Place(string name, Transform parent, Mesh mesh, Material[] materials, bool shadows)
|
||
{
|
||
var go = new GameObject(name);
|
||
go.transform.SetParent(parent, false);
|
||
AddRenderer(go, mesh, materials, shadows);
|
||
return go;
|
||
}
|
||
|
||
static void AddRenderer(GameObject go, Mesh mesh, Material[] materials, bool shadows)
|
||
{
|
||
go.AddComponent<MeshFilter>().sharedMesh = mesh;
|
||
var renderer = go.AddComponent<MeshRenderer>();
|
||
renderer.sharedMaterials = materials;
|
||
renderer.shadowCastingMode = shadows ? ShadowCastingMode.On : ShadowCastingMode.Off;
|
||
}
|
||
|
||
// ------------------------------------------------------------ strategic buildings
|
||
static int Buildings(LuneMaterialSet materials, LuneGround ground)
|
||
{
|
||
var root = new GameObject("Buildings").transform;
|
||
var map = new LuneMap(new LuneBalance());
|
||
const float PlazaLift = .08f;
|
||
int count = 0;
|
||
foreach (BuildingDef definition in map.Buildings)
|
||
{
|
||
Vec2 at = definition.Position;
|
||
string asset;
|
||
Vec2 facing;
|
||
float height = ground.WalkHeight(at);
|
||
if (definition.Tier == "core")
|
||
{
|
||
asset = "ancient";
|
||
facing = (-at).Normalized();
|
||
height += PlazaLift;
|
||
}
|
||
else if (definition.Tier == "base")
|
||
{
|
||
asset = "kernwacht";
|
||
facing = (at - map.CorePosition(definition.Team)).Normalized();
|
||
}
|
||
else
|
||
{
|
||
asset = definition.Tier == "outer" ? "grenzwacht" : "bastion";
|
||
facing = (map.LanePoint(definition.Team, definition.Lane, definition.Progress) - at).Normalized();
|
||
}
|
||
var view = new GameObject(definition.Id).AddComponent<LuneBuildingView>();
|
||
view.transform.SetParent(root, false);
|
||
view.transform.position = LuneSpace.World(at, height);
|
||
view.BuildingId = definition.Id;
|
||
view.Team = definition.Team;
|
||
view.Tier = definition.Tier;
|
||
GameObject model = Model(asset, view.transform, materials, definition.Team);
|
||
model.transform.localRotation = LuneSpace.Facing(facing);
|
||
view.Show(LuneBuildingView.Look.Intact);
|
||
count++;
|
||
}
|
||
var grotto = new GameObject("BossGrotto").transform;
|
||
grotto.SetParent(root, false);
|
||
grotto.position = LuneSpace.World(LuneMap.Grotto, 1.7f);
|
||
Model("bossgrotte", grotto, materials, 2);
|
||
return count + 1;
|
||
}
|
||
|
||
static GameObject Model(string asset, Transform parent, LuneMaterialSet materials, int team)
|
||
{
|
||
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(Art + "Buildings/" + asset + ".fbx");
|
||
var model = (GameObject)PrefabUtility.InstantiatePrefab(prefab, parent);
|
||
model.name = "Model";
|
||
materials.Dress(model, team);
|
||
return model;
|
||
}
|
||
}
|
||
}
|