604 lines
18 KiB
C++
604 lines
18 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "HeadlessChaosTestCloth.h"
|
|
|
|
#include "HeadlessChaos.h"
|
|
#include "HeadlessChaosTestUtility.h"
|
|
|
|
#include "Chaos/PBDSoftsEvolutionFwd.h"
|
|
#include "Chaos/PBDEvolution.h"
|
|
#include "Chaos/GeometryParticles.h"
|
|
#include "Chaos/PBDAxialSpringConstraints.h"
|
|
#include "Chaos/PBDSpringConstraints.h"
|
|
#include "Chaos/TriangleMesh.h"
|
|
#include "Chaos/Utilities.h"
|
|
#include "GeometryCollection/ManagedArrayCollection.h"
|
|
|
|
#include "Modules/ModuleManager.h"
|
|
|
|
namespace ChaosTest {
|
|
|
|
using namespace Chaos;
|
|
DEFINE_LOG_CATEGORY_STATIC(LogChaosTestCloth, Verbose, All);
|
|
|
|
TUniquePtr<Softs::FPBDEvolution> InitPBDEvolution(
|
|
const int32 NumIterations=1,
|
|
const Softs::FSolverReal CollisionThickness=KINDA_SMALL_NUMBER,
|
|
const Softs::FSolverReal SelfCollisionThickness=KINDA_SMALL_NUMBER,
|
|
const Softs::FSolverReal Friction=0.0,
|
|
const Softs::FSolverReal Damping=0.04)
|
|
{
|
|
Chaos::Softs::FSolverParticles Particles;
|
|
Chaos::Softs::FSolverCollisionParticles RigidParticles;
|
|
TUniquePtr<Softs::FPBDEvolution> Evolution(
|
|
new Softs::FPBDEvolution(
|
|
MoveTemp(Particles),
|
|
MoveTemp(RigidParticles),
|
|
{},
|
|
NumIterations,
|
|
CollisionThickness,
|
|
SelfCollisionThickness,
|
|
Friction,
|
|
Damping));
|
|
return Evolution;
|
|
}
|
|
|
|
template <class TEvolutionPtr>
|
|
void InitSingleParticle(
|
|
TEvolutionPtr& Evolution,
|
|
const Softs::FSolverVec3& Position = Softs::FSolverVec3(0),
|
|
const Softs::FSolverVec3& Velocity = Softs::FSolverVec3(0),
|
|
const Softs::FSolverReal Mass = 1.0)
|
|
{
|
|
auto& Particles = Evolution->Particles();
|
|
const uint32 Idx = Particles.Size();
|
|
Particles.AddParticles(1);
|
|
Particles.SetX(Idx, Position);
|
|
Particles.V(Idx) = Velocity;
|
|
Particles.M(Idx) = Mass;
|
|
Particles.InvM(Idx) = 1.0 / Mass;
|
|
}
|
|
|
|
template <class TEvolutionPtr>
|
|
void InitTriMesh_EquilateralTri(
|
|
FTriangleMesh& TriMesh,
|
|
TEvolutionPtr& Evolution,
|
|
const Softs::FSolverVec3& XOffset=Softs::FSolverVec3(0))
|
|
{
|
|
auto& Particles = Evolution->Particles();
|
|
const uint32 InitialNumParticles = Particles.Size();
|
|
|
|
FTriangleMesh::InitEquilateralTriangleYZ(TriMesh, Particles);
|
|
|
|
// Initialize particles. Use 1/3 area of connected triangles for particle mass.
|
|
for (uint32 i = InitialNumParticles; i < Particles.Size(); i++)
|
|
{
|
|
Particles.SetX(i, Particles.GetX(i) + XOffset);
|
|
Particles.V(i) = Chaos::Softs::FSolverVec3(0);
|
|
Particles.M(i) = 0;
|
|
}
|
|
for (const Chaos::TVec3<int32>& Tri : TriMesh.GetElements())
|
|
{
|
|
const Softs::FSolverReal TriArea = 0.5 * Chaos::Softs::FSolverVec3::CrossProduct(
|
|
Particles.GetX(Tri[1]) - Particles.GetX(Tri[0]),
|
|
Particles.GetX(Tri[2]) - Particles.GetX(Tri[0])).Size();
|
|
for (int32 i = 0; i < 3; i++)
|
|
{
|
|
Particles.M(Tri[i]) += TriArea / 3;
|
|
}
|
|
}
|
|
for (uint32 i = InitialNumParticles; i < Particles.Size(); i++)
|
|
{
|
|
check(Particles.M(i) > SMALL_NUMBER);
|
|
Particles.InvM(i) = 1.0 / Particles.M(i);
|
|
}
|
|
}
|
|
|
|
template <class TEvolutionPtr>
|
|
void AddEdgeLengthConstraint(
|
|
TEvolutionPtr& Evolution,
|
|
const TArray<Chaos::TVec3<int32>>& Topology,
|
|
const Softs::FSolverReal Stiffness)
|
|
{
|
|
check(Stiffness >= 0. && Stiffness <= 1.);
|
|
// TODO: Use Add AddConstraintRuleRange
|
|
//Evolution->AddPBDConstraintFunction(
|
|
// [SpringConstraints = Chaos::FPBDSpringConstraints(
|
|
// Evolution->Particles(), Topology, Stiffness)](
|
|
// TPBDParticles<float, 3>& InParticles, const float Dt)
|
|
// {
|
|
// SpringConstraints.Apply(InParticles, Dt);
|
|
// });
|
|
}
|
|
|
|
template <class TEvolutionPtr>
|
|
void AddAxialConstraint(
|
|
TEvolutionPtr& Evolution,
|
|
TArray<Chaos::TVec3<int32>>&& Topology,
|
|
const Softs::FSolverReal Stiffness)
|
|
{
|
|
check(Stiffness >= 0. && Stiffness <= 1.);
|
|
// TODO: Use Add AddConstraintRuleRange
|
|
//Evolution->AddPBDConstraintFunction(
|
|
// [SpringConstraints = Chaos::FPBDAxialSpringConstraints(
|
|
// Evolution->Particles(), MoveTemp(Topology), Stiffness)](
|
|
// TPBDParticles<float, 3>& InParticles, const float Dt)
|
|
// {
|
|
// SpringConstraints.Apply(InParticles, Dt);
|
|
// });
|
|
}
|
|
|
|
template <class TEvolutionPtr>
|
|
void AdvanceTime(
|
|
TEvolutionPtr& Evolution,
|
|
const uint32 NumFrames=1,
|
|
const uint32 NumTimeStepsPerFrame=1,
|
|
const uint32 FPS=24)
|
|
{
|
|
check(NumTimeStepsPerFrame > 0);
|
|
Evolution->SetIterations(NumTimeStepsPerFrame);
|
|
|
|
check(FPS > 0);
|
|
const Softs::FSolverReal Dt = 1.0 / FPS;
|
|
for (uint32 i = 0; i < NumFrames; i++)
|
|
{
|
|
Evolution->AdvanceOneTimeStep(Dt);
|
|
}
|
|
}
|
|
|
|
template <class TParticleContainer>
|
|
TArray<Chaos::Softs::FSolverVec3> CopyPoints(const TParticleContainer& Particles)
|
|
{
|
|
TArray<Chaos::Softs::FSolverVec3> Points;
|
|
Points.SetNum(Particles.Size());
|
|
|
|
for (uint32 i = 0; i < Particles.Size(); i++)
|
|
Points[i] = Particles.GetX(i);
|
|
|
|
return Points;
|
|
}
|
|
|
|
template <class TParticleContainer>
|
|
void Reset(TParticleContainer& Particles, const TArray<Chaos::Softs::FSolverVec3>& Points)
|
|
{
|
|
for (uint32 i = 0; i < Particles.Size(); i++)
|
|
{
|
|
Particles.SetX(i, Points[i]);
|
|
Particles.V(i) = Chaos::Softs::FSolverVec3(0);
|
|
}
|
|
}
|
|
|
|
TArray<Softs::FSolverVec3> GetDifference(const TArray<Softs::FSolverVec3>& A, const TArray<Softs::FSolverVec3>& B)
|
|
{
|
|
TArray<Softs::FSolverVec3> C;
|
|
C.SetNum(A.Num());
|
|
for (int32 i = 0; i < A.Num(); i++)
|
|
C[i] = A[i] - B[i];
|
|
return C;
|
|
}
|
|
|
|
TArray<Softs::FSolverReal> GetMagnitude(const TArray<Chaos::Softs::FSolverVec3>& V)
|
|
{
|
|
TArray<Softs::FSolverReal> M;
|
|
M.SetNum(V.Num());
|
|
for (int32 i = 0; i < V.Num(); i++)
|
|
M[i] = V[i].Size();
|
|
return M;
|
|
}
|
|
|
|
template <class T>
|
|
bool AllSame(const TArray<T>& V, int32& Idx, const T Tolerance=KINDA_SMALL_NUMBER)
|
|
{
|
|
if (!V.Num())
|
|
return true;
|
|
const T& V0 = V[0];
|
|
for (int32 i = 1; i < V.Num(); i++)
|
|
{
|
|
if (!FMath::IsNearlyEqual(V0, V[i], Tolerance))
|
|
{
|
|
Idx = i;
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
template <class TV>
|
|
bool RunDropTest(
|
|
TUniquePtr<Softs::FPBDEvolution>& Evolution,
|
|
const Softs::FSolverReal GravMag,
|
|
const TV& GravDir,
|
|
const TArray<TV>& InitialPoints,
|
|
const int32 SubFrameSteps,
|
|
const Softs::FSolverReal DistTolerance,
|
|
const char* TestID)
|
|
{
|
|
const Softs::FSolverReal PreTime = Evolution->GetTime();
|
|
AdvanceTime(Evolution, 24, SubFrameSteps); // 1 second
|
|
const Softs::FSolverReal PostTime = Evolution->GetTime();
|
|
EXPECT_NEAR(PostTime-PreTime, 1.0, KINDA_SMALL_NUMBER)
|
|
<< TestID
|
|
<< "Evolution advanced time by " << (PostTime - PreTime)
|
|
<< " seconds, expected 1.0 seconds.";
|
|
|
|
const TArray<Softs::FSolverVec3> PostPoints = CopyPoints(Evolution->Particles());
|
|
const TArray<Softs::FSolverVec3> Diff = GetDifference(PostPoints, InitialPoints);
|
|
const TArray<Softs::FSolverReal> ScalarDiff = GetMagnitude(Diff);
|
|
|
|
// All points did the same thing
|
|
int32 Idx = 0;
|
|
EXPECT_TRUE(AllSame(ScalarDiff, Idx, (Softs::FSolverReal)0.1))
|
|
<< TestID
|
|
<< "Points fell different distances - Index 0: "
|
|
<< ScalarDiff[0] << " != Index "
|
|
<< Idx << ": "
|
|
<< ScalarDiff[Idx] << " +/- 0.1.";
|
|
|
|
// Fell the right amount
|
|
EXPECT_NEAR(ScalarDiff[0], (Softs::FSolverReal)0.5 * GravMag, DistTolerance)
|
|
<< TestID
|
|
<< "Points fell by " << ScalarDiff[0]
|
|
<< ", expected " << ((Softs::FSolverReal)0.5 * GravMag)
|
|
<< " +/- " << DistTolerance << ".";
|
|
|
|
// Fell the right direction
|
|
const Softs::FSolverReal DirDot = Chaos::Softs::FSolverVec3::DotProduct(GravDir, Diff[0].GetSafeNormal());
|
|
EXPECT_NEAR(DirDot, (Softs::FSolverReal)1.0, (Softs::FSolverReal)KINDA_SMALL_NUMBER)
|
|
<< TestID
|
|
<< "Points fell in different directions.";
|
|
|
|
return true;
|
|
}
|
|
|
|
void DeformableGravity()
|
|
{
|
|
const Softs::FSolverReal DistTol = 0.0002;
|
|
|
|
//
|
|
// Initialize solver and gravity
|
|
//
|
|
|
|
TUniquePtr<Softs::FPBDEvolution> Evolution = InitPBDEvolution();
|
|
|
|
const Softs::FSolverVec3 GravDir(0, 0, -1);
|
|
const Softs::FSolverReal GravMag = 980.665;
|
|
|
|
//
|
|
// Drop a single particle
|
|
//
|
|
|
|
InitSingleParticle(Evolution);
|
|
TArray<Softs::FSolverVec3> InitialPoints = CopyPoints(Evolution->Particles());
|
|
|
|
RunDropTest(Evolution, GravMag, GravDir, InitialPoints, 1, DistTol, "Single point falling under gravity, iters: 1 - ");
|
|
Reset(Evolution->Particles(), InitialPoints);
|
|
RunDropTest(Evolution, GravMag, GravDir, InitialPoints, 100, DistTol, "Single point falling under gravity, iters: 100 - ");
|
|
Reset(Evolution->Particles(), InitialPoints);
|
|
|
|
//
|
|
// Add a triangle mesh
|
|
//
|
|
|
|
Chaos::FTriangleMesh TriMesh;
|
|
InitTriMesh_EquilateralTri(TriMesh, Evolution);
|
|
InitialPoints = CopyPoints(Evolution->Particles());
|
|
|
|
//
|
|
// Points falling under gravity
|
|
//
|
|
|
|
RunDropTest(Evolution, GravMag, GravDir, InitialPoints, 1, DistTol, "Points falling under gravity, iters: 1 - ");
|
|
Reset(Evolution->Particles(), InitialPoints);
|
|
RunDropTest(Evolution, GravMag, GravDir, InitialPoints, 100, DistTol, "Points falling under gravity, iters: 100 - ");
|
|
Reset(Evolution->Particles(), InitialPoints);
|
|
|
|
//
|
|
// Points falling under gravity with edge length constraint
|
|
//
|
|
|
|
AddEdgeLengthConstraint(Evolution, TriMesh.GetSurfaceElements(), 1.0);
|
|
|
|
RunDropTest(Evolution, GravMag, GravDir, InitialPoints, 1, DistTol, "Points falling under gravity & edge cnstr, iters: 1 - ");
|
|
Reset(Evolution->Particles(), InitialPoints);
|
|
RunDropTest(Evolution, GravMag, GravDir, InitialPoints, 100, DistTol, "Points falling under gravity & edge cnstr, iters: 100 - ");
|
|
Reset(Evolution->Particles(), InitialPoints);
|
|
}
|
|
|
|
void EdgeConstraints()
|
|
{
|
|
TUniquePtr<Softs::FPBDEvolution> Evolution = InitPBDEvolution();
|
|
FTriangleMesh TriMesh;
|
|
auto& Particles = Evolution->Particles();
|
|
Particles.AddParticles(2145);
|
|
TArray<TVec3<int32>> Triangles;
|
|
Triangles.SetNum(2048);
|
|
// 32 n, 32 m
|
|
// 6 + 4*(n-1) + (m - 1)(3 + 2*(n-1)) = 2*n*m
|
|
for (int32 TriIndex = 0; TriIndex < 2048; TriIndex++)
|
|
{
|
|
int32 i = ((float)rand()) / ((float)RAND_MAX) * 2144;
|
|
int32 j = ((float)rand()) / ((float)RAND_MAX) * 2144;
|
|
int32 k = ((float)rand()) / ((float)RAND_MAX) * 2144;
|
|
Triangles[TriIndex] = TVec3<int32>(i, j, k);
|
|
}
|
|
AddEdgeLengthConstraint(Evolution, Triangles, 1.0);
|
|
AddAxialConstraint(Evolution, MoveTemp(Triangles), 1.0);
|
|
}
|
|
|
|
void ClothCollection()
|
|
{
|
|
FManagedArrayCollection ManagedArrayCollection;
|
|
|
|
const FName DataGroup("Data");
|
|
TManagedArray<int32> Value;
|
|
ManagedArrayCollection.AddExternalAttribute<int32>("Value", DataGroup, Value);
|
|
|
|
const FName ViewGroup("View");
|
|
FManagedArrayCollection::FConstructionParameters DataDependency(DataGroup);
|
|
TManagedArray<int32> ValueStart;
|
|
TManagedArray<int32> ValueEnd;
|
|
TManagedArray<int32> Id;
|
|
ManagedArrayCollection.AddExternalAttribute<int32>("ValueStart", ViewGroup, ValueStart, DataDependency);
|
|
ManagedArrayCollection.AddExternalAttribute<int32>("ValueEnd", ViewGroup, ValueEnd, DataDependency);
|
|
ManagedArrayCollection.AddExternalAttribute<int32>("Id", ViewGroup, Id);
|
|
|
|
auto InsertView = [&ManagedArrayCollection, &Value, &ValueStart, &ValueEnd, &Id, &DataGroup, &ViewGroup](int32 ViewPosition, int32 NumValues)
|
|
{
|
|
check(ViewPosition <= ManagedArrayCollection.NumElements(ViewGroup));
|
|
EXPECT_TRUE(ManagedArrayCollection.InsertElements(1, ViewPosition, ViewGroup) == ViewPosition);
|
|
|
|
static int32 ViewId = 0;
|
|
Id[ViewPosition] = ViewId++;
|
|
|
|
if (NumValues)
|
|
{
|
|
int32 PrevValuePosition = INDEX_NONE;
|
|
for (int32 Index = ViewPosition - 1; Index >= 0; --Index)
|
|
{
|
|
if (ValueEnd[Index] != INDEX_NONE)
|
|
{
|
|
PrevValuePosition = ValueEnd[Index];
|
|
break;
|
|
}
|
|
}
|
|
const int32 ValuePosition = PrevValuePosition + 1;
|
|
|
|
EXPECT_TRUE(ManagedArrayCollection.InsertElements(NumValues, ValuePosition, DataGroup) == ValuePosition);
|
|
|
|
ValueStart[ViewPosition] = ValuePosition;
|
|
ValueEnd[ViewPosition] = ValuePosition + NumValues - 1;
|
|
|
|
for (int32 ValueIndex = ValueStart[ViewPosition]; ValueIndex <= ValueEnd[ViewPosition]; ++ValueIndex)
|
|
{
|
|
Value[ValueIndex] = Id[ViewPosition];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ValueStart[ViewPosition] = INDEX_NONE;
|
|
ValueEnd[ViewPosition] = INDEX_NONE;
|
|
}
|
|
};
|
|
|
|
auto RemoveViews = [&ManagedArrayCollection, &ValueStart, &ValueEnd, &DataGroup, &ViewGroup](int32 ViewPosition, int32 NumViews)
|
|
{
|
|
const int32 ViewStart = ViewPosition;
|
|
const int32 ViewEnd = ViewPosition + NumViews - 1;
|
|
|
|
for (int32 ViewIndex = ViewStart; ViewIndex <= ViewEnd; ++ViewIndex)
|
|
{
|
|
// Remove data
|
|
const int32 Position = ValueStart[ViewIndex];
|
|
if (Position != INDEX_NONE)
|
|
{
|
|
const int32 NumValues = ValueEnd[ViewIndex] - ValueStart[ViewIndex] + 1;
|
|
ManagedArrayCollection.RemoveElements(DataGroup, NumValues, Position);
|
|
}
|
|
}
|
|
// Remove views
|
|
ManagedArrayCollection.RemoveElements(ViewGroup, NumViews, ViewPosition);
|
|
};
|
|
|
|
auto SetViewSize = [&ManagedArrayCollection, &Value, &ValueStart, &ValueEnd, &Id, &DataGroup, &ViewGroup](int32 ViewIndex, int32 InNumValues)
|
|
{
|
|
check(InNumValues >= 0);
|
|
|
|
int32& Start = ValueStart[ViewIndex];
|
|
int32& End = ValueEnd[ViewIndex];
|
|
check(Start != INDEX_NONE || End == INDEX_NONE);
|
|
|
|
const int32 NumValues = (Start == INDEX_NONE) ? 0 : End - Start + 1;
|
|
|
|
if (const int32 Delta = InNumValues - NumValues)
|
|
{
|
|
if (Delta > 0)
|
|
{
|
|
// Find a previous valid index range to insert after when the range is empty
|
|
auto ComputeEnd = [&ValueEnd](int32 ViewIndex)->int32
|
|
{
|
|
for (int32 Index = ViewIndex; Index >= 0; --Index)
|
|
{
|
|
if (ValueEnd[Index] != INDEX_NONE)
|
|
{
|
|
return ValueEnd[Index];
|
|
}
|
|
}
|
|
return INDEX_NONE;
|
|
};
|
|
|
|
// Grow the array
|
|
const int32 Position = ComputeEnd(ViewIndex) + 1;
|
|
ManagedArrayCollection.InsertElements(Delta, Position, DataGroup);
|
|
|
|
// Update Start/End
|
|
if (!NumValues)
|
|
{
|
|
Start = Position;
|
|
}
|
|
End = Start + InNumValues - 1;
|
|
|
|
// Fill the test values with the id check
|
|
for (int32 ValueIndex = Start; ValueIndex <= End; ++ValueIndex)
|
|
{
|
|
Value[ValueIndex] = Id[ViewIndex];
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
// Shrink the array
|
|
const int32 Position = Start + InNumValues;
|
|
ManagedArrayCollection.RemoveElements(DataGroup, -Delta, Position);
|
|
|
|
// Update Start/End
|
|
if (InNumValues)
|
|
{
|
|
End = Position - 1;
|
|
}
|
|
else
|
|
{
|
|
End = Start = INDEX_NONE;
|
|
}
|
|
}
|
|
}
|
|
const int32 Size = ValueEnd[ViewIndex] - ValueStart[ViewIndex] + 1;
|
|
check(
|
|
(InNumValues == 0 && ValueStart[ViewIndex] == INDEX_NONE && ValueEnd[ViewIndex] == INDEX_NONE) ||
|
|
(InNumValues == Size && ValueStart[ViewIndex] != INDEX_NONE && ValueEnd[ViewIndex] != INDEX_NONE));
|
|
};
|
|
|
|
auto HasKeptIntegrity = [&Value , &ValueStart, &ValueEnd, &Id]()->bool
|
|
{
|
|
EXPECT_TRUE(ValueEnd.Num() == ValueStart.Num() && Id.Num() == ValueStart.Num());
|
|
for (int32 ViewIndex = 0; ViewIndex < ValueStart.Num(); ++ViewIndex)
|
|
{
|
|
if (ValueStart[ViewIndex] == INDEX_NONE || ValueEnd[ViewIndex] == INDEX_NONE)
|
|
{
|
|
if (ValueStart[ViewIndex] != ValueEnd[ViewIndex])
|
|
{
|
|
return false;
|
|
}
|
|
continue;
|
|
}
|
|
for (int32 ValueIndex = ValueStart[ViewIndex]; ValueIndex <= ValueEnd[ViewIndex]; ++ValueIndex)
|
|
{
|
|
if (Value[ValueIndex] != Id[ViewIndex])
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
|
|
auto HasKeptOrder = [&Value, &ValueStart, &ValueEnd, &Id]()->bool
|
|
{
|
|
int32 PrevValueEnd = -1;
|
|
for (int32 ViewIndex = 0; ViewIndex < ValueStart.Num(); ++ViewIndex)
|
|
{
|
|
if (ValueStart[ViewIndex] == INDEX_NONE)
|
|
{
|
|
check(ValueEnd[ViewIndex] == INDEX_NONE);
|
|
continue;
|
|
}
|
|
if (ValueStart[ViewIndex] != PrevValueEnd + 1)
|
|
{
|
|
return false;
|
|
}
|
|
PrevValueEnd = ValueEnd[ViewIndex];
|
|
}
|
|
return PrevValueEnd == Value.Num() - 1;
|
|
};
|
|
|
|
// Insert 5 views from the start of the array
|
|
for (int32 Index = 0; Index < 5; ++Index)
|
|
{
|
|
InsertView(0, (Index + 1) * 100);
|
|
}
|
|
// Insert 2 more views for testing consecutive empty views
|
|
for (int32 Index = 0; Index < 2; ++Index)
|
|
{
|
|
InsertView(0, 0);
|
|
}
|
|
EXPECT_TRUE(HasKeptIntegrity());
|
|
EXPECT_TRUE(HasKeptOrder());
|
|
|
|
// Add 1 view in the middle of the array
|
|
InsertView(ManagedArrayCollection.NumElements(ViewGroup) / 2, 500);
|
|
EXPECT_TRUE(HasKeptIntegrity());
|
|
EXPECT_TRUE(HasKeptOrder());
|
|
|
|
// Add 1 view at the end of the array
|
|
InsertView(ManagedArrayCollection.NumElements(ViewGroup), 1000);
|
|
EXPECT_TRUE(HasKeptIntegrity());
|
|
EXPECT_TRUE(HasKeptOrder());
|
|
|
|
// Remove 1 view from the start of the array
|
|
RemoveViews(0, 1);
|
|
EXPECT_TRUE(HasKeptIntegrity());
|
|
EXPECT_TRUE(HasKeptOrder());
|
|
|
|
// Remove 1 view from the end of the array
|
|
RemoveViews(ManagedArrayCollection.NumElements(ViewGroup) - 1, 1);
|
|
EXPECT_TRUE(HasKeptIntegrity());
|
|
EXPECT_TRUE(HasKeptOrder());
|
|
|
|
// Remove 2 views from the middle
|
|
RemoveViews(ManagedArrayCollection.NumElements(ViewGroup) / 2, 2);
|
|
EXPECT_TRUE(HasKeptIntegrity());
|
|
EXPECT_TRUE(HasKeptOrder());
|
|
|
|
// Remove all remaining 5 views
|
|
RemoveViews(0, 5);
|
|
EXPECT_TRUE(ManagedArrayCollection.NumElements(ViewGroup) == 0);
|
|
EXPECT_TRUE(ManagedArrayCollection.NumElements(DataGroup) == 0);
|
|
|
|
// Insert 5 empty views
|
|
for (int32 Index = 0; Index < 5; ++Index)
|
|
{
|
|
InsertView(0, 0);
|
|
}
|
|
EXPECT_TRUE(HasKeptIntegrity());
|
|
EXPECT_TRUE(HasKeptOrder());
|
|
|
|
// Resize view 1
|
|
SetViewSize(1, 11);
|
|
EXPECT_TRUE(HasKeptIntegrity());
|
|
EXPECT_TRUE(HasKeptOrder());
|
|
|
|
// Resize view 2
|
|
SetViewSize(2, 22);
|
|
EXPECT_TRUE(HasKeptIntegrity());
|
|
EXPECT_TRUE(HasKeptOrder());
|
|
|
|
// Resize view 3
|
|
SetViewSize(3, 33);
|
|
EXPECT_TRUE(HasKeptIntegrity());
|
|
EXPECT_TRUE(HasKeptOrder());
|
|
|
|
// Remove view 2
|
|
RemoveViews(2, 1);
|
|
EXPECT_TRUE(HasKeptIntegrity());
|
|
EXPECT_TRUE(HasKeptOrder());
|
|
|
|
// Resize view 1 to 0
|
|
SetViewSize(1, 0);
|
|
EXPECT_TRUE(HasKeptIntegrity());
|
|
EXPECT_TRUE(HasKeptOrder());
|
|
|
|
// Resize view 3 to 33
|
|
SetViewSize(3, 33);
|
|
EXPECT_TRUE(HasKeptIntegrity());
|
|
EXPECT_TRUE(HasKeptOrder());
|
|
|
|
// Resize view 3 to 0
|
|
SetViewSize(3, 0);
|
|
EXPECT_TRUE(HasKeptIntegrity());
|
|
EXPECT_TRUE(HasKeptOrder());
|
|
|
|
// Resize view 0
|
|
SetViewSize(0, 1);
|
|
EXPECT_TRUE(HasKeptIntegrity());
|
|
EXPECT_TRUE(HasKeptOrder());
|
|
}
|
|
} // namespace ChaosTest
|