29 Commits

Author SHA1 Message Date
8da46c0511 build: Update project version 2025-04-29 19:54:46 +08:00
75b5d7708c fix: Disable force HTTPS in NOAA Atom worker when debugging 2025-04-29 19:54:27 +08:00
5b2177a795 feat: Add using raw location name settings for CENC earthquake reports 2025-04-29 19:54:16 +08:00
8cb33dca5f ci: Update plugins 2025-04-29 19:53:24 +08:00
ae2e0af18a fix: Improve ongoing report round-robin period 2025-04-29 19:52:41 +08:00
2ac5a3d4f0 build: Update project version 2025-04-08 10:01:06 +08:00
5f78a1afde refactor: Code cleanup 2025-04-08 10:00:47 +08:00
ef5cf78a03 feat: Add display culture config 2025-04-08 10:00:28 +08:00
b60e62af70 feat: Adapt to breaking changes 2025-04-08 10:00:01 +08:00
3e59fe1462 feat: Implement region editor 2025-04-08 09:56:47 +08:00
4b4bf5ed65 ci: Update plugins 2025-04-08 09:53:33 +08:00
a4f78b3a95 build: Update project version 2025-03-24 00:07:35 +08:00
074a58dabd perf: Optimize performance for status view 2025-03-24 00:06:51 +08:00
a2ef175a81 fix: Fix potentially bad formats in culture info 2025-03-24 00:06:29 +08:00
915ba55c2e fix: Correct lifecycle of event listeners 2025-03-24 00:05:35 +08:00
f154a2a468 fix: Fix history list view flashing hard on update 2025-03-24 00:04:29 +08:00
c52d438d40 ci: Update plugins 2025-03-24 00:03:33 +08:00
8d3f53ba13 perf: Improve performance for line renderer 2025-03-20 21:49:28 +08:00
1db25e62e7 fix: Fix wave circle shape when hypocenter is near antimeridian 2025-03-20 21:47:52 +08:00
18312176d9 refactor: Decouple grouping logic from core worker 2025-03-20 17:53:10 +08:00
5be6e32b03 refactor: Pull up map tile cache manager 2025-03-20 17:52:17 +08:00
b33e5ca223 build: Update project version 2025-02-26 15:58:15 +08:00
fe4430b1bf feat: Add new event sources 2025-02-26 15:58:02 +08:00
5a48073152 ci: Update plugins 2025-02-26 15:57:04 +08:00
41c1e6f9fd fix: Fix network events invalidating map view 2025-02-26 15:36:02 +08:00
0743fa45eb fix: Keep ongoing events if present on reported 2025-02-26 15:18:37 +08:00
7f2c0d2e23 fix: Keep ongoing events if present on reported 2025-02-26 15:05:34 +08:00
14202714cc fix: Fix error on polygon elements with holes 2025-02-26 01:20:18 +08:00
ce0c23805a refactor: Remove useless suppressions 2025-02-22 15:39:38 +08:00
241 changed files with 5376 additions and 573 deletions

View File

