Skip to content
Snippets Groups Projects
Commit cfdaeab6 authored by Jonathan Ehret's avatar Jonathan Ehret
Browse files

further improvement to gazing, also allowing to disable support so e.g. the...

further improvement to gazing, also allowing to disable support so e.g. the head if the eyes cannot reach their target and fixing previous commit
parent 76d984f0
No related branches found
No related tags found
No related merge requests found
...@@ -84,55 +84,9 @@ void FAnimNode_VHGazing::Evaluate_AnyThread(FPoseContext& Output) ...@@ -84,55 +84,9 @@ void FAnimNode_VHGazing::Evaluate_AnyThread(FPoseContext& Output)
void FAnimNode_VHGazing::UpdateRotations(FPoseContext& LocalPoseContext, FComponentSpacePoseContext& CSPoseContext) { void FAnimNode_VHGazing::UpdateRotations(FPoseContext& LocalPoseContext, FComponentSpacePoseContext& CSPoseContext) {
// Update rotations one after another, however, compute first and apply at the end since we rely on updated current rotation // Update rotations one after another
// of bones up the hierarchy, so for the eyes we need to know how far torso and head are rotated. // We use data of the last frame to see whether, e.g., the eyes were able to reach their target
// However, for the torso we need to also know first whether the head can reach its target or whether it was clamped so that the torso needs to help // or whether, e.g., the head as to help out by also rotating further towards the target
// ****************
// Compute Eyes
// ****************
// full rotation required to be inline with the target
const FRotator FullTargetRotationRightEye = GetRotationToTarget(RightEyeBoneData, LocalPoseContext, CSPoseContext);
const FRotator FullTargetRotationLeftEye = GetRotationToTarget(LeftEyeBoneData, LocalPoseContext, CSPoseContext);
//remove the way that the torso and head already made
//OLD DATA still in head and torso, so only apply this temporarily!
FRotator TargetRotationRightEye = FullTargetRotationRightEye - (HeadBoneData.CurrentRotation + TorsoBoneData.CurrentRotation);
FRotator TargetRotationLeftEye = FullTargetRotationLeftEye - (HeadBoneData.CurrentRotation + TorsoBoneData.CurrentRotation);
// clamp eye rotation to Ocular Motor Range (OMR), store value before to see whether lower body parts can help in reaching the goal
const FRotator PreClampedRightEye = TargetRotationRightEye;
const FRotator PreClampedLeftEye = TargetRotationLeftEye;
TargetRotationRightEye = ClampRotation(TargetRotationRightEye, RightEyeBoneData, VHGazingData.RightEyeData);
TargetRotationLeftEye = ClampRotation(TargetRotationLeftEye, LeftEyeBoneData, VHGazingData.LeftEyeData);
// ****************
// Compute Head
// ****************
FRotator FullTargetRotationHead, TargetRotationHead, PreClampedHead;
if (VHGazingData.GazeMode != EGazeMode::EyesOnly)
{
// Full rotation required to be inline with the target
FullTargetRotationHead = GetRotationToTarget(HeadBoneData, LocalPoseContext, CSPoseContext);
//maybe only use a part of that (as specified by HeadAlignment)
TargetRotationHead = VHGazingData.HeadData.Alignment * FullTargetRotationHead;
//remove the way that the torso already made
//OLD data, so only temporarily (see below)
TargetRotationHead -= TorsoBoneData.CurrentRotation;
//add the rotation that the eyes weren't able to make due to them being clamped at the gaze limits
TargetRotationHead += 0.5f * (PreClampedRightEye + PreClampedLeftEye - TargetRotationRightEye - TargetRotationLeftEye);
//clamp to maximal rotation
PreClampedHead = TargetRotationHead;
TargetRotationHead = ClampRotation(TargetRotationHead, HeadBoneData, VHGazingData.HeadData);
}
// ************************* // *************************
...@@ -145,9 +99,12 @@ void FAnimNode_VHGazing::UpdateRotations(FPoseContext& LocalPoseContext, FCompon ...@@ -145,9 +99,12 @@ void FAnimNode_VHGazing::UpdateRotations(FPoseContext& LocalPoseContext, FCompon
FRotator TargetRotationTorso = GetRotationToTarget(TorsoBoneData, LocalPoseContext, CSPoseContext); FRotator TargetRotationTorso = GetRotationToTarget(TorsoBoneData, LocalPoseContext, CSPoseContext);
TargetRotationTorso = VHGazingData.TorsoData.Alignment * TargetRotationTorso; TargetRotationTorso = VHGazingData.TorsoData.Alignment * TargetRotationTorso;
TargetRotationTorso += PreClampedHead - TargetRotationHead; if (VHGazingData.bSupportClampedRotationByOtherParts) {
TargetRotationTorso += HeadBoneData.MissingRotationDueToClamp;
}
TargetRotationTorso = ClampRotation(TargetRotationTorso, TorsoBoneData, VHGazingData.TorsoData); float MinDistanceToClampTorso_Unused;
TargetRotationTorso = ClampRotation(TargetRotationTorso, VHGazingData.TorsoData, MinDistanceToClampTorso_Unused);
UpdateCurrentRotation(TorsoBoneData, VHGazingData.TorsoData, TargetRotationTorso); UpdateCurrentRotation(TorsoBoneData, VHGazingData.TorsoData, TargetRotationTorso);
ApplyRotation(TorsoBoneData, TorsoBoneData.CurrentRotation, LocalPoseContext, CSPoseContext); ApplyRotation(TorsoBoneData, TorsoBoneData.CurrentRotation, LocalPoseContext, CSPoseContext);
...@@ -162,22 +119,38 @@ void FAnimNode_VHGazing::UpdateRotations(FPoseContext& LocalPoseContext, FCompon ...@@ -162,22 +119,38 @@ void FAnimNode_VHGazing::UpdateRotations(FPoseContext& LocalPoseContext, FCompon
if (VHGazingData.GazeMode != EGazeMode::EyesOnly) if (VHGazingData.GazeMode != EGazeMode::EyesOnly)
{ {
// Full rotation required to be inline with the target
FRotator FullTargetRotationHead = GetRotationToTarget(HeadBoneData, LocalPoseContext, CSPoseContext);
//maybe only use a part of that (as specified by HeadAlignment) //maybe only use a part of that (as specified by HeadAlignment)
TargetRotationHead = VHGazingData.HeadData.Alignment * FullTargetRotationHead; FRotator TargetRotationHead = VHGazingData.HeadData.Alignment * FullTargetRotationHead;
//remove the way that the torso already made //remove the way that the torso already made
TargetRotationHead -= TorsoBoneData.CurrentRotation; TargetRotationHead -= TorsoBoneData.CurrentRotation;
if (VHGazingData.bSupportClampedRotationByOtherParts) {
//add the rotation that the eyes weren't able to make due to them being clamped at the gaze limits //add the rotation that the eyes weren't able to make due to them being clamped at the gaze limits
TargetRotationHead += 0.5f * (PreClampedRightEye + PreClampedLeftEye - TargetRotationRightEye - TargetRotationLeftEye); //this is it?!? TargetRotationHead += 0.5f * (RightEyeBoneData.MissingRotationDueToClamp + LeftEyeBoneData.MissingRotationDueToClamp);
}
if (AdditionalHeadRotation != FRotator::ZeroRotator) if (AdditionalHeadRotation != FRotator::ZeroRotator)
{ {
TargetRotationHead += AdditionalHeadRotation; TargetRotationHead += AdditionalHeadRotation;
} }
//clamp again, no need to store here, since information was already used //clamp to maximal rotation
TargetRotationHead = ClampRotation(TargetRotationHead, HeadBoneData, VHGazingData.HeadData); FRotator PreClampedHead = TargetRotationHead;
float DistanceToClampHead;
TargetRotationHead = ClampRotation(TargetRotationHead, VHGazingData.HeadData, DistanceToClampHead);
FRotator MissingHead = PreClampedHead - TargetRotationHead;
//store what part of the rotation the torso potentially has to take over in the next frame
if ((DistanceToClampHead > 1.f && MissingHead.IsNearlyZero()) || !MissingHead.IsNearlyZero()) {
// however, take the mean of this and last frame to avoid jittering
HeadBoneData.MissingRotationDueToClamp = 0.5f * (HeadBoneData.MissingRotationDueToClamp + MissingHead);
}
//else we probably only reached the target because the troso helped, so don't set the missing part to 0 to avoid jitter
UpdateCurrentRotation(HeadBoneData, VHGazingData.HeadData, TargetRotationHead); UpdateCurrentRotation(HeadBoneData, VHGazingData.HeadData, TargetRotationHead);
ApplyRotation(HeadBoneData, HeadBoneData.CurrentRotation, LocalPoseContext, CSPoseContext); ApplyRotation(HeadBoneData, HeadBoneData.CurrentRotation, LocalPoseContext, CSPoseContext);
...@@ -188,9 +161,13 @@ void FAnimNode_VHGazing::UpdateRotations(FPoseContext& LocalPoseContext, FCompon ...@@ -188,9 +161,13 @@ void FAnimNode_VHGazing::UpdateRotations(FPoseContext& LocalPoseContext, FCompon
// Apply Eyes // Apply Eyes
// **************** // ****************
// full rotation required to be inline with the target
const FRotator FullTargetRotationRightEye = GetRotationToTarget(RightEyeBoneData, LocalPoseContext, CSPoseContext);
const FRotator FullTargetRotationLeftEye = GetRotationToTarget(LeftEyeBoneData, LocalPoseContext, CSPoseContext);
//remove the way that the torso and head already made //remove the way that the torso and head already made
TargetRotationRightEye = FullTargetRotationRightEye - (HeadBoneData.CurrentRotation + TorsoBoneData.CurrentRotation); FRotator TargetRotationRightEye = FullTargetRotationRightEye - (HeadBoneData.CurrentRotation + TorsoBoneData.CurrentRotation);
TargetRotationLeftEye = FullTargetRotationLeftEye - (HeadBoneData.CurrentRotation + TorsoBoneData.CurrentRotation); FRotator TargetRotationLeftEye = FullTargetRotationLeftEye - (HeadBoneData.CurrentRotation + TorsoBoneData.CurrentRotation);
//add (micro) saccade offset //add (micro) saccade offset
TargetRotationRightEye += VHGazingData.AdditionalSaccadeRotation; TargetRotationRightEye += VHGazingData.AdditionalSaccadeRotation;
...@@ -202,9 +179,27 @@ void FAnimNode_VHGazing::UpdateRotations(FPoseContext& LocalPoseContext, FCompon ...@@ -202,9 +179,27 @@ void FAnimNode_VHGazing::UpdateRotations(FPoseContext& LocalPoseContext, FCompon
TargetRotationLeftEye -= AdditionalHeadRotation; TargetRotationLeftEye -= AdditionalHeadRotation;
} }
// clamp eye rotation to Ocular Motor Range (OMR) // clamp eye rotation to Ocular Motor Range (OMR), store value before to see whether lower body parts can help in reaching the goal
TargetRotationRightEye = ClampRotation(TargetRotationRightEye, RightEyeBoneData, VHGazingData.RightEyeData); const FRotator PreClampedRightEye = TargetRotationRightEye;
TargetRotationLeftEye = ClampRotation(TargetRotationLeftEye, LeftEyeBoneData, VHGazingData.LeftEyeData); const FRotator PreClampedLeftEye = TargetRotationLeftEye;
float DistanceToClampLeftEye, DistanceToClampRightEye;
TargetRotationRightEye = ClampRotation(TargetRotationRightEye, VHGazingData.RightEyeData, DistanceToClampRightEye);
TargetRotationLeftEye = ClampRotation(TargetRotationLeftEye, VHGazingData.LeftEyeData, DistanceToClampLeftEye);
FRotator MissingLeft = PreClampedLeftEye - TargetRotationLeftEye;
FRotator MissingRight = PreClampedRightEye - TargetRotationRightEye;
// store what head and torso need to take over for next frame.
// if this angele is small, we probably only reached the target because the head + torso helped,
// so don't set the missing part to 0 directly to avoid jitter
if ((DistanceToClampRightEye > 1.f && MissingRight.IsNearlyZero()) || !MissingRight.IsNearlyZero()) {
// however, take the mean of this and last frame to avoid jittering
RightEyeBoneData.MissingRotationDueToClamp = 0.5 * (RightEyeBoneData.MissingRotationDueToClamp + MissingRight);
}
if ((DistanceToClampLeftEye > 1.f && MissingLeft.IsNearlyZero()) || !MissingLeft.IsNearlyZero()) {
// however, take the mean of this and last frame to avoid jittering
LeftEyeBoneData.MissingRotationDueToClamp = 0.5 * (LeftEyeBoneData.MissingRotationDueToClamp + MissingLeft);
}
//compute the dynamic update to the CurrentRotation //compute the dynamic update to the CurrentRotation
UpdateCurrentRotation(RightEyeBoneData, VHGazingData.RightEyeData, TargetRotationRightEye); UpdateCurrentRotation(RightEyeBoneData, VHGazingData.RightEyeData, TargetRotationRightEye);
...@@ -229,16 +224,6 @@ void FAnimNode_VHGazing::UpdateCurrentRotation(FGazingBoneData& GazingBoneData, ...@@ -229,16 +224,6 @@ void FAnimNode_VHGazing::UpdateCurrentRotation(FGazingBoneData& GazingBoneData,
FQuat TargetRotQuat = TargetRotation.Quaternion(); FQuat TargetRotQuat = TargetRotation.Quaternion();
const float Angle = FMath::RadiansToDegrees(CurrRotQuat.AngularDistance(TargetRotQuat)); const float Angle = FMath::RadiansToDegrees(CurrRotQuat.AngularDistance(TargetRotQuat));
//float MinAngle = DeltaTime * GazingPartData.MaxVelocity;
const float MinAngle = 5.0f; //the above would somewhat make more sense, during testing, however, this magic number performed much better...
if(Angle < MinAngle && GazingBoneData.AmplitudeCurrMovement == 0.0f)
{
// any rotation below MinAngle, is directly applied to avoid jittering etc. with moving targets and especially parallel full-body animations
// so if this is only a small correction movement, apply it!
GazingBoneData.CurrentRotation = TargetRotation;
return;
}
//we need to keep track how far this movement went to adapt speeds according to https://doi.org/10.1145/2724731 //we need to keep track how far this movement went to adapt speeds according to https://doi.org/10.1145/2724731
if (Angle > GazingBoneData.AmplitudeCurrMovement) { if (Angle > GazingBoneData.AmplitudeCurrMovement) {
GazingBoneData.AmplitudeCurrMovement = Angle; GazingBoneData.AmplitudeCurrMovement = Angle;
...@@ -254,19 +239,20 @@ void FAnimNode_VHGazing::UpdateCurrentRotation(FGazingBoneData& GazingBoneData, ...@@ -254,19 +239,20 @@ void FAnimNode_VHGazing::UpdateCurrentRotation(FGazingBoneData& GazingBoneData,
} }
float Velocity = 0.0f; float Velocity = 0.0f;
if (Angle > 0.5f * GazingBoneData.AmplitudeCurrMovement) float FractionMovement = FMath::Clamp(1.0f - Angle / GazingBoneData.AmplitudeCurrMovement, 0.0f, 1.0f); //going from 0.0f at the start of the movement to 1.0f at the end
if (FractionMovement < 0.5f)
{ {
// following https://doi.org/10.1145/2724731 the start velocity factor is 0.5 (for eye) or 0.25 (for head/trunk) MaxVelocity // following https://doi.org/10.1145/2724731 the start velocity factor is 0.5 (for eye) or 0.25 (for head/trunk) MaxVelocity
// velocity then increases linearly for half the saccade to MaxVelocity // velocity then increases linearly for half the saccade to MaxVelocity
Velocity = GazingPartData.MaxVelocity * (GazingPartData.StartVelocityFactor + (1.0f - (Angle / GazingBoneData.AmplitudeCurrMovement)) * 2.0 * (1.0f - GazingPartData.StartVelocityFactor)); Velocity = GazingPartData.MaxVelocity * (GazingPartData.StartVelocityFactor + 2.0f * FractionMovement * (1.0f - GazingPartData.StartVelocityFactor));
//UE_LOG(LogTemp, Warning, TEXT("Phase 1 Angle: %f of Amplitude: %f Speed: %f deltaTime: %f alpha before: %f alpha after: %f"), Angle, GazingBoneData.AmplitudeCurrMovement, Velocity, DeltaTime, Angle/ GazingBoneData.AmplitudeCurrMovement, (Angle - Velocity * DeltaTime) / GazingBoneData.AmplitudeCurrMovement) //UE_LOG(LogTemp, Warning, TEXT("Phase 1 Angle: %f of Amplitude: %f Speed: %f deltaTime: %f alpha before: %f alpha after: %f"), Angle, GazingBoneData.AmplitudeCurrMovement, Velocity, DeltaTime, Angle/ GazingBoneData.AmplitudeCurrMovement, (Angle - Velocity * DeltaTime) / GazingBoneData.AmplitudeCurrMovement)
} }
else if (Angle>0.1) //so we avoid division by 0 for very small angles else
{ {
// for the second half speed has a ease in / ease out look, which was approximated in https://doi.org/10.1145/2724731 // for the second half speed has a ease in / ease out look, which was approximated in https://doi.org/10.1145/2724731
// by (8r^3 - 18r^2 + 12r - 1.5)*v_max for 0.5<=r<1.0 // by (8r^3 - 18r^2 + 12r - 1.5)*v_max for 0.5<=r<1.0 (but only for eye and slightly different for head/torso since it needs to go down
// we aproximate it by quadratic ease in/out // we tried to aproximate it by quadratic ease in/out
Velocity = FMath::InterpEaseInOut(GazingPartData.MaxVelocity, GazingPartData.StartVelocityFactor * GazingPartData.MaxVelocity, 1.0f - 2.0f * Angle / GazingBoneData.AmplitudeCurrMovement, 2.0f); Velocity = FMath::InterpEaseInOut(GazingPartData.MaxVelocity, GazingPartData.StartVelocityFactor * GazingPartData.MaxVelocity, 2.0f * (FractionMovement - 0.5f), 2.0f);
//UE_LOG(LogTemp, Warning, TEXT("Phase 2 Angle: %f of Amplitude: %f Speed: %f deltaTime: %f alpha before: %f alpha after: %f"), Angle, GazingBoneData.AmplitudeCurrMovement, Velocity, DeltaTime, Angle / GazingBoneData.AmplitudeCurrMovement, (Angle - Velocity * DeltaTime) / GazingBoneData.AmplitudeCurrMovement) //UE_LOG(LogTemp, Warning, TEXT("Phase 2 Angle: %f of Amplitude: %f Speed: %f deltaTime: %f alpha before: %f alpha after: %f"), Angle, GazingBoneData.AmplitudeCurrMovement, Velocity, DeltaTime, Angle / GazingBoneData.AmplitudeCurrMovement, (Angle - Velocity * DeltaTime) / GazingBoneData.AmplitudeCurrMovement)
} }
...@@ -381,10 +367,19 @@ FRotator FAnimNode_VHGazing::TransformToAgentForward(FRotator Rotation) const ...@@ -381,10 +367,19 @@ FRotator FAnimNode_VHGazing::TransformToAgentForward(FRotator Rotation) const
return FRotator(Rotation.Roll, Rotation.Yaw, -Rotation.Pitch); return FRotator(Rotation.Roll, Rotation.Yaw, -Rotation.Pitch);
} }
FRotator FAnimNode_VHGazing::ClampRotation(FRotator Rotation, FGazingBoneData& GazingBoneData, const FGazingPartData& GazingPartData) const { FRotator FAnimNode_VHGazing::ClampRotation(FRotator Rotation, const FGazingPartData& GazingPartData, float& MinDistanceToClamp) const {
FRotator OutRotation = Rotation; FRotator OutRotation = Rotation;
OutRotation.Yaw = FMath::Clamp(OutRotation.Yaw, -GazingPartData.MaxYaw, GazingPartData.MaxYaw); OutRotation.Yaw = FMath::Clamp(OutRotation.Yaw, -GazingPartData.MaxYaw, GazingPartData.MaxYaw);
OutRotation.Pitch = FMath::Clamp(OutRotation.Pitch, -GazingPartData.MaxPitch, GazingPartData.MaxPitch); OutRotation.Pitch = FMath::Clamp(OutRotation.Pitch, -GazingPartData.MaxPitch, GazingPartData.MaxPitch);
//compute min distance to nearest border
MinDistanceToClamp = FMath::Max(2 * GazingPartData.MaxYaw, 2 * GazingPartData.MaxPitch); //set it to potential maximum
MinDistanceToClamp = FMath::Min(OutRotation.Yaw - (-GazingPartData.MaxYaw), MinDistanceToClamp);
MinDistanceToClamp = FMath::Min(GazingPartData.MaxYaw - OutRotation.Yaw, MinDistanceToClamp);
MinDistanceToClamp = FMath::Min(OutRotation.Pitch - (-GazingPartData.MaxPitch), MinDistanceToClamp);
MinDistanceToClamp = FMath::Min(GazingPartData.MaxPitch - OutRotation.Pitch, MinDistanceToClamp);
return OutRotation; return OutRotation;
} }
...@@ -93,6 +93,8 @@ void UVHGazing::TickComponent(float DeltaTime, ELevelTick TickType, FActorCompon ...@@ -93,6 +93,8 @@ void UVHGazing::TickComponent(float DeltaTime, ELevelTick TickType, FActorCompon
FaceAnimInstance->VHGazingData.TorsoData.DelayConstant = TorsoRotationDelayConstant; FaceAnimInstance->VHGazingData.TorsoData.DelayConstant = TorsoRotationDelayConstant;
FaceAnimInstance->VHGazingData.TorsoData.DelayLinear = TorsoRotationDelayLinear; FaceAnimInstance->VHGazingData.TorsoData.DelayLinear = TorsoRotationDelayLinear;
FaceAnimInstance->VHGazingData.bSupportClampedRotationByOtherParts = bSupportClampedRotationByOtherParts;
//copy over to body anim instance if needed (remember, e.g., for CC3 BodyAnimInstance==FaceAnimInstance) //copy over to body anim instance if needed (remember, e.g., for CC3 BodyAnimInstance==FaceAnimInstance)
if (BodyAnimInstance != FaceAnimInstance) BodyAnimInstance->VHGazingData = FaceAnimInstance->VHGazingData; if (BodyAnimInstance != FaceAnimInstance) BodyAnimInstance->VHGazingData = FaceAnimInstance->VHGazingData;
......
...@@ -44,6 +44,8 @@ struct FVHGazingData ...@@ -44,6 +44,8 @@ struct FVHGazingData
FGazingPartData LeftEyeData; FGazingPartData LeftEyeData;
FGazingPartData HeadData; FGazingPartData HeadData;
FGazingPartData TorsoData; FGazingPartData TorsoData;
bool bSupportClampedRotationByOtherParts;
}; };
USTRUCT(BlueprintType) USTRUCT(BlueprintType)
...@@ -165,7 +167,9 @@ private: ...@@ -165,7 +167,9 @@ private:
// CAUTION: as of now it does not work for any other DefautAgentForward directions! // CAUTION: as of now it does not work for any other DefautAgentForward directions!
FRotator TransformToAgentForward(FRotator Rotation) const; FRotator TransformToAgentForward(FRotator Rotation) const;
FRotator ClampRotation(FRotator Rotation, FGazingBoneData& GazingBoneData, const FGazingPartData& GazingPartData) const; //Clamps the Rotation between the max jaw and pitch (both directions) given in GazingPartData
// also reports the min distance to any of these clamps, so ~0 if the value was clamped
FRotator ClampRotation(FRotator Rotation, const FGazingPartData& GazingPartData, float& MinDistanceToClamp) const;
const FReferenceSkeleton* RefSkel = nullptr; const FReferenceSkeleton* RefSkel = nullptr;
......
...@@ -141,11 +141,17 @@ public: ...@@ -141,11 +141,17 @@ public:
// HeadAlignment = 1: the head is fully aligned towards the target and the eyes gaze straight ahead. // HeadAlignment = 1: the head is fully aligned towards the target and the eyes gaze straight ahead.
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VHGazing|GazeDynamics") UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VHGazing|GazeDynamics")
float HeadAlignment = 0.9f; float HeadAlignment = 0.9f;
// TorsoAlignment: Value between 0 and 1, to specify how much the torso aligns towards the target. // TorsoAlignment: Value between 0 and 1, to specify how much the torso aligns towards the target.
// same as with HeadAlignment // same as with HeadAlignment
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VHGazing|GazeDynamics") UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VHGazing|GazeDynamics")
float TorsoAlignment = 0.1f; float TorsoAlignment = 0.1f;
// whether, e.g., the head shoul rotate more in case the eyes cannot reach their target without
// still remaining within the limits but potentially surpassing the Alignment factors given
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VHGazing|GazeDynamics")
bool bSupportClampedRotationByOtherParts = true;
//which parts of the body are involved in the gazing movement //which parts of the body are involved in the gazing movement
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VHGazing") UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "VHGazing")
EGazeMode GazeMode = EGazeMode::EyesHeadTorso; EGazeMode GazeMode = EGazeMode::EyesHeadTorso;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment