I mentioned recently that I was looking for an interesting PHP project. This evening I remembered an idea I had a while ago to make a 3D renderer in PHP (and if anyone's tempted to ask - "because I can" ). I did a bit of reading on old 3D engines, as I didn't want something modern and slow, and found out that the "raycaster" rendering used in Wolfenstein 3D is ridiculously easy to implement. A few hours later and it's up and running I've not run any proper speed tests yet, but the images load up pretty much instantaneously. I have a couple of ideas of what this could be used for, so watch this space Sample render: Works off axis too of course: And on the vertical: The code is currently just over 300 lines long. It could be compressed down a lot (maybe even to half it's current size) just to show how stupidly simple things are with PHP. It will bulk out marginally once I get all of the functions in place for setting up the render size, camera position, angle, FOV etc as they are currently all just variables set in __construct. Even the map is just being randomly generated and thrown in. Next real things to do will be to expand the texture mapping somewhat (at the moment it's just 1 for each of walls, floor and ceiling) and get it showing sprites. Then maybe HDR and anisotropic filtering
Wow, that's awesome. That could be used for some great stuff. 3d website, HTML based RPG, stuff like that!
Thanks, Jazz. I did consider it could be used to add some visual depth to those HTML-based RPG games. Would be pretty easy to do, but until I've run some load tests I couldn't say how it would affect the server, it might get pretty intensive if the site's getting a lot of hits. One of the big overheads at the moment is that the textures have to be loaded from disk each time, and there are a lot of sin/cos/tan calculations. It's currently using lookup tables for those, but again, it's having to either generate or load those each time the script runs so some persistent memory or suchlike may need to be looked at Of course if you wanted this kind of thing on a large site it would make more sense to just code the renderer in C or something and have your scripts execute the program when necessary. PHP's not bad for things like this, but it's far from being the fastest.
Surely you're loading .dlls and just calling functions from within the DLL using PHP? I'm not denying that it's cool - it is - but if i'm right then PHP isn't doing the fun stuff Please tell me I'm 110% wrong!
No I'm not, RTT, that would be silly The only thing I'm using is mathematical functions, binary operators and the GD library. No extensions, no DLLs, just classic software rendering.
Here are the functions used RTT : require_once mt_rand sin cos tan pi sqrt pow imagecreatetruecolor imagecreatefrompng imagecopy imagedestroy imagepng imagecolorat imagesetpixel header Plus of course the functions in the class I've written.
Right now I'm going to bed, but maybe tonight (Friday) I'll get a chance to, if I'm not out pimping of course
require_once aint a function Hehe... can't wait for updates on this. Some source wouldn't go amiss either /curious
Code for those who were asking: PHP: public function render() { // find which map block the camera is in $cameraBlockX = $this->camX >> 6; $cameraBlockY = $this->camY >> 6; $offset = 0; // clamp angle for lookup tables if ($this->camAngle < 0) $this->camAngle += pi() * 2; if ($this->camAngle > pi() * 2) $this->camAngle -= pi() * 2; $angle = $this->camAngle + $this->camFov * 0.5; if ($angle > pi() * 2) $angle -= pi() * 2; $width = $this->width; $this->camPCentre = $this->height / 2 - $this->camRoll; $ang = ($this->camAngle * $this->divisor) | 0; $invertZ = 64 - $this->camZ; // loop through each vertical strip while ($width-- > 0) { // set nearest face to practical infinity $nearestFace = 4294967296; $angleLookup = ($angle * $this->divisor); // find oriented face aligned to X axis of map $x = $cameraBlockX; $y = $cameraBlockY; if ($this->sin[$angleLookup] < 0) { while ($y > -1 && $x > -1 && $x < $this->mapWidth) { $absY = $y << 6; $absX = $this->camX + ($absY - $this->camY) / $this->tan[$angleLookup]; $x = $absX >> 6; // check if there is a block here if ($this->map[$x][--$y]) { $nearestFace = pow($absX - $this->camX, 2) + pow($absY - $this->camY, 2); $offset = $absX & 63; break; } } } else { while ($y++ < $this->mapHeight && $x > -1 && $x < $this->mapWidth) { $absY = $y << 6; $absX = $this->camX + ($absY - $this->camY) / $this->tan[$angleLookup]; $x = $absX >> 6; // check if there is a block here if ($this->map[$x][$y]) { $nearestFace = pow($absX - $this->camX, 2) + pow($absY - $this->camY, 2); $offset = 64 - $absX & 63; break; } } } // find closest face oriented to Y axis of map $x = $cameraBlockX; $y = $cameraBlockY; if ($this->cos[$angleLookup] < 0) { while ($x > -1 && $y > -1 && $y < $this->mapHeight) { $absX = $x << 6; $absY = $this->camY + ($absX - $this->camX) * $this->tan[$angleLookup]; $y = $absY >> 6; // check if there is a block here if ($this->map[--$x][$y]) { $distance = pow($absX - $this->camX, 2) + pow($absY - $this->camY, 2); if ($distance < $nearestFace) { $nearestFace = $distance; $offset = 64 - $absY & 63; } break; } } } else { while ($x++ < $this->mapWidth && $y > -1 && $y < $this->mapHeight) { $absX = $x << 6; $absY = $this->camY + ($absX - $this->camX) * $this->tan[$angleLookup]; $y = $absY >> 6; // check if there is a block here if ($this->map[$x][$y]) { $distance = pow($absX - $this->camX, 2) + pow($absY - $this->camY, 2); if ($distance < $nearestFace) { $nearestFace = $distance; $offset = $absY & 63; } break; } } } // check whether the current strip is looking left or right of centre angle if ($angleLookup < $ang) { $distort = $this->camDistance / $this->cos[7200 + $angleLookup - $ang]; } else { $distort = $this->camDistance / $this->cos[$angleLookup - $ang]; } $wallHeight = $distort / sqrt($nearestFace); $distanceFloor = $invertZ * $distort; $distanceCeiling = $this->camZ * $distort; $floorLevel = (int) ($this->camPCentre + $wallHeight * $this->camZ + 0.5); $wallLevel = (int) ($this->camPCentre - $wallHeight * $invertZ); $height = $this->height; // draw floor while (--$height > $floorLevel && $height >= 0) { $distance = $distanceFloor / ($height - $this->camPCentre); imagesetpixel($this->canvas, $width, $height, imagecolorat($this->texFloor, ($this->camX + $this->cos[$angleLookup] * $distance) & 63, ($this->camY + $this->sin[$angleLookup] * $distance) & 63)); } // draw wall while (--$height > $wallLevel && $height >= 0) { imagesetpixel($this->canvas, $width, $height, imagecolorat($this->texWall, $offset, ($height - $wallLevel) / $wallHeight)); } // draw ceiling while (--$height > -1) { $distance = $distanceCeiling / ($this->camPCentre - $height); imagesetpixel($this->canvas, $width, $height, imagecolorat($this->texCeil, ($this->camX + $this->cos[$angleLookup] * $distance) & 63, ($this->camY + $this->sin[$angleLookup] * $distance) & 63)); } $angle -= $this->camPerRayAngle; if ($angle < 0) $angle += pi() * 2; } } The rest of the class needs some work before it's really presentable
Wow, very impressive. I wouldn't even know where to begin with soemthing like this. Damn I hate sucking at math.