Skip to content

Instantly share code, notes, and snippets.

@hazpotts
Last active February 19, 2022 17:03
Show Gist options
  • Save hazpotts/ac0b6f872600ceb7f95d to your computer and use it in GitHub Desktop.
Save hazpotts/ac0b6f872600ceb7f95d to your computer and use it in GitHub Desktop.
Small algorithm for calculating coordinates on a spiral
<?php
/*
* Calculates the coordinates from an Id. The counting starts from 0,0 and then spirals out
* like this:
*
* +--+--+--+-->
* |
* + +--+--+--+
* | | |
* + + +--+ +
* | | | |
* + +--+--+ +
* | |
* +--+--+--+--+
*
* Where id 24 would be 1,2 (x = 1, y = 2)
* Where id 18 would be -2,-1 (x = -2, y = -1)
*
* 21--22--23--24--15
* |
* 20 7---8---9---10
* | | |
* 19 6 1---2 11
* | | | |
* 18 5---4---3 12
* | |
* 17--16--15--14--13
*
* This is calculated by finding the square root of the id, using that to get
* the closest odd whole (perfect) square roots above and below it, to get the
* start and end points of the ring of the spiral that it is on.
* From there the side of the ring is calculated and the position along that side.
*/
function coordinates($id) {
if ($id == 1) {
$x = 0;
$y = 0;
}
else {
$idSqrt = sqrt($id); //find the square root of the id
$lowSqrt = floor($idSqrt); //round the square root down
$highSqrt = ceil($idSqrt); //round the square root up
if ($lowSqrt == $highSqrt) { //if they are the same it's the last position
$lowSqrt = $lowSqrt - 2; //to calculate previous odd square root
}
if ($lowSqrt % 2 == 0) { //if in the second half of the ring the low root is even, need to find the previous odd because centered rings are odd number sided squares
$lowSqrt--;
}
if ($highSqrt % 2 == 0) { //same but for first half and high root
$highSqrt++;
}
$previousRingEnd = pow($lowSqrt, 2); //square root rounded down and then squared finds the number at the end of the previous ring
$ringEnd = pow($highSqrt, 2); //the same but for the end of the current ring
$ringPosition = $id - $previousRingEnd; // (15-9 = 6) taking the previous ring from the id finds the position around the current ring
$ringTotal = $ringEnd - $previousRingEnd; // (25-9 = 16) taking the previous ring from the current finds the total count around the current ring
$sideLengthMinusOne = $ringTotal / 4; // 16/4 = 4
$halfSideLengthMO = $sideLengthMinusOne / 2; // 4/2 = 2
$side = $ringPosition/$ringTotal;
/*
* This calculates what side it's on
* and it's position along that side.
* Corner positions count as being the last on the side
* hence the minus 1 on side length, because the first
* corner of each side counted for the previous side.
* Corners could be calculated from either side that they
* are a part of.
*/
switch (true) {
case $side <= 0.25: // right hand side
$x = $halfSideLengthMO;
$y = $halfSideLengthMO - $ringPosition;
break;
case $side <= 0.5: // bottom
$x = $halfSideLengthMO - ($ringPosition - $sideLengthMinusOne);
$y = -$halfSideLengthMO;
break;
case $side <= 0.75: // left hand side
$x = -$halfSideLengthMO;
$y = -$halfSideLengthMO + ($ringPosition - ($sideLengthMinusOne*2));
break;
case $side > 0.75: // top
$x = -$halfSideLengthMO + ($ringPosition - ($sideLengthMinusOne*3));
$y = $halfSideLengthMO;
break;
}
}
return compact('x','y');
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment