Страницы

Monday, 30 May 2011

Руководство по Image_3D: Возвращение к практическим примерам (часть 9 из 13)

Перевод девятой части руководства по PEAR пакету Image_3D, оригинал http://www.ibm.com/developerworks/opensource/tutorials/os-php-3d/section9.html

Возвращение к практическим примерам

Пока, примеры которые вы видели демонстрировали возможности этого пакета для создания 3D изображения в PHP. Кто бы мог подумать, что этот язык, который был изобретен для управления веб-страницами, может быть использован для создания таких сложных графических файлов? Ну вот все прекрасно и замечательно, но если вы не 3D волшебник или вычислительная машина, вам может быть скучно. Давайте посмотрим, как вы можете взять ваши простые объекты и сценарии командной строки и воссоздать несколько интересных примеров.

Анимация 3D изображений

Графическая библиотека GD в прошлом имела поддержку изображений в формате GIF. Из-за проблем лицензирования, поддержка GIF-изображений была удалена из GD после V1.6, хотя это было восстановлено в V2.0.28. В любом случае, Image_3D не может экспортировать анимированные GIF изображения, а PNG файлы не поддерживают анимацию.

Вместо того чтобы создавать один файл, который будет играть циклические анимации, давайте посмотрим, как вы можете взять несколько файлов для создания анимации. Вы расширите пример с кубом сфер, генерирующий 30 отдельных файлов. В результате при просмотре анимации вы увидите вращающийся куб вокруг двух осей.

Вы будете использовать один сценарий для создания каждого из изображений: $cycles. Просто внутри внешнего цикла, вы установите три вращательных значения, которые будут меняться для каждого файла, но будет применяться к каждой сфере в отдельных файлах. Ваша цель плавная анимация цикла, так что ваши вращения должны проходить через 360 градусов.

Для вращения по оси Y, начните со статического вращательного значения: 30. Оттуда, добавьте увеличение 360-градусного цикла, основанного на файле, который вы создаете в очередности. Оператор модуля используется чтобы срезать любое целое с шагом 360 - вращение 375 будет сокращено до 15 градусов, оставаясь в рамках первого круга вращения.
$rot_y = (30 + (int) (360 / $cycles * $i)) % 360;
Самая трудная часть, генерирующая эти изображения, была пройдена. Остальная часть кода должна быть понятна. Вы создаете объект $world и применяете два источника света. Используя три цикла, вы создаете куб из сфер.

Помните, что первый куб из сфер, который вы построили, имел пять сфер с каждой стороны, что требует создания 125 3D объектов. Сейчас вы создаете 30 этих изображений - более чем 3,000 сфер! В коде ниже, вы сократили до четырех сфер с каждой стороны и снизили уровень детализации каждой сферы до трех (см. листинг 14).

Листинг 14. Создание 30 изображений
<?php
require_once('Image/3D.php');

$cycles = 30;
for ($i=0; $i < $cycles; $i++) {

    $rot_x = 45;
    $rot_y = (30 + (int) (360 / $cycles * $i)) % 360;
    $rot_z = (15 + (int) (360 / $cycles * $i)) % 360;

    $world = new Image_3D();
    $world->setColor(new Image_3D_Color(255, 255, 255));

    $light1 = $world->createLight(-500, 0, -500);
    $light1->setColor(new Image_3D_Color(255, 255, 0));

    $light2 = $world->createLight(300, -300, -1000);
    $light2->setColor(new Image_3D_Color(255, 162, 0));

    for ($x=0; $x < 4; $x++) {
        for ($y=0; $y < 4; $y++) {
            for ($z=0; $z < 4; $z++) {
                $sphere = $world->createObject('sphere', array('r' => 25, 'detail' => 3));

                $sphere->setColor(new Image_3D_Color(255, 255, 255));

                $sphere->transform($world->createMatrix('Move', array(($x * 75) + 50, $y * 75, $z * 75)));

                $sphere->transform($world->createMatrix('Rotation', array($rot_x, $rot_y, $rot_z)));
            }
        }
    }

    $world->transform($world->createMatrix('Move', array(-225, -100, 0)));

    $world->createRenderer('perspectively');
    $world->createDriver('gd');
    $world->render(800, 800, 'animated_png/anim' . ($i+1) . '.png');
}
?>
Выполните этот код из командной строки и пойдите сделайте чашку кофе. Это займет некоторое время.

Следующим шагом будет соединить все эти изображения вместе. Вы сделаете это показав первое изображение на HTML странице и используете JavaScript для замены изображений. Замена изображений на странице является стандартной методикой, обрабатывается изменение src свойства объекта изображения. Ваш пример ниже на JavaScript устанавливает массив изображений для циклической анимации, определяет функции для замены изображений, пишет первое изображение на страницу, и выполняет animate() функцию с помощью таймера setInterval().

Листинг 15. HTML и JavaScript используются для создания циклической анимации
<html>
<head>
<title>Animated PNG</title>
</head>
<body>

<script type="text/javascript">
var imageset = new Object();
imageset.seconds = 0.1;
imageset.imgTag = "animation";
imageset.images = new Array();
for ($i=1; $i <= 30; $i++) {
    imageset.images.push('animation_01/anim' + $i + '.png');
}

function animate(animObj) {
    if (!animObj.index) {
         animObj.index = 0;
    }

    animObj.index++;
    if (animObj.index >= animObj.images.length) {
         animObj.index = 0;
    }

    document.images[ animObj.imgTag ].src = 
                                   animObj.images[ animObj.index ];
}

document.write('<img src="' 
              + imageset.images[0] 
              + '" name="animation" />');

setInterval("animate(imageset);", imageset.seconds * 1000);
</script>

</body>
</html>
В результате анимация (см. раздел Скачать) приводит ваши статические 3D изображения к жизни.

Просмотр динамических изображений в интернете

Одна из трудностей, создания 3D изображений является то, что это может занять много времени, памяти и процессорного времени, что делает их изначально плохо пригодными для использования на веб-сайтах. Что вы можете сделать, чтобы победить эти проблемами?

Во-первых, если вы решили запускать эти сценарии в веб-браузере, вы можете обнаружить, что соединение с сервером обрывается по окончании времени ожидания, пока вы ждете когда пакет Image_3D закончит создание изображения. Вы можете отключить максимальное время выполнения PHP путем размещения следующей строки в верхней части вашего скрипта:
set_time_limit(0);
Создание комплекса 3D изображений могут съедать много памяти в PHP. Запуск из командной строки не должен вызывать проблем, но как только вы запустите эти сценарии через браузер, вы можете обнаружить, опять же, что ваш сервер закрывает соединение. Убедитесь, что вы выделили достаточно памяти для ваших PHP сценариев. Вы можете обычно изменить значение memory_limit в php.ini или в локальном .htaccess файле.

Во всех примерах, Image_3D создает изображения и сохраняет их в файл. Имя файла может быть заменено, с помощью одного из двух выходных потоков PHP:

  • php://stdout
  • php://output

Этот метод не сработает, если вы тестировали на сервере Windows, хотя вполне возможно, что версия GD не длолжным образом поддерживает потоки вывода. Так что если у вас возникли проблемы с этим методом, рассмотрим сохранение изображения в файл, открытие файла и передачу содержимого обратно в буфер вывода. Это может показаться не самым эффективным решением, но помните, что вы создаете 3D изображения и I/O файлов, вероятно, не будет узким местом в этом сценарии.

Помните, что при создании файла с помощью PHP, в заголовки ответа сервера всегда включают соответствующий тип содержимого. В случае изоображений в формате PNG, используйте image/png, как показано ниже:
header("Content-type: image/png");

Кэширование динамических файлов

Создание 3D изображений "на лету" это хорошо, но если вы получаете больше, чем несколько запросов к сценарию в час, это может привести к возникновению значительной потери производительности вашего веб-сервера. Давайте рассмотрим пример того, как вы могли бы предотвратить это.

Представьте, что у вас есть сценарий, который создает изображения 3D сфер. Соответственно, вы называете сценарий spheres.php. Если он создает только сферы одного размера и цвета, это не будет очень полезным. Предположим, вы передаете два параметра в скрипт каждый раз, когда его вызываете: первый параметр, радиус, будет значение r для создания объекта сферы. Второй параметр, цвет, будет девятизначное значение RGB цвета сферы. Вот пример того, как вы могли бы вызвать этот сценарий с веб-страницы:
<img src="sphere.php?radius=20&color=255000000" alt="Red ball" />
Предположим, что для создания сферы вы использовали высокий уровень детализации, 5 или больше, и вы, хотите избежать воссоздания изображения каждый раз, когда посетитель заходит на веб-страницу.

Подход который вы будете применять заключается в создании специально сформированного имени файла для каждого созданного изображения, которое будет включать в себя два параметра, используемых для создания динамических изображений. Например, sphere_20_255000000.png.

Каждый раз, когда вызывается сценарий spheres.php, сначала проверьте, есть ли у вас соответствующий файл изображения, который удовлетворяет запросу. Если это так, откройте файл и передайте содержимое в поток вывода. Если нет, создайте объект Image_3D, сохраните новый файл изображения, а затем откройте файл и передайте содержимое в поток вывода. Чтобы увидеть изображение первому посетителю придется подождать некоторое время, но следующие посетители получат версию, которая была сохранена в кэше.

И ваш системный администратор полюбит вас!

Получите свой кусок пирога

Вы, наконец-то, в точке, где увидите действительно замечательный пример Image_3D, который может быть использован непосредственно в ваших приложениях для бизнеса и баз данных.

Image_3D поддерживает еще один тип объекта, который не был представлен ранее: круговая диаграмма. Давайте посмотрим как сектор выглядит в трех измерениях. Каждый сектор будет разнгого цвета, так что вместо использования белых объектов и цветных источников света, вы будете использовать белый свет и цветные объекты.

Начните с вашего стандартного пространства из листинга 1, но замените два источника света одним белого (см. листинг 16).

Листинг 16. Синяя 45-ти градусная секция круговой диаграммы
$light = $world->createLight(0, 1000, 1000);
$light->setColor(new Image_3D_Color(255, 255, 255));

$pie = $world->createObject('pie', array('start' => 0, 
                                         'end' => 45, 
                                         'detail' => 20, 
                                         'outside' => 150));
$pie->setColor(new Image_3D_Color(0, 0, 255));

$world->transform($world->createMatrix('Scale', array(1, 1, 10)));
$world->transform($world->createMatrix('Rotation', array(-60, 0, 0)));
Для создания объекта диаграммы требуется четыре значения в массиве который передается параметром методу createObject(). 'start' и 'end' ссылаются на начало и конец угла сектора в градусах и определяются от 0 градусов. Если бы диаграмма была часами, 0 градусов указывает на 3 часа, а угловые измерения производится в направлении по часовой стрелке. 'detail' задает уровень гладкости объекта, а 'outside' является относительным размером круговой диаграммы.

Вы применяете два преобразования на этом объекте. Преобразование масштаба используется, чтобы задать каждому сектогу глубину размером 10 с помощью оси Z. Вращение, поворачивает пирог в ориентацию, которая используется в бизнесс диаграммах, применяется к оси X (рис. 18).

Рисунок 18. Сектор объекта круговой диаграммы


Когда добавите сектора для постройки диаграммы, вы обнаружите, что они накладываются друг на друга неожиданным образом. Чтобы это исправить достаточно заменить драйвер GD на ZBuffer.

Предположим, вы используете базу данных Apache Derby для хранения записей о клиентах для коммерческого веб-сайта. Каждая запись клиента будет включать в себя почтовый адрес. Быстрый SQL запрос покажет, сколько клиентов живут в каждом штате, как показано в листинге 17.

Листинг 17. Выборка количества клиентов в различных штатах
SELECT COUNT( * ) AS customers, state
FROM customers
GROUP BY state
ORDER BY customers DESC

Проектирование интерфейса класса

Вы можете легко создать разметку, которая преобразует результат выборки из базы данных в таблицу HTML. Давайте представим круговую диаграмму которая связана с таблицей. В этом примере начнем с проектирования интерфейса класса, так как вы хотите, чтобы ваша программа создавала 3D диаграмму. Каждый сектор будет определяться в процентах от всего. Если хотите связать эти сектора непосредственно с данными в таблице HTML, можете присвоить цвет каждому сектору. Наконец, третий параметр позволит выдвинуть конкретные сектора от основной диаграммы ($slice->explode); см. листинг 18.

Листинг 18. Использование класса круговой диаграммы для создания 3D изображения
<?php
require_once 'pie_chart_class.php';

$chart = new PieChart(400, 400, array(255,255,255));

$chart->addSlice(15, array(255,0,0), true);
$chart->addSlice(5,  array(255,255,0), true);
$chart->addSlice(35, array(0,255,0));
$chart->addSlice(30, array(0,255,255));
$chart->addSlice(15, array(0,0,255));

$chart->render('pie.png');
?>
Проценты в листинге выше являются примером, что бы вы сделали верный вывод, как вы будете генерировать результаты из БД Derby и переводить каждое количество клиентов в процент от общего числа клиентов. Это является стандартом взаимодействия с базой данных и чем-то касается вашей задачи.

Построение класса

Теперь у вас есть образец интерфейса класса, заполним пробелы путем создания самого класса. В реализации этого класса нет ничего особенного. Стоит отметить одну деталь, что каждый сектор добавляется в диаграмму в направлении по часовой стрелке. Для любого сектора, со свойством explode установленным в true, вам придется рассчитывать направления вдоль осей X и Y, в которых вы будете выдвигать сектора из диаграммы (см. листинг 19).

Листинг 19. PHP класс для создания 3D круговых диаграмм
<?php
require_once 'Image/3D.php';

class PieChart {

    private $slices;
    private $width;
    private $height;
    private $bg;
    private $world;

    public function __construct($width, $height, $bg_list)
    {
        $this->width = $width;
        $this->height = $height;
        $this->bg = $bg_list;
    }

    public function addSlice ($percent, $color_list, $explode=false)
    {
        $this->slices[] = new PieChart_Slice($percent, $color_list, $explode);
    }

    public function render ($filename)
    {
        $radius = round((min($this->width,$this->height) * 0.85) / 2);

        $world = new Image_3D();
        $world->setColor(new Image_3D_Color($this->bg[0], $this->bg[1], $this->bg[2]));

        $light = $world->createLight(0, 1000, 1000);
        $light->setColor(new Image_3D_Color(255, 255, 255));

        $start = 0;
        foreach ($this->slices as $slice) {

            $end = $start + $slice->degrees;

            $options = array('start'   => $start,  
                             'end'     => $end,
                             'detail'  => 20,      
                             'outside' => $radius);
            $pie = $world->createObject('pie', $options);
            $color = new Image_3D_Color($slice->rgb[0], 
                                        $slice->rgb[1], 
                                        $slice->rgb[2]);
            if ($slice->explode) {
                $mid = $end - (($end - $start) / 2);
                $dx = cos(deg2rad($mid)) * ($radius * 0.15);
                $dy = sin(deg2rad($mid)) * ($radius * 0.15);
                $pie->transform($world->createMatrix('Move', array($dx, $dy, 0)));
                $color->addLight($color, 0.4);
            }
            $pie->setColor($color);

            $start = $end;
        }

        $world->transform($world->createMatrix('Scale', array(1, 1, 10)));
        $world->transform($world->createMatrix('Rotation', array(-60, 0, 0)));

        $world->createRenderer('perspectively');
        $world->createDriver('zbuffer');
        $world->render($this->width, $this->height, $filename);
    }
}

class PieChart_Slice {

    public $percent;
    public $rgb;
    public $degrees;
    public $explode;

    public function __construct($percent, $color_list, $explode=false)
    {
        $this->percent = $percent;
        $this->rgb = $color_list;
        $this->explode = $explode;
        $this->degrees = 360 * ($percent / 100);
    }
}
?>
Полученная диаграмма показана ниже.

Рисунок 19. Круговая диаграмма с выдвинутыми красным и желтым секторами


Другие части перевода

Перед началом работы (часть 1 из 13)
Расположение в пространстве (часть 2 из 13)
Создание вашего первого мира (часть 3 из 13)
Источники света и цвета (часть 4 из 13)
Изменение объектов и форм (часть 5 из 13)
Дополнительные объекты (часть 6 из 13)
Пользовательские формы и поверхности (часть 7 из 13)
Дополнительные драйверы вывода (часть 8 из 13)
Возвращение к практическим примерам (часть 9 из 13)
Суммарно (часть 10 из 13)
Скачать (часть 11 из 13)
Ресурсы (часть 12 из 13)
Об авторе (часть 13 из 13)

Автор перевода: reket.

No comments:

Post a comment