champions-of-luneraria/Assets/Luneraria/Editor/World/LuneWorldCapture.cs
Martin Stickelbroeck 7fa038a147 U5.2 Stufen: Zielqualität entlang der Lanes, einfachere Randzonen
Entscheidung des Projektinhabers vom 2. Oktober 2026: Weg 2 (Stufen)
statt Gleichziehen oder Drosseln. Der Lankorridor trägt die vollen
PBR-Sätze; Berge, Kluft und ferner Hintergrund behalten Farbe und
Steinschicht.

- LuneZones: Korridor von 22 m um eine Lane-Mittellinie, ein Gebäude
  oder die Grotte. NearLane entscheidet die Stufe eines Weltknotens.
- Surface-Shader _Quality: 1 bindet Albedo, Normal und ORM triplanar;
  0 lässt die neun Map-Samples weg und behält Farbe, Vertex-Tönung,
  Steinschicht und Emission — das U2-Aussehen der Berge.
- LuneMaterialFactory: Spec.Quality, TerrainEdge.mat für die umgebenden
  Berge; Randmaterialien binden keine PBR-Sätze.
- LuneWorldBuilder.ApplyTier: jeder Weltknoten jenseits des Korridors
  bekommt _Quality 0 über einen MaterialPropertyBlock. Gebäude,
  Einheiten, Wege, Wasser und der innere Boden bleiben Lane-Stufe.
- Nachweis: screenshots/u5/edge-mountains.png (Randstufe) und
  lane-and-edge.png (Übergang Lane → Rand in einem Bild).
- Leistung (macOS-Player, 116 Einheiten): 133 fps Übersicht, 180 fps
  Mid nah — auf dem Niveau des Slices, weil die Messfenster den
  Korridor sehen. Die Ersparnis liegt in Weitwinkeln und am Rand.
- Tests: EditMode 29/29, PlayMode 13/13, Kerntests 457/457. Keine
  Regeländerung.
- Dokumentation: U5-VISUAL (Entscheidung und Umsetzung), PORT-QA,
  PORT-STATUS, KNOWN-BEHAVIOR-DIFFERENCES.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 17:45:51 +02:00

