SphericalCoords

From ZDoom Wiki
Jump to navigation Jump to search
Note: This feature is for ZScript only.


struct LevelLocals
static Vector3 SphericalCoords(Vector3 viewpoint, Vector3 targetPos [, Vector2 viewAngles] [, bool absolute]);

Usage

Computes spherical coordinates pointing to targetPos from viewpoint.

Parameters

  • Vector3 viewpoint
Origin point (in world x,y,z coordinates). Computed spherical coordinates will be relative to this origin point.
  • Vector3 targetPos
A distant point (in world x,y,z coordinates). Computed spherical coordinates will point from viewpoint to targetPos.
  • Vector2 viewAngles
2-component vector containing the viewpoint's yaw and pitch angles. This corresponds to the actor fields Angle and Pitch. Default is (0, 0).
  • bool absolute
If true, the calculation will ignore portals. Default is false, meaning portals will be accounted for.

Return Value

A Vector3, with these components:

  • X
How far viewAngles.X (the viewpoint's yaw) must turn left (if negative) or right (if positive) to face targetPos, in degrees. This is the azimuthal angle or φ (phi).
  • Y
How far viewAngles.Y (the viewpoint's pitch) must pitch down (if negative) or up (if positive) to face targetPos, in degrees. This is the polar angle or θ (theta).
  • Z
How far targetPos is from viewpoint, in world units. This is the radius or r.

Note that the Z coordinate is the same as you'd get from LevelLocals.Vec3Diff(viewpoint, targetPos).Length(), and similar to a.Distance3D(b) (where a and b are actors). Distance3D only computes distances between the bottoms of actors, however, and does not account for their heights or viewpoints.

Examples

You can use this to determine how close an actor is to the player's crosshairs.

PlayerPawn viewer;
Actor other;

// Get the world position (x,y,z) of the player's eyes.
Vector3 viewpoint = viewer.Pos;
viewpoint.Z = viewer.Player.viewz;

// Get the world position of the middle of the other actor. Actor.Pos is normally at the very bottom of the actor, so we'll take other.Pos and add half of its height.
Vector3 otherCenter = other.Pos;
otherCenter.Z += other.Height * .5;

// Get the player actor's yaw and pitch.
Vector2 viewAngles = (viewer.Angle, viewer.Pitch);

// Compute how close the other actor is to the player's crosshairs.
Vector3 sphericalCoords = LevelLocals.SphericalCoords(viewpoint, otherCenter, viewAngles);
double distanceFromCenter = sphericalCoords.XY.Length();

if (distanceFromCenter <= 20)
{
	// This block only runs if other is no more than 20° from the center of the player's view.
}

Here's a more complete example: an inventory item that tells you what you're aiming at.

class SimpleActorIdentifier : Inventory {
	override void Tick() {
		if (!Owner) {
			Target = null;
			return;
		}
		
		// Get the world position (x,y,z) of the owner's eyes.
		Vector3 viewpoint = Owner.Pos;
		
		if (Owner.Player)
			viewpoint.Z = Owner.Player.viewz;
		else
			viewpoint.Z += Owner.GetCameraHeight();
		
		// Find the owner's view angles (yaw and pitch).
		Vector2 viewAngles = (Owner.Angle, Owner.Pitch);
		
		// Find the actor that's closest to the owner's crosshairs. Ignore actors that are more than 20° away from the center.
		Actor closest = null;
		double closestDistFromCenter = 20;
		
		for (let i = BlockThingsIterator.Create(Owner, 512); i.Next();) {
			Actor other = i.thing;
			
			// The owner can't aim at itself, of course.
			if (other == Owner)
				continue;
			
			// Get the world position of the middle of the other actor. Actor.Pos is normally at the very bottom of the actor, so we'll take other.Pos and add half of its height.
			Vector3 otherCenter = other.Pos;
			otherCenter.Z += other.Height * .5;
			
			// Compute how close the other actor is to the player's crosshairs.
			Vector3 sphericalCoords = LevelLocals.SphericalCoords(viewpoint, otherCenter, viewAngles);
			double distanceFromCenter = sphericalCoords.XY.Length();
			
			// Is it closer?
			if (distanceFromCenter < closestDistFromCenter) {
				closest = other;
				closestDistFromCenter = distanceFromCenter;
			}
		}
		
		// Now, decide on what message to log, if any.
		String msg = "";
		
		if (!closest && Target)
			// Was aiming at something, but no longer is.
			msg = "Not aiming at anything";
		else if (closest != Target)
			// Was aiming at something else (or nothing).
			msg = String.Format("Now aiming at %s", closest.GetTag(closest.GetClassName()));
		
		// Log the message, if any.
		if (msg)
			Owner.A_Log(msg);
		
		// Update the Target pointer to whatever was chosen. Next tic, we'll check again whether the owner is still aiming at the same thing.
		Target = closest;
	}
}