1022 lines
29 KiB
C#
1022 lines
29 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.XR;
|
|
|
|
namespace BlockSpace.Voxels
|
|
{
|
|
[DefaultExecutionOrder(-100)]
|
|
[DisallowMultipleComponent]
|
|
public sealed class VRGroupSelector : MonoBehaviour
|
|
{
|
|
private sealed class GrabbedGroupState
|
|
{
|
|
public VoxelGroup group;
|
|
|
|
public Vector3 startWorldPosition;
|
|
|
|
public byte startPackedRotation;
|
|
|
|
public bool colliderWasEnabled;
|
|
|
|
public float lastPreviewSentAt = float.NegativeInfinity;
|
|
}
|
|
|
|
private struct GroupMergeAnimationState
|
|
{
|
|
public VoxelGroup group;
|
|
|
|
public Vector3 startTransformPosition;
|
|
|
|
public Vector3 startBoundsCenter;
|
|
|
|
public Vector3 burstStartCenter;
|
|
|
|
public Vector3 burstPeakCenter;
|
|
}
|
|
|
|
private const string GroupTickClipResourcePath = "Click";
|
|
|
|
private const string GroupImpactClipResourcePath = "Thock";
|
|
|
|
[SerializeField]
|
|
private XRNode rightHand = XRNode.RightHand;
|
|
|
|
[SerializeField]
|
|
private XRNode leftHand = XRNode.LeftHand;
|
|
|
|
[SerializeField]
|
|
private Transform selectorOrigin;
|
|
|
|
[SerializeField]
|
|
private float selectionRadius = 0.1f;
|
|
|
|
[SerializeField]
|
|
private KeyCode keyboardSelectKey = KeyCode.G;
|
|
|
|
[SerializeField]
|
|
private KeyCode keyboardMergeKey = KeyCode.F;
|
|
|
|
[SerializeField]
|
|
private KeyCode keyboardDuplicateKey = KeyCode.D;
|
|
|
|
[SerializeField]
|
|
private KeyCode keyboardPaintSelectionKey = KeyCode.Q;
|
|
|
|
[SerializeField]
|
|
private KeyCode keyboardInventoryKey = KeyCode.I;
|
|
|
|
[SerializeField]
|
|
private KeyCode keyboardDeleteKey = KeyCode.Delete;
|
|
|
|
[SerializeField]
|
|
private float gripGrabThreshold = 0.85f;
|
|
|
|
[SerializeField]
|
|
private float deleteTriggerThreshold = 0.7f;
|
|
|
|
[SerializeField]
|
|
private float undoChordWindow = 0.12f;
|
|
|
|
[SerializeField]
|
|
private float grabPositionSnap = 0.125f;
|
|
|
|
[SerializeField]
|
|
private float grabRotationSnapDegrees = 90f;
|
|
|
|
[SerializeField]
|
|
private float movePreviewSendInterval = 0.05f;
|
|
|
|
[Header("Grouping Audio")]
|
|
[SerializeField]
|
|
private AudioClip groupingClickClip;
|
|
|
|
[SerializeField]
|
|
private AudioClip groupingThockClip;
|
|
|
|
[SerializeField]
|
|
private VoxelGroupManager groupManager;
|
|
|
|
private readonly HashSet<VoxelGroup> selectedGroups = new HashSet<VoxelGroup>();
|
|
|
|
private readonly List<VoxelGroup> selectionBuffer = new List<VoxelGroup>();
|
|
|
|
private readonly List<VoxelGroup> cleanupBuffer = new List<VoxelGroup>();
|
|
|
|
private readonly List<GrabbedGroupState> grabbedGroupStates = new List<GrabbedGroupState>();
|
|
|
|
private InputDevice rightHandDevice;
|
|
|
|
private InputDevice leftHandDevice;
|
|
|
|
private bool mergeButtonPrevious;
|
|
|
|
private bool duplicateButtonPrevious;
|
|
|
|
private bool thumbstickClickPrevious;
|
|
|
|
private bool inventoryButtonPrevious;
|
|
|
|
private bool selectionHeld;
|
|
|
|
private bool selectionInputHeld;
|
|
|
|
private bool undoChordActive;
|
|
|
|
private bool suppressRightHandActionsUntilNeutral;
|
|
|
|
private bool interactionSuppressed;
|
|
|
|
private bool selectModeLatchedThisHold;
|
|
|
|
private bool gripHeldPrevious;
|
|
|
|
private bool triggerHeldPrevious;
|
|
|
|
private float lastGripPressTime = float.NegativeInfinity;
|
|
|
|
private float lastTriggerPressTime = float.NegativeInfinity;
|
|
|
|
private Transform trackingOrigin;
|
|
|
|
private bool groupingAnimationActive;
|
|
|
|
private bool selectionGrabActive;
|
|
|
|
private Vector3 grabStartReferencePosition;
|
|
|
|
private Quaternion grabStartReferenceRotation;
|
|
|
|
private Vector3 grabStartSelectionCenter;
|
|
|
|
private Vector3 grabStartSelectionOffset;
|
|
|
|
public static VRGroupSelector Instance { get; private set; }
|
|
|
|
public bool IsSelecting => selectionHeld;
|
|
|
|
public bool HasSelection => selectedGroups.Count > 0;
|
|
|
|
public float SelectionRadius => selectionRadius;
|
|
|
|
public bool BlocksRightHandActions
|
|
{
|
|
get
|
|
{
|
|
if (!undoChordActive && !suppressRightHandActionsUntilNeutral && !groupingAnimationActive)
|
|
{
|
|
return selectionGrabActive;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
public void SetInteractionSuppressed(bool suppressed)
|
|
{
|
|
if (interactionSuppressed == suppressed)
|
|
{
|
|
return;
|
|
}
|
|
interactionSuppressed = suppressed;
|
|
if (interactionSuppressed)
|
|
{
|
|
if (HasSelection)
|
|
{
|
|
ClearSelectionInternal(suppressUntilNeutral: false);
|
|
}
|
|
selectionHeld = false;
|
|
selectionInputHeld = false;
|
|
undoChordActive = false;
|
|
suppressRightHandActionsUntilNeutral = false;
|
|
selectModeLatchedThisHold = false;
|
|
EndSelectionGrab();
|
|
}
|
|
}
|
|
|
|
public void Configure(VoxelGroupManager manager, Transform origin, Transform handAnchor)
|
|
{
|
|
if (manager != null)
|
|
{
|
|
groupManager = manager;
|
|
}
|
|
trackingOrigin = origin;
|
|
if (handAnchor != null)
|
|
{
|
|
selectorOrigin = handAnchor;
|
|
}
|
|
}
|
|
|
|
public bool TryGetSelectionCenter(out Vector3 worldPosition)
|
|
{
|
|
return TryGetSelectorPosition(out worldPosition);
|
|
}
|
|
|
|
public void SetSelectionRadius(float radius)
|
|
{
|
|
selectionRadius = Mathf.Max(0.001f, radius);
|
|
}
|
|
|
|
public void ClearSelectionForToolUse()
|
|
{
|
|
if (HasSelection)
|
|
{
|
|
ClearSelectionInternal(suppressUntilNeutral: false);
|
|
}
|
|
}
|
|
|
|
private void Awake()
|
|
{
|
|
if (Instance != null && Instance != this)
|
|
{
|
|
Debug.LogError("Multiple VRGroupSelector instances found.", this);
|
|
return;
|
|
}
|
|
Instance = this;
|
|
if (groupManager == null)
|
|
{
|
|
groupManager = ((VoxelGroupManager.Instance != null) ? VoxelGroupManager.Instance : UnityEngine.Object.FindObjectOfType<VoxelGroupManager>());
|
|
}
|
|
selectionRadius = Mathf.Max(0.001f, selectionRadius);
|
|
gripGrabThreshold = Mathf.Clamp(gripGrabThreshold, 0.5f, 1f);
|
|
undoChordWindow = Mathf.Max(0.01f, undoChordWindow);
|
|
grabPositionSnap = Mathf.Max(0.001f, grabPositionSnap);
|
|
grabRotationSnapDegrees = Mathf.Max(1f, grabRotationSnapDegrees);
|
|
EnsureGroupingClipsAssigned();
|
|
}
|
|
|
|
private void OnEnable()
|
|
{
|
|
rightHandDevice = InputDevices.GetDeviceAtXRNode(rightHand);
|
|
leftHandDevice = InputDevices.GetDeviceAtXRNode(leftHand);
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
if (Instance == this)
|
|
{
|
|
Instance = null;
|
|
}
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (groupManager == null)
|
|
{
|
|
return;
|
|
}
|
|
if (!groupManager.CreatorMode || groupManager.EditingLocked)
|
|
{
|
|
if (HasSelection)
|
|
{
|
|
ClearSelectionInternal(suppressUntilNeutral: false);
|
|
}
|
|
selectionHeld = false;
|
|
selectionInputHeld = false;
|
|
undoChordActive = false;
|
|
suppressRightHandActionsUntilNeutral = false;
|
|
selectModeLatchedThisHold = false;
|
|
EndSelectionGrab();
|
|
return;
|
|
}
|
|
VRVoxelBrush instance = VRVoxelBrush.Instance;
|
|
bool flag = instance == null || instance.UsingXRInput;
|
|
if (!rightHandDevice.isValid)
|
|
{
|
|
rightHandDevice = InputDevices.GetDeviceAtXRNode(rightHand);
|
|
}
|
|
if (!leftHandDevice.isValid)
|
|
{
|
|
leftHandDevice = InputDevices.GetDeviceAtXRNode(leftHand);
|
|
}
|
|
if (groupingAnimationActive)
|
|
{
|
|
return;
|
|
}
|
|
if (interactionSuppressed)
|
|
{
|
|
bool flag2 = ((!flag) ? (Input.GetMouseButton(2) || Input.GetKey(keyboardSelectKey)) : (ReadBool(rightHandDevice, CommonUsages.primary2DAxisClick) || Input.GetKey(keyboardSelectKey)));
|
|
bool flag3 = (flag ? ReadGrabHeld(rightHandDevice) : Input.GetMouseButton(1));
|
|
bool flag4 = flag && ReadFloat(rightHandDevice, CommonUsages.trigger) >= deleteTriggerThreshold;
|
|
bool flag5 = flag && ReadBool(rightHandDevice, CommonUsages.primaryButton);
|
|
bool flag6 = flag && ReadBool(leftHandDevice, CommonUsages.primaryButton);
|
|
bool flag7 = flag && ReadBool(rightHandDevice, CommonUsages.secondaryButton);
|
|
selectionHeld = false;
|
|
selectionInputHeld = false;
|
|
thumbstickClickPrevious = flag2;
|
|
mergeButtonPrevious = flag5;
|
|
duplicateButtonPrevious = flag6;
|
|
inventoryButtonPrevious = flag7;
|
|
gripHeldPrevious = flag3;
|
|
triggerHeldPrevious = flag4;
|
|
if (!flag3 && !flag4)
|
|
{
|
|
undoChordActive = false;
|
|
suppressRightHandActionsUntilNeutral = false;
|
|
}
|
|
return;
|
|
}
|
|
CleanupDestroyedSelections();
|
|
Vector3 worldPosition;
|
|
bool num = TryGetSelectorPosition(out worldPosition);
|
|
selectionBuffer.Clear();
|
|
if (num)
|
|
{
|
|
selectionBuffer.AddRange(groupManager.GetGroupsInRadius(worldPosition, selectionRadius));
|
|
}
|
|
bool flag8 = ((!flag) ? (Input.GetMouseButton(2) || Input.GetKey(keyboardSelectKey)) : (ReadBool(rightHandDevice, CommonUsages.primary2DAxisClick) || Input.GetKey(keyboardSelectKey)));
|
|
bool flag9 = (flag ? ReadGrabHeld(rightHandDevice) : Input.GetMouseButton(1));
|
|
bool flag10 = flag && ReadFloat(rightHandDevice, CommonUsages.trigger) >= deleteTriggerThreshold;
|
|
bool flag11 = flag && ReadBool(rightHandDevice, CommonUsages.primaryButton);
|
|
bool flag12 = flag && ReadBool(leftHandDevice, CommonUsages.primaryButton);
|
|
bool flag13 = flag && ReadBool(rightHandDevice, CommonUsages.secondaryButton);
|
|
bool flag14 = Input.GetKeyDown(keyboardDuplicateKey) && (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl));
|
|
if (flag9 && !gripHeldPrevious)
|
|
{
|
|
lastGripPressTime = Time.unscaledTime;
|
|
}
|
|
if (flag10 && !triggerHeldPrevious)
|
|
{
|
|
lastTriggerPressTime = Time.unscaledTime;
|
|
}
|
|
if (flag8 && !thumbstickClickPrevious)
|
|
{
|
|
selectModeLatchedThisHold = false;
|
|
}
|
|
if (!flag8 && thumbstickClickPrevious)
|
|
{
|
|
selectModeLatchedThisHold = false;
|
|
}
|
|
if (flag8 && selectionBuffer.Count > 0)
|
|
{
|
|
selectModeLatchedThisHold = true;
|
|
}
|
|
selectionInputHeld = flag8 && selectModeLatchedThisHold;
|
|
if (!undoChordActive && flag9 && flag10)
|
|
{
|
|
float num2 = Mathf.Max(lastGripPressTime, lastTriggerPressTime);
|
|
if (num2 > float.NegativeInfinity && Mathf.Abs(lastGripPressTime - lastTriggerPressTime) <= undoChordWindow && Time.unscaledTime - num2 <= undoChordWindow)
|
|
{
|
|
undoChordActive = true;
|
|
selectionHeld = false;
|
|
EndSelectionGrab();
|
|
groupManager.UndoLastOperation();
|
|
}
|
|
}
|
|
if (!undoChordActive && !suppressRightHandActionsUntilNeutral && HasSelection && ((flag12 && !duplicateButtonPrevious) || flag14))
|
|
{
|
|
DuplicateSelectedGroups();
|
|
}
|
|
if (!undoChordActive && HasSelection && flag9 && !flag10)
|
|
{
|
|
if (!selectionGrabActive)
|
|
{
|
|
BeginSelectionGrab();
|
|
}
|
|
UpdateSelectionGrab();
|
|
}
|
|
else if (selectionGrabActive)
|
|
{
|
|
EndSelectionGrab();
|
|
}
|
|
if (!selectionInputHeld && !flag9 && !flag10 && !flag11 && !flag12 && !flag13)
|
|
{
|
|
undoChordActive = false;
|
|
suppressRightHandActionsUntilNeutral = false;
|
|
}
|
|
selectionHeld = selectionInputHeld && !undoChordActive && !suppressRightHandActionsUntilNeutral && !selectionGrabActive;
|
|
if (selectionHeld)
|
|
{
|
|
for (int i = 0; i < selectionBuffer.Count; i++)
|
|
{
|
|
VoxelGroup voxelGroup = selectionBuffer[i];
|
|
if (!(voxelGroup == null) && selectedGroups.Add(voxelGroup))
|
|
{
|
|
voxelGroup.SetSelected(selected: true);
|
|
VRHaptics.Pulse(rightHandDevice, 0.18f, 0.035f);
|
|
}
|
|
}
|
|
}
|
|
if (!undoChordActive && !suppressRightHandActionsUntilNeutral && ((flag11 && !mergeButtonPrevious) || Input.GetKeyDown(keyboardMergeKey)) && HasSelection)
|
|
{
|
|
BeginMergeSelectedGroups();
|
|
suppressRightHandActionsUntilNeutral = true;
|
|
}
|
|
if (!undoChordActive && HasSelection && flag13 && !inventoryButtonPrevious)
|
|
{
|
|
PaintSelectedGroups();
|
|
}
|
|
if (!undoChordActive && HasSelection && Input.GetKeyDown(keyboardPaintSelectionKey))
|
|
{
|
|
PaintSelectedGroups();
|
|
}
|
|
if (!undoChordActive && Input.GetKeyDown(keyboardInventoryKey))
|
|
{
|
|
SendSelectedGroupsToInventory();
|
|
}
|
|
if (!undoChordActive && Input.GetKeyDown(keyboardDeleteKey) && HasSelection)
|
|
{
|
|
DeleteSelectedGroups();
|
|
suppressRightHandActionsUntilNeutral = true;
|
|
}
|
|
mergeButtonPrevious = flag11;
|
|
duplicateButtonPrevious = flag12;
|
|
thumbstickClickPrevious = flag8;
|
|
inventoryButtonPrevious = flag13;
|
|
gripHeldPrevious = flag9;
|
|
triggerHeldPrevious = flag10;
|
|
}
|
|
|
|
public void ClearSelection()
|
|
{
|
|
ClearSelectionInternal(suppressUntilNeutral: true);
|
|
}
|
|
|
|
private void ClearSelectionInternal(bool suppressUntilNeutral)
|
|
{
|
|
cleanupBuffer.Clear();
|
|
cleanupBuffer.AddRange(selectedGroups);
|
|
for (int i = 0; i < cleanupBuffer.Count; i++)
|
|
{
|
|
VoxelGroup voxelGroup = cleanupBuffer[i];
|
|
if (voxelGroup != null)
|
|
{
|
|
voxelGroup.SetSelected(selected: false);
|
|
}
|
|
}
|
|
selectedGroups.Clear();
|
|
cleanupBuffer.Clear();
|
|
selectionHeld = false;
|
|
selectionInputHeld = false;
|
|
if (suppressUntilNeutral)
|
|
{
|
|
suppressRightHandActionsUntilNeutral = true;
|
|
}
|
|
}
|
|
|
|
private void BeginMergeSelectedGroups()
|
|
{
|
|
CleanupDestroyedSelections();
|
|
if (selectedGroups.Count >= 2)
|
|
{
|
|
selectionBuffer.Clear();
|
|
selectionBuffer.AddRange(selectedGroups);
|
|
StartCoroutine(AnimateAndMergeSelectedGroups(new List<VoxelGroup>(selectionBuffer)));
|
|
selectionBuffer.Clear();
|
|
}
|
|
}
|
|
|
|
private void DuplicateSelectedGroups()
|
|
{
|
|
CleanupDestroyedSelections();
|
|
if (selectedGroups.Count == 0)
|
|
{
|
|
return;
|
|
}
|
|
selectionBuffer.Clear();
|
|
selectionBuffer.AddRange(selectedGroups);
|
|
ClearSelectionInternal(suppressUntilNeutral: false);
|
|
for (int i = 0; i < selectionBuffer.Count; i++)
|
|
{
|
|
VoxelGroup voxelGroup = groupManager.DuplicateGroup(selectionBuffer[i], Vector3Int.zero);
|
|
if (!(voxelGroup == null))
|
|
{
|
|
voxelGroup.SetSelected(selected: true);
|
|
selectedGroups.Add(voxelGroup);
|
|
}
|
|
}
|
|
selectionBuffer.Clear();
|
|
VRHaptics.Pulse(leftHandDevice, 0.32f, 0.055f);
|
|
}
|
|
|
|
private void BeginSelectionGrab()
|
|
{
|
|
CleanupDestroyedSelections();
|
|
if (selectedGroups.Count == 0 || !TryGetGrabReferencePose(out grabStartReferencePosition, out grabStartReferenceRotation) || !TryGetSelectedBounds(out var bounds))
|
|
{
|
|
return;
|
|
}
|
|
grabbedGroupStates.Clear();
|
|
foreach (VoxelGroup selectedGroup in selectedGroups)
|
|
{
|
|
if (!(selectedGroup == null))
|
|
{
|
|
grabbedGroupStates.Add(new GrabbedGroupState
|
|
{
|
|
group = selectedGroup,
|
|
startWorldPosition = selectedGroup.transform.position,
|
|
startPackedRotation = selectedGroup.packedRotation,
|
|
colliderWasEnabled = (selectedGroup.meshCollider == null || selectedGroup.meshCollider.enabled)
|
|
});
|
|
groupManager.SetGroupColliderEnabled(selectedGroup, enabled: false);
|
|
groupManager.BroadcastMovePreview(selectedGroup);
|
|
}
|
|
}
|
|
if (grabbedGroupStates.Count != 0)
|
|
{
|
|
grabStartSelectionCenter = bounds.center;
|
|
grabStartSelectionOffset = grabStartSelectionCenter - grabStartReferencePosition;
|
|
selectionGrabActive = true;
|
|
selectionHeld = false;
|
|
selectionInputHeld = false;
|
|
}
|
|
}
|
|
|
|
private void UpdateSelectionGrab()
|
|
{
|
|
if (!selectionGrabActive || groupManager == null || !TryGetGrabReferencePose(out var worldPosition, out var worldRotation))
|
|
{
|
|
return;
|
|
}
|
|
float voxelSize = groupManager.VoxelSize;
|
|
float step = Mathf.Max(0.001f, grabPositionSnap);
|
|
Quaternion quaternion = SnapRotation(worldRotation * Quaternion.Inverse(grabStartReferenceRotation));
|
|
Vector3 vector = SnapVector(worldPosition + grabStartSelectionOffset, step);
|
|
for (int num = grabbedGroupStates.Count - 1; num >= 0; num--)
|
|
{
|
|
GrabbedGroupState grabbedGroupState = grabbedGroupStates[num];
|
|
if (grabbedGroupState == null || grabbedGroupState.group == null || !selectedGroups.Contains(grabbedGroupState.group))
|
|
{
|
|
if (grabbedGroupState != null && grabbedGroupState.group != null)
|
|
{
|
|
groupManager.SetGroupColliderEnabled(grabbedGroupState.group, grabbedGroupState.colliderWasEnabled);
|
|
groupManager.BroadcastMoveFinal(grabbedGroupState.group);
|
|
}
|
|
grabbedGroupStates.RemoveAt(num);
|
|
}
|
|
else
|
|
{
|
|
Vector3 vector2 = grabbedGroupState.startWorldPosition - grabStartSelectionCenter;
|
|
Vector3Int gridPosition = WorldToGrid(vector + quaternion * vector2, voxelSize);
|
|
byte rotation = PackSnappedRotation(quaternion * GridRotationUtility.ToQuaternion(grabbedGroupState.startPackedRotation));
|
|
if (groupManager.TransformGroup(grabbedGroupState.group, gridPosition, rotation, sendNetworkUpdate: false))
|
|
{
|
|
grabbedGroupState.group.SetSelected(selected: true);
|
|
if (Time.unscaledTime - grabbedGroupState.lastPreviewSentAt >= Mathf.Max(0.01f, movePreviewSendInterval))
|
|
{
|
|
groupManager.BroadcastMovePreview(grabbedGroupState.group);
|
|
grabbedGroupState.lastPreviewSentAt = Time.unscaledTime;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void EndSelectionGrab()
|
|
{
|
|
if (!selectionGrabActive)
|
|
{
|
|
grabbedGroupStates.Clear();
|
|
return;
|
|
}
|
|
selectionGrabActive = false;
|
|
for (int i = 0; i < grabbedGroupStates.Count; i++)
|
|
{
|
|
GrabbedGroupState grabbedGroupState = grabbedGroupStates[i];
|
|
if (grabbedGroupState != null && !(grabbedGroupState.group == null))
|
|
{
|
|
groupManager.SetGroupColliderEnabled(grabbedGroupState.group, grabbedGroupState.colliderWasEnabled);
|
|
groupManager.BroadcastMoveFinal(grabbedGroupState.group);
|
|
}
|
|
}
|
|
grabbedGroupStates.Clear();
|
|
}
|
|
|
|
private void DeleteSelectedGroups()
|
|
{
|
|
CleanupDestroyedSelections();
|
|
if (selectedGroups.Count == 0)
|
|
{
|
|
return;
|
|
}
|
|
selectionBuffer.Clear();
|
|
selectionBuffer.AddRange(selectedGroups);
|
|
ClearSelectionInternal(suppressUntilNeutral: true);
|
|
for (int i = 0; i < selectionBuffer.Count; i++)
|
|
{
|
|
VoxelGroup voxelGroup = selectionBuffer[i];
|
|
if (voxelGroup != null)
|
|
{
|
|
groupManager.RemoveGroup(voxelGroup);
|
|
}
|
|
}
|
|
selectionBuffer.Clear();
|
|
}
|
|
|
|
private void SendSelectedGroupsToInventory()
|
|
{
|
|
CleanupDestroyedSelections();
|
|
if (selectedGroups.Count != 0)
|
|
{
|
|
SendSelectedGroupsToInventoryStub(selectedGroups.Count);
|
|
}
|
|
}
|
|
|
|
public List<VoxelGroup> GetSelectedGroupsSnapshot()
|
|
{
|
|
CleanupDestroyedSelections();
|
|
if (selectedGroups.Count == 0)
|
|
{
|
|
return new List<VoxelGroup>();
|
|
}
|
|
List<VoxelGroup> list = new List<VoxelGroup>(selectedGroups.Count);
|
|
foreach (VoxelGroup selectedGroup in selectedGroups)
|
|
{
|
|
if (selectedGroup != null)
|
|
{
|
|
list.Add(selectedGroup);
|
|
}
|
|
}
|
|
return list;
|
|
}
|
|
|
|
private void PaintSelectedGroups()
|
|
{
|
|
CleanupDestroyedSelections();
|
|
if (selectedGroups.Count == 0)
|
|
{
|
|
return;
|
|
}
|
|
VRVoxelBrush instance = VRVoxelBrush.Instance;
|
|
byte voxelValue = ((instance != null) ? instance.GetSelectedVoxelValue() : PackedVoxel.Create(16, VoxelMaterial.Lit));
|
|
selectionBuffer.Clear();
|
|
selectionBuffer.AddRange(selectedGroups);
|
|
for (int i = 0; i < selectionBuffer.Count; i++)
|
|
{
|
|
VoxelGroup voxelGroup = selectionBuffer[i];
|
|
if (voxelGroup != null && groupManager.PaintWholeGroup(voxelGroup, voxelValue))
|
|
{
|
|
VRHaptics.Pulse(rightHandDevice, 0.32f, 0.055f);
|
|
}
|
|
}
|
|
selectionBuffer.Clear();
|
|
}
|
|
|
|
private void CleanupDestroyedSelections()
|
|
{
|
|
cleanupBuffer.Clear();
|
|
foreach (VoxelGroup selectedGroup in selectedGroups)
|
|
{
|
|
if (selectedGroup == null)
|
|
{
|
|
cleanupBuffer.Add(selectedGroup);
|
|
}
|
|
}
|
|
for (int i = 0; i < cleanupBuffer.Count; i++)
|
|
{
|
|
selectedGroups.Remove(cleanupBuffer[i]);
|
|
}
|
|
cleanupBuffer.Clear();
|
|
}
|
|
|
|
private bool TryGetSelectorPosition(out Vector3 worldPosition)
|
|
{
|
|
VRVoxelBrush instance = VRVoxelBrush.Instance;
|
|
if (instance != null && !instance.UsingXRInput && instance.TryGetDesktopSelectionPoint(out worldPosition))
|
|
{
|
|
return true;
|
|
}
|
|
if (selectorOrigin != null)
|
|
{
|
|
worldPosition = selectorOrigin.position;
|
|
return true;
|
|
}
|
|
if (rightHandDevice.isValid && rightHandDevice.TryGetFeatureValue(CommonUsages.devicePosition, out var value) && rightHandDevice.TryGetFeatureValue(CommonUsages.deviceRotation, out var value2))
|
|
{
|
|
if (trackingOrigin != null)
|
|
{
|
|
worldPosition = trackingOrigin.TransformPoint(value);
|
|
_ = trackingOrigin.rotation * value2;
|
|
}
|
|
else
|
|
{
|
|
worldPosition = value;
|
|
}
|
|
return true;
|
|
}
|
|
worldPosition = base.transform.position;
|
|
return true;
|
|
}
|
|
|
|
private bool TryGetGrabReferencePose(out Vector3 worldPosition, out Quaternion worldRotation)
|
|
{
|
|
VRVoxelBrush instance = VRVoxelBrush.Instance;
|
|
if (instance != null && !instance.UsingXRInput)
|
|
{
|
|
Camera main = Camera.main;
|
|
if (main != null)
|
|
{
|
|
worldPosition = main.transform.position;
|
|
worldRotation = main.transform.rotation;
|
|
return true;
|
|
}
|
|
}
|
|
if (selectorOrigin != null)
|
|
{
|
|
worldPosition = selectorOrigin.position;
|
|
worldRotation = selectorOrigin.rotation;
|
|
return true;
|
|
}
|
|
if (rightHandDevice.isValid && rightHandDevice.TryGetFeatureValue(CommonUsages.devicePosition, out var value) && rightHandDevice.TryGetFeatureValue(CommonUsages.deviceRotation, out var value2))
|
|
{
|
|
if (trackingOrigin != null)
|
|
{
|
|
worldPosition = trackingOrigin.TransformPoint(value);
|
|
worldRotation = trackingOrigin.rotation * value2;
|
|
}
|
|
else
|
|
{
|
|
worldPosition = value;
|
|
worldRotation = value2;
|
|
}
|
|
return true;
|
|
}
|
|
worldPosition = base.transform.position;
|
|
worldRotation = base.transform.rotation;
|
|
return true;
|
|
}
|
|
|
|
private bool TryGetSelectedBounds(out Bounds bounds)
|
|
{
|
|
CleanupDestroyedSelections();
|
|
bounds = default(Bounds);
|
|
bool flag = false;
|
|
foreach (VoxelGroup selectedGroup in selectedGroups)
|
|
{
|
|
if (!(selectedGroup == null))
|
|
{
|
|
if (!flag)
|
|
{
|
|
bounds = selectedGroup.GetWorldBounds();
|
|
flag = true;
|
|
}
|
|
else
|
|
{
|
|
bounds.Encapsulate(selectedGroup.GetWorldBounds());
|
|
}
|
|
}
|
|
}
|
|
return flag;
|
|
}
|
|
|
|
private Vector3 SnapVector(Vector3 value, float step)
|
|
{
|
|
return new Vector3(Mathf.Round(value.x / step) * step, Mathf.Round(value.y / step) * step, Mathf.Round(value.z / step) * step);
|
|
}
|
|
|
|
private Quaternion SnapRotation(Quaternion rotation)
|
|
{
|
|
Vector3 eulerAngles = rotation.eulerAngles;
|
|
return Quaternion.Euler(SnapAngle(eulerAngles.x), SnapAngle(eulerAngles.y), SnapAngle(eulerAngles.z));
|
|
}
|
|
|
|
private float SnapAngle(float angle)
|
|
{
|
|
return Mathf.Round(NormalizeSignedAngle(angle) / grabRotationSnapDegrees) * grabRotationSnapDegrees;
|
|
}
|
|
|
|
private static float NormalizeSignedAngle(float angle)
|
|
{
|
|
angle %= 360f;
|
|
if (angle > 180f)
|
|
{
|
|
angle -= 360f;
|
|
}
|
|
else if (angle < -180f)
|
|
{
|
|
angle += 360f;
|
|
}
|
|
return angle;
|
|
}
|
|
|
|
private static Vector3Int WorldToGrid(Vector3 worldPosition, float voxelSize)
|
|
{
|
|
return new Vector3Int(Mathf.RoundToInt(worldPosition.x / voxelSize), Mathf.RoundToInt(worldPosition.y / voxelSize), Mathf.RoundToInt(worldPosition.z / voxelSize));
|
|
}
|
|
|
|
private static byte PackSnappedRotation(Quaternion rotation)
|
|
{
|
|
Vector3 eulerAngles = rotation.eulerAngles;
|
|
return GridRotationUtility.Pack(new Vector3Int(AngleToQuarterTurns(eulerAngles.x), AngleToQuarterTurns(eulerAngles.y), AngleToQuarterTurns(eulerAngles.z)));
|
|
}
|
|
|
|
private static int AngleToQuarterTurns(float angle)
|
|
{
|
|
int num = Mathf.RoundToInt(NormalizeSignedAngle(angle) / 90f) % 4;
|
|
if (num >= 0)
|
|
{
|
|
return num;
|
|
}
|
|
return num + 4;
|
|
}
|
|
|
|
private static bool ReadBool(InputDevice device, InputFeatureUsage<bool> usage)
|
|
{
|
|
bool value = default(bool);
|
|
return device.isValid && device.TryGetFeatureValue(usage, out value) && value;
|
|
}
|
|
|
|
private bool ReadGrabHeld(InputDevice device)
|
|
{
|
|
if (!device.isValid)
|
|
{
|
|
return false;
|
|
}
|
|
bool value;
|
|
bool num = device.TryGetFeatureValue(CommonUsages.gripButton, out value) && value;
|
|
bool flag = ReadFloat(device, CommonUsages.grip) >= gripGrabThreshold;
|
|
return num || flag;
|
|
}
|
|
|
|
private static float ReadFloat(InputDevice device, InputFeatureUsage<float> usage)
|
|
{
|
|
if (!device.isValid || !device.TryGetFeatureValue(usage, out var value))
|
|
{
|
|
return 0f;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
private IEnumerator AnimateAndMergeSelectedGroups(List<VoxelGroup> groupsToMerge)
|
|
{
|
|
groupingAnimationActive = true;
|
|
float num = Mathf.Max(((groupManager != null) ? groupManager.VoxelSize : 0.125f) * 0.9f, 0.12f);
|
|
List<GroupMergeAnimationState> states = new List<GroupMergeAnimationState>(groupsToMerge.Count);
|
|
Vector3 mergeCenter = Vector3.zero;
|
|
int num2 = 0;
|
|
for (int i = 0; i < groupsToMerge.Count; i++)
|
|
{
|
|
VoxelGroup voxelGroup = groupsToMerge[i];
|
|
if (!(voxelGroup == null))
|
|
{
|
|
mergeCenter += voxelGroup.GetWorldBounds().center;
|
|
num2++;
|
|
}
|
|
}
|
|
if (num2 < 2)
|
|
{
|
|
groupingAnimationActive = false;
|
|
yield break;
|
|
}
|
|
mergeCenter /= (float)num2;
|
|
PlayGroupingClip(groupingClickClip, mergeCenter, 0.85f);
|
|
VRHaptics.Pulse(rightHandDevice, 0.18f, 0.035f);
|
|
VRHaptics.Pulse(rightHandDevice, 0.65f, 0.11f);
|
|
for (int j = 0; j < groupsToMerge.Count; j++)
|
|
{
|
|
VoxelGroup voxelGroup2 = groupsToMerge[j];
|
|
if (!(voxelGroup2 == null))
|
|
{
|
|
Vector3 center = voxelGroup2.GetWorldBounds().center;
|
|
Vector3 vector = center - mergeCenter;
|
|
if (vector.sqrMagnitude < 0.0001f)
|
|
{
|
|
float f = MathF.PI * 2f * (float)j / (float)Mathf.Max(1, num2);
|
|
vector = new Vector3(Mathf.Cos(f), 0f, Mathf.Sin(f));
|
|
}
|
|
vector.Normalize();
|
|
GroupMergeAnimationState item = new GroupMergeAnimationState
|
|
{
|
|
group = voxelGroup2,
|
|
startTransformPosition = voxelGroup2.transform.position,
|
|
startBoundsCenter = center,
|
|
burstStartCenter = center,
|
|
burstPeakCenter = center + vector * num
|
|
};
|
|
states.Add(item);
|
|
}
|
|
}
|
|
float elapsed = 0f;
|
|
while (elapsed < 0.18f)
|
|
{
|
|
elapsed += Time.unscaledDeltaTime;
|
|
float num3 = Mathf.Clamp01(elapsed / 0.18f);
|
|
for (int k = 0; k < states.Count; k++)
|
|
{
|
|
GroupMergeAnimationState state = states[k];
|
|
if (!(state.group == null))
|
|
{
|
|
float t = 1f - Mathf.Pow(1f - num3, 2f);
|
|
Vector3 animatedCenter = Vector3.LerpUnclamped(state.burstStartCenter, state.burstPeakCenter, t);
|
|
SetAnimatedGroupCenter(state, animatedCenter);
|
|
}
|
|
}
|
|
yield return null;
|
|
}
|
|
elapsed = 0f;
|
|
while (elapsed < 0.17f)
|
|
{
|
|
elapsed += Time.unscaledDeltaTime;
|
|
float num4 = Mathf.Clamp01(elapsed / 0.17f);
|
|
for (int l = 0; l < states.Count; l++)
|
|
{
|
|
GroupMergeAnimationState state2 = states[l];
|
|
if (!(state2.group == null))
|
|
{
|
|
float t2 = num4 * num4 * (3f - 2f * num4);
|
|
Vector3 animatedCenter2 = Vector3.LerpUnclamped(state2.burstPeakCenter, state2.startBoundsCenter, t2);
|
|
SetAnimatedGroupCenter(state2, animatedCenter2);
|
|
}
|
|
}
|
|
yield return null;
|
|
}
|
|
for (int m = 0; m < states.Count; m++)
|
|
{
|
|
GroupMergeAnimationState groupMergeAnimationState = states[m];
|
|
if (!(groupMergeAnimationState.group == null))
|
|
{
|
|
groupMergeAnimationState.group.transform.position = groupMergeAnimationState.startTransformPosition;
|
|
}
|
|
}
|
|
selectionBuffer.Clear();
|
|
for (int n = 0; n < states.Count; n++)
|
|
{
|
|
if (states[n].group != null)
|
|
{
|
|
selectionBuffer.Add(states[n].group);
|
|
}
|
|
}
|
|
ClearSelectionInternal(suppressUntilNeutral: true);
|
|
VoxelGroup mergedGroup = groupManager.MergeGroups(selectionBuffer);
|
|
selectionBuffer.Clear();
|
|
PlayGroupingClip(groupingThockClip, mergeCenter, 1f);
|
|
VRHaptics.Pulse(rightHandDevice, 0.65f, 0.11f);
|
|
if (mergedGroup != null)
|
|
{
|
|
float mergedVoxelSize = ((groupManager != null) ? groupManager.VoxelSize : 0.125f);
|
|
Vector3 mergedCenter = mergedGroup.GetWorldBounds().center;
|
|
Quaternion mergedRotation = mergedGroup.transform.rotation;
|
|
Vector3 mergedLocalBoundsCenter = new Vector3(mergedGroup.size.x, mergedGroup.size.y, mergedGroup.size.z) * (mergedVoxelSize * 0.5f);
|
|
float impactElapsed = 0f;
|
|
while (impactElapsed < 0.05f)
|
|
{
|
|
impactElapsed += Time.unscaledDeltaTime;
|
|
float num5 = Mathf.Clamp01(impactElapsed / 0.05f);
|
|
float uniformScale = ((!(num5 < 0.5f)) ? Mathf.LerpUnclamped(1.05f, 1f, (num5 - 0.5f) / 0.5f) : Mathf.LerpUnclamped(0.95f, 1.05f, num5 / 0.5f));
|
|
ApplyCenteredGroupScale(mergedGroup, mergedCenter, mergedRotation, mergedLocalBoundsCenter, uniformScale);
|
|
yield return null;
|
|
}
|
|
ResetMergedGroupTransform(mergedGroup, mergedVoxelSize);
|
|
}
|
|
groupingAnimationActive = false;
|
|
}
|
|
|
|
private void SetAnimatedGroupCenter(GroupMergeAnimationState state, Vector3 animatedCenter)
|
|
{
|
|
if (!(state.group == null))
|
|
{
|
|
Vector3 vector = animatedCenter - state.startBoundsCenter;
|
|
state.group.transform.position = state.startTransformPosition + vector;
|
|
}
|
|
}
|
|
|
|
private static void ApplyCenteredGroupScale(VoxelGroup group, Vector3 worldCenter, Quaternion rotation, Vector3 localBoundsCenter, float uniformScale)
|
|
{
|
|
if (!(group == null))
|
|
{
|
|
Vector3 vector = localBoundsCenter * uniformScale;
|
|
Vector3 position = worldCenter - rotation * vector;
|
|
group.transform.SetPositionAndRotation(position, rotation);
|
|
group.transform.localScale = Vector3.one * uniformScale;
|
|
}
|
|
}
|
|
|
|
private static void ResetMergedGroupTransform(VoxelGroup group, float voxelSize)
|
|
{
|
|
if (!(group == null))
|
|
{
|
|
group.transform.localScale = Vector3.one;
|
|
group.transform.SetPositionAndRotation((Vector3)group.position * voxelSize, GridRotationUtility.ToQuaternion(group.packedRotation));
|
|
}
|
|
}
|
|
|
|
private void PlayGroupingClip(AudioClip clip, Vector3 position, float volume)
|
|
{
|
|
if (!(clip == null))
|
|
{
|
|
AudioSource.PlayClipAtPoint(clip, position, volume);
|
|
}
|
|
}
|
|
|
|
private void EnsureGroupingClipsAssigned()
|
|
{
|
|
if (groupingClickClip == null)
|
|
{
|
|
groupingClickClip = Resources.Load<AudioClip>("Click");
|
|
}
|
|
if (groupingThockClip == null)
|
|
{
|
|
groupingThockClip = Resources.Load<AudioClip>("Thock");
|
|
}
|
|
}
|
|
|
|
private void SendSelectedGroupsToInventoryStub(int selectedGroupCount)
|
|
{
|
|
if (selectedGroupCount > 0)
|
|
{
|
|
Debug.Log($"BlockSpace inventory send stub for {selectedGroupCount} selected groups.", this);
|
|
}
|
|
}
|
|
}
|
|
}
|