439 lines
13 KiB
C++
439 lines
13 KiB
C++
/*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#ifndef __FREESTYLE_SPHERICAL_GRID_H__
|
|
#define __FREESTYLE_SPHERICAL_GRID_H__
|
|
|
|
/** \file
|
|
* \ingroup freestyle
|
|
* \brief Class to define a cell grid surrounding the projected image of a scene
|
|
*/
|
|
|
|
#define SPHERICAL_GRID_LOGGING 0
|
|
|
|
// I would like to avoid using deque because including ViewMap.h and <deque> or <vector> separately
|
|
// results in redefinitions of identifiers. ViewMap.h already includes <vector> so it should be a
|
|
// safe fall-back.
|
|
//#include <vector>
|
|
//#include <deque>
|
|
|
|
#include "GridDensityProvider.h"
|
|
#include "OccluderSource.h"
|
|
#include "ViewMap.h"
|
|
|
|
#include "../geometry/Polygon.h"
|
|
#include "../geometry/BBox.h"
|
|
#include "../geometry/GridHelpers.h"
|
|
|
|
#include "../system/PointerSequence.h"
|
|
|
|
#include "../winged_edge/WEdge.h"
|
|
|
|
#include "BKE_global.h"
|
|
|
|
#ifdef WITH_CXX_GUARDEDALLOC
|
|
# include "MEM_guardedalloc.h"
|
|
#endif
|
|
|
|
namespace Freestyle {
|
|
|
|
class SphericalGrid {
|
|
public:
|
|
// Helper classes
|
|
struct OccluderData {
|
|
explicit OccluderData(OccluderSource &source, Polygon3r &p);
|
|
Polygon3r poly;
|
|
Polygon3r cameraSpacePolygon;
|
|
real shallowest, deepest;
|
|
// N.B. We could, of course, store face in poly's userdata member, like the old ViewMapBuilder
|
|
// code does. However, code comments make it clear that userdata is deprecated, so we avoid the
|
|
// temptation to save 4 or 8 bytes.
|
|
WFace *face;
|
|
|
|
#ifdef WITH_CXX_GUARDEDALLOC
|
|
MEM_CXX_CLASS_ALLOC_FUNCS("Freestyle:SphericalGrid:OccluderData")
|
|
#endif
|
|
};
|
|
|
|
private:
|
|
struct Cell {
|
|
// Can't store Cell in a vector without copy and assign
|
|
// Cell(const Cell& other);
|
|
// Cell& operator=(const Cell& other);
|
|
|
|
explicit Cell();
|
|
~Cell();
|
|
|
|
static bool compareOccludersByShallowestPoint(const OccluderData *a, const OccluderData *b);
|
|
|
|
void setDimensions(real x, real y, real sizeX, real sizeY);
|
|
void checkAndInsert(OccluderSource &source, Polygon3r &poly, OccluderData *&occluder);
|
|
void indexPolygons();
|
|
|
|
real boundary[4];
|
|
// deque<OccluderData*> faces;
|
|
vector<OccluderData *> faces;
|
|
};
|
|
|
|
public:
|
|
/*! Iterator needs to allow the user to avoid full 3D comparison in two cases:
|
|
*
|
|
* (1) Where (*current)->deepest < target[2], where the occluder is unambiguously in front of
|
|
* the target point.
|
|
*
|
|
* (2) Where (*current)->shallowest > target[2], where the occluder is unambiguously in back of
|
|
* the target point.
|
|
*
|
|
* In addition, when used by OptimizedFindOccludee, Iterator should stop iterating as soon as it
|
|
* has an occludee candidate and (*current)->shallowest > candidate[2], because at that point
|
|
* forward no new occluder could possibly be a better occludee.
|
|
*/
|
|
|
|
class Iterator {
|
|
public:
|
|
// epsilon is not used in this class, but other grids with the same interface may need an
|
|
// epsilon
|
|
explicit Iterator(SphericalGrid &grid, Vec3r ¢er, real epsilon = 1.0e-06);
|
|
~Iterator();
|
|
void initBeforeTarget();
|
|
void initAfterTarget();
|
|
void nextOccluder();
|
|
void nextOccludee();
|
|
bool validBeforeTarget();
|
|
bool validAfterTarget();
|
|
WFace *getWFace() const;
|
|
Polygon3r *getCameraSpacePolygon();
|
|
void reportDepth(Vec3r origin, Vec3r u, real t);
|
|
|
|
private:
|
|
bool testOccluder(bool wantOccludee);
|
|
void markCurrentOccludeeCandidate(real depth);
|
|
|
|
Cell *_cell;
|
|
Vec3r _target;
|
|
bool _foundOccludee;
|
|
real _occludeeDepth;
|
|
// deque<OccluderData*>::iterator _current, _occludeeCandidate;
|
|
vector<OccluderData *>::iterator _current, _occludeeCandidate;
|
|
|
|
#ifdef WITH_CXX_GUARDEDALLOC
|
|
MEM_CXX_CLASS_ALLOC_FUNCS("Freestyle:SphericalGrid:Iterator")
|
|
#endif
|
|
};
|
|
|
|
class Transform : public GridHelpers::Transform {
|
|
public:
|
|
explicit Transform();
|
|
explicit Transform(Transform &other);
|
|
Vec3r operator()(const Vec3r &point) const;
|
|
static Vec3r sphericalProjection(const Vec3r &M);
|
|
};
|
|
|
|
private:
|
|
// Prevent implicit copies and assignments.
|
|
SphericalGrid(const SphericalGrid &other);
|
|
SphericalGrid &operator=(const SphericalGrid &other);
|
|
|
|
public:
|
|
explicit SphericalGrid(OccluderSource &source,
|
|
GridDensityProvider &density,
|
|
ViewMap *viewMap,
|
|
Vec3r &viewpoint,
|
|
bool enableQI);
|
|
virtual ~SphericalGrid();
|
|
|
|
// Generate Cell structure
|
|
void assignCells(OccluderSource &source, GridDensityProvider &density, ViewMap *viewMap);
|
|
// Fill Cells
|
|
void distributePolygons(OccluderSource &source);
|
|
// Insert one polygon into each matching cell, return true if any cell consumes the polygon
|
|
bool insertOccluder(OccluderSource &source, OccluderData *&occluder);
|
|
// Sort occluders in each cell
|
|
void reorganizeCells();
|
|
|
|
Cell *findCell(const Vec3r &point);
|
|
|
|
// Accessors:
|
|
bool orthographicProjection() const;
|
|
const Vec3r &viewpoint() const;
|
|
bool enableQI() const;
|
|
|
|
private:
|
|
void getCellCoordinates(const Vec3r &point, unsigned &x, unsigned &y);
|
|
|
|
typedef PointerSequence<vector<Cell *>, Cell *> cellContainer;
|
|
// typedef PointerSequence<deque<OccluderData*>, OccluderData*> occluderContainer;
|
|
typedef PointerSequence<vector<OccluderData *>, OccluderData *> occluderContainer;
|
|
unsigned _cellsX, _cellsY;
|
|
float _cellSize;
|
|
float _cellOrigin[2];
|
|
cellContainer _cells;
|
|
occluderContainer _faces;
|
|
Vec3r _viewpoint;
|
|
bool _enableQI;
|
|
|
|
#ifdef WITH_CXX_GUARDEDALLOC
|
|
MEM_CXX_CLASS_ALLOC_FUNCS("Freestyle:SphericalGrid")
|
|
#endif
|
|
};
|
|
|
|
inline void SphericalGrid::Iterator::initBeforeTarget()
|
|
{
|
|
_current = _cell->faces.begin();
|
|
while (_current != _cell->faces.end() && !testOccluder(false)) {
|
|
++_current;
|
|
}
|
|
}
|
|
|
|
inline void SphericalGrid::Iterator::initAfterTarget()
|
|
{
|
|
if (_foundOccludee) {
|
|
#if SPHERICAL_GRID_LOGGING
|
|
if (G.debug & G_DEBUG_FREESTYLE) {
|
|
std::cout << "\tStarting occludee search from occludeeCandidate at depth " << _occludeeDepth
|
|
<< std::endl;
|
|
}
|
|
#endif
|
|
_current = _occludeeCandidate;
|
|
return;
|
|
}
|
|
|
|
#if SPHERICAL_GRID_LOGGING
|
|
if (G.debug & G_DEBUG_FREESTYLE) {
|
|
std::cout << "\tStarting occludee search from current position" << std::endl;
|
|
}
|
|
#endif
|
|
|
|
while (_current != _cell->faces.end() && !testOccluder(true)) {
|
|
++_current;
|
|
}
|
|
}
|
|
|
|
inline bool SphericalGrid::Iterator::testOccluder(bool wantOccludee)
|
|
{
|
|
// End-of-list is not even a valid iterator position
|
|
if (_current == _cell->faces.end()) {
|
|
// Returning true seems strange, but it will break us out of whatever loop is calling
|
|
// testOccluder, and _current=_cell->face.end() will make the calling routine give up.
|
|
return true;
|
|
}
|
|
#if SPHERICAL_GRID_LOGGING
|
|
if (G.debug & G_DEBUG_FREESTYLE) {
|
|
std::cout << "\tTesting occluder " << (*_current)->poly.getVertices()[0];
|
|
for (unsigned int i = 1; i < (*_current)->poly.getVertices().size(); ++i) {
|
|
std::cout << ", " << (*_current)->poly.getVertices()[i];
|
|
}
|
|
std::cout << " from shape " << (*_current)->face->GetVertex(0)->shape()->GetId() << std::endl;
|
|
}
|
|
#endif
|
|
|
|
// If we have an occluder candidate and we are unambiguously after it, abort
|
|
if (_foundOccludee && (*_current)->shallowest > _occludeeDepth) {
|
|
#if SPHERICAL_GRID_LOGGING
|
|
if (G.debug & G_DEBUG_FREESTYLE) {
|
|
std::cout << "\t\tAborting: shallowest > occludeeCandidate->deepest" << std::endl;
|
|
}
|
|
#endif
|
|
_current = _cell->faces.end();
|
|
|
|
// See note above
|
|
return true;
|
|
}
|
|
|
|
// Specific continue or stop conditions when searching for each type
|
|
if (wantOccludee) {
|
|
if ((*_current)->deepest < _target[2]) {
|
|
#if SPHERICAL_GRID_LOGGING
|
|
if (G.debug & G_DEBUG_FREESTYLE) {
|
|
std::cout << "\t\tSkipping: shallower than target while looking for occludee" << std::endl;
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
}
|
|
else {
|
|
if ((*_current)->shallowest > _target[2]) {
|
|
#if SPHERICAL_GRID_LOGGING
|
|
if (G.debug & G_DEBUG_FREESTYLE) {
|
|
std::cout << "\t\tStopping: deeper than target while looking for occluder" << std::endl;
|
|
}
|
|
#endif
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Depthwise, this is a valid occluder.
|
|
|
|
// Check to see if target is in the 2D bounding box
|
|
Vec3r bbMin, bbMax;
|
|
(*_current)->poly.getBBox(bbMin, bbMax);
|
|
if (_target[0] < bbMin[0] || _target[0] > bbMax[0] || _target[1] < bbMin[1] ||
|
|
_target[1] > bbMax[1]) {
|
|
#if SPHERICAL_GRID_LOGGING
|
|
if (G.debug & G_DEBUG_FREESTYLE) {
|
|
std::cout << "\t\tSkipping: bounding box violation" << std::endl;
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
// We've done all the corner cutting we can. Let the caller work out whether or not the geometry
|
|
// is correct.
|
|
return true;
|
|
}
|
|
|
|
inline void SphericalGrid::Iterator::reportDepth(Vec3r origin, Vec3r u, real t)
|
|
{
|
|
// The reported depth is the length of a ray in camera space. We need to convert it into the
|
|
// distance from viewpoint If origin is the viewpoint, depth == t. A future optimization could
|
|
// allow the caller to tell us if origin is viewponit or target, at the cost of changing the
|
|
// OptimizedGrid API.
|
|
real depth = (origin + u * t).norm();
|
|
#if SPHERICAL_GRID_LOGGING
|
|
if (G.debug & G_DEBUG_FREESTYLE) {
|
|
std::cout << "\t\tReporting depth of occluder/ee: " << depth;
|
|
}
|
|
#endif
|
|
if (depth > _target[2]) {
|
|
#if SPHERICAL_GRID_LOGGING
|
|
if (G.debug & G_DEBUG_FREESTYLE) {
|
|
std::cout << " is deeper than target" << std::endl;
|
|
}
|
|
#endif
|
|
// If the current occluder is the best occludee so far, save it.
|
|
if (!_foundOccludee || _occludeeDepth > depth) {
|
|
markCurrentOccludeeCandidate(depth);
|
|
}
|
|
}
|
|
else {
|
|
#if SPHERICAL_GRID_LOGGING
|
|
if (G.debug & G_DEBUG_FREESTYLE) {
|
|
std::cout << std::endl;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
inline void SphericalGrid::Iterator::nextOccluder()
|
|
{
|
|
if (_current != _cell->faces.end()) {
|
|
do {
|
|
++_current;
|
|
} while (_current != _cell->faces.end() && !testOccluder(false));
|
|
}
|
|
}
|
|
|
|
inline void SphericalGrid::Iterator::nextOccludee()
|
|
{
|
|
if (_current != _cell->faces.end()) {
|
|
do {
|
|
++_current;
|
|
} while (_current != _cell->faces.end() && !testOccluder(true));
|
|
}
|
|
}
|
|
|
|
inline bool SphericalGrid::Iterator::validBeforeTarget()
|
|
{
|
|
return _current != _cell->faces.end() && (*_current)->shallowest <= _target[2];
|
|
}
|
|
|
|
inline bool SphericalGrid::Iterator::validAfterTarget()
|
|
{
|
|
return _current != _cell->faces.end();
|
|
}
|
|
|
|
inline void SphericalGrid::Iterator::markCurrentOccludeeCandidate(real depth)
|
|
{
|
|
#if SPHERICAL_GRID_LOGGING
|
|
if (G.debug & G_DEBUG_FREESTYLE) {
|
|
std::cout << "\t\tFound occludeeCandidate at depth " << depth << std::endl;
|
|
}
|
|
#endif
|
|
_occludeeCandidate = _current;
|
|
_occludeeDepth = depth;
|
|
_foundOccludee = true;
|
|
}
|
|
|
|
inline WFace *SphericalGrid::Iterator::getWFace() const
|
|
{
|
|
return (*_current)->face;
|
|
}
|
|
|
|
inline Polygon3r *SphericalGrid::Iterator::getCameraSpacePolygon()
|
|
{
|
|
return &((*_current)->cameraSpacePolygon);
|
|
}
|
|
|
|
inline SphericalGrid::OccluderData::OccluderData(OccluderSource &source, Polygon3r &p)
|
|
: poly(p), cameraSpacePolygon(source.getCameraSpacePolygon()), face(source.getWFace())
|
|
{
|
|
const Vec3r viewpoint(0, 0, 0);
|
|
// Get the point on the camera-space polygon that is closest to the viewpoint
|
|
// shallowest is the distance from the viewpoint to that point
|
|
shallowest = GridHelpers::distancePointToPolygon(viewpoint, cameraSpacePolygon);
|
|
|
|
// Get the point on the camera-space polygon that is furthest from the viewpoint
|
|
// deepest is the distance from the viewpoint to that point
|
|
deepest = cameraSpacePolygon.getVertices()[2].norm();
|
|
for (unsigned int i = 0; i < 2; ++i) {
|
|
real t = cameraSpacePolygon.getVertices()[i].norm();
|
|
if (t > deepest) {
|
|
deepest = t;
|
|
}
|
|
}
|
|
}
|
|
|
|
inline void SphericalGrid::Cell::checkAndInsert(OccluderSource &source,
|
|
Polygon3r &poly,
|
|
OccluderData *&occluder)
|
|
{
|
|
if (GridHelpers::insideProscenium(boundary, poly)) {
|
|
if (occluder == NULL) {
|
|
// Disposal of occluder will be handled in SphericalGrid::distributePolygons(),
|
|
// or automatically by SphericalGrid::_faces;
|
|
occluder = new OccluderData(source, poly);
|
|
}
|
|
faces.push_back(occluder);
|
|
}
|
|
}
|
|
|
|
inline bool SphericalGrid::insertOccluder(OccluderSource &source, OccluderData *&occluder)
|
|
{
|
|
Polygon3r &poly(source.getGridSpacePolygon());
|
|
occluder = NULL;
|
|
|
|
Vec3r bbMin, bbMax;
|
|
poly.getBBox(bbMin, bbMax);
|
|
// Check overlapping cells
|
|
unsigned startX, startY, endX, endY;
|
|
getCellCoordinates(bbMin, startX, startY);
|
|
getCellCoordinates(bbMax, endX, endY);
|
|
|
|
for (unsigned int i = startX; i <= endX; ++i) {
|
|
for (unsigned int j = startY; j <= endY; ++j) {
|
|
if (_cells[i * _cellsY + j] != NULL) {
|
|
_cells[i * _cellsY + j]->checkAndInsert(source, poly, occluder);
|
|
}
|
|
}
|
|
}
|
|
|
|
return occluder != NULL;
|
|
}
|
|
|
|
} /* namespace Freestyle */
|
|
|
|
#endif // __FREESTYLE_SPHERICAL_GRID_H__
|