Files
BeatSaber/Assets/VRBeatsKit/Modules/VRSDK/VR/VR_TwoHandGrabbable.cs
T
whdwo798 4dad9e5d5b feat: SongCreator 씬 완성 — Beat Sage URL 지원, info.dat 메타데이터 자동 추출
- BeatSageUploader: audio_url 지원(UploadFromUrl), PollAndDownload 공통화, ZIP 500 오류 3회 재시도
- BeatSageConverter: info.dat 파싱(SongMetadata), BPM 자동 감지 → 노트 타이밍 변환에 적용
- SongCreatorManager: title/BPM 필수 입력 제거, 난이도 4개 자동 선택, GenerateFlowFromUrl 버그 수정
- NasPublisher: audioPath null 허용(URL 흐름에서 로컬 파일 없는 경우 스킵)
- .gitignore/.gitattributes 초기 설정

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 23:37:34 +09:00

238 lines
8.5 KiB
C#

using UnityEngine;
using UnityEngine.Events;
namespace VRSDK
{
public class VR_TwoHandGrabbable : MonoBehaviour
{
[SerializeField] private VR_Grabbable dominantGrabbable = null;
[SerializeField] private VR_Grabbable secondaryGrabbable = null;
[SerializeField] private UnityEvent onTwoHandGrabStart = null;
[SerializeField] private Vector3 rightHandRotOffset = Vector3.zero;
[SerializeField] private Vector3 leftHandRotOffset = Vector3.zero;
[SerializeField] private UnityEvent onTwoHandGrabEnd = null;
private VR_Controller secondaryController = null;
private Quaternion desireRotation = Quaternion.identity;
private Vector3 dir = Vector3.zero;
private bool applyTwoHandTransformManually = false;
public UnityEvent OnTwoHandGrabStart { get { return onTwoHandGrabStart; } }
public UnityEvent OnTwoHandGrabEnd { get { return onTwoHandGrabEnd; } }
private Quaternion HandRotOffset
{
get
{
if (dominantGrabbable.GrabController.ControllerType == VR_ControllerType.Right)
return Quaternion.Euler( rightHandRotOffset );
return Quaternion.Euler( leftHandRotOffset );
}
}
private void Awake()
{
//disable secondary grabbable while dominat grabbable is dropped
secondaryGrabbable.enabled = false;
dominantGrabbable.OnGrabStateChange.AddListener( OnDominantGrabbableGrabStateChange );
secondaryGrabbable.OnGrabStateChange.AddListener( OnSecondaryGrabbableGrabStateChange );
//prevent the VR_Grabbable for making changes in the object, so we can control it
secondaryGrabbable.PreventDefault();
}
private void Update()
{
if (CanUpdate())
return;
//set the desire rotation to the dominanthand
dominantGrabbable.GrabController.transform.rotation = desireRotation;
}
private void LateUpdate()
{
if (!applyTwoHandTransformManually)
{
TwoHandUpdate();
}
}
public void TwoHandUpdate()
{
if (CanUpdate())
{
//for some reason the secondary controller should be dropped now
if (SecondaryGrabbableShouldBeDropped())
{
ReleaseSecondaryGrabbable();
}
//dominantGrabbable.IsUsingTwoHandGrabbable = false;
return;
}
//dominantGrabbable.IsUsingTwoHandGrabbable = true;
desireRotation = CalculateDesireRotation();
/*
if (dominantGrabbable.GrabMode == GrabMode.Physics)
{
dominantGrabbable.TwoHandGrabbableDesireRotation = desireRotation;
}*/
if (dominantGrabbable.GrabController.transform.parent == null)
{
return;
}
Quaternion rotOffset = dominantGrabbable.GrabController.RotationOffset;
if (rotOffset.eulerAngles.magnitude > 0.001f)
{
desireRotation = desireRotation * dominantGrabbable.RotationOffset;
}
//desireRotation = desireRotation * Quaternion.Euler( dominantGrabbable.GetCurrentHandInteractSettings().rotationOffset);
//set the desire rotation to the dominanthand
dominantGrabbable.GrabController.transform.rotation = desireRotation;
dominantGrabbable.GrabController.transform.localPosition = dominantGrabbable.GrabController.InitialPosition + ( dominantGrabbable.GrabController.transform.parent.InverseTransformDirection( dir * -1.0f ) * ( dominantGrabbable.GrabController.PositionOffset.magnitude ) );
}
private bool CanUpdate()
{
return dominantGrabbable.CurrentGrabState != GrabState.Grab || secondaryGrabbable.CurrentGrabState != GrabState.Grab;
}
private Quaternion CalculateDesireRotation()
{
dir = ( secondaryGrabbable.GrabController.OriginalParent.transform.position - GetDominatGrabbableGrabPosition() ).normalized;
Vector3 customDir = dominantGrabbable.GrabController.OriginalParent.forward;
float angle = Vector2.Angle( new Vector2( dir.x, dir.z ), new Vector2( customDir.x, customDir.z ) );
Quaternion result = Quaternion.LookRotation( dir ) * Quaternion.Euler( 0.0f, 0.0f, dominantGrabbable.GrabController.OriginalParent.localEulerAngles.z * ( angle > 90.0f && angle < 180.0f ? -1.0f : 1.0f ) );
return result * HandRotOffset;
}
private bool SecondaryGrabbableShouldBeDropped()
{
return dominantGrabbable.CurrentGrabState == GrabState.UnGrab && secondaryGrabbable.CurrentGrabState == GrabState.Grab;
}
private bool ShouldCalculateRotationFromOriginalPosition()
{
return false;
}
private Vector3 GetDominatGrabbableGrabPosition()
{
return ShouldCalculateRotationFromOriginalPosition() ? dominantGrabbable.GrabController.OriginalParent.position : dominantGrabbable.GrabController.transform.position;
}
private void OnDominantGrabbableGrabStateChange(GrabState state)
{
if (state == GrabState.Drop)
{
if (secondaryGrabbable.CurrentGrabState == GrabState.Grab || secondaryGrabbable.CurrentGrabState == GrabState.Drop)
{
ReleaseSecondaryGrabbable();
}
secondaryGrabbable.enabled = false;
}
else if (state == GrabState.Grab)
{
secondaryGrabbable.enabled = true;
}
}
private void ReleaseSecondaryGrabbable()
{
if (secondaryController == null)
{
secondaryGrabbable.ForceDrop();
if(OnTwoHandGrabEnd != null)
OnTwoHandGrabEnd.Invoke();
return;
}
secondaryGrabbable.ForceDrop();
secondaryController.SetVisibility( true );
//dominantGrabbable.GrabController.UsePositionOffset = true;
secondaryController.SetPositionControlMode( MotionControlMode.Engine );
secondaryController.SetRotationControlMode( MotionControlMode.Engine );
if(OnTwoHandGrabEnd != null)
OnTwoHandGrabEnd.Invoke();
}
private void OnSecondaryGrabbableGrabStateChange( GrabState state)
{
if (state == GrabState.Grab)
{
dominantGrabbable.GrabController.UsePositionOffset = false;
secondaryController = secondaryGrabbable.GrabController;
Transform interactPoint = secondaryGrabbable.GetCurrentHandInteractSettings().interactPoint;
secondaryController.SetVisibility( !secondaryGrabbable.GetCurrentHandAnimationSettings().hideHandOnGrab );
secondaryController.SetPositionControlMode( MotionControlMode.Free );
secondaryController.SetRotationControlMode( MotionControlMode.Free );
secondaryController.transform.parent = interactPoint;
secondaryController.transform.localPosition = Vector3.zero;
secondaryController.transform.rotation = interactPoint.rotation * Quaternion.Euler( secondaryGrabbable.GetCurrentHandInteractSettings().rotationOffset );
if (OnTwoHandGrabStart != null)
OnTwoHandGrabStart.Invoke();
}
else if(state == GrabState.Drop && dominantGrabbable.CurrentGrabState == GrabState.Grab)
{
dominantGrabbable.GrabController.UsePositionOffset = true;
secondaryController.SetPositionControlMode( MotionControlMode.Engine );
secondaryController.SetRotationControlMode( MotionControlMode.Engine );
secondaryController.SetVisibility( true);
if (OnTwoHandGrabEnd != null)
OnTwoHandGrabEnd.Invoke();
}
}
public void SetApplyTwoHandTransformManually(bool value)
{
applyTwoHandTransformManually = value;
}
}
}