@@ -56,7 +56,7 @@ namespace Cryville.Common.Unity.UI {
const float _placeholderLength = 100; const float _placeholderLength = 100;
int _firstIndex, _lastIndex; int _firstIndex, _lastIndex;
readonly Stack<GameObject> _pool = new(); readonly Stack<GameObject> _pool = new();
void Update() { void LateUpdate() {
int axis = (int)m_direction; int axis = (int)m_direction;
int sign = m_direction == 0 ? 1 : -1; int sign = m_direction == 0 ? 1 : -1;
float padding = axis == 0 ? m_padding.left : m_padding.top; float padding = axis == 0 ? m_padding.left : m_padding.top;

View File

@@ -1,3 +1,3 @@
using System.Reflection; using System.Reflection;
[assembly: AssemblyVersion("0.0.1")] [assembly: AssemblyVersion("0.0.5")]

View File

@@ -13,6 +13,8 @@ namespace Cryville.EEW.Unity {
bool DoDisplayTimeZone, bool DoDisplayTimeZone,
bool DoSwitchBackToHistory, bool DoSwitchBackToHistory,
string OverrideDisplayCulture,
IReadOnlyCollection<EventSourceConfig> EventSources IReadOnlyCollection<EventSourceConfig> EventSources
) { ) {
public static Config Default => new( public static Config Default => new(
@@ -25,6 +27,8 @@ namespace Cryville.EEW.Unity {
true, true,
true, true,
"",
new List<EventSourceConfig> { new List<EventSourceConfig> {
new JMAAtomEventSourceConfig(Array.Empty<string>()), new JMAAtomEventSourceConfig(Array.Empty<string>()),
new UpdateCheckerEventSourceConfig(), new UpdateCheckerEventSourceConfig(),
@@ -34,21 +38,27 @@ namespace Cryville.EEW.Unity {
} }
[JsonPolymorphic(TypeDiscriminatorPropertyName = "Type", UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] [JsonPolymorphic(TypeDiscriminatorPropertyName = "Type", UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)]
[JsonDerivedType(typeof(BMKGOpenDataEventSourceConfig), "BMKGOpenData")]
[JsonDerivedType(typeof(CWAOpenDataEventSourceConfig), "CWAOpenData")] [JsonDerivedType(typeof(CWAOpenDataEventSourceConfig), "CWAOpenData")]
[JsonDerivedType(typeof(EMSCRealTimeEventSourceConfig), "EMSCRealTime")]
[JsonDerivedType(typeof(GlobalQuakeServerEventSourceConfig), "GlobalQuakeServer")] [JsonDerivedType(typeof(GlobalQuakeServerEventSourceConfig), "GlobalQuakeServer")]
[JsonDerivedType(typeof(GlobalQuakeServer15EventSourceConfig), "GlobalQuakeServer15")] [JsonDerivedType(typeof(GlobalQuakeServer15EventSourceConfig), "GlobalQuakeServer15")]
[JsonDerivedType(typeof(JMAAtomEventSourceConfig), "JMAAtom")] [JsonDerivedType(typeof(JMAAtomEventSourceConfig), "JMAAtom")]
[JsonDerivedType(typeof(NOAAEventSourceConfig), "NOAA")] [JsonDerivedType(typeof(NOAAEventSourceConfig), "NOAA")]
[JsonDerivedType(typeof(UpdateCheckerEventSourceConfig), "UpdateChecker")] [JsonDerivedType(typeof(UpdateCheckerEventSourceConfig), "UpdateChecker")]
[JsonDerivedType(typeof(USGSQuakeMLEventSourceConfig), "USGSQuakeML")]
[JsonDerivedType(typeof(WolfxEventSourceConfig), "Wolfx")] [JsonDerivedType(typeof(WolfxEventSourceConfig), "Wolfx")]
abstract record EventSourceConfig(); abstract record EventSourceConfig();
record BMKGOpenDataEventSourceConfig([property: JsonRequired] string[] Subtypes) : EventSourceConfig;
record CWAOpenDataEventSourceConfig([property: JsonRequired] string Subtype, [property: JsonRequired] string Token) : EventSourceConfig; record CWAOpenDataEventSourceConfig([property: JsonRequired] string Subtype, [property: JsonRequired] string Token) : EventSourceConfig;
record EMSCRealTimeEventSourceConfig() : EventSourceConfig;
record GlobalQuakeServerEventSourceConfig([property: JsonRequired] string Host, int Port = 38000) : EventSourceConfig; record GlobalQuakeServerEventSourceConfig([property: JsonRequired] string Host, int Port = 38000) : EventSourceConfig;
record GlobalQuakeServer15EventSourceConfig(string Host, int Port = 38000) : GlobalQuakeServerEventSourceConfig(Host, Port); record GlobalQuakeServer15EventSourceConfig(string Host, int Port = 38000) : GlobalQuakeServerEventSourceConfig(Host, Port);
record JMAAtomEventSourceConfig(IReadOnlyCollection<string> Filter = null, bool IsFilterWhitelist = false) : EventSourceConfig; record JMAAtomEventSourceConfig(IReadOnlyCollection<string> Filter = null, bool IsFilterWhitelist = false) : EventSourceConfig;
record NOAAEventSourceConfig([property: JsonRequired] string Subtype) : EventSourceConfig; record NOAAEventSourceConfig([property: JsonRequired] string Subtype) : EventSourceConfig;
record UpdateCheckerEventSourceConfig : EventSourceConfig; record UpdateCheckerEventSourceConfig : EventSourceConfig;
record WolfxEventSourceConfig(IReadOnlyCollection<string> Filter = null, bool IsFilterWhitelist = false) : EventSourceConfig; record USGSQuakeMLEventSourceConfig([property: JsonRequired] string Subtype) : EventSourceConfig;
record WolfxEventSourceConfig(IReadOnlyCollection<string> Filter = null, bool IsFilterWhitelist = false, bool UseRawCENCLocationName = false) : EventSourceConfig;
[JsonSerializable(typeof(Config))] [JsonSerializable(typeof(Config))]
[JsonSourceGenerationOptions(WriteIndented = true)] [JsonSourceGenerationOptions(WriteIndented = true)]

View File

@@ -15,17 +15,26 @@ namespace Cryville.EEW.Unity.Map {
MapElementManager m_layerElementSub; MapElementManager m_layerElementSub;
[SerializeField] [SerializeField]
GameObject m_prefabTile; GameObject m_prefabTile;
[SerializeField]
GameObject m_prefabBitmapHolder;
[SerializeField]
int m_maxMapTileZoom = 10;
[SerializeField]
bool m_isEditor;
readonly MapTileCacheManager _tiles = new(); MapTileCacheManager _tiles;
float _elementLayerZ; float _elementLayerZ;
void Start() { void Start() {
_camera = GetComponent<Camera>(); _camera = GetComponent<Camera>();
_tiles = m_isEditor ? new EditorMapTileCacheManager() : new MapTileCacheManager();
_tiles.ExtraCachedZoomLevel = 20;
_tiles.Parent = m_layerTile; _tiles.Parent = m_layerTile;
_tiles.PrefabTile = m_prefabTile; _tiles.PrefabTile = m_prefabTile;
_tiles.PrefabBitmapHolder = m_prefabBitmapHolder;
_tiles.CacheDir = Application.temporaryCachePath; _tiles.CacheDir = Application.temporaryCachePath;
_camera.orthographicSize = 0.5f / MathF.Max(1, (float)_camera.pixelWidth / _camera.pixelHeight); _camera.orthographicSize = 0.5f / MathF.Max(1, (float)_camera.pixelWidth / _camera.pixelHeight);
_elementLayerZ = m_layerElement.transform.position.z; if (m_layerElement != null) _elementLayerZ = m_layerElement.transform.position.z;
_mapElementUpdated = true; _mapElementUpdated = true;
} }
void OnDestroy() { void OnDestroy() {
@@ -70,7 +79,7 @@ namespace Cryville.EEW.Unity.Map {
} }
} }
void ZoomToMapElement() { void ZoomToMapElement() {
var aabb = m_layerElement.AABB; var aabb = m_layerElement != null ? m_layerElement.AABB : null;
if (aabb is not RectangleF b) return; if (aabb is not RectangleF b) return;
if (b.Width * _camera.pixelHeight < _camera.pixelWidth * b.Height) if (b.Width * _camera.pixelHeight < _camera.pixelWidth * b.Height)
Scale = b.Height; Scale = b.Height;
@@ -93,14 +102,17 @@ namespace Cryville.EEW.Unity.Map {
transform.localPosition = new(nx, Math.Clamp(transform.position.y, h / 2 - 1, -h / 2), -20); transform.localPosition = new(nx, Math.Clamp(transform.position.y, h / 2 - 1, -h / 2), -20);
var bounds = new Bounds((Vector2)transform.position, new Vector2(w, h)); var bounds = new Bounds((Vector2)transform.position, new Vector2(w, h));
int zoom = Math.Clamp((int)Math.Log(vz / 256, 2) + 1, 0, 10); int zoom = Math.Clamp((int)Math.Log(vz / 256, 2) + 1, 0, m_maxMapTileZoom);
int zoomScale = 1 << zoom; int zoomScale = 1 << zoom;
_tiles.MoveTo( _tiles.MoveTo(
new(Mathf.FloorToInt(bounds.min.x * zoomScale), Mathf.FloorToInt(-bounds.max.y * zoomScale), zoom), new(Mathf.FloorToInt(bounds.min.x * zoomScale), Mathf.FloorToInt(-bounds.max.y * zoomScale), zoom),
new(Mathf.CeilToInt(bounds.max.x * zoomScale), Mathf.CeilToInt(-bounds.min.y * zoomScale), zoom) new(Mathf.CeilToInt(bounds.max.x * zoomScale), Mathf.CeilToInt(-bounds.min.y * zoomScale), zoom)
); );
if (m_layerElement != null) {
m_layerElement.Scale = h; m_layerElement.Scale = h;
}
if (m_layerElementSub != null) {
m_layerElementSub.Scale = h; m_layerElementSub.Scale = h;
if (nx - w / 2 < 0) { if (nx - w / 2 < 0) {
@@ -116,4 +128,5 @@ namespace Cryville.EEW.Unity.Map {
} }
} }
} }
}
} }

View File

@@ -0,0 +1,15 @@
using Cryville.EEW.Core.Map;
using System.IO;
using UnityEngine;
namespace Cryville.EEW.Unity.Map {
sealed class EditorMapTileCacheManager : MapTileCacheManager {
protected override MapTileBitmapHolder CreateBitmapHolder(MapTileIndex index) => new(
index,
GameObject.Instantiate(PrefabBitmapHolder, Parent, false),
new($"https://tile.openstreetmap.org/{index.Z}/{index.NX}/{index.NY}.png")
);
protected override string GetCacheFilePath(MapTileIndex index) => Path.Combine(CacheDir, $"map_editor/{index.Z}/{index.NX}/{index.NY}");
}
}

View File

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

View File

@@ -98,24 +98,25 @@ namespace Cryville.EEW.Unity.Map.Element {
for (int d = 0; d < 360; d++) { for (int d = 0; d < 360; d++) {
Quaternion q = Quaternion.AngleAxis(d, axis); Quaternion q = Quaternion.AngleAxis(d, axis);
Vector3 p = q * rp; Vector3 p = q * rp;
Vector2 p2 = ToTilePos(p).ToVector2(); AddVertex(renderer, ref lp2, ref segmentIndex, d, p);
if (lp2 != null) {
float dx = p2.x - lp2.Value.x;
if (MathF.Abs(dx) >= 0.5) {
_vertexBuffer[d] = p2.x < 0.5 ? p2 + new Vector2(1, 0) : p2 - new Vector2(1, 0);
renderer.AddSegment(_vertexBuffer, segmentIndex, d - segmentIndex + 1);
segmentIndex = d;
} }
} AddVertex(renderer, ref lp2, ref segmentIndex, 360, rp);
_vertexBuffer[d] = p2;
lp2 = p2;
}
Vector2 rp2 = ToTilePos(rp).ToVector2();
_vertexBuffer[360] = rp2;
renderer.AddSegment(_vertexBuffer, segmentIndex, 361 - segmentIndex); renderer.AddSegment(_vertexBuffer, segmentIndex, 361 - segmentIndex);
} }
renderer.SetMaterial(isHistory ? m_historyMaterial : m_ongoingMaterial); renderer.SetMaterial(isHistory ? m_historyMaterial : m_ongoingMaterial);
} }
void AddVertex(MultiLineRenderer renderer, ref Vector2? lp2, ref int segmentIndex, int d, Vector3 p) {
Vector2 p2 = ToTilePos(p).ToVector2();
if (lp2 != null && MathF.Abs(p2.x - lp2.Value.x) >= 0.5) {
_vertexBuffer[d] = p2.x < 0.5 ? p2 + new Vector2(1, 0) : p2 - new Vector2(1, 0);
renderer.AddSegment(_vertexBuffer, segmentIndex, d - segmentIndex + 1);
segmentIndex = d;
}
_vertexBuffer[d] = p2;
lp2 = p2;
}
static PointF ToTilePos(Vector3 p) => MapTileUtils.WorldToTilePos(new(MathF.Atan2(p.z, p.x) / MathF.PI * 180f, MathF.Asin(p.y) / MathF.PI * 180f)); static PointF ToTilePos(Vector3 p) => MapTileUtils.WorldToTilePos(new(MathF.Atan2(p.z, p.x) / MathF.PI * 180f, MathF.Asin(p.y) / MathF.PI * 180f));
} }
} }

