Rotating an entity 180 degrees around a specific point in 2D space

My goal is to rotate an entity around a fixed point by 180 degrees. The following is what I have tried so far:

Rotating an entity by x degrees around a point a known distance away is easy enough:

execute positioned ~ ~ ~10 facing entity @s feet rotated ~x ~ positioned ^ ^ ^10 run teleport @s ~ ~ ~

If, as in my case, you want to teleport by exactly 180 degrees it is even easier:

teleport @s ~ ~ ~20

However, I haven't been able to come up with a simple method to rotate an entity around a fixed point in space. The above methods rely on knowing how far away the point around which to rotate is from the entity (in this case exactly 10 blocks down z, relative to the entity). To keep things simple, I stuck with just the xz plane.

The only working method I have found employs a systematic approach using AECs (again around the point 30.5 ~ 30.5):

#> the following is not optimized, purely for clarity's sake
summon area_effect_cloud 30.5 ~ 30.5 {Duration:2,Tags:["rot.origin"]}

# Calculate offset on each axis from Entity E to the AEC (looking down from above): 
#  E-------------
#  |            |
#  |-----------AEC------------|
#               |             |
#               |-------------E*
# Then move the AEC by the offset amount on each axis so it ends up at E*
# Finally, teleport E to E*

# get the offset from the entity to the AEC
execute store result score @s pos.x run data get entity @s Pos[0] 1000
execute store result score @s pos.z run data get entity @s Pos[2] 1000
scoreboard players set @e[tag=rot.origin] pos.x 30500
scoreboard players set @e[tag=rot.origin] pos.z 30500
scoreboard players set @e[tag=rot.origin] offset.x 30500
scoreboard players set @e[tag=rot.origin] offset.z 30500
scoreboard players operation @e[tag=rot.origin] offset.x -= @s pos.x
scoreboard players operation @e[tag=rot.origin] offset.z -= @s pos.z

# move AEC and teleport
scoreboard players operation @e[tag=rot.origin] pos.x += @s offset.x
scoreboard players operation @e[tag=rot.origin] pos.z += @s offset.z
execute as @e[tag=rot.origin] store result entity @s Pos[0] 0.001 run scoreboard players get @s pos.x
execute as @e[tag=rot.origin] store result entity @s Pos[2] 0.001 run scoreboard players get @s pos.z

teleport @s @e[tag=rot.origin,limit=1]
kill @e[tag=rot.origin]

This approach is undoubtedly slow, even after optimizing the selectors into separate functions.

The closest simple solution I found was the following: the entity rotates around the point 30.5 ~ 30.5; but it again relies on knowing the distance to the player (note the 10 block offset):

execute positioned 30.5 ~ 30.5 facing entity @s feet run teleport @s ^10 ^ ^

This is a bit of a mess of a post, I did my best to share everything I've tried so far. If anyone has any ideas on how to this in a simpler fashion I'd love to hear your thoughts. Here is a brief summary of my intentions: Rotate the entity...

  • ...around a fixed, known point; like 30.5 ~ 30.5
  • ...when the entity is an arbitrary distance away, be it 5, 10 or 1412.2222 blocks, from that point.

If I understand it right, you want to mirror E on P to get E'. That's basically just the same as moving E to P and then moving the same distance again in the same direction.

To also change the viewing direction, you can simply add 180°. Minecraft will already handle the cases over 360° properly for you. Unlike the movement, this would get more complicated in 3D.

Actually figuring out the distance between E and P and getting that to be usable in commands is the difficult part. Luckily I did something similar already here.
The steps are:
(0. Preparation)

  1. Get coordinates of the entity at E and the location P into scoreboards.
  2. Subtract the E scores from the P scores to get the difference/distance. I'll actually do this by multiplying the E scores with -1, then adding P to them, that way I can do it for multiple entities at once.
  3. Add that to the P scores to get E'.
  4. Write that back into the entity to move it.
  5. Rotate it.

The commands for that, in this example rotating every armour stand around x=12 z=34 (with 0.02 blocks precision):

/scoreboard objectives add const dummy
/scoreboard objectives add x dummy
/scoreboard objectives add z dummy
/scoreboard players set -1 const -1

/scoreboard players set P x 600
/scoreboard players set P z 1700
/execute as @e[type=armor_stand] store result score @s x run data get entity @s Pos[0] 50
/execute as @e[type=armor_stand] store result score @s z run data get entity @s Pos[2] 50

/scoreboard players operation @e[type=armor_stand] x *= -1 const
/scoreboard players operation @e[type=armor_stand] z *= -1 const
/scoreboard players operation @e[type=armor_stand] x += P x
/scoreboard players operation @e[type=armor_stand] z += P z

/scoreboard players operation @e[type=armor_stand] x += P x
/scoreboard players operation @e[type=armor_stand] z += P z

/execute as @e[type=armor_stand] store result entity @s Pos[0] double 0.02 run scoreboard players get @s x
/execute as @e[type=armor_stand] store result entity @s Pos[2] double 0.02 run scoreboard players get @s z

/execute as @e[type=armor_stand] at @s run tp @s ~ ~ ~ ~180 ~

Some more explanation:

  • Using a scoreboard named const for numbers is somewhat of a coding standard in Minecraft commands.
  • Scoreboards only hold integers, so you need to multiply the coordinates by 50 and later by 0.02 to get higher precision. That gives you 12*50=600 and 34*50=1700.
  • Pos is an array with the X, Y and Z coordinate in it. If the entity is not a player, you can write into it to teleport the entity. If it is a player, you'll need to use a dummy entity and teleport the player to it afterwards (in the same tick if it goes into unloaded chunks).
  • I've used the name "E" from your post. You would probably want to choose a better fake player name. "E" could in theory be a very old player name, I think. You can start it with e.g. $ to prevent such collisions or with #, which also hides it from scoreboard displays.