Problem语句/上下文:
表示射线,给定原点和方向。此外,还定义了一个min_t和max_t值。这些值定义了沿射线的距离,使描述射线上的空隙成为可能。射线类的建议是支持2d射线跟踪(一种2D射线跟踪可视化工具),如以下图片所示:

实现:
我提议的C++17解决方案:
#pragma once
#ifndef Flatland_Ray_ceb9b0b4_4236_4bfe_b918_11a02c72ad7c_h
#define Flatland_Ray_ceb9b0b4_4236_4bfe_b918_11a02c72ad7c_h
#include "flatland/core/namespace.h"
#include "flatland/math/point.h"
#include "flatland/math/vector.h"
#include
#include
FLATLAND_BEGIN_NAMESPACE
template
class Ray {
public:
Ray(const PointType &origin, const VectorType &direction, const ScalarType min_t, const ScalarType max_t)
: origin(origin), direction(direction), min_t(min_t), max_t(max_t) {
}
PointType operator()(const float t) const {
assert(isDirectionVectorNormalized());
return origin + t * direction;
}
bool isDirectionVectorNormalized() const {
const ScalarType epsilon = static_cast(0.001);
return direction.norm() - static_cast(1.0) < epsilon;
}
public:
PointType origin;
VectorType direction;
ScalarType min_t;
ScalarType max_t;
enum { dimension = Dimension};
};
template
using Ray2 = Ray, Vector2, ScalarType, 2>;
template
using Ray3 = Ray, Vector3, ScalarType, 3>;
using Ray2f = Ray2;
using Ray2d = Ray2;
using Ray3f = Ray3;
using Ray3d = Ray3;
namespace internal {
template
std::string convertTypeToString() {
std::string typeAsString = "UnknownType";
if(std::is_same::value)
typeAsString = "i";
if(std::is_same::value)
typeAsString = "f";
if(std::is_same::value)
typeAsString = "d";
return typeAsString;
}
template
std::string dimensionAsString() {
std::string dimensionAsString = "UnknownDimension";
if(Dimension == 2)
dimensionAsString = "2";
if(Dimension == 3)
dimensionAsString = "3";
return dimensionAsString;
}
}
template
std::ostream &operator<<(std::ostream &os, const Ray &r) {
auto typeAsString = internal::convertTypeToString();
auto dimensionAsString = internal::dimensionAsString();
os << "Ray" << dimensionAsString << typeAsString << "[" << std::endl
<< " origin = " << r.origin << "," << std::endl
<< " direction = " << r.direction << "," << std::endl
<< " min_t = " << r.min_t << "," << std::endl
<< " max_t = " << r.max_t << "" << std::endl
<< "]" << std::endl;
return os;
}
FLATLAND_END_NAMESPACE
#endif // end define Flatland_Ray_ceb9b0b4_4236_4bfe_b918_11a02c72ad7c_hMy测试:
#include "flatland/math/ray.h"
#include
using namespace Flatland;
TEST(Ray2f, GivenDirectionOriginMaxMinT_WhenRayIsInitialized_ThenInitzalizedRayValues) {
Point2f origin(0.0f, 0.0f);
Vector2f direction(1.0f, 0.0f);
Ray2f r(origin, direction, 1.0f, 10.0f);
EXPECT_THAT(r.origin.x(), 0.0f);
EXPECT_THAT(r.origin.y(), 0.0f);
EXPECT_THAT(r.direction.x(), 1.0f);
EXPECT_THAT(r.direction.y(), 0.0f);
EXPECT_THAT(r.min_t, 1.0f);
EXPECT_THAT(r.max_t, 10.0f);
}
TEST(Ray2f, GivenNormalizedRay_WhenCheckingIfRayIsNormalized_ThenNormalizedIsTrue) {
Point2f origin(0.0f, 0.0f);
Vector2f direction(1.0f, 0.0f);
Ray2f r(origin, direction, 1.0f, 10.0f);
EXPECT_TRUE(r.isDirectionVectorNormalized());
}
TEST(Ray2f, GivenNonNormalizedRay_WhenCheckingIfRayIsNormalized_ThenNormalizedIsFalse) {
Point2f origin(0.0f, 0.0f);
Vector2f direction(2.0f, 0.0f);
Ray2f r(origin, direction, 1.0f, 10.0f);
EXPECT_FALSE(r.isDirectionVectorNormalized());
}
TEST(Ray2f, GivenANormalizedRay_WhenDeterminPointAtMinAndMaxT_ThenExpectStartAndEndPoint) {
Point2f origin(0.0f, 0.0f);
Vector2f direction(1.0f, 0.0f);
Ray2f r(origin, direction, 1.0f, 10.0f);
Point2f start = r(r.min_t);
Point2f end = r(r.max_t);
EXPECT_THAT(start.x(), 1.0f);
EXPECT_THAT(start.y(), 0.0f);
EXPECT_THAT(end.x(), 10.0f);
EXPECT_THAT(end.y(), 0.0f);
}
TEST(Ray2f, GivenARay_WhenPrintedToStdOutput_ExpectRayAsStringRepresentation) {
testing::internal::CaptureStdout();
Point2f origin(0.0f, 0.0f);
Vector2f direction(1.0f, 0.0f);
Ray2f r(origin, direction, 1.0f, 10.0f);
std::cout << r;
std::string output = testing::internal::GetCapturedStdout();
EXPECT_THAT(output, ::testing::HasSubstr("Ray2f["));
EXPECT_THAT(output, ::testing::HasSubstr("origin ="));
EXPECT_THAT(output, ::testing::HasSubstr("direction ="));
EXPECT_THAT(output, ::testing::HasSubstr("min_t = 1"));
EXPECT_THAT(output, ::testing::HasSubstr("max_t = 10"));
EXPECT_THAT(output, ::testing::HasSubstr("]"));
}
TEST(Ray2d, GivenARay2d_WhenPrintedToStdOutput_ExpectRayTypeRay2d) {
testing::internal::CaptureStdout();
Point2d origin(0.0, 0.0);
Vector2d direction(1.0, 0.0);
Ray2d r(origin, direction, 1.0, 10.0);
std::cout << r;
std::string output = testing::internal::GetCapturedStdout();
EXPECT_THAT(output, ::testing::HasSubstr("Ray2d["));
EXPECT_THAT(output, ::testing::HasSubstr("origin ="));
EXPECT_THAT(output, ::testing::HasSubstr("direction ="));
EXPECT_THAT(output, ::testing::HasSubstr("min_t = 1"));
EXPECT_THAT(output, ::testing::HasSubstr("max_t = 10"));
EXPECT_THAT(output, ::testing::HasSubstr("]"));
}
TEST(Ray3f, GivenARay3f_WhenPrintedToStdOutput_ExpectRayTypeRay3f) {
testing::internal::CaptureStdout();
Point3f origin(0.0f, 0.0f, 0.0f);
Vector3f direction(1.0f, 0.0f, 0.0f);
Ray3f r(origin, direction, 1.0f, 10.0f);
std::cout << r;
std::string output = testing::internal::GetCapturedStdout();
EXPECT_THAT(output, ::testing::HasSubstr("Ray3f["));
EXPECT_THAT(output, ::testing::HasSubstr("origin ="));
EXPECT_THAT(output, ::testing::HasSubstr("direction ="));
EXPECT_THAT(output, ::testing::HasSubstr("min_t = 1"));
EXPECT_THAT(output, ::testing::HasSubstr("max_t = 10"));
EXPECT_THAT(output, ::testing::HasSubstr("]"));
}
TEST(internal_convertTypToString, GivenIntegralType_WhenConverTypeToString_ThenShortStringRepersentationExpected) {
EXPECT_THAT(internal::convertTypeToString(), ::testing::HasSubstr("i"));
EXPECT_THAT(internal::convertTypeToString(), ::testing::HasSubstr("f"));
EXPECT_THAT(internal::convertTypeToString(), ::testing::HasSubstr("d"));
}
TEST(internal_dimensionAsString, GivenRayDimension_WhenConvertingToStringRepresentation_ThenExpectValidNumber) {
EXPECT_THAT(internal::dimensionAsString<2u>(), ::testing::HasSubstr("2"));
EXPECT_THAT(internal::dimensionAsString<3u>(), ::testing::HasSubstr("3"));
}问题:
实现
Ray的template class Ray中更改PointType,然后在类中使用D11。但我认为,如果我将来要改变PointType (目前的特征是在你的引擎盖下使用--也许我以后会换别的东西),那就更难了。关于这一点,我会怎么看呢?测试
您对实现和测试是否有其他的反馈、改进?
发布于 2020-09-16 22:17:31
使用接吻原则,不要使事情变得过于复杂。使用更多的语言特性本身不应该是一个目标。此外,不要在类中添加超出必要的内容。你为什么要跟踪dimension?全班没有任何东西能利用它。维度是PointType和VectorType的属性,因此它在某种程度上已经存在了。类似地,指定ScalarType与底层标量类型的PointType和VectorType不同是否有意义?
class Ray没有什么是私有的。考虑让它成为一个struct。而且,就像现在一样,您甚至不需要显式地为它创建构造函数。这是一个结构,去掉了一些不必要的行李:
template
struct Ray {
using ScalarType = PointType::value_type;
PointType operator()(const float t) const {
assert(isDirectionVectorNormalized());
return origin + t * direction;
}
bool isDirectionVectorNormalized() const {
const ScalarType epsilon{0.001};
return direction.norm() - ScalarType{1.0} < epsilon;
}
PointType origin{};
VectorType direction{};
ScalarType min_t{};
ScalarType max_t{};
};在这里,我假设PointType包含有关它存储的值的底层类型的信息。我还使用了这样的事实:我们可以构造一个类型的值,而不是static_cast<>。它节省了一些打字的时间。
那么如何得到一个Ray的维数呢?同样,我将确保您的PointType包含该信息,因此要获得射线的维度,只需编写如下内容:
Ray<...> ray = ...;
auto dimension = ray.origin.dimension;origin和direction无法使用不同的类型
创建一个Ray, Vector3>意味着什么?这没有意义。要么我只使用一种模板类型:
template
struct Ray {
...
VectorType origin;
VectorType direction;
};或者使用一些SFINAE技巧来执行维度和基本标量类型是相同的。
如果您不应该使用未规范化的Ray构造direction,那么我将只编写一个使direction规范化的构造函数,而不是在运行时使用assert()来检查这一点:
template <...>
struct Ray {
Ray(const PointType &origin, const VectorType &direction, ...)
: origin(origin)
, direction(direction.normalized())
, ...还请注意,您的函数isDirectionNormalized()忘记接受差值的绝对值。浮点比较很难,实施规范化就回避了这个问题。
你做太多测试了吗?也许吧。老实说,我不会编写只检查构造函数是否正确地将值复制到成员变量中的测试用例,因为这是一个非常基本的语言函数,如果不起作用,您就会遇到更大的问题。但是,如果您有一个使direction规范化的构造函数,那么我肯定会测试它。
在测试std::ostream操作符Ray时,我不会写到std::cout,然后让您的测试库跳过圈来获得标准输出,而是创建一个std::ostringstream来捕获输出。而且,我不会检查是否存在子字符串,而是验证整个字符串是否与预期的完全相同:
Ray2f r({1.0f, 2.0f}, {3.0f, 4.0f}, 5.0f, 6.0f);
std::ostringstream os;
os << r;
EXPECT_THAT(os.str(), std::string(R"(Ray2f[
origin = {1.0, 2.0},
direciton = {3.0, 4.0},
min_t = 5.0,
max_t = 6.0
])"));发布于 2020-09-17 15:08:23
convertTypeToString和dimensionAsString的实现有点低效和不自然。首先,不需要在分配给它并最终返回的函数中使用一个变量。其次,不应将未知或不可接受的值表示为字符串。从用户的角度来考虑它--不得不与"UnknownType“这样的”神奇值“进行比较是不太好的(也不适合改变)。此外,您已经在编译时获得了类型信息,那么为什么不使用它呢?
例如,我们可以写:
template
std::string convertTypeToString();
template <>
std::string convertTypeToString() {
return "i";
}
template <>
std::string convertTypeToString() {
return "f";
}
template <>
std::string convertTypeToString() {
return "d";
}否则,您在编译时会遇到错误,如果您尝试这样做,比如convertTypeToString();。如果您愿意,还可以使用static_assert输出更有意义的错误消息,比如“未实现”之类的错误消息。
您最好通过使用constexpr和类来实现这个完整的编译时:
template
struct convertTypeToString;
template <>
struct convertTypeToString {
constexpr static char value = 'i';
};
// Later on, you just do
convertTypeToString::value;https://codereview.stackexchange.com/questions/249457
复制相似问题