141 lines
7.3 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using Luneraria.Core;
using Luneraria.View;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
namespace Luneraria.EditorTools
{
/// <summary>
/// Screenshots of the G1 map scene with the camera settings of the Godot G2 captures
/// (../saumwacht-godot/tools/luneraria/g2_views.gd), so both can be compared side by side:
/// 15 overview, 01 Blau Ancient, 05 Grenzwacht, 06 Bastion, 07 base towers, 10 grotto.
/// Unity -batchmode -projectPath . -executeMethod Luneraria.EditorTools.LuneWorldCapture.CaptureAll
/// </summary>
public static class LuneWorldCapture
{
public const int Width = 1920;
public const int Height = 1080;
struct View
{
public string Label;
public Vec2 At;
public float Zoom;
public int YawSteps;
}
public static void CaptureAll()
{
int code = 0;
try
{
EditorSceneManager.OpenScene(LuneWorldBuilder.ScenePath, OpenSceneMode.Single);
var ground = AssetDatabase.LoadAssetAtPath<LuneGround>(LuneWorldBuilder.GroundPath);
var map = new LuneMap(new LuneBalance());
Vec2 blue = map.CorePosition(0);
Vec2 red = map.CorePosition(1);
Vec2 outer = map.BuildingDef("rot_mid_outer").Position;
Vec2 inner = map.BuildingDef("rot_mid_inner").Position;
Vec2 baseA = map.BuildingDef("rot_base_1").Position;
Vec2 baseB = map.BuildingDef("rot_base_2").Position;
var views = new List<View>
{
new View { Label = "15-gesamtkarte", At = new Vec2(0, 0), Zoom = LuneCameraMath.OverviewZoom },
new View { Label = "01-ancient-100", At = blue + new Vec2(1.0, -1.0), Zoom = 28f, YawSteps = -1 },
new View { Label = "05-tower1-grenzwacht", At = outer + new Vec2(0, -2.4), Zoom = 14f },
new View { Label = "06-tower2-bastion", At = inner + new Vec2(0, -2.8), Zoom = 14f },
new View { Label = "07-basistuerme", At = (baseA + baseB) * .5 * .5 + red * .5, Zoom = 24f, YawSteps = -1 },
new View { Label = "10-grotte", At = map.GrottoCenter(), Zoom = 20f },
};
string folder = Path.Combine(Path.GetDirectoryName(Application.dataPath), "docs/port/screenshots/u2b");
Directory.CreateDirectory(folder);
Camera camera = Camera.main;
foreach (View view in views)
{
// Godot: look_at_point clamps the focus and puts it on the ground under it.
var focus = new Vec2(Mathf.Clamp((float)view.At.X, -LuneCameraMath.FocusLimit, LuneCameraMath.FocusLimit), Mathf.Clamp((float)view.At.Y, -LuneCameraMath.FocusLimit, LuneCameraMath.FocusLimit));
Vector3 point = LuneSpace.World(focus, ground.StandHeight(focus));
LuneCameraMath.Pose(point, view.Zoom, view.YawSteps * Mathf.PI * .25f, out Vector3 position, out Quaternion rotation);
camera.transform.SetPositionAndRotation(position, rotation);
Render(camera, Path.Combine(folder, view.Label + ".png"));
}
}
catch (Exception error)
{
Debug.LogError("LUNE_CAPTURE failed: " + error);
code = 1;
}
EditorApplication.Exit(code);
}
public static void Render(Camera camera, string file)
{
var target = new RenderTexture(Width, Height, 24, RenderTextureFormat.ARGB32) { antiAliasing = 4 };
camera.aspect = (float)Width / Height;
camera.targetTexture = target;
camera.Render();
RenderTexture.active = target;
var image = new Texture2D(Width, Height, TextureFormat.RGB24, false);
image.ReadPixels(new Rect(0, 0, Width, Height), 0, 0);
image.Apply();
File.WriteAllBytes(file, image.EncodeToPNG());
RenderTexture.active = null;
camera.targetTexture = null;
UnityEngine.Object.DestroyImmediate(target);
UnityEngine.Object.DestroyImmediate(image);
Debug.Log("LUNE_CAPTURE wrote " + file);
}
/// <summary>
/// U5 vertical slice: the Mid-Lane of the target-quality zone (docs/port/U5-VISUAL.md).
/// Godot G3 comparison shots live in ../saumwacht-godot/docs/luneraria/screenshots/g3/.
/// Unity -batchmode -projectPath . -executeMethod Luneraria.EditorTools.LuneWorldCapture.CaptureMidLane
/// </summary>
public static void CaptureMidLane()
{
int code = 0;
try
{
EditorSceneManager.OpenScene(LuneWorldBuilder.ScenePath, OpenSceneMode.Single);
var ground = AssetDatabase.LoadAssetAtPath<LuneGround>(LuneWorldBuilder.GroundPath);
var map = new LuneMap(new LuneBalance());
Vec2 blue = map.CorePosition(0);
Vec2 red = map.CorePosition(1);
Vec2 outer = map.BuildingDef("rot_mid_outer").Position;
Vec2 inner = map.BuildingDef("rot_mid_inner").Position;
var views = new List<View>
{
new View { Label = "mid-lane-overview", At = (outer + inner) * .5, Zoom = 30f },
new View { Label = "mid-lane-towers", At = outer + new Vec2(0, -2.5), Zoom = 16f },
new View { Label = "mid-lane-ancient", At = blue + new Vec2(1.5, -2.5), Zoom = 24f, YawSteps = -1 },
new View { Label = "mid-lane-grotto", At = map.GrottoCenter(), Zoom = 22f },
new View { Label = "mid-lane-wide", At = (blue + red) * .5, Zoom = 48f, YawSteps = -1 },
// Weg 2 edge tier: the mountains beyond the lane corridor keep colour and stone relief.
new View { Label = "edge-mountains", At = new Vec2(-55, 55), Zoom = 36f, YawSteps = -1 },
new View { Label = "lane-and-edge", At = new Vec2(-28, 28), Zoom = 56f },
};
string folder = Path.Combine(Path.GetDirectoryName(Application.dataPath), "docs/port/screenshots/u5");
Directory.CreateDirectory(folder);
Camera camera = Camera.main;
foreach (View view in views)
{
var focus = new Vec2(Mathf.Clamp((float)view.At.X, -LuneCameraMath.FocusLimit, LuneCameraMath.FocusLimit), Mathf.Clamp((float)view.At.Y, -LuneCameraMath.FocusLimit, LuneCameraMath.FocusLimit));
Vector3 point = LuneSpace.World(focus, ground.StandHeight(focus));
LuneCameraMath.Pose(point, view.Zoom, view.YawSteps * Mathf.PI * .25f, out Vector3 position, out Quaternion rotation);
camera.transform.SetPositionAndRotation(position, rotation);
Render(camera, Path.Combine(folder, view.Label + ".png"));
}
}
catch (Exception error)
{
Debug.LogError("LUNE_CAPTURE mid failed: " + error);
code = 1;
}
EditorApplication.Exit(code);
}
}
}