View File

@@ -150,12 +150,8 @@ namespace Cryville.EEW.Unity.Map {
_mesh.Clear(); _mesh.Clear();
if (_positions == null) return; if (_positions == null) return;
if (_positionCount <= 1) return; if (_positionCount <= 1) return;
float hw = m_width / 2;
int maxVertexCount = 4 * (_positionCount - 1);
var vbuf = ArrayPool<Vector3>.Shared.Rent(maxVertexCount);
var ubuf = ArrayPool<Vector2>.Shared.Rent(maxVertexCount);
var ibuf = ArrayPool<int>.Shared.Rent(3 * (2 + 4 * (_positionCount - 2)));
float hw = m_width / 2;
int i, vi = 0, ii = 0, li = 0, ri = 1; int i, vi = 0, ii = 0, li = 0, ri = 1;
float uvScale = 1 / (m_tilingScale * m_width); float uvScale = 1 / (m_tilingScale * m_width);
Vector2 p0 = _positions[0], p1 = default; Vector2 p0 = _positions[0], p1 = default;
@@ -163,6 +159,12 @@ namespace Cryville.EEW.Unity.Map {
if ((p1 = _positions[i]) != p0) break; if ((p1 = _positions[i]) != p0) break;
} }
if (i >= _positionCount) return; if (i >= _positionCount) return;
int maxVertexCount = 4 * (_positionCount - 1);
var vbuf = ArrayPool<Vector3>.Shared.Rent(maxVertexCount);
var ubuf = ArrayPool<Vector2>.Shared.Rent(maxVertexCount);
var ibuf = ArrayPool<int>.Shared.Rent(3 * (2 + 4 * (_positionCount - 2)));
Vector2 dp0 = NormalizeSmallVector(p1 - p0), np0 = GetNormal(dp0 * hw); Vector2 dp0 = NormalizeSmallVector(p1 - p0), np0 = GetNormal(dp0 * hw);
vbuf[vi] = p0 - np0; ubuf[vi++] = new(0, 0); vbuf[vi] = p0 - np0; ubuf[vi++] = new(0, 0);
vbuf[vi] = p0 + np0; ubuf[vi++] = new(0, 1); vbuf[vi] = p0 + np0; ubuf[vi++] = new(0, 1);

View File

