345 lines
12 KiB
C#
345 lines
12 KiB
C#
|
using UdonSharp;
|
||
|
using UnityEngine;
|
||
|
using VRC.SDK3.Components;
|
||
|
using VRC.SDKBase;
|
||
|
using VRC.Udon.Common.Interfaces;
|
||
|
|
||
|
#pragma warning disable CS0108
|
||
|
#pragma warning disable IDE0044
|
||
|
#pragma warning disable IDE0066, IDE0074
|
||
|
#pragma warning disable IDE0090, IDE1006
|
||
|
|
||
|
namespace QvPen.UdonScript
|
||
|
{
|
||
|
[DefaultExecutionOrder(11)]
|
||
|
[UdonBehaviourSyncMode(BehaviourSyncMode.NoVariableSync)]
|
||
|
public class QvPen_Eraser : UdonSharpBehaviour
|
||
|
{
|
||
|
private const string _version = QvPen_Pen._version;
|
||
|
|
||
|
[SerializeField]
|
||
|
private Material normal;
|
||
|
private Material erasing;
|
||
|
|
||
|
[SerializeField]
|
||
|
private Transform inkPoolRoot;
|
||
|
|
||
|
private Renderer _renderer;
|
||
|
private Renderer renderer => _renderer ? _renderer : (_renderer = GetComponentInChildren<Renderer>(true));
|
||
|
|
||
|
private SphereCollider _sphereCollider;
|
||
|
private SphereCollider sphereCollider => _sphereCollider ? _sphereCollider : (_sphereCollider = GetComponent<SphereCollider>());
|
||
|
|
||
|
private VRC_Pickup _pickup;
|
||
|
private VRC_Pickup pickup => _pickup ? _pickup : (_pickup = (VRC_Pickup)GetComponent(typeof(VRC_Pickup)));
|
||
|
|
||
|
private VRCObjectSync _objectSync;
|
||
|
private VRCObjectSync objectSync
|
||
|
=> _objectSync ? _objectSync : (_objectSync = (VRCObjectSync)GetComponent(typeof(VRCObjectSync)));
|
||
|
|
||
|
private bool isUser;
|
||
|
public bool IsUser => isUser;
|
||
|
|
||
|
private bool isErasing;
|
||
|
|
||
|
// EraserManager
|
||
|
private QvPen_EraserManager manager;
|
||
|
|
||
|
private float _eraserRadius = 0f;
|
||
|
private float eraserRadius
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (_eraserRadius > 0f)
|
||
|
return _eraserRadius;
|
||
|
else
|
||
|
{
|
||
|
var s = transform.lossyScale;
|
||
|
_eraserRadius = Mathf.Min(s.x, s.y, s.z) * sphereCollider.radius;
|
||
|
return _eraserRadius;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private int inkColliderLayer;
|
||
|
|
||
|
private const string inkPoolRootName = QvPen_Pen.inkPoolRootName;
|
||
|
|
||
|
private VRCPlayerApi _localPlayer;
|
||
|
private VRCPlayerApi localPlayer => _localPlayer ?? (_localPlayer = Networking.LocalPlayer);
|
||
|
|
||
|
private int localPlayerId => VRC.SDKBase.Utilities.IsValid(localPlayer) ? localPlayer.playerId : -1;
|
||
|
|
||
|
public void _Init(QvPen_EraserManager manager)
|
||
|
{
|
||
|
this.manager = manager;
|
||
|
inkColliderLayer = manager.inkColliderLayer;
|
||
|
|
||
|
//-> [DefaultExecutionOrder(11)]
|
||
|
var inkPoolRootGO = GameObject.Find($"/{inkPoolRootName}");
|
||
|
if (inkPoolRootGO)
|
||
|
{
|
||
|
inkPoolRoot = inkPoolRootGO.transform;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
inkPoolRoot.name = inkPoolRootName;
|
||
|
SetParentAndResetLocalTransform(inkPoolRoot, null);
|
||
|
inkPoolRoot.SetAsFirstSibling();
|
||
|
#if !UNITY_EDITOR
|
||
|
const string ureishi = nameof(ureishi);
|
||
|
Log($"{nameof(QvPen)} {_version}");
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
erasing = renderer.sharedMaterial;
|
||
|
|
||
|
pickup.InteractionText = "Eraser";
|
||
|
pickup.UseText = "Erase";
|
||
|
}
|
||
|
|
||
|
public override void OnPlayerJoined(VRCPlayerApi player)
|
||
|
{
|
||
|
if (isUser && isErasing)
|
||
|
SendCustomNetworkEvent(NetworkEventTarget.All, nameof(OnPickupEvent));
|
||
|
}
|
||
|
|
||
|
public override void OnPickup()
|
||
|
{
|
||
|
isUser = true;
|
||
|
|
||
|
sphereCollider.enabled = false;
|
||
|
|
||
|
manager._TakeOwnership();
|
||
|
manager.SendCustomNetworkEvent(NetworkEventTarget.All, nameof(QvPen_EraserManager.StartUsing));
|
||
|
|
||
|
SendCustomNetworkEvent(NetworkEventTarget.All, nameof(OnPickupEvent));
|
||
|
}
|
||
|
|
||
|
public override void OnDrop()
|
||
|
{
|
||
|
isUser = false;
|
||
|
|
||
|
sphereCollider.enabled = true;
|
||
|
|
||
|
manager._ClearSyncBuffer();
|
||
|
manager.SendCustomNetworkEvent(NetworkEventTarget.All, nameof(QvPen_EraserManager.EndUsing));
|
||
|
|
||
|
SendCustomNetworkEvent(NetworkEventTarget.All, nameof(OnDropEvent));
|
||
|
}
|
||
|
|
||
|
public override void OnPickupUseDown()
|
||
|
{
|
||
|
SendCustomNetworkEvent(NetworkEventTarget.All, nameof(StartErasing));
|
||
|
}
|
||
|
|
||
|
public override void OnPickupUseUp()
|
||
|
{
|
||
|
SendCustomNetworkEvent(NetworkEventTarget.All, nameof(FinishErasing));
|
||
|
}
|
||
|
|
||
|
public void OnPickupEvent() => renderer.sharedMaterial = normal;
|
||
|
|
||
|
public void OnDropEvent() => renderer.sharedMaterial = erasing;
|
||
|
|
||
|
public void StartErasing()
|
||
|
{
|
||
|
isErasing = true;
|
||
|
renderer.sharedMaterial = erasing;
|
||
|
}
|
||
|
|
||
|
public void FinishErasing()
|
||
|
{
|
||
|
isErasing = false;
|
||
|
renderer.sharedMaterial = normal;
|
||
|
}
|
||
|
|
||
|
#region Data protocol
|
||
|
|
||
|
#region Base
|
||
|
|
||
|
// Mode
|
||
|
private const int MODE_UNKNOWN = QvPen_Pen.MODE_UNKNOWN;
|
||
|
private const int MODE_DRAW = QvPen_Pen.MODE_DRAW;
|
||
|
private const int MODE_ERASE = QvPen_Pen.MODE_ERASE;
|
||
|
private const int MODE_DRAW_PLANE = QvPen_Pen.MODE_DRAW_PLANE;
|
||
|
|
||
|
// Footer element
|
||
|
private const int FOOTER_ELEMENT_DATA_INFO = QvPen_Pen.FOOTER_ELEMENT_DATA_INFO;
|
||
|
private const int FOOTER_ELEMENT_PEN_ID = QvPen_Pen.FOOTER_ELEMENT_PEN_ID;
|
||
|
|
||
|
private const int FOOTER_ELEMENT_DRAW_INK_INFO = QvPen_Pen.FOOTER_ELEMENT_DRAW_INK_INFO;
|
||
|
private const int FOOTER_ELEMENT_DRAW_LENGTH = QvPen_Pen.FOOTER_ELEMENT_DRAW_LENGTH;
|
||
|
|
||
|
private const int FOOTER_ELEMENT_ERASE_POINTER_POSITION = QvPen_Pen.FOOTER_ELEMENT_ERASE_POINTER_POSITION;
|
||
|
private const int FOOTER_ELEMENT_ERASE_POINTER_RADIUS = QvPen_Pen.FOOTER_ELEMENT_ERASE_POINTER_RADIUS;
|
||
|
private const int FOOTER_ELEMENT_ERASE_LENGTH = QvPen_Pen.FOOTER_ELEMENT_ERASE_LENGTH;
|
||
|
|
||
|
#endregion
|
||
|
|
||
|
private int GetFooterSize(int mode)
|
||
|
{
|
||
|
switch (mode)
|
||
|
{
|
||
|
case MODE_DRAW: return FOOTER_ELEMENT_DRAW_LENGTH;
|
||
|
case MODE_ERASE: return FOOTER_ELEMENT_ERASE_LENGTH;
|
||
|
case MODE_UNKNOWN: return 0;
|
||
|
default: return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private Vector3 GetData(Vector3[] data, int index)
|
||
|
=> data.Length > index ? data[data.Length - 1 - index] : Vector3.negativeInfinity;
|
||
|
|
||
|
private void SetData(Vector3[] data, int index, Vector3 element)
|
||
|
{
|
||
|
if (data.Length > index)
|
||
|
data[data.Length - 1 - index] = element;
|
||
|
}
|
||
|
|
||
|
private int GetMode(Vector3[] data)
|
||
|
=> data.Length > 0 ? (int)GetData(data, 0).y : MODE_UNKNOWN;
|
||
|
|
||
|
public void _SendData(Vector3[] data)
|
||
|
=> manager._SendData(data);
|
||
|
|
||
|
#endregion
|
||
|
|
||
|
private readonly Collider[] results = new Collider[4];
|
||
|
public override void PostLateUpdate()
|
||
|
{
|
||
|
if (!isUser || !isHeld || !isErasing)
|
||
|
return;
|
||
|
|
||
|
var count = Physics.OverlapSphereNonAlloc(transform.position, eraserRadius, results, 1 << inkColliderLayer, QueryTriggerInteraction.Ignore);
|
||
|
for (var i = 0; i < count; i++)
|
||
|
{
|
||
|
var other = results[i];
|
||
|
|
||
|
if (other && other.transform.parent && other.transform.parent.parent && other.transform.parent.parent.parent
|
||
|
&& other.transform.parent.parent.parent.parent == inkPoolRoot)
|
||
|
{
|
||
|
var syncer = other.GetComponentInParent<QvPen_LateSync>();
|
||
|
if (syncer)
|
||
|
{
|
||
|
var pen = syncer.pen;
|
||
|
var penIdVector = pen.penIdVector;
|
||
|
var lineRenderer = other.GetComponentInParent<LineRenderer>();
|
||
|
if (lineRenderer && lineRenderer.positionCount > 0)
|
||
|
{
|
||
|
var data = new Vector3[GetFooterSize(MODE_ERASE)];
|
||
|
|
||
|
SetData(data, FOOTER_ELEMENT_DATA_INFO, new Vector3(localPlayerId, MODE_ERASE, GetFooterSize(MODE_ERASE)));
|
||
|
SetData(data, FOOTER_ELEMENT_PEN_ID, penIdVector);
|
||
|
SetData(data, FOOTER_ELEMENT_ERASE_POINTER_POSITION, transform.position);
|
||
|
SetData(data, FOOTER_ELEMENT_ERASE_POINTER_RADIUS, Vector3.right * eraserRadius);
|
||
|
|
||
|
_SendData(data);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
results[i] = null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void _UnpackData(Vector3[] data)
|
||
|
{
|
||
|
if (data.Length == 0)
|
||
|
return;
|
||
|
|
||
|
switch (GetMode(data))
|
||
|
{
|
||
|
case MODE_ERASE:
|
||
|
if (isUser && VRCPlayerApi.GetPlayerCount() > 1)
|
||
|
tmpErasedData = data;
|
||
|
else
|
||
|
EraseInk(data);
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private Vector3[] tmpErasedData;
|
||
|
public void ExecuteEraseInk()
|
||
|
{
|
||
|
if (tmpErasedData != null)
|
||
|
EraseInk(tmpErasedData);
|
||
|
|
||
|
tmpErasedData = null;
|
||
|
}
|
||
|
|
||
|
private void EraseInk(Vector3[] data)
|
||
|
{
|
||
|
if (data.Length < GetFooterSize(MODE_ERASE))
|
||
|
return;
|
||
|
|
||
|
var pointerPosition = GetData(data, FOOTER_ELEMENT_ERASE_POINTER_POSITION);
|
||
|
var radius = GetData(data, FOOTER_ELEMENT_ERASE_POINTER_RADIUS).x;
|
||
|
var count = Physics.OverlapSphereNonAlloc(pointerPosition, radius, results, 1 << inkColliderLayer, QueryTriggerInteraction.Ignore);
|
||
|
for (var i = 0; i < count; i++)
|
||
|
{
|
||
|
var other = results[i];
|
||
|
Transform t;
|
||
|
if (other && (t = other.transform.parent) && (t = t.parent) && (t = t.parent) && t.parent == inkPoolRoot)
|
||
|
{
|
||
|
Destroy(other.GetComponent<MeshCollider>().sharedMesh);
|
||
|
Destroy(other.transform.parent.gameObject);
|
||
|
}
|
||
|
|
||
|
results[i] = null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
[System.NonSerialized]
|
||
|
public bool pickuped = false; // protected
|
||
|
public bool isHeld => pickuped;
|
||
|
|
||
|
public void _Respawn()
|
||
|
{
|
||
|
pickup.Drop();
|
||
|
|
||
|
if (Networking.LocalPlayer.IsOwner(gameObject))
|
||
|
objectSync.Respawn();
|
||
|
}
|
||
|
|
||
|
#region Utility
|
||
|
|
||
|
private void SetParentAndResetLocalTransform(Transform child, Transform parent)
|
||
|
{
|
||
|
if (child)
|
||
|
{
|
||
|
child.SetParent(parent);
|
||
|
child.localPosition = Vector3.zero;
|
||
|
child.localRotation = Quaternion.identity;
|
||
|
child.localScale = Vector3.one;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#endregion
|
||
|
|
||
|
#region Log
|
||
|
|
||
|
private void Log(object o) => Debug.Log($"{logPrefix}{o}", this);
|
||
|
private void Warning(object o) => Debug.LogWarning($"{logPrefix}{o}", this);
|
||
|
private void Error(object o) => Debug.LogError($"{logPrefix}{o}", this);
|
||
|
|
||
|
private readonly Color logColor = new Color(0xf2, 0x7d, 0x4a, 0xff) / 0xff;
|
||
|
private string ColorBeginTag(Color c) => $"<color=\"#{ToHtmlStringRGB(c)}\">";
|
||
|
private const string ColorEndTag = "</color>";
|
||
|
|
||
|
private string _logPrefix;
|
||
|
private string logPrefix
|
||
|
=> string.IsNullOrEmpty(_logPrefix)
|
||
|
? (_logPrefix = $"[{ColorBeginTag(logColor)}{nameof(QvPen)}.{nameof(QvPen.Udon)}.{nameof(QvPen_Eraser)}{ColorEndTag}] ") : _logPrefix;
|
||
|
|
||
|
private string ToHtmlStringRGB(Color c)
|
||
|
{
|
||
|
c *= 0xff;
|
||
|
return $"{Mathf.RoundToInt(c.r):x2}{Mathf.RoundToInt(c.g):x2}{Mathf.RoundToInt(c.b):x2}";
|
||
|
}
|
||
|
|
||
|
#endregion
|
||
|
}
|
||
|
}
|