Code de principe
Principe pour trouver la ligne droite entre A(Ax,Ay) et B(Bx, By) dans une carte de taille [X,Y] dont le bord droit rejoint le gauche et dont le bord haut rejoint le bas:
Démonstration rapide
(Bx - Ax) est un réel quelconque, de ]-infinity .. 0 .. +infinity[
(Bx - Ax + X/2) ∈ ]-infinity .. X/2 .. +infinity[
(Bx - Ax + X/2) % X ∈ [0 .. X/2 .. X[
(Bx - Ax + X/2) % X - X/2 ∈ [-X/2 .. 0 .. X/2[
Ce dernier intervalle est centré autour de zéro, et de rayon X/2, le rayon de la carte en abscisse. Idem sur Y. On obtient donc un vecteur dont les coordonnées sont toutes deux inférieures au rayon de la carte (en valeur absolu), et on a bien le bouclage.
Code PHP complet
Use case complet en PHP, la classe TorusVectorCalculator en est le coeur; attention au modulo négatif de PHP.
Proposition de correction du code Python
Pour le code Python proposé, si A et B sont tous deux hors de la carte central, il y a un problème, non?
Si la carte fait 1000x1000, mais que A est en (-10.000, -10.000) et B en (10.000, 10.000), alors le calcul n'est plus correct.
Il faudrait ajuster les "xb_possibles" avec le modulo de la taille de la carte:
Principe pour trouver la ligne droite entre A(Ax,Ay) et B(Bx, By) dans une carte de taille [X,Y] dont le bord droit rejoint le gauche et dont le bord haut rejoint le bas:
Code :
X = (Bx - Ax + X/2) % X - X/2
Y = (By - Ay + Y/2) % Y - Y/2
Démonstration rapide
(Bx - Ax) est un réel quelconque, de ]-infinity .. 0 .. +infinity[
(Bx - Ax + X/2) ∈ ]-infinity .. X/2 .. +infinity[
(Bx - Ax + X/2) % X ∈ [0 .. X/2 .. X[
(Bx - Ax + X/2) % X - X/2 ∈ [-X/2 .. 0 .. X/2[
Ce dernier intervalle est centré autour de zéro, et de rayon X/2, le rayon de la carte en abscisse. Idem sur Y. On obtient donc un vecteur dont les coordonnées sont toutes deux inférieures au rayon de la carte (en valeur absolu), et on a bien le bouclage.
Code PHP complet
Use case complet en PHP, la classe TorusVectorCalculator en est le coeur; attention au modulo négatif de PHP.
<?php
interface IVector2D
{
public function __construct($p_x, $p_y); // constructeur, (abscisse; ordonnée)
public function getX(); // get abscisse
public function getY(); // get ordonnée
public function setX($p_x); // set abscisse
public function setY($p_y); // set ordonnée
public function getLength2(); // longueur au carré
public function getLength(); // longueur du vecteur
}
interface IVectorCalculator // Calcul vectoriel dans une carte
{
public function getAB(\IVector2D $p_A, \IVector2D $p_B); // renvoit le vecteur AB dans la carte
}
/**
* Vecteur 2D classique, quelque soit le repère
*/
class vec2d implements IVector2D
{
protected $x, $y;
public function __construct($p_x, $p_y)
{
$this->setX($p_x);
$this->setY($p_y);
}
public function getX()
{
return $this->x;
}
public function getY()
{
return $this->y;
}
public function setX($p_x)
{
$this->x = (double)$p_x;
}
public function setY($p_y)
{
$this->y = (double)$p_y;
}
public function __toString()
{
return '('.$this->getX().';'.$this->getY().')';
}
public function getLength2()
{
return ($this->getX()*$this->getX() + $this->getY()*$this->getY());
}
public function getLength()
{
return sqrt($this->getLength2());
}
}
/**
* Calculateur vectoriel dans un espace type "tore"
*/
class TorusVectorCalculator implements IVectorCalculator
{
const tailleX = 1000; // taille de la carte à l'horizontale
const tailleY = 800; // taille de la carte à la verticale
public function getAB(\IVector2D $p_A, \IVector2D $p_B)
{
$ABVector = new vec2d(
modPositive($p_B->getX() - $p_A->getX() + $this::tailleX/2, $this::tailleX) - $this::tailleX/2,
modPositive($p_B->getY() - $p_A->getY() + $this::tailleY/2, $this::tailleY) - $this::tailleY/2
);
var_dump($p_A . '->' . $p_B . ' = ' . $ABVector . ' ['.$ABVector->getLength().']');
// ouais, le var_dump ne devrait pas être dans la classe mais bon, tant pis
return $ABVector;
}
}
/**
* Renvoie le reste dans la division euclidienne de $p_n par $p_mod
* Ou, autrement dit, le modulo mais toujours positif
*/
function modPositive($p_n, $p_mod)
{
// return ($p_n % $p_mod); // PHP renvoie le modulo dans ]-mod .. +mod[
return (($p_n % $p_mod) + $p_mod)%$p_mod;
}
/**
* Teste si $p_value = $p_expected
*/
function test($p_value, $p_expected)
{
assert($p_value == $p_expected);
}
//
// Tests du calculateur vectoriel
// Les 2 derniers comparent les carrés des distances, car leur racine ne serait pas un entier
// et comparer des flottants, on oublie !
//
test( (new TorusVectorCalculator())->getAB(new vec2D(120, 100), new vec2D(200, 100))->getLength(), 80);
// le bouclage n'influe pas
test( (new TorusVectorCalculator())->getAB(new vec2D(120, 100), new vec2D(900, 100))->getLength(), 220);
// bouclage horizontal seul
test( (new TorusVectorCalculator())->getAB(new vec2D(900, 100), new vec2D(120, 100))->getLength(), 220);
// bouclage horizontal seul
test( (new TorusVectorCalculator())->getAB(new vec2D(120, 100), new vec2D(120, 700))->getLength(), 200);
// bouclage vertical seul
test( (new TorusVectorCalculator())->getAB(new vec2D(120, 700), new vec2D(120, 100))->getLength(), 200);
// bouclage vertical seul
test( (new TorusVectorCalculator())->getAB(new vec2D(120, 100), new vec2D(900, 700))->getLength2(), 88400);
// les 2 bouclages
test( (new TorusVectorCalculator())->getAB(new vec2D(900, 700), new vec2D(120, 100))->getLength2(), 88400);
// les 2 bouclages
// Test si A et B sont hors de la carte
// La distance ne doit pas changer
test( (new TorusVectorCalculator())->getAB(new vec2D(900+4*1000, 700+2*800), new vec2D(120-6*1000, 100-4*800))->getLength2(), 88400);
// les 2 bouclages
test( (new TorusVectorCalculator())->getAB(new vec2D(900+4*1000, 700-2*800), new vec2D(120-6*1000, 100+4*800))->getLength2(), 88400);
// les 2 bouclages
test( (new TorusVectorCalculator())->getAB(new vec2D(900-4*1000, 700+2*800), new vec2D(120+6*1000, 100-4*800))->getLength2(), 88400);
// les 2 bouclages
test( (new TorusVectorCalculator())->getAB(new vec2D(900-4*1000, 700-2*800), new vec2D(120+6*1000, 100+4*800))->getLength2(), 88400);
// les 2 bouclages
// Résultats:
// string '(120;100)->(200;100) = (80;0) [80]' (length=34)
// string '(120;100)->(900;100) = (-220;0) [220]' (length=37)
// string '(900;100)->(120;100) = (220;0) [220]' (length=36)
// string '(120;100)->(120;700) = (0;-200) [200]' (length=37)
// string '(120;700)->(120;100) = (0;200) [200]' (length=36)
// string '(120;100)->(900;700) = (-220;-200) [297.32137494637]' (length=52)
// string '(900;700)->(120;100) = (220;200) [297.32137494637]' (length=50)
// string '(4900;2300)->(-5880;-3100) = (220;200) [297.32137494637]' (length=56)
// string '(4900;-900)->(-5880;3300) = (220;200) [297.32137494637]' (length=55)
// string '(-3100;2300)->(6120;-3100) = (220;200) [297.32137494637]' (length=56)
// string '(-3100;-900)->(6120;3300) = (220;200) [297.32137494637]' (length=55)
//
// Tous les tests validés
?>
Proposition de correction du code Python
Pour le code Python proposé, si A et B sont tous deux hors de la carte central, il y a un problème, non?
Si la carte fait 1000x1000, mais que A est en (-10.000, -10.000) et B en (10.000, 10.000), alors le calcul n'est plus correct.
Il faudrait ajuster les "xb_possibles" avec le modulo de la taille de la carte:
xb_mod = [xb % width]
yb_mod = [yb % width]
xa_mod = [xa % width]
ya_mod = [ya % width]
# On remplace ensuite les xb, yb, xa, ya du code de ThetaTauTau par le *_mod correspondant
# Je ne connais pas bien le Python, donc je ne suis pas certain de ma syntaxe !