void UFlareSpacecraftDamageSystem::OnCollision(class AActor* Other, FVector HitLocation, FVector NormalImpulse) { // If receive hit from over actor, like a ship we must apply collision damages. // The applied damage energy is 0.2% of the kinetic energy of the other actor. The kinetic // energy is calculated from the relative speed between the 2 actors, and only with the relative // speed projected in the axis of the collision normal: if 2 very fast ship only slightly touch, // only few energy will be decipated by the impact. // // The damages are applied only to the current actor, the ReceiveHit method of the other actor // will also call an it will apply its collision damages itself. // If the other actor is a projectile, specific weapon damage code is done in the projectile hit // handler: in this case we ignore the collision AFlareShell* OtherProjectile = Cast<AFlareShell>(Other); if (OtherProjectile) { return; } // No primitive component, ignore UPrimitiveComponent* OtherRoot = Cast<UPrimitiveComponent>(Other->GetRootComponent()); if (!OtherRoot) { return; } // Ignore debris AStaticMeshActor* OtherActor = Cast<AStaticMeshActor>(Other); if (OtherActor) { if (OtherActor->GetName().StartsWith("Debris")) { return; } } // Relative velocity FVector DeltaVelocity = ((OtherRoot->GetPhysicsLinearVelocity() - Spacecraft->Airframe->GetPhysicsLinearVelocity()) / 100); // Compute the relative velocity in the impact axis then compute kinetic energy /*float ImpactSpeed = DeltaVelocity.Size(); float ImpactMass = FMath::Min(Spacecraft->GetSpacecraftMass(), OtherRoot->GetMass()); */ //200 m /s -> 6301.873047 * 20000 -> 300 / 2 damage float ImpactSpeed = 0; float ImpactEnergy = 0; float ImpactMass = Spacecraft->GetSpacecraftMass(); // Check if the mass was set and is valid if (ImpactMass > KINDA_SMALL_NUMBER) { ImpactSpeed = NormalImpulse.Size() / (ImpactMass * 100.f); ImpactEnergy = ImpactMass * ImpactSpeed / 8402.f; } float Radius = 0.2 + FMath::Sqrt(ImpactEnergy) * 0.11; //FLOGV("OnCollision %s", *Spacecraft->GetImmatriculation().ToString()); //FLOGV(" OtherRoot->GetPhysicsLinearVelocity()=%s", *OtherRoot->GetPhysicsLinearVelocity().ToString()); //FLOGV(" OtherRoot->GetPhysicsLinearVelocity().Size()=%f", OtherRoot->GetPhysicsLinearVelocity().Size()); //FLOGV(" Spacecraft->Airframe->GetPhysicsLinearVelocity()=%s", *Spacecraft->Airframe->GetPhysicsLinearVelocity().ToString()); //FLOGV(" Spacecraft->Airframe->GetPhysicsLinearVelocity().Size()=%f", Spacecraft->Airframe->GetPhysicsLinearVelocity().Size()); //FLOGV(" dot=%f", FVector::DotProduct(DeltaVelocity.GetUnsafeNormal(), HitNormal.GetUnsafeNormal())); /*FLOGV(" DeltaVelocity=%s", *DeltaVelocity.ToString()); FLOGV(" ImpactSpeed=%f", ImpactSpeed); FLOGV(" ImpactMass=%f", ImpactMass); FLOGV(" ImpactEnergy=%f", ImpactEnergy); FLOGV(" Radius=%f", Radius);*/ bool HasHit = false; FHitResult BestHitResult; float BestHitDistance = 0; for (int32 ComponentIndex = 0; ComponentIndex < Components.Num(); ComponentIndex++) { UFlareSpacecraftComponent* Component = Cast<UFlareSpacecraftComponent>(Components[ComponentIndex]); if (Component) { FHitResult HitResult(ForceInit); FCollisionQueryParams TraceParams(FName(TEXT("Fragment Trace")), true); TraceParams.bTraceComplex = true; TraceParams.bReturnPhysicalMaterial = false; Component->LineTraceComponent(HitResult, HitLocation, HitLocation + Spacecraft->GetLinearVelocity().GetUnsafeNormal() * 10000, TraceParams); if (HitResult.Actor.IsValid()){ float HitDistance = (HitResult.Location - HitLocation).Size(); if (!HasHit || HitDistance < BestHitDistance) { BestHitDistance = HitDistance; BestHitResult = HitResult; } //FLOGV("Collide hit %s at a distance=%f", *Component->GetReadableName(), HitDistance); HasHit = true; } } } if (HasHit) { //DrawDebugLine(Spacecraft->GetWorld(), HitLocation, BestHitResult.Location, FColor::Magenta, true); } else { int32 BestComponentIndex = -1; BestHitDistance = 0; for (int32 ComponentIndex = 0; ComponentIndex < Components.Num(); ComponentIndex++) { UFlareSpacecraftComponent* Component = Cast<UFlareSpacecraftComponent>(Components[ComponentIndex]); if (Component) { float ComponentDistance = (Component->GetComponentLocation() - HitLocation).Size(); if (BestComponentIndex == -1 || BestHitDistance > ComponentDistance) { BestComponentIndex = ComponentIndex; BestHitDistance = ComponentDistance; } } } UFlareSpacecraftComponent* Component = Cast<UFlareSpacecraftComponent>(Components[BestComponentIndex]); FCollisionQueryParams TraceParams(FName(TEXT("Fragment Trace")), true); TraceParams.bTraceComplex = true; TraceParams.bReturnPhysicalMaterial = false; Component->LineTraceComponent(BestHitResult, HitLocation, Component->GetComponentLocation(), TraceParams); //DrawDebugLine(Spacecraft->GetWorld(), HitLocation, BestHitResult.Location, FColor::Yellow, true); } AFlareSpacecraft* OtherSpacecraft = Cast<AFlareSpacecraft>(Other); UFlareCompany* DamageSource = NULL; if (OtherSpacecraft) { DamageSource = OtherSpacecraft->GetParent()->GetCompany(); LastDamageCauser = OtherSpacecraft; } else { LastDamageCauser = NULL; } ApplyDamage(ImpactEnergy, Radius, BestHitResult.Location, EFlareDamage::DAM_Collision, DamageSource); }
void UBuoyancyForceComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) { Super::TickComponent(DeltaTime, TickType, ThisTickFunction); // If disabled or we are not attached to a parent component, return. if (!bIsActive || !GetAttachParent()) return; if (!OceanManager) return; UPrimitiveComponent* BasePrimComp = Cast<UPrimitiveComponent>(GetAttachParent()); if (!BasePrimComp) return; if (!BasePrimComp->IsSimulatingPhysics()) { if (!SnapToSurfaceIfNoPhysics) return; UE_LOG(LogTemp, Warning, TEXT("Running in no physics mode..")); float waveHeight = OceanManager->GetWaveHeightValue(BasePrimComp->GetComponentLocation(), World, true, TwoGerstnerIterations).Z; BasePrimComp->SetWorldLocation(FVector(BasePrimComp->GetComponentLocation().X, BasePrimComp->GetComponentLocation().Y, waveHeight)); return; } //Get gravity float Gravity = BasePrimComp->GetPhysicsVolume()->GetGravityZ(); //--------------- If Skeletal --------------- USkeletalMeshComponent* SkeletalComp = Cast<USkeletalMeshComponent>(GetAttachParent()); if (SkeletalComp && ApplyForceToBones) { TArray<FName> BoneNames; SkeletalComp->GetBoneNames(BoneNames); for (int32 Itr = 0; Itr < BoneNames.Num(); Itr++) { FBodyInstance* BI = SkeletalComp->GetBodyInstance(BoneNames[Itr], false); if (BI && BI->IsValidBodyInstance() && BI->bEnableGravity) //Buoyancy doesn't exist without gravity { bool isUnderwater = false; //FVector worldBoneLoc = SkeletalComp->GetBoneLocation(BoneNames[Itr]); FVector worldBoneLoc = BI->GetCOMPosition(); //Use center of mass of the bone's physics body instead of bone's location FVector waveHeight = OceanManager->GetWaveHeightValue(worldBoneLoc, World, true, TwoGerstnerIterations); float BoneDensity = MeshDensity; float BoneTestRadius = FMath::Abs(TestPointRadius); float SignedBoneRadius = FMath::Sign(Gravity) * TestPointRadius; //Direction of radius (test radius is actually a Z offset, should probably rename it!). Just in case we need an upside down world. //Get density & radius from the override array, if available. for (int pointIndex = 0; pointIndex < BoneOverride.Num(); pointIndex++) { FStructBoneOverride Override = BoneOverride[pointIndex]; if (Override.BoneName.IsEqual(BoneNames[Itr])) { BoneDensity = Override.Density; BoneTestRadius = FMath::Abs(Override.TestRadius); SignedBoneRadius = FMath::Sign(Gravity) * BoneTestRadius; } } //If test point radius is below water surface, add buoyancy force. if (waveHeight.Z > (worldBoneLoc.Z + SignedBoneRadius)) { isUnderwater = true; float DepthMultiplier = (waveHeight.Z - (worldBoneLoc.Z + SignedBoneRadius)) / (BoneTestRadius * 2); DepthMultiplier = FMath::Clamp(DepthMultiplier, 0.f, 1.f); float Mass = SkeletalComp->CalculateMass(BoneNames[Itr]); //Mass of this specific bone's physics body /** * -------- * Buoyancy force formula: (Volume(Mass / Density) * Fluid Density * -Gravity) / Total Points * Depth Multiplier * -------- */ float BuoyancyForceZ = Mass / BoneDensity * FluidDensity * -Gravity * DepthMultiplier; //Velocity damping. FVector DampingForce = -BI->GetUnrealWorldVelocity() * VelocityDamper * Mass * DepthMultiplier; //Experimental xy wave force if (EnableWaveForces) { float waveVelocity = FMath::Clamp(BI->GetUnrealWorldVelocity().Z, -20.f, 150.f) * (1 - DepthMultiplier); DampingForce += FVector(OceanManager->GlobalWaveDirection.X, OceanManager->GlobalWaveDirection.Y, 0) * Mass * waveVelocity * WaveForceMultiplier; } //Add force to this bone BI->AddForce(FVector(DampingForce.X, DampingForce.Y, DampingForce.Z + BuoyancyForceZ)); //BasePrimComp->AddForceAtLocation(FVector(DampingForce.X, DampingForce.Y, DampingForce.Z + BuoyancyForceZ), worldBoneLoc, BoneNames[Itr]); } //Apply fluid damping & clamp velocity if (isUnderwater) { BI->SetLinearVelocity(-BI->GetUnrealWorldVelocity() * (FluidLinearDamping / 10), true); BI->SetAngularVelocity(-BI->GetUnrealWorldAngularVelocity() * (FluidAngularDamping / 10), true); //Clamp the velocity to MaxUnderwaterVelocity if (ClampMaxVelocity && BI->GetUnrealWorldVelocity().Size() > MaxUnderwaterVelocity) { FVector Velocity = BI->GetUnrealWorldVelocity().GetSafeNormal() * MaxUnderwaterVelocity; BI->SetLinearVelocity(Velocity, false); } } if (DrawDebugPoints) { FColor DebugColor = FLinearColor(0.8, 0.7, 0.2, 0.8).ToRGBE(); if (isUnderwater) { DebugColor = FLinearColor(0, 0.2, 0.7, 0.8).ToRGBE(); } //Blue color underwater, yellow out of watter DrawDebugSphere(World, worldBoneLoc, BoneTestRadius, 8, DebugColor); } } } return; } //-------------------------------------------------------- float TotalPoints = TestPoints.Num(); if (TotalPoints < 1) return; int PointsUnderWater = 0; for (int pointIndex = 0; pointIndex < TotalPoints; pointIndex++) { if (!TestPoints.IsValidIndex(pointIndex)) return; //Array size changed during runtime bool isUnderwater = false; FVector testPoint = TestPoints[pointIndex]; FVector worldTestPoint = BasePrimComp->GetComponentTransform().TransformPosition(testPoint); FVector waveHeight = OceanManager->GetWaveHeightValue(worldTestPoint, World, !EnableWaveForces, TwoGerstnerIterations); //Direction of radius (test radius is actually a Z offset, should probably rename it!). Just in case we need an upside down world. float SignedRadius = FMath::Sign(BasePrimComp->GetPhysicsVolume()->GetGravityZ()) * TestPointRadius; //If test point radius is below water surface, add buoyancy force. if (waveHeight.Z > (worldTestPoint.Z + SignedRadius) && BasePrimComp->IsGravityEnabled()) //Buoyancy doesn't exist without gravity { PointsUnderWater++; isUnderwater = true; float DepthMultiplier = (waveHeight.Z - (worldTestPoint.Z + SignedRadius)) / (TestPointRadius * 2); DepthMultiplier = FMath::Clamp(DepthMultiplier, 0.f, 1.f); //If we have a point density override, use the overridden value instead of MeshDensity float PointDensity = PointDensityOverride.IsValidIndex(pointIndex) ? PointDensityOverride[pointIndex] : MeshDensity; /** * -------- * Buoyancy force formula: (Volume(Mass / Density) * Fluid Density * -Gravity) / Total Points * Depth Multiplier * -------- */ float BuoyancyForceZ = BasePrimComp->GetMass() / PointDensity * FluidDensity * -Gravity / TotalPoints * DepthMultiplier; //Experimental velocity damping using VelocityAtPoint. FVector DampingForce = -GetUnrealVelocityAtPoint(BasePrimComp, worldTestPoint) * VelocityDamper * BasePrimComp->GetMass() * DepthMultiplier; //Experimental xy wave force if (EnableWaveForces) { DampingForce += BasePrimComp->GetMass() * FVector2D(waveHeight.X, waveHeight.Y).Size() * FVector(OceanManager->GlobalWaveDirection.X, OceanManager->GlobalWaveDirection.Y, 0) * WaveForceMultiplier / TotalPoints; //float waveVelocity = FMath::Clamp(GetUnrealVelocityAtPoint(BasePrimComp, worldTestPoint).Z, -20.f, 150.f) * (1 - DepthMultiplier); //DampingForce += OceanManager->GlobalWaveDirection * BasePrimComp->GetMass() * waveVelocity * WaveForceMultiplier / TotalPoints; } //Add force for this test point BasePrimComp->AddForceAtLocation(FVector(DampingForce.X, DampingForce.Y, DampingForce.Z + BuoyancyForceZ), worldTestPoint); } if (DrawDebugPoints) { FColor DebugColor = FLinearColor(0.8, 0.7, 0.2, 0.8).ToRGBE(); if (isUnderwater) { DebugColor = FLinearColor(0, 0.2, 0.7, 0.8).ToRGBE(); } //Blue color underwater, yellow out of watter DrawDebugSphere(World, worldTestPoint, TestPointRadius, 8, DebugColor); } } //Clamp the velocity to MaxUnderwaterVelocity if there is any point underwater if (ClampMaxVelocity && PointsUnderWater > 0 && BasePrimComp->GetPhysicsLinearVelocity().Size() > MaxUnderwaterVelocity) { FVector Velocity = BasePrimComp->GetPhysicsLinearVelocity().GetSafeNormal() * MaxUnderwaterVelocity; BasePrimComp->SetPhysicsLinearVelocity(Velocity); } //Update damping based on number of underwater test points BasePrimComp->SetLinearDamping(_baseLinearDamping + FluidLinearDamping / TotalPoints * PointsUnderWater); BasePrimComp->SetAngularDamping(_baseAngularDamping + FluidAngularDamping / TotalPoints * PointsUnderWater); }
void UVaOceanBuoyancyComponent::PerformWaveReaction(float DeltaTime) { AActor* MyOwner = GetOwner(); if (!UpdatedComponent || MyOwner == NULL) { return; } UPrimitiveComponent* OldPrimitive = Cast<UPrimitiveComponent>(UpdatedComponent); const FVector OldLocation = MyOwner->GetActorLocation(); const FRotator OldRotation = MyOwner->GetActorRotation(); const FVector OldLinearVelocity = OldPrimitive->GetPhysicsLinearVelocity(); const FVector OldAngularVelocity = OldPrimitive->GetPhysicsAngularVelocity(); const FVector OldCenterOfMassWorld = OldLocation + OldRotation.RotateVector(COMOffset); const FVector OwnerScale = MyOwner->GetActorScale(); // XYZ === Throttle, Steering, Rise == Forwards, Sidewards, Upwards FVector X, Y, Z; GetAxes(OldRotation, X, Y, Z); // Process tension dots and get torque from wind/waves for (FVector TensionDot : TensionDots) { // Translate point to world coordinates FVector TensionDotDisplaced = OldRotation.RotateVector(TensionDot + COMOffset); FVector TensionDotWorld = OldLocation + TensionDotDisplaced; // Get point depth float DotAltitude = GetAltitude(TensionDotWorld); // Don't process dots above water if (DotAltitude > 0) { continue; } // Surface normal (not modified!) FVector DotSurfaceNormal = GetSurfaceNormal(TensionDotWorld) * GetSurfaceWavesNum(); // Modify normal with real Z value and normalize it DotSurfaceNormal.Z = GetOceanLevel(TensionDotWorld); DotSurfaceNormal.Normalize(); // Point dynamic pressure [http://en.wikipedia.org/wiki/Dynamic_pressure] // rho = 1.03f for ocean water FVector WaveVelocity = GetWaveVelocity(TensionDotWorld); float DotQ = 0.515f * FMath::Square(WaveVelocity.Size()); FVector WaveForce = FVector(0.0,0.0,1.0) * DotQ /* DotSurfaceNormal*/ * (-DotAltitude) * TensionDepthFactor; // We don't want Z to be affected by DotQ WaveForce.Z /= DotQ; // Scale to DeltaTime to break FPS addiction WaveForce *= DeltaTime; // Apply actor scale WaveForce *= OwnerScale.X;// *OwnerScale.Y * OwnerScale.Z; OldPrimitive->AddForceAtLocation(WaveForce * Mass, TensionDotWorld); } // Static metacentric forces (can be useful on small waves) if (bUseMetacentricForces) { FVector TensionTorqueResult = FVector(0.0f, 0.0f, 0.0f); // Calc recovering torque (transverce) FRotator RollRot = FRotator(0.0f, 0.0f, 0.0f); RollRot.Roll = OldRotation.Roll; FVector MetacenterDisplaced = RollRot.RotateVector(TransverseMetacenter + COMOffset); TensionTorqueResult += X * FVector::DotProduct((TransverseMetacenter - MetacenterDisplaced), FVector(0.0f, -1.0f, 0.0f)) * TensionTorqueRollFactor; // Calc recovering torque (longitude) FRotator PitchRot = FRotator(0.0f, 0.0f, 0.0f); PitchRot.Pitch = OldRotation.Pitch; MetacenterDisplaced = PitchRot.RotateVector(LongitudinalMetacenter + COMOffset); TensionTorqueResult += Y * FVector::DotProduct((LongitudinalMetacenter - MetacenterDisplaced), FVector(1.0f, 0.0f, 0.0f)) * TensionTorquePitchFactor; // Apply torque TensionTorqueResult *= DeltaTime; TensionTorqueResult *= OwnerScale.X;// *OwnerScale.Y * OwnerScale.Z; OldPrimitive->AddTorque(TensionTorqueResult); } }