几何库中算法和数据的分离(需要三重调度?)

Separation of algorithms and data in a geometry library (triple-dispatching needed?)

本文关键字:何库中 三重 调度 数据 分离 算法      更新时间:2023-10-16

我在设计应用程序中处理几何图形的部分时遇到问题。特别是,我希望有一个类的层次结构和用于交集的单独方法。

问题

层次结构是这样的:

  • 几何
    • 网格
    • 参数化
      • 方框
      • 球体

交集方法类似于:

namespace intersections
{
bool intersection( const Box &, const Box &);
bool intersection( const Box &, const Sphere &);
}

这很简单。现在,当我想将所有几何图形存储在一个结构中时,问题就出现了,例如std::vector(或KD树,或其他什么)。

要做到这一点,我需要使用std::vector<Geometry*>。然而,从这个向量中读取会得到Geometry*对象,因此我无法调用适当的交集函数。

问题示例:

std::vector<Geometry*> arrGeometry;
// add the elements
arrGeometry.push( new Box() );
arrGeometry.push( new Sphere() );
// ... some more code
// try to intersect them?
Geometry* g1 = arrGeometry[0];
Geometry* g2 = arrGeometry[1];
bool intersecting = intersections::intersection( g1, g2 ); //< As expected, this does
// not work

如果我在几何对象内部实现算法,那么问题可以通过访问者和一些非常奇怪的函数调用弹跳来解决。

但是,我希望将交集算法保留在Geometry类之外。原因是:

  • 以避免决定哪个应该拥有所有权(例如,在BoxSphere中,在哪里实现长方体和球体之间的交集?)

  • 为了避免混淆几何对象,所有可以做的都是几何,这是相当多的(仅举几个例子:体素化,计算交集,应用构造几何运算符…)。因此,在这里,将逻辑与数据分离是非常可取的。

另一方面,我需要一个层次结构而不是模板,因为对于某些事情,特定的几何体可以抽象掉。。。(例如,用于将其存储在std::vector或KD树中,或…)。

你将如何解决这个问题?有适合这个的设计模式吗?我试着看了一些图书馆,但最终我更加困惑,因为我已经。。。

最简单的方法(有时也会使用)是使用RTTI(或伪造它)和下广播,但这并不完全可维护。。。(添加一个新的几何体意味着通过所有代码更改许多switch语句)。

有什么想法吗?

事先非常感谢。

我想到了另一个解决方案,如果你关心速度,这有O(1)的复杂性,但你可能需要为模式几何类型自动生成c++代码的程序。您可以制作交叉函数数组(伪代码,我手头没有任何编译器):

enum GeometryType
{
TypeMesh,
TypeParamBox,
TypeParamSphere,
MaxType,
};
bool intersection( const Mesh*, const Mesh* );
bool intersection( const Mesh*, const Box* );
bool intersection( const Mesh*, const Sphere* );
bool intersection( const Box*, const Mesh* );
bool intersection( const Box*, const Box* );
bool intersection( const Box*, const Sphere* );
bool intersection( const Sphere*, const Mesh* );
bool intersection( const Sphere*, const Box* );
bool intersection( const Sphere*, const Sphere* );
template<class T1,class T2>
bool t_intersection( const Geometry* first, const Geometry* second )
{
return intersection( static_cast<const T1*>( first ), static_cast<const T1*>( second ) );
}
typedef bool (*uni_intersect)( const Geometry*, const Geometry* );
const uni_intersect IntersectionArray[] = // 2D array all possible combinations
{
t_intersection<Mesh,Mesh>,
t_intersection<Mesh,Box>,
t_intersection<Mesh,Sphere>,
t_intersection<Box,Mesh>,
t_intersection<Box,Box>,
t_intersection<Box,Sphere>,
t_intersection<Sphere,Mesh>,
t_intersection<Sphere,Box>,
t_intersection<Sphere,Sphere>,
};
bool main_intersection( const Geometry* first, const Geometry* second )
{
const unsigned index = (unsigned)(first->Type) * (unsigned)MaxType + (unsigned)(second->Type);
return IntersectionArray[ index ]( first, second );
}

或替代方案:

const uni_intersect IntersectionArray[] = // 2D array all possible combinations
{
t_intersection<Mesh,Mesh>,
t_intersection<Mesh,Box>,
t_intersection<Mesh,Sphere>,
nullptr, // skip mirrored functions
t_intersection<Box,Box>,
t_intersection<Box,Sphere>,
nullptr,
nullptr,
t_intersection<Sphere,Sphere>,
};
bool main_intersection( const Geometry* first, const Geometry* second )
{
const unsigned Type1 = unsigned(first->Type);
const unsigned Type2 = unsigned(second->Type);
unsigned index;
if( Type1 < Type2 )
index = Type1 * (unsigned)MaxType + Type2;
else
index = Type1 + Type2 * (unsigned)MaxType;
return IntersectionArray[ index ]( first, second );
}

这个问题与此类似。

class Geometry
{
public:
enum eType
{
TypeMesh,
TypeParamBox,
TypeParamSphere,
};
Geometry( eType t ): Type( t ) { }
~Geometry() { }
const eType Type;
};

