[TinyRenderer] Chapter1 p2 vec

qiyuewuyi2333發表於2024-06-12

在上一小節中,我們完成了對BMPImage類的構建,成功實現了我們這個小小引擎的影像輸出功能。

你已經完成了影像輸出了,接著就開始路徑追蹤吧。。。
開個玩笑XD
對於曾經學習過一些圖形學經典教材的人來說,下一步應當開始著手於畫線演算法了,但對於本文來說,肯定是要走一些不走尋常路的

所謂萬事開頭難,我決定先打好地基。編寫一個魯棒性,擴充套件性還不錯的向量類。

由於我們不打算藉助外部庫,所以,適當的設計是必要的。在這裡,我打算借鑑一下pbrt-v4的設計。
也就是利用模板基類來定製我們的資料結構。

這麼做的好處是,我們可以按照元素數量劃分父類

本章目標

構建基本的資料結構,包括點和向量。

需求

  • 型別可擴充套件,魯棒性還可以
  • 易於使用

實現

class Tuple2
/**
 * \brief every element's parent class who has two parameter
 * \tparam Child a template class contains one template parameter
 * \tparam T defined by the child's type of template parameter
 */
template <template <typename> class Child, typename T> class Tuple2
{
  public:
    static constexpr int nDimensions = 2;

    Tuple2() = default;
    Tuple2(T x, T y) : x(x), y(y)
    {
    }
    Tuple2(Child<T> c)
    {
        x = c.x;
        y = c.y;
    }
    Child<T>& operator=(Child<T> c)
    {
        x = c.x;
        y = c.y;
        return static_cast<Child<T>&>(*this);
    }
    template <typename U> auto operator+(Child<U> c) const -> Child<decltype(T{} + U{})>
    {
        return {x + c.x, y + c.y};
    }
    template <typename U> Child<T>& operator+=(Child<U> c)
    {
        x += c.x;
        y += c.y;
        return static_cast<Child<T>&>(*this);
    }

    template <typename U> auto operator-(Child<U> c) const -> Child<decltype(T{} - U{})>
    {
        return {x - c.x, y - c.y};
    }
    template <typename U> Child<T>& operator-=(Child<U> c)
    {
        x -= c.x;
        y -= c.y;
        return static_cast<Child<T>&>(*this);
    }

    bool operator==(Child<T> c) const
    {
        return x == c.x && y == c.y;
    }

    bool operator!=(Child<T> c) const
    {
        return x != c.x || y != c.y;
    }

    template <typename U> auto operator*(U s) const -> Child<decltype(T{} * U{})>
    {
        return {s * x, s * y};
    }
    template <typename U> Child<T>& operator*=(U s)
    {
        x *= s;
        y *= s;
        return static_cast<Child<T>&>(*this);
    }
    template <typename U> Child<decltype(T{} / U{})> operator/(U d) const
    {
        VEC_CHECK(d != 0);
        return {x / d, y / d};
    }

    template <typename U> [[nodiscard]] Child<T>& operator/=(U d)
    {
        VEC_CHECK(d != 0);
        x /= d;
        y /= d;
        return static_cast<Child<T>&>(*this);
    }

    [[nodiscard]] Child<T> operator-() const
    {
        return {-x, -y};
    }

    [[nodiscard]] T& operator[](const int i) const
    {
        VEC_CHECK(i >= 0 && i <= 1);
        return (i == 0) ? x : y;
    }
    [[nodiscard]] T& operator[](const int i)
    {
        VEC_CHECK(i >= 0 && i <= 1);
        return (i == 0) ? x : y;
    }
    [[nodiscard]] std::string toString() const
    {
        return std::to_string(x) + std::to_string(x);
    }
    T x{};
    T y{};
};

在程式碼中,用到了一些C++的高階技巧,我將在下面一一解釋給大家:

  1. VEC_CHECK()宏定義
    為了達成魯棒性的需求,我在方法的定義中加入了僅在debug模式下生效的assert,同時封裝進入宏變數中,提高了程式碼健壯性的同時,也不會影響效能。

  2. [[nodiscard]]宣告
    該宣告於C++17版本中加入,宣告在使用該方法時,不應當遺棄返回值,否則會發出警告,在除錯時,略有作用

  3. template <template <typename> class Child, typename T>
    作為Tuple2的模板引數宣告,使用Child作為當基類進行繼承時的模板特化
    這是一種二級的模板特化結構,子類對父類的繼承僅指定了Child這一模板引數的值,如此實現,基類的模板方法會非常好寫。
    而T只需要在編譯時進行確定,就可以生成對應的類了。
    這是一種在C++中實現動態繫結的方式。

那麼在Tuple2的基礎上,再去實現Vec2和Point2就十分容易了。

