Files
planet/ue_client/Source/PlanetClient/PlanetPlayerController.cpp
linkong 1e6f4b338b feat: add UE5 LED display client — backend API, C++ source, stereo framework
Backend:
- Add /api/v1/ue/* endpoints (compute-points, cables, landing-points, satellites, status)
  returning flat JSON optimised for UE5 C++ parsing

UE5 client (ue_client/):
- PlanetDataManager: HTTP fetch + local mock JSON loader, spawns ComputePointActors
- ComputePointActor / InteractiveObjectBase: hover/select state, material switching
- GlobeInteractionComponent: drag-to-rotate via CesiumGeoreference origin shift, inertia, zoom
- StereoRenderingManager: runtime SbS/TbB stereo toggle, IPD control (format TBD)
- MotionCaptureInterface: protocol-agnostic gesture/rotate/zoom delegate interface (impl TBD)
- PlanetPlayerController: unified mouse + motion-capture input routing
- PlanetGameMode, Build.cs, Config, mock data

Docs:
- ue5_mvp_fused_plan.md updated to v3.0 for LED display context
- ue_client_setup_guide.md: step-by-step editor setup guide
- ue_todo.md: pending items blocked on vendor answers (stereo format + mocap protocol)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 18:39:05 +08:00

247 lines
7.6 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "PlanetPlayerController.h"
#include "GlobeInteractionComponent.h"
#include "InteractiveObjectBase.h"
#include "Engine/World.h"
#include "GameFramework/Pawn.h"
APlanetPlayerController::APlanetPlayerController()
{
bShowMouseCursor = true;
bEnableClickEvents = true;
bEnableMouseOverEvents = true;
PrimaryActorTick.bCanEverTick = true;
GlobeInteraction = CreateDefaultSubobject<UGlobeInteractionComponent>(
TEXT("GlobeInteraction"));
}
void APlanetPlayerController::BeginPlay()
{
Super::BeginPlay();
SetInputMode(FInputModeGameAndUI());
// TODO供应商回复后取消注释:
// BindMotionCaptureEvents();
}
// ---------------------------------------------------------------------------
// 输入绑定
// ---------------------------------------------------------------------------
void APlanetPlayerController::SetupInputComponent()
{
Super::SetupInputComponent();
InputComponent->BindAction("SelectPoint", IE_Pressed, this,
&APlanetPlayerController::OnLeftMouseDown);
InputComponent->BindAction("SelectPoint", IE_Released, this,
&APlanetPlayerController::OnLeftMouseUp);
InputComponent->BindAction("Escape", IE_Pressed, this,
&APlanetPlayerController::OnEscape);
InputComponent->BindAxis("RotateYaw", this, &APlanetPlayerController::OnMouseX);
InputComponent->BindAxis("RotatePitch", this, &APlanetPlayerController::OnMouseY);
InputComponent->BindAxis("ZoomCamera", this, &APlanetPlayerController::OnZoom);
}
// ---------------------------------------------------------------------------
// Tick悬停检测
// ---------------------------------------------------------------------------
void APlanetPlayerController::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
if (!bIsDragging) UpdateHover();
}
// ---------------------------------------------------------------------------
// 鼠标输入
// ---------------------------------------------------------------------------
void APlanetPlayerController::OnLeftMouseDown()
{
bLeftMouseDown = true;
bIsDragging = false;
float X, Y;
GetMousePosition(X, Y);
MouseDownPosition = FVector2D(X, Y);
// 停止惯性,准备接管控制
if (GlobeInteraction) GlobeInteraction->StopInertia();
}
void APlanetPlayerController::OnLeftMouseUp()
{
if (!bIsDragging)
{
// 没有拖拽 → 判定为点击
AInteractiveObjectBase* Clicked = GetObjectUnderCursor();
if (Clicked && Clicked != SelectedObject)
{
// 取消上一个选中
DeselectCurrent();
// 选中新物件
SelectedObject = Clicked;
SelectedObject->SetInteractiveState(EInteractiveObjectState::Selected);
OnObjectSelected.Broadcast(SelectedObject);
}
else if (!Clicked)
{
// 点了空白处 → 取消选中
DeselectCurrent();
}
else
{
// 点了已选中的物件 → 取消选中
DeselectCurrent();
}
}
bLeftMouseDown = false;
bIsDragging = false;
}
void APlanetPlayerController::OnMouseX(float Value)
{
if (!bLeftMouseDown || FMath::IsNearlyZero(Value)) return;
// 判断是否超过拖拽阈值
if (!bIsDragging)
{
float X, Y;
GetMousePosition(X, Y);
float Dist = FVector2D::Distance(FVector2D(X, Y), MouseDownPosition);
if (Dist < DragThresholdPixels) return;
bIsDragging = true;
}
if (GlobeInteraction) GlobeInteraction->RotateGlobe(Value * MouseDragSensitivity, 0.f);
}
void APlanetPlayerController::OnMouseY(float Value)
{
if (!bLeftMouseDown || FMath::IsNearlyZero(Value)) return;
if (!bIsDragging) return;
if (GlobeInteraction) GlobeInteraction->RotateGlobe(0.f, Value * MouseDragSensitivity);
}
void APlanetPlayerController::OnZoom(float Value)
{
if (FMath::IsNearlyZero(Value)) return;
if (GlobeInteraction) GlobeInteraction->ZoomGlobe(Value);
}
void APlanetPlayerController::OnEscape()
{
DeselectCurrent();
}
// ---------------------------------------------------------------------------
// 悬停
// ---------------------------------------------------------------------------
void APlanetPlayerController::UpdateHover()
{
AInteractiveObjectBase* UnderCursor = GetObjectUnderCursor();
if (HoveredObject && HoveredObject != UnderCursor && HoveredObject != SelectedObject)
{
HoveredObject->SetInteractiveState(EInteractiveObjectState::Normal);
}
HoveredObject = UnderCursor;
if (HoveredObject && HoveredObject != SelectedObject)
{
HoveredObject->SetInteractiveState(EInteractiveObjectState::Hovered);
}
}
// ---------------------------------------------------------------------------
// 取消选中
// ---------------------------------------------------------------------------
void APlanetPlayerController::DeselectCurrent()
{
if (!SelectedObject) return;
SelectedObject->SetInteractiveState(EInteractiveObjectState::Normal);
SelectedObject = nullptr;
OnObjectDeselected.Broadcast();
}
// ---------------------------------------------------------------------------
// 射线检测
// ---------------------------------------------------------------------------
AInteractiveObjectBase* APlanetPlayerController::GetObjectUnderCursor() const
{
FHitResult Hit;
bool bHit = GetHitResultUnderCursorByChannel(
UEngineTypes::ConvertToTraceType(ECC_Visibility), true, Hit);
if (!bHit) return nullptr;
return Cast<AInteractiveObjectBase>(Hit.GetActor());
}
// ---------------------------------------------------------------------------
// 动捕事件TODO: 供应商回复后实现)
// ---------------------------------------------------------------------------
void APlanetPlayerController::BindMotionCaptureEvents()
{
if (!MotionCaptureReceiver)
{
UE_LOG(LogTemp, Warning,
TEXT("PlayerController: MotionCaptureReceiver 未设置,动捕功能禁用"));
return;
}
MotionCaptureReceiver->OnGestureDetected.AddDynamic(
this, &APlanetPlayerController::OnMotionGesture);
MotionCaptureReceiver->OnRotateDelta.AddDynamic(
this, &APlanetPlayerController::OnMotionRotate);
MotionCaptureReceiver->OnZoomDelta.AddDynamic(
this, &APlanetPlayerController::OnMotionZoom);
MotionCaptureReceiver->Connect();
UE_LOG(LogTemp, Log, TEXT("PlayerController: 动捕事件已绑定"));
}
void APlanetPlayerController::OnMotionGesture(EMotionGesture Gesture)
{
if (Gesture == MotionActionMapping.SelectPoint)
{
// 手势确认 → 等同于点击当前悬停的物件
if (HoveredObject)
{
DeselectCurrent();
SelectedObject = HoveredObject;
SelectedObject->SetInteractiveState(EInteractiveObjectState::Selected);
OnObjectSelected.Broadcast(SelectedObject);
}
}
else if (Gesture == MotionActionMapping.DismissInfoCard)
{
DeselectCurrent();
}
else if (Gesture == MotionActionMapping.ZoomIn)
{
if (GlobeInteraction) GlobeInteraction->ZoomGlobe(1.f);
}
else if (Gesture == MotionActionMapping.ZoomOut)
{
if (GlobeInteraction) GlobeInteraction->ZoomGlobe(-1.f);
}
}
void APlanetPlayerController::OnMotionRotate(float DeltaYaw, float DeltaPitch)
{
if (GlobeInteraction) GlobeInteraction->RotateGlobe(DeltaYaw, DeltaPitch);
}
void APlanetPlayerController::OnMotionZoom(float DeltaScale)
{
if (GlobeInteraction) GlobeInteraction->ZoomGlobe(DeltaScale);
}