This repository has been archived on 2026-10-29. You can view files and clone it, but you cannot make any changes to its state, such as pushing and creating new issues, pull requests or comments.
EveUnity/packages/org.gamecult.eve.unity-scene/Runtime/Fields/EveUnityFieldsViewportFrame.cs
2026-07-16 17:44:44 +01:00

128 lines
5.5 KiB
C#

using System;
using System.Collections.Generic;
using System.Globalization;
using GameCult.Eve.PluginFields;
using UnityEngine;
#nullable enable
namespace GameCult.Eve.UnityScene.Fields
{
public static class EveUnityFieldsViewportFrame
{
public static bool TryResolve(
EveFieldsSplatsDocument source,
IReadOnlyDictionary<string, string> props,
Vector2 cameraPosition,
int snapTextureWidth,
int snapTextureHeight,
out EveFieldsSplatsDocument projected,
out Vector2 center)
{
projected = source;
center = default;
if (source == null || source.Viewport == null) return false;
var width = (float)(source.Viewport.MaxX - source.Viewport.MinX);
var height = (float)(source.Viewport.MaxY - source.Viewport.MinY);
if (!float.IsFinite(width) || !float.IsFinite(height) || width <= 0f || height <= 0f)
return false;
center = new Vector2(
(float)((source.Viewport.MinX + source.Viewport.MaxX) * 0.5),
(float)((source.Viewport.MinY + source.Viewport.MaxY) * 0.5));
if (!string.Equals(Prop(props, "viewportAnchor"), "active-camera.xz", StringComparison.Ordinal))
return true;
var span = PositiveInt(props, "span");
var cellWorldSize = PositiveFloat(props, "cellWorldSize");
var snapTexels = PositiveInt(props, "viewportSnapTexels");
if (!TryValidateSpatialLattice(
width, height, snapTextureWidth, snapTextureHeight,
span, cellWorldSize, snapTexels))
return false;
center.x = SnapCameraCoordinate(cameraPosition.x, width, snapTextureWidth, snapTexels);
center.y = SnapCameraCoordinate(cameraPosition.y, height, snapTextureHeight, snapTexels);
projected = Project(source, center, width, height);
return true;
}
public static float SnapCameraCoordinate(
float coordinate,
float viewportWidth,
int snapTargetWidth,
float snapTexels)
{
if (!float.IsFinite(coordinate) || !float.IsFinite(viewportWidth) ||
!float.IsFinite(snapTexels) || viewportWidth <= 0f ||
snapTargetWidth <= 0 || snapTexels <= 0f)
throw new ArgumentOutOfRangeException(nameof(viewportWidth));
var snapLength = viewportWidth * snapTexels / snapTargetWidth;
return (int)(coordinate / snapLength) * snapLength;
}
public static bool TryValidateSpatialLattice(
float viewportWidth,
float viewportHeight,
int gravityTextureWidth,
int gravityTextureHeight,
int span,
float cellWorldSize,
int gravityTexelsPerCell)
{
if (!float.IsFinite(viewportWidth) || !float.IsFinite(viewportHeight) ||
!float.IsFinite(cellWorldSize) || viewportWidth <= 0f || viewportHeight <= 0f ||
gravityTextureWidth <= 0 || gravityTextureHeight <= 0 || span <= 0 ||
cellWorldSize <= 0f || gravityTexelsPerCell <= 0)
return false;
var expectedExtent = span * cellWorldSize;
var expectedCellFromX = viewportWidth * gravityTexelsPerCell / gravityTextureWidth;
var expectedCellFromY = viewportHeight * gravityTexelsPerCell / gravityTextureHeight;
const float tolerance = 0.0001f;
return Mathf.Abs(viewportWidth - expectedExtent) <= tolerance &&
Mathf.Abs(viewportHeight - expectedExtent) <= tolerance &&
Mathf.Abs(expectedCellFromX - cellWorldSize) <= tolerance &&
Mathf.Abs(expectedCellFromY - cellWorldSize) <= tolerance;
}
private static EveFieldsSplatsDocument Project(
EveFieldsSplatsDocument source,
Vector2 center,
float width,
float height) =>
new EveFieldsSplatsDocument
{
Schema = source.Schema,
FrameId = source.FrameId,
PublishedAtUtc = source.PublishedAtUtc,
SimulationTimeSeconds = source.SimulationTimeSeconds,
RunId = source.RunId,
ZoneIndex = source.ZoneIndex,
ZoneName = source.ZoneName,
Viewport = new EveFieldsViewport
{
MinX = center.x - width * 0.5,
MinY = center.y - height * 0.5,
MaxX = center.x + width * 0.5,
MaxY = center.y + height * 0.5
},
Layers = source.Layers,
Splats = source.Splats
};
private static string Prop(IReadOnlyDictionary<string, string> props, string key) =>
props != null && props.TryGetValue(key, out var value) ? value ?? "" : "";
private static int PositiveInt(IReadOnlyDictionary<string, string> props, string key) =>
int.TryParse(Prop(props, key), NumberStyles.Integer, CultureInfo.InvariantCulture, out var value) && value > 0
? value
: 0;
private static float PositiveFloat(IReadOnlyDictionary<string, string> props, string key) =>
float.TryParse(Prop(props, key), NumberStyles.Float, CultureInfo.InvariantCulture, out var value) &&
float.IsFinite(value) && value > 0f
? value
: 0f;
}
}