Add twisty Bound 4 visualization packet
This commit is contained in:
parent
dc621d97e4
commit
1f20628b7a
4 changed files with 721 additions and 1 deletions
|
|
@ -308,6 +308,104 @@ namespace HyperTwistPuzzleViewerComponentInternal
|
|||
: 0;
|
||||
}
|
||||
|
||||
EHyperTwistViewerVisualizationMode ResolvePreferredVisualizationMode(
|
||||
const EHyperTwistViewerVisualizationPreference Preference
|
||||
)
|
||||
{
|
||||
return Preference == EHyperTwistViewerVisualizationPreference::Prefer3D
|
||||
? EHyperTwistViewerVisualizationMode::ThreeD
|
||||
: EHyperTwistViewerVisualizationMode::TwoD;
|
||||
}
|
||||
|
||||
EHyperTwistViewerPresentationMode ResolvePresentationMode(
|
||||
const EHyperTwistViewerPresentationMode RequestedMode,
|
||||
const bool bAllowExpandedView,
|
||||
const bool bAllowFullscreen
|
||||
)
|
||||
{
|
||||
switch (RequestedMode)
|
||||
{
|
||||
case EHyperTwistViewerPresentationMode::Fullscreen:
|
||||
if (bAllowFullscreen)
|
||||
{
|
||||
return EHyperTwistViewerPresentationMode::Fullscreen;
|
||||
}
|
||||
if (bAllowExpandedView)
|
||||
{
|
||||
return EHyperTwistViewerPresentationMode::Expanded;
|
||||
}
|
||||
return EHyperTwistViewerPresentationMode::Inline;
|
||||
|
||||
case EHyperTwistViewerPresentationMode::Expanded:
|
||||
return bAllowExpandedView
|
||||
? EHyperTwistViewerPresentationMode::Expanded
|
||||
: EHyperTwistViewerPresentationMode::Inline;
|
||||
|
||||
default:
|
||||
return EHyperTwistViewerPresentationMode::Inline;
|
||||
}
|
||||
}
|
||||
|
||||
bool SupportsThreeDVisualization(
|
||||
const FHyperTwistViewerEmbeddedHostBootstrapState& BootstrapState,
|
||||
const FHyperTwistTrainingViewerEmbedContract& EmbedContract,
|
||||
const FHyperTwistTrainingSpatialUiContract& UiContract,
|
||||
const FHyperTwistTrainingSpatialEffectBoundary& EffectBoundary,
|
||||
const FHyperTwistTrainingSpatialStateMotionContract& StateMotionContract
|
||||
)
|
||||
{
|
||||
return BootstrapState.bReadyForEmbed
|
||||
&& BootstrapState.BindingMode == EHyperTwistViewerEmbeddedHostBindingMode::ScissoredView
|
||||
&& BootstrapState.RenderStateProfile == TEXT("classic-replay")
|
||||
&& BootstrapState.SpatialEventSurfaceTags.Contains(TEXT("scissored view embedding"))
|
||||
&& BootstrapState.SpatialEventSurfaceTags.Contains(TEXT("HTML overlay bridge"))
|
||||
&& EmbedContract.ShippableBehaviorTags.Contains(TEXT("orbit-controls"))
|
||||
&& UiContract.StateSurfaceTags.Contains(TEXT("HTML/spatial overlay bridge"))
|
||||
&& EffectBoundary.EffectFamilies.Contains(TEXT("tone mapping and highlight effects"))
|
||||
&& StateMotionContract.MotionKinds.Contains(TEXT("reduced-motion support"));
|
||||
}
|
||||
|
||||
EHyperTwistViewerVisualizationFallbackReason ResolveThreeDFallbackReason(
|
||||
const FHyperTwistViewerEmbeddedHostBootstrapState& BootstrapState,
|
||||
const FHyperTwistTrainingViewerEmbedContract& EmbedContract,
|
||||
const FHyperTwistTrainingSpatialUiContract& UiContract,
|
||||
const FHyperTwistTrainingSpatialEffectBoundary& EffectBoundary,
|
||||
const FHyperTwistTrainingSpatialStateMotionContract& StateMotionContract
|
||||
)
|
||||
{
|
||||
if (BootstrapState.BindingMode != EHyperTwistViewerEmbeddedHostBindingMode::ScissoredView)
|
||||
{
|
||||
return EHyperTwistViewerVisualizationFallbackReason::HostBindingDoesNotSupport3D;
|
||||
}
|
||||
|
||||
if (BootstrapState.RenderStateProfile != TEXT("classic-replay"))
|
||||
{
|
||||
return EHyperTwistViewerVisualizationFallbackReason::RenderProfilePrefers2D;
|
||||
}
|
||||
|
||||
return SupportsThreeDVisualization(BootstrapState, EmbedContract, UiContract, EffectBoundary, StateMotionContract)
|
||||
? EHyperTwistViewerVisualizationFallbackReason::None
|
||||
: EHyperTwistViewerVisualizationFallbackReason::MissingSupportContract;
|
||||
}
|
||||
|
||||
bool CanApplyPresentationMode(
|
||||
const FHyperTwistViewerVisualizationShellState& VisualizationState,
|
||||
const EHyperTwistViewerPresentationMode NewMode
|
||||
)
|
||||
{
|
||||
switch (NewMode)
|
||||
{
|
||||
case EHyperTwistViewerPresentationMode::Fullscreen:
|
||||
return VisualizationState.bSupportsFullscreen;
|
||||
|
||||
case EHyperTwistViewerPresentationMode::Expanded:
|
||||
return VisualizationState.bSupportsExpandedView;
|
||||
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
FHyperTwistViewerPositionSnapshot BuildPositionSnapshot(
|
||||
const TArray<FHyperTwistViewerMoveEntry>& MoveList,
|
||||
const TArray<int32>& MoveBoundaryTimesMs,
|
||||
|
|
@ -487,6 +585,7 @@ void UHyperTwistPuzzleViewerComponent::LoadSceneContext(const FHyperTwistSimulat
|
|||
SceneContext = InContext;
|
||||
PlaybackState.bSceneContextLoaded = InContext.IsStructurallyValid();
|
||||
HostBootstrapState = FHyperTwistViewerEmbeddedHostBootstrapState();
|
||||
VisualizationShellState = FHyperTwistViewerVisualizationShellState();
|
||||
|
||||
// Reset playhead — loaded content has changed.
|
||||
PlaybackState.Mode = EHyperTwistViewerPlaybackMode::Stopped;
|
||||
|
|
@ -507,6 +606,7 @@ void UHyperTwistPuzzleViewerComponent::LoadMoveList(const TArray<FHyperTwistView
|
|||
ReplaySummary = FHyperTwistDerivedReplaySummary();
|
||||
bReplayPacketLoaded = false;
|
||||
HostBootstrapState = FHyperTwistViewerEmbeddedHostBootstrapState();
|
||||
VisualizationShellState = FHyperTwistViewerVisualizationShellState();
|
||||
|
||||
PlaybackState.Mode = EHyperTwistViewerPlaybackMode::Stopped;
|
||||
PlaybackState.Direction = EHyperTwistViewerPlaybackDirection::None;
|
||||
|
|
@ -531,6 +631,7 @@ bool UHyperTwistPuzzleViewerComponent::LoadReplayPacket(const FHyperTwistReplayP
|
|||
bReplayPacketLoaded = true;
|
||||
MoveList = HyperTwistPuzzleViewerComponentInternal::BuildReplayMoveList(InReplayPacket);
|
||||
HostBootstrapState = FHyperTwistViewerEmbeddedHostBootstrapState();
|
||||
VisualizationShellState = FHyperTwistViewerVisualizationShellState();
|
||||
|
||||
FHyperTwistSimulationSceneContext ReplaySceneContext;
|
||||
if (HyperTwistPuzzleViewerComponentInternal::TryBuildSceneContextFromReplay(InReplayPacket, ReplaySceneContext))
|
||||
|
|
@ -808,6 +909,7 @@ bool UHyperTwistPuzzleViewerComponent::TryBootstrapEmbeddedClassicCubingHost(
|
|||
)
|
||||
{
|
||||
OutBootstrapState = FHyperTwistViewerEmbeddedHostBootstrapState();
|
||||
VisualizationShellState = FHyperTwistViewerVisualizationShellState();
|
||||
|
||||
if (!Request.IsStructurallyValid())
|
||||
{
|
||||
|
|
@ -876,6 +978,170 @@ bool UHyperTwistPuzzleViewerComponent::TryBootstrapEmbeddedClassicCubingHost(
|
|||
return true;
|
||||
}
|
||||
|
||||
bool UHyperTwistPuzzleViewerComponent::TryBuildVisualizationShell(
|
||||
const FHyperTwistViewerVisualizationShellRequest& Request,
|
||||
FHyperTwistViewerVisualizationShellState& OutVisualizationShellState
|
||||
)
|
||||
{
|
||||
OutVisualizationShellState = FHyperTwistViewerVisualizationShellState();
|
||||
|
||||
if (!Request.IsStructurallyValid())
|
||||
{
|
||||
VisualizationShellState = FHyperTwistViewerVisualizationShellState();
|
||||
return false;
|
||||
}
|
||||
|
||||
FHyperTwistViewerEmbeddedHostBootstrapState BootstrapState;
|
||||
if (!TryBootstrapEmbeddedClassicCubingHost(Request.HostBootstrapRequest, BootstrapState))
|
||||
{
|
||||
VisualizationShellState = FHyperTwistViewerVisualizationShellState();
|
||||
return false;
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingViewerReferenceBundle BrowserViewerBundle =
|
||||
UHyperTwistTrainingViewerLibrary::BuildBundledBrowserViewerReferenceBundle();
|
||||
const FHyperTwistTrainingSpatialReferenceBundle SpatialBundle =
|
||||
UHyperTwistTrainingSpatialLibrary::BuildBundledBrowserSpatialReferenceBundle();
|
||||
|
||||
FHyperTwistTrainingViewerEmbedContract EmbedContract;
|
||||
FHyperTwistTrainingSpatialUiContract UiContract;
|
||||
FHyperTwistTrainingSpatialEffectBoundary EffectBoundary;
|
||||
FHyperTwistTrainingSpatialStateMotionContract StateMotionContract;
|
||||
|
||||
if (!UHyperTwistTrainingViewerLibrary::TryGetEmbedContractById(
|
||||
BrowserViewerBundle,
|
||||
BrowserViewerBundle.PrimaryEmbedContractId,
|
||||
EmbedContract
|
||||
)
|
||||
|| !UHyperTwistTrainingSpatialLibrary::TryGetSpatialUiContractById(
|
||||
SpatialBundle,
|
||||
SpatialBundle.PrimarySpatialUiContractId,
|
||||
UiContract
|
||||
)
|
||||
|| !UHyperTwistTrainingSpatialLibrary::TryGetEffectBoundaryById(
|
||||
SpatialBundle,
|
||||
SpatialBundle.PrimaryEffectBoundaryId,
|
||||
EffectBoundary
|
||||
)
|
||||
|| !UHyperTwistTrainingSpatialLibrary::TryGetStateMotionContractById(
|
||||
SpatialBundle,
|
||||
SpatialBundle.PrimaryStateMotionContractId,
|
||||
StateMotionContract
|
||||
))
|
||||
{
|
||||
VisualizationShellState = FHyperTwistViewerVisualizationShellState();
|
||||
return false;
|
||||
}
|
||||
|
||||
VisualizationShellState = FHyperTwistViewerVisualizationShellState();
|
||||
VisualizationShellState.VisualizationId = FString::Printf(
|
||||
TEXT("visualization/%s/%s"),
|
||||
*Request.HostBootstrapRequest.HostInstanceId,
|
||||
*BootstrapState.SceneContextId
|
||||
);
|
||||
VisualizationShellState.BootstrapId = BootstrapState.BootstrapId;
|
||||
VisualizationShellState.AdapterProjectionId = BootstrapState.AdapterProjectionId;
|
||||
VisualizationShellState.ViewerAdapterContractId = BootstrapState.ViewerAdapterContractId;
|
||||
VisualizationShellState.BrowserEmbedContractId = EmbedContract.ContractId;
|
||||
VisualizationShellState.SpatialSceneContractId = BootstrapState.SpatialSceneContractId;
|
||||
VisualizationShellState.SpatialUiContractId = UiContract.ContractId;
|
||||
VisualizationShellState.SpatialEffectBoundaryId = EffectBoundary.BoundaryId;
|
||||
VisualizationShellState.SpatialStateMotionContractId = StateMotionContract.ContractId;
|
||||
VisualizationShellState.SceneContextId = BootstrapState.SceneContextId;
|
||||
VisualizationShellState.RenderStateProfile = BootstrapState.RenderStateProfile;
|
||||
VisualizationShellState.PreferredMode =
|
||||
HyperTwistPuzzleViewerComponentInternal::ResolvePreferredVisualizationMode(Request.VisualizationPreference);
|
||||
|
||||
if (VisualizationShellState.PreferredMode == EHyperTwistViewerVisualizationMode::ThreeD)
|
||||
{
|
||||
const bool bSupportsThreeD = HyperTwistPuzzleViewerComponentInternal::SupportsThreeDVisualization(
|
||||
BootstrapState,
|
||||
EmbedContract,
|
||||
UiContract,
|
||||
EffectBoundary,
|
||||
StateMotionContract
|
||||
);
|
||||
|
||||
if (bSupportsThreeD)
|
||||
{
|
||||
VisualizationShellState.ActiveMode = EHyperTwistViewerVisualizationMode::ThreeD;
|
||||
VisualizationShellState.bUsingFallback = false;
|
||||
VisualizationShellState.FallbackReason = EHyperTwistViewerVisualizationFallbackReason::None;
|
||||
VisualizationShellState.bUsesSpatialRendering = true;
|
||||
}
|
||||
else if (Request.bAllow2DFallback)
|
||||
{
|
||||
VisualizationShellState.ActiveMode = EHyperTwistViewerVisualizationMode::TwoD;
|
||||
VisualizationShellState.bUsingFallback = true;
|
||||
VisualizationShellState.FallbackReason =
|
||||
HyperTwistPuzzleViewerComponentInternal::ResolveThreeDFallbackReason(
|
||||
BootstrapState,
|
||||
EmbedContract,
|
||||
UiContract,
|
||||
EffectBoundary,
|
||||
StateMotionContract
|
||||
);
|
||||
VisualizationShellState.bUsesSpatialRendering = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
VisualizationShellState = FHyperTwistViewerVisualizationShellState();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
VisualizationShellState.ActiveMode = EHyperTwistViewerVisualizationMode::TwoD;
|
||||
VisualizationShellState.bUsingFallback = false;
|
||||
VisualizationShellState.FallbackReason = EHyperTwistViewerVisualizationFallbackReason::None;
|
||||
VisualizationShellState.bUsesSpatialRendering = false;
|
||||
}
|
||||
|
||||
VisualizationShellState.PresentationMode = HyperTwistPuzzleViewerComponentInternal::ResolvePresentationMode(
|
||||
Request.InitialPresentationMode,
|
||||
Request.bAllowExpandedView,
|
||||
Request.bAllowFullscreen
|
||||
);
|
||||
VisualizationShellState.bReadyForVisualization = true;
|
||||
VisualizationShellState.bSupportsExpandedView = Request.bAllowExpandedView;
|
||||
VisualizationShellState.bSupportsFullscreen = Request.bAllowFullscreen;
|
||||
VisualizationShellState.ViewerBehaviorTags = EmbedContract.ShippableBehaviorTags;
|
||||
VisualizationShellState.ViewerAnnotationSurfaceTags = EmbedContract.AnnotationSurfaceTags;
|
||||
VisualizationShellState.SpatialUiComponentFamilies = UiContract.ComponentFamilies;
|
||||
VisualizationShellState.SpatialUiStateSurfaceTags = UiContract.StateSurfaceTags;
|
||||
VisualizationShellState.SpatialSceneEventSurfaceTags = BootstrapState.SpatialEventSurfaceTags;
|
||||
VisualizationShellState.SpatialEffectFamilies = EffectBoundary.EffectFamilies;
|
||||
VisualizationShellState.MotionKinds = StateMotionContract.MotionKinds;
|
||||
|
||||
if (!VisualizationShellState.IsStructurallyValid())
|
||||
{
|
||||
VisualizationShellState = FHyperTwistViewerVisualizationShellState();
|
||||
return false;
|
||||
}
|
||||
|
||||
OutVisualizationShellState = VisualizationShellState;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UHyperTwistPuzzleViewerComponent::TrySetVisualizationPresentationMode(
|
||||
const EHyperTwistViewerPresentationMode NewMode,
|
||||
FHyperTwistViewerVisualizationShellState& OutVisualizationShellState
|
||||
)
|
||||
{
|
||||
OutVisualizationShellState = FHyperTwistViewerVisualizationShellState();
|
||||
|
||||
if (!VisualizationShellState.IsStructurallyValid()
|
||||
|| !VisualizationShellState.bReadyForVisualization
|
||||
|| !HyperTwistPuzzleViewerComponentInternal::CanApplyPresentationMode(VisualizationShellState, NewMode))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
VisualizationShellState.PresentationMode = NewMode;
|
||||
OutVisualizationShellState = VisualizationShellState;
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Readout
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ DECLARE_MULTICAST_DELEGATE_OneParam(
|
|||
//
|
||||
// Bound 2 adds observer channels and deterministic cursor/timeline transport.
|
||||
// Bound 3 adds adapter projection and embedded-host bootstrap surfaces.
|
||||
// Viewport rendering is Bound 4.
|
||||
// Bound 4 adds a visualization shell with local fallback and presentation modes.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
UCLASS(ClassGroup = "HyperTwist", meta = (BlueprintSpawnableComponent), DisplayName = "HyperTwist Puzzle Viewer")
|
||||
|
|
@ -121,6 +121,23 @@ public:
|
|||
FHyperTwistViewerEmbeddedHostBootstrapState& OutBootstrapState
|
||||
);
|
||||
|
||||
// Build a bounded visualization shell on top of the closed replay, adapter,
|
||||
// and browser-support layers without taking browser or classic-cubing
|
||||
// ownership into this lane.
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Viewer")
|
||||
bool TryBuildVisualizationShell(
|
||||
const FHyperTwistViewerVisualizationShellRequest& Request,
|
||||
FHyperTwistViewerVisualizationShellState& OutVisualizationShellState
|
||||
);
|
||||
|
||||
// Update only the visualization-local presentation mode for the current
|
||||
// shell without widening into host-lifecycle ownership.
|
||||
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Viewer")
|
||||
bool TrySetVisualizationPresentationMode(
|
||||
EHyperTwistViewerPresentationMode NewMode,
|
||||
FHyperTwistViewerVisualizationShellState& OutVisualizationShellState
|
||||
);
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Readout accessors
|
||||
// ------------------------------------------------------------------
|
||||
|
|
@ -152,6 +169,9 @@ public:
|
|||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Viewer")
|
||||
FHyperTwistViewerEmbeddedHostBootstrapState GetHostBootstrapState() const { return HostBootstrapState; }
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Viewer")
|
||||
FHyperTwistViewerVisualizationShellState GetVisualizationShellState() const { return VisualizationShellState; }
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Delegates
|
||||
// ------------------------------------------------------------------
|
||||
|
|
@ -205,6 +225,9 @@ private:
|
|||
UPROPERTY(VisibleAnywhere, Category = "HyperTwist|Viewer")
|
||||
FHyperTwistViewerEmbeddedHostBootstrapState HostBootstrapState;
|
||||
|
||||
UPROPERTY(VisibleAnywhere, Category = "HyperTwist|Viewer")
|
||||
FHyperTwistViewerVisualizationShellState VisualizationShellState;
|
||||
|
||||
UPROPERTY(VisibleAnywhere, Category = "HyperTwist|Viewer")
|
||||
TArray<int32> MoveBoundaryTimesMs;
|
||||
|
||||
|
|
|
|||
|
|
@ -49,6 +49,37 @@ enum class EHyperTwistViewerEmbeddedHostBindingMode : uint8
|
|||
ScissoredView UMETA(DisplayName = "Scissored View")
|
||||
};
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EHyperTwistViewerVisualizationMode : uint8
|
||||
{
|
||||
TwoD UMETA(DisplayName = "2D"),
|
||||
ThreeD UMETA(DisplayName = "3D")
|
||||
};
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EHyperTwistViewerVisualizationPreference : uint8
|
||||
{
|
||||
Prefer2D UMETA(DisplayName = "Prefer 2D"),
|
||||
Prefer3D UMETA(DisplayName = "Prefer 3D")
|
||||
};
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EHyperTwistViewerVisualizationFallbackReason : uint8
|
||||
{
|
||||
None UMETA(DisplayName = "None"),
|
||||
HostBindingDoesNotSupport3D UMETA(DisplayName = "Host Binding Does Not Support 3D"),
|
||||
RenderProfilePrefers2D UMETA(DisplayName = "Render Profile Prefers 2D"),
|
||||
MissingSupportContract UMETA(DisplayName = "Missing Support Contract")
|
||||
};
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EHyperTwistViewerPresentationMode : uint8
|
||||
{
|
||||
Inline UMETA(DisplayName = "Inline"),
|
||||
Expanded UMETA(DisplayName = "Expanded"),
|
||||
Fullscreen UMETA(DisplayName = "Fullscreen")
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Control action — what the caller wants the viewer to do
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -571,6 +602,165 @@ struct UNREALHYPERTWIST_API FHyperTwistViewerEmbeddedHostBootstrapState
|
|||
}
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Bound 4 visualization-shell records
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct UNREALHYPERTWIST_API FHyperTwistViewerVisualizationShellRequest
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
FHyperTwistViewerEmbeddedHostBootstrapRequest HostBootstrapRequest;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
EHyperTwistViewerVisualizationPreference VisualizationPreference =
|
||||
EHyperTwistViewerVisualizationPreference::Prefer3D;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
EHyperTwistViewerPresentationMode InitialPresentationMode = EHyperTwistViewerPresentationMode::Inline;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
bool bAllow2DFallback = true;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
bool bAllowExpandedView = true;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
bool bAllowFullscreen = true;
|
||||
|
||||
bool IsStructurallyValid() const
|
||||
{
|
||||
return HostBootstrapRequest.IsStructurallyValid();
|
||||
}
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct UNREALHYPERTWIST_API FHyperTwistViewerVisualizationShellState
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
FString VisualizationId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
FString BootstrapId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
FString AdapterProjectionId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
FString ViewerAdapterContractId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
FString BrowserEmbedContractId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
FString SpatialSceneContractId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
FString SpatialUiContractId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
FString SpatialEffectBoundaryId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
FString SpatialStateMotionContractId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
FString SceneContextId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
FString RenderStateProfile;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
EHyperTwistViewerVisualizationMode PreferredMode = EHyperTwistViewerVisualizationMode::ThreeD;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
EHyperTwistViewerVisualizationMode ActiveMode = EHyperTwistViewerVisualizationMode::TwoD;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
EHyperTwistViewerVisualizationFallbackReason FallbackReason =
|
||||
EHyperTwistViewerVisualizationFallbackReason::None;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
EHyperTwistViewerPresentationMode PresentationMode = EHyperTwistViewerPresentationMode::Inline;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
bool bReadyForVisualization = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
bool bUsingFallback = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
bool bSupportsExpandedView = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
bool bSupportsFullscreen = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
bool bUsesSpatialRendering = false;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
TArray<FString> ViewerBehaviorTags;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
TArray<FString> ViewerAnnotationSurfaceTags;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
TArray<FString> SpatialUiComponentFamilies;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
TArray<FString> SpatialUiStateSurfaceTags;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
TArray<FString> SpatialSceneEventSurfaceTags;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
TArray<FString> SpatialEffectFamilies;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist|Viewer")
|
||||
TArray<FString> MotionKinds;
|
||||
|
||||
bool IsStructurallyValid() const
|
||||
{
|
||||
if (!bReadyForVisualization)
|
||||
{
|
||||
return VisualizationId.IsEmpty()
|
||||
&& BootstrapId.IsEmpty()
|
||||
&& AdapterProjectionId.IsEmpty()
|
||||
&& ViewerAdapterContractId.IsEmpty()
|
||||
&& BrowserEmbedContractId.IsEmpty()
|
||||
&& SpatialSceneContractId.IsEmpty()
|
||||
&& SpatialUiContractId.IsEmpty()
|
||||
&& SpatialEffectBoundaryId.IsEmpty()
|
||||
&& SpatialStateMotionContractId.IsEmpty()
|
||||
&& SceneContextId.IsEmpty()
|
||||
&& RenderStateProfile.IsEmpty();
|
||||
}
|
||||
|
||||
return !VisualizationId.IsEmpty()
|
||||
&& !BootstrapId.IsEmpty()
|
||||
&& !AdapterProjectionId.IsEmpty()
|
||||
&& !ViewerAdapterContractId.IsEmpty()
|
||||
&& !BrowserEmbedContractId.IsEmpty()
|
||||
&& !SpatialSceneContractId.IsEmpty()
|
||||
&& !SpatialUiContractId.IsEmpty()
|
||||
&& !SpatialEffectBoundaryId.IsEmpty()
|
||||
&& !SpatialStateMotionContractId.IsEmpty()
|
||||
&& !SceneContextId.IsEmpty()
|
||||
&& !RenderStateProfile.IsEmpty()
|
||||
&& ViewerBehaviorTags.Num() > 0
|
||||
&& ViewerAnnotationSurfaceTags.Num() > 0
|
||||
&& SpatialUiComponentFamilies.Num() > 0
|
||||
&& SpatialUiStateSurfaceTags.Num() > 0
|
||||
&& SpatialSceneEventSurfaceTags.Num() > 0
|
||||
&& SpatialEffectFamilies.Num() > 0
|
||||
&& MotionKinds.Num() > 0;
|
||||
}
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Runtime surface - minimal compact replay-player bundle exposed through the
|
||||
// training runtime library without moving shell ownership out of simulation.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,241 @@
|
|||
// Copyright HyperTwist, Inc. All Rights Reserved.
|
||||
|
||||
#include "Misc/AutomationTest.h"
|
||||
|
||||
#include "HyperTwistBootstrap/HyperTwistContractLibrary.h"
|
||||
#include "HyperTwistSimulation/HyperTwistPuzzleViewerComponent.h"
|
||||
#include "JsonObjectConverter.h"
|
||||
|
||||
#if WITH_AUTOMATION_TESTS
|
||||
|
||||
namespace HyperTwistTwistyBound4VisualizationTestInternal
|
||||
{
|
||||
template <typename TStruct>
|
||||
FString SerializeStructToJson(const TStruct& Value)
|
||||
{
|
||||
FString Json;
|
||||
FJsonObjectConverter::UStructToJsonObjectString(TStruct::StaticStruct(), &Value, Json, 0, 0);
|
||||
return Json;
|
||||
}
|
||||
|
||||
FHyperTwistReplayEvent MakeReplayEvent(
|
||||
const FString& EventId,
|
||||
const int32 Sequence,
|
||||
const int32 TimeMs,
|
||||
const EHyperTwistReplayEventType EventType,
|
||||
const FString& PayloadJson
|
||||
)
|
||||
{
|
||||
FHyperTwistReplayEvent Event;
|
||||
Event.EventId = EventId;
|
||||
Event.Sequence = Sequence;
|
||||
Event.TimeMs = TimeMs;
|
||||
Event.EventType = EventType;
|
||||
Event.PayloadJson = PayloadJson;
|
||||
return Event;
|
||||
}
|
||||
|
||||
FHyperTwistReplayPacket MakeReplayPacket()
|
||||
{
|
||||
const FHyperTwistPuzzleState SampleState = UHyperTwistContractLibrary::MakeSampleClassicPuzzleState();
|
||||
|
||||
FHyperTwistStateSnapshot Snapshot;
|
||||
Snapshot.SnapshotId = TEXT("snapshot-bound4");
|
||||
Snapshot.State = SampleState;
|
||||
Snapshot.DerivedHash = TEXT("snapshot-bound4-hash");
|
||||
|
||||
FHyperTwistReplayMovePayload FirstMovePayload;
|
||||
FirstMovePayload.Notation = TEXT("R");
|
||||
FirstMovePayload.TransformationRef = TEXT("classic/r");
|
||||
FirstMovePayload.Source = TEXT("unit-test");
|
||||
|
||||
FHyperTwistReplayMovePayload SecondMovePayload;
|
||||
SecondMovePayload.Notation = TEXT("U");
|
||||
SecondMovePayload.TransformationRef = TEXT("classic/u");
|
||||
SecondMovePayload.Source = TEXT("unit-test");
|
||||
|
||||
FHyperTwistReplayMovePayload ThirdMovePayload;
|
||||
ThirdMovePayload.Notation = TEXT("F'");
|
||||
ThirdMovePayload.TransformationRef = TEXT("classic/f-prime");
|
||||
ThirdMovePayload.Source = TEXT("unit-test");
|
||||
|
||||
FHyperTwistReplayPacket ReplayPacket;
|
||||
ReplayPacket.ReplayId = TEXT("replay-bound4-001");
|
||||
ReplayPacket.SessionId = TEXT("session-bound4-001");
|
||||
ReplayPacket.PuzzleDefinition = SampleState.Definition;
|
||||
ReplayPacket.CaptureMode = EHyperTwistCaptureMode::Imported;
|
||||
ReplayPacket.Events = {
|
||||
MakeReplayEvent(
|
||||
TEXT("evt-001"),
|
||||
1,
|
||||
0,
|
||||
EHyperTwistReplayEventType::StateSnapshot,
|
||||
SerializeStructToJson(Snapshot)
|
||||
),
|
||||
MakeReplayEvent(
|
||||
TEXT("evt-002"),
|
||||
2,
|
||||
0,
|
||||
EHyperTwistReplayEventType::TimerStart,
|
||||
TEXT("")
|
||||
),
|
||||
MakeReplayEvent(
|
||||
TEXT("evt-003"),
|
||||
3,
|
||||
200,
|
||||
EHyperTwistReplayEventType::Move,
|
||||
SerializeStructToJson(FirstMovePayload)
|
||||
),
|
||||
MakeReplayEvent(
|
||||
TEXT("evt-004"),
|
||||
4,
|
||||
400,
|
||||
EHyperTwistReplayEventType::Move,
|
||||
SerializeStructToJson(SecondMovePayload)
|
||||
),
|
||||
MakeReplayEvent(
|
||||
TEXT("evt-005"),
|
||||
5,
|
||||
700,
|
||||
EHyperTwistReplayEventType::Move,
|
||||
SerializeStructToJson(ThirdMovePayload)
|
||||
),
|
||||
MakeReplayEvent(
|
||||
TEXT("evt-006"),
|
||||
6,
|
||||
1000,
|
||||
EHyperTwistReplayEventType::SolveEnd,
|
||||
TEXT("")
|
||||
)
|
||||
};
|
||||
|
||||
return ReplayPacket;
|
||||
}
|
||||
}
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FHyperTwistTwistyBound4VisualizationShellTest,
|
||||
"HyperTwist.CleanRoom.TwistyJs.Bound4.VisualizationShell",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
|
||||
)
|
||||
|
||||
bool FHyperTwistTwistyBound4VisualizationShellTest::RunTest(const FString& Parameters)
|
||||
{
|
||||
UHyperTwistPuzzleViewerComponent* Viewer = NewObject<UHyperTwistPuzzleViewerComponent>();
|
||||
TestNotNull(TEXT("Viewer component instance must be created."), Viewer);
|
||||
TestTrue(
|
||||
TEXT("Replay packet load must succeed for the Bound 4 visualization-shell test."),
|
||||
Viewer->LoadReplayPacket(HyperTwistTwistyBound4VisualizationTestInternal::MakeReplayPacket())
|
||||
);
|
||||
|
||||
FHyperTwistViewerVisualizationShellRequest Request;
|
||||
Request.HostBootstrapRequest.HostInstanceId = TEXT("visual-host-01");
|
||||
Request.HostBootstrapRequest.BindingMode = EHyperTwistViewerEmbeddedHostBindingMode::ScissoredView;
|
||||
Request.HostBootstrapRequest.bBindTimelineSurface = true;
|
||||
Request.HostBootstrapRequest.bBindScrubberSurface = true;
|
||||
Request.HostBootstrapRequest.bBindStatusSurface = true;
|
||||
Request.HostBootstrapRequest.bAllowPointerInput = true;
|
||||
Request.VisualizationPreference = EHyperTwistViewerVisualizationPreference::Prefer3D;
|
||||
Request.InitialPresentationMode = EHyperTwistViewerPresentationMode::Expanded;
|
||||
Request.bAllow2DFallback = true;
|
||||
Request.bAllowExpandedView = true;
|
||||
Request.bAllowFullscreen = true;
|
||||
|
||||
FHyperTwistViewerVisualizationShellState VisualizationState;
|
||||
TestTrue(
|
||||
TEXT("The viewer must prepare a bounded visualization shell."),
|
||||
Viewer->TryBuildVisualizationShell(Request, VisualizationState)
|
||||
);
|
||||
TestTrue(TEXT("The visualization shell state must be structurally valid."), VisualizationState.IsStructurallyValid());
|
||||
TestEqual(TEXT("The visualization shell should preserve the bootstrap id."), VisualizationState.BootstrapId, Viewer->GetHostBootstrapState().BootstrapId);
|
||||
TestEqual(TEXT("The visualization shell should preserve the scene context id."), VisualizationState.SceneContextId, Viewer->GetSceneContext().SceneContextId);
|
||||
TestEqual(TEXT("The visualization shell should preserve the classic-cubing viewer adapter contract."), VisualizationState.ViewerAdapterContractId, FString(TEXT("classic-cubing-viewer-adapter-stack")));
|
||||
TestEqual(TEXT("The visualization shell should reference the browser viewer embed contract."), VisualizationState.BrowserEmbedContractId, FString(TEXT("browser-viewer-embed")));
|
||||
TestEqual(TEXT("The visualization shell should reference the browser spatial scene contract."), VisualizationState.SpatialSceneContractId, FString(TEXT("browser-spatial-scene-stack")));
|
||||
TestEqual(TEXT("The visualization shell should reference the browser spatial UI contract."), VisualizationState.SpatialUiContractId, FString(TEXT("browser-spatial-ui-stack")));
|
||||
TestEqual(TEXT("The visualization shell should reference the browser effect boundary."), VisualizationState.SpatialEffectBoundaryId, FString(TEXT("browser-spatial-effect-boundary")));
|
||||
TestEqual(TEXT("The visualization shell should reference the browser state-motion contract."), VisualizationState.SpatialStateMotionContractId, FString(TEXT("browser-spatial-state-motion-stack")));
|
||||
TestEqual(TEXT("The preferred visualization mode should stay 3D."), VisualizationState.PreferredMode, EHyperTwistViewerVisualizationMode::ThreeD);
|
||||
TestEqual(TEXT("The active visualization mode should stay 3D when the support stack is available."), VisualizationState.ActiveMode, EHyperTwistViewerVisualizationMode::ThreeD);
|
||||
TestFalse(TEXT("The 3D shell should not require a fallback in the scissored-view path."), VisualizationState.bUsingFallback);
|
||||
TestTrue(TEXT("The 3D shell should mark spatial rendering active."), VisualizationState.bUsesSpatialRendering);
|
||||
TestEqual(TEXT("The requested expanded presentation mode should be preserved."), VisualizationState.PresentationMode, EHyperTwistViewerPresentationMode::Expanded);
|
||||
TestTrue(TEXT("The visualization shell should support fullscreen when requested."), VisualizationState.bSupportsFullscreen);
|
||||
TestTrue(TEXT("The visualization shell should support expanded mode when requested."), VisualizationState.bSupportsExpandedView);
|
||||
TestTrue(TEXT("The visualization shell should surface embeddable browser-viewer behavior tags."), VisualizationState.ViewerBehaviorTags.Contains(TEXT("embeddable-viewer")));
|
||||
TestTrue(TEXT("The visualization shell should surface browser-viewer annotation tags."), VisualizationState.ViewerAnnotationSurfaceTags.Contains(TEXT("focus-annotation")));
|
||||
TestTrue(TEXT("The visualization shell should surface world-anchored UI tags."), VisualizationState.SpatialUiStateSurfaceTags.Contains(TEXT("HTML/spatial overlay bridge")));
|
||||
TestTrue(TEXT("The visualization shell should surface browser scene event tags."), VisualizationState.SpatialSceneEventSurfaceTags.Contains(TEXT("scissored view embedding")));
|
||||
TestTrue(TEXT("The visualization shell should surface effect-family tags."), VisualizationState.SpatialEffectFamilies.Contains(TEXT("tone mapping and highlight effects")));
|
||||
TestTrue(TEXT("The visualization shell should surface motion-kind tags."), VisualizationState.MotionKinds.Contains(TEXT("reduced-motion support")));
|
||||
TestEqual(TEXT("The stored visualization shell state should match the returned visualization id."), Viewer->GetVisualizationShellState().VisualizationId, VisualizationState.VisualizationId);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
||||
FHyperTwistTwistyBound4FallbackPolicyTest,
|
||||
"HyperTwist.CleanRoom.TwistyJs.Bound4.FallbackPolicy",
|
||||
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
|
||||
)
|
||||
|
||||
bool FHyperTwistTwistyBound4FallbackPolicyTest::RunTest(const FString& Parameters)
|
||||
{
|
||||
UHyperTwistPuzzleViewerComponent* Viewer = NewObject<UHyperTwistPuzzleViewerComponent>();
|
||||
TestNotNull(TEXT("Viewer component instance must be created."), Viewer);
|
||||
TestTrue(
|
||||
TEXT("Replay packet load must succeed for the Bound 4 fallback-policy test."),
|
||||
Viewer->LoadReplayPacket(HyperTwistTwistyBound4VisualizationTestInternal::MakeReplayPacket())
|
||||
);
|
||||
|
||||
FHyperTwistViewerVisualizationShellRequest Request;
|
||||
Request.HostBootstrapRequest.HostInstanceId = TEXT("visual-host-02");
|
||||
Request.HostBootstrapRequest.BindingMode = EHyperTwistViewerEmbeddedHostBindingMode::InlineCanvas;
|
||||
Request.HostBootstrapRequest.bBindTimelineSurface = true;
|
||||
Request.HostBootstrapRequest.bBindScrubberSurface = true;
|
||||
Request.HostBootstrapRequest.bBindStatusSurface = true;
|
||||
Request.HostBootstrapRequest.bAllowPointerInput = true;
|
||||
Request.VisualizationPreference = EHyperTwistViewerVisualizationPreference::Prefer3D;
|
||||
Request.InitialPresentationMode = EHyperTwistViewerPresentationMode::Inline;
|
||||
Request.bAllow2DFallback = true;
|
||||
Request.bAllowExpandedView = true;
|
||||
Request.bAllowFullscreen = true;
|
||||
|
||||
FHyperTwistViewerVisualizationShellState VisualizationState;
|
||||
TestTrue(
|
||||
TEXT("The viewer must still build a bounded visualization shell when 3D falls back to 2D."),
|
||||
Viewer->TryBuildVisualizationShell(Request, VisualizationState)
|
||||
);
|
||||
TestTrue(TEXT("The fallback visualization shell state must be structurally valid."), VisualizationState.IsStructurallyValid());
|
||||
TestEqual(TEXT("The preferred visualization mode should remain 3D."), VisualizationState.PreferredMode, EHyperTwistViewerVisualizationMode::ThreeD);
|
||||
TestEqual(TEXT("Inline-canvas host binding should fall back to the 2D shell."), VisualizationState.ActiveMode, EHyperTwistViewerVisualizationMode::TwoD);
|
||||
TestTrue(TEXT("The 2D shell should report that it is using the shared fallback policy."), VisualizationState.bUsingFallback);
|
||||
TestEqual(
|
||||
TEXT("The fallback reason should identify the host-binding constraint."),
|
||||
VisualizationState.FallbackReason,
|
||||
EHyperTwistViewerVisualizationFallbackReason::HostBindingDoesNotSupport3D
|
||||
);
|
||||
TestFalse(TEXT("The fallback 2D shell should not mark spatial rendering active."), VisualizationState.bUsesSpatialRendering);
|
||||
TestEqual(TEXT("The initial inline presentation mode should be preserved."), VisualizationState.PresentationMode, EHyperTwistViewerPresentationMode::Inline);
|
||||
|
||||
FHyperTwistViewerVisualizationShellState FullscreenState;
|
||||
TestTrue(
|
||||
TEXT("Fullscreen should remain a visualization-local concern even on the 2D fallback shell."),
|
||||
Viewer->TrySetVisualizationPresentationMode(EHyperTwistViewerPresentationMode::Fullscreen, FullscreenState)
|
||||
);
|
||||
TestEqual(TEXT("The presentation mode should move to fullscreen."), FullscreenState.PresentationMode, EHyperTwistViewerPresentationMode::Fullscreen);
|
||||
TestEqual(TEXT("Fullscreen should preserve the active 2D fallback mode."), FullscreenState.ActiveMode, EHyperTwistViewerVisualizationMode::TwoD);
|
||||
|
||||
FHyperTwistViewerVisualizationShellState ExpandedState;
|
||||
TestTrue(
|
||||
TEXT("Expanded view should also remain a visualization-local concern."),
|
||||
Viewer->TrySetVisualizationPresentationMode(EHyperTwistViewerPresentationMode::Expanded, ExpandedState)
|
||||
);
|
||||
TestEqual(TEXT("The presentation mode should move to expanded."), ExpandedState.PresentationMode, EHyperTwistViewerPresentationMode::Expanded);
|
||||
TestEqual(TEXT("Expanded view should preserve the active 2D fallback mode."), ExpandedState.ActiveMode, EHyperTwistViewerVisualizationMode::TwoD);
|
||||
TestEqual(TEXT("The stored visualization shell state should now reflect the expanded mode."), Viewer->GetVisualizationShellState().PresentationMode, EHyperTwistViewerPresentationMode::Expanded);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif // WITH_AUTOMATION_TESTS
|
||||
Loading…
Add table
Reference in a new issue