// Accepts a triangle (XYZ and UV values for each point) and returns a poly base and UV vectors // NOTE : the UV coords should be scaled by the texture size static inline void FTexCoordsToVectors(const FVector& V0, const FVector& UV0, const FVector& V1, const FVector& InUV1, const FVector& V2, const FVector& InUV2, FVector* InBaseResult, FVector* InUResult, FVector* InVResult ) { // Create polygon normal. FVector PN = FVector((V0-V1) ^ (V2-V0)); PN = PN.SafeNormal(); FVector UV1( InUV1 ); FVector UV2( InUV2 ); // Fudge UV's to make sure no infinities creep into UV vector math, whenever we detect identical U or V's. if( ( UV0.X == UV1.X ) || ( UV2.X == UV1.X ) || ( UV2.X == UV0.X ) || ( UV0.Y == UV1.Y ) || ( UV2.Y == UV1.Y ) || ( UV2.Y == UV0.Y ) ) { UV1 += FVector(0.004173f,0.004123f,0.0f); UV2 += FVector(0.003173f,0.003123f,0.0f); } // // Solve the equations to find our texture U/V vectors 'TU' and 'TV' by stacking them // into a 3x3 matrix , one for u(t) = TU dot (x(t)-x(o) + u(o) and one for v(t)= TV dot (.... , // then the third assumes we're perpendicular to the normal. // FMatrix TexEqu = FMatrix::Identity; TexEqu.SetAxis( 0, FVector( V1.X - V0.X, V1.Y - V0.Y, V1.Z - V0.Z ) ); TexEqu.SetAxis( 1, FVector( V2.X - V0.X, V2.Y - V0.Y, V2.Z - V0.Z ) ); TexEqu.SetAxis( 2, FVector( PN.X, PN.Y, PN.Z ) ); TexEqu = TexEqu.InverseFast(); const FVector UResult( UV1.X-UV0.X, UV2.X-UV0.X, 0.0f ); const FVector TUResult = TexEqu.TransformVector( UResult ); const FVector VResult( UV1.Y-UV0.Y, UV2.Y-UV0.Y, 0.0f ); const FVector TVResult = TexEqu.TransformVector( VResult ); // // Adjust the BASE to account for U0 and V0 automatically, and force it into the same plane. // FMatrix BaseEqu = FMatrix::Identity; BaseEqu.SetAxis( 0, TUResult ); BaseEqu.SetAxis( 1, TVResult ); BaseEqu.SetAxis( 2, FVector( PN.X, PN.Y, PN.Z ) ); BaseEqu = BaseEqu.InverseFast(); const FVector BResult = FVector( UV0.X - ( TUResult|V0 ), UV0.Y - ( TVResult|V0 ), 0.0f ); *InBaseResult = - 1.0f * BaseEqu.TransformVector( BResult ); *InUResult = TUResult; *InVResult = TVResult; }
void UDestructibleComponent::ApplyDamage(float DamageAmount, const FVector& HitLocation, const FVector& ImpulseDir, float ImpulseStrength) { #if WITH_APEX if (ApexDestructibleActor != NULL) { const FVector& NormalizedImpactDir = ImpulseDir.SafeNormal(); // Transfer damage information to the APEX NxDestructibleActor interface ApexDestructibleActor->applyDamage(DamageAmount, ImpulseStrength, U2PVector( HitLocation ), U2PVector( ImpulseDir )); } #endif }
void UFloatingPawnMovement::ApplyControlInputToVelocity(float DeltaTime) { const FVector ControlAcceleration = GetPendingInputVector().ClampMaxSize(1.f); const float AnalogInputModifier = (ControlAcceleration.SizeSquared() > 0.f ? ControlAcceleration.Size() : 0.f); const float MaxPawnSpeed = GetMaxSpeed() * AnalogInputModifier; const bool bExceedingMaxSpeed = IsExceedingMaxSpeed(MaxPawnSpeed); if (AnalogInputModifier > 0.f && !bExceedingMaxSpeed) { // Apply change in velocity direction if (Velocity.SizeSquared() > 0.f) { Velocity -= (Velocity - ControlAcceleration * Velocity.Size()) * FMath::Min(DeltaTime * 8.f, 1.f); } } else { // Dampen velocity magnitude based on deceleration. if (Velocity.SizeSquared() > 0.f) { const FVector OldVelocity = Velocity; const float VelSize = FMath::Max(Velocity.Size() - FMath::Abs(Deceleration) * DeltaTime, 0.f); Velocity = Velocity.SafeNormal() * VelSize; // Don't allow braking to lower us below max speed if we started above it. if (bExceedingMaxSpeed && Velocity.SizeSquared() < FMath::Square(MaxPawnSpeed)) { Velocity = OldVelocity.SafeNormal() * MaxPawnSpeed; } } } // Apply acceleration and clamp velocity magnitude. const float NewMaxSpeed = (IsExceedingMaxSpeed(MaxPawnSpeed)) ? Velocity.Size() : MaxPawnSpeed; Velocity += ControlAcceleration * FMath::Abs(Acceleration) * DeltaTime; Velocity = Velocity.ClampMaxSize(NewMaxSpeed); ConsumeInputVector(); }
float UAnimInstance::CalculateDirection(const FVector & Velocity, const FRotator & BaseRotation) { FMatrix RotMatrix = FRotationMatrix(BaseRotation); FVector ForwardVector = RotMatrix.GetScaledAxis(EAxis::X); FVector RightVector = RotMatrix.GetScaledAxis(EAxis::Y); FVector NormalizedVel = Velocity.SafeNormal(); ForwardVector.Z = RightVector.Z = NormalizedVel.Z = 0.f; // get a cos(alpha) of forward vector vs velocity float ForwardCosAngle = FVector::DotProduct(ForwardVector, NormalizedVel); // now get the alpha and convert to degree float ForwardDeltaDegree = FMath::RadiansToDegrees( FMath::Acos(ForwardCosAngle) ); // depending on where right vector is, flip it float RightCosAngle = FVector::DotProduct(RightVector, NormalizedVel); if ( RightCosAngle < 0 ) { ForwardDeltaDegree *= -1; } return ForwardDeltaDegree; }
bool AGameplayAbilityTargetActor_GroundTrace::AdjustCollisionResultForShape(const FVector OriginalStartPoint, const FVector OriginalEndPoint, const FCollisionQueryParams Params, FHitResult& OutHitResult) const { UWorld *ThisWorld = GetWorld(); //Pull back toward player to find a better spot, accounting for the width of our object FVector Movement = (OriginalEndPoint - OriginalStartPoint); FVector MovementDirection = Movement.SafeNormal(); float MovementMagnitude2D = Movement.Size2D(); if (bDebug) { if (CollisionHeight > 0.0f) { DrawDebugCapsule(ThisWorld, OriginalEndPoint, CollisionHeight * 0.5f, CollisionRadius, FQuat::Identity, FColor::Black); } else { DrawDebugSphere(ThisWorld, OriginalEndPoint, CollisionRadius, 8, FColor::Black); } } if ((MovementMagnitude2D < (CollisionRadius * 2.0f)) || (CollisionRadius <= 1.0f)) { return false; //Bad case! } float IncrementSize = FMath::Clamp<float>(CollisionRadius * 0.5f, 25.0f, 250.0f); float LerpIncrement = IncrementSize / MovementMagnitude2D; FHitResult LocalResult; FVector TraceStart; FVector TraceEnd; //This needs to ramp up - the first few increments should be small, then we should start moving in larger steps. for (float LerpValue = CollisionRadius / MovementMagnitude2D; LerpValue < 1.0f; LerpValue += LerpIncrement) { TraceEnd = TraceStart = OriginalEndPoint - (LerpValue * Movement); TraceEnd.Z -= 99999.0f; ThisWorld->SweepSingle(LocalResult, TraceStart, TraceEnd, FQuat::Identity, TraceChannel, CollisionShape, Params); if (!LocalResult.bStartPenetrating) { if (!LocalResult.bBlockingHit) { //This is probably off the map and should not be considered valid. This should not happen in a non-debug map. if (bDebug) { if (CollisionHeight > 0.0f) { DrawDebugCapsule(ThisWorld, LocalResult.Location, CollisionHeight * 0.5f, CollisionRadius, FQuat::Identity, FColor::Yellow); } else { DrawDebugSphere(ThisWorld, LocalResult.Location, CollisionRadius, 8, FColor::Yellow); } } continue; //LocalResult.Location = TraceStart; } if (bDebug) { if (CollisionHeight > 0.0f) { DrawDebugCapsule(ThisWorld, LocalResult.Location, CollisionHeight * 0.5f, CollisionRadius, FQuat::Identity, FColor::Green); } else { DrawDebugSphere(ThisWorld, LocalResult.Location, CollisionRadius, 8, FColor::Green); } } OutHitResult = LocalResult; return true; } if (bDebug) { if (CollisionHeight > 0.0f) { DrawDebugCapsule(ThisWorld, TraceStart, CollisionHeight * 0.5f, CollisionRadius, FQuat::Identity, FColor::Red); } else { DrawDebugSphere(ThisWorld, TraceStart, CollisionRadius, 8, FColor::Red); } } } return false; }
void FAnimNode_Trail::EvaluateBoneTransforms(USkeletalMeshComponent* SkelComp, const FBoneContainer& RequiredBones, FA2CSPose& MeshBases, TArray<FBoneTransform>& OutBoneTransforms) { check(OutBoneTransforms.Num() == 0); if( ChainLength < 2 ) { return; } // The incoming BoneIndex is the 'end' of the spline chain. We need to find the 'start' by walking SplineLength bones up hierarchy. // Fail if we walk past the root bone. int32 WalkBoneIndex = TrailBone.BoneIndex; TArray<int32> ChainBoneIndices; ChainBoneIndices.AddZeroed(ChainLength); ChainBoneIndices[ChainLength - 1] = WalkBoneIndex; for (int32 i = 1; i < ChainLength; i++) { // returns to avoid a crash // @TODO : shows an error message why failed if (WalkBoneIndex == 0) { return; } // Get parent bone. WalkBoneIndex = RequiredBones.GetParentBoneIndex(WalkBoneIndex); //Insert indices at the start of array, so that parents are before children in the array. int32 TransformIndex = ChainLength - (i + 1); ChainBoneIndices[TransformIndex] = WalkBoneIndex; } OutBoneTransforms.AddZeroed(ChainLength); // If we have >0 this frame, but didn't last time, record positions of all the bones. // Also do this if number has changed or array is zero. bool bHasValidStrength = (Alpha > 0.f); if(TrailBoneLocations.Num() != ChainLength || (bHasValidStrength && !bHadValidStrength)) { TrailBoneLocations.Empty(); TrailBoneLocations.AddZeroed(ChainLength); for(int32 i=0; i<ChainBoneIndices.Num(); i++) { int32 ChildIndex = ChainBoneIndices[i]; FTransform ChainTransform = MeshBases.GetComponentSpaceTransform(ChildIndex); TrailBoneLocations[i] = ChainTransform.GetTranslation(); } OldLocalToWorld = SkelComp->GetTransformMatrix(); } bHadValidStrength = bHasValidStrength; // transform between last frame and now. FMatrix OldToNewTM = OldLocalToWorld * SkelComp->GetTransformMatrix().InverseFast(); // Add fake velocity if present to all but root bone if(!FakeVelocity.IsZero()) { FVector FakeMovement = -FakeVelocity * ThisTimstep; if (bActorSpaceFakeVel && SkelComp->GetOwner()) { const FTransform BoneToWorld(SkelComp->GetOwner()->GetActorRotation(), SkelComp->GetOwner()->GetActorLocation()); FakeMovement = BoneToWorld.TransformVector(FakeMovement); } FakeMovement = SkelComp->GetTransformMatrix().InverseTransformVector(FakeMovement); // Then add to each bone for(int32 i=1; i<TrailBoneLocations.Num(); i++) { TrailBoneLocations[i] += FakeMovement; } } // Root bone of trail is not modified. int32 RootIndex = ChainBoneIndices[0]; FTransform ChainTransform = MeshBases.GetComponentSpaceTransform(RootIndex); OutBoneTransforms[0] = FBoneTransform(RootIndex, ChainTransform); TrailBoneLocations[0] = ChainTransform.GetTranslation(); // Starting one below head of chain, move bones. for(int32 i=1; i<ChainBoneIndices.Num(); i++) { // Parent bone position in component space. int32 ParentIndex = ChainBoneIndices[i-1]; FVector ParentPos = TrailBoneLocations[i-1]; FVector ParentAnimPos = MeshBases.GetComponentSpaceTransform(ParentIndex).GetTranslation(); // Child bone position in component space. int32 ChildIndex = ChainBoneIndices[i]; FVector ChildPos = OldToNewTM.TransformPosition(TrailBoneLocations[i]); // move from 'last frames component' frame to 'this frames component' frame FVector ChildAnimPos = MeshBases.GetComponentSpaceTransform(ChildIndex).GetTranslation(); // Desired parent->child offset. FVector TargetDelta = (ChildAnimPos - ParentAnimPos); // Desired child position. FVector ChildTarget = ParentPos + TargetDelta; // Find vector from child to target FVector Error = ChildTarget - ChildPos; // Calculate how much to push the child towards its target float Correction = FMath::Clamp<float>(ThisTimstep * TrailRelaxation, 0.f, 1.f); // Scale correction vector and apply to get new world-space child position. TrailBoneLocations[i] = ChildPos + (Error * Correction); // If desired, prevent bones stretching too far. if(bLimitStretch) { float RefPoseLength = TargetDelta.Size(); FVector CurrentDelta = TrailBoneLocations[i] - TrailBoneLocations[i-1]; float CurrentLength = CurrentDelta.Size(); // If we are too far - cut it back (just project towards parent particle). if( (CurrentLength - RefPoseLength > StretchLimit) && CurrentLength > SMALL_NUMBER ) { FVector CurrentDir = CurrentDelta / CurrentLength; TrailBoneLocations[i] = TrailBoneLocations[i-1] + (CurrentDir * (RefPoseLength + StretchLimit)); } } // Modify child matrix OutBoneTransforms[i] = FBoneTransform(ChildIndex, MeshBases.GetComponentSpaceTransform(ChildIndex)); OutBoneTransforms[i].Transform.SetTranslation(TrailBoneLocations[i]); // Modify rotation of parent matrix to point at this one. // Calculate the direction that parent bone is currently pointing. FVector CurrentBoneDir = OutBoneTransforms[i-1].Transform.TransformVector( GetAlignVector(ChainBoneAxis, bInvertChainBoneAxis) ); CurrentBoneDir = CurrentBoneDir.SafeNormal(SMALL_NUMBER); // Calculate vector from parent to child. FVector NewBoneDir = FVector(OutBoneTransforms[i].Transform.GetTranslation() - OutBoneTransforms[i - 1].Transform.GetTranslation()).SafeNormal(SMALL_NUMBER); // Calculate a quaternion that gets us from our current rotation to the desired one. FQuat DeltaLookQuat = FQuat::FindBetween(CurrentBoneDir, NewBoneDir); FTransform DeltaTM( DeltaLookQuat, FVector(0.f) ); // Apply to the current parent bone transform. FTransform TmpMatrix = FTransform::Identity; TmpMatrix.CopyRotationPart(OutBoneTransforms[i - 1].Transform); TmpMatrix = TmpMatrix * DeltaTM; OutBoneTransforms[i - 1].Transform.CopyRotationPart(TmpMatrix); } // For the last bone in the chain, use the rotation from the bone above it. OutBoneTransforms[ChainLength - 1].Transform.CopyRotationPart(OutBoneTransforms[ChainLength - 2].Transform); // Update OldLocalToWorld OldLocalToWorld = SkelComp->GetTransformMatrix(); }
FRotator UKismetMathLibrary::RotatorFromAxisAndAngle(FVector Axis, float Angle) { FVector SafeAxis = Axis.SafeNormal(); // Make sure axis is unit length return FQuat(SafeAxis, FMath::DegreesToRadians(Angle)).Rotator(); }
FVector UKismetMathLibrary::MirrorVectorByNormal(FVector A, FVector B) { B = B.SafeNormal(); return A - 2.f * B * (B | A); }
FVector UKismetMathLibrary::Normal(FVector A) { return A.SafeNormal(); }
FVector UKismetMathLibrary::RotateAngleAxis(FVector InVect, float AngleDeg, FVector Axis) { return InVect.RotateAngleAxis(AngleDeg, Axis.SafeNormal()); }
void DrawDebugCone(const UWorld* InWorld, FVector const& Origin, FVector const& Direction, float Length, float AngleWidth, float AngleHeight, int32 NumSides, FColor const& DrawColor, bool bPersistentLines, float LifeTime, uint8 DepthPriority) { // no debug line drawing on dedicated server if (GEngine->GetNetMode(InWorld) != NM_DedicatedServer) { // Need at least 4 sides NumSides = FMath::Max(NumSides, 4); const float Angle1 = FMath::Clamp<float>(AngleHeight, (float)KINDA_SMALL_NUMBER, (float)(PI - KINDA_SMALL_NUMBER)); const float Angle2 = FMath::Clamp<float>(AngleWidth, (float)KINDA_SMALL_NUMBER, (float)(PI - KINDA_SMALL_NUMBER)); const float SinX_2 = FMath::Sin(0.5f * Angle1); const float SinY_2 = FMath::Sin(0.5f * Angle2); const float SinSqX_2 = SinX_2 * SinX_2; const float SinSqY_2 = SinY_2 * SinY_2; const float TanX_2 = FMath::Tan(0.5f * Angle1); const float TanY_2 = FMath::Tan(0.5f * Angle2); TArray<FVector> ConeVerts; ConeVerts.AddUninitialized(NumSides); for(int32 i = 0; i < NumSides; i++) { const float Fraction = (float)i/(float)(NumSides); const float Thi = 2.f * PI * Fraction; const float Phi = FMath::Atan2(FMath::Sin(Thi)*SinY_2, FMath::Cos(Thi)*SinX_2); const float SinPhi = FMath::Sin(Phi); const float CosPhi = FMath::Cos(Phi); const float SinSqPhi = SinPhi*SinPhi; const float CosSqPhi = CosPhi*CosPhi; const float RSq = SinSqX_2*SinSqY_2 / (SinSqX_2*SinSqPhi + SinSqY_2*CosSqPhi); const float R = FMath::Sqrt(RSq); const float Sqr = FMath::Sqrt(1-RSq); const float Alpha = R*CosPhi; const float Beta = R*SinPhi; ConeVerts[i].X = (1 - 2*RSq); ConeVerts[i].Y = 2 * Sqr * Alpha; ConeVerts[i].Z = 2 * Sqr * Beta; } // Calculate transform for cone. FVector YAxis, ZAxis; FVector DirectionNorm = Direction.SafeNormal(); DirectionNorm.FindBestAxisVectors(YAxis, ZAxis); const FMatrix ConeToWorld = FScaleMatrix(FVector(Length)) * FMatrix(DirectionNorm, YAxis, ZAxis, Origin); // this means foreground lines can't be persistent ULineBatchComponent* const LineBatcher = GetDebugLineBatcher( InWorld, bPersistentLines, LifeTime, (DepthPriority == SDPG_Foreground) ); if(LineBatcher != NULL) { float const LineLifeTime = (LifeTime > 0.f) ? LifeTime : LineBatcher->DefaultLifeTime; TArray<FBatchedLine> Lines; Lines.Empty(NumSides); FVector CurrentPoint, PrevPoint, FirstPoint; for(int32 i = 0; i < NumSides; i++) { CurrentPoint = ConeToWorld.TransformPosition(ConeVerts[i]); Lines.Add(FBatchedLine(ConeToWorld.GetOrigin(), CurrentPoint, DrawColor, LineLifeTime, 0.0f, DepthPriority)); // PrevPoint must be defined to draw junctions if( i > 0 ) { Lines.Add(FBatchedLine(PrevPoint, CurrentPoint, DrawColor, LineLifeTime, 0.0f, DepthPriority)); } else { FirstPoint = CurrentPoint; } PrevPoint = CurrentPoint; } // Connect last junction to first Lines.Add(FBatchedLine(CurrentPoint, FirstPoint, DrawColor, LineLifeTime, 0.0f, DepthPriority)); LineBatcher->DrawLines(Lines); } } }
int SceneIFC::DoUnSmoothVerts(VActor *Thing, INT DoTangentVectorSeams ) { // // Special Spherical-harmonics Tangent-space criterium -> unsmooth boundaries where there is // FaceDeterminants.Empty(); // Compute face tangents. Face tangent determinants whose signs don't match should not have smoothing between them (i.e. require // duplicated vertices. if( DoTangentVectorSeams ) { FaceDeterminants.AddZeroed( Thing->SkinData.Faces.Num()); for( INT FaceIndex=0; FaceIndex< Thing->SkinData.Faces.Num(); FaceIndex++) { FVector P1 = Thing->SkinData.Points[ Thing->SkinData.Wedges[ Thing->SkinData.Faces[FaceIndex].WedgeIndex[0] ].PointIndex ].Point; FVector P2 = Thing->SkinData.Points[ Thing->SkinData.Wedges[ Thing->SkinData.Faces[FaceIndex].WedgeIndex[1] ].PointIndex ].Point; FVector P3 = Thing->SkinData.Points[ Thing->SkinData.Wedges[ Thing->SkinData.Faces[FaceIndex].WedgeIndex[2] ].PointIndex ].Point; FVector TriangleNormal = FPlane(P3,P2,P1); FMatrix ParameterToLocal( FPlane( P2.X - P1.X, P2.Y - P1.Y, P2.Z - P1.Z, 0 ), FPlane( P3.X - P1.X, P3.Y - P1.Y, P3.Z - P1.Z, 0 ), FPlane( P1.X, P1.Y, P1.Z, 0 ), FPlane( 0, 0, 0, 1 ) ); FLOAT T1U= Thing->SkinData.Wedges[ Thing->SkinData.Faces[FaceIndex].WedgeIndex[0] ].UV.U; FLOAT T1V= Thing->SkinData.Wedges[ Thing->SkinData.Faces[FaceIndex].WedgeIndex[0] ].UV.V; FLOAT T2U= Thing->SkinData.Wedges[ Thing->SkinData.Faces[FaceIndex].WedgeIndex[1] ].UV.U; FLOAT T2V= Thing->SkinData.Wedges[ Thing->SkinData.Faces[FaceIndex].WedgeIndex[1] ].UV.V; FLOAT T3U= Thing->SkinData.Wedges[ Thing->SkinData.Faces[FaceIndex].WedgeIndex[2] ].UV.U; FLOAT T3V= Thing->SkinData.Wedges[ Thing->SkinData.Faces[FaceIndex].WedgeIndex[2] ].UV.V; FMatrix ParameterToTexture( FPlane( T2U - T1U, T2V - T1V, 0, 0 ), FPlane( T3U - T1U, T3V - T1V, 0, 0 ), FPlane( T1U, T1V, 1, 0 ), FPlane( 0, 0, 0, 1 ) ); FMatrix TextureToLocal = ParameterToTexture.Inverse() * ParameterToLocal; FVector TangentX = TextureToLocal.TransformNormal(FVector(1,0,0)).SafeNormal(), TangentY = TextureToLocal.TransformNormal(FVector(0,1,0)).SafeNormal(), TangentZ; TangentX = TangentX - TriangleNormal * (TangentX | TriangleNormal); TangentY = TangentY - TriangleNormal * (TangentY | TriangleNormal); TangentZ = TriangleNormal; TangentX.SafeNormal(); TangentY.SafeNormal(); TangentZ.SafeNormal(); FMatrix TangentBasis( TangentX, TangentY, TangentZ ); TangentBasis.Transpose(); // FaceDeterminants[FaceIndex] = TangentBasis.Determinant3x3(); } } // // Connectivity: triangles with non-matching smoothing groups will be physically split. // // -> Splitting involves: the UV+material-contaning vertex AND the 3d point. // // -> Tally smoothing groups for each and every (textured) vertex. // // -> Collapse: // -> start from a vertex and all its adjacent triangles - go over // each triangle - if any connecting one (sharing more than one vertex) gives a smoothing match, // accumulate it. Then IF more than one resulting section, // ensure each boundary 'vert' is split _if not already_ to give each smoothing group // independence from all others. // int DuplicatedVertCount = 0; int RemappedHoeks = 0; int TotalSmoothMatches = 0; int TotalConnexChex = 0; // Link _all_ faces to vertices. TArray<VertsFans> Fans; TArray<tInfluences> PointInfluences; TArray<tWedgeList> PointWedges; Fans.AddExactZeroed( Thing->SkinData.Points.Num() ); PointInfluences.AddExactZeroed( Thing->SkinData.Points.Num() ); PointWedges.AddExactZeroed( Thing->SkinData.Points.Num() ); {for(INT i=0; i< Thing->SkinData.RawWeights.Num(); i++) { PointInfluences[ Thing->SkinData.RawWeights[i].PointIndex ].RawInfIndices.AddItem( i ); }} {for(INT i=0; i< Thing->SkinData.Wedges.Num(); i++) { PointWedges[ Thing->SkinData.Wedges[i].PointIndex ].WedgeList.AddItem( i ); }} for(INT f=0; f< Thing->SkinData.Faces.Num(); f++ ) { // For each face, add a pointer to that face into the Fans[vertex]. for( INT i=0; i<3; i++) { INT WedgeIndex = Thing->SkinData.Faces[f].WedgeIndex[i]; INT PointIndex = Thing->SkinData.Wedges[ WedgeIndex ].PointIndex; tFaceRecord NewFR; NewFR.FaceIndex = f; NewFR.HoekIndex = i; NewFR.WedgeIndex = WedgeIndex; // This face touches the point courtesy of Wedges[Wedgeindex]. NewFR.SmoothFlags = Thing->SkinData.Faces[f].SmoothingGroups; NewFR.FanFlags = 0; Fans[ PointIndex ].FaceRecord.AddItem( NewFR ); Fans[ PointIndex ].FanGroupCount = 0; } } // Investigate connectivity and assign common group numbers (1..+) to the fans' individual FanFlags. for( INT p=0; p< Fans.Num(); p++) // The fan of faces for each 3d point 'p'. { // All faces connecting. if( Fans[p].FaceRecord.Num() > 0 ) { INT FacesProcessed = 0; TArray<tFaceSet> FaceSets; // Sets with indices INTO FANS, not into face array. // Digest all faces connected to this vertex (p) into one or more smooth sets. only need to check // all faces MINUS one.. while( FacesProcessed < Fans[p].FaceRecord.Num() ) { // One loop per group. For the current ThisFaceIndex, tally all truly connected ones // and put them in a new Tarray. Once no more can be connected, stop. INT NewSetIndex = FaceSets.Num(); // 0 to start FaceSets.AddZeroed(1); // first one will be just ThisFaceIndex. // Find the first non-processed face. There will be at least one. INT ThisFaceFanIndex = 0; { INT SearchIndex = 0; while( Fans[p].FaceRecord[SearchIndex].FanFlags == -1 ) // -1 indicates already processed. { SearchIndex++; } ThisFaceFanIndex = SearchIndex; //Fans[p].FaceRecord[SearchIndex].FaceIndex; } // Inital face. FaceSets[ NewSetIndex ].Faces.AddItem( ThisFaceFanIndex ); // Add the unprocessed Face index to the "local smoothing group" [NewSetIndex]. Fans[p].FaceRecord[ThisFaceFanIndex].FanFlags = -1; // Mark as processed. FacesProcessed++; // Find all faces connected to this face, and if there's any // smoothing group matches, put it in current face set and mark it as processed; // until no more match. INT NewMatches = 0; do { NewMatches = 0; // Go over all current faces in this faceset and set if the FaceRecord (local smoothing groups) has any matches. // there will be at least one face already in this faceset - the first face in the fan. for( INT n=0; n< FaceSets[NewSetIndex].Faces.Num(); n++) { INT HookFaceIdx = Fans[p].FaceRecord[ FaceSets[NewSetIndex].Faces[n] ].FaceIndex; //Go over the fan looking for matches. for( INT s=0; s< Fans[p].FaceRecord.Num(); s++) { // Skip if same face, skip if face already processed. if( ( HookFaceIdx != Fans[p].FaceRecord[s].FaceIndex ) && ( Fans[p].FaceRecord[s].FanFlags != -1 )) { TotalConnexChex++; // Process if connected with more than one vertex, AND smooth.. if( FacesAreSmoothlyConnected( Thing, HookFaceIdx, Fans[p].FaceRecord[s].FaceIndex ) ) { TotalSmoothMatches++; Fans[p].FaceRecord[s].FanFlags = -1; // Mark as processed. FacesProcessed++; // Add FaceSets[NewSetIndex].Faces.AddItem( s ); // Store FAN index of this face index into smoothing group's faces. // Tally NewMatches++; } } // not the same... }// all faces in fan } // all faces in FaceSet }while( NewMatches ); }// Repeat until all faces processed. // For the new non-initialized face sets, // Create a new point, influences, and uv-vertex(-ices) for all individual FanFlag groups with an index of 2+ and also remap // the face's vertex into those new ones. if( FaceSets.Num() > 1 ) { for( INT f=1; f<FaceSets.Num(); f++ ) { // We duplicate the current vertex. (3d point) INT NewPointIndex = Thing->SkinData.Points.Num(); Thing->SkinData.Points.AddItem( Thing->SkinData.Points[p] ); DuplicatedVertCount++; // Duplicate all related weights. for( INT t=0; t< PointInfluences[p].RawInfIndices.Num(); t++ ) { // Add new weight INT NewWeightIndex = Thing->SkinData.RawWeights.Num(); Thing->SkinData.RawWeights.AddItem( Thing->SkinData.RawWeights[ PointInfluences[p].RawInfIndices[t] ] ); Thing->SkinData.RawWeights[NewWeightIndex].PointIndex = NewPointIndex; } // Duplicate any and all Wedges associated with it; and all Faces' wedges involved. for( INT w=0; w< PointWedges[p].WedgeList.Num(); w++) { INT OldWedgeIndex = PointWedges[p].WedgeList[w]; INT NewWedgeIndex = Thing->SkinData.Wedges.Num(); Thing->SkinData.Wedges.AddItem( Thing->SkinData.Wedges[ OldWedgeIndex ] ); Thing->SkinData.Wedges[ NewWedgeIndex ].PointIndex = NewPointIndex; // Update relevant face's Wedges. Inelegant: just check all associated faces for every new wedge. for( INT s=0; s< FaceSets[f].Faces.Num(); s++) { INT FanIndex = FaceSets[f].Faces[s]; if( Fans[p].FaceRecord[ FanIndex ].WedgeIndex == OldWedgeIndex ) { // Update just the right one for this face (HoekIndex!) Thing->SkinData.Faces[ Fans[p].FaceRecord[ FanIndex].FaceIndex ].WedgeIndex[ Fans[p].FaceRecord[ FanIndex ].HoekIndex ] = NewWedgeIndex; RemappedHoeks++; } } } } } // if FaceSets.Num(). -> duplicate stuff }// while( FacesProcessed < Fans[p].FaceRecord.Num() ) } // Fans for each 3d point return DuplicatedVertCount; }