Consume EveUnity fields projection

This commit is contained in:
Meta Vens 2026-07-10 15:45:19 +01:00
commit 002e709e98
13 changed files with 27 additions and 655 deletions

View file

@ -15,5 +15,6 @@
<ProjectReference Include="..\..\CultLib\src\GameCult.Networking\GameCult.Networking.csproj" />
<ProjectReference Include="..\..\CultMath\src\CultMath\CultMath.csproj" />
<ProjectReference Include="..\GameCult.Eve.Surface.csproj" />
<ProjectReference Include="..\..\EvePlugins\plugins\eve-plugin-fields\GameCult.Eve.PluginFields.csproj" />
</ItemGroup>
</Project>

View file

@ -1,137 +0,0 @@
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

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

View file

@ -1,300 +1,6 @@
using GameCult.Aetheria.State.Verse;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Experimental.Rendering;
using UnityEngine.Rendering;
using GameCult.Eve.UnityScene.Fields;
public sealed class AetheriaRenderSplatRasterizer : MonoBehaviour
// Existing scenes retain this MonoScript GUID while EveUnity owns projection.
public sealed class AetheriaRenderSplatRasterizer : EveFieldsSplatRasterizer
{
[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(AetheriaRuntimeViewportBounds viewport)
{
viewport ??= new AetheriaRuntimeViewportBounds();
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

@ -1,68 +0,0 @@
Shader "Aetheria/Render Splats"
{
Properties
{
_ValueScale("Value Scale", Float) = 1
}
SubShader
{
Tags { "RenderType" = "Transparent" "Queue" = "Transparent" }
Pass
{
Name "Add"
Cull Off
ZWrite Off
ZTest Always
BlendOp Add
Blend One One
HLSLPROGRAM
#pragma target 4.5
#pragma vertex Vert
#pragma fragment Frag
#include "UnityCG.cginc"
#define AETHERIA_SPLAT_PASS
#include "AetheriaRenderSplatsCore.hlsl"
ENDHLSL
}
Pass
{
Name "Max"
Cull Off
ZWrite Off
ZTest Always
BlendOp Max
Blend One One
HLSLPROGRAM
#pragma target 4.5
#pragma vertex Vert
#pragma fragment Frag
#include "UnityCG.cginc"
#define AETHERIA_SPLAT_PASS
#include "AetheriaRenderSplatsCore.hlsl"
ENDHLSL
}
Pass
{
Name "Alpha"
Cull Off
ZWrite Off
ZTest Always
Blend SrcAlpha OneMinusSrcAlpha
HLSLPROGRAM
#pragma target 4.5
#pragma vertex Vert
#pragma fragment Frag
#include "UnityCG.cginc"
#define AETHERIA_SPLAT_PASS
#include "AetheriaRenderSplatsCore.hlsl"
ENDHLSL
}
}
}

View file

@ -1,10 +0,0 @@
fileFormatVersion: 2
guid: a1aa8658e37f433cb08a29ab01f4e8e5
ShaderImporter:
externalObjects: {}
defaultTextures: []
nonModifiableTextures: []
preprocessorOverride: 0
userData:
assetBundleName:
assetBundleVariant:

View file

@ -1,115 +0,0 @@
#ifndef AETHERIA_RENDER_SPLATS_CORE_INCLUDED
#define AETHERIA_RENDER_SPLATS_CORE_INCLUDED
struct AetheriaSplat
{
float4 centerHalfExtent;
float4 rotationChannelFalloff;
float4 layerSource;
float4 sourceFrequencyPhase;
float4 value;
};
StructuredBuffer<AetheriaSplat> _AetheriaSplats;
float4x4 _AetheriaViewportToClip;
int _AetheriaSplatCount;
int _AetheriaChannelFilter;
float _ValueScale;
float hash21(float2 p)
{
p = frac(p * float2(123.34, 456.21));
p += dot(p, p + 45.32);
return frac(p.x * p.y);
}
float valueNoise(float2 p)
{
float2 i = floor(p);
float2 f = frac(p);
float a = hash21(i);
float b = hash21(i + float2(1, 0));
float c = hash21(i + float2(0, 1));
float d = hash21(i + float2(1, 1));
float2 u = f * f * (3 - 2 * f);
return lerp(lerp(a, b, u.x), lerp(c, d, u.x), u.y) * 2 - 1;
}
struct Varyings
{
float4 position : SV_POSITION;
float2 localUv : TEXCOORD0;
nointerpolation uint instanceId : TEXCOORD1;
};
Varyings Vert(uint vertexId : SV_VertexID, uint instanceId : SV_InstanceID)
{
static const float2 corners[6] =
{
float2(-1, -1),
float2(1, -1),
float2(1, 1),
float2(-1, -1),
float2(1, 1),
float2(-1, 1)
};
AetheriaSplat splat = _AetheriaSplats[instanceId];
float2 local = corners[vertexId];
float2 scaled = local * splat.centerHalfExtent.zw;
float c = splat.rotationChannelFalloff.x;
float s = splat.rotationChannelFalloff.y;
float2 world = splat.centerHalfExtent.xy + float2(
scaled.x * c - scaled.y * s,
scaled.x * s + scaled.y * c);
Varyings output;
output.position = mul(_AetheriaViewportToClip, float4(world, 0, 1));
output.localUv = local;
output.instanceId = instanceId;
return output;
}
float ResolveFalloff(float2 localUv, int falloff)
{
float distance01 = saturate(length(localUv));
if (falloff == 0)
return 1;
if (falloff == 1)
return saturate(1 - distance01);
if (falloff == 3)
{
float t = smoothstep(0, 1, distance01);
return t;
}
return 1 - smoothstep(0, 1, distance01);
}
float4 Frag(Varyings input) : SV_Target
{
AetheriaSplat splat = _AetheriaSplats[input.instanceId];
int channel = (int)round(splat.rotationChannelFalloff.z);
if (_AetheriaChannelFilter >= 0 && channel != _AetheriaChannelFilter)
discard;
int falloff = (int)round(splat.rotationChannelFalloff.w);
float alpha = ResolveFalloff(input.localUv, falloff);
clip(alpha - 0.0001);
int sourceKind = (int)round(splat.layerSource.y);
float source = 1;
if (sourceKind == 1 || sourceKind == 2)
{
float2 frequency = splat.sourceFrequencyPhase.xy;
float2 phase = splat.sourceFrequencyPhase.zw;
float timeOffset = sourceKind == 2 ? _Time.y * splat.layerSource.z : 0;
source = valueNoise(input.localUv * frequency + phase + timeOffset);
if (splat.layerSource.w != 0)
source = abs(source);
}
return splat.value * (alpha * source * _ValueScale);
}
#endif

View file

@ -1,10 +0,0 @@
fileFormatVersion: 2
guid: 91df759011f84ed8b003bb7828809e0b
ShaderImporter:
externalObjects: {}
defaultTextures: []
nonModifiableTextures: []
preprocessorOverride: 0
userData:
assetBundleName:
assetBundleVariant:

View file

@ -23,7 +23,8 @@
"org.gamecult.cultmath": "file:../../CultMath",
"org.gamecult.cultlib": "https://github.com/GameCult/CultLib.git?path=/unity/org.gamecult.cultlib#cultlib-unity-source-v1.0.3-r2",
"org.gamecult.eve.surface": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.surface#eveunity-surface-v0.1.0",
"org.gamecult.eve.unity-scene": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-scene#eveunity-scene-v0.1.1",
"org.gamecult.eve.plugin-fields": "https://github.com/GameCult/EvePlugins.git?path=/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields#eve-plugin-fields-unity-v0.1.1",
"org.gamecult.eve.unity-scene": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-scene#eveunity-scene-v0.1.2",
"org.gamecult.eve.unity-uitoolkit": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-uitoolkit#eveunity-uitoolkit-v0.1.0",
"com.unity.modules.accessibility": "1.0.0",
"com.unity.modules.adaptiveperformance": "1.0.0",

View file

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using GameCult.Caching;
using GameCult.Eve.PluginFields;
using MessagePack;
#nullable enable
@ -1305,7 +1306,7 @@ namespace GameCult.Aetheria.State.Verse
}
[MessagePackObject]
public sealed class AetheriaRuntimeViewportBounds
public sealed class AetheriaRuntimeViewportBounds : IEveFieldsViewport
{
[Key(0)]
public double MinX { get; set; }

View file

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using GameCult.Caching;
using GameCult.Eve.PluginFields;
using MessagePack;
#nullable enable
@ -52,7 +53,7 @@ namespace GameCult.Aetheria.State.Verse
[CultDocument("gamecult.fields.splats", "gamecult.fields.splats.v1")]
[MessagePackObject]
public sealed class AetheriaRuntimeRenderSplatsViewportDocument
public sealed class AetheriaRuntimeRenderSplatsViewportDocument : IEveFieldsSplatsDocument
{
[Key(0)]
public string Schema { get; set; } = AetheriaRuntimeDaemonSchemas.RenderSplatsViewport;
@ -84,10 +85,14 @@ namespace GameCult.Aetheria.State.Verse
[Key(9)]
public AetheriaRuntimeRenderSplatSoa Splats { get; set; } = new AetheriaRuntimeRenderSplatSoa();
IEveFieldsViewport IEveFieldsSplatsDocument.Viewport => Viewport;
IReadOnlyList<IEveFieldsSplatLayer> IEveFieldsSplatsDocument.Layers => Layers;
IEveFieldsSplatSoa IEveFieldsSplatsDocument.Splats => Splats;
}
[MessagePackObject]
public sealed class AetheriaRuntimeRenderSplatLayerDefinition
public sealed class AetheriaRuntimeRenderSplatLayerDefinition : IEveFieldsSplatLayer
{
[Key(0)]
public string LayerKey { get; set; } = "";
@ -121,7 +126,7 @@ namespace GameCult.Aetheria.State.Verse
}
[MessagePackObject]
public sealed class AetheriaRuntimeRenderSplatSoa
public sealed class AetheriaRuntimeRenderSplatSoa : IEveFieldsSplatSoa
{
[Key(0)]
public int Count { get; set; }

View file

@ -3,6 +3,7 @@
"rootNamespace": "GameCult.Aetheria.State.Unity",
"references": [
"GameCult.Eve.Surface",
"GameCult.Eve.PluginFields",
"GameCult.Mesh",
"CultMath",
"MessagePack"

View file

@ -323,6 +323,13 @@
"source": "local",
"dependencies": {}
},
"org.gamecult.eve.plugin-fields": {
"version": "https://github.com/GameCult/EvePlugins.git?path=/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields#eve-plugin-fields-unity-v0.1.1",
"depth": 0,
"source": "git",
"dependencies": {},
"hash": "0b099274a4e2b4c301eced4322166837792f585a"
},
"org.gamecult.eve.surface": {
"version": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.surface#eveunity-surface-v0.1.0",
"depth": 0,
@ -333,14 +340,15 @@
"hash": "e10821f1cc81f619479c6507438f6a097d1f0ce2"
},
"org.gamecult.eve.unity-scene": {
"version": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-scene#eveunity-scene-v0.1.1",
"version": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-scene#eveunity-scene-v0.1.2",
"depth": 0,
"source": "git",
"dependencies": {
"org.gamecult.cultlib": "1.0.3",
"org.gamecult.eve.plugin-fields": "0.1.1",
"org.gamecult.eve.surface": "0.1.0"
},
"hash": "7e65154d7f42cf1c4ac55871c35676e7ccd2e5b2"
"hash": "3e23fff486f2d29451cad59eafc7a6a2d4ac460b"
},
"org.gamecult.eve.unity-uitoolkit": {
"version": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-uitoolkit#eveunity-uitoolkit-v0.1.0",