220 lines
6.0 KiB
C#
220 lines
6.0 KiB
C#
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();
|
|
}
|
|
}
|
|
}
|