Integrate Aetheria reactive state worktree

This commit is contained in:
Meta Vens 2026-06-28 01:18:21 +01:00
commit 65133ac8a0
61 changed files with 3427 additions and 402 deletions

View file

@ -67,8 +67,8 @@ public sealed class AetheriaDaemonIndirectRenderer : MonoBehaviour
foreach (var renderGroup in renderGroups)
{
_seenGroupIds.Add(renderGroup.GroupId);
if (!assetCatalog.TryResolveMesh(renderGroup.MeshKey, out var mesh) ||
!assetCatalog.TryResolveMaterial(renderGroup.MaterialKey, out var material))
if (!TryResolveMesh(renderGroup, out var mesh) ||
!TryResolveMaterial(renderGroup, out var material))
{
if (logMissingAssets)
{
@ -237,4 +237,28 @@ public sealed class AetheriaDaemonIndirectRenderer : MonoBehaviour
_staleGroupIds.Clear();
}
private bool TryResolveMesh(AetheriaRuntimeDaemonRenderGroupDocument renderGroup, out Mesh mesh)
{
if (renderGroup?.MeshAsset != null &&
!string.IsNullOrWhiteSpace(renderGroup.MeshAsset.AssetKey) &&
assetCatalog.TryResolveMesh(renderGroup.MeshAsset, out mesh))
{
return true;
}
return assetCatalog.TryResolveMesh(renderGroup?.MeshKey, out mesh);
}
private bool TryResolveMaterial(AetheriaRuntimeDaemonRenderGroupDocument renderGroup, out Material material)
{
if (renderGroup?.MaterialAsset != null &&
!string.IsNullOrWhiteSpace(renderGroup.MaterialAsset.AssetKey) &&
assetCatalog.TryResolveMaterial(renderGroup.MaterialAsset, out material))
{
return true;
}
return assetCatalog.TryResolveMaterial(renderGroup?.MaterialKey, out material);
}
}

View file

@ -1,4 +1,5 @@
using System;
using GameCult.Aetheria.State.Verse;
using UnityEngine;
[CreateAssetMenu(
@ -33,6 +34,11 @@ public sealed class AetheriaDaemonRenderAssetCatalog : ScriptableObject
return mesh != null;
}
public bool TryResolveMesh(AetheriaRuntimeAssetRef asset, out Mesh mesh)
{
return TryResolveMesh(ResolveLocalKey(asset), out mesh);
}
public bool TryResolveMaterial(string key, out Material material)
{
material = null;
@ -54,6 +60,22 @@ public sealed class AetheriaDaemonRenderAssetCatalog : ScriptableObject
return material != null;
}
public bool TryResolveMaterial(AetheriaRuntimeAssetRef asset, out Material material)
{
return TryResolveMaterial(ResolveLocalKey(asset), out material);
}
public bool TryResolveTexture(AetheriaRuntimeAssetRef asset, out Texture2D texture)
{
texture = null;
var key = ResolveLocalKey(asset);
if (string.IsNullOrWhiteSpace(key))
return false;
texture = Resources.Load<Texture2D>(NormalizeResourceKey(key));
return texture != null;
}
private static string NormalizeResourceKey(string key)
{
const string resourcesPrefix = "resources://";
@ -65,6 +87,17 @@ public sealed class AetheriaDaemonRenderAssetCatalog : ScriptableObject
return key;
}
private static string ResolveLocalKey(AetheriaRuntimeAssetRef asset)
{
if (asset == null)
return "";
if (string.Equals(asset.Transport, AetheriaRuntimeAssetTransports.Resources, StringComparison.OrdinalIgnoreCase))
return asset.Uri;
return string.IsNullOrWhiteSpace(asset.Uri) ? asset.AssetKey : asset.Uri;
}
[Serializable]
private struct MeshEntry
{

View file

@ -0,0 +1,137 @@
using GameCult.Aetheria.State.Verse;
using Unity.Mathematics;
using UnityEngine;
public sealed class AetheriaRenderSplatBuffer : System.IDisposable
{
public const string SplatBufferPropertyName = "_AetheriaSplats";
public const string SplatCountPropertyName = "_AetheriaSplatCount";
public const string ViewportToClipPropertyName = "_AetheriaViewportToClip";
public const string ChannelFilterPropertyName = "_AetheriaChannelFilter";
private const int SplatStrideBytes = 80;
private AetheriaRenderSplat[] _splats = System.Array.Empty<AetheriaRenderSplat>();
private GraphicsBuffer _buffer;
private int _capacity;
public int Count { get; private set; }
public GraphicsBuffer Buffer => _buffer;
public bool HasGpuBuffer => _buffer != null && _buffer.IsValid() && Count > 0;
public bool Upload(AetheriaRuntimeRenderSplatSoa splats, int channelFilter = -1, int layerFilter = -1)
{
Count = Pack(splats, channelFilter, layerFilter);
EnsureCapacity(Count);
if (Count <= 0)
return false;
_buffer.SetData(_splats, 0, 0, Count);
return true;
}
private int Pack(AetheriaRuntimeRenderSplatSoa splats, int channelFilter, int layerFilter)
{
if (splats == null || splats.Count <= 0)
return 0;
EnsureCpuCapacity(splats.Count);
var count = 0;
for (var i = 0; i < splats.Count; i++)
{
var channel = Read(splats.Channel, i, 0);
if (channelFilter >= 0 && channel != channelFilter)
continue;
var layerIndex = Read(splats.LayerIndex, i, -1);
if (layerFilter >= 0 && layerIndex != layerFilter)
continue;
var halfExtentX = (float)Read(splats.HalfExtentX, i, 0);
var halfExtentY = (float)Read(splats.HalfExtentY, i, 0);
if (halfExtentX <= 0 || halfExtentY <= 0)
continue;
_splats[count++] = new AetheriaRenderSplat
{
CenterHalfExtent = new float4(
(float)Read(splats.CenterX, i, 0),
(float)Read(splats.CenterY, i, 0),
halfExtentX,
halfExtentY),
RotationChannelFalloff = new float4(
(float)Read(splats.RotationCos, i, 1),
(float)Read(splats.RotationSin, i, 0),
channel,
Read(splats.Falloff, i, AetheriaRuntimeRenderSplatFalloffs.Smooth)),
LayerSource = new float4(
layerIndex,
Read(splats.SourceKind, i, AetheriaRuntimeRenderSplatSourceKinds.Constant),
(float)Read(splats.AnimationSpeed, i, 0),
(float)Read(splats.SourceFlags, i, 0)),
SourceFrequencyPhase = new float4(
(float)Read(splats.FrequencyX, i, 1),
(float)Read(splats.FrequencyY, i, 1),
(float)Read(splats.PhaseX, i, 0),
(float)Read(splats.PhaseY, i, 0)),
Value = new float4(
(float)Read(splats.ValueR, i, 0),
(float)Read(splats.ValueG, i, 0),
(float)Read(splats.ValueB, i, 0),
(float)Read(splats.ValueA, i, 1))
};
}
return count;
}
private void EnsureCpuCapacity(int count)
{
if (_splats.Length >= count)
return;
_splats = new AetheriaRenderSplat[math.ceilpow2(math.max(1, count))];
}
private void EnsureCapacity(int count)
{
if (_buffer != null && _buffer.IsValid() && _capacity >= count)
return;
_buffer?.Release();
_buffer = null;
_capacity = 0;
if (count <= 0)
return;
_capacity = math.ceilpow2(math.max(1, count));
_buffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, _capacity, SplatStrideBytes);
}
public void Dispose()
{
_buffer?.Release();
_buffer = null;
_capacity = 0;
Count = 0;
}
private static double Read(System.Collections.Generic.IReadOnlyList<double> values, int index, double fallback)
{
return values != null && index >= 0 && index < values.Count ? values[index] : fallback;
}
private static int Read(System.Collections.Generic.IReadOnlyList<int> values, int index, int fallback)
{
return values != null && index >= 0 && index < values.Count ? values[index] : fallback;
}
private struct AetheriaRenderSplat
{
public float4 CenterHalfExtent;
public float4 RotationChannelFalloff;
public float4 LayerSource;
public float4 SourceFrequencyPhase;
public float4 Value;
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 32e6f17486f34e8c8c5f7640bff319af
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,241 @@
using System;
using System.Collections.Generic;
using GameCult.Aetheria.State.Verse;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Experimental.Rendering;
using UnityEngine.UI;
public sealed class AetheriaRenderSplatLayerRenderer : MonoBehaviour
{
[SerializeField]
private AetheriaRenderSplatRasterizer rasterizer;
[SerializeField]
private AetheriaRenderSplatLayerBinding[] bindings =
{
new AetheriaRenderSplatLayerBinding
{
LayerKey = AetheriaRuntimeRenderSplatLayerKeys.GravityHeight,
MaterialTextureProperty = "_DetailTex",
GlobalTextureName = "_AetheriaGravityHeight"
},
new AetheriaRenderSplatLayerBinding
{
LayerKey = AetheriaRuntimeRenderSplatLayerKeys.FogTint,
MaterialTextureProperty = "_DetailTex",
GlobalTextureName = "_NebulaTint"
},
new AetheriaRenderSplatLayerBinding
{
LayerKey = AetheriaRuntimeRenderSplatLayerKeys.FogSurfaceHeight,
GlobalTextureName = "_NebulaSurfaceHeight",
WidthScale = 0.5f,
HeightScale = 0.5f
},
new AetheriaRenderSplatLayerBinding
{
LayerKey = AetheriaRuntimeRenderSplatLayerKeys.FogPatchHeight,
GlobalTextureName = "_NebulaPatchHeight",
WidthScale = 0.5f,
HeightScale = 0.5f
},
new AetheriaRenderSplatLayerBinding
{
LayerKey = AetheriaRuntimeRenderSplatLayerKeys.FogPatch,
GlobalTextureName = "_NebulaPatch",
WidthScale = 0.5f,
HeightScale = 0.5f
},
new AetheriaRenderSplatLayerBinding
{
LayerKey = AetheriaRuntimeRenderSplatLayerKeys.Influence,
MaterialTextureProperty = "_DetailTex",
GlobalTextureName = "_AetheriaInfluence"
}
};
private readonly Dictionary<string, RenderTexture> _texturesByLayerKey =
new Dictionary<string, RenderTexture>(StringComparer.Ordinal);
private void Reset()
{
rasterizer = GetComponent<AetheriaRenderSplatRasterizer>();
}
public bool TryGetTexture(string layerKey, out RenderTexture texture)
{
return _texturesByLayerKey.TryGetValue(layerKey ?? "", out texture);
}
public void Render(
AetheriaRuntimeRenderSplatsViewportDocument document,
int width,
int height)
{
if (document == null)
return;
ResolveRasterizer();
if (rasterizer == null)
return;
var layers = document.Layers ?? Array.Empty<AetheriaRuntimeRenderSplatLayerDefinition>();
for (var layerIndex = 0; layerIndex < layers.Count; layerIndex++)
{
var layer = layers[layerIndex];
if (layer == null || string.IsNullOrWhiteSpace(layer.LayerKey))
continue;
foreach (var binding in bindings ?? Array.Empty<AetheriaRenderSplatLayerBinding>())
{
if (binding == null ||
!binding.Enabled ||
!string.Equals(binding.LayerKey, layer.LayerKey, StringComparison.Ordinal))
{
continue;
}
var texture = EnsureTexture(binding, layer, width, height);
rasterizer.RenderLayerToTarget(
document,
texture,
layerIndex,
ResolveMaterialPass(layer.BlendMode),
ResolveClearColor(layer));
ApplyBinding(binding, texture);
_texturesByLayerKey[layer.LayerKey] = texture;
}
}
}
private void ResolveRasterizer()
{
if (rasterizer != null)
return;
rasterizer = GetComponent<AetheriaRenderSplatRasterizer>();
if (rasterizer == null)
rasterizer = gameObject.AddComponent<AetheriaRenderSplatRasterizer>();
}
private RenderTexture EnsureTexture(
AetheriaRenderSplatLayerBinding binding,
AetheriaRuntimeRenderSplatLayerDefinition layer,
int width,
int height)
{
var targetWidth = math.max(1, Mathf.RoundToInt(width * Mathf.Max(0.01f, binding.WidthScale)));
var targetHeight = math.max(1, Mathf.RoundToInt(height * Mathf.Max(0.01f, binding.HeightScale)));
var format = ResolveGraphicsFormat(binding.GraphicsFormatOverride, layer.GraphicsFormat);
var existing = binding.TargetTexture;
if (existing != null &&
existing.width == targetWidth &&
existing.height == targetHeight &&
existing.graphicsFormat == format)
{
return existing;
}
if (existing != null)
{
existing.Release();
Destroy(existing);
}
var descriptor = new RenderTextureDescriptor(targetWidth, targetHeight)
{
depthBufferBits = 0,
graphicsFormat = format,
msaaSamples = 1,
sRGB = false,
useMipMap = binding.UseMipMaps,
autoGenerateMips = binding.UseMipMaps
};
binding.TargetTexture = new RenderTexture(descriptor)
{
name = $"Aetheria {layer.LayerKey}",
filterMode = binding.FilterMode,
wrapMode = TextureWrapMode.Clamp
};
binding.TargetTexture.Create();
return binding.TargetTexture;
}
private static GraphicsFormat ResolveGraphicsFormat(string bindingOverride, string layerFormat)
{
var value = string.IsNullOrWhiteSpace(bindingOverride) ? layerFormat : bindingOverride;
return Enum.TryParse(value, out GraphicsFormat format)
? format
: GraphicsFormat.R16_SFloat;
}
private static int ResolveMaterialPass(string blendMode)
{
switch (blendMode)
{
case AetheriaRuntimeRenderSplatBlendModes.Max:
return 1;
case AetheriaRuntimeRenderSplatBlendModes.Alpha:
return 2;
default:
return 0;
}
}
private static Color ResolveClearColor(AetheriaRuntimeRenderSplatLayerDefinition layer)
{
return new Color(
(float)layer.ClearR,
(float)layer.ClearG,
(float)layer.ClearB,
(float)layer.ClearA);
}
private static void ApplyBinding(AetheriaRenderSplatLayerBinding binding, RenderTexture texture)
{
if (binding.Display != null)
{
var material = binding.Display.material;
if (material != null && !string.IsNullOrWhiteSpace(binding.MaterialTextureProperty))
material.SetTexture(binding.MaterialTextureProperty, texture);
else
binding.Display.material.mainTexture = texture;
}
if (!string.IsNullOrWhiteSpace(binding.GlobalTextureName))
Shader.SetGlobalTexture(binding.GlobalTextureName, texture);
}
private void OnDisable()
{
foreach (var binding in bindings ?? Array.Empty<AetheriaRenderSplatLayerBinding>())
{
if (binding?.TargetTexture == null)
continue;
binding.TargetTexture.Release();
Destroy(binding.TargetTexture);
binding.TargetTexture = null;
}
_texturesByLayerKey.Clear();
}
}
[Serializable]
public sealed class AetheriaRenderSplatLayerBinding
{
public bool Enabled = true;
public string LayerKey = "";
public Image Display;
public string MaterialTextureProperty = "_DetailTex";
public string GlobalTextureName = "";
public string GraphicsFormatOverride = "";
public float WidthScale = 1.0f;
public float HeightScale = 1.0f;
public bool UseMipMaps;
public FilterMode FilterMode = FilterMode.Bilinear;
[NonSerialized]
public RenderTexture TargetTexture;
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0f3c143f0a844f38a109f2de02ce4423
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,300 @@
using GameCult.Aetheria.State.Verse;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Experimental.Rendering;
using UnityEngine.Rendering;
public sealed class AetheriaRenderSplatRasterizer : MonoBehaviour
{
[SerializeField]
private Material splatMaterial;
[SerializeField]
private RenderTexture targetTexture;
[SerializeField]
private GraphicsFormat targetFormat = GraphicsFormat.R16_SFloat;
[SerializeField]
private int width = 512;
[SerializeField]
private int height = 512;
[SerializeField]
private int channelFilter = -1;
[SerializeField]
private int layerFilter = -1;
[SerializeField]
private int materialPass;
[SerializeField]
private Color clearColor = Color.clear;
[SerializeField]
private bool clearBeforeDraw = true;
private readonly AetheriaRenderSplatBuffer _buffer = new AetheriaRenderSplatBuffer();
private CommandBuffer _commandBuffer;
private RenderTexture _ownedTargetTexture;
private Material _ownedSplatMaterial;
private static readonly int SplatBufferPropertyId =
Shader.PropertyToID(AetheriaRenderSplatBuffer.SplatBufferPropertyName);
private static readonly int SplatCountPropertyId =
Shader.PropertyToID(AetheriaRenderSplatBuffer.SplatCountPropertyName);
private static readonly int ViewportToClipPropertyId =
Shader.PropertyToID(AetheriaRenderSplatBuffer.ViewportToClipPropertyName);
private static readonly int ChannelFilterPropertyId =
Shader.PropertyToID(AetheriaRenderSplatBuffer.ChannelFilterPropertyName);
public RenderTexture TargetTexture => targetTexture != null ? targetTexture : _ownedTargetTexture;
public int ChannelFilter
{
get => channelFilter;
set => channelFilter = value;
}
public int LayerFilter
{
get => layerFilter;
set => layerFilter = value;
}
public int LastDrawnCount { get; private set; }
public RenderTexture Render(
AetheriaRuntimeRenderSplatsViewportDocument document,
int overrideWidth = 0,
int overrideHeight = 0)
{
if (document == null)
{
LastDrawnCount = 0;
return TargetTexture;
}
var output = EnsureTarget(overrideWidth, overrideHeight);
var material = ResolveMaterial();
if (output == null || material == null)
{
LastDrawnCount = 0;
return output;
}
_buffer.Upload(document.Splats, channelFilter, layerFilter);
LastDrawnCount = _buffer.Count;
if (!_buffer.HasGpuBuffer)
{
if (clearBeforeDraw)
Clear(output);
return output;
}
_commandBuffer ??= new CommandBuffer { name = "Aetheria Render Splats" };
_commandBuffer.Clear();
_commandBuffer.SetRenderTarget(output);
if (clearBeforeDraw)
_commandBuffer.ClearRenderTarget(false, true, clearColor);
_commandBuffer.SetGlobalBuffer(SplatBufferPropertyId, _buffer.Buffer);
_commandBuffer.SetGlobalInt(SplatCountPropertyId, _buffer.Count);
_commandBuffer.SetGlobalInt(ChannelFilterPropertyId, channelFilter);
_commandBuffer.SetGlobalMatrix(ViewportToClipPropertyId, BuildViewportToClip(document.Viewport));
_commandBuffer.DrawProcedural(
Matrix4x4.identity,
material,
math.max(0, materialPass),
MeshTopology.Triangles,
6,
_buffer.Count);
Graphics.ExecuteCommandBuffer(_commandBuffer);
return output;
}
public RenderTexture Render(
AetheriaRuntimeRenderSplatsViewportDocument document,
int overrideWidth,
int overrideHeight,
int overrideChannelFilter)
{
var previousChannelFilter = channelFilter;
channelFilter = overrideChannelFilter;
try
{
return Render(document, overrideWidth, overrideHeight);
}
finally
{
channelFilter = previousChannelFilter;
}
}
public RenderTexture RenderLayer(
AetheriaRuntimeRenderSplatsViewportDocument document,
int overrideWidth,
int overrideHeight,
int overrideLayerFilter,
int overrideMaterialPass,
GraphicsFormat overrideTargetFormat,
Color overrideClearColor)
{
var previousLayerFilter = layerFilter;
var previousMaterialPass = materialPass;
var previousTargetFormat = targetFormat;
var previousClearColor = clearColor;
layerFilter = overrideLayerFilter;
materialPass = overrideMaterialPass;
targetFormat = overrideTargetFormat;
clearColor = overrideClearColor;
try
{
return Render(document, overrideWidth, overrideHeight);
}
finally
{
layerFilter = previousLayerFilter;
materialPass = previousMaterialPass;
targetFormat = previousTargetFormat;
clearColor = previousClearColor;
}
}
public RenderTexture RenderLayerToTarget(
AetheriaRuntimeRenderSplatsViewportDocument document,
RenderTexture output,
int overrideLayerFilter,
int overrideMaterialPass,
Color overrideClearColor)
{
var previousTargetTexture = targetTexture;
var previousLayerFilter = layerFilter;
var previousMaterialPass = materialPass;
var previousClearColor = clearColor;
targetTexture = output;
layerFilter = overrideLayerFilter;
materialPass = overrideMaterialPass;
clearColor = overrideClearColor;
try
{
return Render(document, output != null ? output.width : 0, output != null ? output.height : 0);
}
finally
{
targetTexture = previousTargetTexture;
layerFilter = previousLayerFilter;
materialPass = previousMaterialPass;
clearColor = previousClearColor;
}
}
private Material ResolveMaterial()
{
if (splatMaterial != null)
return splatMaterial;
if (_ownedSplatMaterial != null)
return _ownedSplatMaterial;
var shader = Shader.Find("Aetheria/Render Splats");
if (shader == null)
return null;
_ownedSplatMaterial = new Material(shader)
{
hideFlags = HideFlags.DontSave
};
return _ownedSplatMaterial;
}
private RenderTexture EnsureTarget(int overrideWidth, int overrideHeight)
{
if (targetTexture != null)
return targetTexture;
var targetWidth = math.max(1, overrideWidth > 0 ? overrideWidth : width);
var targetHeight = math.max(1, overrideHeight > 0 ? overrideHeight : height);
if (_ownedTargetTexture != null &&
_ownedTargetTexture.width == targetWidth &&
_ownedTargetTexture.height == targetHeight &&
_ownedTargetTexture.graphicsFormat == targetFormat)
{
return _ownedTargetTexture;
}
ReleaseOwnedTarget();
var descriptor = new RenderTextureDescriptor(targetWidth, targetHeight)
{
depthBufferBits = 0,
graphicsFormat = targetFormat,
msaaSamples = 1,
sRGB = false,
useMipMap = false,
autoGenerateMips = false
};
_ownedTargetTexture = new RenderTexture(descriptor)
{
name = "Aetheria Render Splats",
filterMode = FilterMode.Bilinear,
wrapMode = TextureWrapMode.Clamp
};
_ownedTargetTexture.Create();
return _ownedTargetTexture;
}
private void Clear(RenderTexture output)
{
_commandBuffer ??= new CommandBuffer { name = "Aetheria Render Splats" };
_commandBuffer.Clear();
_commandBuffer.SetRenderTarget(output);
_commandBuffer.ClearRenderTarget(false, true, clearColor);
Graphics.ExecuteCommandBuffer(_commandBuffer);
}
private static Matrix4x4 BuildViewportToClip(AetheriaRuntimeRtsViewportBounds viewport)
{
viewport ??= new AetheriaRuntimeRtsViewportBounds();
var minX = (float)math.min(viewport.MinX, viewport.MaxX);
var minY = (float)math.min(viewport.MinY, viewport.MaxY);
var maxX = (float)math.max(viewport.MinX, viewport.MaxX);
var maxY = (float)math.max(viewport.MinY, viewport.MaxY);
var width = math.max(0.0001f, maxX - minX);
var height = math.max(0.0001f, maxY - minY);
return new Matrix4x4(
new Vector4(2f / width, 0, 0, 0),
new Vector4(0, 2f / height, 0, 0),
new Vector4(0, 0, 1, 0),
new Vector4(-(maxX + minX) / width, -(maxY + minY) / height, 0, 1));
}
private void OnDisable()
{
_buffer.Dispose();
_commandBuffer?.Release();
_commandBuffer = null;
DestroyOwnedMaterial();
ReleaseOwnedTarget();
LastDrawnCount = 0;
}
private void DestroyOwnedMaterial()
{
if (_ownedSplatMaterial == null)
return;
Destroy(_ownedSplatMaterial);
_ownedSplatMaterial = null;
}
private void ReleaseOwnedTarget()
{
if (_ownedTargetTexture == null)
return;
_ownedTargetTexture.Release();
Destroy(_ownedTargetTexture);
_ownedTargetTexture = null;
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f5150857a83849f195312d13b8f4d19b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -36,7 +36,6 @@ public sealed class AetheriaUnityPilotFrameAdapter
return;
}
var renderSettings = ZoneRenderer?.RenderSettings;
TargetPresentation?.Tick(currentEntity, timeSeconds);
var look = Input.Player.Look.ReadValue<Vector2>();
@ -50,13 +49,14 @@ public sealed class AetheriaUnityPilotFrameAdapter
_setViewDirection(viewDirection);
PilotCommands.RequestLookDirection(viewDirection);
if (renderSettings != null)
if (ZoneRenderer != null)
{
var renderSettings = ZoneRenderer.RenderSettings;
if (HeatstrokePost != null)
HeatstrokePost.weight = (float)renderSettings.Value.NormalizeHeatstrokePost(currentEntity.Heatstroke);
HeatstrokePost.weight = (float)renderSettings.NormalizeHeatstrokePost(currentEntity.Heatstroke);
if (SevereHeatstrokePost != null)
SevereHeatstrokePost.weight =
(float)renderSettings.Value.ResolveSevereHeatstrokePostWeight(currentEntity.Heatstroke, timeSeconds);
(float)renderSettings.ResolveSevereHeatstrokePostWeight(currentEntity.Heatstroke, timeSeconds);
}
if (currentEntity is Ship)

View file

@ -0,0 +1,164 @@
using System;
using System.IO;
using GameCult.Aetheria.State.Verse;
using UnityEngine;
public sealed class AetheriaUnityRenderSplatViewportSource : MonoBehaviour
{
[SerializeField]
private AetheriaRenderSplatRasterizer rasterizer;
[SerializeField]
private AetheriaRenderSplatLayerRenderer layerRenderer;
[SerializeField]
private string layerKey = AetheriaRuntimeRenderSplatLayerKeys.GravityHeight;
[SerializeField]
private Vector2 position;
[SerializeField]
private Vector2 size = new Vector2(1024, 1024);
[SerializeField]
private float refreshIntervalSeconds = 0.5f;
[SerializeField]
private bool renderInLateUpdate = true;
private AetheriaClient _client;
private string _clientStatePath = "";
private float _nextRefreshTime;
private AetheriaRuntimeRenderSplatsViewportDocument _document;
public RenderTexture TargetTexture
{
get
{
if (layerRenderer != null && layerRenderer.TryGetTexture(layerKey, out var texture))
return texture;
return rasterizer != null ? rasterizer.TargetTexture : null;
}
}
private void Reset()
{
rasterizer = GetComponent<AetheriaRenderSplatRasterizer>();
layerRenderer = GetComponent<AetheriaRenderSplatLayerRenderer>();
}
private void LateUpdate()
{
if (renderInLateUpdate)
RenderLatest();
}
public bool RenderLatest()
{
ResolveRenderers();
if (layerRenderer == null && rasterizer == null)
return false;
RefreshDocument(false);
if (_document == null)
return false;
if (layerRenderer != null)
{
layerRenderer.Render(_document, Mathf.RoundToInt(size.x), Mathf.RoundToInt(size.y));
return true;
}
rasterizer.Render(_document, Mathf.RoundToInt(size.x), Mathf.RoundToInt(size.y));
return true;
}
private void ResolveRenderers()
{
if (layerRenderer == null)
layerRenderer = GetComponent<AetheriaRenderSplatLayerRenderer>();
if (rasterizer == null)
rasterizer = GetComponent<AetheriaRenderSplatRasterizer>();
if (layerRenderer == null && rasterizer == null)
layerRenderer = gameObject.AddComponent<AetheriaRenderSplatLayerRenderer>();
}
public void SetLayerKey(string value)
{
layerKey = string.IsNullOrWhiteSpace(value)
? AetheriaRuntimeRenderSplatLayerKeys.GravityHeight
: value;
}
public void SetViewport(Vector2 center, Vector2 viewportSize)
{
position = center;
size = viewportSize;
RefreshDocument(true);
}
private void RefreshDocument(bool force)
{
if (!force && Time.unscaledTime < _nextRefreshTime)
return;
_nextRefreshTime = Time.unscaledTime + Mathf.Max(0.02f, refreshIntervalSeconds);
try
{
_document = ResolveClient()
.RenderSplatsViewportAsync(ResolveViewportBounds())
.GetAwaiter()
.GetResult();
}
catch (Exception ex)
{
Debug.LogWarning($"Failed to read Aetheria render splats from local Verse state: {ex.Message}");
}
}
private AetheriaRuntimeRtsViewportBounds ResolveViewportBounds()
{
var halfSize = size * 0.5f;
return new AetheriaRuntimeRtsViewportBounds
{
MinX = position.x - halfSize.x,
MinY = position.y - halfSize.y,
MaxX = position.x + halfSize.x,
MaxY = position.y + halfSize.y
};
}
private AetheriaClient ResolveClient()
{
var gameDataDirectory = new DirectoryInfo(Path.Combine(Application.dataPath, "..", "GameData"));
var stateBoot = AetheriaRuntimeStateBoot.Inspect(gameDataDirectory);
if (_client != null && string.Equals(_clientStatePath, stateBoot.StateFilePath, StringComparison.Ordinal))
return _client;
DisposeClient();
_client = AetheriaClient
.OpenLocalAsync(
gameDataDirectory,
"unity-render-splat-viewport",
"local",
pullOnOpen: true)
.GetAwaiter()
.GetResult();
_clientStatePath = stateBoot.StateFilePath;
return _client;
}
private void OnDestroy()
{
DisposeClient();
}
private void DisposeClient()
{
_client?.Dispose();
_client = null;
_clientStatePath = "";
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 473b4029da16430aa0c0ef89298070d8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -61,12 +61,12 @@ public sealed class AetheriaUnityTargetPresentation
_observedHostileIndicatorTargets.Clear();
_observedFriendlyIndicatorTargets.Clear();
var renderSettings = ZoneRenderer?.RenderSettings;
if (currentEntity != null && renderSettings != null)
if (currentEntity != null && ZoneRenderer != null)
{
var renderSettings = ZoneRenderer.RenderSettings;
var contacts = ResolveVisibleContacts?.Invoke(
currentEntity,
renderSettings.Value.TargetDetectionInfoThreshold,
renderSettings.TargetDetectionInfoThreshold,
true) ?? Array.Empty<AetheriaRuntimeZoneContactRow>();
foreach (var contact in contacts)
@ -153,10 +153,10 @@ public sealed class AetheriaUnityTargetPresentation
float time,
bool blinkSpottedTargets)
{
var renderSettings = ZoneRenderer?.RenderSettings;
if (renderSettings == null)
if (ZoneRenderer == null)
return;
var renderSettings = ZoneRenderer.RenderSettings;
foreach (var indicator in indicators)
{
indicator.Value.gameObject.SetActive(indicator.Key != observedTarget);
@ -168,9 +168,9 @@ public sealed class AetheriaUnityTargetPresentation
}
var infoGathered = ResolveInfoGathered?.Invoke(indicator.Key, currentEntity) ?? 0f;
indicator.Value.Fill.fillAmount = (float)renderSettings.Value.NormalizeDetectionProgress(infoGathered);
indicator.Value.Fill.fillAmount = (float)renderSettings.NormalizeDetectionProgress(infoGathered);
indicator.Value.Fill.enabled = !blinkSpottedTargets ||
renderSettings.Value.ResolveTargetSpottedFillEnabled(infoGathered, time);
renderSettings.ResolveTargetSpottedFillEnabled(infoGathered, time);
}
}

View file

@ -89,9 +89,8 @@ public class InventoryMenu : MonoBehaviour
// {
// _destroyItem = false;
// });
var hasDockingIndex = TryResolveObservedDockingIndex(out var dockingIndex);
var hasObservedCurrentEntity = hasDockingIndex && dockingIndex.TryResolveCurrentEntity(out var currentEntity);
var cargo = hasDockingIndex && dockingIndex.TryResolveCurrentDockingBay(out var dockingBay)
var hasObservedCurrentEntity = TryResolveCurrentEntityFacade(out var currentEntity);
var cargo = TryGetTypedCurrentDockingBayFacade(out var dockingBay)
? dockingBay
: currentEntity?.CargoBays.FirstOrDefault();
if (cargo!=null)
@ -727,46 +726,81 @@ public class InventoryMenu : MonoBehaviour
private bool TryResolveCurrentDockingBayRow(out AetheriaRuntimeStationDockingBayRow dockingBay)
{
dockingBay = null;
return TryResolveObservedDockingIndex(out var dockingIndex) &&
dockingIndex.TryResolveCurrentDockingBayRow(out dockingBay);
var stationRefit = ResolveStationRefit();
dockingBay = stationRefit?.IsDocked == true && stationRefit.DockingBayIndex >= 0
? (stationRefit.DockingBays ?? Array.Empty<AetheriaRuntimeStationDockingBayRow>())
.FirstOrDefault(row => row != null && row.DockingBayIndex == stationRefit.DockingBayIndex)
: null;
return dockingBay != null;
}
private bool TryResolveCurrentEntityKey(out string currentEntityKey)
{
currentEntityKey = "";
return TryResolveObservedDockingIndex(out var dockingIndex) &&
dockingIndex.TryResolveCurrentEntityKey(out currentEntityKey);
currentEntityKey = ResolveDockingState()?.CurrentEntityKey ?? "";
return !string.IsNullOrWhiteSpace(currentEntityKey);
}
private bool TryResolveCurrentEntityDocument(out AetheriaRuntimeCurrentEntityDocument currentEntity)
{
currentEntity = null;
return TryResolveObservedDockingIndex(out var dockingIndex) &&
dockingIndex.TryResolveCurrentEntityDocument(out currentEntity);
try
{
currentEntity = ResolveClient()
.Aetheria()
.DockingState
.Latest()
?.CurrentEntity;
}
catch (Exception ex)
{
Debug.LogWarning($"Failed to read Aetheria current entity for inventory ship settings: {ex.Message}");
return false;
}
return currentEntity != null;
}
private AetheriaRuntimeStationRefitDocument ResolveStationRefit()
{
return TryResolveObservedDockingIndex(out var dockingIndex)
? dockingIndex.ResolveStationRefit()
: null;
return ResolveDockingState()?.StationRefit;
}
private bool TryResolveCurrentDocking(out AetheriaRuntimeCurrentDockingDocument docking)
private bool TryResolveCurrentEntityFacade(out Entity currentEntity)
{
docking = null;
return TryResolveObservedDockingIndex(out var dockingIndex) &&
dockingIndex.TryResolveCurrentDocking(out docking);
currentEntity = null;
var currentEntityKey = ResolveDockingState()?.CurrentEntityKey;
return _observedFacadeIndex != null &&
!string.IsNullOrWhiteSpace(currentEntityKey) &&
_observedFacadeIndex.TryResolveEntityByRecordKey(currentEntityKey, out currentEntity);
}
private bool TryResolveObservedDockingIndex(out AetheriaUnityObservedDockingIndex dockingIndex)
private bool TryGetTypedCurrentDockingBayFacade(out EquippedCargoBay dockingBay)
{
dockingIndex = null;
if (_observedFacadeIndex == null)
return false;
dockingBay = null;
var docking = ResolveDockingState()?.CurrentDocking;
return _observedFacadeIndex != null &&
docking?.IsDocked == true &&
_observedFacadeIndex.TryResolveDockingBayByRecordKey(
docking.DockParentEntityKey,
docking.DockingBayIndex,
out dockingBay);
}
dockingIndex = new AetheriaUnityObservedDockingIndex(ResolveClient, _observedFacadeIndex);
return true;
private AetheriaClientDockingSnapshot ResolveDockingState()
{
try
{
return ResolveClient()
.Aetheria()
.DockingState
.Latest();
}
catch (Exception ex)
{
Debug.LogWarning($"Failed to read Aetheria docking state for inventory menu: {ex.Message}");
return null;
}
}
private bool TryResolveCargoBay(EquippedCargoBay cargoBay, out string entityKey, out int cargoIndex)

View file

@ -260,9 +260,7 @@ public class InventoryPanel : MonoBehaviour, IPointerClickHandler
}
return;
case AetheriaRuntimeInventoryDropdownSelectionKind.DockingBay:
if (TryResolveObservedDockingIndex(out var dockingIndex) &&
dockingIndex.TryResolveCurrentDockingBayRow(out _) &&
dockingIndex.TryResolveCurrentDockingBay(out var dockingBay))
if (TryResolveCurrentDockingBayFacade(out var dockingBay))
{
Display(dockingBay);
}
@ -1133,29 +1131,53 @@ private void Update()
private bool TryResolveCurrentEntityKey(out string currentEntityKey)
{
currentEntityKey = "";
return TryResolveObservedDockingIndex(out var dockingIndex) &&
dockingIndex.TryResolveCurrentEntityKey(out currentEntityKey);
var snapshot = ResolveDockingState();
currentEntityKey = snapshot?.CurrentEntityKey ?? "";
return !string.IsNullOrWhiteSpace(currentEntityKey);
}
private bool TryResolveCurrentDockingBayRow(out AetheriaRuntimeStationDockingBayRow dockingBay)
{
dockingBay = null;
return TryResolveObservedDockingIndex(out var dockingIndex) &&
dockingIndex.TryResolveCurrentDockingBayRow(out dockingBay);
var stationRefit = ResolveStationRefit();
dockingBay = stationRefit?.IsDocked == true && stationRefit.DockingBayIndex >= 0
? (stationRefit.DockingBays ?? Array.Empty<AetheriaRuntimeStationDockingBayRow>())
.FirstOrDefault(row => row != null && row.DockingBayIndex == stationRefit.DockingBayIndex)
: null;
return dockingBay != null;
}
private bool TryResolveCurrentDocking(out AetheriaRuntimeCurrentDockingDocument docking)
private bool TryResolveCurrentDockingBayFacade(out EquippedCargoBay dockingBay)
{
docking = null;
return TryResolveObservedDockingIndex(out var dockingIndex) &&
dockingIndex.TryResolveCurrentDocking(out docking);
dockingBay = null;
var docking = ResolveDockingState()?.CurrentDocking;
return _observedFacadeIndex != null &&
docking?.IsDocked == true &&
_observedFacadeIndex.TryResolveDockingBayByRecordKey(
docking.DockParentEntityKey,
docking.DockingBayIndex,
out dockingBay);
}
private AetheriaRuntimeStationRefitDocument ResolveStationRefit()
{
return TryResolveObservedDockingIndex(out var dockingIndex)
? dockingIndex.ResolveStationRefit()
: null;
return ResolveDockingState()?.StationRefit;
}
private AetheriaClientDockingSnapshot ResolveDockingState()
{
try
{
return ResolveClient()
.Aetheria()
.DockingState
.Latest();
}
catch (Exception ex)
{
Debug.LogWarning($"Failed to read Aetheria docking state for inventory panel: {ex.Message}");
return null;
}
}
private bool TryResolveObservedDockingIndex(out AetheriaUnityObservedDockingIndex dockingIndex)

View file

@ -205,38 +205,36 @@ public class LocalMenu : MonoBehaviour
return "Local";
}
private bool TryResolveCurrentDocking(out AetheriaRuntimeCurrentDockingDocument docking)
private bool TryResolveDockingState(out AetheriaClientDockingSnapshot dockingState)
{
docking = null;
dockingState = null;
try
{
docking = ResolveClient()
dockingState = ResolveClient()
.Aetheria()
.Current
.Docking
.LatestAsync()
.GetAwaiter()
.GetResult();
.DockingState
.Latest();
}
catch (Exception ex)
{
Debug.LogWarning($"Failed to read Aetheria current docking for local story surface: {ex.Message}");
Debug.LogWarning($"Failed to read Aetheria docking state for local story surface: {ex.Message}");
return false;
}
return docking != null;
return dockingState != null;
}
private bool TryResolveDockedLocalStory(out LocationStory story)
{
story = null;
if (_observedFacadeIndex == null ||
!TryResolveCurrentDocking(out var docking) ||
!docking.IsDocked ||
!TryResolveDockingState(out var dockingState) ||
dockingState?.IsDocked != true ||
dockingState.CurrentDocking == null ||
!_observedFacadeIndex.TryResolveDockingBayByRecordKey(
docking.DockParentEntityKey,
docking.DockingBayIndex,
dockingState.CurrentDocking.DockParentEntityKey,
dockingState.CurrentDocking.DockingBayIndex,
out var dockingBay) ||
dockingBay?.Entity is not OrbitalEntity { Story: { } dockedStory })
{

View file

@ -25,24 +25,27 @@ public class MapRenderer : MonoBehaviour
public Camera InfluenceCamera;
public Image OverlayDisplay;
public Image GravityDisplay;
public Material GravityBackdropMaterial;
public Image TintDisplay;
public Image InfluenceDisplay;
public AetheriaRenderSplatLayerRenderer SplatLayerRenderer;
public AetheriaDaemonRenderAssetCatalog AssetCatalog;
public RectTransform RtsIconRoot;
public RawImage RtsIconPrototype;
public float Scale;
public float2 Position;
public float IconSize = 1f/128;
private RectTransform _rect;
private RenderTexture _mapTexture;
private RenderTexture _gravityTexture;
private RenderTexture _tintTexture;
private RenderTexture _influenceTexture;
private int2 _size;
private bool _init;
private AetheriaClient _client;
private string _clientStatePath = "";
private AetheriaRuntimeObjectsViewportDocument _objectsViewport;
private AetheriaRuntimeGravityViewportDocument _gravityViewport;
private AetheriaRuntimeRenderSplatsViewportDocument _renderSplatsViewport;
private float _nextViewportRefreshTime;
private readonly List<RawImage> _rtsIconPool = new List<RawImage>();
void Start()
{
@ -52,10 +55,16 @@ public class MapRenderer : MonoBehaviour
private void OnEnable()
{
_init = true;
MapOverlayCamera.gameObject.SetActive(true);
GravityCamera.gameObject.SetActive(true);
TintCamera.gameObject.SetActive(true);
InfluenceCamera.gameObject.SetActive(true);
SetCameraActive(MapOverlayCamera, false);
SetCameraActive(GravityCamera, false);
SetCameraActive(TintCamera, false);
SetCameraActive(InfluenceCamera, false);
if (OverlayDisplay != null)
OverlayDisplay.gameObject.SetActive(false);
if (GravityBackdropMaterial != null && GravityDisplay != null)
GravityDisplay.material = GravityBackdropMaterial;
EnsureRtsIconRoot();
SetRtsIconsActive(true);
RefreshViewportDocuments(force: true);
// If hiding minimap asteroids, turn them back on for the map screen
@ -69,10 +78,13 @@ public class MapRenderer : MonoBehaviour
{
ReleaseTextures();
}
MapOverlayCamera.gameObject.SetActive(false);
GravityCamera.gameObject.SetActive(false);
TintCamera.gameObject.SetActive(false);
InfluenceCamera.gameObject.SetActive(false);
SetCameraActive(MapOverlayCamera, false);
SetCameraActive(GravityCamera, false);
SetCameraActive(TintCamera, false);
SetCameraActive(InfluenceCamera, false);
if (OverlayDisplay != null)
OverlayDisplay.gameObject.SetActive(false);
SetRtsIconsActive(false);
// If hiding minimap asteroids, turn them back off when leaving the map screen
if (!ResolveShowAsteroidsInMinimap())
@ -88,12 +100,6 @@ public class MapRenderer : MonoBehaviour
{
_mapTexture.Release();
_mapTexture = null;
_gravityTexture.Release();
_gravityTexture = null;
_tintTexture.Release();
_tintTexture = null;
_influenceTexture.Release();
_influenceTexture = null;
}
void LateUpdate()
@ -108,40 +114,12 @@ public class MapRenderer : MonoBehaviour
ReleaseTextures();
}
_mapTexture = new RenderTexture(_size.x, _size.y, 0, RenderTextureFormat.Default);
MapOverlayCamera.targetTexture = _mapTexture;
OverlayDisplay.material.SetTexture("_DetailTex", _mapTexture);
_gravityTexture = new RenderTexture(_size.x, _size.y, 0, RenderTextureFormat.RFloat);
GravityCamera.targetTexture = _gravityTexture;
GravityDisplay.material.SetTexture("_DetailTex", _gravityTexture);
_tintTexture = new RenderTexture(_size.x / 2, _size.y / 2, 0, RenderTextureFormat.RGB111110Float);
TintCamera.targetTexture = _tintTexture;
TintDisplay.material.SetTexture("_DetailTex", _tintTexture);
_influenceTexture = new RenderTexture(_size.x, _size.y, 0, RenderTextureFormat.RFloat);
InfluenceCamera.targetTexture = _influenceTexture;
InfluenceDisplay.material.SetTexture("_DetailTex", _influenceTexture);
}
var pos = ((Vector2) Position).Flatland(1);
MapOverlayCamera.transform.position = pos;
MapOverlayCamera.orthographicSize = _size.y * Scale * .5f;
GravityCamera.transform.position = pos;
GravityCamera.orthographicSize = _size.y * Scale * .5f;
GravityDisplay.material.SetFloat("_Scale", Scale / 2);
TintCamera.transform.position = pos;
TintCamera.orthographicSize = _size.y * Scale * .5f;
InfluenceCamera.transform.position = pos;
InfluenceCamera.orthographicSize = _size.y * Scale * .5f;
ZoneRenderer.SetIconSize(IconSize * Scale);
RefreshViewportDocuments(force: false);
RenderSplatLayers();
RenderRtsIcons();
}
private void RefreshViewportDocuments(bool force)
@ -158,8 +136,8 @@ public class MapRenderer : MonoBehaviour
.ObjectsViewportAsync(viewport)
.GetAwaiter()
.GetResult();
_gravityViewport = client
.GravityViewportAsync(viewport)
_renderSplatsViewport = client
.RenderSplatsViewportAsync(viewport)
.GetAwaiter()
.GetResult();
@ -175,6 +153,153 @@ public class MapRenderer : MonoBehaviour
}
}
private void RenderSplatLayers()
{
if (_renderSplatsViewport == null)
return;
if (SplatLayerRenderer == null)
SplatLayerRenderer = GetComponent<AetheriaRenderSplatLayerRenderer>() ??
gameObject.AddComponent<AetheriaRenderSplatLayerRenderer>();
SplatLayerRenderer.Render(_renderSplatsViewport, _size.x, _size.y);
ApplyLayerTexture(AetheriaRuntimeRenderSplatLayerKeys.GravityHeight, GravityDisplay);
ApplyLayerTexture(AetheriaRuntimeRenderSplatLayerKeys.FogTint, TintDisplay);
ApplyLayerTexture(AetheriaRuntimeRenderSplatLayerKeys.Influence, InfluenceDisplay);
}
private void ApplyLayerTexture(string layerKey, Image display)
{
if (display == null ||
SplatLayerRenderer == null ||
!SplatLayerRenderer.TryGetTexture(layerKey, out var texture) ||
texture == null)
{
return;
}
display.material.SetTexture("_DetailTex", texture);
}
private static void SetCameraActive(Camera camera, bool active)
{
if (camera != null)
camera.gameObject.SetActive(active);
}
private void RenderRtsIcons()
{
EnsureRtsIconRoot();
if (RtsIconRoot == null)
return;
var objects = _objectsViewport?.Objects ?? Array.Empty<AetheriaRuntimeRtsViewportObject>();
for (var i = 0; i < objects.Count; i++)
{
var icon = ResolveRtsIcon(i);
ApplyRtsIcon(icon, objects[i]);
}
for (var i = objects.Count; i < _rtsIconPool.Count; i++)
if (_rtsIconPool[i] != null)
_rtsIconPool[i].gameObject.SetActive(false);
}
private RawImage ResolveRtsIcon(int index)
{
while (_rtsIconPool.Count <= index)
{
RawImage icon;
if (RtsIconPrototype != null)
{
icon = Instantiate(RtsIconPrototype, RtsIconRoot);
}
else
{
var go = new GameObject("RTS Map Icon", typeof(RectTransform), typeof(CanvasRenderer), typeof(RawImage));
go.transform.SetParent(RtsIconRoot, false);
icon = go.GetComponent<RawImage>();
icon.texture = Texture2D.whiteTexture;
icon.raycastTarget = false;
}
_rtsIconPool.Add(icon);
}
return _rtsIconPool[index];
}
private void ApplyRtsIcon(RawImage icon, AetheriaRuntimeRtsViewportObject obj)
{
if (icon == null || obj == null)
return;
icon.gameObject.SetActive(true);
if (AssetCatalog != null && AssetCatalog.TryResolveTexture(obj.IconAsset, out var texture))
icon.texture = texture;
icon.color = ResolveIconColor(obj);
var rectTransform = icon.rectTransform;
var pixelSize = Mathf.Max(4f, IconSize * (obj.Controlled ? 1.25f : 1f));
rectTransform.anchorMin = rectTransform.anchorMax = new Vector2(0.5f, 0.5f);
rectTransform.pivot = new Vector2(0.5f, 0.5f);
rectTransform.sizeDelta = new Vector2(pixelSize, pixelSize);
rectTransform.anchoredPosition = WorldToMapPosition(obj.X, obj.Y);
rectTransform.localRotation = Quaternion.Euler(
0,
0,
Mathf.Atan2((float)obj.DirectionY, (float)obj.DirectionX) * Mathf.Rad2Deg - 90f);
}
private Vector2 WorldToMapPosition(double worldX, double worldY)
{
var viewport = _objectsViewport?.Viewport ?? ResolveViewportBounds();
var minX = Math.Min(viewport.MinX, viewport.MaxX);
var maxX = Math.Max(viewport.MinX, viewport.MaxX);
var minY = Math.Min(viewport.MinY, viewport.MaxY);
var maxY = Math.Max(viewport.MinY, viewport.MaxY);
var width = Math.Max(0.0001, maxX - minX);
var height = Math.Max(0.0001, maxY - minY);
var rootRect = RtsIconRoot != null ? RtsIconRoot.rect : new Rect(0, 0, _size.x, _size.y);
var u = (float)((worldX - minX) / width);
var v = (float)((worldY - minY) / height);
return new Vector2(
(u - 0.5f) * rootRect.width,
(v - 0.5f) * rootRect.height);
}
private static Color ResolveIconColor(AetheriaRuntimeRtsViewportObject obj)
{
var color = obj.Controlled
? new Color(0.3f, 0.9f, 1.0f, 1.0f)
: Color.HSVToRGB(frac((obj.FactionKey ?? obj.Kind ?? "").GetHashCode() * 0.0137f), 0.55f, 0.95f);
color.a = Mathf.Clamp01(Mathf.Max(0.25f, (float)obj.Visibility));
return color;
}
private void EnsureRtsIconRoot()
{
if (RtsIconRoot != null)
return;
var parent = GravityDisplay != null
? GravityDisplay.transform.parent
: transform;
var go = new GameObject("RTS Command Icons", typeof(RectTransform));
go.transform.SetParent(parent, false);
RtsIconRoot = go.GetComponent<RectTransform>();
RtsIconRoot.anchorMin = Vector2.zero;
RtsIconRoot.anchorMax = Vector2.one;
RtsIconRoot.offsetMin = Vector2.zero;
RtsIconRoot.offsetMax = Vector2.zero;
RtsIconRoot.SetAsLastSibling();
}
private void SetRtsIconsActive(bool active)
{
if (RtsIconRoot != null)
RtsIconRoot.gameObject.SetActive(active);
}
private AetheriaRuntimeRtsViewportBounds ResolveViewportBounds()
{
var screenHeight = _size.y <= 0 ? Math.Max(1, Screen.height) : _size.y;

View file

@ -169,17 +169,14 @@ public class MenuPanel : MonoBehaviour
.ToArray();
}
private AetheriaRuntimeCurrentDockingDocument ResolveCurrentDocking()
private AetheriaClientDockingSnapshot ResolveCurrentDocking()
{
try
{
return ResolveClient()
.Aetheria()
.Current
.Docking
.LatestAsync()
.GetAwaiter()
.GetResult();
.DockingState
.Latest();
}
catch (Exception ex)
{

View file

@ -15,6 +15,7 @@ using float2 = Unity.Mathematics.float2;
public class SectorMap : MonoBehaviour
{
public Camera InfluenceCamera;
public AetheriaRenderSplatRasterizer InfluenceSplatRasterizer;
public Prototype InfluenceRendererPrototype;
public MeshRenderer SectorRenderer;
public Prototype ZonePrototype;
@ -216,6 +217,7 @@ public class SectorMap : MonoBehaviour
public void Start()
{
EnsureSectorMapLoaded();
DisableLegacyInfluenceCamera();
}
private void EnsureSectorMapLoaded()
@ -341,33 +343,162 @@ public class SectorMap : MonoBehaviour
private void RenderInfluence()
{
DisableLegacyInfluenceCamera();
foreach (var pair in _factionMaterials)
{
foreach (var zone in _sectorMap?.Zones ?? Array.Empty<AetheriaRuntimeSectorMapZone>())
{
if (!_zoneInstances.TryGetValue(zone.ZoneIndex, out var instance))
continue;
var influence = 0f;
if (zone.FactionIndices?.Count > 0)
{
if (zone.FactionIndices.Contains(pair.Key))
{
influence = 10;
if (zone.OwnerFactionIndex != pair.Key)
influence *= .5f;
}
else influence = -10;
}
instance.Influence.material.SetFloat("_Depth", influence);
}
InfluenceCamera.targetTexture = pair.Value.influence;
InfluenceCamera.Render();
var document = BuildInfluenceSplatDocument(pair.Key);
ResolveInfluenceSplatRasterizer().RenderLayerToTarget(
document,
pair.Value.influence,
0,
0,
Color.clear);
}
}
private AetheriaRenderSplatRasterizer ResolveInfluenceSplatRasterizer()
{
if (InfluenceSplatRasterizer != null)
return InfluenceSplatRasterizer;
InfluenceSplatRasterizer = GetComponent<AetheriaRenderSplatRasterizer>();
if (InfluenceSplatRasterizer == null)
InfluenceSplatRasterizer = gameObject.AddComponent<AetheriaRenderSplatRasterizer>();
return InfluenceSplatRasterizer;
}
private AetheriaRuntimeRenderSplatsViewportDocument BuildInfluenceSplatDocument(int factionIndex)
{
var zones = (_sectorMap?.Zones ?? Array.Empty<AetheriaRuntimeSectorMapZone>()).ToArray();
var minX = zones.Length > 0 ? zones.Min(zone => zone.X) : -1;
var maxX = zones.Length > 0 ? zones.Max(zone => zone.X) : 1;
var minY = zones.Length > 0 ? zones.Min(zone => zone.Y) : -1;
var maxY = zones.Length > 0 ? zones.Max(zone => zone.Y) : 1;
var padding = Math.Max(IconBackgroundSize * 2, 4);
var centerX = new List<double>();
var centerY = new List<double>();
var halfExtentX = new List<double>();
var halfExtentY = new List<double>();
var rotationCos = new List<double>();
var rotationSin = new List<double>();
var channel = new List<int>();
var falloff = new List<int>();
var valueR = new List<double>();
var valueG = new List<double>();
var valueB = new List<double>();
var valueA = new List<double>();
var sourceKey = new List<string>();
var layerIndex = new List<int>();
var sourceKind = new List<int>();
var frequencyX = new List<double>();
var frequencyY = new List<double>();
var phaseX = new List<double>();
var phaseY = new List<double>();
var animationSpeed = new List<double>();
var sourceFlags = new List<double>();
foreach (var zone in zones)
{
if (!_zoneInstances.ContainsKey(zone.ZoneIndex))
continue;
var influence = ResolveZoneInfluence(zone, factionIndex);
if (Math.Abs(influence) <= 0.0001f)
continue;
centerX.Add(zone.X);
centerY.Add(zone.Y);
halfExtentX.Add(Math.Max(IconBackgroundSize, 1));
halfExtentY.Add(Math.Max(IconBackgroundSize, 1));
rotationCos.Add(1);
rotationSin.Add(0);
channel.Add(AetheriaRuntimeRenderSplatChannels.Influence);
falloff.Add(AetheriaRuntimeRenderSplatFalloffs.Smooth);
valueR.Add(influence);
valueG.Add(influence);
valueB.Add(influence);
valueA.Add(1);
sourceKey.Add($"sector-zone:{zone.ZoneIndex}:faction:{factionIndex}");
layerIndex.Add(0);
sourceKind.Add(AetheriaRuntimeRenderSplatSourceKinds.Constant);
frequencyX.Add(0);
frequencyY.Add(0);
phaseX.Add(0);
phaseY.Add(0);
animationSpeed.Add(0);
sourceFlags.Add(0);
}
return new AetheriaRuntimeRenderSplatsViewportDocument
{
Schema = AetheriaRuntimeDaemonSchemas.RenderSplatsViewport,
Viewport = new AetheriaRuntimeRtsViewportBounds
{
MinX = minX - padding,
MinY = minY - padding,
MaxX = maxX + padding,
MaxY = maxY + padding
},
Layers = new[]
{
new AetheriaRuntimeRenderSplatLayerDefinition
{
LayerKey = AetheriaRuntimeRenderSplatLayerKeys.Influence,
DisplayName = "Sector Influence",
Channel = AetheriaRuntimeRenderSplatChannels.Influence,
BlendMode = AetheriaRuntimeRenderSplatBlendModes.Add,
GraphicsFormat = "R16_SFloat"
}
},
Splats = new AetheriaRuntimeRenderSplatSoa
{
Count = centerX.Count,
CenterX = centerX,
CenterY = centerY,
HalfExtentX = halfExtentX,
HalfExtentY = halfExtentY,
RotationCos = rotationCos,
RotationSin = rotationSin,
Channel = channel,
Falloff = falloff,
ValueR = valueR,
ValueG = valueG,
ValueB = valueB,
ValueA = valueA,
SourceKey = sourceKey,
LayerIndex = layerIndex,
SourceKind = sourceKind,
FrequencyX = frequencyX,
FrequencyY = frequencyY,
PhaseX = phaseX,
PhaseY = phaseY,
AnimationSpeed = animationSpeed,
SourceFlags = sourceFlags
}
};
}
private static float ResolveZoneInfluence(AetheriaRuntimeSectorMapZone zone, int factionIndex)
{
if (zone.FactionIndices?.Count <= 0)
return 0f;
if (!zone.FactionIndices.Contains(factionIndex))
return -10f;
return zone.OwnerFactionIndex == factionIndex ? 10f : 5f;
}
private void DisableLegacyInfluenceCamera()
{
if (InfluenceCamera == null)
return;
InfluenceCamera.targetTexture = null;
InfluenceCamera.gameObject.SetActive(false);
}
private AetheriaClient ResolveClient()
{
var gameDataDirectory = new DirectoryInfo(Path.Combine(Application.dataPath, "..", "GameData"));

View file

@ -47,6 +47,7 @@ public class TradeMenu : MonoBehaviour
private AetheriaRuntimeTradeCargoSelectorSurfaceProjection _cargoSelectorSurfaceProjection;
private AetheriaRuntimeStationCargoTargetRow[] _cargoSelectorStationRefitTargets =
Array.Empty<AetheriaRuntimeStationCargoTargetRow>();
private AetheriaClientDockingSnapshot _dockingState;
private AetheriaRuntimeStationRefitDocument _stationRefit;
private AetheriaRuntimeTradeFilterSurfaceProjection _filterSurfaceProjection;
private Action[] _rowActionCallbacks = Array.Empty<Action>();
@ -56,8 +57,10 @@ public class TradeMenu : MonoBehaviour
private void OnEnable()
{
if (!TryResolveCurrentDocking(out var docking) ||
!docking.IsDocked ||
var dockingState = ResolveDockingState();
var docking = dockingState?.CurrentDocking;
if (dockingState?.IsDocked != true ||
docking == null ||
string.IsNullOrWhiteSpace(docking.DockParentEntityKey) ||
docking.DockingBayIndex < 0)
{
@ -514,7 +517,8 @@ public class TradeMenu : MonoBehaviour
private bool TryResolveCurrentDockingTargetEntityKey(out string targetEntityKey)
{
targetEntityKey = "";
if (!TryResolveCurrentDocking(out var docking) ||
var docking = ResolveDockingState()?.CurrentDocking;
if (docking == null ||
!docking.IsDocked ||
string.IsNullOrWhiteSpace(docking.DockParentEntityKey))
{
@ -525,54 +529,40 @@ public class TradeMenu : MonoBehaviour
return true;
}
private bool TryResolveCurrentDocking(out AetheriaRuntimeCurrentDockingDocument docking)
{
docking = null;
try
{
docking = ResolveClient()
.Aetheria()
.Current
.Docking
.LatestAsync()
.GetAwaiter()
.GetResult();
}
catch (Exception ex)
{
Debug.LogWarning($"Failed to read Aetheria current docking for trade menu: {ex.Message}");
return false;
}
return docking != null;
}
private AetheriaRuntimeStationRefitDocument ResolveStationRefit()
{
if (_stationRefit != null)
return _stationRefit;
_stationRefit = ResolveDockingState()?.StationRefit;
return _stationRefit;
}
private AetheriaClientDockingSnapshot ResolveDockingState()
{
if (_dockingState != null)
return _dockingState;
try
{
_stationRefit = ResolveClient()
_dockingState = ResolveClient()
.Aetheria()
.StationRefit
.LatestAsync()
.GetAwaiter()
.GetResult();
.DockingState
.Latest();
_stationRefit = _dockingState?.StationRefit;
}
catch (Exception ex)
{
Debug.LogWarning($"Failed to read Aetheria station refit projection for trade menu: {ex.Message}");
Debug.LogWarning($"Failed to read Aetheria docking state for trade menu: {ex.Message}");
return null;
}
return _stationRefit;
return _dockingState;
}
private void InvalidateStationRefit()
{
_dockingState = null;
_stationRefit = null;
}
@ -649,6 +639,7 @@ public class TradeMenu : MonoBehaviour
_clientStatePath = "";
_catalog = null;
_playerSettings = null;
_dockingState = null;
_stationRefit = null;
}

View file

@ -5,6 +5,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using GameCult.Aetheria.State.Verse;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.Serialization;
@ -15,6 +16,7 @@ public class MapView : MonoBehaviour
public Camera MinimapGravity;
public Camera Minimap;
public MeshRenderer GravityRenderer;
public AetheriaUnityRenderSplatViewportSource GravitySplatSource;
public float ZoomSpeed;
[FormerlySerializedAs("SectorRenderer")] public ZoneRenderer ZoneRenderer;
@ -26,7 +28,6 @@ public class MapView : MonoBehaviour
private RenderTexture _minimapGravityTexture;
private Transform _gravityRendererTransform;
private CopyTransform _gravityRendererCopyTransform;
private RenderTexture _fullscreenGravityTexture;
private AetheriaInput.UIActions _input;
private float _viewDistance = 1024;
private float _aspectRatio;
@ -49,9 +50,14 @@ public class MapView : MonoBehaviour
_gravityRendererCopyTransform.enabled = false;
_previousGravityRendererScale = _gravityRendererTransform.localScale;
_fullscreenGravityTexture = new RenderTexture(Screen.width / 2, Screen.height / 2, 1, RenderTextureFormat.RFloat);
GravityRenderer.material.mainTexture = _fullscreenGravityTexture;
MinimapGravity.targetTexture = _fullscreenGravityTexture;
GravitySplatSource = ResolveGravitySplatSource();
GravitySplatSource.SetLayerKey(AetheriaRuntimeRenderSplatLayerKeys.GravityHeight);
GravitySplatSource.SetViewport(
new Vector2(_minimapTransform.position.x, _minimapTransform.position.z),
new Vector2(_viewDistance * _aspectRatio * 2, _viewDistance * 2));
GravitySplatSource.RenderLatest();
GravityRenderer.material.mainTexture = GravitySplatSource.TargetTexture;
MinimapGravity.targetTexture = null;
Minimap.targetTexture = null;
SetZoom();
}
@ -61,6 +67,14 @@ public class MapView : MonoBehaviour
//ZoneRenderer.ViewDistance = _viewDistance * _aspectRatio;
Minimap.orthographicSize = MinimapGravity.orthographicSize = _viewDistance;
_gravityRendererTransform.localScale = new Vector3(_viewDistance*_aspectRatio*2, _viewDistance*2, 1);
if (GravitySplatSource != null && _minimapTransform != null)
{
GravitySplatSource.SetViewport(
new Vector2(_minimapTransform.position.x, _minimapTransform.position.z),
new Vector2(_viewDistance * _aspectRatio * 2, _viewDistance * 2));
GravitySplatSource.RenderLatest();
GravityRenderer.material.mainTexture = GravitySplatSource.TargetTexture;
}
}
// public void BindInput(AetheriaInput.UIActions input)
@ -91,6 +105,7 @@ public class MapView : MonoBehaviour
_gravityRendererTransform.position = gravPos;
Main.transform.position = _minimapTransform.position = _minimapGravityTransform.position = pos;
SetZoom();
}
}
@ -106,6 +121,20 @@ public class MapView : MonoBehaviour
Minimap.targetTexture = _minimapTexture;
MinimapGravity.targetTexture = _minimapGravityTexture;
GravityRenderer.material.mainTexture = _minimapGravityTexture;
_fullscreenGravityTexture.Release();
}
private AetheriaUnityRenderSplatViewportSource ResolveGravitySplatSource()
{
if (GravitySplatSource != null)
return GravitySplatSource;
GravitySplatSource = GetComponent<AetheriaUnityRenderSplatViewportSource>();
if (GravitySplatSource == null)
GravitySplatSource = gameObject.AddComponent<AetheriaUnityRenderSplatViewportSource>();
if (GetComponent<AetheriaRenderSplatLayerRenderer>() == null)
gameObject.AddComponent<AetheriaRenderSplatLayerRenderer>();
return GravitySplatSource;
}
}

View file

@ -3,6 +3,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
using System;
using GameCult.Aetheria.State.Verse;
using UnityEngine;
using Unity.Mathematics;
using UnityEngine.Experimental.Rendering;
@ -26,6 +27,8 @@ public class VolumeSampling : MonoBehaviour
public RenderTexture NebulaPatchHeight;
public RenderTexture NebulaPatch;
public RenderTexture NebulaTint;
public AetheriaUnityRenderSplatViewportSource SplatViewportSource;
public AetheriaRenderSplatLayerRenderer SplatLayerRenderer;
private MeshRenderer _gridMeshRenderer;
private float _flowScroll;
@ -42,6 +45,8 @@ public class VolumeSampling : MonoBehaviour
if (GridMesh != null)
_gridMeshRenderer = GridMesh.GetComponent<MeshRenderer>();
ResolveSplatRenderers();
}
void Update()
@ -52,6 +57,8 @@ public class VolumeSampling : MonoBehaviour
if(GridCamera != null)
Shader.SetGlobalVector("_GridTransform", new Vector4(_gridTransform.position.x,_gridTransform.position.z,GridCamera.orthographicSize*2));
RenderSplatTextures();
var environment = Settings.DefaultEnvironment;
if (GridMesh != null)
@ -106,4 +113,48 @@ public class VolumeSampling : MonoBehaviour
Shader.SetKeyword(_keywordFlowSlope, environment.Flow.SlopeAmplitude != 0 || environment.Flow.SwirlAmplitude != 0);
Shader.SetKeyword(_keywordNoiseSlope, environment.Noise.SlopeExponent != 0);
}
private void RenderSplatTextures()
{
ResolveSplatRenderers();
if (SplatViewportSource == null)
return;
var size = GridCamera != null
? new Vector2(GridCamera.orthographicSize * 2, GridCamera.orthographicSize * 2)
: new Vector2(1024, 1024);
var center = _gridTransform != null
? new Vector2(_gridTransform.position.x, _gridTransform.position.z)
: Vector2.zero;
SplatViewportSource.SetViewport(center, size);
SplatViewportSource.RenderLatest();
if (SplatLayerRenderer == null)
return;
NebulaSurfaceHeight = ResolveLayerTexture(AetheriaRuntimeRenderSplatLayerKeys.FogSurfaceHeight, NebulaSurfaceHeight);
NebulaPatchHeight = ResolveLayerTexture(AetheriaRuntimeRenderSplatLayerKeys.FogPatchHeight, NebulaPatchHeight);
NebulaPatch = ResolveLayerTexture(AetheriaRuntimeRenderSplatLayerKeys.FogPatch, NebulaPatch);
NebulaTint = ResolveLayerTexture(AetheriaRuntimeRenderSplatLayerKeys.FogTint, NebulaTint);
}
private RenderTexture ResolveLayerTexture(string layerKey, RenderTexture fallback)
{
return SplatLayerRenderer.TryGetTexture(layerKey, out var texture) && texture != null
? texture
: fallback;
}
private void ResolveSplatRenderers()
{
if (SplatLayerRenderer == null)
SplatLayerRenderer = GetComponent<AetheriaRenderSplatLayerRenderer>();
if (SplatLayerRenderer == null)
SplatLayerRenderer = gameObject.AddComponent<AetheriaRenderSplatLayerRenderer>();
if (SplatViewportSource == null)
SplatViewportSource = GetComponent<AetheriaUnityRenderSplatViewportSource>();
if (SplatViewportSource == null)
SplatViewportSource = gameObject.AddComponent<AetheriaUnityRenderSplatViewportSource>();
}
}

View file

@ -30,6 +30,7 @@ public class ZoneRenderer : MonoBehaviour
public CinemachineVirtualCamera SceneCamera;
public Camera[] FogCameras;
public Camera[] MinimapCameras;
public bool UseRenderSplatTextureLayers = true;
public Material FogMaterial;
public float FogFarFadeFraction = .125f;
public float FarPlaneDistanceMultiplier = 2;
@ -115,7 +116,8 @@ public class ZoneRenderer : MonoBehaviour
Array.Empty<AetheriaRuntimeZoneRenderBodyPose>();
private IReadOnlyList<AetheriaRuntimeZoneRenderAsteroidBeltPose> _zoneRenderAsteroidBeltPoses =
Array.Empty<AetheriaRuntimeZoneRenderAsteroidBeltPose>();
private AetheriaRuntimeGravityViewportDocument _daemonGravityViewport;
private IReadOnlyList<AetheriaRuntimeBodySnapshotCommit> _zoneRenderBodies =
Array.Empty<AetheriaRuntimeBodySnapshotCommit>();
private AetheriaClient _client;
private string _clientStatePath = "";
private AetheriaRuntimeCatalogSnapshot _catalog;
@ -146,11 +148,16 @@ public class ZoneRenderer : MonoBehaviour
set
{
_viewDistance = value;
foreach (var camera in FogCameras)
camera.orthographicSize = value;
SceneCamera.m_Lens.FarClipPlane = value * FarPlaneDistanceMultiplier;
FogMaterial.SetFloat("_DepthCeiling", value);
FogMaterial.SetFloat("_DepthBlend", FogFarFadeFraction * value);
foreach (var camera in FogCameras ?? Array.Empty<Camera>())
if (camera != null)
camera.orthographicSize = value;
if (SceneCamera != null)
SceneCamera.m_Lens.FarClipPlane = value * FarPlaneDistanceMultiplier;
if (FogMaterial != null)
{
FogMaterial.SetFloat("_DepthCeiling", value);
FogMaterial.SetFloat("_DepthBlend", FogFarFadeFraction * value);
}
}
}
@ -159,9 +166,11 @@ public class ZoneRenderer : MonoBehaviour
set
{
_minimapDistance = value;
foreach (var camera in MinimapCameras)
camera.orthographicSize = value;
SetIconSize((float)RenderSettings.ResolveMinimapIconSize(value));
foreach (var camera in MinimapCameras ?? Array.Empty<Camera>())
if (camera != null)
camera.orthographicSize = value;
if (RenderSettings != null)
SetIconSize((float)RenderSettings.ResolveMinimapIconSize(value));
// MinimapGravityQuad.transform.localScale = value * 2 * Vector3.one;
}
}
@ -235,6 +244,7 @@ public class ZoneRenderer : MonoBehaviour
_tourTimer = TourSwitchTime;
_transposer = SceneCamera.GetCinemachineComponent<CinemachineTransposer>();
DisableLegacyTextureCameras();
}
public void LoadDaemonZoneView(
@ -244,9 +254,6 @@ public class ZoneRenderer : MonoBehaviour
ClearZone();
_observedEntityFacadesByDaemonIndex = observedEntityFacadesByDaemonIndex;
ApplyZoneRender(render);
RefreshDaemonBodyPoses();
RefreshDaemonAsteroidBeltPoses();
SyncDaemonBodyViews();
RefreshDaemonVisibleEntityInstances();
SyncDaemonEntityInstances();
@ -260,11 +267,17 @@ public class ZoneRenderer : MonoBehaviour
_daemonSimulationTimeSeconds = render?.SimulationTimeSeconds ?? 0;
_zoneRenderBodyPoses = render?.BodyPoses ?? Array.Empty<AetheriaRuntimeZoneRenderBodyPose>();
_zoneRenderAsteroidBeltPoses = render?.AsteroidBeltPoses ?? Array.Empty<AetheriaRuntimeZoneRenderAsteroidBeltPose>();
_zoneRenderBodies = render?.Bodies ?? Array.Empty<AetheriaRuntimeBodySnapshotCommit>();
RefreshDaemonContactRows();
var zoneRenderRadius = (float)Math.Max(0, render?.ZoneRenderRadius ?? 2000);
SectorBrushes.localScale = zoneRenderRadius * 2 * Vector3.one;
SlimeGravityCamera.orthographicSize = zoneRenderRadius;
if (SlimeGravityCamera != null)
SlimeGravityCamera.orthographicSize = zoneRenderRadius;
SlimeRenderer.ZoneRadius = zoneRenderRadius;
DisableLegacyTextureCameras();
RefreshDaemonBodyPoses();
RefreshDaemonAsteroidBeltPoses();
SyncDaemonBodyViews();
_daemonWormholeExits.Clear();
foreach (var exit in render?.WormholeExits ?? Array.Empty<AetheriaRuntimeZoneRenderWormholeExit>())
{
@ -673,30 +686,23 @@ public class ZoneRenderer : MonoBehaviour
private void SyncDaemonBodyViews()
{
try
{
var viewport = ToViewportBounds(ResolveDaemonRenderViewport());
var gravity = ResolveClient()
.GravityViewportAsync(viewport)
.GetAwaiter()
.GetResult();
_daemonGravityViewport = gravity;
}
catch (Exception ex)
{
Debug.LogWarning($"Failed to read Aetheria gravity viewport for zone renderer terrain: {ex.Message}");
}
_daemonBodyViews.Clear();
foreach (var bodyView in _daemonGravityViewport?.Bodies ?? Array.Empty<AetheriaRuntimeRtsBodyView>())
var bodySnapshotsByBodyKey = _zoneRenderBodies
.Where(body => body != null && !string.IsNullOrWhiteSpace(body.BodyKey))
.ToDictionary(body => body.BodyKey, StringComparer.Ordinal);
foreach (var bodyPose in _zoneRenderBodyPoses ?? Array.Empty<AetheriaRuntimeZoneRenderBodyPose>())
{
if (bodyView?.Body == null || string.IsNullOrWhiteSpace(bodyView.BodyKey))
if (bodyPose == null ||
string.IsNullOrWhiteSpace(bodyPose.BodyKey) ||
!bodySnapshotsByBodyKey.TryGetValue(bodyPose.BodyKey, out var body))
{
continue;
}
_daemonBodyViews.Add(new AetheriaRuntimeDaemonBodyView(
bodyView.Body,
ToDaemonBodyPose(bodyView),
bodyView.IsAsteroidBelt));
body,
ToDaemonBodyPose(bodyPose),
IsAsteroidBelt(body)));
}
var beltPosesByBodyKey = _daemonAsteroidBeltPoses
.Where(pose => !string.IsNullOrWhiteSpace(pose.BodyKey))
@ -739,6 +745,19 @@ public class ZoneRenderer : MonoBehaviour
_suns = _bodyViewsByBodyKey.Values.Where(p => p is SunObject).ToArray();
}
private void DisableLegacyTextureCameras()
{
if (!UseRenderSplatTextureLayers)
return;
foreach (var camera in FogCameras ?? Array.Empty<Camera>())
if (camera != null)
camera.gameObject.SetActive(false);
if (SlimeGravityCamera != null)
SlimeGravityCamera.gameObject.SetActive(false);
}
private AetheriaRuntimeXzRect ResolveDaemonRenderViewport()
{
var center = PerspectiveEntity?.CultPositionXZ ?? default;
@ -960,42 +979,31 @@ public class ZoneRenderer : MonoBehaviour
private double EvaluateGravityTerrainHeight(double positionX, double positionZ)
{
var gravity = _daemonGravityViewport;
if (gravity == null)
return 0;
var height = 0.0;
if (gravity.TerrainRadius > 0 && gravity.TerrainDepth != 0)
foreach (var body in _zoneRenderBodies ?? Array.Empty<AetheriaRuntimeBodySnapshotCommit>())
{
var distance = Math.Sqrt(positionX * positionX + positionZ * positionZ);
height -= PowerPulse(
distance / (gravity.TerrainRadius * 2.0),
Math.Max(0.0001, gravity.TerrainDepthExponent)) * gravity.TerrainDepth;
}
foreach (var brush in gravity.GravityInfluences ?? Array.Empty<AetheriaRuntimeRtsGravityInfluence>())
{
if (brush == null)
if (body == null)
continue;
var dx = positionX - brush.X;
var dz = positionZ - brush.Y;
var dx = positionX - body.GravityInfluenceCenterX;
var dz = positionZ - body.GravityInfluenceCenterZ;
var distance = Math.Sqrt(dx * dx + dz * dz);
if (brush.Radius > 0 && distance < brush.Radius && brush.GravityDepth != 0)
var radius = ResolveGravityRadius(body);
if (radius > 0 && distance < radius && body.GravityWellDepth != 0)
{
height -= PowerPulse(
distance / brush.Radius,
Math.Max(0.0001, brush.GravityDepthExponent)) * brush.GravityDepth;
distance / radius,
Math.Max(0.0001, body.GravityDepthExponent)) * body.GravityWellDepth;
}
if (brush.WaveRadius > 0 && distance < brush.WaveRadius && brush.WaveDepth != 0)
if (body.GravityWaveRadius > 0 && distance < body.GravityWaveRadius && body.GravityWaveDepth != 0)
{
height -= RadialWaves(
distance / brush.WaveRadius,
distance / body.GravityWaveRadius,
8.0,
1.25,
gravity.TerrainWaveFrequency,
DaemonSimulationTimeSeconds * brush.WaveSpeed) * brush.WaveDepth;
1.0,
DaemonSimulationTimeSeconds * body.GravityWaveSpeed) * body.GravityWaveDepth;
}
}
@ -1006,33 +1014,45 @@ public class ZoneRenderer : MonoBehaviour
double minimapGravityRange,
double maxDepth)
{
var gravity = _daemonGravityViewport;
if (gravity == null)
return new AetheriaRuntimeGravityTerrainBand(0, Math.Max(0, maxDepth));
var startDepth = PowerPulse(
minimapGravityRange,
Math.Max(0.0001, gravity.TerrainDepthExponent)) * gravity.TerrainDepth;
var terrainDepth = (_zoneRenderBodies ?? Array.Empty<AetheriaRuntimeBodySnapshotCommit>())
.Where(body => body != null)
.Select(body => Math.Max(0, body.GravityWellDepth))
.DefaultIfEmpty(0)
.Max();
var startDepth = Math.Min(terrainDepth, Math.Max(0, minimapGravityRange));
return new AetheriaRuntimeGravityTerrainBand(
startDepth,
gravity.TerrainDepth - startDepth + maxDepth);
Math.Max(0, terrainDepth - startDepth) + maxDepth);
}
private static AetheriaRuntimeDaemonBodyPose ToDaemonBodyPose(AetheriaRuntimeRtsBodyView bodyView)
private static AetheriaRuntimeDaemonBodyPose ToDaemonBodyPose(AetheriaRuntimeZoneRenderBodyPose bodyPose)
{
if (bodyView == null)
if (bodyPose == null)
return default;
return new AetheriaRuntimeDaemonBodyPose(
bodyView.BodyKey,
bodyView.OrbitKey,
"",
bodyView.Kind,
bodyView.X,
bodyView.Y,
0,
0,
bodyView.Body?.GravityWaveSpeed ?? 0);
bodyPose.BodyKey,
bodyPose.OrbitKey,
bodyPose.ParentOrbitKey,
bodyPose.Kind,
bodyPose.CenterX,
bodyPose.CenterZ,
bodyPose.ParentCenterX,
bodyPose.ParentCenterZ,
bodyPose.GravityWaveSpeed);
}
private static bool IsAsteroidBelt(AetheriaRuntimeBodySnapshotCommit body)
{
return (body?.Kind ?? "").IndexOf("asteroid", StringComparison.OrdinalIgnoreCase) >= 0;
}
private static double ResolveGravityRadius(AetheriaRuntimeBodySnapshotCommit body)
{
if (body != null && double.IsFinite(body.GravityInfluenceRadius) && body.GravityInfluenceRadius > 0)
return body.GravityInfluenceRadius;
return Math.Max(32, (body?.BodyRadiusMultiplier ?? 1) * 70);
}
private static double PowerPulse(double x, double exponent)