1<?php
2
3/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4
5/**
6 * 3d Library
7 *
8 * PHP versions 5
9 *
10 * LICENSE:
11 * This library is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU Lesser General Public
13 * License as published by the Free Software Foundation; either
14 * version 2.1 of the License, or (at your option) any later version.
15 *
16 * This library is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19 * Lesser General Public License for more details.
20 *
21 * You should have received a copy of the GNU Lesser General Public
22 * License along with this library; if not, write to the Free Software
23 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
24 *
25 * @category  Image
26 * @package   Image_3D
27 * @author    Kore Nordmann <3d@kore-nordmann.de>
28 * @copyright 1997-2005 Kore Nordmann
29 * @license   http://www.gnu.org/licenses/lgpl.txt lgpl 2.1
30 * @version   CVS: $Id$
31 * @link      http://pear.php.net/package/PackageName
32 * @since     File available since Release 0.1.0
33 */
34
35// {{{ Image_3D_Renderer_Raytrace
36
37/**
38 * Image_3D_Renderer_Raytrace
39 *
40 * @category  Image
41 * @package   Image_3D
42 * @author    Kore Nordmann <3d@kore-nordmann.de>
43 * @copyright 1997-2005 Kore Nordmann
44 * @license   http://www.gnu.org/licenses/lgpl.txt lgpl 2.1
45 * @version   Release: @package_version@
46 * @link      http://pear.php.net/package/PackageName
47 * @since     Class available since Release 0.1.0
48 */
49class Image_3D_Renderer_Raytrace extends Image_3D_Renderer
50{
51
52    protected $_image;
53
54    protected $_camera;
55    protected $_shadows;
56    protected $_rays;
57    protected $_depth;
58
59    public function __construct()
60    {
61        parent::__construct();
62
63        $this->_camera  = new Image_3D_Coordinate(0, 0, -100);
64        $this->_shadows = true;
65        $this->_rays    = 1;
66        $this->_depth   = 5;
67    }
68
69    // {{{ _calculateScreenCoordiantes()
70
71    /**
72     * Caclulate Screen Coordinates
73     *
74     * Does not do anything
75     *
76     * @param Image_3D_Point $point Point to process
77     *
78     * @return  void
79     */
80    protected function _calculateScreenCoordiantes(Image_3D_Point $point)
81    {
82    }
83
84    // }}}
85    // {{{ _sortPolygones()
86
87    /**
88     * Sort polygones
89     *
90     * Does not do anything
91     *
92     * @return  void
93     */
94    protected function _sortPolygones()
95    {
96    }
97
98    // }}}
99    // {{{ setShading()
100
101    /**
102     * Set the quality of the shading
103     *
104     * Does not do anything
105     *
106     * @return  void
107     */
108    public function setShading()
109    {
110    }
111
112    // }}}
113    // {{{ setDriver()
114
115    /**
116     * Set the driver
117     *
118     * Does not do anything
119     *
120     * @return  void
121     */
122    public function setDriver()
123    {
124    }
125
126    // }}}
127    public function setCameraPosition(Image_3D_Coordinate $position)
128    {
129        $this->_camera = $position;
130    }
131
132    public function setRaysPerPixel($rays)
133    {
134        $this->_rays = max(1, (int) $rays);
135    }
136
137    public function scanDepth($depth)
138    {
139        $this->_depth = max(1, (int) $depth);
140    }
141
142    public function enableShadows($shadows)
143    {
144        $this->_shadows = (bool) $shadows;
145    }
146
147    protected function _sendRay(Image_3D_Line $ray, $depth)
148    {
149        if ($depth <= 0) {
150            return false;
151        }
152
153        $lowest  = 1000;
154        $nearest = false;
155        foreach ($this->_polygones as $nr => $polygon) {
156            $t = $polygon->distance($ray);
157            if ($t === false) {
158                continue;
159            }
160
161            if ($t < $lowest) {
162                $nearest = $nr;
163                $lowest  = $t;
164            }
165        }
166
167        if ($nearest === false) {
168            return false;
169        }
170
171        // Examine cutting point
172        $cuttingPoint = clone $ray;
173        $direction    = $cuttingPoint->getDirection();
174        $cuttingPoint->add($cuttingPoint->getDirection()->multiply($lowest));
175
176        // Create point to use for enlightenment
177        $point = new Image_3D_Point($cuttingPoint->getX(), $cuttingPoint->getY(), $cuttingPoint->getZ());
178        $point->addVector($this->_polygones[$nearest]->getNormale());
179        $point->addColor($this->_polygones[$nearest]->getColor());
180
181        // Perhaps send new rays
182        $transparency = end($this->_polygones[$nearest]->getColor()->getValues());
183        $reflection   = $this->_polygones[$nearest]->getColor()->getReflection();
184
185
186        if ($reflection > 0) {
187            // Calculate reflection vector
188            $normale = $this->_polygones[$nearest]->getNormale();
189            $normale->unify();
190
191            $direction = $ray->getDirection();
192
193            $reflectionRay = new Image_3D_Line($cuttingPoint->getX(),
194                                                $cuttingPoint->getY(),
195                                                $cuttingPoint->getZ(),
196                                                // l - 2n (n * l)
197                                                $direction->sub($normale->multiply($normale->scalar($direction))->multiply(2)));
198
199            $reflectionColor = $this->_sendRay($reflectionRay, $depth - 1);
200            if ($reflectionColor === false) {
201                $reflectionColor = $this->_background;
202            }
203            $reflectionColor->mixAlpha($reflection);
204            $point->addColor($reflectionColor);
205        }
206
207        if ($transparency > 0) {
208            // Calculate colors in the back of our polygon
209            $transparencyRay = new Image_3D_Line($cuttingPoint->getX(), $cuttingPoint->getY(), $cuttingPoint->getZ(), $ray->getDirection());
210
211            $transparencyColor = $this->_sendRay($transparencyRay, $depth - 1);
212            if ($transparencyColor === false) {
213                $transparencyColor = $this->_background;
214            }
215            $transparencyColor->mixAlpha($transparency);
216            $point->addColor($transparencyColor);
217        }
218
219        // Check lights influence for cutting point
220        $pointLights = array();
221        foreach ($this->_lights as $light) {
222            // Check for shadow casting polygones
223            if ($this->_shadows) {
224                // Create line from point to light source
225                $lightVector = new Image_3D_Vector($light->getX(), $light->getY(), $light->getZ());
226                $lightVector->sub($cuttingPoint);
227
228                $lightVector = new Image_3D_Line($cuttingPoint->getX(), $cuttingPoint->getY(), $cuttingPoint->getZ(), $lightVector);
229
230                // Check all polygones for possible shadows to cast
231                $modifyingPolygones = array();
232
233                $modifyLight = false;
234                foreach ($this->_polygones as $polygon) {
235                    $t = $polygon->distance($lightVector);
236
237                    // $t > 1 means polygon is behind the light, but crosses the ray
238                    if (($t !== false) && ($t < 1)) {
239                        $transparency = end($polygon->getColor()->getValues());
240                        if ($transparency > 0) {
241                            // Polygon modifies light source
242                            $modifyingPolygones[] = $polygon;
243
244                            $modifyLight = true;
245                        } else {
246                            // Does not use lightsource when non transparent polygon is in its way
247                            continue 2;
248                        }
249                    }
250                }
251
252                // We only reach this code with no, or only transparent polygones
253                if ($modifyLight) {
254                    $light = clone $light;
255
256                    $lightColor = $light->getRawColor()->getValues();
257
258                    // Modify color for all polygones in the rays way to earth
259                    foreach ($modifyingPolygones as $polygon) {
260                        $polygonColor = $polygon->getColor()->getValues();
261
262                        $lightColor[0] *= $polygonColor[0] * (1 - $polygonColor[3]);
263                        $lightColor[1] *= $polygonColor[1] * (1 - $polygonColor[3]);
264                        $lightColor[2] *= $polygonColor[2] * (1 - $polygonColor[3]);
265                        $lightColor[3] *= $polygonColor[3];
266                    }
267
268                    $light->setColor(new Image_3D_Color($lightColor[0], $lightColor[1], $lightColor[2], $lightColor[3]));
269                }
270            }
271
272            $pointLights[] = $light;
273        }
274
275        $point->calculateColor($pointLights);
276        return $point->getColor();
277    }
278
279    protected function _raytrace()
280    {
281        // Create basic ray ... modify direction later
282        $ray = new Image_3D_Line($this->_camera->getX(), $this->_camera->getY(), $this->_camera->getZ(), new Image_3D_Vector(0, 0, 1));
283
284        // Colorarray for resulting image
285        $canvas = array();
286
287        // Iterate over viewplane
288        for ($x = -$this->_size[0]; $x < $this->_size[0]; ++$x) {
289            for ($y = -$this->_size[1]; $y < $this->_size[1]; ++$y) {
290                $canvas[$x + $this->_size[0]][$y + $this->_size[1]] = array();
291
292                // Iterate over rays for one pixel
293                $inPixelRayDiff = 1 / ($this->_rays + 1);
294                for ($i = 0; $i < $this->_rays; ++$i) {
295                    for ($j = 0; $j < $this->_rays; ++$j) {
296
297                        // Modify ray
298                        $ray->setDirection(new Image_3D_Vector(
299                            ($x + $i * $inPixelRayDiff) - $this->_camera->getX(),
300                            ($y + $j * $inPixelRayDiff) - $this->_camera->getY(),
301                            - $this->_camera->getZ()
302                        ));
303
304                        // Get color for ray
305                        $color = $this->_sendRay($ray, $this->_depth);
306                        if ($color !== false) {
307                            $canvas[$x + $this->_size[0]][$y + $this->_size[1]][] = $color;
308                        } else {
309                            $canvas[$x + $this->_size[0]][$y + $this->_size[1]][] = $this->_background;
310                        }
311                    }
312                }
313            }
314        }
315
316        return $canvas;
317    }
318
319    protected function _getColor(Image_3D_Color $color)
320    {
321        $values = $color->getValues();
322
323        $values[0] = (int) round($values[0] * 255);
324        $values[1] = (int) round($values[1] * 255);
325        $values[2] = (int) round($values[2] * 255);
326        $values[3] = (int) round($values[3] * 127);
327
328        if ($values[3] > 0) {
329            // Tranzparente Farbe allokieren
330            $color = imageColorExactAlpha($this->_image, $values[0], $values[1], $values[2], $values[3]);
331            if ($color === -1) {
332                // Wenn nicht Farbe neu alloziieren
333                $color = imageColorAllocateAlpha($this->_image, $values[0], $values[1], $values[2], $values[3]);
334            }
335        } else {
336            // Deckende Farbe allozieren
337            $color = imageColorExact($this->_image, $values[0], $values[1], $values[2]);
338            if ($color === -1) {
339                // Wenn nicht Farbe neu alloziieren
340                $color = imageColorAllocate($this->_image, $values[0], $values[1], $values[2]);
341            }
342        }
343
344        return $color;
345    }
346
347    // {{{ render()
348
349    /**
350     * Render the image
351     *
352     * Render the image into the metioned file
353     *
354     * @param string $file Filename
355     *
356     * @return  void
357     */
358    public function render($file)
359    {
360        // Render image...
361        $canvas = $this->_raytrace();
362
363        // Write canvas to file
364        $this->_image = imagecreatetruecolor($this->_size[0] * 2, $this->_size[1] * 2);
365
366        $bg = $this->_getColor($this->_background);
367        imagefill($this->_image, 1, 1, $bg);
368
369        $x = 0;
370        foreach ($canvas as $row) {
371            $y = 0;
372            foreach ($row as $pixel) {
373                if (count($pixel)) {
374                    $color = new Image_3D_Color();
375                    $color->merge($pixel);
376
377                    imagesetpixel($this->_image, $x, $y, $this->_getColor($color));
378                }
379                ++$y;
380            }
381            ++$x;
382        }
383
384        imagepng($this->_image, $file);
385    }
386
387    // }}}
388}
389
390// }}}
391