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 selectedGroups = new HashSet(); private readonly List selectionBuffer = new List(); private readonly List cleanupBuffer = new List(); private readonly List grabbedGroupStates = new List(); 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()); } 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(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 GetSelectedGroupsSnapshot() { CleanupDestroyedSelections(); if (selectedGroups.Count == 0) { return new List(); } List list = new List(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 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 usage) { if (!device.isValid || !device.TryGetFeatureValue(usage, out var value)) { return 0f; } return value; } private IEnumerator AnimateAndMergeSelectedGroups(List groupsToMerge) { groupingAnimationActive = true; float num = Mathf.Max(((groupManager != null) ? groupManager.VoxelSize : 0.125f) * 0.9f, 0.12f); List states = new List(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("Click"); } if (groupingThockClip == null) { groupingThockClip = Resources.Load("Thock"); } } private void SendSelectedGroupsToInventoryStub(int selectedGroupCount) { if (selectedGroupCount > 0) { Debug.Log($"BlockSpace inventory send stub for {selectedGroupCount} selected groups.", this); } } } }