Creating a new behaviour area module

This is an example of a behaviour area module. It fills the context steering data using the curve tangents and applies a force to keep the agents on the curve.

#pragma once #include "CoreMinimal.h" #include "AtomsUnreal/Public/Components/AtomsBehaviourAreaComponent.h" #include <AtomsUtils/SampledCurve.h> #include "MyFollowCurveBehaviourAreaComponent.generated.h" UCLASS(ClassGroup = "AtomsBehaviourArea", meta = (BlueprintSpawnableComponent)) class ATOMSUNREAL_API UMyFollowCurveBehaviourAreaComponent: public UAtomsBehaviourAreaComponent { GENERATED_BODY() public: /*Number of curve samples*/ UPROPERTY(EditAnywhere, Category = "Behaviour") int32 NumSamples; /*The max distance from the curve. Any agent farther than this value from the curve will be ignored*/ UPROPERTY(EditAnywhere, Category = "Behaviour") float MaxDistance; /* Drop off value for smoothly assume curve closest tangent direction*/ UPROPERTY(EditAnywhere, Category = "Behaviour") float MaxDistanceDropoff; /* Affects only the orientation and not the direction */ UPROPERTY(EditAnywhere, Category = "Behaviour") bool OrientationOnly; /* Enable influence on the left side of the curve */ UPROPERTY(EditAnywhere, Category = "Behaviour") bool EnableLeftSide; /* Enable influence on the right side of the curve */ UPROPERTY(EditAnywhere, Category = "Behaviour") bool EnableRightSide; /* Enable the influence arount the first and the last point, * if off the influence is limited to the right and left side of the curve*/ UPROPERTY(EditAnywhere, Category = "Behaviour") bool EnableCaps; /* Generate a force to keep the agent position onthe curve */ UPROPERTY(EditAnywhere, Category = "Behaviour") bool KeepAgentOnCurve; UPROPERTY(EditAnywhere, Category = "Behaviour", meta=(EditCondition=KeepAgentOnCurve)) float MaxForce; UMyFollowCurveBehaviourAreaComponent(); virtual void BeginPlay() override; virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; virtual FBoxSphereBounds CalcBounds(const FTransform& LocalToWorld) const override; UFUNCTION(BlueprintCallable, Category="My Follow Curve Behaviour Area") void UpdatedCurve(); virtual bool FillContextSteeringMaps( Atoms::AgentGroup* AgentGroup, Atoms::Agent* Agent, const FVector& MapOrigin, const FVector& MapOrientation, const double Distance, const double DeltaTime) override; virtual bool EvaluateForce( Atoms::AgentGroup* AgentGroup, Atoms::Agent* Agent, const FVector& Direction, const FVector& Velocity, const float DeltaTime, FVector& OutForce) override; private: FBoxSphereBounds CurveBound; AtomsUtils::SampledCurve Curve; AtomsMath::Matrix WorldMatrix; AtomsMath::Matrix WorldMatrixNormal; AtomsMath::Matrix WorldMatrixInverse; };
#include "MyFollowCurveBehaviourAreaComponent.h" #include "Components/SplineComponent.h" #include <Atoms/Agent.h> #include "AtomsConversions.h" UMyFollowCurveBehaviourAreaComponent::UMyFollowCurveBehaviourAreaComponent(): NumSamples(10), MaxDistance(1000.0f), MaxDistanceDropoff(0.0f), OrientationOnly(false), EnableLeftSide(true), EnableRightSide(true), EnableCaps(true), KeepAgentOnCurve(false), MaxForce(500.0f) { } void UMyFollowCurveBehaviourAreaComponent::BeginPlay() { UpdatedCurve(); WorldMatrix = AtomsConverter::ToMatrix(GetComponentTransform()); WorldMatrixInverse = WorldMatrix.inverse(); Super::BeginPlay(); } void UMyFollowCurveBehaviourAreaComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) { WorldMatrix = AtomsConverter::ToMatrix(GetComponentTransform()); WorldMatrixInverse = WorldMatrix.inverse(); WorldMatrixNormal = WorldMatrix.transposed(); Super::TickComponent(DeltaTime, TickType, ThisTickFunction); } FBoxSphereBounds UMyFollowCurveBehaviourAreaComponent::CalcBounds(const FTransform& LocalToWorld) const { return CurveBound.TransformBy(LocalToWorld); } void UMyFollowCurveBehaviourAreaComponent::UpdatedCurve() { FVector MaxDistVec(MaxDistance, MaxDistance, MaxDistance); USplineComponent* SplineComponent = Cast<USplineComponent>(GetAttachParent()); FBox CurrentBound; CurrentBound.Init(); if (SplineComponent) { float length = SplineComponent->GetSplineLength(); auto PointsNo = SplineComponent->GetNumberOfSplinePoints(); auto numSamples = PointsNo * NumSamples; auto& AtomsPoints = Curve.cvs(); AtomsPoints.reserve(numSamples); for (auto i = 0; i < numSamples; i++) { auto Point = SplineComponent->GetLocationAtDistanceAlongSpline(float(i) * length / static_cast<float>(numSamples - 1), ESplineCoordinateSpace::Local); AtomsPoints.push_back(AtomsConverter::ToVector3(Point)); CurrentBound += Point + MaxDistVec; CurrentBound += Point - MaxDistVec; } Curve.setDegree(3); Curve.computeUniformKnots(); Curve.resample(numSamples); } CurveBound.BoxExtent = CurrentBound.GetExtent(); CurveBound.Origin = CurrentBound.GetCenter(); CurveBound.SphereRadius = CurrentBound.GetExtent().Size() * 0.5; CurveBound.ExpandBy(MaxDistance); } bool UMyFollowCurveBehaviourAreaComponent::FillContextSteeringMaps( Atoms::AgentGroup* AgentGroup, Atoms::Agent* Agent, const FVector& MapOrigin, const FVector& MapOrientation, const double Distance, const double DeltaTime) { if (Curve.cvs().size() == 0) return false; Atoms::ContextSteeringData& data = Agent->contextSteeringData(); auto sample = Curve.closestTangent(data.position * WorldMatrixInverse); if (sample.first < 0.0) return false; AtomsMath::Vector3 closetsLocalP = Curve.pointOnCurve(sample.first); AtomsMath::Vector3 closetsP = closetsLocalP * WorldMatrix; AtomsMath::Vector3 vecToClosestP = closetsP - data.position; vecToClosestP.y = 0.0; double distanceSq = vecToClosestP.length2(); if (distanceSq > (MaxDistance * MaxDistance)) { return false; } const double errorRange = 0.0001 * 0.0001; if (!EnableCaps && ((closetsLocalP - Curve.cvs().front()).length2() < errorRange || (closetsLocalP - Curve.cvs().back()).length2() < errorRange)) { return false; } AtomsMath::Vector3 outTangent = sample.second; AtomsMath::Vector4 tangent4(outTangent.x, outTangent.y, outTangent.z, 0.0); tangent4 = tangent4 * WorldMatrixNormal; outTangent.x = tangent4.x; outTangent.y = tangent4.y; outTangent.z = tangent4.z; outTangent.normalize(); double Weight = 1.0; double agentDistance = FMath::Sqrt(distanceSq); float startDropOff = MaxDistance - MaxDistanceDropoff; if (MaxDistanceDropoff > 0.0 && agentDistance >= startDropOff) { Weight *= 1.0 - (agentDistance - startDropOff) / MaxDistanceDropoff; } if (!EnableLeftSide || !EnableRightSide) { double dotSide = (vecToClosestP / agentDistance).cross(outTangent).normalize().dot(data.up); if ((!EnableLeftSide && dotSide < 0.0) || (!EnableRightSide && dotSide > 0.0)) { Weight = 0.0f; } } if (OrientationOnly && data.direction.dot(outTangent) < 0.0) { outTangent = -outTangent; } if (Weight == 1.0) { data.direction = outTangent; return true; } AtomsMath::Quaternion rotation; rotation.setRotation(data.direction, outTangent); AtomsCore::Vector3 rotAxis = rotation.axis(); double angle = rotation.angle() * Weight; rotation.setAxisAngle(rotAxis, angle); data.direction = rotation.rotateVector(data.direction); if (FMath::IsNaN(data.direction.x) || FMath::IsNaN(data.direction.y) || FMath::IsNaN(data.direction.z)) { check(false); //AtomsUtils::Logger::warning() << "Nan "; } return true; } bool UMyFollowCurveBehaviourAreaComponent::EvaluateForce( Atoms::AgentGroup* AgentGroup, Atoms::Agent* Agent, const FVector& Direction, const FVector& Velocity, const float DeltaTime, FVector& OutForce) { if (!KeepAgentOnCurve || Curve.cvs().size() == 0) return false; Atoms::ContextSteeringData& data = Agent->contextSteeringData(); auto sample = Curve.closestPoint(data.position * WorldMatrixInverse); if (sample.first < 0.001 || sample.first > (Curve.cvs().size() - Curve.degree() - 0.5)) return false; AtomsMath::Vector3 closestP(sample.second); float agentRadius = Agent->agentType()->collisionRadius() * Agent->scale()->get().x * 0.5; AtomsMath::Vector3 vecToClosestP = closestP * WorldMatrix - data.position; vecToClosestP.y = 0.0; double distanceSq = vecToClosestP.length2(); if (distanceSq > (MaxDistance * MaxDistance)) { return false; } FVector CurrentForce = AtomsConverter::FromVector3(vecToClosestP / DeltaTime) / DeltaTime; auto CurrentMagnitude = CurrentForce.Size(); if (CurrentMagnitude < 0.0001) return false; auto ForceMagnitude = FMath::Min(CurrentMagnitude, MaxForce); CurrentForce *= ForceMagnitude / CurrentMagnitude; OutForce += CurrentForce; return true; }

Copyright © 2017, Toolchefs LTD.