С детства есть у меня заветная программерская мечта — написать физический движок. Как и положено мечте, я к ней никогда близко не подходил. Но однажды выдалась пара ночей, когда я должен был дежурить в помещении, и у меня с собой был ноутбук.
В общем, взялся я моделировать движение и столкновение частиц на 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, этот:

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