/******************************************************************************* * bsptree.cpp * * --------------------------------------------------------------------------- * Persistence of Vision Ray Tracer ('POV-Ray') version 3.7. * Copyright 1991-2013 Persistence of Vision Raytracer Pty. Ltd. * * POV-Ray is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * POV-Ray is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * --------------------------------------------------------------------------- * POV-Ray is based on the popular DKB raytracer version 2.12. * DKBTrace was originally written by David K. Buck. * DKBTrace Ver 2.0-2.12 were written by David K. Buck & Aaron A. Collins. * --------------------------------------------------------------------------- * $File: //depot/public/povray/3.x/source/backend/support/bsptree.cpp $ * $Revision: #1 $ * $Change: 6069 $ * $DateTime: 2013/11/06 11:59:40 $ * $Author: chrisc $ *******************************************************************************/ #include #include // frame.h must always be the first POV file included (pulls in platform config) #include "backend/frame.h" #include "backend/support/bsptree.h" #include "backend/shape/boxes.h" #include "base/pov_err.h" // this must be the last file included #include "base/povdebug.h" namespace pov { #ifndef BSP_READNODES #define BSP_READNODES 0 #endif #ifndef BSP_WRITEBOUNDS #define BSP_WRITEBOUNDS 0 #endif #ifndef BSP_WRITETREE #define BSP_WRITETREE 0 #endif #define MAX_BSP_TREE_LEVEL 128 #define OBJECT_ISECT_COST 150.0f #define BASE_ACCESS_COST 1.0f #define CHILD_ACCESS_COST 5.0f #define MISS_CHANCE 0.2f #define BSP_TOLERANCE 0.00001f const unsigned int NODE_PROGRESS_INTERVAL = 1000; // we allow the values to be set by users to promote experimentation with tree // building. at a later date we may remove this facility since using compile-time // constants is more efficient. // // we use 0.0f as defaults rather than making the defaults equal to the above // #defines to avoid having to expose the above values to any code that wants // to construct a BSPTree (e.g. if it only wanted to set mc, for example, it // would have to know the other values). // // missChance is only ever used with 1.0f added to it, so we do that now as well // // NB all these values are declared const in the class definition BSPTree::BSPTree(unsigned int md, float oic, float bac, float cac, float mc) : maxDepth((md == 0) || (md > MAX_BSP_TREE_LEVEL) ? MAX_BSP_TREE_LEVEL : md), objectIsectCost(oic == 0.0f ? OBJECT_ISECT_COST : oic), baseAccessCost(bac == 0.0f ? BASE_ACCESS_COST : bac), childAccessCost(cac == 0.0f ? CHILD_ACCESS_COST : cac), missChance(mc == 0.0f ? MISS_CHANCE + 1.0f : mc + 1.0f) { } BSPTree::~BSPTree() { } static FILE *gFile = NULL; bool BSPTree::operator()(const Ray& ray, Intersect& isect, Mailbox& mailbox, double maxdist) { TraceStack tstack[MAX_BSP_TREE_LEVEL]; Vector3d rayorigin(ray.GetOrigin()); Vector3d raydir(ray.GetDirection()); Vector3d invraydir(Vector3d(1.0) / raydir); double rentry, rexit; int ignore1, ignore2; unsigned int tstackpos = 0; if(Box::Intersect(ray, NULL, *bmin, *bmax, &rentry, &rexit, &ignore1, &ignore2) == false) return false; // no objects hit unsigned int inode = 0; while(rentry < maxdist) { // descend into child if(nodes[inode].type == Node::Split) { unsigned int axis = nodes[inode].data; unsigned int ileft = nodes[inode].index; unsigned int iright = ileft + 1; float plane = nodes[inode].plane; float rdist = (plane - rayorigin[axis]) * invraydir[axis]; // decide which child to descend into if((rayorigin[axis] > plane) || ((rdist == 0.0f) && (raydir[axis] < 0))) swap(ileft, iright); // determine which child is next if((rdist < 0.0f) || (rdist > rexit)) inode = ileft; else if(rdist < rentry) inode = iright; // if both children are intersected, remember one and continue with the other else { // remember right child tstack[tstackpos].inode = iright; tstack[tstackpos].rentry = rdist; tstack[tstackpos].rexit = rexit; tstackpos++; // continue with left child inode = ileft; rexit = rdist; } } else { // insert objects into mailbox switch(nodes[inode].data) { case Node::Empty: // nothing to do break; case Node::SingleObject: if(mailbox.insert(nodes[inode].index) == true) isect(nodes[inode].index, maxdist); break; case Node::DoubleObject: if(mailbox.insert(nodes[inode].index) == true) isect(nodes[inode].index, maxdist); if(mailbox.insert(nodes[inode].index2) == true) isect(nodes[inode].index2, maxdist); break; case Node::ObjectList: for(unsigned int i = nodes[inode].index2, e = i + nodes[inode].index; i < e; i++) { if(mailbox.insert(lists[i]) == true) isect(lists[i], maxdist); } break; } // see if there is another node to process if(tstackpos > 0) { tstackpos--; inode = tstack[tstackpos].inode; rentry = tstack[tstackpos].rentry; rexit = tstack[tstackpos].rexit; } // no nodes left, so terminate loop else break; } } return isect(); // see if any objects were hit } bool BSPTree::operator()(const Vector3d& origin, Inside& inside, Mailbox& mailbox, bool earlyExit) { unsigned int tstackpos = 0; unsigned int tstack[MAX_BSP_TREE_LEVEL]; // make sure the origin is within the bounded volume if ((origin[X] < bmin[X]) || (origin[Y] < bmin[Y]) || (origin[Z] < bmin[Z]) || (origin[X] > bmax[X]) || (origin[Y] > bmax[Y]) || (origin[Z] > bmax[Z])) return false; tstack[tstackpos++] = 0; while(tstackpos > 0) { unsigned int inode = tstack[--tstackpos]; if(nodes[inode].type == Node::Split) { if(origin[nodes[inode].data] <= nodes[inode].plane) tstack[tstackpos++] = nodes[inode].index; if(origin[nodes[inode].data] >= nodes[inode].plane) tstack[tstackpos++] = nodes[inode].index + 1; } else { // insert objects into mailbox switch(nodes[inode].data) { case Node::Empty: // nothing to do break; case Node::SingleObject: if(mailbox.insert(nodes[inode].index) == true) inside(nodes[inode].index); break; case Node::DoubleObject: if(mailbox.insert(nodes[inode].index) == true) inside(nodes[inode].index); if(mailbox.insert(nodes[inode].index2) == true) inside(nodes[inode].index2); break; case Node::ObjectList: for(unsigned int i = nodes[inode].index2, e = i + nodes[inode].index; i < e; i++) if(mailbox.insert(lists[i]) == true) inside(lists[i]); break; } if (earlyExit && inside()) return true; } } return inside(); } void BSPTree::build(const Progress& progress, const Objects& objects, unsigned int& totalnodes, unsigned int& splitnodes, unsigned int& objectnodes, unsigned int& emptynodes, unsigned int& maxobjects, float& averageobjects, unsigned int& maxdepth, float& averagedepth, unsigned int& aborts, float& averageaborts, float& averageabortobjects, const UCS2String& inputFile) { BBOX bbox; lastProgressNodeCounter = 0; maxObjectsInNode = 0; maxTreeDepth = 0; maxTreeDepthNodes = 0; emptyNodeCounter = 0; objectNodeCounter = 0; objectsInTreeCounter = 0; objectsAtMaxDepthCounter = 0; treeDepthCounter = 0; progress(0); bbox.pmin[X] = BOUND_HUGE; bbox.pmin[Y] = BOUND_HUGE; bbox.pmin[Z] = BOUND_HUGE; bbox.pmax[X] = -BOUND_HUGE; bbox.pmax[Y] = -BOUND_HUGE; bbox.pmax[Z] = -BOUND_HUGE; // allocate memory that is going to be needed for building indices.reserve(objects.size() * 4); // can't tell what we need, but we'll start with object count * 4 indices.resize(objects.size()); splits[X].resize(objects.size() * 2); splits[Y].resize(objects.size() * 2); splits[Z].resize(objects.size() * 2); #if BSP_WRITEBOUNDS || BSP_READNODES || BSP_WRITETREE string tempstr = UCS2toASCIIString(inputFile); if (tempstr.empty() == true) tempstr = "default"; string::size_type pos = tempstr.find_last_of('.'); if (pos != string::npos) tempstr.erase(pos); #endif #if BSP_WRITEBOUNDS FILE *bb = fopen(string(tempstr + ".bounds").c_str(), "w"); #else FILE *bb = NULL; #endif if(bb != NULL) fprintf(bb, "%d\n", objects.size()); // find bounding box containing all objects for(unsigned int i = 0; i < objects.size(); i++) { bbox.pmin[X] = min(bbox.pmin[X], objects.GetMin(X, i)); bbox.pmin[Y] = min(bbox.pmin[Y], objects.GetMin(Y, i)); bbox.pmin[Z] = min(bbox.pmin[Z], objects.GetMin(Z, i)); bbox.pmax[X] = max(bbox.pmax[X], objects.GetMax(X, i)); bbox.pmax[Y] = max(bbox.pmax[Y], objects.GetMax(Y, i)); bbox.pmax[Z] = max(bbox.pmax[Z], objects.GetMax(Z, i)); indices[i] = i; if(bb != NULL) fprintf(bb, "%f %f %f %f %f %f\n", objects.GetMin(X, i), objects.GetMin(Y, i), objects.GetMin(Z, i), objects.GetMax(X, i), objects.GetMax(Y, i), objects.GetMax(Z, i)); } if(bb != NULL) { fprintf(bb, "%f %f %f %f %f %f\n", bbox.pmin[X], bbox.pmin[Y], bbox.pmin[Z], bbox.pmax[X], bbox.pmax[Y], bbox.pmax[Z]); fflush(bb); fclose(bb); } // remember bounding box for intersection testing bmin = Vector3d(bbox.pmin[X], bbox.pmin[Y], bbox.pmin[Z]); bmax = Vector3d(bbox.pmax[X], bbox.pmax[Y], bbox.pmax[Z]); #if BSP_WRITETREE gFile = fopen(string(tempstr + ".tree").c_str(), "w"); #endif if(gFile != NULL) { fprintf(gFile, "> %f %f %f %f %f %f\n", bbox.pmin[X], bbox.pmin[Y], bbox.pmin[Z], bbox.pmax[X], bbox.pmax[Y], bbox.pmax[Z]); fprintf(gFile, "T %d\n", objects.size()); } // recursively build BSP tree nodes.push_back(Node()); #if BSP_READNODES FILE *infile = fopen(string(tempstr + ".nodes").c_str(), "r"); if(infile == NULL) throw POV_EXCEPTION(kCannotOpenFileErr, "Cannot open BSP nodes file (BSP_READNODES == true, tree generation disabled)"); try { ValidateBounds(infile, objects); } catch(pov_base::Exception& e) { if (gFile != NULL) fclose (gFile); if ((e.codevalid() != false) && (e.code() == kFileDataErr)) { int line = 0; char str[1024]; long pos = ftell(infile); fseek(infile, 0, SEEK_SET); while (fgets(str, sizeof(str) - 1, infile) != NULL) { line++; if (ftell(infile) >= pos) { fclose (infile); sprintf (str, "%s.nodes line %d: %s", tempstr.c_str(), line, e.what()); throw POV_EXCEPTION(e.code(), str); } } } fclose (infile); throw; } ReadRecursive(progress, infile, 0, 0, objects.size() - 1); fclose(infile); #else BuildRecursive(progress, objects, 0, 0, (unsigned int) indices.size(), bbox, maxDepth); #endif if(gFile != NULL) { fflush(gFile); fclose(gFile); } // memory was only needed for building splits[X].clear(); splits[Y].clear(); splits[Z].clear(); progress((unsigned int) nodes.size()); unsigned int nodesoftypeobject = emptyNodeCounter + objectNodeCounter; // number of terminal nodes totalnodes = (unsigned int) nodes.size(); splitnodes = totalnodes - nodesoftypeobject; objectnodes = objectNodeCounter; emptynodes = emptyNodeCounter; maxobjects = maxObjectsInNode; averageobjects = float(double(objectsInTreeCounter) / double(nodesoftypeobject)); maxdepth = maxTreeDepth; averagedepth = float(double(treeDepthCounter) / double(nodesoftypeobject)); aborts = maxTreeDepthNodes; if(aborts > 0) { averageaborts = float(double(aborts) / double(nodesoftypeobject)); averageabortobjects = float(double(objectsAtMaxDepthCounter) / double(aborts)); } else { averageaborts = 0.0f; averageabortobjects = 0.0f; } // free up unused allocation in lists and nodes vector tmplists; tmplists.swap(lists); lists = tmplists; vector tmpnodes; tmpnodes.swap(nodes); nodes = tmpnodes; indices.clear(); } void BSPTree::clear() { nodes.clear(); lists.clear(); } void BSPTree::BuildRecursive(const Progress& progress, const Objects& objects, unsigned int inode, unsigned int indexbegin, unsigned int indexend, BBOX& cell, unsigned int maxlevel) { maxTreeDepth = max(maxTreeDepth, maxDepth - maxlevel); if((nodes.size() - lastProgressNodeCounter) > NODE_PROGRESS_INTERVAL) { lastProgressNodeCounter = (unsigned int) nodes.size(); progress(lastProgressNodeCounter); } if(gFile != NULL) fprintf(gFile, "%*s", (maxDepth - maxlevel) * 2, ""); unsigned int cnt = indexend - indexbegin; // number of objects // stop if there are no more objects if(cnt == 0) { if(gFile != NULL) fprintf(gFile, "*\n"); nodes[inode].type = Node::Object; nodes[inode].data = Node::Empty; nodes[inode].index = 0; emptyNodeCounter++; treeDepthCounter += (maxDepth - maxlevel); return; } // stop if maximum split recursion level reached or we only have one object if((maxlevel == 0) || (cnt == 1)) { SetObjectNode(inode, indexbegin, indexend); if(maxlevel == 0) { maxTreeDepthNodes++; objectsAtMaxDepthCounter += cnt; } treeDepthCounter += (maxDepth - maxlevel); return; } unsigned int bestscnt = 0; unsigned int bestaxis = Node::NoAxis; unsigned int bestsplit = 0; // baseAccessCost is TK1 in Eric's article // objectIsectCost is TP in Eric's article // childAccessCost is TK3 in Eric's article // set bestcost to estimated time for processing unsplit node float bestcost = baseAccessCost + (cnt * objectIsectCost); // find best split axis and plane { float cellsize[5]; cellsize[X] = cellsize[X + 3] = cell.pmax[X] - cell.pmin[X]; cellsize[Y] = cellsize[Y + 3] = cell.pmax[Y] - cell.pmin[Y]; cellsize[Z] = cell.pmax[Z] - cell.pmin[Z]; // enh is node hit expectance float enh = cellsize[X] * cellsize[Y] + cellsize[X] * cellsize[Z] + cellsize[Y] * cellsize[Z]; float enhinv = 1.0f / enh; // try every axis for(unsigned int axis = 0; axis < 3; axis++) { unsigned int pa = 0; // objects only in left side unsigned int pb = cnt; // objects only in right side unsigned int pab = 0; // objects in both float bmin = cell.pmin[axis]; float bmax = cell.pmax[axis]; // eph is plane hit expectance float eph = cellsize[axis + 1] * cellsize[axis + 2]; // cph is plane hit relative chance (eph / enh) float cph = eph * enhinv; // relmul is used to calculate 'r' given the offset into the node float relmul = 1.0f / cellsize[axis]; // chmul is used to calculate cah and cbh once we know r float chmul = cellsize[axis] * (cellsize[axis + 1] + cellsize[axis + 2]) * enhinv; // constcost is TK1 + TK2 + (1 + CPH) * TK3 in Eric's article float constcost = baseAccessCost + ((1.0f + cph) * childAccessCost); // since cph/2 is used in the main cost calculation we do the division here to avoid // doing it multiple times in the below loop. cph *= 0.5; unsigned int scnt = 0; for(unsigned int i = indexbegin; i < indexend; i++) { float smin = objects.GetMin(axis, indices[i]) - BSP_TOLERANCE; float smax = objects.GetMax(axis, indices[i]) + BSP_TOLERANCE; // (if they are equal for our purpose we consider it outside) if((smin >= bmax) || (smax <= bmin)) continue ; if(smin < bmin) { // definitely intersects a, may not intersect b // if it does intersect b then as it also intersects a, we need to add // one to the common count (pab) and decrement the right-only count (pb). pab++; pb--; } splits[axis][scnt++] = Split(Split::Min, indices[i], smin); splits[axis][scnt++] = Split(Split::Max, indices[i], smax); } sort(splits[axis].begin(), splits[axis].begin() + scnt); for(unsigned int i = 0; i < scnt; i++) { float plane = splits[axis][i].plane; if(splits[axis][i].se == Split::Max) // leaving object { pa++; pab--; } if((plane > bmin) && (plane < bmax)) { float r = (plane - cell.pmin[axis]) * relmul; // range 0.0 (close boundary) to 1.0 (far boundary) float cah = r * chmul; // chance of 'a' hit float cbh = (1.0f - r) * chmul; // chance of 'b' hit // cost function as presented in Ray Tracing News Vol. 17 No. 1 by Eric Haines [trf] // NB cph has been pre-divided by 2 and missChance has had 1.0 added to it. float cost = constcost + (objectIsectCost * (pab + (cph * ((missChance * pa) + (missChance * pb))) + (cah * pa) + (cbh * pb))); if(cost < bestcost) { bestcost = cost; bestsplit = i; bestaxis = axis; bestscnt = scnt; } } if(splits[axis][i].se == Split::Min) // entering object { pab++; pb--; } } } } if(bestaxis == Node::NoAxis) // no better split found, so stop at this node { SetObjectNode(inode, indexbegin, indexend); treeDepthCounter += (maxDepth - maxlevel); } else // better split found, so create child nodes { unsigned int ichild = (unsigned int) nodes.size(); // child node position float bestplane = splits[bestaxis][bestsplit].plane; float ptemp = 0.0f; // create child nodes nodes.push_back(Node()); nodes.push_back(Node()); // set current node nodes[inode].type = Node::Split; nodes[inode].data = bestaxis; nodes[inode].index = ichild; nodes[inode].plane = bestplane; // if the best split is at the maximum side, the split goes // into the left child, otherwise it goes into the right side // and thus the mid-point for sorting has to be moved [trf] if(splits[bestaxis][bestsplit].se == Split::Max) bestsplit++; // reorder indices to find objects completely in one child Split::CompareIndex ci; sort(splits[bestaxis].begin(), splits[bestaxis].begin() + bestsplit, ci); sort(splits[bestaxis].begin() + bestsplit, splits[bestaxis].begin() + bestscnt, ci); if(gFile != NULL) { fprintf(gFile, "| %c = %g ", (int)('x' + bestaxis), bestplane); fprintf(gFile, "[%g,%g,%g -> %g,%g,%g]\n", cell.pmin[0], cell.pmin[1], cell.pmin[2], cell.pmax[0], cell.pmax[1], cell.pmax[2]); } unsigned int begin = (unsigned int) indices.size(); for (vector::iterator it = splits[bestaxis].begin(), en = it + bestsplit; it != en; ) { unsigned int index = it++->index; indices.push_back(index); if((it != en) && (it->index == index)) // keep only once if completely in child it++; } unsigned int middle = (unsigned int) indices.size(); for (vector::iterator it = splits[bestaxis].begin() + bestsplit, en = splits[bestaxis].begin() + bestscnt; it != en; ) { unsigned int index = it++->index; indices.push_back(index); if((it != en) && (it->index == index)) // keep only once if completely in child it++; } unsigned int end = (unsigned int) indices.size(); // split left cell ptemp = cell.pmax[bestaxis]; cell.pmax[bestaxis] = bestplane; BuildRecursive(progress, objects, ichild, begin, middle, cell, maxlevel - 1); cell.pmax[bestaxis] = ptemp; // split right cell ptemp = cell.pmin[bestaxis]; cell.pmin[bestaxis] = bestplane; BuildRecursive(progress, objects, ichild + 1, middle, end, cell, maxlevel - 1); cell.pmin[bestaxis] = ptemp; // the efficiency of this code depends on the assumption that resize() does not // de-allocate memory when truncating a vector. indices.resize(begin); } } void BSPTree::SetObjectNode(unsigned int inode, unsigned int indexbegin, unsigned int indexend) { unsigned int count = indexend - indexbegin; if(gFile != NULL) { fprintf(gFile, "# (%d) ", count); for(unsigned int i = indexbegin; i < indexend; i++) fprintf(gFile, " %d", indices[i]); fprintf(gFile, "\n"); } objectNodeCounter++; // single object if(count == 1) { nodes[inode].type = Node::Object; nodes[inode].data = Node::SingleObject; nodes[inode].index = indices[indexbegin]; maxObjectsInNode = max(maxObjectsInNode, (unsigned int)1); objectsInTreeCounter += 1; } // double object else if(count == 2) { nodes[inode].type = Node::Object; nodes[inode].data = Node::DoubleObject; nodes[inode].index = indices[indexbegin]; nodes[inode].index2 = indices[indexbegin + 1]; maxObjectsInNode = max(maxObjectsInNode, (unsigned int)2); objectsInTreeCounter += 2; } // object list else { unsigned int s((unsigned int) lists.size()); nodes[inode].type = Node::Object; nodes[inode].data = Node::ObjectList; nodes[inode].index = count; // length of list nodes[inode].index2 = s; // list offset // Note: It is actually *much* faster with the Microsoft STL for large trees to not call lists.reserve() here! [cjc] Need to check this for other STL implementations... [trf] // lists.reserve(s + c); // Note: This could first search for an already existing sequence of the same objects // and then adjust the value of index2 accordingly, which would reduce memory consumption [trf] lists.insert(lists.end(), indices.begin() + indexbegin, indices.begin() + indexend); maxObjectsInNode = max(maxObjectsInNode, count); objectsInTreeCounter += count; } } char *BSPTree::GetLine(char *str, int len, FILE *infile) { char *s; while((s = fgets(str, len, infile)) != NULL) { while(isspace(*s)) s++; if(*s == '\0') continue; if(*s != '/') break; if(*++s != '/') throw POV_EXCEPTION(kFileDataErr, "Invalid character in node file"); } if(s == NULL) throw POV_EXCEPTION(kFileDataErr, "Unexpected EOF in node file"); return s; } void BSPTree::ValidateBounds(FILE *infile, const Objects& objects) { int count; char str[1024]; if(sscanf(GetLine(str, sizeof(str), infile), "%d\n", &count) != 1) throw POV_EXCEPTION(kFileDataErr, "Expected count of objects at start of node file"); if(count != objects.size()) throw POV_EXCEPTION(kFileDataErr, "Object count in node file does not match parsed file"); for(unsigned int i = 0; i < objects.size(); i++) { // since runtime libraries use double internally when dealing with float // types, we scan in and compare as double rather than float to avoid // additional float->double->float conversions, which make comparison // less precise. double llx1, lly1, llz1, urx1, ury1, urz1; if (sscanf(GetLine(str, sizeof(str), infile), "%lf %lf %lf %lf %lf %lf\n", &llx1, &lly1, &llz1, &urx1, &ury1, &urz1) != 6) throw POV_EXCEPTION(kFileDataErr, "Failed to parse bounds line in node file"); double llx2, lly2, llz2, urx2, ury2, urz2; llx2 = objects.GetMin(X, i); lly2 = objects.GetMin(Y, i); llz2 = objects.GetMin(Z, i); urx2 = objects.GetMax(X, i); ury2 = objects.GetMax(Y, i); urz2 = objects.GetMax(Z, i); if ((fabs (llx1 - llx2) > 0.00001) || (fabs (lly1 - lly2) > 0.00001) || (fabs (llz1 - llz2) > 0.00001) || (fabs (urx1 - urx2) > 0.00001) || (fabs (ury1 - ury2) > 0.00001) || (fabs (urz1 - urz2) > 0.00001)) throw POV_EXCEPTION(kFileDataErr, "Node file bounds do not match that of parsed file"); } GetLine(str, sizeof(str), infile); if (*GetLine(str, sizeof(str), infile) != '-') throw POV_EXCEPTION(kFileDataErr, "Invalid separator line in node file"); } void BSPTree::ReadRecursive(const Progress& progress, FILE *infile, unsigned int inode, unsigned int level, unsigned int maxIndex) { if (level == MAX_BSP_TREE_LEVEL) throw POV_EXCEPTION(kFileDataErr, "Depth in node file exceeded MAX_BSP_TREE_LEVEL"); if((nodes.size() - lastProgressNodeCounter) > NODE_PROGRESS_INTERVAL) { lastProgressNodeCounter = (unsigned int) nodes.size(); progress(lastProgressNodeCounter); } maxTreeDepth = max(maxTreeDepth, level); int c = fgetc(infile); // read node type if(c == '|') // split node { unsigned int ichild = (unsigned int) nodes.size(); // child node position unsigned int bestaxis = 0; float bestplane = 0.0f; if(fscanf(infile, " %u %f\n", &bestaxis, &bestplane) != 2) // read axis and plane throw POV_EXCEPTION(kFileDataErr, "Expected axis and plane whilst reading node file"); if(gFile != NULL) fprintf(gFile, "%*s| %c = %g\n", level * 2, "", 'x' + bestaxis, bestplane); // create child nodes nodes.push_back(Node()); nodes.push_back(Node()); // set current node nodes[inode].type = Node::Split; nodes[inode].data = bestaxis; nodes[inode].index = ichild; nodes[inode].plane = bestplane; // left cell ReadRecursive(progress, infile, ichild, level + 1, maxIndex); // right cell ReadRecursive(progress, infile, ichild + 1, level + 1, maxIndex); } else if(c == '#') // object node { unsigned int cnt = 0; if(fscanf(infile, " %u", &cnt) != 1) // read number of objects throw POV_EXCEPTION(kFileDataErr, "Expected number of objects whilst reading node file"); vector ind(cnt); treeDepthCounter += level; if (cnt == 0) { if(gFile != NULL) fprintf(gFile, "%*s*\n", level * 2, ""); nodes[inode].type = Node::Object; nodes[inode].data = Node::Empty; nodes[inode].index = 0; emptyNodeCounter++; fscanf(infile, "\n"); return; } if(level == MAX_BSP_TREE_LEVEL - 1) { maxTreeDepthNodes++; objectsAtMaxDepthCounter += cnt; } for(unsigned int i = 0; i < cnt; i++) { if(fscanf(infile, " %u", &ind[i]) != 1) // read object index throw POV_EXCEPTION(kFileDataErr, "Expected object index whilst reading node file"); if(ind[i] > maxIndex) throw POV_EXCEPTION(kFileDataErr, "Invalid object index in node file"); } fscanf(infile, "\n"); if(gFile != NULL) fprintf(gFile, "%*s", level * 2, ""); SetObjectNode(inode, 0, (unsigned int) ind.size()); } else { throw POV_EXCEPTION(kFileDataErr, "Unexpected character in node file"); } } }