С детства есть у меня заветная программерская мечта — написать физический движок. Как и положено мечте, я к ней никогда близко не подходил. Но однажды выдалась пара ночей, когда я должен был дежурить в помещении, и у меня с собой был ноутбук.
В общем, взялся я моделировать движение и столкновение частиц на PHP. Почему на PHP? Потому что это единственный язык, на котором я могу свободно излагать свои программерские мысли. В общем, сначала координаты выводились в консоли, потом стал делать графические снимки. Немедленно появилась мысль генерировать анимацию…

Погуглив, я нашел древненький класс GIFEncoder v.2, который позволял собирать кадры в фильм! Меня это очень воодушевило, и я продолжил эксперименты.
Самой первой, сложной и главной проблемой стало сложение импульсов. Дело в том, что при простом перемещении частицы на X, например, 1, и такой же Y, разбросанные во все 360 сторон частицы не хотели образовывать круг — они упорно выстраивались в ромб. Я плохо учил алгебру, но мне было интересно импровизировать, экспериментируя с коэффициентами.
Второй проблемой стала проекция разбросанных в 3D точек на плоскость. Так как геометрию я учил не лучше алгебры, меня выручили импровизированные коэффициенты: с правильными подобрать более-менее правдоподобную проекцию было бы не просто. Так или иначе, энтузиазма хватило до создания скрипта, генерирующего вышеприведенный ролик. А механизм лучше всего объяснить прямо в коде. Вы должны свободно читать и понимать PHP:
Принцип вам понятен. Одним из первых роликов был этот:

Потом в 3D, этот:

Бред? Возможно. Но не бойтесь реализовывать самые бредовые идеи. Это — лучший способ научиться думать на языке программирования.
В общем, взялся я моделировать движение и столкновение частиц на PHP. Почему на PHP? Потому что это единственный язык, на котором я могу свободно излагать свои программерские мысли. В общем, сначала координаты выводились в консоли, потом стал делать графические снимки. Немедленно появилась мысль генерировать анимацию…

