/*******************************************************************************
* 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");
}
}
}