UPhysicalMaterial::UPhysicalMaterial(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { Friction = 0.7f; Restitution = 0.3f; RaiseMassToPower = 0.75f; Density = 1.0f; DestructibleDamageThresholdScale = 1.0f; TireFrictionScale = 1.0f; bOverrideFrictionCombineMode = false; #if WITH_PHYSX PhysxUserData = FPhysxUserData(this); #endif }
/** * Create physics engine constraint. */ void FConstraintInstance::InitConstraint(USceneComponent* Owner, FBodyInstance* Body1, FBodyInstance* Body2, float Scale) { OwnerComponent = Owner; #if WITH_PHYSX PhysxUserData = FPhysxUserData(this); // if there's already a constraint, get rid of it first if (ConstraintData) { TermConstraint(); } PxRigidActor* PActor1 = nullptr; PxRigidActor* PActor2 = nullptr; PxScene* PScene = GetPScene_LockFree(Body1, Body2); SCOPED_SCENE_WRITE_LOCK(PScene); const bool bValidConstraintSetup = PScene && GetPActors_AssumesLocked(Body1, Body2, &PActor1, &PActor2) && CreatePxJoint_AssumesLocked(PActor1, PActor2, PScene, Scale); if (!bValidConstraintSetup) { return; } // update mass UpdateAverageMass_AssumesLocked(PActor1, PActor2); //flags and projection settings UpdateConstraintFlags_AssumesLocked(); //limits UpdateAngularLimit(); UpdateLinearLimit(); //breakable UpdateBreakable(); //motors SetLinearDriveParams(LinearDriveSpring, LinearDriveDamping, LinearDriveForceLimit); SetAngularDriveParams(AngularDriveSpring, AngularDriveDamping, AngularDriveForceLimit); UpdateDriveTarget(); EnsureSleepingActorsStaySleeping_AssumesLocked(PActor1, PActor2); #endif // WITH_PHYSX }
/** Exposes creation of physics-engine scene outside Engine (for use with PhAT for example). */ FPhysScene::FPhysScene() #if WITH_APEX : PendingApexDamageManager(new FPendingApexDamageManager) #endif { LineBatcher = NULL; OwningWorld = NULL; #if WITH_PHYSX #if WITH_VEHICLE VehicleManager = NULL; #endif PhysxUserData = FPhysxUserData(this); // Create dispatcher for tasks if (PhysSingleThreadedMode()) { CPUDispatcher = new FPhysXCPUDispatcherSingleThread(); } else { CPUDispatcher = new FPhysXCPUDispatcher(); } // Create sim event callback SimEventCallback = new FPhysXSimEventCallback(); #endif //#if WITH_PHYSX // initialize console variable - this console variable change requires it to restart scene. static bool bInitializeConsoleVariable = true; static float InitialAverageFrameRate = 0.016f; UPhysicsSettings * PhysSetting = UPhysicsSettings::Get(); if (bInitializeConsoleVariable) { InitialAverageFrameRate = PhysSetting->InitialAverageFrameRate; FrameTimeSmoothingFactor[PST_Sync] = PhysSetting->SyncSceneSmoothingFactor; FrameTimeSmoothingFactor[PST_Async] = PhysSetting->AsyncSceneSmoothingFactor; bInitializeConsoleVariable = false; } #if WITH_SUBSTEPPING bSubstepping = PhysSetting->bSubstepping; bSubsteppingAsync = PhysSetting->bSubsteppingAsync; #endif bAsyncSceneEnabled = PhysSetting->bEnableAsyncScene; NumPhysScenes = bAsyncSceneEnabled ? PST_Async + 1 : PST_Cloth + 1; // Create scenes of all scene types for (uint32 SceneType = 0; SceneType < NumPhysScenes; ++SceneType) { // Create the physics scene InitPhysScene(SceneType); // Also initialize scene data bPhysXSceneExecuting[SceneType] = false; // Initialize to a value which would be acceptable if FrameTimeSmoothingFactor[i] = 1.0f, i.e. constant simulation substeps AveragedFrameTime[SceneType] = InitialAverageFrameRate; // gets from console variable, and clamp to [0, 1] - 1 should be fixed time as 30 fps FrameTimeSmoothingFactor[SceneType] = FMath::Clamp<float>(FrameTimeSmoothingFactor[SceneType], 0.f, 1.f); } if (!bAsyncSceneEnabled) { PhysXSceneIndex[PST_Async] = 0; } // Make sure we use the sync scene for apex world support of destructibles in the async scene #if WITH_APEX NxApexScene* ApexScene = GetApexScene(bAsyncSceneEnabled ? PST_Async : PST_Sync); check(ApexScene); PxScene* SyncPhysXScene = GetPhysXScene(PST_Sync); check(SyncPhysXScene); check(GApexModuleDestructible); GApexModuleDestructible->setWorldSupportPhysXScene(*ApexScene, SyncPhysXScene); GApexModuleDestructible->setDamageApplicationRaycastFlags(NxDestructibleActorRaycastFlags::AllChunks, *ApexScene); #endif }
void FPhysScene::InitPhysScene(uint32 SceneType) { check(SceneType < NumPhysScenes); #if WITH_PHYSX PhysxUserData = FPhysxUserData(this); // Include scene descriptor in loop, so that we might vary it with scene type PxSceneDesc PSceneDesc(GPhysXSDK->getTolerancesScale()); PSceneDesc.cpuDispatcher = CPUDispatcher; FPhysSceneShaderInfo PhysSceneShaderInfo; PhysSceneShaderInfo.PhysScene = this; PSceneDesc.filterShaderData = &PhysSceneShaderInfo; PSceneDesc.filterShaderDataSize = sizeof(PhysSceneShaderInfo); PSceneDesc.filterShader = PhysXSimFilterShader; PSceneDesc.simulationEventCallback = SimEventCallback; if(UPhysicsSettings::Get()->bEnablePCM) { PSceneDesc.flags |= PxSceneFlag::eENABLE_PCM; } //LOC_MOD enable kinematic vs kinematic for APEX destructibles. This is for the kinematic cube moving horizontally in QA-Destructible map to collide with the destructible. // Was this flag turned off in UE4? Do we want to turn it on for both sync and async scenes? PSceneDesc.flags |= PxSceneFlag::eENABLE_KINEMATIC_PAIRS; // Set bounce threshold PSceneDesc.bounceThresholdVelocity = UPhysicsSettings::Get()->BounceThresholdVelocity; // Possibly set flags in async scene for better behavior with piles #if USE_ADAPTIVE_FORCES_FOR_ASYNC_SCENE if (SceneType == PST_Async) { PSceneDesc.flags |= PxSceneFlag::eADAPTIVE_FORCE; } #endif #if USE_SPECIAL_FRICTION_MODEL_FOR_ASYNC_SCENE if (SceneType == PST_Async) { PSceneDesc.flags |= PxSceneFlag::eENABLE_ONE_DIRECTIONAL_FRICTION; } #endif // If we're frame lagging the async scene (truly running it async) then use the scene lock #if USE_SCENE_LOCK if(UPhysicsSettings::Get()->bWarnMissingLocks) { PSceneDesc.flags |= PxSceneFlag::eREQUIRE_RW_LOCK; } #endif // We want to use 'active transforms' PSceneDesc.flags |= PxSceneFlag::eENABLE_ACTIVETRANSFORMS; // @TODO Should we set up PSceneDesc.limits? How? // Do this to improve loading times, esp. for streaming in sublevels PSceneDesc.staticStructure = PxPruningStructure::eDYNAMIC_AABB_TREE; // Default to rebuilding tree slowly PSceneDesc.dynamicTreeRebuildRateHint = PhysXSlowRebuildRate; bool bIsValid = PSceneDesc.isValid(); if (!bIsValid) { UE_LOG(LogPhysics, Log, TEXT("Invalid PSceneDesc")); } // Create scene, and add to map PxScene* PScene = GPhysXSDK->createScene(PSceneDesc); #if WITH_APEX // Build the APEX scene descriptor for the PhysX scene NxApexSceneDesc ApexSceneDesc; ApexSceneDesc.scene = PScene; // This interface allows us to modify the PhysX simulation filter shader data with contact pair flags ApexSceneDesc.physX3Interface = &GApexPhysX3Interface; // Create the APEX scene from our descriptor NxApexScene* ApexScene = GApexSDK->createScene(ApexSceneDesc); // This enables debug rendering using the "legacy" method, not using the APEX render API ApexScene->setUseDebugRenderable(true); // Allocate a view matrix for APEX scene LOD ApexScene->allocViewMatrix(physx::ViewMatrixType::LOOK_AT_RH); // Add the APEX scene to the map instead of the PhysX scene, since we can access the latter through the former GPhysXSceneMap.Add(PhysXSceneCount, ApexScene); #else // #if WITH_APEX GPhysXSceneMap.Add(PhysXSceneCount, PScene); #endif // #if WITH_APEX // Lock scene lock, in case it is required SCENE_LOCK_WRITE(PScene); // enable CCD at scene level if (bGlobalCCD) { PScene->setFlag(PxSceneFlag::eENABLE_CCD, true); } // Need to turn this on to consider kinematics turning into dynamic. Otherwise, you'll need to call resetFiltering to do the expensive broadphase reinserting PScene->setFlag(PxSceneFlag::eENABLE_KINEMATIC_STATIC_PAIRS, true); // Unlock scene lock, in case it is required SCENE_UNLOCK_WRITE(PScene); // Save pointer to FPhysScene in userdata PScene->userData = &PhysxUserData; #if WITH_APEX ApexScene->userData = &PhysxUserData; #endif // Store index of PhysX Scene in this FPhysScene this->PhysXSceneIndex[SceneType] = PhysXSceneCount; DeferredSceneData[SceneType].SceneIndex = PhysXSceneCount; // Increment scene count PhysXSceneCount++; #if WITH_VEHICLE // Only create PhysXVehicleManager in the sync scene if (SceneType == PST_Sync) { check(VehicleManager == NULL); VehicleManager = new FPhysXVehicleManager(PScene); } #endif #if WITH_SUBSTEPPING //Initialize substeppers //we don't bother sub-stepping cloth #if WITH_PHYSX #if WITH_APEX PhysSubSteppers[SceneType] = SceneType == PST_Cloth ? NULL : new FPhysSubstepTask(ApexScene); #else PhysSubSteppers[SceneType] = SceneType == PST_Cloth ? NULL : new FPhysSubstepTask(PScene); #endif #endif #if WITH_VEHICLE if (SceneType == PST_Sync) { PhysSubSteppers[SceneType]->SetVehicleManager(VehicleManager); } #endif #endif #endif // WITH_PHYSX }
void UDestructibleComponent::CreatePhysicsState() { // to avoid calling PrimitiveComponent, I'm just calling ActorComponent::CreatePhysicsState // @todo lh - fix me based on the discussion with Bryan G UActorComponent::CreatePhysicsState(); bPhysicsStateCreated = true; // What we want to do with BodySetup is simply use it to store a PhysicalMaterial, and possibly some other relevant fields. Set up pointers from the BodyInstance to the BodySetup and this component UBodySetup* BodySetup = GetBodySetup(); BodyInstance.OwnerComponent = this; BodyInstance.BodySetup = BodySetup; BodyInstance.InstanceBodyIndex = 0; #if WITH_APEX if( SkeletalMesh == NULL ) { return; } FPhysScene* PhysScene = World->GetPhysicsScene(); check(PhysScene); if( GApexModuleDestructible == NULL ) { UE_LOG(LogPhysics, Log, TEXT("UDestructibleComponent::CreatePhysicsState(): APEX must be enabled to init UDestructibleComponent physics.") ); return; } if( ApexDestructibleActor != NULL ) { UE_LOG(LogPhysics, Log, TEXT("UDestructibleComponent::CreatePhysicsState(): NxDestructibleActor already created.") ); return; } UDestructibleMesh* TheDestructibleMesh = GetDestructibleMesh(); if( TheDestructibleMesh == NULL || TheDestructibleMesh->ApexDestructibleAsset == NULL) { UE_LOG(LogPhysics, Log, TEXT("UDestructibleComponent::CreatePhysicsState(): No DestructibleMesh or missing ApexDestructibleAsset.") ); return; } int32 ChunkCount = TheDestructibleMesh->ApexDestructibleAsset->getChunkCount(); // Ensure the chunks start off invisible. RefreshBoneTransforms should make them visible. for (int32 ChunkIndex = 0; ChunkIndex < ChunkCount; ++ChunkIndex) { SetChunkVisible(ChunkIndex, false); } #if WITH_EDITOR if (GIsEditor && !World->IsGameWorld()) { // In the editor, only set the 0 chunk to be visible. if (TheDestructibleMesh->ApexDestructibleAsset->getChunkCount() > 0) { SetChunkVisible(0, true); } return; } #endif // WITH_EDITOR // Only create physics in the game if( !World->IsGameWorld() ) { return; } // Set template actor/body/shape properties // Find the PhysicalMaterial we need to apply to the physics bodies. UPhysicalMaterial* PhysMat = BodyInstance.GetSimplePhysicalMaterial(); // Get the default actor descriptor NxParameterized data from the asset NxParameterized::Interface* ActorParams = TheDestructibleMesh->GetDestructibleActorDesc(PhysMat); // Create PhysX transforms from ComponentToWorld const PxMat44 GlobalPose(PxMat33(U2PQuat(ComponentToWorld.GetRotation())), U2PVector(ComponentToWorld.GetTranslation())); const PxVec3 Scale = U2PVector(ComponentToWorld.GetScale3D()); // Set the transform in the actor descriptor verify( NxParameterized::setParamMat44(*ActorParams,"globalPose",GlobalPose) ); verify( NxParameterized::setParamVec3(*ActorParams,"scale",Scale) ); // Set the (initially) dynamic flag in the actor descriptor // See if we are 'static' verify( NxParameterized::setParamBool(*ActorParams,"dynamic", BodyInstance.bSimulatePhysics != false) ); // Set the sleep velocity frame decay constant (was sleepVelocitySmoothingFactor) - a new feature that should help sleeping in large piles verify( NxParameterized::setParamF32(*ActorParams,"sleepVelocityFrameDecayConstant", 20.0f) ); // Set up the shape desc template // Get collision channel and response PxFilterData PQueryFilterData, PSimFilterData; uint8 MoveChannel = GetCollisionObjectType(); FCollisionResponseContainer CollResponse; if(IsCollisionEnabled()) { // Only enable a collision response if collision is enabled CollResponse = GetCollisionResponseToChannels(); LargeChunkCollisionResponse.SetCollisionResponseContainer(CollResponse); SmallChunkCollisionResponse.SetCollisionResponseContainer(CollResponse); SmallChunkCollisionResponse.SetResponse(ECC_Pawn, ECR_Overlap); } else { // now since by default it will all block, if collision is disabled, we need to set to ignore MoveChannel = ECC_WorldStatic; CollResponse.SetAllChannels(ECR_Ignore); LargeChunkCollisionResponse.SetAllChannels(ECR_Ignore); SmallChunkCollisionResponse.SetAllChannels(ECR_Ignore); } const bool bEnableImpactDamage = IsImpactDamageEnabled(TheDestructibleMesh, 0); const bool bEnableContactModification = TheDestructibleMesh->DefaultDestructibleParameters.DamageParameters.bCustomImpactResistance && TheDestructibleMesh->DefaultDestructibleParameters.DamageParameters.ImpactResistance > 0.f; // Passing AssetInstanceID = 0 so we'll have self-collision AActor* Owner = GetOwner(); CreateShapeFilterData(MoveChannel, GetUniqueID(), CollResponse, 0, 0, PQueryFilterData, PSimFilterData, BodyInstance.bUseCCD, bEnableImpactDamage, false, bEnableContactModification); // Build filterData variations for complex and simple PSimFilterData.word3 |= EPDF_SimpleCollision | EPDF_ComplexCollision; PQueryFilterData.word3 |= EPDF_SimpleCollision | EPDF_ComplexCollision; // Set the filterData in the shape descriptor verify( NxParameterized::setParamU32(*ActorParams,"p3ShapeDescTemplate.simulationFilterData.word0", PSimFilterData.word0 ) ); verify( NxParameterized::setParamU32(*ActorParams,"p3ShapeDescTemplate.simulationFilterData.word1", PSimFilterData.word1 ) ); verify( NxParameterized::setParamU32(*ActorParams,"p3ShapeDescTemplate.simulationFilterData.word2", PSimFilterData.word2 ) ); verify( NxParameterized::setParamU32(*ActorParams,"p3ShapeDescTemplate.simulationFilterData.word3", PSimFilterData.word3 ) ); verify( NxParameterized::setParamU32(*ActorParams,"p3ShapeDescTemplate.queryFilterData.word0", PQueryFilterData.word0 ) ); verify( NxParameterized::setParamU32(*ActorParams,"p3ShapeDescTemplate.queryFilterData.word1", PQueryFilterData.word1 ) ); verify( NxParameterized::setParamU32(*ActorParams,"p3ShapeDescTemplate.queryFilterData.word2", PQueryFilterData.word2 ) ); verify( NxParameterized::setParamU32(*ActorParams,"p3ShapeDescTemplate.queryFilterData.word3", PQueryFilterData.word3 ) ); // Set the PhysX material in the shape descriptor PxMaterial* PMaterial = PhysMat->GetPhysXMaterial(); verify( NxParameterized::setParamU64(*ActorParams,"p3ShapeDescTemplate.material", (physx::PxU64)PMaterial) ); // Set the rest depth to match the skin width in the shape descriptor const physx::PxCookingParams& CookingParams = GApexSDK->getCookingInterface()->getParams(); verify( NxParameterized::setParamF32(*ActorParams,"p3ShapeDescTemplate.restOffset", -CookingParams.skinWidth) ); // Set the PhysX material in the actor descriptor verify( NxParameterized::setParamBool(*ActorParams,"p3ActorDescTemplate.flags.eDISABLE_GRAVITY",false) ); verify( NxParameterized::setParamBool(*ActorParams,"p3ActorDescTemplate.flags.eVISUALIZATION",true) ); // Set the PxActor's and PxShape's userData fields to this component's body instance verify( NxParameterized::setParamU64(*ActorParams,"p3ActorDescTemplate.userData", 0 ) ); // All shapes created by this DestructibleActor will have the userdata of the owning component. // We need this, as in some cases APEX is moving shapes accross actors ( ex. FormExtended structures ) verify( NxParameterized::setParamU64(*ActorParams,"p3ShapeDescTemplate.userData", (PxU64)&PhysxUserData ) ); // Set up the body desc template in the actor descriptor verify( NxParameterized::setParamF32(*ActorParams,"p3BodyDescTemplate.angularDamping", BodyInstance.AngularDamping ) ); verify( NxParameterized::setParamF32(*ActorParams,"p3BodyDescTemplate.linearDamping", BodyInstance.LinearDamping ) ); const PxTolerancesScale& PScale = GPhysXSDK->getTolerancesScale(); PxF32 SleepEnergyThreshold = 0.00005f*PScale.speed*PScale.speed; // 1/1000 Default, since the speed scale is quite high if (BodyInstance.SleepFamily == ESleepFamily::Sensitive) { SleepEnergyThreshold /= 20.0f; } verify( NxParameterized::setParamF32(*ActorParams,"p3BodyDescTemplate.sleepThreshold", SleepEnergyThreshold) ); // NxParameterized::setParamF32(*ActorParams,"bodyDescTemplate.sleepDamping", SleepDamping ); verify( NxParameterized::setParamF32(*ActorParams,"p3BodyDescTemplate.density", 0.001f*PhysMat->Density) ); // Convert from g/cm^3 to kg/cm^3 // Enable CCD if requested verify( NxParameterized::setParamBool(*ActorParams,"p3BodyDescTemplate.flags.eENABLE_CCD", BodyInstance.bUseCCD != 0) ); // Ask the actor to create chunk events, for more efficient visibility updates verify( NxParameterized::setParamBool(*ActorParams,"createChunkEvents", true) ); // Enable hard sleeping if requested verify( NxParameterized::setParamBool(*ActorParams,"useHardSleeping", bEnableHardSleeping) ); // Destructibles are always dynamic or kinematic, and therefore only go into one of the scenes const uint32 SceneType = BodyInstance.UseAsyncScene(PhysScene) ? PST_Async : PST_Sync; NxApexScene* ApexScene = PhysScene->GetApexScene(SceneType); PxScene* PScene = PhysScene->GetPhysXScene(SceneType); BodyInstance.SceneIndexSync = SceneType == PST_Sync ? PhysScene->PhysXSceneIndex[PST_Sync] : 0; BodyInstance.SceneIndexAsync = SceneType == PST_Async ? PhysScene->PhysXSceneIndex[PST_Async] : 0; check(ApexScene); ChunkInfos.Reset(ChunkCount); ChunkInfos.AddZeroed(ChunkCount); PhysxChunkUserData.Reset(ChunkCount); PhysxChunkUserData.AddZeroed(ChunkCount); // Create an APEX NxDestructibleActor from the Destructible asset and actor descriptor ApexDestructibleActor = static_cast<NxDestructibleActor*>(TheDestructibleMesh->ApexDestructibleAsset->createApexActor(*ActorParams, *ApexScene)); check(ApexDestructibleActor); // Make a backpointer to this component PhysxUserData = FPhysxUserData(this); ApexDestructibleActor->userData = &PhysxUserData; // Cache cooked collision data // BRGTODO : cook in asset ApexDestructibleActor->cacheModuleData(); // BRGTODO : Per-actor LOD setting // ApexDestructibleActor->forcePhysicalLod( DestructibleActor->LOD ); // Start asleep if requested PxRigidDynamic* PRootActor = ApexDestructibleActor->getChunkPhysXActor(0); // Put to sleep or wake up only if the component is physics-simulated if (PRootActor != NULL && BodyInstance.bSimulatePhysics) { SCOPED_SCENE_WRITE_LOCK(PScene); //Question, since apex is defer adding actors do we need to lock? Locking the async scene is expensive! PRootActor->setActorFlag(PxActorFlag::eDISABLE_GRAVITY, !BodyInstance.bEnableGravity); // Sleep/wake up as appropriate if (!BodyInstance.bStartAwake) { ApexDestructibleActor->setChunkPhysXActorAwakeState(0, false); } } UpdateBounds(); #endif // #if WITH_APEX }
void UDestructibleComponent::SetChunkVisible( int32 ChunkIndex, bool bVisible ) { // Bone 0 is a dummy root bone const int32 BoneIndex = ChunkIdxToBoneIdx(ChunkIndex); bool bClearActorFromChunkInfo = false; if( bVisible ) { UnHideBone(BoneIndex); #if WITH_APEX PxRigidDynamic* PActor = ApexDestructibleActor != NULL ? ApexDestructibleActor->getChunkPhysXActor(ChunkIndex) : NULL; UDestructibleMesh* DMesh = GetDestructibleMesh(); if (PActor != NULL) { // If actor has already a chunk info and userdata, we just make sure it is valid and update the // physx actor if needed. We do NOT do this for FormExtended structures, as in this case, the shapes/actors // are moved to the 1st structure object internally by APEX. if(PActor->userData != NULL && !DMesh->DefaultDestructibleParameters.Flags.bFormExtendedStructures) { FDestructibleChunkInfo* CI = FPhysxUserData::Get<FDestructibleChunkInfo>(PActor->userData); checkf(CI, TEXT("If a chunk actor has user data and it is not a DestructibleChunkInfo, something is messed up.")); //check(CI->OwningComponent == this); if (CI->ChunkIndex != ChunkIndex) { // grab the old actor and clear its user data, as we steal the ChunkInfo here if (CI->Actor && CI->Actor != PActor) { CI->Actor->userData = NULL; } CI->ChunkIndex = ChunkIndex; } CI->OwningComponent = this; CI->Actor = PActor; } else if (PActor->userData == NULL) { // Setup the user data to have a proper chunk - actor mapping int32 InfoIndex = ChunkInfos.AddUninitialized(); FDestructibleChunkInfo* CI = &ChunkInfos[InfoIndex]; CI->Index = InfoIndex; CI->ChunkIndex = ChunkIndex; CI->OwningComponent = this; CI->Actor = PActor; int32 UserDataIdx = PhysxChunkUserData.Add(FPhysxUserData(CI)); check(InfoIndex == UserDataIdx); PActor->userData = &PhysxChunkUserData[UserDataIdx]; // Set collision response to non-root chunks if (GetDestructibleMesh()->ApexDestructibleAsset->getChunkParentIndex(ChunkIndex) >= 0) { SetCollisionResponseForActor(ChunkCollisionResponse, PActor, ChunkIndex); } } } else { bClearActorFromChunkInfo = true; } #endif // WITH_APEX } else { HideBone(BoneIndex, PBO_None); bClearActorFromChunkInfo = true; } #if WITH_APEX if (bClearActorFromChunkInfo) { // Make sure we clear the physx actor pointer of the chunk info as it might (and probably will) be // invalid from now on for (int32 i=0; i < ChunkInfos.Num(); ++i) { if (ChunkInfos[i].ChunkIndex == ChunkIndex) { ChunkInfos[i].Actor = NULL; break; } } } #endif // WITH_APEX // Mark the transform as dirty, so the bounds are updated and sent to the render thread MarkRenderTransformDirty(); // New bone positions need to be sent to render thread MarkRenderDynamicDataDirty(); }