#математика #алгоритм #php
Товарищи программисты-математики, отдаю на ваш справедливый суд данный класс.
Некоторые очевидные методы, вроде lineTo я убрал, чтобы не было tl;dr, то есть, это
выжимка интересующих меня моментов.
x = $x?$x:rand(1,IMG_SIZE_X);
$this->y = $y?$y:rand(1,IMG_SIZE_Y);
}
/**
* @param point $p
* @param float $dist
*
* @return point
*/
function midPoint(point $p,$dist=0.5){
$new_x = $this->x+($p->x-$this->x)*$dist;
$new_y = $this->y+($p->y-$this->y)*$dist;
$new_point = new point($new_x,$new_y);
return $new_point;
}
}
/**
* Class bezierimage
*/
class bezierimage{
private $img;
public $color;
public $bgcolor;
public $brush_position;
function __construct(){
$this->img = imagecreatetruecolor(IMG_SIZE_X,IMG_SIZE_Y);
$this->color = imagecolorallocate($this->img,255,255,255);
}
/**
* @param point[] $points
* @param float $dist
*
* @return point[]
*/
function getBezierPoints($points,$dist=0.5){
if(count($points)==1){
return $points[0];
}
$new_points = array();
for($i=1;$imidPoint($points[$i],$dist);
}
return $this->getBezierPoints($new_points,$dist);
}
/**
* @param point[] $points
* @param int $precision
*/
function bezierCurve($points,$precision=700){
foreach($points as $point){
$this->putPixel($point);
}
for($i=0;$i<$precision;$i++){
$dist = $i/$precision;
$this->lineTo($this->getBezierPoints($points,$dist),2);
}
}
}
Можно ли, не прибегая к другим языкам, оптимизировать данный код? Он заметно начинает
тормозить уже на порядке 70, а на 100 просто утыкается в ограничение по вложенности
рекурсии. Особенно интересуют два момента:
метод getBezierPoints - оптимизация
метод bezierCurve - динамический подбор количества полигонов, в зависимости от кривизны
линии на данном отрезке.
По поводу сторонних библиотек: к сожалению, чаще всего находятся максимум 3-4 порядка,
так что не вариант, разве что собрать свою.
P.S. таки да, я опять пытаюсь использовать PHP в целях, для которых он не предназначен.
Но интерес больше академический, для практики и правда более 6-10 точек не надо.
Ответы
Ответ 1
По поводу getBezierPoints — можно сделать в один проход без рекурсии. Смотрите. Пусть в начале у нас есть точки p[0], ..., p[n-1]. После первого шага у нас получается q[0] = (p[0] + p[1]) / 2, q[1] = (p[1] + p[2]) / 2, ..., q[n-2] = (p[n-2] + p[n-1]) / 2, то есть q[i] = (p[i] + p[i+1]) / 2. На следующем шаге r[i] = (q[i] + q[i+1]) / 2 = (p[i] + 2p[i+1] + p[i+2]) / 4. Видно, что возникают биномиальные коэффициенты: r[i] = (C_2^0 p[i + 0] + C_2^1 p[i + 1] + C_2^2 p[i + 2]) / 2^2. (Немного математики: По индукции легко доказать, что биномиальные коэффициенты будут и дальше: если на j-м уровне s[i] = (\sum_k=0^{j-1} C_j^k p[i + k]) / 2^j, то на (j+1)-м t[i] = (s[i] + s[i+1]) / 2 = = (\sum_k=0^j (C_j^k + C_j^{k + 1}) p[i + k]) / 2^{j + 1} = (\sum_k=0^j C_{j + 1}^k p[i + k]) / 2^{j + 1}. База индукции очевидна.) Итак, нам нужны биномиальные коэффициенты. Их можно тоже легко вычислить на лету. Конечный алгоритм должен быть таким: int l = count($points); int coeff = 1; point result = $points[0]; for (int idx = 1; idx < l; idx++) { coeff *= (l - idx); coeff /= idx; result += $points[idx] * coeff; } result /= (1 << (l - 1)); // степень двойки Если делить надо не обязательно на 2, аналогично получается вот что: int l = count($points); double alpha = 0.333333; // или любое другое значение между 0 и 1 double beta = 1 - alpha; double beta2alpha = beta / alpha; int coeff = alpha ** (l - 1); // степень point result = $points[0] * coeff; for (int idx = 1; idx < l; idx++) { coeff *= (l - idx); coeff /= idx; coeff *= beta2alpha; result += $points[idx] * coeff; }
Комментариев нет:
Отправить комментарий