class Mesh : public Geometry
{
public:
Mesh(): Geometry( TypeMesh ) { }
};

class Parametric : public Geometry
{
public:
Parametric( eType t ): Geometry( TypeMesh ) { }
};

class Box : public Parametric
{
public:
Box(): Parametric( TypeParamBox ) { }
};

class Sphere : public Parametric
{
public:
Sphere(): Parametric( TypeParamSphere ) { }
};

namespace intersections
{
bool intersection( const Geometry* first, const Geometry* second );
template <typename T>
bool t_intersection( const T* first, const Geometry* second );
bool intersection( const Box*, const Box* );
bool intersection( const Box*, const Sphere* );
bool intersection( const Sphere*, const Box* );
bool intersection( const Sphere*, const Sphere* );
}

void foo_test()
{
std::vector<Geometry*> arrGeometry;
// add the elements
arrGeometry.push_back( new Box() );
arrGeometry.push_back( new Sphere() );
// ... some more code
// try to intersect them?
Geometry* g1 = arrGeometry[0];
Geometry* g2 = arrGeometry[1];
bool intersecting = intersections::intersection( g1, g2 );
}

bool intersections::intersection( const Geometry* first, const Geometry* second )
{
switch( first->Type )
{
case Geometry::TypeParamBox: return t_intersection( static_cast<const Box*>( first ), second );
case Geometry::TypeParamSphere: return t_intersection( static_cast<const Sphere*>( first ), second );
default: return false;
}
}

template <typename T>
bool intersections::t_intersection( const T* first, const Geometry* second )
{
switch( second->Type )
{
case Geometry::TypeParamBox: return intersection( first, static_cast<const Box*>( second ) );
case Geometry::TypeParamSphere: return intersection( first, static_cast<const Sphere*>( second ) );
default: return false;
}
}

或者,如果您不使用继承:

struct Mesh{};
struct Box{};
struct Sphere{};

enum GeometryType
{
TypeMesh,
TypeParamBox,
TypeParamSphere
};

struct Geometry
{
GeometryType Type;
union
{
Mesh* pMesh;
Box* pBox;
Sphere* pSphere;
} Ptr;
};

namespace intersections
{
bool intersection( const Geometry* first, const Geometry* second );
template <typename T>
bool t_intersection( const T* first, const Geometry* second );
bool intersection( const Box*, const Box* );
bool intersection( const Box*, const Sphere* );
bool intersection( const Sphere*, const Box* );
bool intersection( const Sphere*, const Sphere* );
}

void foo_test()
{
std::vector<Geometry*> arrGeometry;
// add the elements
//  arrGeometry.push_back( new Box() );
//  arrGeometry.push_back( new Sphere() );
// ... some more code
// try to intersect them?
Geometry* g1 = arrGeometry[0];
Geometry* g2 = arrGeometry[1];
bool intersecting = intersections::intersection( g1, g2 );
}

bool intersections::intersection( const Geometry* first, const Geometry* second )
{
switch( first->Type )
{
case TypeParamBox: return t_intersection( first->Ptr.pBox, second );
case TypeParamSphere: return t_intersection( first->Ptr.pSphere, second );
default: return false;
}
}

template <typename T>
bool intersections::t_intersection( const T* first, const Geometry* second )
{
switch( second->Type )
{
case TypeParamBox: return intersection( first, second->Ptr.pBox );
case TypeParamSphere: return intersection( first, second->Ptr.pSphere );
default: return false;
}
}


注:若你们知道一个几何体的类型,你们可以使用模板函数:

Box* pBox;
Geometry* pUnknownGeometry;
bool intersecting = intersections::t_intersection( pBox, pUnknownGeometry );


注2:您可以编写类似这样的相同函数:

bool intersection( const Box* first, const Sphere* second );
bool intersection( const Sphere* first, const Box* second )
{
return intersection( second, first );
}


编辑:

若您将问题减少为双重调度,则可以使用此技术。

对于交叉点,将层次结构视为:

  • 几何
    • 网格
    • 方框
    • 球体

以及其他操作:

  • 几何
    • 网格
    • 参数化
      • 方框
      • 球体

不如将交集算法表示为类,这些类可以决定它们是否可以处理它们获得的几何图形集,并提供一个具有原始接口的交集代理。这看起来像这样:

class IIntersection
{
public:
bool intersection( const Geometry &, const Geometry &);
bool accept( const Geometry &, const Geometry &);
}
class IntersectionBoxSphere : public IIntersection
{
public:
bool intersection( const Geometry &, const Geometry &);
bool accept( const Geometry & a, const Geometry &b)
{
return ((dynamic_cast<Box>(a) != NULL || dynamic_cast<Box>(b) != NULL)
(dynamic_cast<Sphere>(a) != NULL || dynamic_cast<Sphere>(b) != NULL))
}
}
class IntersectionBoxbox  : public IIntersection
...
/**collect some where all algorithms*/
IIntersection* myintersections[2];
myintersections[0] = new IntersectionBoxSphere()
myintersections[1] = new IntersectionBoxbox()
...
/**decide here which to use */
bool intersection( const Geometry &a, const Geometry &b)
{
for ( i ... )
if (myintersections[i]->appect(a,b))
return myintersections[i]->intersection(a,b)
}