418 lines
15 KiB
C#
418 lines
15 KiB
C#
using System.Text;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.Networking;
|
|
using UnityEngine.UI;
|
|
using UnityEngine.XR.ARFoundation;
|
|
|
|
namespace ARLocation
|
|
{
|
|
[RequireComponent(typeof(WorldBuilder))]
|
|
public class WorldBuilderApplicationController : MonoBehaviour
|
|
{
|
|
[System.Serializable]
|
|
public class UiElementsSettings
|
|
{
|
|
public Button CubeBtn;
|
|
public Button CylinderBtn;
|
|
public Button LogoBtn;
|
|
public Button MoveBtn;
|
|
public Button RotateBtn;
|
|
public Button DeselectBtn;
|
|
public Button ClearWorldBtn;
|
|
public Button HeightBtn;
|
|
public Button DeleteObjectBtn;
|
|
public Text DebugText;
|
|
}
|
|
|
|
[System.Serializable]
|
|
public class RaycastMarginSettings
|
|
{
|
|
public float Top;
|
|
public float Bottom;
|
|
public float Left;
|
|
public float Right;
|
|
}
|
|
|
|
[System.Serializable]
|
|
public class GeneralSettings
|
|
{
|
|
public float HeightAdjustmentSensitivity = 0.25f;
|
|
public float RotationAdjustmentSensitivity = 1.0f;
|
|
public string SaveWorldUrl;
|
|
public string RestoreWorldUrl;
|
|
public bool SaveToServer;
|
|
}
|
|
|
|
public UiElementsSettings UiElements;
|
|
public RaycastMarginSettings RaycastMargins;
|
|
public GeneralSettings Settings;
|
|
|
|
private WorldBuilder worldBuilder;
|
|
private PlaceAtLocation selectedObject;
|
|
private WorldBuilder.Entry selectedObjectEntry;
|
|
|
|
enum AppState
|
|
{
|
|
PlacementMode,
|
|
MoveMode,
|
|
RotateMode,
|
|
HeightMode,
|
|
IdleMode
|
|
};
|
|
|
|
class State
|
|
{
|
|
public string CurrentMeshId;
|
|
public AppState AppState;
|
|
}
|
|
|
|
private readonly State state = new State();
|
|
|
|
private void Awake()
|
|
{
|
|
worldBuilder = GetComponent<WorldBuilder>();
|
|
|
|
if (Settings.SaveToServer)
|
|
{
|
|
worldBuilder.UseLocalStorage = false;
|
|
}
|
|
|
|
Debug.Assert(worldBuilder != null);
|
|
Debug.Assert(worldBuilder.PrefabDatabase.Entries.Count > 0);
|
|
|
|
state.CurrentMeshId = "Cube";
|
|
UiElements.CubeBtn.image.color = UiElements.CubeBtn.colors.pressedColor;
|
|
|
|
SetObjectSelectedUIVisible(false);
|
|
|
|
InitListeners();
|
|
}
|
|
|
|
IEnumerator Start()
|
|
{
|
|
if (Settings.SaveToServer)
|
|
{
|
|
|
|
while (!worldBuilder.Initialized)
|
|
{
|
|
yield return new WaitForSeconds(0.1f);
|
|
}
|
|
|
|
yield return RestoreWorldFromServer();
|
|
}
|
|
}
|
|
|
|
IEnumerator RestoreWorldFromServer()
|
|
{
|
|
var request = UnityWebRequest.Get($"{Settings.RestoreWorldUrl}{worldBuilder.Id}");
|
|
|
|
yield return request.SendWebRequest();
|
|
|
|
if (Utils.Misc.WebRequestResultIsError(request))
|
|
{
|
|
Debug.Log("Failed to restore world from server!");
|
|
yield break;
|
|
}
|
|
|
|
Debug.Log(request.downloadHandler.text);
|
|
Debug.Log(request.responseCode);
|
|
worldBuilder.FromJson(request.downloadHandler.text);
|
|
}
|
|
|
|
IEnumerator SaveWorldToServer()
|
|
{
|
|
var request = new UnityWebRequest($"{Settings.SaveWorldUrl}{worldBuilder.Id}", "POST");
|
|
byte[] bodyRaw = Encoding.UTF8.GetBytes(worldBuilder.ToJson());
|
|
request.uploadHandler = (UploadHandler) new UploadHandlerRaw(bodyRaw);
|
|
request.downloadHandler = (DownloadHandler) new DownloadHandlerBuffer();
|
|
request.SetRequestHeader("Content-Type", "application/json");
|
|
|
|
yield return request.SendWebRequest();
|
|
|
|
if (Utils.Misc.WebRequestResultIsError(request))
|
|
{
|
|
Debug.LogWarning("Failed to save to server!");
|
|
}
|
|
|
|
}
|
|
|
|
void SetObjectSelectedUIVisible(bool visible)
|
|
{
|
|
UiElements.MoveBtn.gameObject.SetActive(visible);
|
|
UiElements.RotateBtn.gameObject.SetActive(visible);
|
|
UiElements.DeselectBtn.gameObject.SetActive(visible);
|
|
UiElements.HeightBtn.gameObject.SetActive(visible);
|
|
UiElements.DeleteObjectBtn.gameObject.SetActive(visible);
|
|
|
|
UiElements.CubeBtn.gameObject.SetActive(!visible);
|
|
UiElements.CylinderBtn.gameObject.SetActive(!visible);
|
|
UiElements.LogoBtn.gameObject.SetActive(!visible);
|
|
UiElements.ClearWorldBtn.gameObject.SetActive(!visible);
|
|
}
|
|
|
|
void SetMoveMode()
|
|
{
|
|
UiElements.MoveBtn.image.color = UiElements.MoveBtn.colors.pressedColor;
|
|
UiElements.RotateBtn.image.color = UiElements.MoveBtn.colors.normalColor;
|
|
UiElements.DeselectBtn.image.color = UiElements.MoveBtn.colors.normalColor;
|
|
UiElements.HeightBtn.image.color = UiElements.MoveBtn.colors.normalColor;
|
|
state.AppState = AppState.MoveMode;
|
|
}
|
|
|
|
void SetRotateMode()
|
|
{
|
|
UiElements.MoveBtn.image.color = UiElements.MoveBtn.colors.normalColor;
|
|
UiElements.RotateBtn.image.color = UiElements.MoveBtn.colors.pressedColor;
|
|
UiElements.DeselectBtn.image.color = UiElements.MoveBtn.colors.normalColor;
|
|
UiElements.HeightBtn.image.color = UiElements.MoveBtn.colors.normalColor;
|
|
state.AppState = AppState.RotateMode;
|
|
}
|
|
|
|
private void SetHeightMode()
|
|
{
|
|
UiElements.MoveBtn.image.color = UiElements.MoveBtn.colors.normalColor;
|
|
UiElements.RotateBtn.image.color = UiElements.MoveBtn.colors.normalColor;
|
|
UiElements.DeselectBtn.image.color = UiElements.MoveBtn.colors.normalColor;
|
|
UiElements.HeightBtn.image.color = UiElements.MoveBtn.colors.pressedColor;
|
|
state.AppState = AppState.HeightMode;
|
|
}
|
|
|
|
void InitListeners()
|
|
{
|
|
UiElements.ClearWorldBtn.onClick.AddListener(() =>
|
|
{
|
|
worldBuilder.ClearWorld();
|
|
SetObjectSelectedUIVisible(false);
|
|
state.AppState = AppState.PlacementMode;
|
|
});
|
|
UiElements.CubeBtn.onClick.AddListener(() =>
|
|
{
|
|
UiElements.CubeBtn.image.color = UiElements.CubeBtn.colors.pressedColor;
|
|
UiElements.CylinderBtn.image.color = UiElements.CylinderBtn.colors.normalColor;
|
|
UiElements.LogoBtn.image.color = UiElements.LogoBtn.colors.normalColor;
|
|
state.CurrentMeshId = "Cube";
|
|
});
|
|
|
|
UiElements.CylinderBtn.onClick.AddListener(() =>
|
|
{
|
|
UiElements.CubeBtn.image.color = UiElements.CubeBtn.colors.normalColor;
|
|
UiElements.CylinderBtn.image.color = UiElements.CylinderBtn.colors.pressedColor;
|
|
UiElements.LogoBtn.image.color = UiElements.LogoBtn.colors.normalColor;
|
|
state.CurrentMeshId = "Cylinder";
|
|
});
|
|
|
|
UiElements.LogoBtn.onClick.AddListener(() =>
|
|
{
|
|
UiElements.CubeBtn.image.color = UiElements.CubeBtn.colors.normalColor;
|
|
UiElements.CylinderBtn.image.color = UiElements.CylinderBtn.colors.normalColor;
|
|
UiElements.LogoBtn.image.color = UiElements.LogoBtn.colors.pressedColor;
|
|
state.CurrentMeshId = "Logo";
|
|
});
|
|
|
|
UiElements.DeselectBtn.onClick.AddListener(() =>
|
|
{
|
|
state.CurrentMeshId = "Cube";
|
|
UiElements.CubeBtn.image.color = UiElements.CubeBtn.colors.pressedColor;
|
|
UiElements.CylinderBtn.image.color = UiElements.CylinderBtn.colors.normalColor;
|
|
UiElements.LogoBtn.image.color = UiElements.LogoBtn.colors.normalColor;
|
|
state.AppState = AppState.PlacementMode;
|
|
SetObjectSelectedUIVisible(false);
|
|
selectedObjectEntry = null;
|
|
});
|
|
|
|
UiElements.RotateBtn.onClick.AddListener(() =>
|
|
{
|
|
SetRotateMode();
|
|
});
|
|
|
|
UiElements.MoveBtn.onClick.AddListener(() =>
|
|
{
|
|
SetMoveMode();
|
|
});
|
|
|
|
UiElements.HeightBtn.onClick.AddListener(() =>
|
|
{
|
|
SetHeightMode();
|
|
});
|
|
|
|
UiElements.DeleteObjectBtn.onClick.AddListener(() =>
|
|
{
|
|
worldBuilder.RemoveEntry(selectedObjectEntry);
|
|
});
|
|
}
|
|
|
|
void DebugOutput(string text)
|
|
{
|
|
if (UiElements.DebugText)
|
|
{
|
|
UiElements.DebugText.text = text;
|
|
}
|
|
}
|
|
|
|
void Update()
|
|
{
|
|
DebugOutput($" c = {ARLocationManager.Instance.MainCamera.transform.position}");
|
|
if (Input.touchCount == 1)
|
|
{
|
|
var touch = Input.touches[0];
|
|
|
|
if (state.AppState == AppState.RotateMode)
|
|
{
|
|
if (touch.phase == TouchPhase.Moved || touch.phase == TouchPhase.Stationary)
|
|
{
|
|
float dx = Settings.RotationAdjustmentSensitivity * (180.0f / Mathf.PI) * touch.deltaPosition.x / Screen.width;
|
|
|
|
DebugOutput($"dx = {dx}");
|
|
|
|
if (selectedObjectEntry != null && selectedObjectEntry.Instance != null)
|
|
{
|
|
var euler = selectedObjectEntry.Instance.transform.localEulerAngles;
|
|
selectedObjectEntry.Rotation = euler.y + dx;
|
|
selectedObjectEntry.Instance.transform.localEulerAngles = new Vector3(euler.x, selectedObjectEntry.Rotation, euler.z);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
else if (state.AppState == AppState.HeightMode)
|
|
{
|
|
if (touch.phase == TouchPhase.Moved || touch.phase == TouchPhase.Stationary)
|
|
{
|
|
float dy = Settings.HeightAdjustmentSensitivity * (180.0f / Mathf.PI) * touch.deltaPosition.y / Screen.height;
|
|
|
|
DebugOutput($"dy = {dy}");
|
|
|
|
if (selectedObjectEntry != null && selectedObjectEntry.Instance != null)
|
|
{
|
|
var Location = selectedObjectEntry.Instance.GetComponent<PlaceAtLocation>().Location.Clone();
|
|
Location.Altitude += dy;
|
|
selectedObjectEntry.Location.Altitude += dy;
|
|
selectedObjectEntry.Instance.GetComponent<PlaceAtLocation>().Location = Location;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
else if (touch.phase == TouchPhase.Began)
|
|
{
|
|
if (UnityEngine.EventSystems.EventSystem.current.IsPointerOverGameObject(0)) return;
|
|
|
|
OnTouchOrClick(touch.position);
|
|
}
|
|
}
|
|
|
|
if (Application.isEditor && Input.GetMouseButtonDown(0))
|
|
{
|
|
OnTouchOrClick(Input.mousePosition);
|
|
}
|
|
}
|
|
|
|
private void OnTouchOrClick(Vector2 p)
|
|
{
|
|
float x = p.x / Screen.width;
|
|
float y = p.y / Screen.height;
|
|
|
|
if (x < RaycastMargins.Left || x > (1 - RaycastMargins.Right)) return;
|
|
if (y < RaycastMargins.Bottom || y > (1 - RaycastMargins.Top)) return;
|
|
|
|
var camera = Application.isEditor ? Camera.main : ARLocationManager.Instance.MainCamera;
|
|
var ray = camera.ScreenPointToRay(p);
|
|
|
|
|
|
if (state.AppState == AppState.PlacementMode)
|
|
{
|
|
RaycastHit hit;
|
|
if (Physics.Raycast(ray, out hit))
|
|
{
|
|
GameObject go = null;
|
|
WorldBuilder.Entry entry = null;
|
|
var o = hit.collider.transform;
|
|
while (o.parent)
|
|
{
|
|
Debug.Log(o.name);
|
|
entry = worldBuilder.GetWorld().Entries.Find(e => e.Instance == o.gameObject);
|
|
|
|
if (entry != null)
|
|
{
|
|
go = entry.Instance;
|
|
break;
|
|
}
|
|
|
|
o = o.parent;
|
|
}
|
|
|
|
if (go != null && entry != null)
|
|
{
|
|
selectedObjectEntry = entry;
|
|
SetObjectSelectedUIVisible(true);
|
|
SetMoveMode();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
float enter;
|
|
if (RaycastGround(ray, out enter))
|
|
{
|
|
var point = ray.GetPoint(enter);
|
|
switch (state.AppState)
|
|
{
|
|
case AppState.PlacementMode:
|
|
OnPlacementRaycast(point);
|
|
break;
|
|
|
|
case AppState.MoveMode:
|
|
OnMoveModeRaycast(point);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private bool RaycastGround(Ray ray, out float t)
|
|
{
|
|
var arRaycastManager = FindObjectOfType<ARRaycastManager>();
|
|
|
|
if (Application.isEditor || arRaycastManager == null)
|
|
{
|
|
var camera = Application.isEditor ? Camera.main : ARLocationManager.Instance.MainCamera;
|
|
var plane = new Plane(new Vector3(0, 1, 0), camera.transform.position - new Vector3(0, 1.4f, 0));
|
|
return plane.Raycast(ray, out t);
|
|
}
|
|
else
|
|
{
|
|
List<ARRaycastHit> hits = new List<ARRaycastHit>();
|
|
arRaycastManager.Raycast(ray, hits, trackableTypes: UnityEngine.XR.ARSubsystems.TrackableType.PlaneWithinInfinity);
|
|
|
|
if (hits.Count > 0)
|
|
{
|
|
t = hits[0].distance;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
var camera = Application.isEditor ? Camera.main : ARLocationManager.Instance.MainCamera;
|
|
var plane = new Plane(new Vector3(0, 1, 0), camera.transform.position - new Vector3(0, 1.4f, 0));
|
|
return plane.Raycast(ray, out t);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnMoveModeRaycast(Vector3 point)
|
|
{
|
|
worldBuilder.MoveEntry(selectedObjectEntry, point);
|
|
}
|
|
|
|
private void OnPlacementRaycast(Vector3 v)
|
|
{
|
|
Debug.Log("ok!");
|
|
worldBuilder.AddEntry(state.CurrentMeshId, v);
|
|
|
|
if (Settings.SaveToServer)
|
|
{
|
|
Debug.Log("ok2!");
|
|
StartCoroutine(SaveWorldToServer());
|
|
}
|
|
}
|
|
}
|
|
}
|