x,$v1->y,$v1->z,$v2->x,$v2->y,$v2->z,$s,$material); //echo "polygonAsWireFrame() lineVoxels=".print_r($lineVoxels,1); $voxels = array_merge($voxels,$lineVoxels); } return $voxels; } ////////////////////////////////////////////////////////////////////////////////////////// // Find the smallest Voxel of a random polygon. // find minimums of X,Y,Z // find maximums of X,Y,Z // find lengths of max-mins for X,Y,Z // Voxel Point = (minX, minY, minZ,longest side) function polygonAsBlocks($vertices,$s=false,$material='default') { if ($s === false) { global $minVoxelSize; $s = $minVoxelSize; } //echo "polygonAsBlocks()\n"; $minX = false; $minY = false; $minZ = false; $maxX = false; $maxY = false; $maxZ = false; foreach ($vertices as $vertex) { if ($minX === false || $vertex->x < $minX) $minX = $vertex->x; if ($minY === false || $vertex->y < $minY) $minY = $vertex->y; if ($minZ === false || $vertex->z < $minZ) $minZ = $vertex->z; if ($maxX === false || $vertex->x > $maxX) $maxX = $vertex->x; if ($maxY === false || $vertex->y > $maxY) $maxY = $vertex->y; if ($maxZ === false || $vertex->z > $maxZ) $maxZ = $vertex->z; } $longestSide = max($maxX-$minX,$maxY-$minY,$maxZ-$minZ); $shortestSide = max(0.0005,min($maxX-$minX,$maxY-$minY,$maxZ-$minZ)); //printf("findVoxelsOfPolygon() shortestSide=$shortestSide\n"); //printf("findVoxelsOfPolygon() minX=$minX, maxX=$maxX\n"); //printf("findVoxelsOfPolygon() minY=$minY, maxY=$maxY\n"); //printf("findVoxelsOfPolygon() minZ=$minZ, maxZ=$maxZ\n"); $voxels = array(); // Could we??? // use the shortest side as our voxel unit, and build up // the partial volume from those blocks....??? $x = $minX; while ($x <= $maxX) { //printf("findVoxelsOfPolygon() X loop x=$x, y=$y, z=$z\n"); $y = $minY; while($y <= $maxY) { //printf("findVoxelsOfPolygon() x=$x, y=$y, z=$z\n"); $z = $minZ; while ($z <= $maxZ) { //printf("findVoxelsOfPolygon() x=$x, y=$y, z=$z, shortestSide=$shortestSide\n"); $voxel = new Voxel(); $voxel->x = (float)$x; $voxel->y = (float)$y; $voxel->z = (float)$z; $voxel->s = (float)$shortestSide; $voxels[]=$voxel; $z+=$shortestSide; } $y+=$shortestSide; } $x+=$shortestSide; } return $voxels; } // $scaleU is the scale in universal coordinates that the voxels will // be scaled to fill. we use 1 by default function rescaleVoxels($voxels,$scaleU=1) { echo "rescaleVoxels()...\n"; $minX = false; $minY = false; $minZ = false; $maxX = false; $maxY = false; $maxZ = false; $vc=0; foreach ($voxels as $voxel) { $vc++; echoProgress("searching for bounds voxel:$vc"); //print_r($voxel); if ($minX === false || $voxel->x < $minX) { //echo "found new minX!\n"; $minX = $voxel->x; } if ($minY === false || $voxel->y < $minY) { //echo "found new miny!\n"; $minY = $voxel->y; } if ($minZ === false || $voxel->z < $minZ) { //echo "found new minZ!\n"; $minZ = $voxel->z; } if ($maxX === false || ($voxel->x+$voxel->s) > $maxX) { $maxX = ($voxel->x+$voxel->s); } if ($maxY === false || ($voxel->y+$voxel->s) > $maxY) { //echo "found new maxY!\n"; $maxY = ($voxel->y+$voxel->s); } if ($maxZ === false || ($voxel->z+$voxel->s) > $maxZ) { //echo "found new maxZ!\n"; $maxZ = ($voxel->z+$voxel->s); } } echoProgress("Done searching for bounds...\n",true); echo "minX=$minX, minY=$minY, minZ=$minZ\n"; echo "maxX=$maxX, maxY=$maxY, maxZ=$maxZ\n"; $widthX = $maxX-$minX; $widthY = $maxY-$minY; $widthZ = $maxZ-$minZ; $widthU = max($widthX,$widthY,$widthZ); echo "widthX=$widthX, widthY=$widthY, widthZ=$widthZ, widthU=$widthU\n"; $vc=0; foreach ($voxels as $voxel) { $vc++; echoProgress("Scaling voxel:$vc"); $voxel->x = $widthX ? (($voxel->x - $minX)/$widthU)*$scaleU : 0; $voxel->y = $widthY ? (($voxel->y - $minY)/$widthU)*$scaleU : 0; $voxel->z = $widthZ ? (($voxel->z - $minZ)/$widthU)*$scaleU : 0; $voxel->s = ($voxel->s/$widthU)*$scaleU; } echoProgress("Done Rescaling Voxels!\n",true); } ////////////////////////////////////////////////////////////////////////////////////////// // Function: voxelPolygon() // Description: Given an array of universal points forming a polygon in 3D space, this // will return an array of voxels that fill the interior of this polygon // The voxels will match size s. The input values x,y,z range 0.0 <= v < 1.0 // To Do: Currently, this will only work on convex polygons. If the polygon is // concave, we need to partition it into smaller convex polygons. // Complaints: Brad :) function polygonAsSurface($vertices,$s=false,$material='default') { if ($s === false) { global $minVoxelSize; $s = $minVoxelSize; } //echo "polygonAsSurface()\n"; //echoProgress("polygonAsSurface()"); //echo "polygonAsSurface() vertices=".print_r($vertices,1); $voxels = array(); // starting at v[1] // for each vertice v // for N over v[v+1] to v[v+2] // draw a line from v to v[v+1~v+2:N] // NOTE: this will result in several overlapping/duplicate voxels, so discard extras!! // ALSO NOTE: this will only work on convex polygons. // If the polygon is concave, we need to partition it into smaller convex polygons $count = count($vertices); $vO = $vertices[0]; //echo "polygonAsSurface() count=$count\n"; //echo "polygonAsSurface() vO=".print_r($vO,1); for($n = 1; $n < $count; $n++) { $nP1 = ($n+1)%$count; //echo "polygonAsSurface() n=$n nP1=$nP1\n"; $vA = $vertices[$n]; $vB = $vertices[$nP1]; //echo "polygonAsSurface() vA=".print_r($vA,1); //echo "polygonAsSurface() vB=".print_r($vB,1); //echo "polygonAsSurface() ... voxelLine({$vA->x},{$vA->y},{$vA->z},{$vB->x},{$vB->y},{$vB->z},$s,$material)\n"; $lAB = voxelLine($vA->x,$vA->y,$vA->z,$vB->x,$vB->y,$vB->z,$s,$material); //echo "**** polygonAsSurface() count(lAB)=".count($lAB)."\n"; $lc=0; foreach($lAB as $vAB) { //echo ">>> polygonAsSurface() vAB=".print_r($vAB,1); //echo ">>> polygonAsSurface() ... voxelLine({$vO->x},{$vO->y},{$vO->z},{$vAB->x},{$vAB->y},{$vAB->z},$s,$material)\n"; $lOAB = voxelLine($vO->x,$vO->y,$vO->z,$vAB->x,$vAB->y,$vAB->z,$s,$material); $lc++; //echoProgress("polygonAsSurface()... voxelLine() $lc"); //echo ">>> polygonAsSurface() count(lOAB)=".count($lOAB)."\n"; $voxels = array_merge($voxels,$lOAB); } } //echo "polygonAsSurface() count(voxels)=".count($voxels)."\n"; //echoProgress("polygonAsSurface() count(voxels)=".count($voxels)); return $voxels; } ////////////////////////////////////////////////////////////////////////////////////////// // Function: voxelLine() // Description: Given two universal points with location x1,y1,z1 and x2,y2,z2 this will // return an array of voxels that connect these two points with a straight // line. The voxels will match size s. // The input values x,y,z range 0.0 <= v < 1.0 // Complaints: Brad :) function voxelLine($x1,$y1,$z1,$x2,$y2,$z2,$s,$material='default') { $voxels = array(); $xV = $x2-$x1; $yV = $y2-$y1; $zV = $z2-$z1; $mV = max(abs($xV),abs($yV),abs($zV)); // max vector, which is the one we'll iterate accoss! $its = round($mV/$s); /** echo "voxelLine() xV=$xV\n"; echo "voxelLine() yV=$yV\n"; echo "voxelLine() zV=$zV\n"; echo "voxelLine() mV=$mV\n"; echo "voxelLine() its=$its\n"; **/ for ($i = 0; $i < $its; $i++) { //echo "voxelLine() loop i=$i\n"; $x = $x1+(($xV/$its)*$i); $y = $y1+(($yV/$its)*$i); $z = $z1+(($zV/$its)*$i); $voxel = new Voxel(); $voxel->x=$x; $voxel->y=$y; $voxel->z=$z; $voxel->s=$s; $voxel->material_name=$material; $voxels[] = $voxel; } return $voxels; } ////////////////////////////////////////////////////////////////////////////////////////// // Function: pointToVoxel() // Description: Given a universal point with location x,y,z this will return the voxel // voxel code corresponding to the closest voxel which encloses a cube with // lower corners at x,y,z, having side of length S. // The input values x,y,z range 0.0 <= v < 1.0 // TO DO: This code is not very DRY. It should be cleaned up to be DRYer. // IMPORTANT: The voxel is returned to you a buffer which you MUST delete when you are // done with it. // Usage: // unsigned char* voxelData = pointToVoxel(x,y,z,s,red,green,blue); // tree->readCodeColorBufferToTree(voxelData); // delete voxelData; // // Complaints: Brad :) global $maxOctet,$minOctet; $maxOctet=0; $minOctet=100; function pointToVoxel($x,$y,$z,$s,$r=false,$g=false,$b=false) { $xTest = $yTest = $zTest = $sTest = 0.5; // First determine the voxelSize that will properly encode a // voxel of size S. $voxelSizeInBits = 0; while ($sTest > $s) { $sTest /= 2.0; $voxelSizeInBits+=3; } $voxelSizeInBytes = round(($voxelSizeInBits/8)+1); $voxelSizeInOctets = round($voxelSizeInBits/3); $voxelBufferSize = $voxelSizeInBytes+1+3; // 1 for size, 3 for color if ($voxelSizeInOctets < 1) { echo "************ WTH!! voxelSizeInOctets=$voxelSizeInOctets s=$s sTest=$sTest ************\n"; } // for debugging, track our largest octet global $maxOctet,$minOctet; //echo "voxelSizeInOctets=$voxelSizeInOctets\n"; $maxOctet = max($maxOctet,$voxelSizeInOctets); $minOctet = min($minOctet,$voxelSizeInOctets); if ($minOctet < 1) { echo "************ WTH!! minOctet=$minOctet\n"; } //echo "maxOctet=$maxOctet\n"; // allocate our resulting buffer $voxelOut = array(); // first byte of buffer is always our size in octets $voxelOut[0]=$voxelSizeInOctets; $sTest = 0.5; // reset sTest so we can do this again. $byte = 0; // we will be adding coding bits here $bitInByteNDX = 0; // keep track of where we are in byte as we go $byteNDX = 1; // keep track of where we are in buffer of bytes as we go $octetsDone = 0; // Now we actually fill out the voxel code while ($octetsDone < $voxelSizeInOctets) { //echo "coding octet: octetsDone=$octetsDone voxelSizeInOctets=$voxelSizeInOctets\n"; if ($x > $xTest) { // $byte = ($byte << 1) | true; $xTest += $sTest/2.0; } else { // $byte = ($byte << 1) | false; $xTest -= $sTest/2.0; } $bitInByteNDX++; // If we've reached the last bit of the byte, then we want to copy this byte // into our buffer. And get ready to start on a new byte if ($bitInByteNDX > 7) { //printf("xTest: got 8 bits, write to array byteNDX=$byteNDX byte=[%08b]\n",$byte); $voxelOut[$byteNDX]=$byte; $byteNDX += 1; $bitInByteNDX=0; $byte=0; } if ($y > $yTest) { // $byte = ($byte << 1) | true; $yTest += $sTest/2.0; } else { // $byte = ($byte << 1) | false; $yTest -= $sTest/2.0; } $bitInByteNDX++; //echo "coding octet: octetsDone=$octetsDone voxelSizeInOctets=$voxelSizeInOctets bitInByteNDX=$bitInByteNDX\n"; // If we've reached the last bit of the byte, then we want to copy this byte // into our buffer. And get ready to start on a new byte if ($bitInByteNDX > 7) { //printf("yTest: got 8 bits, write to array byteNDX=$byteNDX byte=[%08b]\n",$byte); $voxelOut[$byteNDX]=$byte; $byteNDX += 1; $bitInByteNDX=0; $byte=0; } if ($z > $zTest) { // $byte = ($byte << 1) | true; $zTest += $sTest/2.0; } else { // $byte = ($byte << 1) | false; $zTest -= $sTest/2.0; } $bitInByteNDX++; // If we've reached the last bit of the byte, then we want to copy this byte // into our buffer. And get ready to start on a new byte if ($bitInByteNDX > 7) { //printf("zTest: got 8 bits, write to array byteNDX=$byteNDX byte=[%08b]\n",$byte); $voxelOut[$byteNDX]=$byte; $byteNDX += 1; $bitInByteNDX=0; $byte=0; } $octetsDone++; $sTest /= 2.0; } //printf("done with bits: byteNDX=$byteNDX bitInByteNDX=$bitInByteNDX\n"); // If we've got here, and we didn't fill the last byte, we need to zero pad this // byte before we copy it into our buffer. if ($bitInByteNDX > 0 && $bitInByteNDX < 8) { // Pad the last byte while ($bitInByteNDX <= 7) { $byte = ($byte << 1) | false; $bitInByteNDX++; } // Copy it into our output buffer //printf("padding: array byteNDX=$byteNDX byte=[%08b]\n",$byte); $voxelOut[$byteNDX]=$byte; $byteNDX += 1; } // copy color data if ($r !== false) { //printf("color: array byteNDX=$byteNDX red=$r [%08b] green=$g [%08b] blue=$b [%08b]\n",$r,$g,$b); } $voxelOut[$byteNDX]=$r; $voxelOut[$byteNDX+1]=$g; $voxelOut[$byteNDX+2]=$b; return $voxelOut; } function dumpVoxelCode($voxelCode) { foreach($voxelCode as $byte) { printf("[%08b]",$byte); } echo "\n"; } class Material { public $name; public $red; public $green; public $blue; } function randomColorValue($miniumum) { return $miniumum + (rand() % (255 - $miniumum)); } global $all_voxels; global $all_voxel_codes; $all_voxels = array(); function add_voxels($voxels) { global $all_voxels; // users can pass an array or a single voxel, // we're rather work on arrays, so conver a single // voxel into an array if (!is_array($voxels)) { $voxels = array($voxels); } //echo "add_voxels()...".print_r($voxels,1); foreach ($voxels as $voxel) { // we ignore color here! $voxelCode = pointToVoxel($voxel->x,$voxel->y,$voxel->z,$voxel->s,false,false,false); $voxelCode = implode(',',$voxelCode); $all_voxels[$voxelCode]=$voxel; } // echo "all_voxels count=".count($all_voxels)."\n"; } function convertObjFile($inputFileName,$outputFileName) { $inputFile = fopen($inputFileName, "rt"); // these are text files $fCount=0; $default_material = new Material(); $default_material->name = 'default'; $default_material->red = 240; $default_material->green = 240; $default_material->blue = 240; $current_material = $default_material; $materials = Array('default'=>$default_material); $vertices = Array(); $voxels = Array(); // Note: AFAIK we can't be certain that all the vertices are included // at the start of the file before faces. So we need: // 1) scan the entire file, and pull out all the vertices. // 2) Then we can resample that set of vertices to match our coordinate system. // 3) THEN we can process the faces to make voxels $filePasses = array('v','f'); $commands = array('v'=>array('v','f','fill_from'),'f'=>array('f','usemtl','material')); foreach ($filePasses as $pass) { fseek($inputFile,0); // always rewind at the start of each pass // Before we do the face pass, resample the vertices if ($pass == 'f') { // now we actually need to rescale our voxels to fit in a 0-1 coordinate space echo "vertices PRE-rescale:\n"; //print_r($vertices); rescaleVoxels($vertices); echo "vertices POST-rescale:\n"; //print_r($vertices); } while (!feof($inputFile) ) { $line_of_text = fgets($inputFile); $line_of_text = ereg_replace('[[:blank:]]+', ' ', $line_of_text); $line_of_text = trim(str_replace('\n', '', $line_of_text)); //echo "$line_of_text"; $parts = explode(' ', $line_of_text); $cmd = $parts[0]; if (in_array($cmd,$commands[$pass])) { switch ($cmd) { // Note: fill from case 'fill_from': // a vertice $v = new Vertice(); $v->x = (float)$parts[1]; $v->y = (float)$parts[2]; $v->z = (float)$parts[3]; $vertices[] = $v; //echo "vertice: ".sizeof($vertices)."\n"; //echo " vertice:".print_r($v,1)."\n"; break; case 'v': // a vertice $v = new Vertice(); $v->x = (float)$parts[1]; $v->y = (float)$parts[2]; $v->z = (float)$parts[3]; $vertices[] = $v; //echo "vertice: ".sizeof($vertices)."\n"; //echo " vertice:".print_r($v,1)."\n"; break; case 'material': // material group... $material_name = trim($parts[1]); $red = $parts[2]; $green = $parts[3]; $blue = $parts[4]; echoProgress("material definition: {$material_name} $red $green $blue\n"); if (!in_array($material,$materials)) { $material = new Material(); } else { $material = $materials[$material_name]; } $material->name = $material_name; $material->red = $red; $material->green = $green; $material->blue = $blue; $materials[$material_name]=$material; break; case 'usemtl': // material group... $material = trim($parts[1]); echoProgress("usemtl: {$material}\n"); if (!isset($materials[$material])) { echoProgress("material: {$material} generating random colors...\n"); $new_material = new Material(); $new_material->name = $material; $new_material->red = randomColorValue(65); $new_material->green = randomColorValue(65); $new_material->blue = randomColorValue(65); $materials[$material]=$new_material; } $current_material = $materials[$material]; break; case 'f': if ($pass=='v') { $ftCount++; } else { // a face! $fCount++; $percentComplete = sprintf("%.2f%%",($fCount/$ftCount)*100); echoProgress("face processing: $fCount of $ftCount {$percentComplete}"); $verticeCount = sizeof($parts) - 1; $faceVertices = Array(); for ($i = 1; $i <= $verticeCount; $i++) { $vdata = $parts[$i]; $vdataParts = explode('/',$vdata); $vref = $vdataParts[0]-1; // indexes in files are 1 based $actualVertice = $vertices[$vref]; $faceVertices[] = $actualVertice; } //echo "face: $fCount material:{$current_material->name}\n"; //echo " face:".print_r($faceVertices,1)."\n"; $voxelsOfPolygon = findVoxelsOfPolygon($faceVertices); //echo "face: $fCount sizeof(voxelOfPolygon)=".sizeof($voxelsOfPolygon)."\n"; foreach ($voxelsOfPolygon as $voxel) { $voxel->material_name = $current_material->name; add_voxels($voxel); } } break; default: // something we don't currently recognize or care about. } } } } fclose($inputFile); // Now that we've made our object. We need to fill it in... if ($outputFileName) { $outputFile = fopen($outputFileName,'wb'); $vCount = 0; global $all_voxels; $vtCount = sizeof($all_voxels); foreach ($all_voxels as $voxel) { $vCount++; $percentComplete = sprintf("%.2f%%",($vCount/$vtCount)*100); echoProgress("writing file: $vCount of $vtCount {$percentComplete}"); $material = $materials[$voxel->material_name]; $r = $material->red; $g = $material->green; $b = $material->blue; //echo "v:$vCount: starting \n"; $voxelCode = pointToVoxel($voxel->x,$voxel->y,$voxel->z,$voxel->s,$r,$g,$b); //print_r($voxel); //echo "v:$vCount: (".sizeof($voxelCode).") "; //dumpVoxelCode($voxelCode); // if we were given an output filename, then write to it. if ($outputFile) { //echo "voxel code size:".sizeof($voxelCode)."\n"; foreach($voxelCode as $byte) { $packedByte = pack('C',$byte); fwrite($outputFile,$packedByte,1); } } } fclose($outputFile); } echo "\n*******\n"; echo "Total Vertices: ".sizeof($vertices)."\n"; echo "Total Voxels: ".sizeof($all_voxels)."\n"; global $maxOctet,$minOctet; echo "Shortest Octet length: {$minOctet}\n"; echo "Largest Octet length: {$maxOctet}\n"; } // -i inputFileName // -o outputFileName // -s minVoxelSize // -m method (block|wireframe|surface) assumes surface $options = getopt("i:o:s:m:"); global $renderMode; //echo "line ".__LINE__." renderMode=$renderMode\n"; // render options switch($options['m']) { case 'blocks': case 'wireframe': case 'surface': $renderMode = $options['m']; break; default: $renderMode = 'surface'; } //echo "line ".__LINE__." renderMode=$renderMode\n"; // voxelsize global $minVoxelSize; //echo "line ".__LINE__." minVoxelSize=$minVoxelSize\n"; if (!empty($options['s'])) { if ($options['s'] < 1.0 && $options['s'] > 0.0) { $minVoxelSize = $options['s']; } } //echo "line ".__LINE__." minVoxelSize=$minVoxelSize\n"; //print_r($options); if (empty($options['i'])) { echo "USAGE: objtohio.php -i 'inputFileName' -o 'outputFileName' -m [blocks|wireframe|surface] -s [voxel size] \n"; } else { echo "Convering OBJ file input:{$options['i']} output:{$options['o']} mode:$renderMode voxelSize:{$minVoxelSize}\n"; convertObjFile($options['i'],$options['o']); /* $filename = $options['i']; $file_handle = fopen($filename, "rt"); // these are text files $line_of_text = fgets($file_handle); echo "read: {$line_of_text}"; fseek($file_handle,0); // always rewind at the start of each pass echo "seek to 0\n"; $line_of_text = fgets($file_handle); echo "read: {$line_of_text}"; fclose($file_handle); */ } ?>