/// /// @file Geometry.h /// /// @brief The header for various geometry definitions and helpers /// /// @author /// /// This file is a part of Ultralight, a fast, lightweight, HTML UI engine /// /// Website: /// /// Copyright (C) 2019 Ultralight, Inc. All rights reserved. /// #pragma once #include #include #include #include namespace ultralight { /// /// 2D Vector Helper /// struct UExport vec2 { union { float value[2]; struct { float x, y; }; }; inline vec2() {} inline vec2(float x, float y) : x(x), y(y) {} inline vec2(float x) : x(x), y(x) {} inline vec2 yx() const { return { y, x }; } inline vec2 xx() const { return { x, x }; } inline vec2 yy() const { return { y, y }; } inline friend vec2 operator+(vec2 lhs, const vec2& rhs) { lhs += rhs; return lhs; } inline friend vec2 operator-(vec2 lhs, const vec2& rhs) { lhs -= rhs; return lhs; } inline friend vec2 operator*(vec2 lhs, const vec2& rhs) { lhs *= rhs; return lhs; } inline friend vec2 operator/(vec2 lhs, const vec2& rhs) { lhs /= rhs; return lhs; } inline friend vec2 operator+(vec2 lhs, float rhs) { lhs += rhs; return lhs; } inline friend vec2 operator-(vec2 lhs, float rhs) { lhs -= rhs; return lhs; } inline friend vec2 operator*(vec2 lhs, float rhs) { lhs *= rhs; return lhs; } inline friend vec2 operator/(vec2 lhs, float rhs) { lhs /= rhs; return lhs; } inline vec2& operator+=(const vec2& rhs) { value[0] += rhs.value[0]; value[1] += rhs.value[1]; return *this; } inline vec2& operator-=(const vec2& rhs) { value[0] -= rhs.value[0]; value[1] -= rhs.value[1]; return *this; } inline vec2& operator*=(const vec2& rhs) { value[0] *= rhs.value[0]; value[1] *= rhs.value[1]; return *this; } inline vec2& operator/=(const vec2& rhs) { value[0] /= rhs.value[0]; value[1] /= rhs.value[1]; return *this; } inline vec2& operator+=(float rhs) { value[0] += rhs; value[1] += rhs; return *this; } inline vec2& operator-=(float rhs) { value[0] -= rhs; value[1] -= rhs; return *this; } inline vec2& operator*=(float rhs) { value[0] *= rhs; value[1] *= rhs; return *this; } inline vec2& operator/=(float rhs) { value[0] /= rhs; value[1] /= rhs; return *this; } inline friend bool operator==(const vec2& a, const vec2& b) { return !memcmp(&a, &b, sizeof(a)); } inline friend bool operator!=(const vec2& a, const vec2& b) { return !(a == b); } inline friend vec2 min_(const vec2& a, const vec2& b) { return{ (b.x < a.x) ? b.x : a.x, (b.y < a.y) ? b.y : a.y }; } inline friend vec2 max_(const vec2& a, const vec2& b) { return{ (a.x < b.x) ? b.x : a.x, (a.y < b.y) ? b.y : a.y }; } inline friend vec2 clamp(const vec2& x, const vec2& minVal, const vec2& maxVal) { return min_(max_(x, minVal), maxVal); } inline friend vec2 mix(const vec2& a, const vec2& b, float t) { return a * (1.0f - t) + b * t; } inline friend float length(const vec2& a) { return sqrtf(a.x * a.x + a.y * a.y); } // squared length inline friend float length2(const vec2& a) { return dot(a, a); } inline friend float distance(const vec2& a, const vec2& b) { return length(a - b); } // squared distance inline friend float distance2(const vec2& a, const vec2& b) { return length2(a - b); } inline friend vec2 normalize(const vec2& a) { return a / length(a); } inline friend float dot(const vec2& a, const vec2& b) { return a.x * b.x + a.y * b.y; } }; /// /// 3D Vector Helper /// struct UExport vec3 { union { float value[3]; struct { float x, y, z; }; }; inline vec3() {} inline vec3(float x, float y, float z) : x(x), y(y), z(z) {} inline vec3(float x) : x(x), y(x), z(x) {} inline friend vec3 operator+(vec3 lhs, const vec3& rhs) { lhs += rhs; return lhs; } inline friend vec3 operator-(vec3 lhs, const vec3& rhs) { lhs -= rhs; return lhs; } inline friend vec3 operator*(vec3 lhs, const vec3& rhs) { lhs *= rhs; return lhs; } inline friend vec3 operator/(vec3 lhs, const vec3& rhs) { lhs /= rhs; return lhs; } inline friend vec3 operator+(vec3 lhs, float rhs) { lhs += rhs; return lhs; } inline friend vec3 operator-(vec3 lhs, float rhs) { lhs -= rhs; return lhs; } inline friend vec3 operator*(vec3 lhs, float rhs) { lhs *= rhs; return lhs; } inline friend vec3 operator/(vec3 lhs, float rhs) { lhs /= rhs; return lhs; } inline vec3& operator+=(const vec3& rhs) { value[0] += rhs.value[0]; value[1] += rhs.value[1]; value[2] += rhs.value[2]; return *this; } inline vec3& operator-=(const vec3& rhs) { value[0] -= rhs.value[0]; value[1] -= rhs.value[1]; value[2] -= rhs.value[2]; return *this; } inline vec3& operator*=(const vec3& rhs) { value[0] *= rhs.value[0]; value[1] *= rhs.value[1]; value[2] *= rhs.value[2]; return *this; } inline vec3& operator/=(const vec3& rhs) { value[0] /= rhs.value[0]; value[1] /= rhs.value[1]; value[2] /= rhs.value[2]; return *this; } inline vec3& operator+=(float rhs) { value[0] += rhs; value[1] += rhs; value[2] += rhs; return *this; } inline vec3& operator-=(float rhs) { value[0] -= rhs; value[1] -= rhs; value[2] -= rhs; return *this; } inline vec3& operator*=(float rhs) { value[0] *= rhs; value[1] *= rhs; value[2] *= rhs; return *this; } inline vec3& operator/=(float rhs) { value[0] /= rhs; value[1] /= rhs; value[2] /= rhs; return *this; } inline friend bool operator==(const vec3& a, const vec3& b) { return !memcmp(&a, &b, sizeof(a)); } inline friend bool operator!=(const vec3& a, const vec3& b) { return !(a == b); } inline friend vec3 min_(const vec3& a, const vec3& b) { return{ (b.x < a.x) ? b.x : a.x, (b.y < a.y) ? b.y : a.y, (b.z < a.z) ? b.z : a.z }; } inline friend vec3 max_(const vec3& a, const vec3& b) { return{ (a.x < b.x) ? b.x : a.x, (a.y < b.y) ? b.y : a.y, (a.z < b.z) ? b.z : a.z }; } inline friend vec3 clamp(const vec3& x, const vec3& minVal, const vec3& maxVal) { return min_(max_(x, minVal), maxVal); } inline friend vec3 mix(const vec3& a, const vec3& b, float t) { return a * (1.0f - t) + b * t; } inline friend float length(const vec3& a) { return sqrtf(a.x * a.x + a.y * a.y + a.z * a.z); } inline friend float distance(const vec3& a, const vec3& b) { return length(a - b); } inline friend vec3 normalize(const vec3& a) { return a / length(a); } inline friend float dot(const vec3& a, const vec3& b) { return a.x * b.x + a.y * b.y + a.z * b.z; } }; /// /// 4D Vector Helper /// struct UExport vec4 { union { float value[4]; struct { float x, y, z, w; }; }; inline vec4() {} inline vec4(float x, float y, float z, float w) : x(x), y(y), z(z), w(w) {} inline vec4(float x) : x(x), y(x), z(x), w(x) {} inline vec4(const float x[4]) { memcpy(value, x, sizeof(value)); } inline friend bool operator==(const vec4& a, const vec4& b) { return !memcmp(&a, &b, sizeof(a)); } inline friend bool operator!=(const vec4& a, const vec4& b) { return !(a == b); } inline friend vec4 operator+(vec4 lhs, const vec4& rhs) { lhs += rhs; return lhs; } inline friend vec4 operator-(vec4 lhs, const vec4& rhs) { lhs -= rhs; return lhs; } inline friend vec4 operator*(vec4 lhs, const vec4& rhs) { lhs *= rhs; return lhs; } inline friend vec4 operator/(vec4 lhs, const vec4& rhs) { lhs /= rhs; return lhs; } inline friend vec4 operator+(vec4 lhs, float rhs) { lhs += rhs; return lhs; } inline friend vec4 operator-(vec4 lhs, float rhs) { lhs -= rhs; return lhs; } inline friend vec4 operator*(vec4 lhs, float rhs) { lhs *= rhs; return lhs; } inline friend vec4 operator/(vec4 lhs, float rhs) { lhs /= rhs; return lhs; } inline vec4& operator+=(const vec4& rhs) { value[0] += rhs.value[0]; value[1] += rhs.value[1]; value[2] += rhs.value[2]; value[3] += rhs.value[3]; return *this; } inline vec4& operator-=(const vec4& rhs) { value[0] -= rhs.value[0]; value[1] -= rhs.value[1]; value[2] -= rhs.value[2]; value[3] -= rhs.value[3]; return *this; } inline vec4& operator*=(const vec4& rhs) { value[0] *= rhs.value[0]; value[1] *= rhs.value[1]; value[2] *= rhs.value[2]; value[3] *= rhs.value[3]; return *this; } inline vec4& operator/=(const vec4& rhs) { value[0] /= rhs.value[0]; value[1] /= rhs.value[1]; value[2] /= rhs.value[2]; value[3] /= rhs.value[3]; return *this; } inline vec4& operator+=(float rhs) { value[0] += rhs; value[1] += rhs; value[2] += rhs; value[3] += rhs; return *this; } inline vec4& operator-=(float rhs) { value[0] -= rhs; value[1] -= rhs; value[2] -= rhs; value[3] -= rhs; return *this; } inline vec4& operator*=(float rhs) { value[0] *= rhs; value[1] *= rhs; value[2] *= rhs; value[3] *= rhs; return *this; } inline vec4& operator/=(float rhs) { value[0] /= rhs; value[1] /= rhs; value[2] /= rhs; value[3] /= rhs; return *this; } inline friend vec4 min_(const vec4& a, const vec4& b) { return{ (b.x < a.x) ? b.x : a.x, (b.y < a.y) ? b.y : a.y, (b.z < a.z) ? b.z : a.z, (b.w < a.w) ? b.w : a.w }; } inline friend vec4 max_(const vec4& a, const vec4& b) { return{ (a.x < b.x) ? b.x : a.x, (a.y < b.y) ? b.y : a.y, (a.z < b.z) ? b.z : a.z, (a.w < b.w) ? b.w : a.w }; } }; /// /// Point is typedef'd to a 2D vector /// typedef vec2 Point; /// /// Float Rectangle Helper /// struct UExport Rect { union { float value[4]; struct { float left, top, right, bottom; }; }; static inline Rect MakeEmpty() { Rect result; result.SetEmpty(); return result; } inline float width() const { return right - left; } inline float height() const { return bottom - top; } inline float x() const { return left; } inline float y() const { return top; } inline float center_x() const { return (left + right) * 0.5f; } inline float center_y() const { return (top + bottom) * 0.5f; } inline Point origin() const { return { left, top }; } inline void SetEmpty() { memset(this, 0, sizeof(*this)); } inline bool IsEmpty() const { return *this == MakeEmpty(); } inline bool IsValid() const { return width() > 0 && height() > 0; } inline void Inset(float dx, float dy) { value[0] += dx; value[1] += dy; value[2] -= dx; value[3] -= dy; } inline void Outset(float dx, float dy) { Inset(-dx, -dy); } inline void Move(float dx, float dy) { value[0] += dx; value[1] += dy; value[2] += dx; value[3] += dy; } inline float area() const { return width() * height(); } inline void Join(const Rect& rhs) { // if we are empty, just assign if (IsEmpty()) { *this = rhs; } else { if (rhs.value[0] < value[0]) value[0] = rhs.value[0]; if (rhs.value[1] < value[1]) value[1] = rhs.value[1]; if (rhs.value[2] > value[2]) value[2] = rhs.value[2]; if (rhs.value[3] > value[3]) value[3] = rhs.value[3]; } } inline void Join(const Point& p) { // if we are empty, just assign if (IsEmpty()) { *this = { p.x, p.y, p.x, p.y }; } else { if (p.x < value[0]) value[0] = p.x; if (p.y < value[1]) value[1] = p.y; if (p.x > value[2]) value[2] = p.x; if (p.y > value[3]) value[3] = p.y; } } inline bool Contains(const Point& p) const { return p.x >= left && p.x <= right && p.y >= top && p.y <= bottom; } inline bool Contains(const Rect& r) const { return left <= r.left && top <= r.top && right >= r.right && bottom >= r.bottom; } inline bool Intersects(const Rect& rhs) const { return !(rhs.left > right || rhs.right < left || rhs.top > bottom || rhs.bottom < top); } inline Rect Intersect(const Rect& other) const { return{ (left < other.left) ? other.left : left, (top < other.top) ? other.top : top, (other.right < right) ? other.right : right, (other.bottom < bottom) ? other.bottom : bottom }; } friend inline bool operator==(const Rect& a, const Rect& b) { return !memcmp(&a, &b, sizeof(a)); } friend inline bool operator!=(const Rect& a, const Rect& b) { return !(a == b); } }; /// /// Integer Rectangle Helper /// struct UExport IntRect { union { int value[4]; struct { int left, top, right, bottom; }; }; static inline IntRect MakeEmpty() { IntRect result; result.SetEmpty(); return result; } inline int width() const { return right - left; } inline int height() const { return bottom - top; } inline int x() const { return left; } inline int y() const { return top; } inline int center_x() const { return (int)std::round((left + right) * 0.5f); } inline int center_y() const { return (int)std::round((top + bottom) * 0.5f); } inline Point origin() const { return{ (float)left, (float)top }; } inline void SetEmpty() { memset(this, 0, sizeof(*this)); } inline bool IsEmpty() const { return *this == MakeEmpty(); } inline bool IsValid() const { return width() > 0 && height() > 0; } inline void Inset(int dx, int dy) { value[0] += dx; value[1] += dy; value[2] -= dx; value[3] -= dy; } inline void Outset(int dx, int dy) { Inset(-dx, -dy); } inline void Move(int dx, int dy) { value[0] += dx; value[1] += dy; value[2] += dx; value[3] += dy; } inline int area() const { return width() * height(); } inline void Join(const IntRect& rhs) { // if we are empty, just assign if (IsEmpty()) { *this = rhs; } else { if (rhs.value[0] < value[0]) value[0] = rhs.value[0]; if (rhs.value[1] < value[1]) value[1] = rhs.value[1]; if (rhs.value[2] > value[2]) value[2] = rhs.value[2]; if (rhs.value[3] > value[3]) value[3] = rhs.value[3]; } } inline void Join(const Point& p) { // if we are empty, just assign if (IsEmpty()) { *this = { (int)std::floor(p.x), (int)std::floor(p.y), (int)std::ceil(p.x), (int)std::ceil(p.y) }; } else { if ((int)std::floor(p.x) < value[0]) value[0] = (int)std::floor(p.x); if ((int)std::floor(p.y) < value[1]) value[1] = (int)std::floor(p.y); if ((int)std::ceil(p.x) > value[2]) value[2] = (int)std::ceil(p.x); if ((int)std::ceil(p.y) > value[3]) value[3] = (int)std::ceil(p.y); } } inline bool Contains(const Point& p) const { return p.x >= left && p.x <= right && p.y >= top && p.y <= bottom; } inline bool Contains(const IntRect& r) const { return left <= r.left && top <= r.top && right >= r.right && bottom >= r.bottom; } inline bool Intersects(const IntRect& rhs) const { // Since this is mostly used for pixel operations, we only count // intersections that have width and height >= 1. return !(rhs.left > right - 1 || rhs.right < left || rhs.top > bottom - 1 || rhs.bottom < top); } inline IntRect Intersect(const IntRect& other) const { return{ (left < other.left) ? other.left : left, (top < other.top) ? other.top : top, (other.right < right) ? other.right : right, (other.bottom < bottom) ? other.bottom : bottom }; } friend inline bool operator==(const IntRect& a, const IntRect& b) { return !memcmp(&a, &b, sizeof(a)); } friend inline bool operator!=(const IntRect& a, const IntRect& b) { return !(a == b); } }; /// /// Rounded Rectangle Helper /// struct UExport RoundedRect { Rect rect; float radii_x[4]; float radii_y[4]; void SetEmpty(); bool IsRounded() const; // Negative is inside, positive is outside. float GetSignedDistance(const Point& p) const; // Returns whether or not intersection is found. Can fail if the resulting // geometry is not a rounded rectangle. bool Intersect(const RoundedRect& other, RoundedRect& result) const; void SnapToPixels(); Rect CalculateInterior() const; }; } // namespace ultralight