Погуглив, я нашел древненький класс GIFEncoder v.2, который позволял собирать кадры в фильм! Меня это очень воодушевило, и я продолжил эксперименты.
Самой первой, сложной и главной проблемой стало сложение импульсов. Дело в том, что при простом перемещении частицы на X, например, 1, и такой же Y, разбросанные во все 360 сторон частицы не хотели образовывать круг — они упорно выстраивались в ромб. Я плохо учил алгебру, но мне было интересно импровизировать, экспериментируя с коэффициентами.
Второй проблемой стала проекция разбросанных в 3D точек на плоскость. Так как геометрию я учил не лучше алгебры, меня выручили импровизированные коэффициенты: с правильными подобрать более-менее правдоподобную проекцию было бы не просто. Так или иначе, энтузиазма хватило до создания скрипта, генерирующего вышеприведенный ролик. А механизм лучше всего объяснить прямо в коде. Вы должны свободно читать и понимать PHP:
<?php
// В зависимости от задачи, может выполняться и пару часов =)
set_time_limit(0);
// засекаем время
$start = microtime(1);
// GIFEncoder вот отсюда:
// http://saintist.ru/2009/05/12/316/
include('gif_animate.php');
// Пространство для дивжения частиц
class space {
// хранилище частиц
public $points = array();
// вместо матрицы - свойства
// с объектным интерфейсом матрица не нужна
private $x;
private $y;
private $z;
// Шаги. См. метод step.
public $steps = 0;
public function __construct($x, $y, $z) {
$this -> x = $x;
$this -> y = $y;
$this -> z = $z;
}
// -да будет частица...
public function addPoint($point, $x, $y, $z, $fx = 0, $fy = 0, $fz = 0) {
array_push($this -> points, array($point, $x, $y, $z));
$point -> addP($fx, $fy, $fz);
}
// Шаг. Ключевой элемент, в этом методе двигаются частицы.
// В какой-то версии скрипта, они у меня сталкивались.
// Чтобы они (на случай - если столкновения включены) не прыгали сквозь друг друга,
// шаги могут быть дробными. Число, которым выражен шаг = скорости самой медленной
// частицы, но только если она < 1.
// Изначально, 1 шаг = перемещение частицы со скоростью 1 на 1 пиксель за шаг.
// Поэтому дробное число можно считать частью шага.
public function step() {
// вот этим числом выражен шаг
$t = 1;
// Матрица занятых координат.
// Использовалась для поиска столкновений, далее не используется.
$busy = array();
// В этом цикле чистим space от вылетевших за пределы поля частиц
// и вычисляем величину шага
foreach ($this -> points as $n => &$point) {
$x = $point[1];
$y = $point[2];
$z = $point[3];
// Если частица за пределами поля - удаляем её, и пропускаем итерацию
if (!($x >= 0 && $x < $this->x && $y >= 0 && $y < $this->y && $z >= 0 && $z < $this->z)) {
unset($this->points[$n]);
continue;
}
// Сила импульсов частицы в трех измерениях
$f = abs($point[0]->p['fx']) + abs($point[0]->p['fy']) + abs($point[0]->p['fz']);
// Если она вообще движется, и при том меньше 1 и текущей $t
if ($f > 0 && 1 / $f < $t) {
// то меняем числовое выражение шага
$t = 1 / $f;
}
}
// Главный цикл. Перемещение частиц.
foreach ($this -> points as $n => &$point) {
$xyz = abs($point[0]->p['fx']) + abs($point[0]->p['fy']) + abs($point[0]->p['fz']);
// Экспериментально вывел этот коэффициент для сложения импульсов
$hypo = pow(abs($point[0]->p['fx']), 2) + pow(abs($point[0]->p['fy']), 2) + pow(abs($point[0]->p['fz']), 2);
// Переменные вида $move_x - это растояние, на которое должна быть перемещена частица.
// Ниже они вычисляются.
// Эта еденица для X и Y равна пикселю, для Z - визуально заметна только
// при наличии импульса в X или Y, естественно.
if ($point[0]->p['fx'] != 0 && $xyz > 0) {
if ($point[0]->p['fx'] < 0) {
$fxmin = 1;
}
$move_x = sqrt($hypo) * ($point[0]->p['fx'] / $xyz);
if (isset($fxmin)) $move_x = $move_x * -1;
} else {
$move_x = 0;
}
if ($point[0]->p['fy'] != 0 && $xyz > 0) {
if ($point[0]->p['fy'] < 0) {
$fymin = 1;
}
$move_y = sqrt($hypo) * ($point[0]->p['fy'] / $xyz);
if (isset($fymin)) $move_y = $move_y * -1;
} else {
$move_y = 0;
}
if ($point[0]->p['fz'] != 0 && $xyz > 0) {
if ($point[0]->p['fz'] < 0) {
$fzmin = 1;
}
$move_z = sqrt($hypo) * ($point[0]->p['fz'] / $xyz);
if (isset($fzmin)) $move_z = $move_z * -1;
} else {
$move_z = 0;
}
// умножаем на длину шага и меняем координаты
$point[1] += $move_x*$t;
$point[2] += $move_y*$t;
$point[3] += $move_z*$t;
}
$this -> steps+=$t;
}
// Метод делает снимок space в GIF
public function shot() {
$r = imagecreatetruecolor($this -> x+1, $this -> y+1);
// думаю, все очевидно
foreach ($this -> points as &$point) {
$x = $point[1];
$y = $point[2];
$z = $point[3];
if ($x >= 0 && $x < $this->x && $y >= 0 && $y < $this->y && $z >= 0 && $z < $this -> z) {
// оказывается, не всё. Нам надо, что частицы, которые дальше по Z
// двигались медленнее тех, что ближе к нам - по Z.
$ox = $this->x / 2;
$oy = $this->y / 2;
$oz = $this->z / 2;
$xf = $x - $ox;
$yf = $y - $oy;
$zf = $z;
$xr = $x - $xf / ($this->z/($this->z - $z));
$yr = $y - $yf / ($this->z/($this->z - $z));
imageline($r, $xr, $yr, $xr, $yr, $point[0]->color);
}
}
ob_start();
imagegif($r);
return ob_get_clean();
}
}
class Point {
public $p = array('fx' => 0, 'fy' => 0, 'fz' => 0);
public $color;
// Конструктор генерирует случайный цвет
public function __construct() {
$red = dechex(rand(1, 255));
if (strlen($red) < 2) $red = '0'.$red;
$green = dechex(rand(1, 255));
if (strlen($green) < 2) $green = '0'.$green;
$blue = dechex(rand(1, 255));
if (strlen($blue) < 2) $blue = '0'.$blue;
$this->color = hexdec("0x$red$green$blue");
}
public function addP($fx = 0, $fy = 0, $fz = 0) {
$this->p['fx'] += $fx;
$this->p['fy'] += $fy;
$this->p['fz'] += $fz;
}
}
$gifs = array(); // GD-шные GIF-ы, которые потом будут объединены
$space = new space(399, 399, 399, false, true);
// Рандомно генерируем импульсы для частиц.
// Чтобы они летели с разной скоростью, и в разных направлениях
$rands = range(-10, 10, 0.5);
for ($i=0; $i<1000; $i++) {
// ... и стало так
$space->addPoint(new Point, 200, 200, 200, $rands[array_rand($rands)], $rands[array_rand($rands)], $rands[array_rand($rands)]);
}
// запускаем на 100 ц е л ы х шагов...
while($space->steps < 100) {
$nshot = $space -> steps;
$space -> step();
if ((int)$nshot < (int)$space -> steps) {
array_push($gifs, $space -> shot());
echo 'Step #',$space -> steps,"\r\n";
}
}
// собираем кадры в фильм...
$gif = new GIFEncoder($gifs, 0, 0, 0, 0, 0, 0, 'bin');
file_put_contents('c:\anim.gif', $gif -> GetAnimation());
echo "\r\nTime: ",substr(microtime(1) - $start, 0, 4);
// приятного просмотра :-)
Принцип вам понятен. Одним из первых роликов был этот:

Потом в 3D, этот:

Бред? Возможно. Но не бойтесь реализовывать самые бредовые идеи. Это — лучший способ научиться думать на языке программирования.