blob: b695ecf269219cfdebc9d221841e227a47f27dfa [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SRC_UI_LIB_ESCHER_GEOMETRY_TYPES_H_
#define SRC_UI_LIB_ESCHER_GEOMETRY_TYPES_H_
// clang-format off
#include "src/ui/lib/glm_workaround/glm_workaround.h"
// clang-format on
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtx/quaternion.hpp>
#include <glm/gtx/transform.hpp>
#include "src/lib/fxl/logging.h"
#include "src/ui/lib/escher/util/debug_print.h"
namespace escher {
using glm::mat2;
using glm::mat3;
using glm::mat4;
using glm::quat;
using glm::vec2;
using glm::vec3;
using glm::vec4;
ESCHER_DEBUG_PRINTABLE(vec2);
ESCHER_DEBUG_PRINTABLE(vec3);
ESCHER_DEBUG_PRINTABLE(vec4);
ESCHER_DEBUG_PRINTABLE(mat2);
ESCHER_DEBUG_PRINTABLE(mat3);
ESCHER_DEBUG_PRINTABLE(mat4);
ESCHER_DEBUG_PRINTABLE(quat);
// A ray with an origin and a direction of travel.
struct ray4 {
// The ray's origin point in space.
// Must be homogeneous (last component must be non-zero).
glm::vec4 origin;
// The ray's direction vector in space.
// This is not necessarily a unit vector. The last component must be zero.
glm::vec4 direction;
// Gets the coordinate point along the ray for a given parameterized distance.
glm::vec4 At(const float t) const { return origin + t * direction; }
};
// Used to compare whether two values are nearly equal.
constexpr float kEpsilon = 0.000001f;
inline ray4 operator*(const glm::mat4& matrix, const ray4& ray) {
FXL_DCHECK(ray.direction.w == 0) << "Ray direction should not be subject to translation.";
return ray4{matrix * ray.origin, matrix * ray.direction};
}
// Oriented plane described by a normal vector and a distance from the origin
// along that vector.
template <typename VecT>
struct planeN {
using VectorType = VecT;
// Default constructor.
planeN() : dir_(0.f), dist_(0.f) { dir_.x = 1.f; }
// |direction| must be normalized.
planeN(VecT direction, float distance) : dir_(direction), dist_(distance) {
FXL_DCHECK(std::abs(glm::dot(direction, direction) - 1.f) < kEpsilon) << direction;
}
planeN(VecT point_on_plane, VecT direction)
: dir_(direction), dist_(glm::dot(point_on_plane, direction)) {
FXL_DCHECK(std::abs(glm::dot(direction, direction) - 1.f) < kEpsilon);
}
planeN(const planeN& other) : dir_(other.dir_), dist_(other.dist_) {}
bool operator==(const planeN<VecT>& other) const {
return dir_ == other.dir_ && dist_ == other.dist_;
}
const VecT& dir() const { return dir_; }
float dist() const { return dist_; }
protected:
VecT dir_;
float dist_;
};
// A "plane2" is simply a line that exists on the z = 0 (XY) plane.
// In standard form this would be written Ax + By + C = 0, where
// (A,B) are the coordinates of the normal vector of the plane and
// C is the distance to the origin along that normal vector. (AB)
// is represented by the parameter "direction" and 'C' is represented
// by the parameter "distance". This is analogous to the equation of
// a plane in 3D which is given by the equation Ax + By + Cz + D = 0.
//
// To generate a "plane2" (line) that represents the intersection of
// an arbitrary 3D (clip) plane and the Z = 0 plane, we simply have
// to solve the following system of equations:
//
// 1) Ax + By + Cz + D = 0
// 2) z = 0
//
// This can be achieved by simply substituting equation 2 into equation
// 1 to yield Ax + By + D = 0, which is the same as our line equation
// as given above, meaning that for any arbitrary 3D plane, we can find
// its line of intersection on the Z = 0 plane by simply deleting the
// original Z component.
//
// We do however require that the normal vector (AB) is normalized,
// despite the fact that mathematically the line equation Ax + By + C = 0
// does not require a normalized (AB) to be a valid line equation.
//
// It is easy to renormalize the equation by realizing that the line
// equation can be rewritten as dot(AB, XY) = -D which itself can be
// expanded out to be |AB| * |XY| * cosTheta = -D. Dividing both
// sides of the equation by |AB| yields:
//
// (|AB|/|AB|) * |XY| * cosTheta = -D / |AB| =
// |XY| * cosTheta = -D / |AB|
//
// So what this means in terms of our implementation is just that we
// have to drop the Z component from the incoming direction, renormalize
// the remaining two components, and then divide the distance by the
// pre-normalized 2D direction.
//
// One last thing to note is that this only works if the incoming 3D plane
// is NOT parallel to the Z = 0 plane. This means we need to check if the
// direction of the incoming plane is (0,0,1) or (0,0,-1) using FXL_DCHECK
// to make sure this is not the case. We use a small epsilon value to
// check within the vicinity of z=1 to account for any floating point wackiness.
struct plane2 : public planeN<vec2> {
using planeN::planeN;
// Project a 3D plane onto the Z=0 plane, as described above.
plane2(const planeN<vec3>& plane) {
vec3 direction = plane.dir();
float_t distance = plane.dist();
// We only want to construct plane2 instead of plane3 when we know that
// the incoming plane will intersect the Z = 0 plane.
FXL_DCHECK(1.f - fabs(direction.z) > kEpsilon);
vec2 projected_direction = vec2(direction);
// Length will be <=1, because the vector being projected has length 1.
const float_t length = glm::length(projected_direction);
const float_t projected_distance = distance / length;
dir_ = glm::normalize(projected_direction);
dist_ = projected_distance;
}
};
struct plane3 : public planeN<vec3> {
using planeN::planeN;
explicit plane3(const planeN<vec2>& p) : planeN<vec3>(vec3(p.dir(), 0.f), p.dist()) {}
};
ESCHER_DEBUG_PRINTABLE(plane2);
ESCHER_DEBUG_PRINTABLE(plane3);
} // namespace escher
#endif // SRC_UI_LIB_ESCHER_GEOMETRY_TYPES_H_