Optimized Vector Rotation

Specialized rotation methods over Pi and Pi/2 in 2D and 3D.

– Created: November 9, 2023 UTC

– Edited: July 28, 2024 UTC

– Tags: Programming, Zig, Optimization


Came up with some useful optimization for 90 and 180 degree rotations while making a grid walker, below implementations are given, ripped straight from source, lol.

Compared to generic cos/sin method of rotation it’s magnitudes of times less work in many cases, especially if glibc implementation is used.

Note: Given example assumes coordinate system where Y grows downwards and X to the right.

Two dimensions

pub fn rotateByHalfPiClockwise(self: Self) Self {
    return .{ .components = .{ -self.y(), self.x() } };
}

pub fn rotateByHalfPiCounterClockwise(self: Self) Self {
    return .{ .components = .{ self.y(), -self.x() } };
}

pub fn rotateByPi(self: Self) Self {
    return .{ .components = .{ -self.x(), -self.y() } };
}

Three dimensions

pub fn rotateByHalfPiClockwiseAroundAxis(self: Self, axis: Axis3) Self {
    return .{ .components = switch (axis) {
        .x => .{ self.x(), -self.z(), self.y() },
        .y => .{ -self.z(), self.y(), self.x() },
        .z => .{ -self.y(), self.x(), self.z() },
    } };
}

pub fn rotateByHalfPiCounterClockwiseAroundAxis(self: Self, axis: Axis3) Self {
    return .{ .components = switch (axis) {
        .x => .{ self.x(), self.z(), -self.y() },
        .y => .{ self.z(), self.y(), -self.x() },
        .z => .{ self.y(), -self.x(), self.z() },
    } };
}

pub fn rotateByPiAroundAxis(self: Self, axis: Axis3) Self {
    return .{ .components = switch (axis) {
        .x => .{ self.x(), -self.x(), -self.y() },
        .y => .{ -self.x(), self.y(), -self.z() },
        .z => .{ -self.x(), -self.y(), self.z() },
    } };
}

Generated amd64 assembly

Note: Procedure prelude/epilogue is omitted. Zig’s calling convention is used, which is roughly equivalent to C’s static marked function in effect.

Note: It’s for vectors stored packed for use in SSE, array/separate scalar passing produces worse result, at least when not inlined.

rotateByHalfPiClockwise

Notice how it’s one instruction longer than coutner-clockwise case, so, choice of coordinate system effects costs of particular direction to rotate around.

        vmovlpd qword ptr [rsp], xmm0
        vmovshdup       xmm1, xmm0
        vpbroadcastd    xmm2, dword ptr [rip + .LCPI2_0]
        vpxor   xmm1, xmm1, xmm2
        vbroadcastss    xmm0, xmm0
        vblendps        xmm0, xmm0, xmm1, 1

rotateByHalfPiCounterClockwise

        vmovlpd qword ptr [rsp], xmm0
        vpbroadcastd    xmm1, dword ptr [rip + .LCPI1_0]
        vpxor   xmm1, xmm0, xmm1
        vmovshdup       xmm0, xmm0
        vinsertps       xmm0, xmm0, xmm1, 16

rotateByPi

        vmovlpd qword ptr [rsp], xmm0
        vpermilps       xmm0, xmm0, 212
        vpbroadcastd    xmm1, dword ptr [rip + .LCPI3_0]
        vpxor   xmm0, xmm0, xmm1

rotateByHalfPiClockwiseAroundAxis (X)

        sub     rsp, 24
        vmovq   qword ptr [rsp], xmm0
        vpermilpd       xmm1, xmm0, 1
        vmovaps xmm2, xmm1
        vmovss  dword ptr [rsp + 8], xmm2
        vpbroadcastd    xmm2, dword ptr [rip + .LCPI4_0]
        vpxor   xmm1, xmm1, xmm2
        vpermilps       xmm0, xmm0, 212
        vinsertps       xmm0, xmm0, xmm1, 16
        add     rsp, 24

rotateByHalfPiCounterClockwiseAroundAxis (X)

Again, one instruction shorter.

        sub     rsp, 24
        vextractps      dword ptr [rsp + 8], xmm0, 2
        vmovq   qword ptr [rsp], xmm0
        vmovshdup       xmm1, xmm0
        vpbroadcastd    xmm2, dword ptr [rip + .LCPI5_0]
        vpxor   xmm1, xmm1, xmm2
        vpermilps       xmm0, xmm0, 232
        vinsertps       xmm0, xmm0, xmm1, 32
        add     rsp, 24

rotateByPiAroundAxis (X)

Now it’s more work.

        sub     rsp, 24
        vmovq   qword ptr [rsp], xmm0
        vpermilpd       xmm1, xmm0, 1
        vmovaps xmm2, xmm1
        vmovss  dword ptr [rsp + 8], xmm2
        vmovshdup       xmm2, xmm0
        vbroadcastss    xmm3, dword ptr [rip + .LCPI6_0]
        vpxor   xmm2, xmm2, xmm3
        vpxor   xmm1, xmm1, xmm3
        vinsertps       xmm0, xmm0, xmm2, 16
        vinsertps       xmm0, xmm0, xmm1, 32
        add     rsp, 24