// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. #include "GeometryCacheModulePrivatePCH.h" #include "GeometryCacheComponent.h" #include "GeometryCacheSceneProxy.h" #include "GeometryCacheTrack.h" #include "GeometryCacheTrackFlipbookAnimation.h" DECLARE_CYCLE_STAT(TEXT("GeometryCacheTick"), STAT_GeometryCacheComponent_TickComponent, STATGROUP_GeometryCache); UGeometryCacheComponent::UGeometryCacheComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { PrimaryComponentTick.bCanEverTick = true; ElapsedTime = 0.0f; bRunning = false; bLooping = true; PlayDirection = 1.0f; StartTimeOffset = 0.0f; PlaybackSpeed = 1.0f; Duration = 0.0f; } void UGeometryCacheComponent::BeginDestroy() { Super::BeginDestroy(); ReleaseResources(); } void UGeometryCacheComponent::OnRegister() { NumTracks = 0; TrackMatrixSampleIndices.Empty(); TrackMatrixSampleIndices.Empty(); TrackSections.Empty(); if (GeometryCache != NULL) { // Refresh NumTracks and clear Index Arrays NumTracks = GeometryCache->Tracks.Num(); TrackMeshSampleIndices.Empty(GeometryCache->Tracks.Num()); TrackMatrixSampleIndices.Empty(GeometryCache->Tracks.Num()); Duration = 0.0f; // Create mesh sections for each GeometryCacheTrack for (int32 TrackIndex = 0; TrackIndex < NumTracks; ++TrackIndex ) { UGeometryCacheTrack* Track = GeometryCache->Tracks[TrackIndex]; FMatrix WorldMatrix; int32 MeshSampleIndex = -1; int32 MatrixSampleIndex = -1; FGeometryCacheMeshData* MeshData = NULL; // Retrieve the matrix/mesh data and the appropriate sample indices Track->UpdateMatrixData(ElapsedTime + StartTimeOffset, bLooping, MatrixSampleIndex, WorldMatrix); Track->UpdateMeshData(ElapsedTime + StartTimeOffset, bLooping, MeshSampleIndex, MeshData); // First time so create rather than update the mesh sections CreateTrackSection(TrackIndex, WorldMatrix, MeshData); // Store the sample indices for both the mesh and matrix data TrackMeshSampleIndices.Add(MeshSampleIndex); TrackMatrixSampleIndices.Add(MatrixSampleIndex); const float TrackMaxSampleTime = Track->GetMaxSampleTime(); Duration = ( Duration > TrackMaxSampleTime ) ? Duration : TrackMaxSampleTime; } } Super::OnRegister(); } void UGeometryCacheComponent::OnUnregister() { Super::OnUnregister(); NumTracks = 0; TrackMatrixSampleIndices.Empty(); TrackMatrixSampleIndices.Empty(); TrackSections.Empty(); } void UGeometryCacheComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) { SCOPE_CYCLE_COUNTER(STAT_GeometryCacheComponent_TickComponent); if (GeometryCache && bRunning) { // Increase total elapsed time since BeginPlay according to PlayDirection and speed ElapsedTime += (DeltaTime * PlayDirection * PlaybackSpeed); // QQ change this for reverse playing? extra float for runningtime vs sample time? if (ElapsedTime < 0.0f) { ElapsedTime += Duration; } for (int32 TrackIndex = 0; TrackIndex < NumTracks; ++TrackIndex) { // Determine if and which part of the section we have to update UGeometryCacheTrack* Track = GeometryCache->Tracks[TrackIndex]; FMatrix WorldMatrix; FGeometryCacheMeshData* MeshData = NULL; const bool bUpdateMatrix = Track->UpdateMatrixData(ElapsedTime + StartTimeOffset, bLooping, TrackMatrixSampleIndices[TrackIndex], WorldMatrix); const bool bUpdateMesh = Track->UpdateMeshData(ElapsedTime + StartTimeOffset, bLooping, TrackMeshSampleIndices[TrackIndex], MeshData); // Update sections according what is required if (bUpdateMatrix) { UpdateTrackSectionMatrixData(TrackIndex, WorldMatrix); } if (bUpdateMesh) { UpdateTrackSectionMeshData(TrackIndex, MeshData); } } } } FBoxSphereBounds UGeometryCacheComponent::CalcBounds(const FTransform& LocalToWorld) const { return LocalBounds.TransformBy(LocalToWorld); } void UGeometryCacheComponent::UpdateLocalBounds() { FBox LocalBox(0); for (const FTrackRenderData& Section : TrackSections) { // Use World matrix per section for correct bounding box LocalBox += (Section.MeshData->BoundingBox.TransformBy(Section.WorldMatrix)); } LocalBounds = LocalBox.IsValid ? FBoxSphereBounds(LocalBox) : FBoxSphereBounds(FVector(0, 0, 0), FVector(0, 0, 0), 0); // fallback to reset box sphere bounds UpdateBounds(); } FPrimitiveSceneProxy* UGeometryCacheComponent::CreateSceneProxy() { SceneProxy = new FGeometryCacheSceneProxy(this); return SceneProxy; } void UGeometryCacheComponent::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) { InvalidateTrackSampleIndices(); MarkRenderStateDirty(); Super::PostEditChangeProperty(PropertyChangedEvent); } int32 UGeometryCacheComponent::GetNumMaterials() const { // Sum the number of materials per track int32 TotalNumMaterial = 0; if ( GeometryCache ) { for (UGeometryCacheTrack* Track : GeometryCache->Tracks) { TotalNumMaterial += Track->GetNumMaterials(); } } return TotalNumMaterial; } void UGeometryCacheComponent::CreateTrackSection(int32 SectionIndex, const FMatrix& WorldMatrix, FGeometryCacheMeshData* MeshData) { // Ensure sections array is long enough if (TrackSections.Num() <= SectionIndex) { TrackSections.SetNum(SectionIndex + 1, false); } // Reset this section (in case it already existed) FTrackRenderData& NewSection = TrackSections[SectionIndex]; NewSection.Reset(); // Number of Vertices const int32 NumVerts = MeshData->Vertices.Num(); // Copy index buffer (clamping to vertex range) const int32 NumTriIndices = NumVerts; NewSection.IndexBuffer.Reset(); NewSection.IndexBuffer.AddUninitialized(NumTriIndices); for (int32 IndexIdx = 0; IndexIdx < NumTriIndices; IndexIdx++) { NewSection.IndexBuffer[IndexIdx] = FMath::Min(IndexIdx, NumVerts - 1); } // Store Matrix and MeshData-pointer NewSection.WorldMatrix = WorldMatrix; NewSection.MeshData = MeshData; UpdateLocalBounds(); // Update overall bounds MarkRenderStateDirty(); // Recreate scene proxy } void UGeometryCacheComponent::UpdateTrackSectionMeshData(int32 SectionIndex, FGeometryCacheMeshData* MeshData) { // Check if the index is in range and Vertices contains any data check(SectionIndex < TrackSections.Num() && MeshData != nullptr && "Invalid SectionIndex or Mesh Data"); // Reset this section (in case it already existed) FTrackRenderData& UpdateSection = TrackSections[SectionIndex]; // Number of Vertices const int32 NumVerts = MeshData->Vertices.Num(); // Determine if we actually need a new index buffer if (UpdateSection.IndexBuffer.Num() != MeshData->Vertices.Num()) { // Copy index buffer (clamping to vertex range) const int32 NumTriIndices = NumVerts; UpdateSection.IndexBuffer.Reset(); UpdateSection.IndexBuffer.AddUninitialized(NumTriIndices); for (int32 IndexIdx = 0; IndexIdx < NumTriIndices; IndexIdx++) { UpdateSection.IndexBuffer[IndexIdx] = FMath::Min(IndexIdx, NumVerts - 1); } if (SceneProxy) { UpdateTrackSectionIndexbuffer(SectionIndex, UpdateSection.IndexBuffer); } } // Update MeshDataPointer UpdateSection.MeshData = MeshData; if (SceneProxy) { UpdateTrackSectionVertexbuffer(SectionIndex, MeshData); // Will update the bounds (used for frustum culling) MarkRenderTransformDirty(); } else { // Recreate scene proxy MarkRenderStateDirty(); } MarkRenderStateDirty(); // Update overall bounds UpdateLocalBounds(); } void UGeometryCacheComponent::UpdateTrackSectionMatrixData(int32 SectionIndex, const FMatrix& WorldMatrix) { check(SectionIndex < TrackSections.Num() && "Invalid SectionIndex"); // Reset this section (in case it already existed) FTrackRenderData& UpdateSection = TrackSections[SectionIndex]; // Update the WorldMatrix only UpdateSection.WorldMatrix = WorldMatrix; if (!IsRenderStateDirty() && SceneProxy != NULL) { // Update it within the scene proxy SceneProxy->UpdateSectionWorldMatrix(SectionIndex, WorldMatrix); } // Update local bounds UpdateLocalBounds(); // Mark transform Dirty MarkRenderTransformDirty(); } void UGeometryCacheComponent::UpdateTrackSectionVertexbuffer(int32 SectionIndex, FGeometryCacheMeshData* MeshData) { ENQUEUE_UNIQUE_RENDER_COMMAND_THREEPARAMETER( FUpdateVertexBufferCommand, FGeometryCacheSceneProxy*, SceneProxy, SceneProxy, const int32, Index, SectionIndex, FGeometryCacheMeshData*, Data, MeshData, { SceneProxy->UpdateSectionVertexBuffer(Index, Data); }); } void UGeometryCacheComponent::UpdateTrackSectionIndexbuffer(int32 SectionIndex, const TArray& Indices) { ENQUEUE_UNIQUE_RENDER_COMMAND_THREEPARAMETER( FUpdateVertexBufferCommand, FGeometryCacheSceneProxy*, SceneProxy, SceneProxy, const int32, Index, SectionIndex, const TArray&, Data, Indices, { SceneProxy->UpdateSectionIndexBuffer(Index, Data); }); } void UGeometryCacheComponent::OnObjectReimported(UGeometryCache* ImportedGeometryCache) { if (GeometryCache == ImportedGeometryCache) { ReleaseResources(); GeometryCache = ImportedGeometryCache; if (GeometryCache != NULL) { // Refresh NumTracks and clear Index Arrays NumTracks = GeometryCache->Tracks.Num(); TrackMeshSampleIndices.Empty(GeometryCache->Tracks.Num()); TrackMatrixSampleIndices.Empty(GeometryCache->Tracks.Num()); Duration = 0.0f; // Create mesh sections for each GeometryCacheTrack for (int32 TrackIndex = 0; TrackIndex < NumTracks; ++TrackIndex) { UGeometryCacheTrack* Track = GeometryCache->Tracks[TrackIndex]; FMatrix WorldMatrix; int32 MeshSampleIndex = -1; int32 MatrixSampleIndex = -1; FGeometryCacheMeshData* MeshData = NULL; // Retrieve the matrix/mesh data and the appropriate sample indices Track->UpdateMatrixData(ElapsedTime + StartTimeOffset, bLooping, MatrixSampleIndex, WorldMatrix); Track->UpdateMeshData(ElapsedTime + StartTimeOffset, bLooping, MeshSampleIndex, MeshData); // First time so create rather than update the mesh sections CreateTrackSection(TrackIndex, WorldMatrix, MeshData); // Store the sample indices for both the mesh and matrix data TrackMeshSampleIndices.Add(MeshSampleIndex); TrackMatrixSampleIndices.Add(MatrixSampleIndex); const float TrackMaxSampleTime = Track->GetMaxSampleTime(); Duration = (Duration > TrackMaxSampleTime) ? Duration : TrackMaxSampleTime; } } if (SceneProxy) { SceneProxy->ClearSections(); } MarkRenderStateDirty(); } } void UGeometryCacheComponent::Play() { bRunning = true; PlayDirection = 1.0f; } void UGeometryCacheComponent::PlayFromStart() { ElapsedTime = 0.0f; bRunning = true; PlayDirection = 1.0f; } void UGeometryCacheComponent::Pause() { bRunning = !bRunning; } void UGeometryCacheComponent::Stop() { bRunning = false; } bool UGeometryCacheComponent::IsPlaying() const { return bRunning; } bool UGeometryCacheComponent::IsLooping() const { return bLooping; } void UGeometryCacheComponent::SetLooping(const bool bNewLooping) { bLooping = bNewLooping; } bool UGeometryCacheComponent::IsPlayingReversed() const { return FMath::IsNearlyEqual( PlayDirection, -1.0f ); } float UGeometryCacheComponent::GetPlaybackSpeed() const { return PlaybackSpeed; } void UGeometryCacheComponent::SetPlaybackSpeed(const float NewPlaybackSpeed) { // Currently only positive play back speeds are supported PlaybackSpeed = fabs( NewPlaybackSpeed ); } void UGeometryCacheComponent::PlayReversedFromEnd() { ElapsedTime = Duration; PlayDirection = -1.0f; bRunning = true; } void UGeometryCacheComponent::PlayReversed() { PlayDirection = -1.0f; bRunning = true; } void UGeometryCacheComponent::InvalidateTrackSampleIndices() { for (int32& Index : TrackMeshSampleIndices) { Index = -1; } for (int32& Index : TrackMatrixSampleIndices) { Index = -1; } } void UGeometryCacheComponent::ReleaseResources() { GeometryCache = NULL; NumTracks = 0; TrackMatrixSampleIndices.Empty(); TrackMatrixSampleIndices.Empty(); TrackSections.Empty(); DetachFence.BeginFence(); } void UGeometryCacheComponent::PreEditUndo() { InvalidateTrackSampleIndices(); MarkRenderStateDirty(); } void UGeometryCacheComponent::PostEditUndo() { InvalidateTrackSampleIndices(); MarkRenderStateDirty(); }