I can draw a circle with something like this.
DrawCircle(GetWorld(), GetOwner()->GetActorLocation(),XVector, YVector, DebugLineColor, GetPolarGridElement(i, j, k).X, 100, false, -1.0f, LineDepthInt8, LineThickness);
The problem is, I cant seem to rotate it correctly with the XVector, and YVector values. Here Unreal asks for X Axis vector and Y axis vector. How does this work, whats the math behind it? Say for example I have
FRotator DesiredAngle = FRotator(0,90,45);
How do I convert this to XVector and YVector such that my circle is drawn correctly?
Think of the circle as being drawn on a sheet of paper. The DrawCircle is asking for the 3d vectors that correspond to the edges of the sheet such that it can render it correctly in 3D space.
As a simple example, you can try using the following:
FVector XVector = DesiredAngle.rotateVector(0,1,0);
FVector YVector = DesiredAngle.rotateVector(0,0,1);
I had to look up the axis system of UE4, and I found that for the Y axis is "sideways" and the Z axis is "up", so we rotate these according to DesiredAngle.
Hey finally figured this out thanks to the help from others below. To add to the answer from Botje. If I wanted to draw a circle that follows the rotation of a actor. I do this:
FRotator DesiredAngleZ = GetOwner()->GetActorRotation();
FVector XVector = DesiredAngleZ.RotateVector(FVector(0, 1, 0));
FVector YVector = DesiredAngleZ.RotateVector(FVector(0, 0, 1));
In my case though I wanted to follow the rotation of the actor, but offset by 90 degrees. I thought I could do something like this
FRotator DesiredAngleZ = GetOwner()->GetActorRotation();
DesiredAngleZ += FRotator(0,0,90);
FVector XVector = DesiredAngleZ.RotateVector(FVector(0, 1, 0));
FVector YVector = DesiredAngleZ.RotateVector(FVector(0, 0, 1));
This though fails terribly. Why? because you cant rotate a Rotator like that. My Solution to the problem was to convert both FRotators to quaternions and then multiply them and then convert back. Final solution looks like this:
FQuat tempQuat = GetOwner()->GetActorRotation().Quaternion() *
FRotator(0, 90, 0).Quaternion();
FRotator DesiredAngleZ = tempQuat.Rotator();
FVector XVector = DesiredAngleZ.RotateVector(FVector(0, 1, 0));
FVector YVector = DesiredAngleZ.RotateVector(FVector(0, 0, 1));
Related
I'm making a sniper shooter arcade style game in Gamemaker Studio 2 and I want the position of targets outside of the viewport to be pointed to by chevrons that move along the circumference of the scope when it moves. I am using trig techniques to determine the coordinates but the chevron is jumping around and doesn't seem to be pointing to the target. I have the code broken into two: the code to determine the coordinates in the step event of the enemies class (the objects that will be pointed to) and a draw event in the same class. Additionally, when I try to rotate the chevron so it also points to the enemy, it doesn't draw at all.
Here's the coordinate algorithm and the code to draw the chevrons, respectively
//determine the angle the target makes with the player
delta_x = abs(ObjectPlayer.x - x); //x axis displacement
delta_y = abs(ObjectPlayer.y - y); //y axis displacement
angle = arctan2(delta_y,delta_x); //angle in radians
angle *= 180/pi //angle in radians
//Determine the direction based on the larger dimension and
largest_distance = max(x,y);
plusOrMinus = (largest_distance == x)?
sign(ObjectPlayer.x-x) : sign(ObjectPlayer.y-y);
//define the chevron coordinates
chevron_x = ObjectPlayer.x + plusOrMinus*(cos(angle) + 20);
chevron_y = ObjectPlayer.y + plusOrMinus*(sign(angle) + 20);
The drawing code
if(object_exists(ObjectEnemy)){
draw_text(ObjectPlayer.x, ObjectPlayer.y-10,string(angle));
draw_sprite(Spr_Chevron,-1,chevron_x,chevron_y);
//sSpr_Chevron.image_angle = angle;
}
Your current code is slightly more complex that it needs to be for this, if you want to draw chevrons pointing towards all enemies, you might as well do that on spot in Draw. And use degree-based functions if you're going to need degrees for drawing anyway
var px = ObjectPlayer.x;
var py = ObjectPlayer.y;
with (ObjectEnemy) {
var angle = point_direction(px, py, x, y);
var chevron_x = px + lengthdir_x(20, angle);
var chevron_y = py + lengthdir_y(20, angle);
draw_sprite_ext(Spr_Chevron, -1, chevron_x, chevron_y, 1, 1, angle, c_white, 1);
}
(also see: an almost-decade old blog post of mine about doing this while clamping to screen edges instead)
Specific problems with your existing code are:
Using a single-axis plusOrMinus with two axes
Adding 20 to sine/cosine instead of multiplying them by it
Trying to apply an angle to sSpr_Chevron (?) instead of using draw_sprite_ext to draw a rotated sprite.
Calculating largest_distance based on executing instance's X/Y instead of delta X/Y.
I'm trying to solve an problem where I cannot find the Relative Offset of a Point inside a Box that exists inside of a space that can be arbitrarily rotated and translated.
I know the WorldSpace Location of the Box (and its 4 Corners, the Coordinates on the Image are Relative) as well as its Rotation. These can be arbitrary (its actually a 3D Trigger Volume within a game, but we are only concerned with it in a 2D plane from top down).
Looking at it Aligned to an Axis the Red Point Relative position would be
0.25, 0.25
If the Box was to be Rotated arbitrarily I cannot seem to figure out how to maintain that given we sample the same Point (its World Location will have changed) its Relative Position doesnt change even though the World Rotation of the Box has.
For reference, the Red Point represents an Object that exists in the scene that the Box is encompassing.
bool UPGMapWidget::GetMapMarkerRelativePosition(UPGMapMarkerComponent* MapMarker, FVector2D& OutPosition)
{
bool bResult = false;
if (MapMarker)
{
const FVector MapMarkerLocation = MapMarker->GetOwner()->GetActorLocation();
float RelativeX = FMath::GetMappedRangeValueClamped(
-FVector2D(FMath::Min(GetMapVolume()->GetCornerTopLeftLocation().X, GetMapVolume()->GetCornerBottomRightLocation().X), FMath::Max(GetMapVolume()->GetCornerTopLeftLocation().X, GetMapVolume()->GetCornerBottomRightLocation().X)),
FVector2D(0.f, 1.f),
MapMarkerLocation.X
);
float RelativeY = FMath::GetMappedRangeValueClamped(
-FVector2D(FMath::Min(GetMapVolume()->GetCornerTopLeftLocation().Y, GetMapVolume()->GetCornerBottomRightLocation().Y), FMath::Max(GetMapVolume()->GetCornerTopLeftLocation().Y, GetMapVolume()->GetCornerBottomRightLocation().Y)),
FVector2D(0.f, 1.f),
MapMarkerLocation.Y
);
OutPosition.X = FMath::Abs(RelativeX);
OutPosition.Y = FMath::Abs(RelativeY);
bResult = true;
}
return bResult;
}
Currently, you can see with the above code that im only using the Top Left and Bottom Right corners of the Box to try and calculate the offset, I know this is not a sufficient solution as doing this does not allow for Rotation (Id need to use the other 2 corners as well) however I cannot for the life of me work out what I need to do to reach the solution.
FMath::GetMappedRangeValueClamped
This converts one range onto another. (20 - 50) becomes (0 - 1) for example.
Any assistance/advice on how to approach this problem would be much appreciated.
Thanks.
UPDATE
#Voo's comment helped me realize that the solution was much simpler than anticipated.
By knowing the Location of 3 of the Corners of the Box, I'm able to find the points on the 2 lines these 3 Locations create, then simply mapping those points into a 0-1 range gives the appropriate value regardless of how the Box is Translated.
bool UPGMapWidget::GetMapMarkerRelativePosition(UPGMapMarkerComponent* MapMarker, FVector2D& OutPosition)
{
bool bResult = false;
if (MapMarker && GetMapVolume())
{
const FVector MapMarkerLocation = MapMarker->GetOwner()->GetActorLocation();
const FVector TopLeftLocation = GetMapVolume()->GetCornerTopLeftLocation();
const FVector TopRightLocation = GetMapVolume()->GetCornerTopRightLocation();
const FVector BottomLeftLocation = GetMapVolume()->GetCornerBottomLeftLocation();
FVector XPlane = FMath::ClosestPointOnLine(TopLeftLocation, TopRightLocation, MapMarkerLocation);
FVector YPlane = FMath::ClosestPointOnLine(TopLeftLocation, BottomLeftLocation, MapMarkerLocation);
// Convert the X axis into a 0-1 range.
float RelativeX = FMath::GetMappedRangeValueUnclamped(
FVector2D(GetMapVolume()->GetCornerTopLeftLocation().X, GetMapVolume()->GetCornerTopRightLocation().X),
FVector2D(0.f, 1.f),
XPlane.X
);
// Convert the Y axis into a 0-1 range.
float RelativeY = FMath::GetMappedRangeValueUnclamped(
FVector2D(GetMapVolume()->GetCornerTopLeftLocation().Y, GetMapVolume()->GetCornerBottomLeftLocation().Y),
FVector2D(0.f, 1.f),
YPlane.Y
);
OutPosition.X = RelativeX;
OutPosition.Y = RelativeY;
bResult = true;
}
return bResult;
}
The above code is the amended code from the original question with the correct solution.
assume the origin is at (x0, y0), the other three are at (x_x_axis, y_x_axis), (x_y_axis, y_y_axis), (x1, y1), the object is at (x_obj, y_obj)
do these operations to all five points:
(1)translate all five points by (-x0, -y0), to make the origin moved to (0, 0) (after that (x_x_axis, y_x_axis) is moved to (x_x_axis - x0, y_x_axis - y0));
(2)rotate all five points around (0, 0) by -arctan((y_x_axis - y0)/(x_x_axis - x0)), to make the (x_x_axis - x0, y_x_axis - y0) moved to x_axis;
(3)assume the new coordinates are (0, 0), (x_x_axis', 0), (0, y_y_axis'), (x_x_axis', y_y_axis'), (x_obj', y_obj'), then the object's zero-one coordinate is (x_obj'/x_x_axis', y_obj'/y_y_axis');
rotate formula:(x_new, y_new)=(x_old * cos(theta) - y_old * sin(theta), x_old * sin(theta) + y_old * cos(theta))
Update:
Note:
If you use the distance method, you have to take care of the sign of the coordinate if the object might go out of the scene in the future;
If there will be other transformations on the scene in the future (like symmetry transformation if you have mirror magic in the game, or transvection transformation if you have shockwaves, heatwaves or gravitational waves in the game), then the distance method no longer applies and you still have to reverse all the transformations your scene has in order to get the object's coordinate.
I need to find the world position/orientation of a VR headset (both Oculus and HTC) and then set an actor to be at the same location. Getting the camera's position seems to not work well in VR, so I thought this code was the proper way to do this:
FQuat DeviceRotation;
FVector DevicePosition;
GEngine->XRSystem->GetCurrentPose(0, OUT DeviceRotation, OUT DevicePosition);
myActor->SetActorLocation(DevicePosition);
myActor->SetActorRotation(DeviceRotation);
However, the resulting coordinates are too low on the Z axis and the orientation doesn't properly match.
So what is the proper way to do this in C++? Do I need to factor in the player controller somehow?
UPDATE:
Looking more into this, it seems you have to add the HMD position rotated by the player pawn rotation:
FQuat DeviceRotation;
FVector DevicePosition;
FVector FinalPosition;
GEngine->XRSystem->GetCurrentPose(IXRTrackingSystem::HMDDeviceId, DeviceRotation, DevicePosition);
APlayerController *PlayerController = UGameplayStatics::GetPlayerController(GetWorld(), 0);
FinalPosition = PlayerController->GetPawn()->GetActorRotation().RotateVector(DevicePosition) + PlayerController->PlayerCameraManager->GetCameraLocation();
myActor->SetActorLocation(FinalPosition);
myActor->SetActorRotation(DeviceRotation);
However, the results are still not correct, as there seems to be a rotation offset. I have the camera set to lock on to the HMD, so I'm not sure what else I'm missing here.
Figured it out. GetCurrentPose only gives an offset position for the HMD, so it needs to be added to the player pawn's transform like so:
FQuat hmdRotation;
FVector hmdLocationOffset;
GEngine->XRSystem->GetCurrentPose(IXRTrackingSystem::HMDDeviceId, hmdRotation, hmdLocationOffset);
APawn * playerPawn = UGameplayStatics::GetPlayerPawn(GetWorld(), 0);
myActor->SetActorLocation(playerPawn->GetTransform().TransformPosition(hmdLocationOffset));
myActor->SetActorRotation(hmdRotation);
So I nearly implemented a free-flight camera using vectors and something like gluLookAt.
The movement in all 4 directions and rotation around the Y-axis work fine.
For the rotation around the Y-axis I calculate the vector between the eye and center vector and then rotate it with the rotation matrix like this:
Vector temp = vecmath.vector(center.x() - eye.x(),
center.y() - eye.y(), center.z() - eye.z());
float vecX = (temp.x()*(float) Math.cos(-turnSpeed)) + (temp.z()* (float)Math.sin(-turnSpeed));
float vecY = temp.y();
float vecZ = (temp.x()*(float) -Math.sin(-turnSpeed))+ (temp.z()*(float)Math.cos(-turnSpeed));
center = vecmath.vector(vecX, vecY, vecZ);
At the end I just set center to the newly calculated vector.
Now when I try to do the same thing for rotation around the X-axis it DOES rotate the vector but in a very strange way, kind of like it would be moving in a wavy line.
I use the same logic as for the previous rotation, just with the x rotation matrix:
Vector temp = vecmath.vector(center.x() - eye.x(),
center.y() - eye.y(), center.z() - eye.z());
float vecX = temp.x();
float vecY = (temp.y()*(float) Math.cos(turnSpeed)) + (temp.z()* (float)-Math.sin(turnSpeed));
float vecZ = (temp.y()*(float) Math.sin(turnSpeed)) + (temp.z()*(float)Math.cos(turnSpeed));
center = vecmath.vector(vecX, vecY, vecZ);
But why does this not work? Maybe I do something else somewhere wrong?
The problem you're facing is the exact same that I had trouble with the first time I tried to implement the camera movement. The problem occurs because if you first turn so that you are looking straight down the X axis and then try to "tilt" the camera by rotating around the X axis, you will effectively actually spin around the direction you are looking.
I find that the best way to handle camera movement is to accumulate the angles in separate variables and every time rotate completely from origin. If you do this you can first "tilt" by rotating around the X-axis then turn by rotating around the Y-axis. By doing it in this order you make sure that the tilting will always be around the correct axis relative to the camera. Something like this:
public void pan(float turnSpeed)
{
totalPan += turnSpeed;
updateOrientation();
}
public void tilt(float turnSpeed)
{
totalTilt += turnSpeed;
updateOrientation();
}
private void updateOrientation()
{
float afterTiltX = 0.0f; // Not used. Only to make things clearer
float afterTiltY = (float) Math.sin(totalTilt));
float afterTiltZ = (float) Math.cos(totalTilt));
float vecX = (float)Math.sin(totalPan) * afterTiltZ;
float vecY = afterTiltY;
float vecZ = (float)Math.cos(totalPan) * afterTiltZ;
center = eye + vecmath.vector(vecX, vecY, vecZ);
}
I don't know if the syntax is completely correct. Haven't programmed in java in a while.
I've a 3D box: center point = (a,b,c), width = w, height = h, depth = d.
the center point isn't the origin.
I have a ball on the box(touch each other), its center and radius.
I can rotate the box(around the X axis but its center STAYS the same..) and I want the ball to stay stuck to the box. so the ball needs to be rotated WITH the box.
the angle of the rotation is 45 degrees.
I tried to do this:
I defined the Rotation Matrix around the X axis:
mat[3][3]
1, 0 , 0
0, cos(45), -sin(45)
0, sin(45), cos(45)
and multiply it by the ball center vector:
(ball.Center().m_x , ball.Center().m_y, ball.Center().m_z) * mat
so I got:
Point3D new_center(ball.Center().m_x,
ball.Center().m_y*cos(45) + ball.Center().m_z*sin(45),
-(ball.Center().m_y)*sin(45) + ball.Center().m_z*cos(45));
ball.Center() = new_center;
the ball is really rotated when the box is rotated but too far. How can I fix it?
Have you tried to translate it to the origin of coordinates, rotate and then translate back?
And I think that the coordinates should be multiplied on the right by the transformation matrix, i.e.:
Point3D new_center(ball.Center().m_x,
ball.Center().m_y*cos(45) - ball.Center().m_z*sin(45),
ball.Center().m_y*sin(45) + ball.Center().m_z*cos(45);
ball.Center() = new_center;
thanks to Alexander Mihailov, here's the final answer:
// correcting the ball center to the origin according the box.Center
Point3D ball_center_corrected = ball.Center() - box.Center();
// rotation_matrix(of X axis) * ball_center_corrected
// so the rotation is around the X axis
Point3D new_center(ball_center_corrected.m_x,
ball_center_corrected.m_y*cos(angle) -
ball_center_corrected.m_z*sin(angle),
ball_center_corrected.m_y*sin(angle) +
ball_center_corrected.m_z*cos(angle));
// translate the ball center back around the box
ball.Center() = new_center + box.Center();