// 派生類 Vec2,繼承自 Tuple2
template <typename T> class Vec2 : public Tuple2<Vec2, T>
{
  public:
    using Tuple2<Vec2, T>::x;
    using Tuple2<Vec2, T>::y;

    Vec2() : Tuple2<Vec2, T>()
    {
    }
    Vec2(T x, T y) : Tuple2<Vec2, T>(x, y)
    {
    }

    void print() const
    {
        std::cout << "Vec2: (" << this->x << ", " << this->y << ")\n";
    }
};

template <typename T> class Point2 : public Tuple2<Point2, T>
{
  public:
    using Tuple2<Point2, T>::x;
    using Tuple2<Point2, T>::y;

    Point2() : Tuple2<Point2, T>()
    {
    }
    Point2(T x, T y) : Tuple2<Point2, T>(x, y)
    {
    }

    void print() const
    {
        std::cout << "Point2: (" << this->x << ", " << this->y << ")\n";
    }
};

同理,只需要把元素數量改成3個,我們就可以得到Tuple3以及其子類

// 基類别範本 Tuple3
template <template <typename> class Child, typename T> class Tuple3
{
  public:
    static constexpr int nDimensions = 3;

    Tuple3() = default;
    Tuple3(T x, T y, T z) : x(x), y(y), z(z)
    {
    }
    Tuple3(Child<T> c)
    {
        x = c.x;
        y = c.y;
        z = c.z;
    }
    Child<T>& operator=(Child<T> c)
    {
        x = c.x;
        y = c.y;
        z = c.z;
        return static_cast<Child<T>&>(*this);
    }

    template <typename U> auto operator+(Child<U> c) const -> Child<decltype(T{} + U{})>
    {
        return {x + c.x, y + c.y, z + c.z};
    }

    template <typename U> Child<T>& operator+=(Child<U> c)
    {
        x += c.x;
        y += c.y;
        z += c.z;
        return static_cast<Child<T>&>(*this);
    }

    template <typename U> auto operator-(Child<U> c) const -> Child<decltype(T{} - U{})>
    {
        return {x - c.x, y - c.y, z - c.z};
    }

    template <typename U> Child<T>& operator-=(Child<U> c)
    {
        x -= c.x;
        y -= c.y;
        z -= c.z;
        return static_cast<Child<T>&>(*this);
    }

    bool operator==(Child<T> c) const
    {
        return x == c.x && y == c.y && z == c.z;
    }

    bool operator!=(Child<T> c) const
    {
        return x != c.x || y != c.y || z != c.z;
    }

    template <typename U> auto operator*(U s) const -> Child<decltype(T{} * U{})>
    {
        return {s * x, s * y, s * z};
    }

    template <typename U> Child<T>& operator*=(U s)
    {
        x *= s;
        y *= s;
        z *= s;
        return static_cast<Child<T>&>(*this);
    }

    template <typename U> Child<decltype(T{} / U{})> operator/(U d) const
    {
        VEC_CHECK(d != 0);
        return {x / d, y / d, z / d};
    }

    template <typename U> [[nodiscard]] Child<T>& operator/=(U d)
    {
        VEC_CHECK(d != 0);
        x /= d;
        y /= d;
        z /= d;
        return static_cast<Child<T>&>(*this);
    }

    [[nodiscard]] Child<T> operator-() const
    {
        return {-x, -y, -z};
    }

    [[nodiscard]] T& operator[](const int i) const
    {
        VEC_CHECK(i >= 0 && i <= 2);
        return (i == 0) ? x : (i == 1) ? y : z;
    }

    [[nodiscard]] T& operator[](const int i)
    {
        VEC_CHECK(i >= 0 && i <= 2);
        return (i == 0) ? x : (i == 1) ? y : z;
    }

    [[nodiscard]] std::string toString() const
    {
        return std::to_string(x) + ", " + std::to_string(y) + ", " + std::to_string(z);
    }

    T x{};
    T y{};
    T z{};
};
// 派生類 Vec3,繼承自 Tuple3
template <typename T> class Vec3 : public Tuple3<Vec3, T>
{
  public:
    // 顯式引入模板基類的屬性
    using Tuple3<Vec3, T>::x;
    using Tuple3<Vec3, T>::y;
    using Tuple3<Vec3, T>::z;

    Vec3() : Tuple3<Vec3, T>()
    {
    }
    Vec3(T x, T y, T z) : Tuple3<Vec3, T>(x, y, z)
    {
    }

    void print() const
    {
        std::cout << "Vec3: (" << this->x << ", " << this->y << ", " << this->z << ")\n";
    }
};
template <typename T> class Point3 : public Tuple3<Point3, T>
{
  public:
    using Tuple3<Point3, T>::x;
    using Tuple3<Point3, T>::y;
    using Tuple3<Point3, T>::z;

    Point3() : Tuple3<Point3, T>()
    {
    }
    Point3(T x, T y, T z) : Tuple3<Point3, T>(x, y, z)
    {
    }

    void print() const
    {
        std::cout << "Point3: (" << this->x << ", " << this->y << ", " << this->z << ")\n";
    }
};

相關文章