@@ -1,9 +1,12 @@
using Cryville.EEW.BMKGOpenData.Map;
using Cryville.EEW.Core; using Cryville.EEW.Core;
using Cryville.EEW.CWAOpenData.Map; using Cryville.EEW.CWAOpenData.Map;
using Cryville.EEW.EMSC.Map;
using Cryville.EEW.GlobalQuake.Map; using Cryville.EEW.GlobalQuake.Map;
using Cryville.EEW.JMAAtom.Map; using Cryville.EEW.JMAAtom.Map;
using Cryville.EEW.Map; using Cryville.EEW.Map;
using Cryville.EEW.NOAA.Map; using Cryville.EEW.NOAA.Map;
using Cryville.EEW.QuakeML.Map;
using Cryville.EEW.Report; using Cryville.EEW.Report;
using Cryville.EEW.Wolfx.Map; using Cryville.EEW.Wolfx.Map;
using System.Collections.Generic; using System.Collections.Generic;
@@ -23,6 +26,7 @@ namespace Cryville.EEW.Unity.Map {
readonly List<int> _displayingOrder = new(); readonly List<int> _displayingOrder = new();
public int Count => _displayingReports.Count; public int Count => _displayingReports.Count;
public int OngoingCount => _displayingReports.Count - (_selected != null ? 1 : 0);
[SerializeField] MapElementManager m_subManager; [SerializeField] MapElementManager m_subManager;
@@ -123,16 +127,19 @@ namespace Cryville.EEW.Unity.Map {
} }
readonly ContextedGeneratorManager<IMapGeneratorContext, MapElement> _gen = new(new IContextedGenerator<IMapGeneratorContext, MapElement>[] { readonly ContextedGeneratorManager<IMapGeneratorContext, MapElement> _gen = new(new IContextedGenerator<IMapGeneratorContext, MapElement>[] {
new BMKGEarthquakeMapGenerator(),
new CENCEarthquakeMapGenerator(), new CENCEarthquakeMapGenerator(),
new CENCEEWMapGenerator(), new CENCEEWMapGenerator(),
new CWAEarthquakeMapGenerator(), new CWAEarthquakeMapGenerator(),
new CWAEEWMapGenerator(), new CWAEEWMapGenerator(),
new CWATsunamiMapGenerator(), new CWATsunamiMapGenerator(),
new EMSCRealTimeEventMapGenerator(),
new FujianEEWMapGenerator(), new FujianEEWMapGenerator(),
new GlobalQuakeMapViewGenerator(), new GlobalQuakeMapViewGenerator(),
new JMAAtomMapGenerator(), new JMAAtomMapGenerator(),
new JMAEEWMapGenerator(), new JMAEEWMapGenerator(),
new NOAAMapGenerator(), new NOAAMapGenerator(),
new QuakeMLEventMapGenerator(),
new SichuanEEWMapGenerator(), new SichuanEEWMapGenerator(),
}); });
public UnityMapElement Build(object e, out CultureInfo culture, out int order) { public UnityMapElement Build(object e, out CultureInfo culture, out int order) {

View File

@@ -1,44 +1,21 @@
using Cryville.EEW.Core.Map; using Cryville.EEW.Core.Map;
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine; using UnityEngine;
using UnityEngine.Networking;
namespace Cryville.EEW.Unity.Map { namespace Cryville.EEW.Unity.Map {
[RequireComponent(typeof(SpriteRenderer))] [RequireComponent(typeof(SpriteRenderer))]
sealed class MapTile : MonoBehaviour { sealed class MapTile : MonoBehaviour {
static readonly SemaphoreSlim _semaphore = new(2);
static readonly HttpClient _httpClient = new() { Timeout = TimeSpan.FromSeconds(10) };
[SerializeField] Transform _idView; [SerializeField] Transform _idView;
public MapTileIndex Index { get; set; } public MapTileIndex Index { get; set; }
public bool IsEmpty { get; private set; }
Action<MapTile> _callback;
SpriteRenderer _renderer; SpriteRenderer _renderer;
UnityWebRequest _req;
DownloadHandlerTexture _texHandler;
Texture2D _tex;
Sprite _sprite;
void Awake() { void Awake() {
_renderer = GetComponent<SpriteRenderer>(); _renderer = GetComponent<SpriteRenderer>();
} }
FileInfo _localFile; public void Init(MapTileIndex index) {
bool _downloadDone;
public void Load(MapTileIndex index, string cacheDir, Action<MapTile> onUpdated) {
Index = index; Index = index;
_callback = onUpdated;
_localFile = new(Path.Combine(cacheDir, $"map/{Index.Z}/{Index.NX}/{Index.NY}"));
float z = 1 << index.Z; float z = 1 << index.Z;
transform.localPosition = new(index.X / z, -(index.Y + 1) / z, -index.Z / 100f); transform.localPosition = new(index.X / z, -(index.Y + 1) / z, -index.Z / 100f);
transform.localScale = new Vector3(1 / z, 1 / z, 1); transform.localScale = new Vector3(1 / z, 1 / z, 1);
@@ -46,71 +23,22 @@ namespace Cryville.EEW.Unity.Map {
byte e = SharedSettings.Instance.IdBytes[((index.X << 2) + index.Y) & 0x1f]; byte e = SharedSettings.Instance.IdBytes[((index.X << 2) + index.Y) & 0x1f];
int ex = e >> 4, ey = e & 0xf; int ex = e >> 4, ey = e & 0xf;
_idView.localPosition = new(ex / 16f, 1 - ey / 16f, -1 / 200f); _idView.localPosition = new(ex / 16f, 1 - ey / 16f, -1 / 200f);
if (_localFile.Exists) {
_downloadDone = true;
}
else {
Task.Run(() => RunAsync($"https://server.arcgisonline.com/ArcGIS/rest/services/Ocean/World_Ocean_Base/MapServer/tile/{Index.Z}/{Index.NY}/{Index.NX}"));
}
}
async Task RunAsync(string url) {
await _semaphore.WaitAsync().ConfigureAwait(true);
try {
Directory.CreateDirectory(_localFile.DirectoryName);
using var webStream = await _httpClient.GetStreamAsync(new Uri(url)).ConfigureAwait(true);
using var fileStream = new FileStream(_localFile.FullName, FileMode.Create, FileAccess.Write);
await webStream.CopyToAsync(fileStream).ConfigureAwait(true);
}
finally {
_semaphore.Release();
}
_downloadDone = true;
} }
[SuppressMessage("CodeQuality", "IDE0051", Justification = "Unity message")] public void Set(Sprite sprite) {
if (_renderer) {
_renderer.sprite = sprite;
}
}
bool _isDestroyed;
public void Destroy() {
_isDestroyed = true;
}
void Update() { void Update() {
if (_downloadDone) { if (_isDestroyed) {
try { Destroy(gameObject);
_texHandler = new DownloadHandlerTexture();
_req = new UnityWebRequest($"file:///{_localFile}") {
downloadHandler = _texHandler,
disposeDownloadHandlerOnDispose = true,
};
_req.SendWebRequest();
} }
catch (Exception ex) {
Debug.LogException(ex);
}
_downloadDone = false;
}
if (_req == null || !_req.isDone) return;
if (_texHandler.isDone) {
_tex = _texHandler.texture;
_tex.wrapMode = TextureWrapMode.Clamp;
_sprite = Sprite.Create(_tex, new Rect(0, 0, _tex.width, _tex.height), Vector2.zero, _tex.height, 0, SpriteMeshType.FullRect, Vector4.zero, false);
_renderer.sprite = _sprite;
}
else {
Debug.LogError(_req.error);
_localFile.Delete();
IsEmpty = true;
}
_req.Dispose();
_req = null;
_callback?.Invoke(this);
}
[SuppressMessage("CodeQuality", "IDE0051", Justification = "Unity message")]
void OnDestroy() {
if (_req != null) {
_req.Abort();
_req.Dispose();
_texHandler.Dispose();
}
if (_sprite) Destroy(_sprite);
if (_tex) Destroy(_tex);
IsEmpty = true;
_callback?.Invoke(this);
} }
} }
} }

View File

@@ -0,0 +1,36 @@
using Cryville.EEW.Core.Map;
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
namespace Cryville.EEW.Unity.Map {
sealed class MapTileBitmapHolder : Core.Map.MapTileBitmapHolder {
readonly MapTileBitmapHolderBehaviour _behaviour;
readonly Uri _uri;
public MapTileBitmapHolder(MapTileIndex index, GameObject gameObject, Uri uri) : base(index) {
_behaviour = gameObject.GetComponent<MapTileBitmapHolderBehaviour>();
_uri = uri;
}
protected override void Dispose(bool disposing) {
base.Dispose(disposing);
if (disposing) {
if (_behaviour) _behaviour.Destroy();
}
}
protected override Uri GetUri() => _uri;
protected override Task LoadBitmap(FileInfo file, CancellationToken cancellationToken) {
_behaviour.Load(file);
return Task.CompletedTask;
}
public void Bind(MapTile tile) {
_behaviour.Bind(tile);
}
}
}

View File

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

View File

@@ -0,0 +1,79 @@
using System;
using System.IO;
using UnityEngine;
using UnityEngine.Networking;
namespace Cryville.EEW.Unity.Map {
sealed class MapTileBitmapHolderBehaviour : MonoBehaviour {
Action<Sprite> _callback;
public void Bind(MapTile tile) {
if (_isDone)
tile.Set(_sprite);
else
_callback += tile.Set;
}
UnityWebRequest _req;
DownloadHandlerTexture _texHandler;
Texture2D _tex;
Sprite _sprite;
FileInfo _localFile;
bool _isReady;
bool _isDone;
public void Load(FileInfo file) {
_localFile = file;
_isReady = true;
}
void Update() {
if (_isDestroyed) {
Destroy(gameObject);
return;
}
if (_isReady) {
try {
_texHandler = new DownloadHandlerTexture();
_req = new UnityWebRequest($"file:///{_localFile}") {
downloadHandler = _texHandler,
disposeDownloadHandlerOnDispose = true,
};
_req.SendWebRequest();
}
catch (Exception ex) {
Debug.LogException(ex);
}
_isReady = false;
}
if (_req == null || !_req.isDone) return;
if (_texHandler.isDone && _texHandler.texture != null) {
_tex = _texHandler.texture;
_tex.wrapMode = TextureWrapMode.Clamp;
_sprite = Sprite.Create(_tex, new Rect(0, 0, _tex.width, _tex.height), Vector2.zero, _tex.height, 0, SpriteMeshType.FullRect, Vector4.zero, false);
}
else {
Debug.LogError(_texHandler.error);
_localFile.Delete();
}
_req.Dispose();
_texHandler.Dispose();
_req = null;
_callback?.Invoke(_sprite);
_isDone = true;
}
bool _isDestroyed;
public void Destroy() {
_isDestroyed = true;
}
void OnDestroy() {
if (_req != null) {
_req.Abort();
_req.Dispose();
_texHandler.Dispose();
}
if (_sprite) Destroy(_sprite);
if (_tex) Destroy(_tex);
}
}
}

View File

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

View File

@@ -1,165 +1,40 @@
using Cryville.EEW.Core.Map; using Cryville.EEW.Core.Map;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using UnityEngine; using UnityEngine;
namespace Cryville.EEW.Unity.Map { namespace Cryville.EEW.Unity.Map {
sealed class TileZOrderComparer : IComparer<MapTileIndex>, IComparer<MapTile> { class MapTileCacheManager : MapTileCacheManager<MapTileBitmapHolder> {
public static readonly TileZOrderComparer Instance = new(); public GameObject PrefabTile { get; set; }
public int Compare(MapTileIndex a, MapTileIndex b) { public GameObject PrefabBitmapHolder { get; set; }
var c = a.Z.CompareTo(b.Z);
if (c != 0) return c;
c = a.Y.CompareTo(b.Y);
if (c != 0) return c;
return a.X.CompareTo(b.X);
}
public int Compare(MapTile a, MapTile b) {
if (a == null) return b == null ? 0 : -1;
if (b == null) return 1;
return Compare(a.Index, b.Index);
}
}
sealed class MapTileCacheManager : IDisposable {
public int ExtraCachedZoomLevel { get; set; } = 20;
GameObject m_prefabTile;
public GameObject PrefabTile {
get => m_prefabTile;
set {
m_prefabTile = value;
if (_dummyTask) GameObject.Destroy(_dummyTask.gameObject);
_dummyTask = GameObject.Instantiate(m_prefabTile, Parent, false).GetComponent<MapTile>();
}
}
public Transform Parent { get; set; } public Transform Parent { get; set; }
public string CacheDir { get; set; } public string CacheDir { get; set; }
public event Action Updated; protected override MapTileBitmapHolder CreateBitmapHolder(MapTileIndex index) => new(
void OnUpdated(MapTile tile) { index,
if (tile.IsEmpty) { GameObject.Instantiate(PrefabBitmapHolder, Parent, false),
lock (ActiveTiles) { new($"https://server.arcgisonline.com/ArcGIS/rest/services/Ocean/World_Ocean_Base/MapServer/tile/{index.Z}/{index.NY}/{index.NX}")
if (_cache.Remove(tile.Index)) { );
ActiveTiles.RemoveAt(ActiveTiles.BinarySearch(tile, TileZOrderComparer.Instance));
} protected override string GetCacheFilePath(MapTileIndex index) => Path.Combine(CacheDir, $"map/{index.Z}/{index.NX}/{index.NY}");
}
} readonly Dictionary<MapTile<MapTileBitmapHolder>, MapTile> _map = new();
Updated?.Invoke(); protected override void OnTileCreated(MapTile<MapTileBitmapHolder> tile) {
base.OnTileCreated(tile);
var gameObject = GameObject.Instantiate(PrefabTile, Parent, false);
var uTile = gameObject.GetComponent<MapTile>();
_map.Add(tile, uTile);
uTile.Init(tile.Index);
tile.BitmapHolder.Bind(uTile);
} }
public void Dispose() { protected override void OnTileDestroyed(MapTile<MapTileBitmapHolder> tile) {
MonoBehaviour.Destroy(_dummyTask); base.OnTileDestroyed(tile);
if (_map.TryGetValue(tile, out var uTile)) {
lock (ActiveTiles) { uTile.Destroy();
foreach (var task in ActiveTiles) _map.Remove(tile);
GameObject.Destroy(task.gameObject);
ActiveTiles.Clear();
_cache.Clear();
}
}
readonly Dictionary<MapTileIndex, MapTile> _cache = new();
public List<MapTile> ActiveTiles { get; } = new();
MapTileIndex _a, _b;
public void MoveTo(MapTileIndex a, MapTileIndex b) {
if (a.Z != b.Z) throw new ArgumentException("Mismatched Z index.");
if (a.X >= b.X || a.Y >= b.Y) throw new ArgumentException("Incorrect relative X/Y index.");
lock (ActiveTiles) {
UnloadTiles(a, b);
_a = a; _b = b;
LoadTiles(a, b);
}
}
void LoadTiles(MapTileIndex a, MapTileIndex b) {
for (int z = Math.Max(0, a.Z - ExtraCachedZoomLevel); z <= Math.Max(0, a.Z); z++) {
var ia = a.ZoomToLevel(z, Math.Floor);
var ib = b.ZoomToLevel(z, Math.Ceiling);
for (int x = ia.X; x < ib.X; x++) {
for (int y = ia.Y; y < ib.Y; y++) {
var index = new MapTileIndex(x, y, z);
if (_cache.ContainsKey(index)) continue;
var task = GameObject.Instantiate(PrefabTile, Parent, false).GetComponent<MapTile>();
task.Load(index, CacheDir, OnUpdated);
_cache.Add(index, task);
var i = ~ActiveTiles.BinarySearch(task, TileZOrderComparer.Instance);
ActiveTiles.Insert(i, task);
}
}
}
}
void UnloadTiles(MapTileIndex a, MapTileIndex b) {
if (a.Z != _a.Z) {
for (int z = _a.Z - ExtraCachedZoomLevel; z < a.Z - ExtraCachedZoomLevel; z++) UnloadTilesAtZoomLevel(z);
for (int z = a.Z + 1; z <= _a.Z; z++) UnloadTilesAtZoomLevel(z);
}
if (a.X > _a.X) {
for (int z = Math.Max(0, a.Z); z >= Math.Max(0, a.Z - ExtraCachedZoomLevel); --z) {
var ia0 = _a.ZoomToLevel(z, Math.Floor);
var ib0 = _b.ZoomToLevel(z, Math.Ceiling);
var ia1 = a.ZoomToLevel(z, Math.Floor);
if (ia0.X == ia1.X) break;
UnloadTilesInRegion(ia0.X, ia0.Y, ia1.X, ib0.Y, z);
}
}
if (b.X < _b.X) {
for (int z = Math.Max(0, a.Z); z >= Math.Max(0, a.Z - ExtraCachedZoomLevel); --z) {
var ia0 = _a.ZoomToLevel(z, Math.Floor);
var ib0 = _b.ZoomToLevel(z, Math.Ceiling);
var ib1 = b.ZoomToLevel(z, Math.Ceiling);
if (ib0.X == ib1.X) break;
UnloadTilesInRegion(ib1.X, ia0.Y, ib0.X, ib0.Y, z);
}
}
if (a.Y > _a.Y) {
for (int z = Math.Max(0, a.Z); z >= Math.Max(0, a.Z - ExtraCachedZoomLevel); --z) {
var ia0 = _a.ZoomToLevel(z, Math.Floor);
var ib0 = _b.ZoomToLevel(z, Math.Ceiling);
var ia1 = a.ZoomToLevel(z, Math.Floor);
if (ia0.Y == ia1.Y) break;
UnloadTilesInRegion(ia0.X, ia0.Y, ib0.X, ia1.Y, z);
}
}
if (b.Y < _b.Y) {
for (int z = Math.Max(0, a.Z); z >= Math.Max(0, a.Z - ExtraCachedZoomLevel); --z) {
var ia0 = _a.ZoomToLevel(z, Math.Floor);
var ib0 = _b.ZoomToLevel(z, Math.Ceiling);
var ib1 = b.ZoomToLevel(z, Math.Ceiling);
if (ib0.Y == ib1.Y) break;
UnloadTilesInRegion(ia0.X, ib1.Y, ib0.X, ib0.Y, z);
}
}
}
void UnloadTilesInRegion(int x1, int y1, int x2, int y2, int z) {
for (int x = x1; x < x2; x++) {
for (int y = y1; y < y2; y++) {
var index = new MapTileIndex(x, y, z);
if (!_cache.TryGetValue(index, out var task)) continue;
GameObject.Destroy(task.gameObject);
}
}
}
MapTile _dummyTask;
void UnloadTilesAtZoomLevel(int z) {
if (z < 0) return;
_dummyTask.Index = new(int.MinValue, int.MinValue, z);
var i0 = ActiveTiles.BinarySearch(_dummyTask, TileZOrderComparer.Instance);
if (i0 < 0) i0 = ~i0;
_dummyTask.Index = new(int.MinValue, int.MinValue, z + 1);
var i1 = ActiveTiles.BinarySearch(_dummyTask, TileZOrderComparer.Instance);
if (i1 < 0) i1 = ~i1;
for (var i = i1 - 1; i >= i0; --i) {
var index = ActiveTiles[i].Index;
if (!_cache.TryGetValue(index, out var task)) continue;
GameObject.Destroy(task.gameObject);
} }
} }
} }

View File

@@ -77,7 +77,13 @@ namespace Cryville.EEW.Unity.Map {
DTSweep.Triangulate(tcx); DTSweep.Triangulate(tcx);
var codeToIndex = new Dictionary<uint, int>(); var codeToIndex = new Dictionary<uint, int>();
var vertices = ArrayPool<Vector3>.Shared.Rent(convertedPolygon.Points.Count); int vertexCount = convertedPolygon.Points.Count;
if (convertedPolygon.Holes != null) {
foreach (var hole in convertedPolygon.Holes) {
vertexCount += hole.Points.Count;
}
}
var vertices = ArrayPool<Vector3>.Shared.Rent(vertexCount);
var triangles = ArrayPool<int>.Shared.Rent(convertedPolygon.Triangles.Count * 3); var triangles = ArrayPool<int>.Shared.Rent(convertedPolygon.Triangles.Count * 3);
int vi = 0, ii = 0; int vi = 0, ii = 0;
foreach (var tri in convertedPolygon.Triangles) { foreach (var tri in convertedPolygon.Triangles) {

View File

@@ -0,0 +1,219 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text.Json;
using System.Text.Json.Serialization;
using TMPro;
using UnityEngine;
namespace Cryville.EEW.Unity.Map {
sealed class RegionEditor : MonoBehaviour {
QuadTreeNode _root;
[SerializeField] CameraController m_cameraController;
[SerializeField] GameObject m_regionViewPrefab;
[SerializeField] TMP_Text m_textSelectedInfo;
[SerializeField] TMP_Text m_textHoveredInfo;
[SerializeField] TMP_InputField m_inputId;
readonly Dictionary<QuadTreeNode, RegionView> _map = new();
void Start() {
var file = new FileInfo(Path.Combine(Application.persistentDataPath, "regions.json"));
if (file.Exists) {
using var stream = file.OpenRead();
_root = JsonSerializer.Deserialize<QuadTreeNode>(stream);
}
else {
_root = NewNode();
}
BuildView(_root);
}
public void Save() {
var file = new FileInfo(Path.Combine(Application.persistentDataPath, "regions.json"));
using var stream = file.Open(FileMode.Create);
JsonSerializer.Serialize(stream, _root);
}
void BuildView(QuadTreeNode node) {
var view = Instantiate(m_regionViewPrefab, transform, false).GetComponent<RegionView>();
view.Init(node.X, node.Y, node.Z);
view.Id = node.Data.Id;
view.IsLeaf = node.Children == null;
_map.Add(node, view);
BuildChildViews(node);
}
void BuildChildViews(QuadTreeNode node) {
if (node.Children == null) return;
foreach (var child in node.Children) {
BuildView(child);
}
}
void DestroyChildViews(QuadTreeNode node) {
if (node.Children == null) return;
foreach (var child in node.Children) {
Destroy(_map[child].gameObject);
_map.Remove(child);
}
}
QuadTreeNode _hoveredNode;
QuadTreeNode _selectedNode;
Vector3? _ppos;
void Update() {
var pos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
pos.y += 1;
var hoveredNode = _root.Get(pos);
if (hoveredNode != _hoveredNode) {
HoverNode(hoveredNode);
}
if (Input.GetMouseButtonDown(0)) {
_ppos = Input.mousePosition;
}
if (Input.GetMouseButton(0) && _ppos is Vector3 pos0) {
if (Input.mousePosition != pos0) {
_ppos = null;
}
}
if (hoveredNode == null) return;
if (Input.GetMouseButtonUp(0) && _ppos != null) {
SelectNode(hoveredNode);
_ppos = null;
}
if (m_inputId.isFocused)
return;
if (Input.GetKeyUp(KeyCode.A)) {
MergeNode(hoveredNode);
}
if (Input.GetKeyUp(KeyCode.S)) {
SplitNode(hoveredNode);
}
if (Input.GetKeyUp(KeyCode.C)) {
m_inputId.text = hoveredNode.Data.Id;
}
if (Input.GetKeyUp(KeyCode.V)) {
hoveredNode.Data.Id = m_inputId.text;
_map[hoveredNode].Id = hoveredNode.Data.Id;
}
}
void HoverNode(QuadTreeNode node) {
if (_hoveredNode != null) {
_map[_hoveredNode].IsHovered = false;
}
_hoveredNode = node;
if (_hoveredNode != null) {
_map[_hoveredNode].IsHovered = true;
m_textHoveredInfo.text = string.Format(CultureInfo.InvariantCulture, "<Hovered>\nZ: {2}, XY: ({0}, {1})\nD: {3}", node.X, node.Y, node.Z, node.Data.Id);
}
else {
m_textHoveredInfo.text = "";
}
}
void SelectNode(QuadTreeNode node) {
if (_selectedNode != null) {
_map[_selectedNode].IsSelected = false;
}
_selectedNode = node;
if (_selectedNode != null) {
_map[_selectedNode].IsSelected = true;
m_textSelectedInfo.text = string.Format(CultureInfo.InvariantCulture, "<Selected>\nZ: {2}, XY: ({0}, {1})\nD: {3}", node.X, node.Y, node.Z, node.Data.Id);
}
else {
m_textSelectedInfo.text = "";
}
}
void MergeNode(QuadTreeNode node) {
var parent = node.Parent;
if (parent == null)
return;
DestroyChildViews(parent);
_map[parent].IsLeaf = true;
parent.Merge();
_hoveredNode = null;
if (_selectedNode != null && !_map.ContainsKey(_selectedNode)) {
_selectedNode = null;
}
}
void SplitNode(QuadTreeNode node) {
node.Split();
_map[node].IsLeaf = false;
BuildChildViews(node);
}
static QuadTreeNode NewNode() => new() { Data = new("") };
sealed class QuadTreeNode {
QuadTreeNode[] m_children;
public QuadTreeNode[] Children {
get => m_children;
set {
if (m_children != null) {
foreach (var child in m_children) {
child.DetachFromParent();
}
}
m_children = value;
UpdateChildren();
}
}
QuadTreeNode m_parent;
[JsonIgnore] public QuadTreeNode Parent => m_parent;
void AttachToParent(QuadTreeNode parent, int index) {
if (m_parent != null && m_parent != parent)
throw new InvalidOperationException("Node already in a tree.");
m_parent = parent;
X = (parent.X << 1) | (index is 0 or 3 ? 1 : 0);
Y = (parent.Y << 1) | (index is 0 or 1 ? 1 : 0);
Z = parent.Z + 1;
UpdateChildren();
}
void DetachFromParent() => m_parent = null;
void UpdateChildren() {
if (m_children != null) {
for (int i = 0; i < m_children.Length; i++) {
m_children[i].AttachToParent(this, i);
}
}
}
[JsonIgnore] public int X { get; private set; }
[JsonIgnore] public int Y { get; private set; }
[JsonIgnore] public int Z { get; private set; }
public RegionData Data { get; set; }
public QuadTreeNode Get(Vector2 pos) {
if ((pos.x is < 0 or >= 1) || (pos.y is < 0 or >= 1))
return null;
if (m_children == null)
return this;
Vector2 subPos = pos * 2;
subPos.x %= 1;
subPos.y %= 1;
return pos.x >= 0.5f
? (pos.y >= 0.5f ? m_children[0] : m_children[3]).Get(subPos)
: (pos.y >= 0.5f ? m_children[1] : m_children[2]).Get(subPos);
}
public void Merge() {
Children = null;
}
public void Split() {
Children = new QuadTreeNode[] {
new() { Data = Data.Copy() },
new() { Data = Data.Copy() },
new() { Data = Data.Copy() },
new() { Data = Data.Copy() },
};
}
}
sealed record RegionData(string Id) {
public string Id { get; set; } = Id;
public RegionData Copy() => (RegionData)MemberwiseClone();
}
}
}

View File

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

View File

@@ -0,0 +1,68 @@
using UnityEngine;
namespace Cryville.EEW.Unity.Map {
sealed class RegionView : MonoBehaviour {
[SerializeField]
SpriteRenderer m_spriteRenderer;
Color _color;
bool m_isHovered;
public bool IsHovered {
get => m_isHovered;
set {
m_isHovered = value;
UpdateColor();
}
}
bool m_isSelected;
public bool IsSelected {
get => m_isSelected;
set {
m_isSelected = value;
UpdateColor();
}
}
bool m_isLeaf = true;
public bool IsLeaf {
get => m_isLeaf;
set {
m_isLeaf = value;
UpdateColor();
}
}
string m_id;
public string Id {
get => m_id;
set {
m_id = value;
unchecked {
uint hash = (uint)value.GetHashCode();
_color = Color.HSVToRGB(((hash >> 24) ^ ((hash >> 16) & 0xff) ^ ((hash >> 8) & 0xff) ^ (hash & 0xff)) / (float)0xff, 1, 1);
}
UpdateColor();
}
}
public void Init(int x, int y, int z) {
float scale = 1f / (1 << z);
transform.localScale = new Vector3(scale, scale, 1);
transform.localPosition = new Vector3(x * scale, y * scale - 1, -1 - z / 100f);
}
void UpdateColor() {
if (!m_isLeaf)
_color.a = 0.0f;
else if (m_isSelected)
_color.a = 0.6f;
else if (m_isHovered)
_color.a = 0.4f;
else
_color.a = 0.2f;
m_spriteRenderer.color = _color;
}
}
}

View File

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

View File

@@ -1,4 +1,5 @@
using Cryville.EEW.Colors; using Cryville.EEW.Colors;
using Cryville.EEW.Core;
using Cryville.EEW.Core.Colors; using Cryville.EEW.Core.Colors;
using Cryville.EEW.FERegion; using Cryville.EEW.FERegion;
using Cryville.EEW.Map; using Cryville.EEW.Map;
@@ -25,9 +26,21 @@ namespace Cryville.EEW.Unity {
public IColorScheme ColorScheme { get; private set; } = new SeverityBasedColorScheme(DefaultSeverityScheme.Instance, DefaultSeverityColorMapping.Instance); public IColorScheme ColorScheme { get; private set; } = new SeverityBasedColorScheme(DefaultSeverityScheme.Instance, DefaultSeverityColorMapping.Instance);
public ISubColorScheme BorderColorScheme { get; private set; } = new WrappedColorScheme(new SeverityBasedColorScheme(DefaultSeverityScheme.Instance, DefaultSeverityColorMapping.SecondaryInstance)); public ISubColorScheme BorderColorScheme { get; private set; } = new WrappedColorScheme(new SeverityBasedColorScheme(DefaultSeverityScheme.Instance, DefaultSeverityColorMapping.SecondaryInstance));
public ISubColorScheme TextColorScheme { get; private set; } = new DefaultTextColorScheme(Color.White, Color.Black); public ISubColorScheme TextColorScheme { get; private set; } = new DefaultTextColorScheme(Color.White, Color.Black);
public ILocationConverter LocationConverter => new FERegionLongConverter(); // TODO TTS
public TimeSpan NowcastWarningDelayTolerance => TimeSpan.FromMinutes(60); // TODO TTS public TimeSpan NowcastWarningDelayTolerance => TimeSpan.FromMinutes(60); // TODO TTS
public CultureInfo RVMCulture { get; private set; } = SharedCultures.CurrentUICulture;
readonly int _infoLocationSpecificity = 3;
readonly int _ttsLocationSpecificity = 3;
readonly LocationNamer _locationNamer = new() { Namer = new FERegionLongNamer() }; // TODO TTS
public bool NameLocation(double lat, double lon, CultureInfo localCulture, ref CultureInfo targetCulture, out string name, out int specificity) {
specificity = _ttsLocationSpecificity;
return _locationNamer.Name(lat, lon, localCulture, ref targetCulture, out name, ref specificity);
}
public bool NameLocation(double lat, double lon, CultureInfo localCulture, ref CultureInfo targetCulture, out string name) {
int specificity = _infoLocationSpecificity;
return _locationNamer.Name(lat, lon, localCulture, ref targetCulture, out name, ref specificity);
}
public TimeZoneInfo OverrideTimeZone { get; private set; } public TimeZoneInfo OverrideTimeZone { get; private set; }
public bool DoDisplayTimeZone { get; private set; } = true; public bool DoDisplayTimeZone { get; private set; } = true;
public bool DoSwitchBackToHistory { get; private set; } = true; public bool DoSwitchBackToHistory { get; private set; } = true;
@@ -104,6 +117,9 @@ namespace Cryville.EEW.Unity {
OverrideTimeZone = ParseTimeZone(config.OverrideTimeZone); OverrideTimeZone = ParseTimeZone(config.OverrideTimeZone);
DoDisplayTimeZone = config.DoDisplayTimeZone; DoDisplayTimeZone = config.DoDisplayTimeZone;
DoSwitchBackToHistory = config.DoSwitchBackToHistory; DoSwitchBackToHistory = config.DoSwitchBackToHistory;
RVMCulture = config.OverrideDisplayCulture is string rvmCulture
? (string.IsNullOrEmpty(rvmCulture) ? SharedCultures.CurrentUICulture : SharedCultures.Get(rvmCulture))
: CultureInfo.InvariantCulture;
EventSources = config.EventSources; EventSources = config.EventSources;
} }

View File

@@ -5,7 +5,7 @@ using System.IO;
using UnityEngine; using UnityEngine;
namespace Cryville.EEW.Unity { namespace Cryville.EEW.Unity {
class SoundPlayer : Core.SoundPlayer { class SoundPlayer : Core.Audio.SoundPlayer {
public SoundPlayer() : base(GetEngineList(), AudioUsage.NotificationEvent) { } public SoundPlayer() : base(GetEngineList(), AudioUsage.NotificationEvent) { }
static List<Type> GetEngineList() => new() { static List<Type> GetEngineList() => new() {
typeof(Audio.Wasapi.MMDeviceEnumeratorWrapper), typeof(Audio.Wasapi.MMDeviceEnumeratorWrapper),

View File

@@ -4,7 +4,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Cryville.EEW.Unity { namespace Cryville.EEW.Unity {
class TTSWorker : Core.TTSWorker { class TTSWorker : Core.Audio.TTSWorker {
public TTSWorker() : base(CreateSoundPlayer()) { } public TTSWorker() : base(CreateSoundPlayer()) { }
static SoundPlayer CreateSoundPlayer() { static SoundPlayer CreateSoundPlayer() {

View File

@@ -14,9 +14,12 @@ namespace Cryville.EEW.Unity.UI {
[SerializeField] GameObject m_listViewRail; [SerializeField] GameObject m_listViewRail;
[SerializeField] GameObject m_expander; [SerializeField] GameObject m_expander;
void Start() { void OnEnable() {
m_groupHeader.onClick.AddListener(OnGroupHeaderClicked); m_groupHeader.onClick.AddListener(OnGroupHeaderClicked);
} }
void OnDisable() {
m_groupHeader.onClick.RemoveListener(OnGroupHeaderClicked);
}
void OnGroupHeaderClicked() { void OnGroupHeaderClicked() {
SetExpanded(!m_listViewContainer.activeSelf); SetExpanded(!m_listViewContainer.activeSelf);
} }

View File

@@ -23,6 +23,7 @@ namespace Cryville.EEW.Unity.UI {
child.SetViewModel(e); child.SetViewModel(e);
child.transform.SetParent(m_listView, false); child.transform.SetParent(m_listView, false);
_displayingViews.Add(child); _displayingViews.Add(child);
OnDisplayingViewsChanged();
SwitchTo(_displayingReports.Count - 1); SwitchTo(_displayingReports.Count - 1);
@@ -37,6 +38,7 @@ namespace Cryville.EEW.Unity.UI {
child.SetParent(null, false); child.SetParent(null, false);
Destroy(child.gameObject); Destroy(child.gameObject);
_displayingViews.RemoveAt(index); _displayingViews.RemoveAt(index);
OnDisplayingViewsChanged();
if (_displayingReports.Count == 0) { if (_displayingReports.Count == 0) {
m_currentView.gameObject.SetActive(false); m_currentView.gameObject.SetActive(false);
@@ -49,6 +51,14 @@ namespace Cryville.EEW.Unity.UI {
if (_displayingReports.Count <= 1) m_listView.gameObject.SetActive(false); if (_displayingReports.Count <= 1) m_listView.gameObject.SetActive(false);
} }
void OnDisplayingViewsChanged() {
_maxBaseDuration = 1;
foreach (var e in _displayingReports) {
float duration = GetBaseDuration(e);
if (duration > _maxBaseDuration)
_maxBaseDuration = duration;
}
}
void Awake() { void Awake() {
if (Instance != null) { if (Instance != null) {
@@ -63,6 +73,7 @@ namespace Cryville.EEW.Unity.UI {
int _index = -1; int _index = -1;
float _tickDown; float _tickDown;
float _maxBaseDuration;
void Update() { void Update() {
if (_displayingReports.Count == 0) return; if (_displayingReports.Count == 0) return;
_tickDown -= Time.deltaTime; _tickDown -= Time.deltaTime;
@@ -77,12 +88,15 @@ namespace Cryville.EEW.Unity.UI {
_index = index; _index = index;
var e = _displayingReports[index]; var e = _displayingReports[index];
m_currentView.SetViewModel(e, true); m_currentView.SetViewModel(e, true);
var keyProp = e.Properties.FirstOrDefault();
_displayingViews[_index].SetCurrent(true); _displayingViews[_index].SetCurrent(true);
_tickDown = MathF.Exp(Math.Max(-1f, keyProp?.Severity ?? -1f) + 1); _tickDown = GetBaseDuration(e) / Math.Min(_maxBaseDuration, 4) * 4;
m_currentView.gameObject.SetActive(true); m_currentView.gameObject.SetActive(true);
Worker.Instance.SetCurrent(e); Worker.Instance.SetCurrent(e);
} }
static float GetBaseDuration(ReportViewModel e) {
return MathF.Exp(Math.Max(-1f, e.Properties.FirstOrDefault()?.Severity ?? -1f) + 1);
}
public void OnItemClicked(ReportViewModel viewModel) { public void OnItemClicked(ReportViewModel viewModel) {
int index = _displayingReports.IndexOf(viewModel); int index = _displayingReports.IndexOf(viewModel);
if (index == -1) return; if (index == -1) return;

View File

@@ -27,7 +27,7 @@ namespace Cryville.EEW.Unity.UI {
} }
void SetView(float mainSeverity, string title, string location, CultureInfo culture) { void SetView(float mainSeverity, string title, string location, CultureInfo culture) {
SetSeverity(mainSeverity); SetSeverity(mainSeverity);
SetText(m_textView, string.Format("{0} {1}", title, location), culture); SetText(m_textView, string.Format(culture, "{0} {1}", title, location), culture);
} }
static void SetText(TMPLocalizedText view, string text, CultureInfo culture) { static void SetText(TMPLocalizedText view, string text, CultureInfo culture) {
if (string.IsNullOrWhiteSpace(text)) { if (string.IsNullOrWhiteSpace(text)) {
@@ -47,9 +47,12 @@ namespace Cryville.EEW.Unity.UI {
void Awake() { void Awake() {
_dockRatioTweener = new(() => m_dockLayoutGroup.DockOccupiedRatio, v => m_dockLayoutGroup.DockOccupiedRatio = v, Tweeners.Single); _dockRatioTweener = new(() => m_dockLayoutGroup.DockOccupiedRatio, v => m_dockLayoutGroup.DockOccupiedRatio = v, Tweeners.Single);
} }
void Start() { void OnEnable() {
m_button.onClick.AddListener(OnViewClicked); m_button.onClick.AddListener(OnViewClicked);
} }
void OnDisable() {
m_button.onClick.RemoveListener(OnViewClicked);
}
void OnViewClicked() { void OnViewClicked() {
EventOngoingListView.Instance.OnItemClicked(_viewModel); EventOngoingListView.Instance.OnItemClicked(_viewModel);
} }

View File

@@ -14,9 +14,12 @@ namespace Cryville.EEW.Unity.UI {
[SerializeField] TMPLocalizedText m_revisionView; [SerializeField] TMPLocalizedText m_revisionView;
ReportViewModel _viewModel; ReportViewModel _viewModel;
protected virtual void Start() { protected virtual void OnEnable() {
if (m_reportViewButton != null) m_reportViewButton.onClick.AddListener(OnViewClicked); if (m_reportViewButton != null) m_reportViewButton.onClick.AddListener(OnViewClicked);
} }
protected virtual void OnDisable() {
if (m_reportViewButton != null) m_reportViewButton.onClick.RemoveListener(OnViewClicked);
}
void OnViewClicked() { void OnViewClicked() {
Worker.Instance.SetSelected(_viewModel); Worker.Instance.SetSelected(_viewModel);
} }

View File

@@ -8,10 +8,14 @@ namespace Cryville.EEW.Unity.UI {
[SerializeField] Button m_revisionViewContainerButton; [SerializeField] Button m_revisionViewContainerButton;
protected override void Start() { protected override void OnEnable() {
base.Start(); base.OnEnable();
m_revisionViewContainerButton.onClick.AddListener(OnRevisionViewClicked); m_revisionViewContainerButton.onClick.AddListener(OnRevisionViewClicked);
} }
protected override void OnDisable() {
base.OnDisable();
m_revisionViewContainerButton.onClick.RemoveListener(OnRevisionViewClicked);
}
void OnRevisionViewClicked() { void OnRevisionViewClicked() {
m_listView.gameObject.SetActive(!m_listView.gameObject.activeSelf); m_listView.gameObject.SetActive(!m_listView.gameObject.activeSelf);
} }

View File

@@ -1,4 +1,5 @@
using System.Globalization; using System.Globalization;
using System.Text;
using TMPro; using TMPro;
using UnityEngine; using UnityEngine;
@@ -11,17 +12,30 @@ namespace Cryville.EEW.Unity.UI {
_textView = GetComponent<TMP_Text>(); _textView = GetComponent<TMP_Text>();
} }
readonly StringBuilder _sb = new();
readonly char[] _buffer = new char[256];
void Update() { void Update() {
_textView.text = string.Format( _sb.Clear();
_sb.AppendFormat(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
"FPS: i{0:0} / s{1:0}\nSMem: {2:N0} / {3:N0}\nIMem: {4:N0} / {5:N0}", "FPS: i{0:0} / s{1:0}\n",
1 / Time.deltaTime, 1 / Time.deltaTime,
1 / Time.smoothDeltaTime, 1 / Time.smoothDeltaTime
);
_sb.AppendFormat(
CultureInfo.InvariantCulture,
"SMem: {0:N0} / {1:N0}\n",
UnityEngine.Profiling.Profiler.GetMonoUsedSizeLong(), UnityEngine.Profiling.Profiler.GetMonoUsedSizeLong(),
UnityEngine.Profiling.Profiler.GetMonoHeapSizeLong(), UnityEngine.Profiling.Profiler.GetMonoHeapSizeLong()
);
_sb.AppendFormat(
CultureInfo.InvariantCulture,
"IMem: {0:N0} / {1:N0}",
UnityEngine.Profiling.Profiler.GetTotalAllocatedMemoryLong(), UnityEngine.Profiling.Profiler.GetTotalAllocatedMemoryLong(),
UnityEngine.Profiling.Profiler.GetTotalReservedMemoryLong() UnityEngine.Profiling.Profiler.GetTotalReservedMemoryLong()
); );
_sb.CopyTo(0, _buffer, _sb.Length);
_textView.SetText(_buffer, 0, _sb.Length);
} }
} }
} }

View File

@@ -1,5 +1,4 @@
using System; using System;
using System.Globalization;
using TMPro; using TMPro;
using UnityEngine; using UnityEngine;
@@ -19,7 +18,7 @@ namespace Cryville.EEW.Unity.UI {
time = TimeZoneInfo.ConvertTime(time, timeZone, tTimeZone); time = TimeZoneInfo.ConvertTime(time, timeZone, tTimeZone);
else else
tTimeZone = timeZone; tTimeZone = timeZone;
_textView.text = SharedSettings.Instance.DoDisplayTimeZone ? string.Format(CultureInfo.CurrentCulture, "{0:G} ({1})", time, tTimeZone.ToTimeZoneString()) : time.ToString(CultureInfo.CurrentCulture); _textView.text = SharedSettings.Instance.DoDisplayTimeZone ? string.Format(SharedCultures.CurrentCulture, "{0:G} ({1})", time, tTimeZone.ToTimeZoneString()) : time.ToString(SharedCultures.CurrentCulture);
} }
} }
} }

Some files were not shown because too many files have changed in this diff Show More