mirror of https://github.com/vitalif/openscad
210 lines
7.1 KiB
C++
210 lines
7.1 KiB
C++
#include "csgtermnormalizer.h"
|
|
#include "csgterm.h"
|
|
#include "printutils.h"
|
|
|
|
// Helper function to debug normalization bugs
|
|
#if 0
|
|
static bool validate_tree(const shared_ptr<CSGTerm> &term)
|
|
{
|
|
if (term->type == CSGTerm::TYPE_PRIMITIVE) return true;
|
|
if (!term->left || !term->right) return false;
|
|
if (!validate_tree(term->left)) return false;
|
|
if (!validate_tree(term->right)) return false;
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
/*!
|
|
NB! for e.g. empty intersections, this can normalize a tree to nothing and return NULL.
|
|
*/
|
|
shared_ptr<CSGTerm> CSGTermNormalizer::normalize(const shared_ptr<CSGTerm> &root)
|
|
{
|
|
this->aborted = false;
|
|
shared_ptr<CSGTerm> temp = root;
|
|
while (1) {
|
|
this->rootnode = temp;
|
|
this->nodecount = 0;
|
|
shared_ptr<CSGTerm> n = normalizePass(temp);
|
|
if (!n) return n; // If normalized to nothing
|
|
if (temp == n) break;
|
|
temp = n;
|
|
|
|
if (this->nodecount > this->limit) {
|
|
PRINTB("WARNING: Normalized tree is growing past %d elements. Aborting normalization.\n", this->limit);
|
|
// Clean up any partially evaluated terms
|
|
shared_ptr<CSGTerm> newroot = root, tmproot;
|
|
while (newroot && newroot != tmproot) {
|
|
tmproot = newroot;
|
|
newroot = collapse_null_terms(tmproot);
|
|
}
|
|
newroot = cleanup_term(newroot);
|
|
return newroot;
|
|
}
|
|
}
|
|
this->rootnode.reset();
|
|
return temp;
|
|
}
|
|
|
|
/*!
|
|
After aborting, a subtree might have become invalidated (NULL child term)
|
|
since terms can be instantiated multiple times.
|
|
This will search for NULL children an recursively repair the corresponding
|
|
subtree.
|
|
*/
|
|
shared_ptr<CSGTerm> CSGTermNormalizer::cleanup_term(shared_ptr<CSGTerm> &t)
|
|
{
|
|
if (t->type != CSGTerm::TYPE_PRIMITIVE) {
|
|
if (t->left) t->left = cleanup_term(t->left);
|
|
if (t->right) t->right = cleanup_term(t->right);
|
|
return collapse_null_terms(t);
|
|
}
|
|
else return t;
|
|
}
|
|
|
|
shared_ptr<CSGTerm> CSGTermNormalizer::normalizePass(shared_ptr<CSGTerm> term)
|
|
{
|
|
// This function implements the CSG normalization
|
|
// Reference:
|
|
// Goldfeather, J., Molnar, S., Turk, G., and Fuchs, H. Near
|
|
// Realtime CSG Rendering Using Tree Normalization and Geometric
|
|
// Pruning. IEEE Computer Graphics and Applications, 9(3):20-28,
|
|
// 1989.
|
|
// http://www.cc.gatech.edu/~turk/my_papers/pxpl_csg.pdf
|
|
|
|
if (term->type == CSGTerm::TYPE_PRIMITIVE) {
|
|
return term;
|
|
}
|
|
|
|
do {
|
|
while (term && match_and_replace(term)) { }
|
|
this->nodecount++;
|
|
if (nodecount > this->limit) {
|
|
PRINTB("WARNING: Normalized tree is growing past %d elements. Aborting normalization.\n", this->limit);
|
|
this->aborted = true;
|
|
return shared_ptr<CSGTerm>();
|
|
}
|
|
if (!term || term->type == CSGTerm::TYPE_PRIMITIVE) return term;
|
|
if (term->left) term->left = normalizePass(term->left);
|
|
} while (!this->aborted && term->type != CSGTerm::TYPE_UNION &&
|
|
((term->right && term->right->type != CSGTerm::TYPE_PRIMITIVE) ||
|
|
(term->left && term->left->type == CSGTerm::TYPE_UNION)));
|
|
if (!this->aborted) term->right = normalizePass(term->right);
|
|
|
|
// FIXME: Do we need to take into account any transformation of item here?
|
|
shared_ptr<CSGTerm> t = collapse_null_terms(term);
|
|
|
|
if (this->aborted) {
|
|
if (t) t = cleanup_term(t);
|
|
}
|
|
|
|
return t;
|
|
}
|
|
|
|
shared_ptr<CSGTerm> CSGTermNormalizer::collapse_null_terms(const shared_ptr<CSGTerm> &term)
|
|
{
|
|
if (!term->right) {
|
|
if (term->type == CSGTerm::TYPE_UNION || term->type == CSGTerm::TYPE_DIFFERENCE) return term->left;
|
|
else return term->right;
|
|
}
|
|
if (!term->left) {
|
|
if (term->type == CSGTerm::TYPE_UNION) return term->right;
|
|
else return term->left;
|
|
}
|
|
return term;
|
|
}
|
|
|
|
bool CSGTermNormalizer::match_and_replace(shared_ptr<CSGTerm> &term)
|
|
{
|
|
if (term->type == CSGTerm::TYPE_UNION || term->type == CSGTerm::TYPE_PRIMITIVE) {
|
|
return false;
|
|
}
|
|
|
|
// Part A: The 'x . (y . z)' expressions
|
|
|
|
shared_ptr<CSGTerm> x = term->left;
|
|
shared_ptr<CSGTerm> y = term->right->left;
|
|
shared_ptr<CSGTerm> z = term->right->right;
|
|
|
|
shared_ptr<CSGTerm> result = term;
|
|
|
|
// 1. x - (y + z) -> (x - y) - z
|
|
if (term->type == CSGTerm::TYPE_DIFFERENCE && term->right->type == CSGTerm::TYPE_UNION) {
|
|
term = CSGTerm::createCSGTerm(CSGTerm::TYPE_DIFFERENCE,
|
|
CSGTerm::createCSGTerm(CSGTerm::TYPE_DIFFERENCE, x, y),
|
|
z);
|
|
return true;
|
|
}
|
|
// 2. x * (y + z) -> (x * y) + (x * z)
|
|
else if (term->type == CSGTerm::TYPE_INTERSECTION && term->right->type == CSGTerm::TYPE_UNION) {
|
|
term = CSGTerm::createCSGTerm(CSGTerm::TYPE_UNION,
|
|
CSGTerm::createCSGTerm(CSGTerm::TYPE_INTERSECTION, x, y),
|
|
CSGTerm::createCSGTerm(CSGTerm::TYPE_INTERSECTION, x, z));
|
|
return true;
|
|
}
|
|
// 3. x - (y * z) -> (x - y) + (x - z)
|
|
else if (term->type == CSGTerm::TYPE_DIFFERENCE && term->right->type == CSGTerm::TYPE_INTERSECTION) {
|
|
term = CSGTerm::createCSGTerm(CSGTerm::TYPE_UNION,
|
|
CSGTerm::createCSGTerm(CSGTerm::TYPE_DIFFERENCE, x, y),
|
|
CSGTerm::createCSGTerm(CSGTerm::TYPE_DIFFERENCE, x, z));
|
|
return true;
|
|
}
|
|
// 4. x * (y * z) -> (x * y) * z
|
|
else if (term->type == CSGTerm::TYPE_INTERSECTION && term->right->type == CSGTerm::TYPE_INTERSECTION) {
|
|
term = CSGTerm::createCSGTerm(CSGTerm::TYPE_INTERSECTION,
|
|
CSGTerm::createCSGTerm(CSGTerm::TYPE_INTERSECTION, x, y),
|
|
z);
|
|
return true;
|
|
}
|
|
// 5. x - (y - z) -> (x - y) + (x * z)
|
|
else if (term->type == CSGTerm::TYPE_DIFFERENCE && term->right->type == CSGTerm::TYPE_DIFFERENCE) {
|
|
term = CSGTerm::createCSGTerm(CSGTerm::TYPE_UNION,
|
|
CSGTerm::createCSGTerm(CSGTerm::TYPE_DIFFERENCE, x, y),
|
|
CSGTerm::createCSGTerm(CSGTerm::TYPE_INTERSECTION, x, z));
|
|
return true;
|
|
}
|
|
// 6. x * (y - z) -> (x * y) - z
|
|
else if (term->type == CSGTerm::TYPE_INTERSECTION && term->right->type == CSGTerm::TYPE_DIFFERENCE) {
|
|
term = CSGTerm::createCSGTerm(CSGTerm::TYPE_DIFFERENCE,
|
|
CSGTerm::createCSGTerm(CSGTerm::TYPE_INTERSECTION, x, y),
|
|
z);
|
|
return true;
|
|
}
|
|
|
|
// Part B: The '(x . y) . z' expressions
|
|
|
|
x = term->left->left;
|
|
y = term->left->right;
|
|
z = term->right;
|
|
|
|
// 7. (x - y) * z -> (x * z) - y
|
|
if (term->left->type == CSGTerm::TYPE_DIFFERENCE && term->type == CSGTerm::TYPE_INTERSECTION) {
|
|
term = CSGTerm::createCSGTerm(CSGTerm::TYPE_DIFFERENCE,
|
|
CSGTerm::createCSGTerm(CSGTerm::TYPE_INTERSECTION, x, z),
|
|
y);
|
|
return true;
|
|
}
|
|
// 8. (x + y) - z -> (x - z) + (y - z)
|
|
else if (term->left->type == CSGTerm::TYPE_UNION && term->type == CSGTerm::TYPE_DIFFERENCE) {
|
|
term = CSGTerm::createCSGTerm(CSGTerm::TYPE_UNION,
|
|
CSGTerm::createCSGTerm(CSGTerm::TYPE_DIFFERENCE, x, z),
|
|
CSGTerm::createCSGTerm(CSGTerm::TYPE_DIFFERENCE, y, z));
|
|
return true;
|
|
}
|
|
// 9. (x + y) * z -> (x * z) + (y * z)
|
|
else if (term->left->type == CSGTerm::TYPE_UNION && term->type == CSGTerm::TYPE_INTERSECTION) {
|
|
term = CSGTerm::createCSGTerm(CSGTerm::TYPE_UNION,
|
|
CSGTerm::createCSGTerm(CSGTerm::TYPE_INTERSECTION, x, z),
|
|
CSGTerm::createCSGTerm(CSGTerm::TYPE_INTERSECTION, y, z));
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Counts all non-leaf nodes
|
|
unsigned int CSGTermNormalizer::count(const shared_ptr<CSGTerm> &term) const
|
|
{
|
|
if (!term) return 0;
|
|
return term->type == CSGTerm::TYPE_PRIMITIVE ? 0 : 1 + count(term->left) + count(term->right);
|
|
}
|