Compare commits
48 Commits
0.0.2
...
a131ed10f9
| Author | SHA1 | Date | |
|---|---|---|---|
| a131ed10f9 | |||
| ad0797585b | |||
| 304ab10491 | |||
| 130f880d5d | |||
| cf6d76d4c0 | |||
| 4628db1d49 | |||
| 1eeaa4f728 | |||
| 309ee12b93 | |||
| 59335c9102 | |||
| e182ebd53f | |||
| 65b158f387 | |||
| 9f844f2744 | |||
| ae917a8e51 | |||
| 44a72d1a8f | |||
| 4758b65159 | |||
| de3196d38a | |||
| c2311fb7a4 | |||
| 7562be2c09 | |||
| a8f46113d4 | |||
| 99736f114d | |||
| 4d1a008106 | |||
| 9318cbca4e | |||
| a162f345c4 | |||
| 1af4afc7c6 | |||
| a3efe939e8 | |||
| 5daee1a01a | |||
| 2d5d305528 | |||
| 8da46c0511 | |||
| 75b5d7708c | |||
| 5b2177a795 | |||
| 8cb33dca5f | |||
| ae2e0af18a | |||
| 2ac5a3d4f0 | |||
| 5f78a1afde | |||
| ef5cf78a03 | |||
| b60e62af70 | |||
| 3e59fe1462 | |||
| 4b4bf5ed65 | |||
| a4f78b3a95 | |||
| 074a58dabd | |||
| a2ef175a81 | |||
| 915ba55c2e | |||
| f154a2a468 | |||
| c52d438d40 | |||
| 8d3f53ba13 | |||
| 1db25e62e7 | |||
| 18312176d9 | |||
| 5be6e32b03 |
@@ -2,7 +2,7 @@
|
||||
"name": "Cryville.Common",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:da293eebbcb9a4947a212534c52d1a32"
|
||||
"GUID:6055be8ebefd69e48b49212b09b47b2f"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace Cryville.Common.Unity.UI {
|
||||
const float _placeholderLength = 100;
|
||||
int _firstIndex, _lastIndex;
|
||||
readonly Stack<GameObject> _pool = new();
|
||||
void Update() {
|
||||
void LateUpdate() {
|
||||
int axis = (int)m_direction;
|
||||
int sign = m_direction == 0 ? 1 : -1;
|
||||
float padding = axis == 0 ? m_padding.left : m_padding.top;
|
||||
|
||||
@@ -23,6 +23,12 @@ namespace Cryville.Common.Unity.UI {
|
||||
[SerializeField]
|
||||
protected bool m_ChildScaleHeight;
|
||||
|
||||
[SerializeField]
|
||||
protected bool m_ChildOverflowWidth;
|
||||
|
||||
[SerializeField]
|
||||
protected bool m_ChildOverflowHeight;
|
||||
|
||||
public override void CalculateLayoutInputHorizontal() {
|
||||
base.CalculateLayoutInputHorizontal();
|
||||
CalcAlongAxis(0);
|
||||
@@ -60,9 +66,10 @@ namespace Cryville.Common.Unity.UI {
|
||||
float size = rectTransform.rect.size[axis];
|
||||
bool controlSize = (axis == 0) ? m_ChildControlWidth : m_ChildControlHeight;
|
||||
bool useScale = (axis == 0) ? m_ChildScaleWidth : m_ChildScaleHeight;
|
||||
bool overflow = (axis == 0) ? m_ChildOverflowWidth : m_ChildOverflowHeight;
|
||||
bool childForceExpandSize = (axis == 0) ? m_ChildForceExpandWidth : m_ChildForceExpandHeight;
|
||||
float alignmentOnAxis = GetAlignmentOnAxis(axis);
|
||||
float innerSize = size - ((axis == 0) ? padding.horizontal : padding.vertical);
|
||||
float innerSize = overflow ? float.PositiveInfinity : (size - ((axis == 0) ? padding.horizontal : padding.vertical));
|
||||
RectTransform child = rectChildren[0];
|
||||
GetChildSizes(child, axis, controlSize, childForceExpandSize, out var min2, out var preferred2, out var flexible2);
|
||||
float scaleFactor2 = useScale ? child.localScale[axis] : 1f;
|
||||
|
||||
@@ -6,8 +6,9 @@ using System.Globalization;
|
||||
using System.Reflection;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TextCore;
|
||||
using UnityEngine.TextCore.LowLevel;
|
||||
using UnityEngine.TextCore.Text;
|
||||
using AtlasPopulationMode = TMPro.AtlasPopulationMode;
|
||||
|
||||
namespace Cryville.Common.Unity.UI {
|
||||
[RequireComponent(typeof(TMP_Text))]
|
||||
@@ -16,7 +17,7 @@ namespace Cryville.Common.Unity.UI {
|
||||
public static FontMatcher FontMatcher;
|
||||
public static int MaxFallbackCount = 4;
|
||||
|
||||
static readonly Dictionary<CultureInfo, FontAsset> _cachedFonts = new();
|
||||
static readonly Dictionary<CultureInfo, TMP_FontAsset> _cachedFonts = new();
|
||||
|
||||
[SerializeField]
|
||||
Shader m_shader;
|
||||
@@ -46,7 +47,7 @@ namespace Cryville.Common.Unity.UI {
|
||||
if (MaxFallbackCount <= 0) break;
|
||||
}
|
||||
else {
|
||||
font.fallbackFontAssetTable ??= new List<FontAsset>();
|
||||
font.fallbackFontAssetTable ??= new List<TMP_FontAsset>();
|
||||
font.fallbackFontAssetTable.Add(ifont);
|
||||
if (font.fallbackFontAssetTable.Count >= MaxFallbackCount) break;
|
||||
}
|
||||
@@ -58,23 +59,93 @@ namespace Cryville.Common.Unity.UI {
|
||||
Text.font = font;
|
||||
}
|
||||
|
||||
static MethodInfo _methodCreateFontAsset;
|
||||
static readonly object[] _paramsCreateFontAsset = new object[] { null, null, 90, 9, GlyphRenderMode.SDFAA, 1024, 1024, Type.Missing, Type.Missing };
|
||||
static FontAsset CreateFontAsset(string path, int index) {
|
||||
if (_methodCreateFontAsset == null) {
|
||||
_methodCreateFontAsset = typeof(FontAsset).GetMethod(
|
||||
"CreateFontAsset", BindingFlags.Static | BindingFlags.NonPublic, null,
|
||||
new Type[] {
|
||||
typeof(string), typeof(int), typeof(int), typeof(int),
|
||||
typeof(GlyphRenderMode), typeof(int), typeof(int),
|
||||
typeof(AtlasPopulationMode), typeof(bool)
|
||||
},
|
||||
null
|
||||
);
|
||||
static TMP_FontAsset CreateFontAsset(string path, int index) => CreateFontAsset(path, index, 90, 9, GlyphRenderMode.SDFAA, 1024, 1024, AtlasPopulationMode.Dynamic);
|
||||
|
||||
static readonly Lazy<FieldInfo> _f_m_Version = new(() => typeof(TMP_FontAsset).GetField("m_Version", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic));
|
||||
static readonly Lazy<PropertyInfo> _p_atlasWidth = new(() => typeof(TMP_FontAsset).GetProperty("atlasWidth", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic));
|
||||
static readonly Lazy<PropertyInfo> _p_atlasHeight = new(() => typeof(TMP_FontAsset).GetProperty("atlasHeight", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic));
|
||||
static readonly Lazy<PropertyInfo> _p_atlasPadding = new(() => typeof(TMP_FontAsset).GetProperty("atlasPadding", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic));
|
||||
static readonly Lazy<PropertyInfo> _p_atlasRenderMode = new(() => typeof(TMP_FontAsset).GetProperty("atlasRenderMode", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic));
|
||||
static readonly Lazy<PropertyInfo> _p_freeGlyphRects = new(() => typeof(TMP_FontAsset).GetProperty("freeGlyphRects", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic));
|
||||
static readonly Lazy<PropertyInfo> _p_usedGlyphRects = new(() => typeof(TMP_FontAsset).GetProperty("usedGlyphRects", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic));
|
||||
|
||||
static readonly Lazy<PropertyInfo> _p_ShaderRef_MobileBitmap = new(() => typeof(ShaderUtilities).GetProperty("ShaderRef_MobileBitmap", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic));
|
||||
static readonly Lazy<PropertyInfo> _p_ShaderRef_MobileSDF = new(() => typeof(ShaderUtilities).GetProperty("ShaderRef_MobileSDF", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic));
|
||||
|
||||
public static TMP_FontAsset CreateFontAsset(string path, int faceIndex, int samplingPointSize, int atlasPadding, GlyphRenderMode renderMode, int atlasWidth, int atlasHeight, AtlasPopulationMode atlasPopulationMode = AtlasPopulationMode.Dynamic, bool enableMultiAtlasSupport = true) {
|
||||
// Initialize FontEngine
|
||||
FontEngine.InitializeFontEngine();
|
||||
|
||||
// Load Font Face
|
||||
if (FontEngine.LoadFontFace(path, samplingPointSize, faceIndex) != FontEngineError.Success) {
|
||||
Debug.LogWarning("Unable to load font face at path " + path);
|
||||
return null;
|
||||
}
|
||||
_paramsCreateFontAsset[0] = path;
|
||||
_paramsCreateFontAsset[1] = index;
|
||||
return (FontAsset)_methodCreateFontAsset.Invoke(null, _paramsCreateFontAsset);
|
||||
|
||||
// Create new font asset
|
||||
TMP_FontAsset fontAsset = ScriptableObject.CreateInstance<TMP_FontAsset>();
|
||||
|
||||
_f_m_Version.Value.SetValue(fontAsset, "1.1.0");
|
||||
fontAsset.faceInfo = FontEngine.GetFaceInfo();
|
||||
|
||||
fontAsset.atlasPopulationMode = atlasPopulationMode;
|
||||
|
||||
_p_atlasWidth.Value.SetValue(fontAsset, atlasWidth);
|
||||
_p_atlasHeight.Value.SetValue(fontAsset, atlasHeight);
|
||||
_p_atlasPadding.Value.SetValue(fontAsset, atlasPadding);
|
||||
_p_atlasRenderMode.Value.SetValue(fontAsset, renderMode);
|
||||
|
||||
// Initialize array for the font atlas textures.
|
||||
fontAsset.atlasTextures = new Texture2D[1];
|
||||
|
||||
// Create and add font atlas texture.
|
||||
var texture = new Texture2D(0, 0, TextureFormat.Alpha8, false);
|
||||
fontAsset.atlasTextures[0] = texture;
|
||||
|
||||
fontAsset.isMultiAtlasTexturesEnabled = enableMultiAtlasSupport;
|
||||
|
||||
// Add free rectangle of the size of the texture.
|
||||
int packingModifier;
|
||||
if (((int)renderMode & 0x10) != 0) {
|
||||
packingModifier = 0;
|
||||
|
||||
// Optimize by adding static ref to shader.
|
||||
var tmp_material = new Material((Shader)_p_ShaderRef_MobileBitmap.Value.GetValue(null));
|
||||
|
||||
//tmp_material.name = texture.name + " Material";
|
||||
tmp_material.SetTexture(ShaderUtilities.ID_MainTex, texture);
|
||||
tmp_material.SetFloat(ShaderUtilities.ID_TextureWidth, atlasWidth);
|
||||
tmp_material.SetFloat(ShaderUtilities.ID_TextureHeight, atlasHeight);
|
||||
|
||||
fontAsset.material = tmp_material;
|
||||
}
|
||||
else {
|
||||
packingModifier = 1;
|
||||
|
||||
// Optimize by adding static ref to shader.
|
||||
var tmp_material = new Material((Shader)_p_ShaderRef_MobileSDF.Value.GetValue(null));
|
||||
|
||||
//tmp_material.name = texture.name + " Material";
|
||||
tmp_material.SetTexture(ShaderUtilities.ID_MainTex, texture);
|
||||
tmp_material.SetFloat(ShaderUtilities.ID_TextureWidth, atlasWidth);
|
||||
tmp_material.SetFloat(ShaderUtilities.ID_TextureHeight, atlasHeight);
|
||||
|
||||
tmp_material.SetFloat(ShaderUtilities.ID_GradientScale, atlasPadding + packingModifier);
|
||||
|
||||
tmp_material.SetFloat(ShaderUtilities.ID_WeightNormal, fontAsset.normalStyle);
|
||||
tmp_material.SetFloat(ShaderUtilities.ID_WeightBold, fontAsset.boldStyle);
|
||||
|
||||
fontAsset.material = tmp_material;
|
||||
}
|
||||
|
||||
_p_freeGlyphRects.Value.SetValue(fontAsset, new List<GlyphRect>(8) { new(0, 0, atlasWidth - packingModifier, atlasHeight - packingModifier) });
|
||||
_p_usedGlyphRects.Value.SetValue(fontAsset, new List<GlyphRect>(8));
|
||||
|
||||
// TODO: Consider adding support for extracting glyph positioning data
|
||||
|
||||
fontAsset.ReadFontAssetDefinition();
|
||||
|
||||
return fontAsset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
executionOrder: -95
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
|
||||
@@ -1,19 +1,60 @@
|
||||
using Cryville.Common.Font;
|
||||
using Cryville.Common.Logging;
|
||||
using Cryville.Common.Unity.UI;
|
||||
using Cryville.Culture;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using UnityEngine;
|
||||
using Logger = Cryville.Common.Logging.Logger;
|
||||
|
||||
namespace Cryville.EEW.Unity {
|
||||
class App {
|
||||
public static string AppDataPath { get; private set; }
|
||||
|
||||
public static Logger MainLogger { get; private set; }
|
||||
static FileStream _logFileStream;
|
||||
static StreamLoggerListener _logWriter;
|
||||
|
||||
static bool _init;
|
||||
public static void Init() {
|
||||
if (_init) return;
|
||||
_init = true;
|
||||
|
||||
AppDataPath = Application.persistentDataPath;
|
||||
|
||||
var logPath = Directory.CreateDirectory(Path.Combine(AppDataPath, "logs"));
|
||||
_logFileStream = new FileStream(
|
||||
Path.Combine(
|
||||
logPath.FullName,
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{0}.log",
|
||||
DateTimeOffset.UtcNow.ToString("yyyyMMddHHmmssfff", CultureInfo.InvariantCulture)
|
||||
)
|
||||
),
|
||||
FileMode.Create, FileAccess.Write, FileShare.Read
|
||||
);
|
||||
_logWriter = new StreamLoggerListener(_logFileStream) { AutoFlush = true };
|
||||
MainLogger = new Logger();
|
||||
var listener = new InstantLoggerListener();
|
||||
listener.Log += MainLogger.Log;
|
||||
MainLogger.AddListener(_logWriter);
|
||||
Application.logMessageReceivedThreaded += OnInternalLog;
|
||||
|
||||
MainLogger.Log(1, "App", null, "App Version: {0}", Application.version);
|
||||
MainLogger.Log(1, "App", null, "Unity Version: {0}", Application.unityVersion);
|
||||
MainLogger.Log(1, "App", null, "Operating System: {0}, Unity = {1}, Family = {2}", Environment.OSVersion, SystemInfo.operatingSystem, SystemInfo.operatingSystemFamily);
|
||||
MainLogger.Log(1, "App", null, "Platform: Build = {0}, Unity = {1}", PlatformConfig.Name, Application.platform);
|
||||
MainLogger.Log(1, "App", null, "Culture: {0}, UI = {1}, Unity = {2}", SharedCultures.CurrentCulture, SharedCultures.CurrentUICulture, Application.systemLanguage);
|
||||
MainLogger.Log(1, "App", null, "Device: Model = {0}, Type = {1}", SystemInfo.deviceModel, SystemInfo.deviceType);
|
||||
MainLogger.Log(1, "App", null, "Graphics: Name = {0}, Type = {1}, Vendor = {2}, Version = {3}", SystemInfo.graphicsDeviceName, SystemInfo.graphicsDeviceType, SystemInfo.graphicsDeviceVendor, SystemInfo.graphicsDeviceVersion);
|
||||
MainLogger.Log(1, "App", null, "Processor: Count = {0}, Frequency = {1}MHz, Type = {2}", SystemInfo.processorCount, SystemInfo.processorFrequency, SystemInfo.processorType);
|
||||
|
||||
MainLogger.Log(1, "App", null, "Initializing font manager");
|
||||
foreach (var res in Resources.LoadAll<TextAsset>("cldr/common/validity")) {
|
||||
IdValidity.Load(LoadXmlDocument(res));
|
||||
}
|
||||
@@ -25,7 +66,10 @@ namespace Cryville.EEW.Unity {
|
||||
};
|
||||
TMPLocalizedText.DefaultShader = Resources.Load<Shader>(PlatformConfig.TextShader);
|
||||
|
||||
MainLogger.Log(1, "App", null, "Loading config");
|
||||
SharedSettings.Instance.Init();
|
||||
|
||||
MainLogger.Log(1, "App", null, "Initialized");
|
||||
}
|
||||
|
||||
static readonly Encoding _encoding = new UTF8Encoding(false, true);
|
||||
@@ -40,5 +84,16 @@ namespace Cryville.EEW.Unity {
|
||||
using var reader = XmlReader.Create(stream, _xmlSettings);
|
||||
return XDocument.Load(reader);
|
||||
}
|
||||
|
||||
static void OnInternalLog(string condition, string stackTrace, LogType type) {
|
||||
var l = type switch {
|
||||
LogType.Log => 1,
|
||||
LogType.Assert => 2,
|
||||
LogType.Warning => 3,
|
||||
LogType.Error or LogType.Exception => 4,
|
||||
_ => 1,
|
||||
};
|
||||
MainLogger.Log(l, "Internal", null, "{0}\n{1}", condition, stackTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: AssemblyVersion("0.0.2")]
|
||||
[assembly: AssemblyVersion("0.0.10")]
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
using Cryville.EEW.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Cryville.EEW.Unity {
|
||||
@@ -8,11 +12,19 @@ namespace Cryville.EEW.Unity {
|
||||
float SeverityColorMappingLuminanceMultiplier,
|
||||
bool UseContinuousColor,
|
||||
string ColorScheme,
|
||||
float HillshadeLayerOpacity,
|
||||
string LocationNamer,
|
||||
|
||||
string OverrideTimeZone,
|
||||
bool DoDisplayTimeZone,
|
||||
bool DoSwitchBackToHistory,
|
||||
|
||||
string NowcastWarningDelayTolerance,
|
||||
|
||||
string OverrideDisplayCulture,
|
||||
IReadOnlyCollection<TTSCultureConfig> TTSCultures,
|
||||
bool DoIgnoreLanguageVariant,
|
||||
|
||||
IReadOnlyCollection<EventSourceConfig> EventSources
|
||||
) {
|
||||
public static Config Default => new(
|
||||
@@ -20,11 +32,19 @@ namespace Cryville.EEW.Unity {
|
||||
1f,
|
||||
false,
|
||||
"Default",
|
||||
1,
|
||||
"FERegionLong",
|
||||
|
||||
null,
|
||||
true,
|
||||
true,
|
||||
|
||||
"1:00:00",
|
||||
|
||||
"",
|
||||
new List<TTSCultureConfig> { new(SharedCultures.CurrentUICulture) },
|
||||
true,
|
||||
|
||||
new List<EventSourceConfig> {
|
||||
new JMAAtomEventSourceConfig(Array.Empty<string>()),
|
||||
new UpdateCheckerEventSourceConfig(),
|
||||
@@ -37,26 +57,44 @@ namespace Cryville.EEW.Unity {
|
||||
[JsonDerivedType(typeof(BMKGOpenDataEventSourceConfig), "BMKGOpenData")]
|
||||
[JsonDerivedType(typeof(CWAOpenDataEventSourceConfig), "CWAOpenData")]
|
||||
[JsonDerivedType(typeof(EMSCRealTimeEventSourceConfig), "EMSCRealTime")]
|
||||
[JsonDerivedType(typeof(FANStudioEventSourceConfig), "FANStudio")]
|
||||
[JsonDerivedType(typeof(GeoNetEventSourceConfig), "GeoNet")]
|
||||
[JsonDerivedType(typeof(GlobalQuakeServerEventSourceConfig), "GlobalQuakeServer")]
|
||||
[JsonDerivedType(typeof(GlobalQuakeServer15EventSourceConfig), "GlobalQuakeServer15")]
|
||||
[JsonDerivedType(typeof(JMAAtomEventSourceConfig), "JMAAtom")]
|
||||
[JsonDerivedType(typeof(NOAAEventSourceConfig), "NOAA")]
|
||||
[JsonDerivedType(typeof(UpdateCheckerEventSourceConfig), "UpdateChecker")]
|
||||
[JsonDerivedType(typeof(USGSQuakeMLEventSourceConfig), "USGSQuakeML")]
|
||||
[JsonDerivedType(typeof(USGSEventSourceConfig), "USGSQuakeML")]
|
||||
[JsonDerivedType(typeof(WolfxEventSourceConfig), "Wolfx")]
|
||||
abstract record EventSourceConfig();
|
||||
record BMKGOpenDataEventSourceConfig([property: JsonRequired] string[] Subtypes) : EventSourceConfig;
|
||||
record CWAOpenDataEventSourceConfig([property: JsonRequired] string Subtype, [property: JsonRequired] string Token) : EventSourceConfig;
|
||||
record EMSCRealTimeEventSourceConfig() : EventSourceConfig;
|
||||
record FANStudioEventSourceConfig([property: JsonRequired] string Subtype) : EventSourceConfig;
|
||||
record GeoNetEventSourceConfig(int MinimumMMI = 3, bool DoGetFullHistory = false, bool DoGetStrongMotionInfo = true) : EventSourceConfig;
|
||||
record GlobalQuakeServerEventSourceConfig([property: JsonRequired] string Host, int Port = 38000) : EventSourceConfig;
|
||||
record GlobalQuakeServer15EventSourceConfig(string Host, int Port = 38000) : GlobalQuakeServerEventSourceConfig(Host, Port);
|
||||
record JMAAtomEventSourceConfig(IReadOnlyCollection<string> Filter = null, bool IsFilterWhitelist = false) : EventSourceConfig;
|
||||
record NOAAEventSourceConfig([property: JsonRequired] string Subtype) : EventSourceConfig;
|
||||
record UpdateCheckerEventSourceConfig : EventSourceConfig;
|
||||
record USGSQuakeMLEventSourceConfig([property: JsonRequired] string Subtype) : EventSourceConfig;
|
||||
record WolfxEventSourceConfig(IReadOnlyCollection<string> Filter = null, bool IsFilterWhitelist = false) : EventSourceConfig;
|
||||
record USGSEventSourceConfig([property: JsonRequired] string Subtype, bool UseGeoJSONFeeds, string[] Products) : EventSourceConfig;
|
||||
record WolfxEventSourceConfig(IReadOnlyCollection<string> Filter = null, bool IsFilterWhitelist = false, bool UseRawCENCLocationName = false) : EventSourceConfig;
|
||||
|
||||
[JsonSerializable(typeof(Config))]
|
||||
[JsonSourceGenerationOptions(WriteIndented = true)]
|
||||
[JsonSourceGenerationOptions(Converters = new Type[] { typeof(CultureInfoConverter) }, WriteIndented = true)]
|
||||
sealed partial class ConfigSerializationContext : JsonSerializerContext { }
|
||||
|
||||
sealed class CultureInfoConverter : JsonConverter<CultureInfo> {
|
||||
public override CultureInfo Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
|
||||
Debug.Assert(typeToConvert == typeof(CultureInfo));
|
||||
var value = reader.GetString();
|
||||
if (value == null) return CultureInfo.InvariantCulture;
|
||||
if (value == "") return SharedCultures.CurrentUICulture;
|
||||
return SharedCultures.Get(value);
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, CultureInfo value, JsonSerializerOptions options) {
|
||||
writer.WriteStringValue(value.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:b92f9c7ac10b1c04e86fc48210f62ab1",
|
||||
"GUID:1e0937e40dadba24a97b7342c4559580",
|
||||
"GUID:e5b7e7f40a80a814ba706299d68f9213",
|
||||
"GUID:da293eebbcb9a4947a212534c52d1a32"
|
||||
"GUID:6055be8ebefd69e48b49212b09b47b2f"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Cryville.EEW.Core.Map;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using UnityEngine;
|
||||
@@ -10,26 +11,51 @@ namespace Cryville.EEW.Unity.Map {
|
||||
[SerializeField]
|
||||
Transform m_layerTile;
|
||||
[SerializeField]
|
||||
Transform m_layerTileHillshade;
|
||||
[SerializeField]
|
||||
MapElementManager m_layerElement;
|
||||
[SerializeField]
|
||||
MapElementManager m_layerElementSub;
|
||||
[SerializeField]
|
||||
GameObject m_prefabTile;
|
||||
[SerializeField]
|
||||
GameObject m_prefabTileHillshade;
|
||||
[SerializeField]
|
||||
GameObject m_prefabBitmapHolder;
|
||||
[SerializeField]
|
||||
int m_maxMapTileZoom = 10;
|
||||
[SerializeField]
|
||||
bool m_isEditor;
|
||||
|
||||
readonly MapTileCacheManager _tiles = new();
|
||||
MapTileCacheManager _tiles;
|
||||
MapTileCacheManager _tilesHillshade;
|
||||
float _elementLayerZ;
|
||||
|
||||
void Start() {
|
||||
_camera = GetComponent<Camera>();
|
||||
_tiles = m_isEditor ? new EditorMapTileCacheManager() : new MapTileCacheManager();
|
||||
_tiles.ExtraCachedZoomLevel = 20;
|
||||
_tiles.Parent = m_layerTile;
|
||||
_tiles.PrefabTile = m_prefabTile;
|
||||
_tiles.PrefabBitmapHolder = m_prefabBitmapHolder;
|
||||
_tiles.CacheDir = Application.temporaryCachePath;
|
||||
if (m_layerTileHillshade) {
|
||||
m_prefabTileHillshade.GetComponent<SpriteRenderer>().sharedMaterial.color = new UnityEngine.Color(1, 1, 1, SharedSettings.Instance.HillshadeLayerOpacity);
|
||||
_tilesHillshade = new HillshadeMapTileCacheManager {
|
||||
ExtraCachedZoomLevel = 20,
|
||||
Parent = m_layerTileHillshade,
|
||||
PrefabTile = m_prefabTileHillshade,
|
||||
PrefabBitmapHolder = m_prefabBitmapHolder,
|
||||
CacheDir = Application.temporaryCachePath,
|
||||
};
|
||||
}
|
||||
_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;
|
||||
}
|
||||
void OnDestroy() {
|
||||
_tiles.Dispose();
|
||||
_tilesHillshade?.Dispose();
|
||||
}
|
||||
|
||||
float Scale {
|
||||
@@ -70,7 +96,7 @@ namespace Cryville.EEW.Unity.Map {
|
||||
}
|
||||
}
|
||||
void ZoomToMapElement() {
|
||||
var aabb = m_layerElement.AABB;
|
||||
var aabb = m_layerElement != null ? m_layerElement.AABB : null;
|
||||
if (aabb is not RectangleF b) return;
|
||||
if (b.Width * _camera.pixelHeight < _camera.pixelWidth * b.Height)
|
||||
Scale = b.Height;
|
||||
@@ -93,26 +119,30 @@ namespace Cryville.EEW.Unity.Map {
|
||||
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));
|
||||
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;
|
||||
_tiles.MoveTo(
|
||||
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)
|
||||
);
|
||||
var a = new MapTileIndex(Mathf.FloorToInt(bounds.min.x * zoomScale), Mathf.FloorToInt(-bounds.max.y * zoomScale), zoom);
|
||||
var b = new MapTileIndex(Mathf.CeilToInt(bounds.max.x * zoomScale), Mathf.CeilToInt(-bounds.min.y * zoomScale), zoom);
|
||||
_tiles.MoveTo(a, b);
|
||||
_tilesHillshade?.MoveTo(a, b);
|
||||
|
||||
m_layerElement.Scale = h;
|
||||
m_layerElementSub.Scale = h;
|
||||
if (m_layerElement != null) {
|
||||
m_layerElement.Scale = h;
|
||||
}
|
||||
if (m_layerElementSub != null) {
|
||||
m_layerElementSub.Scale = h;
|
||||
|
||||
if (nx - w / 2 < 0) {
|
||||
m_layerElementSub.gameObject.SetActive(true);
|
||||
m_layerElementSub.transform.localPosition = new(-1, 0, _elementLayerZ);
|
||||
}
|
||||
else if (nx + w / 2 > 1) {
|
||||
m_layerElementSub.gameObject.SetActive(true);
|
||||
m_layerElementSub.transform.localPosition = new(1, 0, _elementLayerZ);
|
||||
}
|
||||
else {
|
||||
m_layerElementSub.gameObject.SetActive(false);
|
||||
if (nx - w / 2 < 0) {
|
||||
m_layerElementSub.gameObject.SetActive(true);
|
||||
m_layerElementSub.transform.localPosition = new(-1, 0, _elementLayerZ);
|
||||
}
|
||||
else if (nx + w / 2 > 1) {
|
||||
m_layerElementSub.gameObject.SetActive(true);
|
||||
m_layerElementSub.transform.localPosition = new(1, 0, _elementLayerZ);
|
||||
}
|
||||
else {
|
||||
m_layerElementSub.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
15
Assets/Cryville.EEW.Unity/Map/EditorMapTileCacheManager.cs
Normal file
15
Assets/Cryville.EEW.Unity/Map/EditorMapTileCacheManager.cs
Normal 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}");
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9e8aa96a139a7414cb4d2031a22588db
|
||||
guid: 478198b8ecc0082449fa3f68795174a9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -98,24 +98,25 @@ namespace Cryville.EEW.Unity.Map.Element {
|
||||
for (int d = 0; d < 360; d++) {
|
||||
Quaternion q = Quaternion.AngleAxis(d, axis);
|
||||
Vector3 p = q * rp;
|
||||
Vector2 p2 = ToTilePos(p).ToVector2();
|
||||
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;
|
||||
}
|
||||
}
|
||||
_vertexBuffer[d] = p2;
|
||||
lp2 = p2;
|
||||
AddVertex(renderer, ref lp2, ref segmentIndex, d, p);
|
||||
}
|
||||
Vector2 rp2 = ToTilePos(rp).ToVector2();
|
||||
_vertexBuffer[360] = rp2;
|
||||
AddVertex(renderer, ref lp2, ref segmentIndex, 360, rp);
|
||||
renderer.AddSegment(_vertexBuffer, segmentIndex, 361 - segmentIndex);
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
using Cryville.EEW.Core.Map;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Cryville.EEW.Unity.Map {
|
||||
sealed class HillshadeMapTileCacheManager : MapTileCacheManager {
|
||||
protected override MapTileBitmapHolder CreateBitmapHolder(MapTileIndex index) => new(
|
||||
index,
|
||||
GameObject.Instantiate(PrefabBitmapHolder, Parent, false),
|
||||
new($"https://services.arcgisonline.com/arcgis/rest/services/Elevation/World_Hillshade/MapServer/tile/{index.Z}/{index.NY}/{index.NX}.png")
|
||||
);
|
||||
|
||||
protected override string GetCacheFilePath(MapTileIndex index) => Path.Combine(CacheDir, $"map_hillshade/{index.Z}/{index.NX}/{index.NY}");
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4f85302336d038c4da80ea264d185657
|
||||
guid: d5fed9b884a4ff54f837aa0f2265ad36
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -150,12 +150,8 @@ namespace Cryville.EEW.Unity.Map {
|
||||
_mesh.Clear();
|
||||
if (_positions == null) 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;
|
||||
float uvScale = 1 / (m_tilingScale * m_width);
|
||||
Vector2 p0 = _positions[0], p1 = default;
|
||||
@@ -163,6 +159,12 @@ namespace Cryville.EEW.Unity.Map {
|
||||
if ((p1 = _positions[i]) != p0) break;
|
||||
}
|
||||
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);
|
||||
vbuf[vi] = p0 - np0; ubuf[vi++] = new(0, 0);
|
||||
vbuf[vi] = p0 + np0; ubuf[vi++] = new(0, 1);
|
||||
|
||||
@@ -2,12 +2,15 @@ using Cryville.EEW.BMKGOpenData.Map;
|
||||
using Cryville.EEW.Core;
|
||||
using Cryville.EEW.CWAOpenData.Map;
|
||||
using Cryville.EEW.EMSC.Map;
|
||||
using Cryville.EEW.FANStudio.Map;
|
||||
using Cryville.EEW.GeoNet.Map;
|
||||
using Cryville.EEW.GlobalQuake.Map;
|
||||
using Cryville.EEW.JMAAtom.Map;
|
||||
using Cryville.EEW.Map;
|
||||
using Cryville.EEW.NOAA.Map;
|
||||
using Cryville.EEW.QuakeML.Map;
|
||||
using Cryville.EEW.Report;
|
||||
using Cryville.EEW.USGS.Map;
|
||||
using Cryville.EEW.Wolfx.Map;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
@@ -128,19 +131,27 @@ namespace Cryville.EEW.Unity.Map {
|
||||
|
||||
readonly ContextedGeneratorManager<IMapGeneratorContext, MapElement> _gen = new(new IContextedGenerator<IMapGeneratorContext, MapElement>[] {
|
||||
new BMKGEarthquakeMapGenerator(),
|
||||
new CEAEEWMapGenerator(),
|
||||
new CENCEarthquakeMapGenerator(),
|
||||
new CENCEEWMapGenerator(),
|
||||
new CWAEarthquakeMapGenerator(),
|
||||
new CWAEEWMapGenerator(),
|
||||
new CWATsunamiMapGenerator(),
|
||||
new EMSCRealTimeEventMapGenerator(),
|
||||
new FujianEEWMapGenerator(),
|
||||
new FANStudio.Map.FujianEEWMapGenerator(),
|
||||
new Wolfx.Map.FujianEEWMapGenerator(),
|
||||
new GeoNetQuakeHistoryMapGenerator(),
|
||||
new GeoNetQuakeMapGenerator(),
|
||||
new GeoNetStrongMapGenerator(),
|
||||
new GlobalQuakeMapViewGenerator(),
|
||||
new ICLEEWMapGenerator(),
|
||||
new JMAAtomMapGenerator(),
|
||||
new JMAEEWMapGenerator(),
|
||||
new NOAAMapGenerator(),
|
||||
new FANStudio.Map.SichuanEEWMapGenerator(),
|
||||
new Wolfx.Map.SichuanEEWMapGenerator(),
|
||||
new QuakeMLEventMapGenerator(),
|
||||
new SichuanEEWMapGenerator(),
|
||||
new USGSContoursMapGenerator(),
|
||||
});
|
||||
public UnityMapElement Build(object e, out CultureInfo culture, out int order) {
|
||||
culture = CultureInfo.InvariantCulture;
|
||||
|
||||
@@ -1,113 +1,50 @@
|
||||
using Cryville.EEW.Core.Map;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Networking;
|
||||
|
||||
namespace Cryville.EEW.Unity.Map {
|
||||
[RequireComponent(typeof(SpriteRenderer))]
|
||||
sealed class MapTile : MonoBehaviour {
|
||||
static readonly SemaphoreSlim _semaphore = new(2);
|
||||
|
||||
static readonly HttpClient _httpClient = new() { Timeout = TimeSpan.FromSeconds(10) };
|
||||
|
||||
[SerializeField] Transform _idView;
|
||||
|
||||
public MapTileIndex Index { get; set; }
|
||||
public bool IsEmpty { get; private set; }
|
||||
|
||||
Action<MapTile> _callback;
|
||||
|
||||
SpriteRenderer _renderer;
|
||||
|
||||
UnityWebRequest _req;
|
||||
DownloadHandlerTexture _texHandler;
|
||||
Texture2D _tex;
|
||||
Sprite _sprite;
|
||||
|
||||
void Awake() {
|
||||
_renderer = GetComponent<SpriteRenderer>();
|
||||
}
|
||||
|
||||
FileInfo _localFile;
|
||||
bool _downloadDone;
|
||||
public void Load(MapTileIndex index, string cacheDir, Action<MapTile> onUpdated) {
|
||||
public void Init(MapTileIndex index) {
|
||||
Index = index;
|
||||
_callback = onUpdated;
|
||||
_localFile = new(Path.Combine(cacheDir, $"map/{Index.Z}/{Index.NX}/{Index.NY}"));
|
||||
float z = 1 << index.Z;
|
||||
transform.localPosition = new(index.X / z, -(index.Y + 1) / z, -index.Z / 100f);
|
||||
transform.localScale = new Vector3(1 / z, 1 / z, 1);
|
||||
_idView.gameObject.SetActive(true);
|
||||
byte e = SharedSettings.Instance.IdBytes[((index.X << 2) + index.Y) & 0x1f];
|
||||
int ex = e >> 4, ey = e & 0xf;
|
||||
_idView.localPosition = new(ex / 16f, 1 - ey / 16f, -1 / 200f);
|
||||
if (_localFile.Exists) {
|
||||
_downloadDone = true;
|
||||
if (_idView) {
|
||||
_idView.gameObject.SetActive(true);
|
||||
byte e = SharedSettings.Instance.IdBytes[((index.X << 2) + index.Y) & 0x1f];
|
||||
int ex = e >> 4, ey = e & 0xf;
|
||||
_idView.localPosition = new(ex / 16f, 1 - ey / 16f, -1 / 200f);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
public void Set(Sprite sprite) {
|
||||
if (_renderer) {
|
||||
_renderer.sprite = sprite;
|
||||
}
|
||||
}
|
||||
|
||||
bool _isDestroyed;
|
||||
public void Destroy() {
|
||||
_isDestroyed = true;
|
||||
}
|
||||
void Update() {
|
||||
if (_downloadDone) {
|
||||
try {
|
||||
_texHandler = new DownloadHandlerTexture();
|
||||
_req = new UnityWebRequest($"file:///{_localFile}") {
|
||||
downloadHandler = _texHandler,
|
||||
disposeDownloadHandlerOnDispose = true,
|
||||
};
|
||||
_req.SendWebRequest();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
Debug.LogException(ex);
|
||||
}
|
||||
_downloadDone = false;
|
||||
if (_isDestroyed) {
|
||||
Destroy(gameObject);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
void OnDestroy() {
|
||||
if (_req != null) {
|
||||
_req.Abort();
|
||||
_req.Dispose();
|
||||
_texHandler.Dispose();
|
||||
}
|
||||
if (_sprite) Destroy(_sprite);
|
||||
if (_tex) Destroy(_tex);
|
||||
IsEmpty = true;
|
||||
_callback?.Invoke(this);
|
||||
public void SetVisible(bool v) {
|
||||
_renderer.enabled = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
49
Assets/Cryville.EEW.Unity/Map/MapTileBitmapHolder.cs
Normal file
49
Assets/Cryville.EEW.Unity/Map/MapTileBitmapHolder.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
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>();
|
||||
_behaviour.Index = index;
|
||||
_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);
|
||||
}
|
||||
|
||||
public void Unbind(MapTile tile) {
|
||||
_behaviour.Unbind(tile);
|
||||
}
|
||||
|
||||
public void AddChild(MapTileBitmapHolder bitmapHolder) {
|
||||
_behaviour.AddChild(bitmapHolder._behaviour);
|
||||
}
|
||||
|
||||
public void RemoveChild(MapTileBitmapHolder bitmapHolder) {
|
||||
_behaviour.RemoveChild(bitmapHolder._behaviour);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 225b67dcce9247b4c806e435b34695d2
|
||||
guid: f7ad3a3ac7d829249ba21987d585b07f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
127
Assets/Cryville.EEW.Unity/Map/MapTileBitmapHolderBehaviour.cs
Normal file
127
Assets/Cryville.EEW.Unity/Map/MapTileBitmapHolderBehaviour.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
using Cryville.EEW.Core.Map;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Networking;
|
||||
|
||||
namespace Cryville.EEW.Unity.Map {
|
||||
sealed class MapTileBitmapHolderBehaviour : MonoBehaviour {
|
||||
public MapTileIndex Index { get; set; }
|
||||
|
||||
readonly List<MapTile> _tiles = new();
|
||||
public void Bind(MapTile tile) {
|
||||
_tiles.Add(tile);
|
||||
if (_sprite) {
|
||||
tile.Set(_sprite);
|
||||
}
|
||||
}
|
||||
public void Unbind(MapTile tile) {
|
||||
_tiles.Remove(tile);
|
||||
}
|
||||
|
||||
readonly List<MapTileBitmapHolderBehaviour> _children = new();
|
||||
public void AddChild(MapTileBitmapHolderBehaviour behaviour) {
|
||||
_children.Add(behaviour);
|
||||
foreach (var tile in _tiles) {
|
||||
tile.SetVisible(false);
|
||||
}
|
||||
SetChildSprite(behaviour);
|
||||
}
|
||||
public void RemoveChild(MapTileBitmapHolderBehaviour behaviour) {
|
||||
_children.Remove(behaviour);
|
||||
bool isActive = _children.Count == 0;
|
||||
foreach (var tile in _tiles) {
|
||||
tile.SetVisible(isActive);
|
||||
}
|
||||
}
|
||||
|
||||
UnityWebRequest _req;
|
||||
DownloadHandlerTexture _texHandler;
|
||||
Texture2D _tex;
|
||||
Sprite _sprite;
|
||||
|
||||
FileInfo _localFile;
|
||||
bool _isReady;
|
||||
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) {
|
||||
App.MainLogger.Log(4, "Map", null, "An error occurred when loading map tile {0}: {1}", _localFile, ex);
|
||||
}
|
||||
_isReady = false;
|
||||
}
|
||||
if (_req == null || !_req.isDone) return;
|
||||
if (_texHandler.isDone && _texHandler.texture != null) {
|
||||
_tex = _texHandler.texture;
|
||||
_tex.wrapMode = TextureWrapMode.Clamp;
|
||||
SetSprite(_tex, 0, 0, 0);
|
||||
}
|
||||
else {
|
||||
App.MainLogger.Log(4, "Map", null, "An error occurred when loading map tile {0}: {1}", _localFile, _texHandler.error);
|
||||
_localFile.Delete();
|
||||
}
|
||||
_req.Dispose();
|
||||
_texHandler.Dispose();
|
||||
_req = null;
|
||||
}
|
||||
|
||||
int _minDz = int.MaxValue;
|
||||
int _x, _y;
|
||||
void SetSprite(Texture2D tex, int dz, int x, int y) {
|
||||
if (dz >= 8) return;
|
||||
if (dz > _minDz) return;
|
||||
_tex = tex;
|
||||
_x = x; _y = y;
|
||||
_minDz = dz;
|
||||
if (_sprite) Destroy(_sprite);
|
||||
int sx = x << (8 - dz), sy = 256 - ((y + 1) << (8 - dz));
|
||||
_sprite = Sprite.Create(tex, new Rect(sx, sy, 1 << (8 - dz), 1 << (8 - dz)), Vector2.zero, 1 << (8 - dz), 0, SpriteMeshType.FullRect, Vector4.zero, false);
|
||||
foreach (var tile in _tiles)
|
||||
tile.Set(_sprite);
|
||||
foreach (var child in _children) {
|
||||
SetChildSprite(child);
|
||||
}
|
||||
}
|
||||
|
||||
void SetChildSprite(MapTileBitmapHolderBehaviour child) {
|
||||
if (!_tex) return;
|
||||
int cdz = child.Index.Z - Index.Z;
|
||||
int cx = child.Index.X % (1 << cdz) | (_x << cdz), cy = child.Index.Y % (1 << cdz) | (_y << cdz);
|
||||
child.SetSprite(_tex, cdz + _minDz, cx, cy);
|
||||
}
|
||||
|
||||
bool _isDestroyed;
|
||||
public void Destroy() {
|
||||
_isDestroyed = true;
|
||||
}
|
||||
void OnDestroy() {
|
||||
if (_req != null) {
|
||||
_req.Abort();
|
||||
if (_texHandler != null) {
|
||||
var tex = _texHandler.texture;
|
||||
if (tex) Destroy(tex);
|
||||
_texHandler.Dispose();
|
||||
}
|
||||
_req.Dispose();
|
||||
}
|
||||
if (_sprite) Destroy(_sprite);
|
||||
if (_tex && _minDz == 0) Destroy(_tex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f0dddddc28f728c42a1fed80ba8f95cc
|
||||
guid: c17f6b26e9a6bd74e8b2d071c6951c41
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -1,166 +1,56 @@
|
||||
using Cryville.EEW.Core.Map;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Cryville.EEW.Unity.Map {
|
||||
sealed class TileZOrderComparer : IComparer<MapTileIndex>, IComparer<MapTile> {
|
||||
public static readonly TileZOrderComparer Instance = new();
|
||||
public int Compare(MapTileIndex a, MapTileIndex b) {
|
||||
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>();
|
||||
}
|
||||
}
|
||||
class MapTileCacheManager : MapTileCacheManager<MapTileBitmapHolder> {
|
||||
public GameObject PrefabTile { get; set; }
|
||||
public GameObject PrefabBitmapHolder { get; set; }
|
||||
|
||||
public Transform Parent { get; set; }
|
||||
|
||||
public string CacheDir { get; set; }
|
||||
|
||||
public event Action Updated;
|
||||
void OnUpdated(MapTile tile) {
|
||||
if (tile.IsEmpty) {
|
||||
lock (ActiveTiles) {
|
||||
if (_cache.Remove(tile.Index)) {
|
||||
ActiveTiles.RemoveAt(ActiveTiles.BinarySearch(tile, TileZOrderComparer.Instance));
|
||||
}
|
||||
}
|
||||
}
|
||||
Updated?.Invoke();
|
||||
protected override MapTileBitmapHolder CreateBitmapHolder(MapTileIndex index) => new(
|
||||
index,
|
||||
GameObject.Instantiate(PrefabBitmapHolder, Parent, false),
|
||||
new($"https://services.arcgisonline.com/arcgis/rest/services/Ocean/World_Ocean_Base/MapServer/tile/{index.Z}/{index.NY}/{index.NX}")
|
||||
);
|
||||
|
||||
protected override string GetCacheFilePath(MapTileIndex index) => Path.Combine(CacheDir, $"map/{index.Z}/{index.NX}/{index.NY}");
|
||||
|
||||
readonly Dictionary<MapTile<MapTileBitmapHolder>, MapTile> _map = new();
|
||||
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);
|
||||
var index = tile.Index;
|
||||
uTile.Init(index);
|
||||
var bitmapHolder = tile.BitmapHolder;
|
||||
bitmapHolder.Bind(uTile);
|
||||
|
||||
if (index.Z <= 0) return;
|
||||
var maybeTile = FindTile(index.ZoomToLevel(index.Z - 1, Math.Floor), false);
|
||||
if (maybeTile is not MapTile<MapTileBitmapHolder> parentTile) return;
|
||||
parentTile.BitmapHolder.AddChild(bitmapHolder);
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
MonoBehaviour.Destroy(_dummyTask);
|
||||
|
||||
lock (ActiveTiles) {
|
||||
foreach (var task in ActiveTiles)
|
||||
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);
|
||||
protected override void OnTileDestroyed(MapTile<MapTileBitmapHolder> tile) {
|
||||
base.OnTileDestroyed(tile);
|
||||
var bitmapHolder = tile.BitmapHolder;
|
||||
if (_map.TryGetValue(tile, out var uTile)) {
|
||||
uTile.Destroy();
|
||||
bitmapHolder.Unbind(uTile);
|
||||
_map.Remove(tile);
|
||||
}
|
||||
var index = tile.Index;
|
||||
if (index.Z <= 0) return;
|
||||
var maybeTile = FindTile(index.ZoomToLevel(index.Z - 1, Math.Floor), false);
|
||||
if (maybeTile is not MapTile<MapTileBitmapHolder> parentTile) return;
|
||||
parentTile.BitmapHolder.RemoveChild(bitmapHolder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
219
Assets/Cryville.EEW.Unity/Map/RegionEditor.cs
Normal file
219
Assets/Cryville.EEW.Unity/Map/RegionEditor.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Cryville.EEW.Unity/Map/RegionEditor.cs.meta
Normal file
11
Assets/Cryville.EEW.Unity/Map/RegionEditor.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fd7b70d11ebfe324e830806e394cc334
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
68
Assets/Cryville.EEW.Unity/Map/RegionView.cs
Normal file
68
Assets/Cryville.EEW.Unity/Map/RegionView.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Cryville.EEW.Unity/Map/RegionView.cs.meta
Normal file
11
Assets/Cryville.EEW.Unity/Map/RegionView.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c57a0e86eb63b6048ba265e9d98e84f6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,4 +1,5 @@
|
||||
using Cryville.EEW.Colors;
|
||||
using Cryville.EEW.Core;
|
||||
using Cryville.EEW.Core.Colors;
|
||||
using Cryville.EEW.FERegion;
|
||||
using Cryville.EEW.Map;
|
||||
@@ -25,8 +26,24 @@ namespace Cryville.EEW.Unity {
|
||||
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 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 float HillshadeLayerOpacity { get; private set; } = 1;
|
||||
public TimeSpan NowcastWarningDelayTolerance { get; private set; } = TimeSpan.FromMinutes(60);
|
||||
|
||||
public CultureInfo RVMCulture { get; private set; } = SharedCultures.CurrentUICulture;
|
||||
readonly int _infoLocationSpecificity = 3;
|
||||
readonly int _ttsLocationSpecificity = 3;
|
||||
readonly LocationNamer _locationNamer = new() { Namer = new FERegionLongNamer() };
|
||||
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 IReadOnlyCollection<TTSCultureConfig> TTSCultures { get; private set; }
|
||||
public bool DoIgnoreLanguageVariant { get; private set; }
|
||||
|
||||
public TimeZoneInfo OverrideTimeZone { get; private set; }
|
||||
public bool DoDisplayTimeZone { get; private set; } = true;
|
||||
@@ -101,9 +118,21 @@ namespace Cryville.EEW.Unity {
|
||||
"SREV" => new DefaultTextColorScheme(Color.White, Color.FromArgb(28, 28, 28), 0.555f),
|
||||
_ => new DefaultTextColorScheme(Color.White, Color.Black),
|
||||
};
|
||||
HillshadeLayerOpacity = config.HillshadeLayerOpacity;
|
||||
_locationNamer.Namer = config.LocationNamer switch {
|
||||
"FERegionShort" => new FERegionShortNamer(),
|
||||
_ => new FERegionLongNamer(),
|
||||
};
|
||||
if (config.NowcastWarningDelayTolerance is string nowcastWarningDelayTolerance)
|
||||
NowcastWarningDelayTolerance = TimeSpan.Parse(nowcastWarningDelayTolerance, CultureInfo.InvariantCulture);
|
||||
OverrideTimeZone = ParseTimeZone(config.OverrideTimeZone);
|
||||
DoDisplayTimeZone = config.DoDisplayTimeZone;
|
||||
DoSwitchBackToHistory = config.DoSwitchBackToHistory;
|
||||
RVMCulture = config.OverrideDisplayCulture is string rvmCulture
|
||||
? (string.IsNullOrEmpty(rvmCulture) ? SharedCultures.CurrentUICulture : SharedCultures.Get(rvmCulture))
|
||||
: CultureInfo.InvariantCulture;
|
||||
TTSCultures = config.TTSCultures ?? new List<TTSCultureConfig> { new(CultureInfo.InvariantCulture) };
|
||||
DoIgnoreLanguageVariant = config.DoIgnoreLanguageVariant;
|
||||
EventSources = config.EventSources;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ using System.IO;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Cryville.EEW.Unity {
|
||||
class SoundPlayer : Core.SoundPlayer {
|
||||
class SoundPlayer : Core.Audio.SoundPlayer {
|
||||
public SoundPlayer() : base(GetEngineList(), AudioUsage.NotificationEvent) { }
|
||||
static List<Type> GetEngineList() => new() {
|
||||
typeof(Audio.Wasapi.MMDeviceEnumeratorWrapper),
|
||||
@@ -13,6 +13,7 @@ namespace Cryville.EEW.Unity {
|
||||
};
|
||||
|
||||
protected override Stream Open(string path) {
|
||||
App.MainLogger.Log(0, "Audio", null, "Opening audio file {0}", path);
|
||||
path = Path.Combine(Application.streamingAssetsPath, "Sounds", path + ".ogg");
|
||||
if (!File.Exists(path)) return null;
|
||||
return new FileStream(path, FileMode.Open, FileAccess.Read);
|
||||
|
||||
@@ -1,25 +1,53 @@
|
||||
using SpeechLib;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Cryville.EEW.Unity {
|
||||
class TTSWorker : Core.TTSWorker {
|
||||
public TTSWorker() : base(CreateSoundPlayer()) { }
|
||||
class TTSWorker : Core.Audio.TTSWorker {
|
||||
readonly ISpVoice _voice;
|
||||
|
||||
public TTSWorker() : base(CreateSoundPlayer()) {
|
||||
App.MainLogger.Log(1, "Audio", null, "Initializing TTS worker");
|
||||
try {
|
||||
_voice = new SpVoiceClass();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
static SoundPlayer CreateSoundPlayer() {
|
||||
App.MainLogger.Log(1, "Audio", null, "Creating sound player");
|
||||
try {
|
||||
return new SoundPlayer();
|
||||
}
|
||||
catch (InvalidOperationException) {
|
||||
catch (InvalidOperationException ex) {
|
||||
App.MainLogger.Log(3, "Audio", null, "An error occurred when creating sound player: {0}", ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool IsSpeaking() => false;
|
||||
protected override bool IsSpeaking() {
|
||||
if (_voice == null) return false;
|
||||
_voice.GetStatus(out var status, out _);
|
||||
return (status.dwRunningState & (uint)SpeechRunState.SRSEIsSpeaking) != 0;
|
||||
}
|
||||
|
||||
protected override Task Speak(CultureInfo culture, string content, CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
protected override Task Speak(CultureInfo culture, string content, CancellationToken cancellationToken) {
|
||||
if (_voice == null) return Task.CompletedTask;
|
||||
_voice.Speak(
|
||||
string.Format(CultureInfo.InvariantCulture, "<LANG LANGID=\"{0:x}\">{1}</LANG>", culture.LCID, content),
|
||||
(uint)(SpeechVoiceSpeakFlags.SVSFlagsAsync | SpeechVoiceSpeakFlags.SVSFPurgeBeforeSpeak),
|
||||
out _
|
||||
);
|
||||
App.MainLogger.Log(0, "Audio", null, "TTS ({0}): {1}", culture, content);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected override void StopCurrent() { }
|
||||
protected override void StopCurrent() {
|
||||
if (_voice == null) return;
|
||||
App.MainLogger.Log(0, "Audio", null, "TTS stopping current");
|
||||
_voice.Skip("SENTENCE", int.MaxValue, out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user