diff options
Diffstat (limited to '')
54 files changed, 10565 insertions, 0 deletions
diff --git a/dev/MinGfx/src/.gitignore b/dev/MinGfx/src/.gitignore new file mode 100644 index 0000000..4d353dd --- /dev/null +++ b/dev/MinGfx/src/.gitignore @@ -0,0 +1,2 @@ +mingfx_config.h + diff --git a/dev/MinGfx/src/CMakeLists.txt b/dev/MinGfx/src/CMakeLists.txt new file mode 100644 index 0000000..ff8225b --- /dev/null +++ b/dev/MinGfx/src/CMakeLists.txt @@ -0,0 +1,107 @@ +# This file is part of the MinGfx cmake build system. +# See the main MinGfx/CMakeLists.txt file for details. + + +set(HEADERFILES + src/aabb.h + src/bvh.h + src/color.h + src/craft_cam.h + src/default_shader.h + src/gfxmath.h + src/graphics_app.h + src/matrix4.h + src/mesh.h + src/mingfx.h + src/mingfx_config.h + src/opengl_headers.h + src/platform.h + src/point2.h + src/point3.h + src/quaternion.h + src/quick_shapes.h + src/ray.h + src/shader_program.h + src/text_shader.h + src/texture2d.h + src/unicam.h + src/vector2.h + src/vector3.h +) + +set(SOURCEFILES + src/aabb.cc + src/bvh.cc + src/color.cc + src/craft_cam.cc + src/default_shader.cc + src/gfxmath.cc + src/graphics_app.cc + src/matrix4.cc + src/mesh.cc + src/platform.cc + src/point2.cc + src/point3.cc + src/quaternion.cc + src/quick_shapes.cc + src/ray.cc + src/shader_program.cc + src/text_shader.cc + src/texture2d.cc + src/unicam.cc + src/vector2.cc + src/vector3.cc +) + +set(EXTRAFILES + src/mingfx_config.h.in +) + +set(SHADERFILES + src/shaders/default.frag + src/shaders/default.vert + src/shaders/fullscreen.frag + src/shaders/fullscreen.vert + src/shaders/text.frag + src/shaders/text.vert +) + +set_source_files_properties(${EXTRAFILES} PROPERTIES HEADER_FILE_ONLY TRUE) +set_source_files_properties(${SHADERFILES} PROPERTIES HEADER_FILE_ONLY TRUE) + +source_group("Header Files" FILES ${HEADERFILES}) +source_group("Source Files" FILES ${SOURCEFILES}) +source_group("Shaders" FILES ${SHADERFILES}) + +#add_library(MinGfx SHARED ${HEADERFILES} ${SOURCEFILES} ${EXTRAFILES} ${SHADERFILES}) + +add_library(MinGfx ${HEADERFILES} ${SOURCEFILES} ${EXTRAFILES} ${SHADERFILES}) + + +# Using target_include_directories() rather than just include_directories() is +# critical in order to support generating a MinGfxConfig.cmake file. It supports +# generator expressions, so we can point to two different include dirs depending +# on whether building or using the installed version. +target_include_directories(MinGfx PUBLIC + $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src> # for headers when building + $<INSTALL_INTERFACE:${INSTALL_INCLUDE_DEST}> # for client in install mode +) + +# Add external dependency on NanoGUI +include(AutoBuildNanoGUI) +AutoBuild_use_package_NanoGUI(MinGfx PUBLIC) + +# Add external dependency on OpenGL +include(AutoBuildOpenGL) +AutoBuild_use_package_OpenGL(MinGfx PUBLIC) + + +install(TARGETS MinGfx EXPORT MinGfxTargets COMPONENT CoreLib + LIBRARY DESTINATION "${INSTALL_LIB_DEST}" + ARCHIVE DESTINATION "${INSTALL_LIB_DEST}" + RUNTIME DESTINATION "${INSTALL_BIN_DEST}" +) + +install(FILES ${HEADERFILES} DESTINATION "${INSTALL_INCLUDE_DEST}" COMPONENT CoreLib) +install(FILES ${SHADERFILES} DESTINATION "${INSTALL_SHADERS_DEST}" COMPONENT CoreLib) + diff --git a/dev/MinGfx/src/aabb.cc b/dev/MinGfx/src/aabb.cc new file mode 100644 index 0000000..1d94900 --- /dev/null +++ b/dev/MinGfx/src/aabb.cc @@ -0,0 +1,150 @@ +#include "aabb.h" + +#include "mesh.h" + +#include <float.h> + +namespace mingfx { + +AABB::AABB() { + min_[0] = min_[1] = min_[2] = std::numeric_limits<float>::max(); + max_[0] = max_[1] = max_[2] = -std::numeric_limits<float>::max(); + user_data_ = 0; +} + + +AABB::AABB(const Point3 &a) { + min_ = a; + max_ = a; + user_data_ = 0; +} + +AABB::AABB(const Vector3 &v) { + min_ = Point3(-0.5f*v[0], -0.5f*v[1], -0.5f*v[2]); + max_ = Point3( 0.5f*v[0], 0.5f*v[1], 0.5f*v[2]); + user_data_ = 0; +} + +AABB::AABB(const Point3 &p, const Vector3 &v) { + min_ = Point3(p[0] - 0.5f*v[0], p[1] - 0.5f*v[1], p[2] - 0.5f*v[2]); + max_ = Point3(p[0] + 0.5f*v[0], p[1] + 0.5f*v[1], p[2] + 0.5f*v[2]); + user_data_ = 0; +} + +AABB::AABB(const Point3 &a, const Point3 &b, const Point3 &c) { + min_ = a; + min_[0] = std::min(min_[0], b[0]); + min_[1] = std::min(min_[1], b[1]); + min_[2] = std::min(min_[2], b[2]); + min_[0] = std::min(min_[0], c[0]); + min_[1] = std::min(min_[1], c[1]); + min_[2] = std::min(min_[2], c[2]); + + max_ = a; + max_[0] = std::max(max_[0], b[0]); + max_[1] = std::max(max_[1], b[1]); + max_[2] = std::max(max_[2], b[2]); + max_[0] = std::max(max_[0], c[0]); + max_[1] = std::max(max_[1], c[1]); + max_[2] = std::max(max_[2], c[2]); + user_data_ = 0; +} + + +AABB::AABB(const Mesh &mesh, unsigned int tri_id) { + std::vector<unsigned int> indices = mesh.read_triangle_indices_data(tri_id); + Point3 a = mesh.read_vertex_data(indices[0]); + Point3 b = mesh.read_vertex_data(indices[1]); + Point3 c = mesh.read_vertex_data(indices[2]); + + min_ = a; + min_[0] = std::min(min_[0], b[0]); + min_[1] = std::min(min_[1], b[1]); + min_[2] = std::min(min_[2], b[2]); + min_[0] = std::min(min_[0], c[0]); + min_[1] = std::min(min_[1], c[1]); + min_[2] = std::min(min_[2], c[2]); + + max_ = a; + max_[0] = std::max(max_[0], b[0]); + max_[1] = std::max(max_[1], b[1]); + max_[2] = std::max(max_[2], b[2]); + max_[0] = std::max(max_[0], c[0]); + max_[1] = std::max(max_[1], c[1]); + max_[2] = std::max(max_[2], c[2]); + + user_data_ = 0; +} + + +AABB::AABB(const Mesh &mesh) { + min_[0] = min_[1] = min_[2] = std::numeric_limits<float>::max(); + max_[0] = max_[1] = max_[2] = -std::numeric_limits<float>::max(); + + for (int i=0; i < mesh.num_vertices(); i++) { + Point3 a = mesh.read_vertex_data(i); + min_[0] = std::min(min_[0], a[0]); + min_[1] = std::min(min_[1], a[1]); + min_[2] = std::min(min_[2], a[2]); + + max_[0] = std::max(max_[0], a[0]); + max_[1] = std::max(max_[1], a[1]); + max_[2] = std::max(max_[2], a[2]); + } + + user_data_ = 0; +} + + +AABB::~AABB() {} + +Vector3 AABB::Dimensions() const { + return max_ - min_; +} + +float AABB::Volume() const { + if (max_[0] < min_[0]) { + // empty box + return -1.0; + } + + Vector3 dims = max_ - min_; + return (dims[0] * dims[1] * dims[2]); +} + + +Point3 AABB::min() const { + return min_; +} + +Point3 AABB::max() const { + return max_; +} + + +void AABB::set_user_data(int data) { + user_data_ = data; +} + +int AABB::user_data() { + return user_data_; +} + + +// Compute an AABB that contains both A and B completely +AABB operator+(const AABB &A, const AABB &B) { + AABB C; + + C.min_[0] = std::min(A.min_[0], B.min_[0]); + C.min_[1] = std::min(A.min_[1], B.min_[1]); + C.min_[2] = std::min(A.min_[2], B.min_[2]); + + C.max_[0] = std::max(A.max_[0], B.max_[0]); + C.max_[1] = std::max(A.max_[1], B.max_[1]); + C.max_[2] = std::max(A.max_[2], B.max_[2]); + + return C; +} + + +} // end namespace diff --git a/dev/MinGfx/src/aabb.h b/dev/MinGfx/src/aabb.h new file mode 100644 index 0000000..0c94140 --- /dev/null +++ b/dev/MinGfx/src/aabb.h @@ -0,0 +1,96 @@ +/* + This file is part of the MinGfx Project. + + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + + Original Author(s) of this File: + David Schroeder, 2010-ish, University of Minnesota + + Author(s) of Significant Updates/Modifications to the File: + Dan Keefe, 2018, University of Minnesota + ... + */ + +#ifndef SRC_AABB_H_ +#define SRC_AABB_H_ + +#include "point3.h" +#include "vector3.h" + +namespace mingfx { + +// forward declaration +class Mesh; + +/** A 3D axis-aligned bounding box defined by two corners (min and max). AABBs + can be added together using the + operator to make them grow to cover the + extents of both boxes. Each box can also store a user_id (int), which can be + used to associate the box with some other object in your program. + */ +class AABB { +public: + /// Creates an empty box. + AABB(); + + /// Box that contains a single point + AABB(const Point3 &a); + + /// Box that contains a triangle defined by 3 points + AABB(const Point3 &a, const Point3 &b, const Point3 &c); + + /// Box centered at the origin with width, height, and depth specified by + /// the vector. + AABB(const Vector3 &extents); + + /// Box centered at the center with width, height, and depth specified by + /// the vector. + AABB(const Point3 ¢er, const Vector3 &extents); + + /// Box that contains a whole mesh + AABB(const Mesh &mesh); + + /// Box that contains just triangle number "tri_id" from the specified mesh. + AABB(const Mesh &mesh, unsigned int tri_id); + + virtual ~AABB(); + + /// Returns the dimensions of the box in x, y, and z as a 3D vector. + Vector3 Dimensions() const; + + /// Returns the volume of the box or -1.0 when empty and 0.0 if the box + /// contains just a single point. + float Volume() const; + + /// Returns the coordinates for the minimum corner of the box. + Point3 min() const; + + /// Returns the coordinates for the maximum corner of the box. + Point3 max() const; + + /// You can set this to whatever you want in order to use it as a handle + /// into your own program. The intent is to make it possible for you to + /// associate this AABB with the id of some object in your application. + void set_user_data(int data); + + /// You can set this to whatever you want in order to use it as a handle + /// into your own program. The intent is to make it possible for you to + /// associate this AABB with the id of some object in your application. + int user_data(); + +private: + + Point3 min_, max_; + + int user_data_; + + friend AABB operator+(const AABB &A, const AABB &B); +}; + +// Returns an AABB that contains both A and B completely (kind of like a union) +AABB operator+(const AABB &A, const AABB &B); + + +} // end namespace + +#endif diff --git a/dev/MinGfx/src/bvh.cc b/dev/MinGfx/src/bvh.cc new file mode 100644 index 0000000..ff8dbbc --- /dev/null +++ b/dev/MinGfx/src/bvh.cc @@ -0,0 +1,120 @@ +#include "bvh.h" + +#include "mesh.h" +#include "ray.h" + +#include <float.h> +#include <algorithm> + +namespace mingfx { + + +BVH::BVH() : root_(NULL) { +} + +BVH::~BVH() { + FreeNodeRecursive(root_); +} + +void BVH::CreateFromMesh(const Mesh &mesh) { + FreeNodeRecursive(root_); + + std::vector<AABB> tri_boxes; + for (int i=0; i<mesh.num_triangles(); i++) { + AABB box = AABB(mesh, i); + box.set_user_data(i); + tri_boxes.push_back(box); + } + + root_ = new Node(); + BuildHierarchyRecursive(root_, tri_boxes); +} + +void BVH::CreateFromListOfBoxes(const std::vector<AABB> &boxes) { + FreeNodeRecursive(root_); + + root_ = new Node(); + BuildHierarchyRecursive(root_, boxes); +} + + +void BVH::FreeNodeRecursive(Node* node) { + if (node == NULL) return; + FreeNodeRecursive(node->child1); + FreeNodeRecursive(node->child2); + delete node; +} + +bool sort_by_x(const AABB &lhs, const AABB &rhs) { + return (lhs.min()[0] + lhs.max()[0]) < (rhs.min()[0] + rhs.max()[0]); +} + +bool sort_by_y(const AABB &lhs, const AABB &rhs) { + return (lhs.min()[1] + lhs.max()[1]) < (rhs.min()[1] + rhs.max()[1]); +} + +bool sort_by_z(const AABB &lhs, const AABB &rhs) { + return (lhs.min()[2] + lhs.max()[2]) < (rhs.min()[2] + rhs.max()[2]); +} + +void BVH::BuildHierarchyRecursive(Node *node, std::vector<AABB> tri_boxes) { + // got down to a leaf, a single box + if (tri_boxes.size() == 1) { + node->box = tri_boxes[0]; + return; + } + + // calc the full bounding box for this node + for (int i=0; i<tri_boxes.size(); i++) { + node->box = node->box + tri_boxes[i]; + } + + // sort boxes along the longest axis + Vector3 dims = node->box.Dimensions(); + dims[0] = fabsf(dims[0]); + dims[1] = fabsf(dims[1]); + dims[2] = fabsf(dims[2]); + + if ((dims[0] > dims[1]) && (dims[0] > dims[2])) { + std::sort(tri_boxes.begin(), tri_boxes.end(), sort_by_x); + } + else if ((dims[1] > dims[0]) && (dims[1] > dims[2])) { + std::sort(tri_boxes.begin(), tri_boxes.end(), sort_by_y); + } + else { + std::sort(tri_boxes.begin(), tri_boxes.end(), sort_by_z); + } + + // assign half to child1 and half to child2 + std::size_t const half_size = tri_boxes.size() / 2; + std::vector<AABB> left_boxes(tri_boxes.begin(), tri_boxes.begin() + half_size); + std::vector<AABB> right_boxes(tri_boxes.begin() + half_size, tri_boxes.end()); + + node->child1 = new Node(); + BuildHierarchyRecursive(node->child1, left_boxes); + node->child2 = new Node(); + BuildHierarchyRecursive(node->child2, right_boxes); +} + +std::vector<int> BVH::IntersectAndReturnUserData(const Ray &r) const { + std::vector<int> data_list; + IntersectRecursive(r, root_, &data_list); + return data_list; +} + +void BVH::IntersectRecursive(const Ray &r, Node *node, std::vector<int> *data_list) const { + float t; + if (r.IntersectAABB(node->box, &t)) { + if ((node->child1 == NULL) && (node->child2 == NULL)) { + // reached a leaf node, add the object's user data to the list + data_list->push_back(node->box.user_data()); + } + else { + // go deeper and check children + IntersectRecursive(r, node->child1, data_list); + IntersectRecursive(r, node->child2, data_list); + } + } +} + +} // end namespace diff --git a/dev/MinGfx/src/bvh.h b/dev/MinGfx/src/bvh.h new file mode 100644 index 0000000..46b15ce --- /dev/null +++ b/dev/MinGfx/src/bvh.h @@ -0,0 +1,106 @@ +/* + This file is part of the MinGfx Project. + + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + + Original Author(s) of this File: + David Schroeder, 2010-ish, University of Minnesota + + Author(s) of Significant Updates/Modifications to the File: + Dan Keefe, 2018, University of Minnesota + ... + */ + +#ifndef SRC_BVH_H_ +#define SRC_BVH_H_ + +#include "aabb.h" +#include "point3.h" + + +namespace mingfx { + +// forward declarations +class Mesh; +class Ray; + + +/** A Bounding Volume Hierarchy (BVH) data structure that can be used to + accelerate ray-object intersection tests by carving up space into a hierarchy + of partitions represented in a tree. Each node of the tree is represented + as an AABB (Axis-Aligned Bounding Box) that contains all of the nodes under it. + Different objects can be stored inside each bounding box. For example, when + a BVH is created for a mesh, each leaf node can contain a AABB that contains + just a single triangle. Or, when a BVH is created for an entire scene, you + could have each leaf node contain an entire mesh or other object within the + scene. In each case, use AABB's set_user_data() and user_data() methods to + store a handle for whetever you want to store inside the nodes. + */ +class BVH { +public: + /// Initializes the class with an empty hierarchy. + BVH(); + + virtual ~BVH(); + + /** Creates a bounding volume hierarchy where each leaf node contains a single + triangle from the mesh. For leaf nodes, the triangle index can be retrieved + with: + ~~~ + int tri_id = leafnode->box.user_data(); + ~~~ + The user_data will be -1 for non-leaf nodes. Once the structure has been + created, it can be used to perform fast ray-mesh intersection tests. See + Ray::FastIntersectMesh(). + */ + void CreateFromMesh(const Mesh &mesh); + + + /** Creates a BVH where each leaf node contains one of the boxes passed in + to the function. + */ + void CreateFromListOfBoxes(const std::vector<AABB> &boxes); + + + /** Traverse the BVH to find leaf nodes whose AABBs are intersected by the + ray. These are candidates to test more thoroughly using whatever ray-object + intersection test is appropriate for the objects stored inside the AABB. + This routine returns the user_data for each AABB leaf node. In the case of + a BVH created using CreateFromMesh, this means it stores the indices to + the mesh triangles that should be tested for ray-triangle intersection. + */ + std::vector<int> IntersectAndReturnUserData(const Ray &r) const; + + +private: + + // Simple internal data structure for storing each node of the BVH tree. + class Node { + public: + Node() : child1(NULL), child2(NULL) {} + + // Links to children + Node *child1; + Node *child2; + + // Contains all geometry below this node. + AABB box; + }; + + + // for now, the copy constructor is private so no copies are allowed. + // eventually, this would be good to implement and then it can be made public. + BVH(const BVH &other); + + void BuildHierarchyRecursive(Node *node, std::vector<AABB> boxes); + void IntersectRecursive(const Ray &r, Node *node, std::vector<int> *data_list) const; + void FreeNodeRecursive(Node* node); + + Node* root_; +}; + + +} // end namespace + +#endif diff --git a/dev/MinGfx/src/color.cc b/dev/MinGfx/src/color.cc new file mode 100644 index 0000000..cc5db87 --- /dev/null +++ b/dev/MinGfx/src/color.cc @@ -0,0 +1,130 @@ +/* + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + See corresponding header file for details. + */ + +#include "color.h" + +#include <math.h> + +namespace mingfx { + +Color::Color() { + c[0] = 0.0; + c[1] = 0.0; + c[2] = 0.0; + c[3] = 1.0; +} + +Color::Color(float red, float green, float blue, float alpha) { + c[0] = red; + c[1] = green; + c[2] = blue; + c[3] = alpha; +} + +Color::Color(float *ptr) { + c[0] = ptr[0]; + c[1] = ptr[1]; + c[2] = ptr[2]; + c[3] = ptr[3]; +} + +Color::Color(const Color& other) { + c[0] = other[0]; + c[1] = other[1]; + c[2] = other[2]; + c[3] = other[3]; +} + +Color::Color(const std::vector<float> &vals) { + c[0] = vals[0]; + c[1] = vals[1]; + c[2] = vals[2]; + if (vals.size() > 3) { + c[3] = vals[3]; + } + else { + c[3] = 1.0; + } +} + + +Color::~Color() { +} + +bool Color::operator==(const Color& other) const { + return ((other[0] == c[0]) && + (other[1] == c[1]) && + (other[2] == c[2]) && + (other[3] == c[3])); +} + +bool Color::operator!=(const Color& other) const { + return ((other[0] != c[0]) || + (other[1] != c[1]) || + (other[2] != c[2]) || + (other[3] != c[3])); +} + +Color& Color::operator=(const Color& other) { + c[0] = other[0]; + c[1] = other[1]; + c[2] = other[2]; + c[3] = other[3]; + return *this; +} + +float Color::operator[](const int i) const { + return c[i]; +} + +float& Color::operator[](const int i) { + return c[i]; +} + + +const float * Color::value_ptr() const { + return c; +} + +std::vector<float> Color::ToVector() const { + std::vector<float> v; + v.push_back(c[0]); + v.push_back(c[1]); + v.push_back(c[2]); + v.push_back(c[3]); + return v; +} + + +Color Color::Lerp(const Color &b, float alpha) const { + float red = (1.0f-alpha)*(*this)[0] + alpha*b[0]; + float grn = (1.0f-alpha)*(*this)[1] + alpha*b[1]; + float blu = (1.0f-alpha)*(*this)[2] + alpha*b[2]; + float alp = (1.0f-alpha)*(*this)[3] + alpha*b[3]; + return Color(red,grn,blu,alp); +} + +Color Color::Lerp(const Color &a, const Color &b, float alpha) { + float red = (1.0f-alpha)*a[0] + alpha*b[0]; + float grn = (1.0f-alpha)*a[1] + alpha*b[1]; + float blu = (1.0f-alpha)*a[2] + alpha*b[2]; + float alp = (1.0f-alpha)*a[3] + alpha*b[3]; + return Color(red,grn,blu,alp); +} + + +std::ostream & operator<< ( std::ostream &os, const Color &c) { + return os << "(" << c[0] << ", " << c[1] << ", " << c[2] << ", " << c[3] << ")"; +} + +std::istream & operator>> ( std::istream &is, Color &c) { + // format: (r, g, b, a) + char dummy; + return is >> dummy >> c[0] >> dummy >> c[1] >> dummy >> c[2] >> dummy >> c[3] >> dummy; +} + + +} // end namespace diff --git a/dev/MinGfx/src/color.h b/dev/MinGfx/src/color.h new file mode 100644 index 0000000..553dc84 --- /dev/null +++ b/dev/MinGfx/src/color.h @@ -0,0 +1,103 @@ +/* + This file is part of the MinGfx Project. + + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + + Original Author(s) of this File: + Dan Keefe, 2018, University of Minnesota + + Author(s) of Significant Updates/Modifications to the File: + ... + */ + +#ifndef SRC_COLOR_H_ +#define SRC_COLOR_H_ + +#include <iostream> +#include <vector> + +namespace mingfx { + + +/** Represents a 4-component (R,G,B,A) color, stored internally in a float array + to be compatable with OpenGL. Example usage: + ~~~ + Color black(0, 0, 0); + Color white(1, 1, 1); + Color gray(0.5, 0.5, 0.5); + Color tranparent_red(1.0, 0.0, 0.0, 0.5); + Color orange(1.0, 0.65, 0.0); + + float red_component_of_orange = orange[0]; + float green_component_of_orange = orange[1]; + float blue_component_of_orange = orange[2]; + float alpha_component_of_orange = orange[3]; + + // Use value_ptr() to send the array of floats directly to OpenGL commands + glColor4fv(orange.value_ptr()); + ~~~ + */ +class Color { +public: + /// Defaults to black + Color(); + + /// Constructs a color. Alpha defaults to 1.0 (completely opaque) + Color(float red, float green, float blue, float alpha=1.0); + + /// Constructs a point given a pointer to float array + Color(float *p); + + /// Constructs a point given a 3 or 4-element vector of floats + Color(const std::vector<float> &vals); + + /// Copy constructor + Color(const Color& p); + + /// Color destructor + virtual ~Color(); + + /// Check for equality + bool operator==(const Color& p) const; + + /// Check for inequality + bool operator!=(const Color& p) const; + + /// Assignment operator + Color& operator=(const Color& p); + + /// Accesses the ith component of the color, stored in RGBA order. + float operator[](const int i) const; + + /// Accesses the ith coordinate of the color, stored in RGBA order. + float& operator[](const int i); + + /// Returns a const pointer to the raw data array + const float * value_ptr() const; + + std::vector<float> ToVector() const; + + /// Linear interpolation between this color and another. Alpha=0.0 returns + /// this color, and alpha=1.0 returns the other color, other values blend + /// between the two via a linear interpolation on each color channel. + Color Lerp(const Color &b, float alpha) const; + + /// Linear interpolation between two colors. Alpha=0.0 returns 'a' and + /// alpha=1.0 returns 'b', other values blend between the two via a linear + /// interpolation on each color channel. + static Color Lerp(const Color &a, const Color &b, float alpha); + + +private: + float c[4]; +}; + + +std::ostream & operator<< ( std::ostream &os, const Color &c); +std::istream & operator>> ( std::istream &is, Color &c); + + +} // namespace + +#endif diff --git a/dev/MinGfx/src/craft_cam.cc b/dev/MinGfx/src/craft_cam.cc new file mode 100644 index 0000000..425f524 --- /dev/null +++ b/dev/MinGfx/src/craft_cam.cc @@ -0,0 +1,127 @@ +/* + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + See corresponding header file for details. + */ + +#include "craft_cam.h" + +namespace mingfx { + + +CraftCam::CraftCam() : t_scale_(1.0), r_scale_(1.0), l_scale_(1.0), yaw_(0.0), pitch_(0.0) { +} + +CraftCam::CraftCam(const Matrix4 &initialViewMatrix) : + t_scale_(1.0), r_scale_(1.0), l_scale_(1.0), yaw_(0.0), pitch_(0.0) +{ +} + +CraftCam::~CraftCam() +{ +} + + +void CraftCam::UpdateSimulation(double dt, GLFWwindow *window_ptr) { + if ((glfwGetKey(window_ptr, GLFW_KEY_UP) == GLFW_PRESS) || + (glfwGetKey(window_ptr, GLFW_KEY_W) == GLFW_PRESS)) { + WalkForward(dt); + } + if ((glfwGetKey(window_ptr, GLFW_KEY_DOWN) == GLFW_PRESS) || + (glfwGetKey(window_ptr, GLFW_KEY_Z) == GLFW_PRESS)) { + WalkBackward(dt); + } + if ((glfwGetKey(window_ptr, GLFW_KEY_LEFT) == GLFW_PRESS) || + (glfwGetKey(window_ptr, GLFW_KEY_A) == GLFW_PRESS)) { + RotateLeft(dt); + } + if ((glfwGetKey(window_ptr, GLFW_KEY_RIGHT) == GLFW_PRESS) || + (glfwGetKey(window_ptr, GLFW_KEY_S) == GLFW_PRESS)) { + RotateRight(dt); + } +} + + +void CraftCam::OnMouseMove(const Vector2 &normalized_mouse_delta) { + LookWithMouse(normalized_mouse_delta); +} + + + +void CraftCam::WalkForward(double dt) { + base_head_ = Matrix4::Translation(3.0f * (float)dt * t_scale_ * Vector3(0,0,1)) * base_head_; +} + +void CraftCam::WalkBackward(double dt) { + base_head_ = Matrix4::Translation(3.0f * (float)dt * t_scale_ * Vector3(0,0,-1)) * base_head_; +} + +void CraftCam::RotateLeft(double dt) { + base_head_ = Matrix4::RotationY(-0.75f * (float)dt * r_scale_) * base_head_; +} + +void CraftCam::RotateRight(double dt) { + base_head_ = Matrix4::RotationY(0.75f * (float)dt * r_scale_) * base_head_; +} + +void CraftCam::LookWithMouse(const Vector2 &mouse_delta) { + yaw_ += l_scale_ * mouse_delta[0]; + pitch_ += l_scale_ * mouse_delta[1]; + added_rot_ = Matrix4::RotationX(-pitch_) * Matrix4::RotationY(yaw_); +} + + +Matrix4 CraftCam::view_matrix() { + return added_rot_ * base_head_; +} + +void CraftCam::set_view_matrix(Matrix4 view_matrix) { + base_head_ = view_matrix; + added_rot_ = Matrix4(); +} + +Point3 CraftCam::eye() { + Matrix4 camMat = view_matrix().Inverse(); + return camMat.ColumnToPoint3(3); +} + +Vector3 CraftCam::look() { + Matrix4 camMat = view_matrix().Inverse(); + return -camMat.ColumnToVector3(2); +} + + +void CraftCam::set_translation_scale(float s) { + t_scale_ = s; +} + +void CraftCam::set_rotation_scale(float s) { + r_scale_ = s; +} + +void CraftCam::set_look_scale(float s) { + l_scale_ = s; +} + + +float CraftCam::translation_scale() { + return t_scale_; +} + +float CraftCam::rotation_scale() { + return r_scale_; +} + +float CraftCam::look_scale() { + return l_scale_; +} + +void CraftCam::UpdateHeight(float new_y_value) { + Vector3 offset = Vector3(0, new_y_value - eye()[1], 0); + base_head_ = Matrix4::Translation(-offset) * base_head_; +} + + + +} // end namespace + diff --git a/dev/MinGfx/src/craft_cam.h b/dev/MinGfx/src/craft_cam.h new file mode 100644 index 0000000..bdfcd7e --- /dev/null +++ b/dev/MinGfx/src/craft_cam.h @@ -0,0 +1,219 @@ +/* + This file is part of the MinGfx Project. + + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + + Original Author(s) of this File: + Dan Keefe, 2018, University of Minnesota + + Author(s) of Significant Updates/Modifications to the File: + ... + */ + +#ifndef SRC_CRAFT_CAM_H_ +#define SRC_CRAFT_CAM_H_ + +#include "point2.h" +#include "matrix4.h" +#include "vector2.h" + + +namespace mingfx { + + +/** This implements a user interface for controlling the camera with the mouse. + This interface is appropriate for "first person" camera control, as in games + like Minecraft. + + Use the arrow keys or A,S,W,Z keys to move around in the virtual world. + UP/DOWN and W/Z move forward and back. LEFT/RIGHT or A/S rotate your body + to face left or right. You can rotate and tilt your head to look + left/right/up/down by moving the mouse. In Minecraft, that movement happens + whenever you move the mouse, regardless of whether you are holding down a + mouse button, but in some games you want to hold the camera still while you + use the mouse to draw on the screen or do something else. It's possible to + use this interface both ways by calling the MouseMove() function either every + time the mouse moves, or only when the mouse is in a dragging mode. + + Example usage: + ~~~ + // Create a global or member variable in your MyGraphicsApp class: + CraftCam cam_; + + + // If you want to always rotate the view with the mouse, use this: + void MyGraphicsApp::OnMouseMove(const Point2 &pos, const Vector2 &delta) { + Vector2 delta_ndc = PixelsToNormalizedDeviceCoords(pos); + cam_.OnMouseMove(delta_ndc); + } + + // Alternatively, if you want to only rotate the view when the mouse button is + // held down, use this instead. Call cam_.OnMouseMove() in either one function + // or the other, but not both! + // void MyGraphicsApp::OnLeftMouseDrag(const Point2 &pos, const Vector2 &delta) { + // Vector2 delta_ndc = PixelsToNormalizedDeviceCoords(pos); + // cam_.OnMouseMove(delta_ndc); + // } + + // This tells the camera to simulate walking based on the keyboard keys currently + // pressed. You need to pass a pointer to the underlying GLFW window created by + // GraphicsApp. + void MyGraphicsApp::UpdateSimulation(double dt) { + cam_.UpdateSimulation(dt, window()); + } + + void MyGraphicsApp::InitOpenGL() { + cam_.set_view_matrix(Matrix4::lookAt(Point3(0,2,2), Point3(0,2,0), Vector3(0,1,0));); + } + + void MyGraphicsApp::DrawOpenGL() { + // draw your scene using the view matrix from the camera + Matrix4 proj_matrix = Matrix4::perspective(60, aspect_ratio(), 1, 200); + Matrix4 view_matrix = cam_.view_matrix(); + Matrix4 model_matrix = Matrix4::RotateY(to_radians(45.0)); + quick_shapes.DrawCube(model_matrix, view_matirx, proj_matrix, Color(1,1,1)); + } + ~~~ + */ +class CraftCam { +public: + + /// Creates a CraftCam object with an initial view matrix = identity. + CraftCam(); + + /// Creates a CraftCam object with the supplied initial view matrix. + CraftCam(const Matrix4 &initial_view_matrix); + + virtual ~CraftCam(); + + + // To make the interaction work, the following set of functions need to be + // called from your GraphicsApp or whatever main application class you use + // to receive user input events and a draw callback. + + /// Call this from your app's UpdateSimulation() method. This tells the + /// camera to simulate walking based on the keyboard keys currently pressed. + /// You need to pass a pointer to the underlying GLFW window created by + /// GraphicsApp. Example: + /// ~~~ + /// void MyGraphicsApp::UpdateSimulation(double dt) { + /// cam_.UpdateSimulation(dt, window()); + /// } + /// ~~~ + void UpdateSimulation(double dt, GLFWwindow *window_ptr); + + + /// Call this from your app's OnMouseMove() or On*MouseDrag() method. Use + /// OnMouseMove() if you want to always rotate the view with the mouse. + /// Remember to convert the mouse coordinates (usually reported by window + /// managers in pixels) into normalized device coordinates: + /// ~~~ + /// void MyGraphicsApp::OnMouseMove(const Point2 &pos, const Vector2 &delta) { + /// Vector2 delta_ndc = PixelsToNormalizedDeviceCoords(pos); + /// cam_.OnMouseMove(delta_ndc); + /// } + /// ~~~ + /// Alternatively, if you want to only rotate the view when the mouse button is + /// held down, use On*MouseDrag() instead: + /// ~~~ + /// void MyGraphicsApp::OnLeftMouseDrag(const Point2 &pos, const Vector2 &delta) { + /// Vector2 delta_ndc = PixelsToNormalizedDeviceCoords(pos); + /// cam_.OnMouseMove(delta_ndc); + /// } + /// ~~~ + void OnMouseMove(const Vector2 &normalized_mouse_delta); + + + /// Access the camera view matrix created by the CraftCam interactions via + /// this method and use it to draw the geometry in your scence. + /// For example, within GraphicsApp::DrawUsingOpenGL(), you might have: + /// ~~~ + /// Matrix4 P = Matrix4::Perspective(30, aspect_ratio(), 1, 20); + /// Matrix4 V = cam.view_matrix(); + /// Matrix4 M = Matrix4::RotateY(GfxMath::ToRadians(45.0)); + /// quick_shapes.DrawCube(M, V, P, Color(1,1,1)); + /// ~~~ + Matrix4 view_matrix(); + + + /// Returns the "eye" point (i.e., focal point) of the camera in world + /// space coordinates. + Point3 eye(); + + /// Returns the look direction (i.e., -Z axis of the camera matrix) in world + /// space coordinates. + Vector3 look(); + + + /// Sets the y value of the camera (i.e., the height). If you want to set + /// the entire view matrix, then use set_view_matrix(), but if you just want + /// to update the height, e.g., while walking around a bumpy terrain, then + /// use this. + void UpdateHeight(float new_y_value); + + + // ------------- + + /// This is not required, but you may use this if you wish to set an initial + /// view matrix or reset the view matrix + void set_view_matrix(Matrix4 view_matrix); + + + /// This is the scale factor used to speed up / slow down forward/backward + /// translation when walking for the UP / DOWN keys. It defaults to 1.0, + /// smaller values will make the camera walk slower, larger values will + /// speed it up. + float translation_scale(); + + /// This is the scale factor used to speed up / slow down forward/backward + /// translation when walking for the UP / DOWN keys. It defaults to 1.0, + /// smaller values will make the camera walk slower, larger values will + /// speed it up. + void set_translation_scale(float s); + + /// This is the scale factor used to speed up / slow down left/right + /// rotation when walking for the LEFT / RIGHT keys. It defaults to 1.0, + /// smaller values will make the camera turn slower, larger values will + /// speed it up. + float rotation_scale(); + + /// This is the scale factor used to speed up / slow down left/right + /// rotation when walking for the LEFT / RIGHT keys. It defaults to 1.0, + /// smaller values will make the camera turn slower, larger values will + /// speed it up. + void set_rotation_scale(float s); + + /// This is the scale factor used to speed up / slow down looking around + /// when moving the head with the mouse. It defaults to 1.0, smaller values + /// will make the camera turn slower, larger values will speed it up. + float look_scale(); + + /// This is the scale factor used to speed up / slow down looking around + /// when moving the head with the mouse. It defaults to 1.0, smaller values + /// will make the camera turn slower, larger values will speed it up. + void set_look_scale(float s); + +private: + + void WalkForward(double dt); + void WalkBackward(double dt); + void RotateLeft(double dt); + void RotateRight(double dt); + void LookWithMouse(const Vector2 &mouse_delta); + + float t_scale_; + float r_scale_; + float l_scale_; + float yaw_; + float pitch_; + Matrix4 base_head_; + Matrix4 added_rot_; +}; + + +} // end namespace + +#endif + + diff --git a/dev/MinGfx/src/default_shader.cc b/dev/MinGfx/src/default_shader.cc new file mode 100644 index 0000000..63331b8 --- /dev/null +++ b/dev/MinGfx/src/default_shader.cc @@ -0,0 +1,168 @@ +/* + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + See corresponding header file for details. + */ + +#include "default_shader.h" + +#include "platform.h" + + +namespace mingfx { + + DefaultShader::DefaultShader(bool addDefaultLight) { + for (int i = 0; i < MAX_LIGHTS; i++) { + lightPositions_[3 * i + 0] = 0.0f; + lightPositions_[3 * i + 1] = 0.0f; + lightPositions_[3 * i + 2] = 0.0f; + + lightIas_[4 * i + 0] = 0.0f; + lightIas_[4 * i + 1] = 0.0f; + lightIas_[4 * i + 2] = 0.0f; + lightIas_[4 * i + 3] = 0.0f; + + lightIds_[4 * i + 0] = 0.0f; + lightIds_[4 * i + 1] = 0.0f; + lightIds_[4 * i + 2] = 0.0f; + lightIds_[4 * i + 3] = 0.0f; + + lightIss_[4 * i + 0] = 0.0f; + lightIss_[4 * i + 1] = 0.0f; + lightIss_[4 * i + 2] = 0.0f; + lightIss_[4 * i + 3] = 0.0f; + } + + if (addDefaultLight) { + AddLight(LightProperties()); + } + } + + DefaultShader::~DefaultShader() { + + } + + void DefaultShader::AddLight(LightProperties light) { + lights_.push_back(light); + update_light_arrays(); + } + + void DefaultShader::SetLight(int i, LightProperties light) { + lights_[i] = light; + update_light_arrays(); + } + + void DefaultShader::update_light_arrays() { + DefaultShader::LightProperties defaultlight; + + for (int i=0; i<MAX_LIGHTS; i++) { + DefaultShader::LightProperties *light; + if (i < lights_.size()) { + light = &lights_[i]; + } + else { + light = &defaultlight; + } + + lightPositions_[3*i + 0] = light->position[0]; + lightPositions_[3*i + 1] = light->position[1]; + lightPositions_[3*i + 2] = light->position[2]; + + lightIas_[4*i + 0] = light->ambient_intensity[0]; + lightIas_[4*i + 1] = light->ambient_intensity[1]; + lightIas_[4*i + 2] = light->ambient_intensity[2]; + lightIas_[4*i + 3] = light->ambient_intensity[3]; + + lightIds_[4*i + 0] = light->diffuse_intensity[0]; + lightIds_[4*i + 1] = light->diffuse_intensity[1]; + lightIds_[4*i + 2] = light->diffuse_intensity[2]; + lightIds_[4*i + 3] = light->diffuse_intensity[3]; + + lightIss_[4*i + 0] = light->specular_intensity[0]; + lightIss_[4*i + 1] = light->specular_intensity[1]; + lightIss_[4*i + 2] = light->specular_intensity[2]; + lightIss_[4*i + 3] = light->specular_intensity[3]; + } + } + + + int DefaultShader::num_lights() { + return (int)lights_.size(); + } + + DefaultShader::LightProperties DefaultShader::light(int i) { + return lights_[i]; + } + + + void DefaultShader::Init() { + phongShader_.AddVertexShaderFromFile(Platform::FindMinGfxShaderFile("default.vert")); + phongShader_.AddFragmentShaderFromFile(Platform::FindMinGfxShaderFile("default.frag")); + phongShader_.LinkProgram(); + } + + + + void DefaultShader::Draw(const Matrix4 &model, const Matrix4 &view, const Matrix4 &projection, + Mesh *mesh, const MaterialProperties &material) + { + UseProgram(model, view, projection, material); + + // Draw the mesh using the shader program + mesh->Draw(); + + StopProgram(); + } + + + void DefaultShader::UseProgram(const Matrix4 &model, const Matrix4 &view, const Matrix4 &projection, + const MaterialProperties &material) + { + if (!phongShader_.initialized()) { + Init(); + } + + Matrix4 normalMatrix = (view*model).Inverse().Transpose(); + + for (int i=0; i<lights_.size(); i++) { + Point3 light_in_eye_space = view * lights_[i].position; + lightPositions_[3*i + 0] = light_in_eye_space[0]; + lightPositions_[3*i + 1] = light_in_eye_space[1]; + lightPositions_[3*i + 2] = light_in_eye_space[2]; + } + + // Activate the shader program + phongShader_.UseProgram(); + + // Pass uniforms and textures from C++ to the GPU Shader Program + phongShader_.SetUniform("ModelMatrix", model); + phongShader_.SetUniform("ViewMatrix", view); + phongShader_.SetUniform("ProjectionMatrix", projection); + phongShader_.SetUniform("NormalMatrix", normalMatrix); + + phongShader_.SetUniform("NumLights", (int)lights_.size()); + phongShader_.SetUniformArray3("LightPositions", lightPositions_, MAX_LIGHTS); + phongShader_.SetUniformArray4("LightIntensitiesAmbient", lightIas_, MAX_LIGHTS); + phongShader_.SetUniformArray4("LightIntensitiesDiffuse", lightIds_, MAX_LIGHTS); + phongShader_.SetUniformArray4("LightIntensitiesSpecular", lightIss_, MAX_LIGHTS); + + phongShader_.SetUniform("MatReflectanceAmbient", material.ambient_reflectance); + phongShader_.SetUniform("MatReflectanceDiffuse", material.diffuse_reflectance); + phongShader_.SetUniform("MatReflectanceSpecular", material.specular_reflectance); + phongShader_.SetUniform("MatReflectanceShininess", material.shinniness); + phongShader_.SetUniform("UseSurfaceTexture", material.surface_texture.initialized()); + if (material.surface_texture.initialized()) { + phongShader_.BindTexture("SurfaceTexture", material.surface_texture); + } + } + + + void DefaultShader::StopProgram() { + // Deactivate the shader program + phongShader_.StopProgram(); + } + + + +} // end namespace + diff --git a/dev/MinGfx/src/default_shader.h b/dev/MinGfx/src/default_shader.h new file mode 100644 index 0000000..677acae --- /dev/null +++ b/dev/MinGfx/src/default_shader.h @@ -0,0 +1,168 @@ +/* + This file is part of the MinGfx Project. + + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + + Original Author(s) of this File: + Dan Keefe, 2018, University of Minnesota + + Author(s) of Significant Updates/Modifications to the File: + ... + */ + +#ifndef SRC_DEFAULT_SHADER_H_ +#define SRC_DEFAULT_SHADER_H_ + +#include "color.h" +#include "point3.h" +#include "shader_program.h" +#include "texture2d.h" +#include "vector3.h" +#include "matrix4.h" +#include "mesh.h" + + + +namespace mingfx { + +/** A simple GLSL shader for textured per-fragment Phong shading with multiple + light sources. This can be used to draw 3D models stored in a mingfx::Mesh + data structure or you can use it with your own geometry data structures. + Lighting properties are stored within the class itself since these are considered + part of the shading model. Material properties are considered properties of the + meshes or other materials you wish to draw so these are stored outside of the + class and passed into the Draw() or UseProgram() functions. + + An example of using DefaultShader to render a mesh: + ~~~ + DefaultShader phong_shader; + Mesh teapot; + DefaultShader::MaterialProperties teapot_material; + + void Init() { + // initialize the shader + DefaultShader::LightProperties red_light; + red_light.position = Point3(-10, 5, 5); + red_light.diffuseIntensity = Color(1,0,0); + phong_shader.AddLight(red_light); + + // initialize the mesh + teapot.LoadFromOBJ(Platform::FindMinGfxDataFile("teapot.obj")); + } + + void DrawUsingOpenGL() { + Matrix4 M; + Matrix4 V = Matrix4::LookAt(Point3(0,0,3), Point3(0,0,0), Vector3(0,1,0)); + Matrix4 P = Matrix4::Perspective(60.0, aspect_ratio(), 0.1, 10.0); + phong_shader.Draw(M, V, P, teapot, teapot_material); + } + ~~~ + */ +class DefaultShader { +public: + + /// If changed, this needs to also be changed in the glsl shader code + static const unsigned int MAX_LIGHTS = 10; + + + /// Small data structure to hold properties of the material to be lit + class MaterialProperties { + public: + Color ambient_reflectance; + Color diffuse_reflectance; + Color specular_reflectance; + float shinniness; + Texture2D surface_texture; + // eventually, this might include a normal map, etc. + + // defaults + MaterialProperties() : + ambient_reflectance(0.25f, 0.25f, 0.25f), + diffuse_reflectance(0.6f, 0.6f, 0.6f), + specular_reflectance(0.4f, 0.4f, 0.4f), + shinniness(20.0f) {} + }; + + /// Small data structure to hold per-light properties + class LightProperties { + public: + Point3 position; + Color ambient_intensity; + Color diffuse_intensity; + Color specular_intensity; + + // defaults + LightProperties() : + position(10.0f, 10.0f, 10.0f), + ambient_intensity(0.25f, 0.25f, 0.25f), + diffuse_intensity(0.6f, 0.6f, 0.6f), + specular_intensity(0.6f, 0.6f, 0.6f) {} + }; + + /// The constructor defaults to adding a single white light to the scene at + /// (10,10,10). Change this by passing it 'false'. The constructor does + /// not load and compile the shader right away. This is done inside Init(). + DefaultShader(bool add_default_light=true); + + virtual ~DefaultShader(); + + /// Multiple lights are supported, this adds one to the end of the list. + /// Up to MAX_LIGHTS can be added. + void AddLight(LightProperties light); + + /// Changes the properties for a light that was already added. + void SetLight(int i, LightProperties light); + + + /// This loads vertex and fragment shaders from files, compiles them, and + /// links them. So, it must be called from within an active OpenGL context, + /// for example, from within GraphicsApp::Init() or GraphicsApp::DrawUsingOpenGL(). + /// If you call Draw() before calling Init(), then Init() will be called as + /// the first step within Draw(). So, if you do not mind a slowdown on the + /// very first frame of your program, it is fine to skip calling Init(). + void Init(); + + /// This starts the shader and sets its uniform variables based upon the + /// current set of lights, the material properties passed in, and the + /// model, view, and projection matrices. Then, it calls mesh->Draw(). + /// After drawing, it disables the shader. + void Draw(const Matrix4 &model, const Matrix4 &view, const Matrix4 &projection, + Mesh *mesh, const MaterialProperties &material); + + + /// Only needed if you do not want to draw a Mesh. + /// This does all of the same setup for drawing that the Draw() function does + /// and then it returns so that you may draw your own geometry however you want. + /// After doing your draw must call StopProgram() to turn off the shader. + void UseProgram(const Matrix4 &model, const Matrix4 &view, const Matrix4 &projection, + const MaterialProperties &material); + + /// Only needed if you do not want to draw a Mesh. Call this after UseProgram() + /// and after drawing your geometry to turn off the shader. + void StopProgram(); + + + int num_lights(); + + LightProperties light(int i); + + +private: + + std::vector<LightProperties> lights_; + + // cached raw float arrays store data to send directly to the gpu + // GLSL requires fixed size arrays for these + float lightPositions_[3*MAX_LIGHTS]; + float lightIas_[4*MAX_LIGHTS]; + float lightIds_[4*MAX_LIGHTS]; + float lightIss_[4*MAX_LIGHTS]; + void update_light_arrays(); + + ShaderProgram phongShader_; +}; + +} // end namespace + +#endif
\ No newline at end of file diff --git a/dev/MinGfx/src/gfxmath.cc b/dev/MinGfx/src/gfxmath.cc new file mode 100644 index 0000000..11180e6 --- /dev/null +++ b/dev/MinGfx/src/gfxmath.cc @@ -0,0 +1,82 @@ +/* + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + See corresponding header file for details. + */ + +#include "gfxmath.h" + +#define _USE_MATH_DEFINES +#include <math.h> +#include <algorithm> + +#include "ray.h" + + +namespace mingfx { + +const float GfxMath::PI = 3.14159265359f; +const float GfxMath::TWO_PI = 6.28318530718f; +const float GfxMath::HALF_PI = 1.57079632679f; + + +float GfxMath::Clamp(float x, float a, float b) { + return std::min(std::max(x, a), b); +} + +float GfxMath::ToRadians(float degrees) { + return degrees * GfxMath::PI / 180.0f; +} + +float GfxMath::ToDegrees(float radians) { + return radians * 180.0f / GfxMath::PI; +} + +Vector3 GfxMath::ToRadians(Vector3 degrees) { + return Vector3(ToRadians(degrees[0]), ToRadians(degrees[1]), ToRadians(degrees[2])); +} + +Vector3 GfxMath::ToDegrees(Vector3 radians) { + return Vector3(ToDegrees(radians[0]), ToDegrees(radians[1]), ToDegrees(radians[2])); +} + +float GfxMath::Lerp(float a, float b, float alpha) { + return (1.0f-alpha)*a + alpha*b; +} + +int GfxMath::iLerp(int a, int b, float alpha) { + return (int)std::round((1.0f-alpha)*(float)a + alpha*(float)b); +} + +Point3 GfxMath::ScreenToNearPlane(const Matrix4 &V, const Matrix4 &P, const Point2 &ndcPoint) { + Matrix4 filmPtToWorld = (P*V).Inverse(); + return filmPtToWorld * Point3(ndcPoint[0], ndcPoint[1], -1.0); +} + + +Point3 GfxMath::ScreenToWorld(const Matrix4 &V, const Matrix4 &P, const Point2 &ndcPoint, float zValue) { + Matrix4 filmPtToWorld = (P*V).Inverse(); + float zneg1topos1 = zValue*2.0f - 1.0f; + return filmPtToWorld * Point3(ndcPoint[0], ndcPoint[1], zneg1topos1); +} + + +Point3 GfxMath::ScreenToDepthPlane(const Matrix4 &V, const Matrix4 &P, const Point2 &ndcPoint, float planeDepth) { + Point3 pNear = ScreenToNearPlane(V, P, ndcPoint); + + Matrix4 camMat = V.Inverse(); + Point3 eye = camMat.ColumnToPoint3(3); + Vector3 look = -camMat.ColumnToVector3(2); + + Ray r(eye, pNear - eye); + + Point3 p3D; + float t; + if (!r.IntersectPlane(eye + planeDepth*look, -look, &t, &p3D)) { + std::cerr << "filmplane2D_to_plane3D() error -- no intersection found!" << std::endl; + } + return p3D; +} + + +} // end namespace diff --git a/dev/MinGfx/src/gfxmath.h b/dev/MinGfx/src/gfxmath.h new file mode 100644 index 0000000..09a3a1d --- /dev/null +++ b/dev/MinGfx/src/gfxmath.h @@ -0,0 +1,81 @@ +/* + This file is part of the MinGfx Project. + + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + + Original Author(s) of this File: + Dan Keefe, 2018, University of Minnesota + + Author(s) of Significant Updates/Modifications to the File: + ... + */ + +#ifndef SRC_GFXMATH_H_ +#define SRC_GFXMATH_H_ + +#include "point2.h" +#include "point3.h" +#include "vector3.h" +#include "matrix4.h" + +namespace mingfx { + + +/** This class holds a variety of static math functions that are useful to have + defined with creating graphics programs. + */ +class GfxMath { +public: + + /// Returns a if x is less than a and b if x is greater than b. + static float Clamp(float x, float a, float b); + + static float ToRadians(float degrees); + + static float ToDegrees(float radians); + + static Vector3 ToRadians(Vector3 degrees); + + static Vector3 ToDegrees(Vector3 radians); + + static float Lerp(float a, float b, float alpha); + + static int iLerp(int a, int b, float alpha); + + /// Converts a 2D point on the filmplane represented in Normalized Device + /// Coorindates, which means (-1,1) for the top left corner of the screen and + /// (1,-1) for the bottom right corner, to a 3D point that lies on the camera's + /// near plane. Useful for converting mouse coordinates into a 3D point. + /// Remember that this uses NORMALIZED device coordinates for the screenPt, + /// not pixels. GraphicsApp and most other graphics engines report mouse move + /// events in pixels, so you need to convert these to normalized device coordinates + /// first. If you are using GraphicsApp, you can do this with: + /// Point2 normPos = graphicsApp->pixels_to_normalized_coordinates(mousePos); + static Point3 ScreenToNearPlane(const Matrix4 &viewMatrix, const Matrix4 &projMatrix, const Point2 &normalizedScreenPt); + + /// Similar to filmplane2D_to_nearplane3D() but here rather than using the + /// nearplane, you specify the depth of the plane to use as a distance away + /// from the camera's focal point. + static Point3 ScreenToDepthPlane(const Matrix4 &viewMatrix, const Matrix4 &projMatrix, const Point2 &normalizedScreenPt, float planeDepth); + + /// Converts a 2D point on the filmplane represented in Normalized Device + /// Coorindates, which means (-1,1) for the top left corner of the screen and + /// (1,-1) for the bottom right corner, to a 3D point in the world. The depth + /// buffer value under the pixel must be supplied. If you are using GraphicsApp, + /// you can use the mouse pos in pixels to get the required arguments like this: + /// Point2 normPos = graphicsApp->pixels_to_normalized_coordinates(mousePos); + /// float normZ = graphicsApp->z_value_at_pixel(mousePos); + static Point3 ScreenToWorld(const Matrix4 &viewMatrix, const Matrix4 &projMatrix, const Point2 &normalizedScreenPt, float normalizedZ); + + + static const float PI; + static const float TWO_PI; + static const float HALF_PI; +}; + + + +} // end namespace + +#endif
\ No newline at end of file diff --git a/dev/MinGfx/src/graphics_app.cc b/dev/MinGfx/src/graphics_app.cc new file mode 100644 index 0000000..57405f5 --- /dev/null +++ b/dev/MinGfx/src/graphics_app.cc @@ -0,0 +1,467 @@ +/* + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + See corresponding header file for details. + */ + +#include "graphics_app.h" + + +namespace mingfx { + + + +GraphicsApp::GraphicsApp(int width, int height, const std::string &caption) : + graphicsInitialized_(false), width_(width), height_(height), caption_(caption), lastDrawT_(0.0), + leftDown_(false), middleDown_(false), rightDown_(false), screen_(NULL), window_(NULL) +{ +} + +GraphicsApp::~GraphicsApp() { +} + +void GraphicsApp::InitGraphicsContext() { + + glfwInit(); + + glfwSetTime(0); + + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + + glfwWindowHint(GLFW_SAMPLES, 0); + glfwWindowHint(GLFW_RED_BITS, 8); + glfwWindowHint(GLFW_GREEN_BITS, 8); + glfwWindowHint(GLFW_BLUE_BITS, 8); + glfwWindowHint(GLFW_ALPHA_BITS, 8); + glfwWindowHint(GLFW_STENCIL_BITS, 8); + glfwWindowHint(GLFW_DEPTH_BITS, 24); + glfwWindowHint(GLFW_RESIZABLE, GL_TRUE); + + + // on OSX, glfwCreateWindow bombs if we pass caption_.c_str() in for the 3rd + // parameter perhaps because it's const. so, copying to a tmp char array here. + char *title = new char[caption_.size() + 1]; + for (int i = 0; i < caption_.size(); i++) { + title[i] = caption_[i]; + } + title[caption_.size()] = '\0'; + std::cout << caption_ << std::endl; + + // Create a GLFWwindow object + window_ = glfwCreateWindow(width_, height_, title, NULL, NULL); + if (window_ == nullptr) { + std::cerr << "Failed to create GLFW window" << std::endl; + glfwTerminate(); + exit(-1); + } + glfwMakeContextCurrent(window_); + glfwSetWindowUserPointer(window_, this); + + // cleanup tmp title hack + delete [] title; + + +#if defined(NANOGUI_GLAD) + if (!gladLoadGLLoader((GLADloadproc) glfwGetProcAddress)) + throw std::runtime_error("Could not initialize GLAD!"); + glGetError(); // pull and ignore unhandled errors like GL_INVALID_ENUM +#endif + + glClearColor(0.2f, 0.25f, 0.3f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + + // Create a nanogui screen and pass the glfw pointer to initialize + screen_ = new nanogui::Screen(); + screen_->initialize(window_, true); + + glfwGetFramebufferSize(window_, &width_, &height_); + glViewport(0, 0, width_, height_); + glfwSwapInterval(0); + glfwSwapBuffers(window_); + + screen_->setVisible(true); + screen_->performLayout(); + + glfwSetCursorPosCallback(window_, + [](GLFWwindow *window, double x, double y) { + GraphicsApp *app = (GraphicsApp*)glfwGetWindowUserPointer(window); + app->cursor_pos_glfw_cb(x, y); + } + ); + + glfwSetMouseButtonCallback(window_, + [](GLFWwindow *window, int button, int action, int modifiers) { + GraphicsApp *app = (GraphicsApp*)glfwGetWindowUserPointer(window); + app->mouse_button_glfw_cb(button, action, modifiers); + } + ); + + glfwSetKeyCallback(window_, + [](GLFWwindow *window, int key, int scancode, int action, int mods) { + GraphicsApp *app = (GraphicsApp*)glfwGetWindowUserPointer(window); + app->key_glfw_cb(key, scancode, action, mods); + } + ); + + glfwSetCharCallback(window_, + [](GLFWwindow *window, unsigned int codepoint) { + GraphicsApp *app = (GraphicsApp*)glfwGetWindowUserPointer(window); + app->char_glfw_cb(codepoint); + } + ); + + glfwSetDropCallback(window_, + [](GLFWwindow *window, int count, const char **filenames) { + GraphicsApp *app = (GraphicsApp*)glfwGetWindowUserPointer(window); + app->drop_glfw_cb(count, filenames); + } + ); + + glfwSetScrollCallback(window_, + [](GLFWwindow *window, double x, double y) { + GraphicsApp *app = (GraphicsApp*)glfwGetWindowUserPointer(window); + app->scroll_glfw_cb(x, y); + } + ); + + glfwSetFramebufferSizeCallback(window_, + [](GLFWwindow *window, int width, int height) { + GraphicsApp *app = (GraphicsApp*)glfwGetWindowUserPointer(window); + app->resize_glfw_cb(width, height); + } + ); + + graphicsInitialized_ = true; + } + + + +void GraphicsApp::Run() { + + if (!graphicsInitialized_) { + InitGraphicsContext(); + } + + InitNanoGUI(); + + InitOpenGL(); + + // Main program loop + glfwSetTime(0.0); + while (!glfwWindowShouldClose(window_)) { + + // Poll for new user input events and call callbacks + glfwPollEvents(); + + // Update the simulation, i.e., perform all non-graphics updates that + // should happen each frame. + double now = glfwGetTime(); + UpdateSimulation(now-lastDrawT_); + lastDrawT_ = now; + + // Clear is handled in this mainloop so that drawing works even for + // users who do not want to fill in DrawUsingOpenGL() + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + + // NanoGUI sets these to something other than the OpenGL defaults, which + // screws up most OpenGL programs, so we need to reset them each frame here. + glEnable(GL_CULL_FACE); + glCullFace(GL_BACK); + glEnable(GL_DEPTH_TEST); + + // Users may fill this in to do raw OpenGL rendering + DrawUsingOpenGL(); + + // This renders the nanogui widgets created on screen_ + screen_->drawContents(); + screen_->drawWidgets(); + + // Users may fill this in to do additional 2D rendering with the NanoVG library + DrawUsingNanoVG(screen_->nvgContext()); + + glfwSwapBuffers(window_); + } + + glfwTerminate(); +} + + + +bool GraphicsApp::cursor_pos_glfw_cb(double x, double y) { + + if (screen_->cursorPosCallbackEvent(x,y)) { + // event was handled by nanogui + lastMouse_ = Point2((float)x, (float)y); + return true; + } + else { + Point2 cur((float)x, (float)y); + Vector2 delta = cur - lastMouse_; + + // if no buttons are down, generate a mouse move event + if (!leftDown_ && !middleDown_ && !rightDown_) { + mouse_move(cur, delta); + } + + // if a button is down, generate a corresponding mouse drag event + if (leftDown_) { + left_mouse_drag(cur, delta); + } + if (middleDown_) { + middle_mouse_drag(cur, delta); + } + if (rightDown_) { + right_mouse_drag(cur, delta); + } + + lastMouse_ = cur; + return false; + } +} + +bool GraphicsApp::mouse_button_glfw_cb(int button, int action, int modifiers) { + if (screen_->mouseButtonCallbackEvent(button, action, modifiers)) { + return true; + } + else if (button == GLFW_MOUSE_BUTTON_LEFT) { + double x,y; + glfwGetCursorPos(window_, &x, &y); + if (action == 1) { + left_mouse_down(Point2((float)x, (float)y)); + leftDown_ = true; + } + else { + left_mouse_up(Point2((float)x, (float)y)); + leftDown_ = false; + } + return true; + } + else if (button == GLFW_MOUSE_BUTTON_MIDDLE) { + double x,y; + glfwGetCursorPos(window_, &x, &y); + if (action == 1) { + middle_mouse_down(Point2((float)x, (float)y)); + middleDown_ = true; + } + else { + middle_mouse_up(Point2((float)x, (float)y)); + middleDown_ = false; + } + return true; + } + else if (button == GLFW_MOUSE_BUTTON_RIGHT) { + double x,y; + glfwGetCursorPos(window_, &x, &y); + if (action == 1) { + right_mouse_down(Point2((float)x, (float)y)); + rightDown_ = true; + } + else { + right_mouse_up(Point2((float)x, (float)y)); + rightDown_ = false; + } + return true; + } + return false; +} + + +bool GraphicsApp::key_glfw_cb(int key, int scancode, int action, int modifiers) { + if (screen_->keyCallbackEvent(key, scancode, action, modifiers)) { + return true; + } + else { + if (glfwGetKeyName(key, scancode) != NULL) { + if (action == GLFW_PRESS) { + key_down(glfwGetKeyName(key, scancode), modifiers); + } + else if (action == GLFW_REPEAT) { + key_repeat(glfwGetKeyName(key, scancode), modifiers); + } + else { + key_up(glfwGetKeyName(key, scancode), modifiers); + } + return true; + } + else { + if (action == GLFW_PRESS) { + special_key_down(key, scancode, modifiers); + } + else if (action == GLFW_REPEAT) { + special_key_repeat(key, scancode, modifiers); + } + else { + special_key_up(key, scancode, modifiers); + } + return true; + } + } + + return false; +} + + +bool GraphicsApp::char_glfw_cb(unsigned int codepoint) { + if (screen_->charCallbackEvent(codepoint)) { + return true; + } + else { + // TODO: could add another virtual function to GraphicsApp to + // respond to this if needed + } + return false; +} + + +bool GraphicsApp::drop_glfw_cb(int count, const char **filenames) { + if (screen_->dropCallbackEvent(count, filenames)) { + return true; + } + else { + // TODO: could add another virtual function to GraphicsApp to + // respond to this if needed + } + return false; +} + + +bool GraphicsApp::scroll_glfw_cb(double x, double y) { + if (screen_->scrollCallbackEvent(x,y)) { + return true; + } + else { + // TODO: could add another virtual function to GraphicsApp to + // respond to this if needed + } + return false; +} + + +bool GraphicsApp::resize_glfw_cb(int width, int height) { + if (screen_->resizeCallbackEvent(width, height)) { + return true; + } + else { + // the width and height reported here are the new framebuffer size + // we will query/save/report the new window size instead + width_ = window_width(); + height_ = window_height(); + OnWindowResize(width_, height_); + } + return false; +} + + +bool GraphicsApp::IsKeyDown(int key) { + return (glfwGetKey(window_, key) == GLFW_PRESS); +} + +bool GraphicsApp::IsLeftMouseDown() { + return (glfwGetMouseButton(window_, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS); +} + + +bool GraphicsApp::IsMiddleMouseDown() { + return (glfwGetMouseButton(window_, GLFW_MOUSE_BUTTON_MIDDLE) == GLFW_PRESS); +} + + +bool GraphicsApp::IsRightMouseDown() { + return (glfwGetMouseButton(window_, GLFW_MOUSE_BUTTON_RIGHT) == GLFW_PRESS); +} + + + +float GraphicsApp::aspect_ratio() { + int width, height; + glfwGetFramebufferSize(window_, &width, &height); + return (float)width/(float)height; +} + +int GraphicsApp::window_width() { + int width, height; + glfwGetWindowSize(window_, &width, &height); + return width; +} + +int GraphicsApp::framebuffer_width() { + int width, height; + glfwGetFramebufferSize(window_, &width, &height); + return width; +} + +int GraphicsApp::window_height() { + int width, height; + glfwGetWindowSize(window_, &width, &height); + return height; +} + +int GraphicsApp::framebuffer_height() { + int width, height; + glfwGetFramebufferSize(window_, &width, &height); + return height; +} + +Point2 GraphicsApp::PixelsToNormalizedDeviceCoords(const Point2 &pointInPixels) { + float x = (pointInPixels[0] / window_width()) * 2.0f - 1.0f; + float y = (1.0f - (pointInPixels[1] / window_height())) * 2.0f - 1.0f; + return Point2(x,y); +} + +Point2 GraphicsApp::NormalizedDeviceCoordsToPixels(const Point2 &pointInNDC) { + float x = 0.5f * (pointInNDC[0] + 1.0f) * window_width(); + float y = (1.0f - (0.5f * (pointInNDC[1] + 1.0f))) * window_height(); + return Point2(x,y); +} + +Vector2 GraphicsApp::PixelsToNormalizedDeviceCoords(const Vector2 &vectorInPixels) { + float x = (2.0f/window_width()) * vectorInPixels[0]; + float y = (-2.0f/window_height()) * vectorInPixels[1]; + return Vector2(x,y); +} + +Vector2 GraphicsApp::NormalizedDeviceCoordsToPixels(const Vector2 &vectorInNDC) { + float x = (window_width()/2.0f) * vectorInNDC[0]; + float y = (-window_height()/2.0f) * vectorInNDC[1]; + return Vector2(x,y); +} + +float GraphicsApp::ReadZValueAtPixel(const Point2 &pointInPixels, unsigned int whichBuffer) { + // scale screen points to framebuffer size, since they are not the same on retina displays + float x01 = pointInPixels[0] / window_width(); + float y01 = pointInPixels[1] / window_height(); + y01 = 1.0f - y01; + + float x = x01 * (float)framebuffer_width(); + float y = y01 * (float)framebuffer_height(); + + float z; + glReadPixels((int)x, (int)y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &z); + return z; +} + + +nanogui::Screen* GraphicsApp::screen() { + return screen_; +} + +GLFWwindow* GraphicsApp::window() { + return window_; +} + + +void GraphicsApp::ResizeWindow(int new_width, int new_height) { + glfwSetWindowSize(window_, new_width, new_height); + width_ = new_width; + height_ = new_height; + OnWindowResize(new_width, new_height); +} + + + + + +} // end namespace diff --git a/dev/MinGfx/src/graphics_app.h b/dev/MinGfx/src/graphics_app.h new file mode 100644 index 0000000..c32be2c --- /dev/null +++ b/dev/MinGfx/src/graphics_app.h @@ -0,0 +1,481 @@ +/* + This file is part of the MinGfx Project. + + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + + Original Author(s) of this File: + Dan Keefe, 2017, University of Minnesota + + Author(s) of Significant Updates/Modifications to the File: + ... + */ + + +#ifndef SRC_GRAPHICS_APP_H_ +#define SRC_GRAPHICS_APP_H_ + +// disable warnings for this 3rd party code +#pragma warning ( push, 0 ) +#include <nanogui/nanogui.h> +#pragma warning ( pop ) + +#include <iostream> + +#include "point2.h" +#include "vector2.h" + +/** Namespace for the MinGfx Toolkit */ +namespace mingfx { + + +/** This is the main application base class for the MinGfx Toolkit. + + _Create a Subclass:_ + + To create your own graphics application, you should create a subclass of GraphicsApp + and then override some key functions: + + 1. User Input: To get input from the keyboard and mouse, override OnMouseMove() and/or the other On...() functions. + + 2. Drawing Graphics: To draw graphics override one or more of the Draw*() functions. + + - DrawUsingNanoVG() is the right place to make 2D drawing calls using the nanovg library. + + - DrawUsingOpenGL() is the right place to make 2D or 3D drawing calls using OpenGL. This includes drawing using the Mesh, QuickShapes, DefaultShader, ShaderProgram, and all other MinGfx classes since these are all based on OpenGL. + + - InitNanoGUI() is the right place to create nanogui windows to add a 2D user interface to your app. + + - InitOpenGL() is the right place to load textures, meshes, shaders, and other graphics objects that can only be created after the OpenGL context exists. + + 3. Physics, Animation, AI, etc.: Override the UpdateSimulation() function to do other non-graphics calculations required by your program. This is called automatically once per frame. + + Keep in mind that internally the app uses a rendering loop that looks something like this: + ~~~ + InitNanoGUI(); // your hook for initializing NanoGUI widgets + InitOpenGL(); // your hook for initializing OpenGL graphics + while (!program_ready_to_close) { + // user input + internal_get_input_events_from_operating_system(); + OnMouseMove(); // your hook for processing input + On*(); // all other event callbacks -- your hook for processing input + + // phyics, etc. + UpdateSimulation(); // your hook for physics, animation, AI, etc. + + // draw graphics + internal_render_gui_elements_using_nanogui(); + DrawUsingNanoVG(); // your hook for drawing 2D vector graphics + DrawUsingOpenGL(); // your hook for 2D/3D rendering with OpenGL + } + ~~~ + + _A Complete Example with GUI Widgets_ + + If you wish to add some buttons, sliders, etc. in your application, you can do this inside GraphicsApp by accessing the NanoGUI library. You will need to pass NanoGUI a nanogui::screen object, which you can get from the screen() function. NanoGui setup should be done in the constructor, like this: + ~~~ + #include <mingfx.h> + using namespace mingfx; + + class MyApp : public GraphcisApp { + public: + MyApp() : GraphicsApp(1024,768, "My Amazing App") { + } + + virtual ~MyApp() {} + + void InitNanoGUI() { + // Setup the GUI window + nanogui::Window *window = new nanogui::Window(screen(), "My GUI Panel"); + window->setPosition(Eigen::Vector2i(10, 10)); + window->setSize(Eigen::Vector2i(400,200)); + window->setLayout(new nanogui::GroupLayout()); + + nanogui::Button pause_btn = new nanogui::Button(window, "Pause"); + pause_btn->setCallback(std::bind(&MyApp::OnPauseBtnPressed, this)); + pause_btn->setTooltip("Toggle playback."); + + screen()->performLayout(); + } + + void InitOpenGL() { + glClearColor(0.0, 0.0, 0.0, 1); + } + + // this callback is for the nanogui pause_btn defined above + void OnPauseBtnPressed() { + std::cout << "Pause pressed." << std::endl; + } + + // this callback is built into the base GraphicsApp class + void OnMouseMove(const Point2 &pos, const Vector2 &delta) { + std::cout << "Mouse moved to " << pos << std::endl; + } + + void DrawUsingOpenGL() { + Matrix4 model = Matrix4::Translation(Vector3(-1,0,0)) * Matrix4::Scale(Vector3(0.5, 0.5, 0.5)); + Matrix4 view = Matrix4::LookAt(Point3(0,0,3), Point3(0,0,0), Vector3(0,1,0)); + Matrix4 proj = Matrix4::Perspective(60.0, aspect_ratio(), 0.1, 10.0); + quick_shapes_.DrawCube(model, view, proj, Color(1,1,1)); + } + + private: + QuickShapes quick_shapes_; + }; + + + int main(int argc, const char *argv[]) { + MyApp app; + app.Run(); + return 0; + } + ~~~ + + */ +class GraphicsApp { +public: + + /** \brief Constructs a new app but does not yet run it. + + \param width The width of the client area of the window in pixels. + \param height The height of the client area of the window in pixels. + \param caption The caption for the window's title bar. + */ + GraphicsApp(int width, int height, const std::string &caption); + + + /// The destructor will shutdown the graphics system and window + virtual ~GraphicsApp(); + + + // Callback methods -- override these and fill in to respond to user + // input events. + + /** If the mouse has moved in the past frame and no mouse buttons are currently + pressed, then this callback function will be called to report the new position + of the mouse to you. + + \param pos This is the current position of the mouse in pixels, where (0,0) + is at the top left corner of the screen and (window_width(), window_height()) + is the bottom right corner. + + \param delta This is the change in the position of the mouse in pixels since + the last frame. + */ + virtual void OnMouseMove(const Point2 &pos, const Vector2 &delta) {} + + /** If the mouse button was pressed down since the last frame, then this function + will be called to notify you. + + \param pos This is the current position of the mouse in pixels, where (0,0) + is at the top left corner of the screen and (window_width(), window_height()) + is the bottom right corner. + */ + virtual void OnLeftMouseDown(const Point2 &pos) {} + + /** If the mouse button is held down and the mouse has moved in the past frame + then this function will be called to tell you that a "dragging" operation is + happening. + + \param pos This is the current position of the mouse in pixels, where (0,0) + is at the top left corner of the screen and (window_width(), window_height()) + is the bottom right corner. + + \param delta This is the change in the position of the mouse in pixels since + the last frame. + */ + virtual void OnLeftMouseDrag(const Point2 &pos, const Vector2 &delta) {} + + /** If the mouse button was released since the last frame, then this function + will be called to notify you. + + \param pos This is the current position of the mouse in pixels, where (0,0) + is at the top left corner of the screen and (window_width()-1, window_height()-1) + is the bottom right corner. + */ + virtual void OnLeftMouseUp(const Point2 &pos) {} + + + /// \copydoc GraphicsApp::OnLeftMouseDown(const Point2 &pos) + virtual void OnMiddleMouseDown(const Point2 &pos) {} + + /// \copydoc GraphicsApp::OnLeftMouseDrag(const Point2 &pos, const Vector2 &delta) + virtual void OnMiddleMouseDrag(const Point2 &pos, const Vector2 &delta) {} + + /// \copydoc GraphicsApp::OnLeftMouseUp(const Point2 &pos) + virtual void OnMiddleMouseUp(const Point2 &pos) {} + + + /// \copydoc GraphicsApp::OnLeftMouseDown(const Point2 &pos) + virtual void OnRightMouseDown(const Point2 &pos) {} + + /// \copydoc GraphicsApp::OnLeftMouseDrag(const Point2 &pos, const Vector2 &delta) + virtual void OnRightMouseDrag(const Point2 &pos, const Vector2 &delta) {} + + /// \copydoc GraphicsApp::OnLeftMouseUp(const Point2 &pos) + virtual void OnRightMouseUp(const Point2 &pos) {} + + + /** Transforms a keyboard down event into the actual character typed. + \param c The character for the key that was pressed. + \param modifiers If any modifiers (Alt, Ctrl, Shift, etc.) were held + at the same time, then these are encoded in this int. See the detailed + description here: http://www.glfw.org/docs/latest/group__mods.html + */ + virtual void OnKeyDown(const char *c, int modifiers) {} + + /** Transforms a keyboard repeat event into the actual character typed. + \param c The character for the key that was pressed. + \param modifiers If any modifiers (Alt, Ctrl, Shift, etc.) were held + at the same time, then these are encoded in this int. See the detailed + description here: http://www.glfw.org/docs/latest/group__mods.html + */ + virtual void OnKeyRepeat(const char *c, int modifiers) {} + + /** Transforms a keyboard up event into the actual character typed. + \param c The character for the key that was pressed. + \param modifiers If any modifiers (Alt, Ctrl, Shift, etc.) were held + at the same time, then these are encoded in this int. See the detailed + description here: http://www.glfw.org/docs/latest/group__mods.html + */ + virtual void OnKeyUp(const char *c, int modifiers) {} + + + + /// The values for key, scancode, and modifiers are documented here: + /// http://www.glfw.org/docs/latest/group__keys.html + virtual void OnSpecialKeyDown(int key, int scancode, int modifiers) {} + + /// The values for key, scancode, and modifiers are documented here: + /// http://www.glfw.org/docs/latest/group__keys.html + virtual void OnSpecialKeyRepeat(int key, int scancode, int modifiers) {} + + /// The values for key, scancode, and modifiers are documented here: + /// http://www.glfw.org/docs/latest/group__keys.html + virtual void OnSpecialKeyUp(int key, int scancode, int modifiers) {} + + + /// Override this to respond when the graphics window and/or framebuffer + /// are resized, either by the user dragging the window or through a call + /// to ResizeWindow(). + virtual void OnWindowResize(int new_width, int new_height) {} + + + /** After creating a new GraphicsApp, call this to start the app's + mainloop. Each time through the mainloop the app will: 1. respond + any user input events by calling the On*() callback methods, 2. call + UpdateSimulation(), and 3. call the two Draw*() methods. Note that + Run() does not return until the user closes the app and the program + is ready to shutdown. + */ + virtual void Run(); + + + /** Called at the beginning of the Run() method. Override this to initialize + any NanoGUI graphics related properties including 2D windows, buttons, + sliders, etc... + + IMPORTANT: Put any NanoGUI initialization code here, NOT in the constructors + of the classes that you create, or, create your classes from within this + function. The graphics calls will fail if the OpenGL context has not yet + been initialized, and it is not guaranteed to be initialized until this + function has been called. + */ + virtual void InitNanoGUI() {} + + /** Override this to initialize the OpenGL context with textures, vertex + buffers, etc. that you will use later inside DrawUsingOpenGL(). This + InitOpenGL() function is called once on program startup just after the + OpenGL drawing context is created. + + IMPORTANT: Put any OpenGL initialization code here, NOT in the constructors + of the classes that you create, or, create your classes from within this + function. The graphics calls will fail if the OpenGL context has not yet + been initialized, and it is not guaranteed to be initialized until this + function has been called. + */ + virtual void InitOpenGL() {} + + + /** Called once per frame. Override this and fill it in to update your + simulation code or any other updates you need to make to your model that + are timed rather than in response to user input. + + \param dt is the elapsed time since the last call. + */ + virtual void UpdateSimulation(double dt) {} + + + /// Override this to draw graphics using the nanovg vector graphics + /// library, which provides an easy way to draw 2D shapes to the screen. + virtual void DrawUsingNanoVG(NVGcontext *ctx) {} + + + + /// Override this to draw graphics using raw OpenGL 2D or 3D graphics + /// calls. + virtual void DrawUsingOpenGL() {} + + + /// True if the specified is is currently held down. Uses the GLFW + /// key codes found here: http://www.glfw.org/docs/latest/group__keys.html + virtual bool IsKeyDown(int key); + + /// True if the left mouse button is currently held down. + virtual bool IsLeftMouseDown(); + + /// True if the middle mouse button is currently held down. + virtual bool IsMiddleMouseDown(); + + /// True if the right mouse button is currently held down. + virtual bool IsRightMouseDown(); + + /// Returns the current width of the client area of the window in pixels + virtual int window_width(); + + /// Returns the current height of the client area of the window in pixels + virtual int window_height(); + + /** Returns the current width of the framebuffer in pixels. Note that on + some displays (e.g., Mac Retina) the framebuffer is larger than the + window. + */ + virtual int framebuffer_width(); + + /** Returns the current height of the framebuffer in pixels. Note that on + some displays (e.g., Mac Retina) the framebuffer is larger than the + window. + */ + virtual int framebuffer_height(); + + /// Returns width/height for the current shape of the window + virtual float aspect_ratio(); + + + /** Transforms a point in viewport coordinates (pixels where top left = (0,0) + and bottom right = (window_width()-1, window_height()-1)) to normalized + device coordinates, (top left = (-1,1) bottom right (1,-1)). + */ + virtual Point2 PixelsToNormalizedDeviceCoords(const Point2 &pointInPixels); + + /** Transforms a point in normalized device coordinates (top left = (-1,1) + bottom right (1,-1)) to pixels (top left = (0,0), bottom right = + (window width-1, window height-1)) + */ + virtual Point2 NormalizedDeviceCoordsToPixels(const Point2 &pointInNDC); + + + /** Transforms a vector in viewport coordinates (pixels where top left = (0,0) + and bottom right = (window width-1, window height-1)) to normalized + device coordinates, (top left = (-1,1) bottom right (1,-1)). + */ + virtual Vector2 PixelsToNormalizedDeviceCoords(const Vector2 &vectorInPixels); + + /** Transforms a vector in normalized device coordinates (top left = (-1,1) + bottom right (1,-1)) to pixels (top left = (0,0), bottom right = + (window width-1, window height-1)) + */ + virtual Vector2 NormalizedDeviceCoordsToPixels(const Vector2 &pointInNDC); + + /// Returns the z buffer value under the specified pixel. z will be 0 at + /// the near plane and +1 at the far plane. + virtual float ReadZValueAtPixel(const Point2 &pointInPixels, unsigned int whichBuffer = GL_BACK); + + /// Access to the underlying NanoGUI Screen object + virtual nanogui::Screen* screen(); + + /// Access to the underlying GLFWwindow object + virtual GLFWwindow* window(); + + + /// Cause the graphics windows to resize programmatically rather than by dragging + /// on the corner manually. + virtual void ResizeWindow(int new_width, int new_height); + + + /** Users cannot make any graphics calls (e.g., setting the clear color, + saving mesh data to the GPU) until the graphics context is initialized + by calling this method. It is called automatically by the Run() method + before calling the InitNanoGUI() and InitOpenGL() methods. So, users + should place all of their graphics initialization code inside one of + those two methods. + */ + virtual void InitGraphicsContext(); + +private: + + bool cursor_pos_glfw_cb(double x, double y); + bool mouse_button_glfw_cb(int button, int action, int modifiers); + bool key_glfw_cb(int key, int scancode, int action, int mods); + bool char_glfw_cb(unsigned int codepoint); + bool drop_glfw_cb(int count, const char **filenames); + bool scroll_glfw_cb(double x, double y); + bool resize_glfw_cb(int width, int height); + + virtual void mouse_move(const Point2 &pos, const Vector2 &delta) { + OnMouseMove(pos, delta); + } + virtual void left_mouse_down(const Point2 &pos) { + OnLeftMouseDown(pos); + } + virtual void left_mouse_drag(const Point2 &pos, const Vector2 &delta) { + OnLeftMouseDrag(pos, delta); + } + virtual void left_mouse_up(const Point2 &pos) { + OnLeftMouseUp(pos); + } + virtual void middle_mouse_down(const Point2 &pos) { + OnMiddleMouseDown(pos); + } + virtual void middle_mouse_drag(const Point2 &pos, const Vector2 &delta) { + OnMiddleMouseDrag(pos, delta); + } + virtual void middle_mouse_up(const Point2 &pos) { + OnMiddleMouseUp(pos); + } + virtual void right_mouse_down(const Point2 &pos) { + OnRightMouseDown(pos); + } + virtual void right_mouse_drag(const Point2 &pos, const Vector2 &delta) { + OnRightMouseDrag(pos, delta); + } + virtual void right_mouse_up(const Point2 &pos) { + OnRightMouseUp(pos); + } + virtual void key_down(const char *c, int modifiers) { + OnKeyDown(c, modifiers); + } + virtual void key_repeat(const char *c, int modifiers) { + OnKeyRepeat(c, modifiers); + } + virtual void key_up(const char *c, int modifiers) { + OnKeyUp(c, modifiers); + } + virtual void special_key_down(int key, int scancode, int modifiers) { + OnSpecialKeyDown(key, scancode, modifiers); + } + virtual void special_key_repeat(int key, int scancode, int modifiers) { + OnSpecialKeyRepeat(key, scancode, modifiers); + } + virtual void special_key_up(int key, int scancode, int modifiers) { + OnSpecialKeyUp(key, scancode, modifiers); + } + + bool graphicsInitialized_; + int width_; + int height_; + const std::string caption_; + nanogui::Screen *screen_; + GLFWwindow* window_; + double lastDrawT_; + Point2 lastMouse_; + bool leftDown_; + bool middleDown_; + bool rightDown_; +}; + + +} // end namespace + +#endif + diff --git a/dev/MinGfx/src/matrix4.cc b/dev/MinGfx/src/matrix4.cc new file mode 100644 index 0000000..e38909c --- /dev/null +++ b/dev/MinGfx/src/matrix4.cc @@ -0,0 +1,445 @@ +/* + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + See corresponding header file for details. + */ + +#include "matrix4.h" + +#include "gfxmath.h" +#include <string.h> + +namespace mingfx { + + +Matrix4::Matrix4() { + m[0] = m[5] = m[10] = m[15] = 1.0; + m[1] = m[2] = m[3] = m[4] = 0.0; + m[6] = m[7] = m[8] = m[9] = 0.0; + m[11]= m[12] = m[13] = m[14] = 0.0; +} + + +Matrix4::Matrix4(const float* a) { + memcpy(m,a,16*sizeof(float)); +} + +Matrix4::Matrix4(const std::vector<float> &a) { + for (int i=0;i<16;i++) { + m[i] = a[i]; + } +} + +Matrix4::Matrix4(const Matrix4& m2) { + memcpy(m,m2.m,16*sizeof(float)); +} + +Matrix4::~Matrix4() { +} + + +bool Matrix4::operator==(const Matrix4& m2) const { + for (int i=0;i<16;i++) { + if (fabs(m2.m[i] - m[i]) > MINGFX_MATH_EPSILON) { + return false; + } + } + return true; +} + +bool Matrix4::operator!=(const Matrix4& m2) const { + return !(*this == m2); +} + +Matrix4& Matrix4::operator=(const Matrix4& m2) { + memcpy(m,m2.m,16*sizeof(float)); + return *this; +} + +const float * Matrix4::value_ptr() const { + return m; +} + +float Matrix4::operator[](const int i) const { + return m[i]; +} + +float& Matrix4::operator[](const int i) { + return m[i]; +} + +float Matrix4::operator()(const int r, const int c) const { + return m[c*4+r]; +} + +float& Matrix4::operator()(const int r, const int c) { + return m[c*4+r]; +} + +std::vector<float> Matrix4::ToVector() const { + std::vector<float> v; + for (int i=0;i<16;i++) { + v.push_back(m[i]); + } + return v; +} + + + +Matrix4 Matrix4::Scale(const Vector3& v) { + return Matrix4::FromRowMajorElements( + v[0], 0, 0, 0, + 0, v[1], 0, 0, + 0, 0, v[2], 0, + 0, 0, 0, 1 + ); +} + + +Matrix4 Matrix4::Translation(const Vector3& v) { + return Matrix4::FromRowMajorElements( + 1, 0, 0, v[0], + 0, 1, 0, v[1], + 0, 0, 1, v[2], + 0, 0, 0, 1 + ); +} + + +Matrix4 Matrix4::RotationX(const float radians) { + const float cosTheta = cos(radians); + const float sinTheta = sin(radians); + return Matrix4::FromRowMajorElements( + 1, 0, 0, 0, + 0, cosTheta, -sinTheta, 0, + 0, sinTheta, cosTheta, 0, + 0, 0, 0, 1 + ); +} + + +Matrix4 Matrix4::RotationY(const float radians) { + const float cosTheta = cos(radians); + const float sinTheta = sin(radians); + return Matrix4::FromRowMajorElements( + cosTheta, 0, sinTheta, 0, + 0, 1, 0, 0, + -sinTheta, 0, cosTheta, 0, + 0, 0, 0, 1 + ); +} + + +Matrix4 Matrix4::RotationZ(const float radians) { + const float cosTheta = cos(radians); + const float sinTheta = sin(radians); + return Matrix4::FromRowMajorElements( + cosTheta, -sinTheta, 0, 0, + sinTheta, cosTheta, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + ); +} + + +Matrix4 Matrix4::Rotation(const Point3& p, const Vector3& v, const float a) { + const float vZ = v[2]; + const float vX = v[0]; + const float theta = atan2(vZ, vX); + const float phi = -atan2((float)v[1], (float)sqrt(vX * vX + vZ * vZ)); + + const Matrix4 transToOrigin = Matrix4::Translation(-1.0*Vector3(p[0], p[1], p[2])); + const Matrix4 A = Matrix4::RotationY(theta); + const Matrix4 B = Matrix4::RotationZ(phi); + const Matrix4 C = Matrix4::RotationX(a); + const Matrix4 invA = Matrix4::RotationY(-theta); + const Matrix4 invB = Matrix4::RotationZ(-phi); + const Matrix4 transBack = Matrix4::Translation(Vector3(p[0], p[1], p[2])); + + return transBack * invA * invB * C * B * A * transToOrigin; +} + + +Matrix4 Matrix4::Align(const Point3 &a_p, const Vector3 &a_v1, const Vector3 &a_v2, + const Point3 &b_p, const Vector3 &b_v1, const Vector3 &b_v2) +{ + Vector3 ax = a_v1.ToUnit(); + Vector3 ay = a_v2.ToUnit(); + Vector3 az = ax.Cross(ay).ToUnit(); + ay = az.Cross(ax); + Matrix4 A = Matrix4::FromRowMajorElements(ax[0], ay[0], az[0], a_p[0], + ax[1], ay[1], az[1], a_p[1], + ax[2], ay[2], az[2], a_p[2], + 0, 0, 0, 1); + + Vector3 bx = b_v1.ToUnit(); + Vector3 by = b_v2.ToUnit(); + Vector3 bz = bx.Cross(by).ToUnit(); + by = bz.Cross(bx); + Matrix4 B = Matrix4::FromRowMajorElements(bx[0], by[0], bz[0], b_p[0], + bx[1], by[1], bz[1], b_p[1], + bx[2], by[2], bz[2], b_p[2], + 0, 0, 0, 1); + return B * A.Inverse(); +} + + + +Matrix4 Matrix4::LookAt(Point3 eye, Point3 target, Vector3 up) { + Vector3 lookDir = (target - eye).ToUnit(); + + // desired x,y,z for the camera itself + Vector3 z = -lookDir; + Vector3 x = up.Cross(z).ToUnit(); + Vector3 y = z.Cross(x); + + // for the view matrix rotation, we want the inverse of the rotation for the + // camera, and the inverse of a rotation matrix is its transpose, so the + // x,y,z colums become x,y,z rows. + Matrix4 R = Matrix4::FromRowMajorElements( + x[0], x[1], x[2], 0, + y[0], y[1], y[2], 0, + z[0], z[1], z[2], 0, + 0, 0, 0, 1 + ); + + // also need to translate by -eye + Matrix4 T = Matrix4::Translation(Point3(0,0,0) - eye); + + return R * T; +} + +Matrix4 Matrix4::Perspective(float fovyInDegrees, float aspectRatio, + float nearVal, float farVal) +{ + // https://www.khronos.org/opengl/wiki/GluPerspective_code + float ymax, xmax; + ymax = nearVal * tanf(fovyInDegrees * GfxMath::PI / 360.0f); + // ymin = -ymax; + // xmin = -ymax * aspectRatio; + xmax = ymax * aspectRatio; + return Matrix4::Frustum(-xmax, xmax, -ymax, ymax, nearVal, farVal); +} + + +Matrix4 Matrix4::Frustum(float left, float right, + float bottom, float top, + float nearVal, float farVal) +{ + return Matrix4::FromRowMajorElements( + 2.0f*nearVal/(right-left), 0.0f, (right+left)/(right-left), 0.0f, + 0.0f, 2.0f*nearVal/(top-bottom), (top+bottom)/(top-bottom), 0.0f, + 0.0f, 0.0f, -(farVal+nearVal)/(farVal-nearVal), -2.0f*farVal*nearVal/(farVal-nearVal), + 0.0f, 0.0f, -1.0f, 0.0 + ); +} + + +Matrix4 Matrix4::FromRowMajorElements( + const float r1c1, const float r1c2, const float r1c3, const float r1c4, + const float r2c1, const float r2c2, const float r2c3, const float r2c4, + const float r3c1, const float r3c2, const float r3c3, const float r3c4, + const float r4c1, const float r4c2, const float r4c3, const float r4c4) +{ + float m[16]; + m[0]=r1c1; m[4]=r1c2; m[8]=r1c3; m[12]=r1c4; + m[1]=r2c1; m[5]=r2c2; m[9]=r2c3; m[13]=r2c4; + m[2]=r3c1; m[6]=r3c2; m[10]=r3c3; m[14]=r3c4; + m[3]=r4c1; m[7]=r4c2; m[11]=r4c3; m[15]=r4c4; + return Matrix4(m); +} + + + + +Matrix4 Matrix4::Orthonormal() const { + Vector3 x = ColumnToVector3(0).ToUnit(); + Vector3 y = ColumnToVector3(1); + y = (y - y.Dot(x)*x).ToUnit(); + Vector3 z = x.Cross(y).ToUnit(); + return Matrix4::FromRowMajorElements( + x[0], y[0], z[0], m[12], + x[1], y[1], z[1], m[13], + x[2], y[2], z[2], m[14], + m[3], m[7], m[11], m[15] + ); +} + + +Matrix4 Matrix4::Transpose() const { + return Matrix4::FromRowMajorElements( + m[0], m[1], m[2], m[3], + m[4], m[5], m[6], m[7], + m[8], m[9], m[10], m[11], + m[12], m[13], m[14], m[15] + ); +} + + + + +// Returns the determinant of the 3x3 matrix formed by excluding the specified row and column +// from the 4x4 matrix. The formula for the determinant of a 3x3 is discussed on +// page 705 of Hill & Kelley, but note that there is a typo within the m_ij indices in the +// equation in the book that corresponds to the cofactor02 line in the code below. +float Matrix4::SubDeterminant(int excludeRow, int excludeCol) const { + // Compute non-excluded row and column indices + int row[3]; + int col[3]; + + int r=0; + int c=0; + for (int i=0; i<4; i++) { + if (i != excludeRow) { + row[r] = i; + r++; + } + if (i != excludeCol) { + col[c] = i; + c++; + } + } + + // Compute the cofactors of each element in the first row + float cofactor00 = (*this)(row[1],col[1]) * (*this)(row[2],col[2]) - (*this)(row[1],col[2]) * (*this)(row[2],col[1]); + float cofactor01 = - ((*this)(row[1],col[0]) * (*this)(row[2],col[2]) - (*this)(row[1],col[2]) * (*this)(row[2],col[0])); + float cofactor02 = (*this)(row[1],col[0]) * (*this)(row[2],col[1]) - (*this)(row[1],col[1]) * (*this)(row[2],col[0]); + + // The determinant is then the dot product of the first row and the cofactors of the first row + return (*this)(row[0],col[0])*cofactor00 + (*this)(row[0],col[1])*cofactor01 + (*this)(row[0],col[2])*cofactor02; +} + +// Returns the cofactor matrix. The cofactor matrix is a matrix where each element c_ij is the cofactor +// of the corresponding element m_ij in M. The cofactor of each element m_ij is defined as (-1)^(i+j) times +// the determinant of the "submatrix" formed by deleting the i-th row and j-th column from M. +// See the definition in section A2.1.4 (page 705) in Hill & Kelley. +Matrix4 Matrix4::Cofactor() const { + Matrix4 out; + // We'll use i to incrementally compute -1^(r+c) + int i = 1; + for (int r = 0; r < 4; ++r) { + for (int c = 0; c < 4; ++c) { + // Compute the determinant of the 3x3 submatrix + float det = SubDeterminant(r, c); + out(r,c) = i * det; + i = -i; + } + i = -i; + } + return out; +} + +// Returns the determinant of the 4x4 matrix +// See the hint in step 2 in Appendix A2.1.5 (page 706) in Hill & Kelley to learn how to compute this +float Matrix4::Determinant() const { + // The determinant is the dot product of any row of C (the cofactor matrix of m) with the corresponding row of m + Matrix4 C = Cofactor(); + return C(0,0)*(*this)(0,0) + C(0,1)*(*this)(0,1) + C(0,2)*(*this)(0,2) + C(0,3)*(*this)(0,3); +} + +// Returns the inverse of the 4x4 matrix if it is nonsingular. If it is singular, then returns the +// identity matrix. +Matrix4 Matrix4::Inverse() const { + // Check for singular matrix + float det = Determinant(); + if (fabs(det) < 1e-8) { + return Matrix4(); + } + + // m in nonsingular, so compute inverse using the 4-step procedure outlined in Appendix A2.1.5 + // (page 706) in Hill & Kelley + // 1. Find cofactor matrix C + Matrix4 C = Cofactor(); + // 2. Find the determinant of M as the dot prod of any row of C with the corresponding row of M. + // det = determinant(m); + // 3. Transpose C to get Ctrans + Matrix4 Ctrans = C.Transpose(); + // 4. Scale each element of Ctrans by (1/det) + return Ctrans * (1.0f / det); +} + + +Vector3 Matrix4::ColumnToVector3(int c) const { + return Vector3(m[c*4], m[c*4+1], m[c*4+2]); +} + +Point3 Matrix4::ColumnToPoint3(int c) const { + return Point3(m[c*4], m[c*4+1], m[c*4+2]); +} + + + + +Matrix4 operator*(const Matrix4& m, const float& s) { + Matrix4 result; + for (int r = 0; r < 4; r++) { + for (int c = 0; c < 4; c++) { + result(r,c) = m(r,c) * s; + } + } + return result; +} + +Matrix4 operator*(const float& s, const Matrix4& m) { + return m*s; +} + + +Point3 operator*(const Matrix4& m, const Point3& p) { + // For our points, p[3]=1 and we don't even bother storing p[3], so need to homogenize + // by dividing by w before returning the new point. + const float winv = 1.0f / (p[0] * m(3,0) + p[1] * m(3,1) + p[2] * m(3,2) + 1.0f * m(3,3)); + return Point3(winv * (p[0] * m(0,0) + p[1] * m(0,1) + p[2] * m(0,2) + 1.0f * m(0,3)), + winv * (p[0] * m(1,0) + p[1] * m(1,1) + p[2] * m(1,2) + 1.0f * m(1,3)), + winv * (p[0] * m(2,0) + p[1] * m(2,1) + p[2] * m(2,2) + 1.0f * m(2,3))); + +} + + +Vector3 operator*(const Matrix4& m, const Vector3& v) { + // For a vector v[3]=0 + return Vector3(v[0] * m(0,0) + v[1] * m(0,1) + v[2] * m(0,2), + v[0] * m(1,0) + v[1] * m(1,1) + v[2] * m(1,2), + v[0] * m(2,0) + v[1] * m(2,1) + v[2] * m(2,2)); + +} + + + +Matrix4 operator*(const Matrix4& m1, const Matrix4& m2) { + Matrix4 m = Matrix4::FromRowMajorElements(0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0); + for (int r = 0; r < 4; r++) { + for (int c = 0; c < 4; c++) { + for (int i = 0; i < 4; i++) { + m(r,c) += m1(r,i) * m2(i,c); + } + } + } + return m; +} + +Ray operator*(const Matrix4& m, const Ray& r) { + Point3 p = m * r.origin(); + Vector3 d = m * r.direction(); + return Ray(p, d); +} + + +std::ostream & operator<< ( std::ostream &os, const Matrix4 &m) { + // format: [[r1c1, r1c2, r1c3, r1c4], [r2c1, r2c2, r2c3, r2c4], etc.. ] + return os << "[[" << m(0,0) << ", " << m(0,1) << ", " << m(0,2) << ", " << m(0,3) << "], " + << "[" << m(1,0) << ", " << m(1,1) << ", " << m(1,2) << ", " << m(1,3) << "], " + << "[" << m(2,0) << ", " << m(2,1) << ", " << m(2,2) << ", " << m(2,3) << "], " + << "[" << m(3,0) << ", " << m(3,1) << ", " << m(3,2) << ", " << m(3,3) << "]]"; +} + +std::istream & operator>> ( std::istream &is, Matrix4 &m) { + // format: [[r1c1, r1c2, r1c3, r1c4], [r2c1, r2c2, r2c3, r2c4], etc.. ] + char c; + return is >> c >> c >> m(0,0) >> c >> m(0,1) >> c >> m(0,2) >> c >> m(0,3) >> c >> c + >> c >> m(1,0) >> c >> m(1,1) >> c >> m(1,2) >> c >> m(1,3) >> c >> c + >> c >> m(2,0) >> c >> m(2,1) >> c >> m(2,2) >> c >> m(2,3) >> c >> c + >> c >> m(3,0) >> c >> m(3,1) >> c >> m(3,2) >> c >> m(3,3) >> c >> c; +} + +} // end namespace diff --git a/dev/MinGfx/src/matrix4.h b/dev/MinGfx/src/matrix4.h new file mode 100644 index 0000000..4abf29f --- /dev/null +++ b/dev/MinGfx/src/matrix4.h @@ -0,0 +1,260 @@ +/* + This file is part of the MinGfx Project. + + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + + Original Author(s) of this File: + Dan Keefe, 2017, University of Minnesota + + Author(s) of Significant Updates/Modifications to the File: + ... + */ + +#ifndef SRC_MATRIX4_H_ +#define SRC_MATRIX4_H_ + +#include <iostream> + +#include "point3.h" +#include "vector3.h" +#include "ray.h" + + +namespace mingfx { + + +/** A 4x4 transformation matrix stored internally as an array of floats in + column-major order so as to be compatible with OpenGL. Examples: + ~~~ + // constructing various matrices: + Matrix4 T = Matrix4::Translation(Vector3(1,0,0)); + Matrix4 S = Matrix4::Scale(Vector3(2,2,2)); + Matrix4 R = Matrix4::RotateX(GfxMath::toRadians(45.0)); + + // compose matrices together by multiplication + Matrix4 M = T * R * S; + Matrix4 Minv = M.Inverse(); + + // transforming points, vectors, etc. + Point3 p1(1,1,1); + Point3 p2 = M * p1; + + Vector3 v1(1,1,1); + Vector3 v2 = M * v1; + + Ray r1(p1, v1); + Ray r2 = M * r1; + ~~~ + */ +class Matrix4 { +public: + + /// The default constructor creates an identity matrix: + Matrix4(); + + /// Constructs a matrix given from an array of 16 floats in OpenGL matrix format + /// (i.e., column major). + Matrix4(const float* a); + + /// Constructs a matrix given from a vector of 16 floats in OpenGL matrix format + /// (i.e., column major). + Matrix4(const std::vector<float> &a); + + /// Copy constructor + Matrix4(const Matrix4& m2); + + /// Destructor + virtual ~Matrix4(); + + /// Check for "equality", taking floating point imprecision into account + bool operator==(const Matrix4& m2) const; + + /// Check for "inequality", taking floating point imprecision into account + bool operator!=(const Matrix4& m2) const; + + /// Matrix assignment operator + Matrix4& operator=(const Matrix4& m2); + + + /// Returns a pointer to the raw data array used to store the matrix. This + /// is a 1D array of 16-elements stored in column-major order. + const float * value_ptr() const; + + /// Accesses the ith element of the raw data array used to store the matrix. + /// This is a 1D array of 16-elements stored in column-major order. + float operator[](const int i) const; + + /// Accesses the ith element of the raw data array used to store the matrix. + /// This is a 1D array of 16-elements stored in column-major order. + float& operator[](const int i); + + /// Access an individual element of the array using the syntax: + /// Matrix4 mat; float row1col2 = mat(1,2); + float operator()(const int row, const int col) const; + + /// Access an individual element of the array using the syntax: + /// Matrix4 mat; mat(1,2) = 1.0; + float& operator()(const int row, const int col); + + + /// Returns the c-th column of the matrix as a Vector type, e.g.,: + /// Vector3 xAxis = mat.getColumnAsVector3(0); + /// Vector3 yAxis = mat.getColumnAsVector3(1); + /// Vector3 zAxis = mat.getColumnAsVector3(2); + Vector3 ColumnToVector3(int c) const; + + /// Returns the c-th column of the matrix as a Vector type, e.g.,: + /// Point3 pos = mat.getColumnAsPoint3(3); + Point3 ColumnToPoint3(int c) const; + + std::vector<float> ToVector() const; + + + // --- Static Constructors for Special Matrices --- + + /** Returns a matrix constructed from individual elements passed in row major + order so that the matrix looks "correct" on the screen as you write this + constructor on 4 lines of code as below. Note the that internally the + matrix constructed will be stored in a 16 element column major array to + be consistent with OpenGL. + */ + static Matrix4 FromRowMajorElements( + const float r1c1, const float r1c2, const float r1c3, const float r1c4, + const float r2c1, const float r2c2, const float r2c3, const float r2c4, + const float r3c1, const float r3c2, const float r3c3, const float r3c4, + const float r4c1, const float r4c2, const float r4c3, const float r4c4 + ); + + // --- Model Transformations --- + + /// Returns the scale matrix described by the vector + static Matrix4 Scale(const Vector3 &v); + + /// Returns the translation matrix described by the vector + static Matrix4 Translation(const Vector3 &v); + + /// Returns the rotation matrix about the x axis by the specified angle + static Matrix4 RotationX(const float radians); + + /// Returns the rotation matrix about the y axis by the specified angle + static Matrix4 RotationY(const float radians); + + /// Returns the rotation matrix about the z axis by the specified angle + static Matrix4 RotationZ(const float radians); + + /// Returns the rotation matrix around the vector v placed at point p, rotate by angle a + static Matrix4 Rotation(const Point3 &p, const Vector3 &v, const float a); + + /// Creates a transformation matrix that maps a coordinate space, *a*, defined + /// one point, *a_p*, and two vectors, *a_v1* and *a_v2*, to a new coordinate + /// space, *b*, also defined by one point, *b_p*, and two vectors, *b_v1* and + /// *b_v2*. The transformation will thus include both some rotation and some + /// translation. Pseudocode example of aligning a billboard defined in the + /// XY plane with a normal in the +Z direction and that rotates around the Y + /// axis with a camera: + /// ~~~ + /// // define a coordiante space for a canonical billboard geometry defined + /// // right at the origin. + /// Point3 a_p = Point3(0,0,0); // billboard's initial base position + /// Vector3 a_v1 = Vector3(0,1,0); // billboard's initial up direction + /// Vector3 a_v2 = Vector3(0,0,1); // billboard's initial normal direction + /// + /// // define a coordinate space for where we want this billboard to go and + /// // the direction it should be facing + /// Point3 b_p = desired_base_pos; // new position for the billboard + /// Vector3 b_v1 = Vector3(0,1,0); // +Y is still up, doesn't change + /// Vector3 b_v2 = (camera.eye() - desired_base_pos); // the normal should point toward the camera + /// b_v2[1] = 0.0; // with 0 change in Y so the billboard does not tilt + /// b_v2.Normalize(); // convert to a unit vector + /// + /// Matrix4 billboard_model_matrix = Matrix4::Align(a_p, a_v1, a_v2, b_p, b_v1, b_v2); + /// ~~~ + static Matrix4 Align(const Point3 &a_p, const Vector3 &a_v1, const Vector3 &a_v2, + const Point3 &b_p, const Vector3 &b_v1, const Vector3 &b_v2); + + + // --- View Matrices --- + + /** Returns a view matrix that centers the camera at the 'eye' position and + orients it to look at the desired 'target' point with the top of the + screen pointed as closely as possible in the direction of the 'up' vector. + */ + static Matrix4 LookAt(Point3 eye, Point3 target, Vector3 up); + + // --- Projection Matrices --- + + /// Returns a perspective projection matrix equivalent to the one gluPerspective + /// creates. + static Matrix4 Perspective(float fov_y_in_degrees, float aspect_ratio, float near_plane_dist, float far_plane_dist); + + /// Returns a projection matrix equivalent the one glFrustum creates + static Matrix4 Frustum(float left, float right, float bottom, float top, float near_plane_dist, float far_plane_dist); + + // --- Inverse, Transposeand Other General Matrix Functions --- + + /// Returns the inverse of the 4x4 matrix if it is nonsingular. If it is + /// singular, then returns the identity matrix. + Matrix4 Inverse() const; + + /** Returns an orthonormal version of the matrix, i.e., guarantees that the + rotational component of the matrix is built from column vectors that are + all unit vectors and orthogonal to each other. + */ + Matrix4 Orthonormal() const; + + /// Returns the transpose of the matrix + Matrix4 Transpose() const; + + /// Returns the determinant of the 3x3 matrix formed by excluding the specified + /// row and column from the 4x4 matrix. + float SubDeterminant(int exclude_row, int exclude_col) const; + + /// Returns the cofactor matrix. + Matrix4 Cofactor() const; + + /// Returns the determinant of the 4x4 matrix + float Determinant() const; + + + +private: + float m[16]; +}; + + + +// ---------- Operator Overloads for Working with Points, Vectors, & Matrices ---------- + + +// --- Matrix multiplication for Points, Vectors, & Matrices --- + +/// Multiply matrix and scalar, returns the new matrix +Matrix4 operator*(const Matrix4& m, const float& s); + +/// Multiply matrix and scalar, returns the new matrix +Matrix4 operator*(const float& s, const Matrix4& m); + +/// Multiply matrix and point, returns the new point +Point3 operator*(const Matrix4& m, const Point3& p); + +/// Multiply matrix and vector, returns the new vector +Vector3 operator*(const Matrix4& m, const Vector3& v); + +/// Multiply two matrices, returns the result +Matrix4 operator*(const Matrix4& m1, const Matrix4& m2); + + + +/// Multiply matrix and the point and vector portions of the ray, returns the new ray +Ray operator*(const Matrix4& m, const Ray& r); + +// --- Stream operators --- + +std::ostream & operator<< ( std::ostream &os, const Matrix4 &m); +std::istream & operator>> ( std::istream &is, Matrix4 &m); + + +} // end namespace + +#endif diff --git a/dev/MinGfx/src/mesh.cc b/dev/MinGfx/src/mesh.cc new file mode 100644 index 0000000..dd967fa --- /dev/null +++ b/dev/MinGfx/src/mesh.cc @@ -0,0 +1,651 @@ +/* + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + See corresponding header file for details. + */ + +#include "mesh.h" + +#include "matrix4.h" +#include "opengl_headers.h" + +#include <sstream> +#include <fstream> + +namespace mingfx { + +#define MAX_TEX_ATTRIBS 5 + + +Mesh::Mesh() : gpu_dirty_(true), vertex_buffer_(0), vertex_array_(0), element_buffer_(0), bvh_dirty_(true) { +} + +Mesh::Mesh(const Mesh &other) { + verts_ = other.verts_; + norms_ = other.norms_; + colors_ = other.colors_; + tex_coords_ = other.tex_coords_; + indices_ = other.indices_; + gpu_dirty_ = true; + bvh_dirty_ = true; +} + +Mesh::~Mesh() { +} + +int Mesh::AddTriangle(Point3 v1, Point3 v2, Point3 v3) { + gpu_dirty_ = true; + bvh_dirty_ = true; + + verts_.push_back(v1[0]); + verts_.push_back(v1[1]); + verts_.push_back(v1[2]); + + verts_.push_back(v2[0]); + verts_.push_back(v2[1]); + verts_.push_back(v2[2]); + + verts_.push_back(v3[0]); + verts_.push_back(v3[1]); + verts_.push_back(v3[2]); + + return num_triangles()-1; +} + +void Mesh::UpdateTriangle(int triangle_id, Point3 v1, Point3 v2, Point3 v3) { + gpu_dirty_ = true; + bvh_dirty_ = true; + + int index = triangle_id * 9; + verts_[(size_t)index + 0] = v1[0]; + verts_[(size_t)index + 1] = v1[1]; + verts_[(size_t)index + 2] = v1[2]; + + verts_[(size_t)index + 3] = v2[0]; + verts_[(size_t)index + 4] = v2[1]; + verts_[(size_t)index + 5] = v2[2]; + + verts_[(size_t)index + 6] = v3[0]; + verts_[(size_t)index + 7] = v3[1]; + verts_[(size_t)index + 8] = v3[2]; +} + + +void Mesh::SetNormals(int triangle_id, Vector3 n1, Vector3 n2, Vector3 n3) { + gpu_dirty_ = true; + + if (triangle_id >= num_triangles()) { + std::cerr << "Mesh::SetNormals() -- warning: cannot set normals for non-existant triangle with ID=" << triangle_id << ". Make sure the triangle has been added first." << std::endl; + return; + } + + int requiredSize = (triangle_id+1)*9; + if (norms_.size() < requiredSize) { + norms_.resize(requiredSize); + } + int index = triangle_id * 9; + norms_[(size_t)index + 0] = n1[0]; + norms_[(size_t)index + 1] = n1[1]; + norms_[(size_t)index + 2] = n1[2]; + + norms_[(size_t)index + 3] = n2[0]; + norms_[(size_t)index + 4] = n2[1]; + norms_[(size_t)index + 5] = n2[2]; + + norms_[(size_t)index + 6] = n3[0]; + norms_[(size_t)index + 7] = n3[1]; + norms_[(size_t)index + 8] = n3[2]; +} + +void Mesh::SetColors(int triangle_id, Color c1, Color c2, Color c3) { + gpu_dirty_ = true; + + if (triangle_id >= num_triangles()) { + std::cerr << "Mesh::SetColors() -- warning: cannot set colors for non-existant triangle with ID=" << triangle_id << ". Make sure the triangle has been added first." << std::endl; + return; + } + + int requiredSize = (triangle_id+1)*12; + if (colors_.size() < requiredSize) { + colors_.resize(requiredSize); + } + int index = triangle_id * 12; + colors_[(size_t)index + 0] = c1[0]; + colors_[(size_t)index + 1] = c1[1]; + colors_[(size_t)index + 2] = c1[2]; + colors_[(size_t)index + 3] = c1[3]; + + colors_[(size_t)index + 4] = c2[0]; + colors_[(size_t)index + 5] = c2[1]; + colors_[(size_t)index + 6] = c2[2]; + colors_[(size_t)index + 7] = c2[3]; + + colors_[(size_t)index + 8] = c3[0]; + colors_[(size_t)index + 9] = c3[1]; + colors_[(size_t)index + 10] = c3[2]; + colors_[(size_t)index + 11] = c3[3]; +} + +void Mesh::SetTexCoords(int triangle_id, int textureUnit, Point2 uv1, Point2 uv2, Point2 uv3) { + gpu_dirty_ = true; + + if (triangle_id >= num_triangles()) { + std::cerr << "Mesh::SetTexCoords() -- warning: cannot set texture coordinates for non-existant triangle with ID=" << triangle_id << ". Make sure the triangle has been added first." << std::endl; + return; + } + + // resize as needed based on the number of textureUnits used + if (tex_coords_.size() < (size_t)textureUnit+1) { + tex_coords_.resize((size_t)textureUnit+1); + } + + // resize the textureUnit-specific array based on the number of triangles + int requiredSize = (triangle_id+1)*6; + if (tex_coords_[textureUnit].size() < requiredSize) { + tex_coords_[textureUnit].resize(requiredSize); + } + int index = triangle_id * 6; + tex_coords_[textureUnit][(size_t)index + 0] = uv1[0]; + tex_coords_[textureUnit][(size_t)index + 1] = uv1[1]; + + tex_coords_[textureUnit][(size_t)index + 2] = uv2[0]; + tex_coords_[textureUnit][(size_t)index + 3] = uv2[1]; + + tex_coords_[textureUnit][(size_t)index + 4] = uv3[0]; + tex_coords_[textureUnit][(size_t)index + 5] = uv3[1]; +} + + +void Mesh::SetVertices(const std::vector<Point3> &verts) { + gpu_dirty_ = true; + bvh_dirty_ = true; + + verts_.clear(); + for (int i=0; i<verts.size(); i++) { + verts_.push_back(verts[i][0]); + verts_.push_back(verts[i][1]); + verts_.push_back(verts[i][2]); + } +} + +void Mesh::SetNormals(const std::vector<Vector3> &norms) { + gpu_dirty_ = true; + norms_.clear(); + for (int i=0; i<norms.size(); i++) { + norms_.push_back(norms[i][0]); + norms_.push_back(norms[i][1]); + norms_.push_back(norms[i][2]); + } +} + +void Mesh::SetColors(const std::vector<Color> &colors) { + gpu_dirty_ = true; + colors_.clear(); + for (int i=0; i<colors.size(); i++) { + colors_.push_back(colors[i][0]); + colors_.push_back(colors[i][1]); + colors_.push_back(colors[i][2]); + colors_.push_back(colors[i][3]); + } +} + +void Mesh::SetTexCoords(int texture_unit, const std::vector<Point2> &tex_coords) { + gpu_dirty_ = true; + // resize as needed based on the number of textureUnits used + if (tex_coords_.size() < (size_t)texture_unit+1) { + tex_coords_.resize((size_t)texture_unit+1); + } + tex_coords_[texture_unit].clear(); + for (int i=0; i<tex_coords.size(); i++) { + tex_coords_[texture_unit].push_back(tex_coords[i][0]); + tex_coords_[texture_unit].push_back(tex_coords[i][1]); + } +} + + +void Mesh::SetIndices(const std::vector<unsigned int> indices) { + gpu_dirty_ = true; + bvh_dirty_ = true; + + indices_.clear(); + for (int i=0; i<indices.size(); i++) { + indices_.push_back(indices[i]); + } +} + + +void Mesh::SetInstanceTransforms(const std::vector<Matrix4> &xforms) { + gpu_dirty_ = true; + instance_xforms_.clear(); + for (int i=0; i<xforms.size(); i++) { + for (int j=0; j<16; j++) { + std::cout << xforms[i][j] << std::endl; + instance_xforms_.push_back(xforms[i][j]); + } + } +} + + +void Mesh::SetVertices(float *vertsArray, int numVerts) { + gpu_dirty_ = true; + bvh_dirty_ = true; + + verts_.clear(); + int numFloats = numVerts * 3; + for (int i=0; i<numFloats; i++) { + verts_.push_back(vertsArray[i]); + } +} + +void Mesh::SetNormals(float *normsArray, int numNorms) { + gpu_dirty_ = true; + norms_.clear(); + int numFloats = numNorms * 3; + for (int i=0; i<numFloats; i++) { + norms_.push_back(normsArray[i]); + } +} + +void Mesh::SetColors(float *colorsArray, int numColors) { + gpu_dirty_ = true; + colors_.clear(); + int numFloats = numColors * 4; + for (int i=0; i<numFloats; i++) { + colors_.push_back(colorsArray[i]); + } +} + +void Mesh::SetTexCoords(int textureUnit, float *texCoordsArray, int numTexCoords) { + gpu_dirty_ = true; + // resize as needed based on the number of textureUnits used + if (tex_coords_.size() < (size_t)textureUnit+1) { + tex_coords_.resize((size_t)textureUnit+1); + } + tex_coords_[textureUnit].clear(); + int numFloats = numTexCoords * 2; + for (int i=0; i<numFloats; i++) { + tex_coords_[textureUnit].push_back(texCoordsArray[i]); + } +} + +void Mesh::SetIndices(unsigned int *indexArray, int numIndices) { + gpu_dirty_ = true; + bvh_dirty_ = true; + + indices_.clear(); + for (int i=0; i<numIndices; i++) { + indices_.push_back(indexArray[i]); + } +} + + + + + + +void Mesh::UpdateGPUMemory() { + if (gpu_dirty_) { + // sanity check -- for each attribute that is added (normals, colors, texcoords) + // make sure the number of triangles is equal to the number of tris in the verts + // array. + if ((norms_.size() != 0) && (norms_.size() / 3 != num_vertices())) { + std::cerr << "Mesh::UpdateGPUMemory() -- warning: the number of per vertex normals in the mesh is not equal to the number vertices in the mesh. (N = " << norms_.size() / 3 << ", V = " << num_vertices() << ")" << std::endl; + } + if ((colors_.size() != 0) && (colors_.size() / 4 != num_vertices())) { + std::cerr << "Mesh::UpdateGPUMemory() -- warning: the number of per vertex colors in the mesh is not equal to the number vertices in the mesh. (C = " << colors_.size() / 4 << ", V = " << num_vertices() << ")" << std::endl; + } + for (int i = 0; i < tex_coords_.size(); i++) { + if ((tex_coords_[i].size() != 0) && (tex_coords_[i].size() / 2 != num_vertices())) { + std::cerr << "Mesh::UpdateGPUMemory() -- warning: the number of per vertex texture coordinates (for texture unit #" << i << ") is not equal to the number vertices in the mesh. (UVs = " << tex_coords_[i].size() / 2 << ", V = " << num_vertices() << ")" << std::endl; + } + } + + GLsizeiptr totalMemSize = 0; + + GLsizeiptr vertsMemSize = verts_.size() * sizeof(float); + GLsizeiptr vertsMemOffset = 0; + totalMemSize += vertsMemSize; + + GLsizeiptr normsMemSize = norms_.size() * sizeof(float); + GLsizeiptr normsMemOffset = totalMemSize; + totalMemSize += normsMemSize; + + GLsizeiptr colorsMemSize = colors_.size() * sizeof(float); + GLsizeiptr colorsMemOffset = totalMemSize; + totalMemSize += colorsMemSize; + + std::vector<GLsizeiptr> texCoordsMemSize; + std::vector<GLsizeiptr> texCoordsMemOffset; + for (int i = 0; i < std::min((int)tex_coords_.size(),(int)MAX_TEX_ATTRIBS); i++) { + texCoordsMemSize.push_back(tex_coords_[i].size() * sizeof(float)); + texCoordsMemOffset.push_back(totalMemSize); + totalMemSize += texCoordsMemSize[i]; + } + + GLsizeiptr instanceXformsMemSize = instance_xforms_.size() * sizeof(float); + GLsizeiptr instanceXformsMemOffset = totalMemSize; + totalMemSize += instanceXformsMemSize; + + + glGenBuffers(1, &vertex_buffer_); + glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_); + glBufferData(GL_ARRAY_BUFFER, totalMemSize, NULL, GL_STATIC_DRAW); + + glBufferSubData(GL_ARRAY_BUFFER, vertsMemOffset, vertsMemSize, &verts_[0]); + if (norms_.size() > 0) { + glBufferSubData(GL_ARRAY_BUFFER, normsMemOffset, normsMemSize, &norms_[0]); + } + if (colors_.size() > 0) { + glBufferSubData(GL_ARRAY_BUFFER, colorsMemOffset, colorsMemSize, &colors_[0]); + } + for (int i=0; i<tex_coords_.size(); i++) { + glBufferSubData(GL_ARRAY_BUFFER, texCoordsMemOffset[i], texCoordsMemSize[i], &(tex_coords_[i][0])); + } + if (instance_xforms_.size() > 0) { + glBufferSubData(GL_ARRAY_BUFFER, instanceXformsMemOffset, instanceXformsMemSize, &instance_xforms_[0]); + } + glGenVertexArrays(1, &vertex_array_); + glBindVertexArray(vertex_array_); + glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_); + + // attribute 0 = vertices (required) + int attribID = 0; + int nComponents = 3; + glEnableVertexAttribArray(attribID); + glVertexAttribPointer(attribID, nComponents, GL_FLOAT, GL_FALSE, nComponents*sizeof(GLfloat), (char*)0 + vertsMemOffset); + + // attribute 1 = normals (optional) + attribID = 1; + if (norms_.size()) { + nComponents = 3; + glEnableVertexAttribArray(attribID); + glVertexAttribPointer(attribID, nComponents, GL_FLOAT, GL_TRUE, nComponents*sizeof(GLfloat), (char*)0 + normsMemOffset); + } + else { + glDisableVertexAttribArray(attribID); + } + + // attribute 2 = colors (optional) + attribID = 2; + if (colors_.size()) { + nComponents = 4; + glEnableVertexAttribArray(attribID); + glVertexAttribPointer(attribID, nComponents, GL_FLOAT, GL_TRUE, nComponents*sizeof(GLfloat), (char*)0 + colorsMemOffset); + } + else { + glDisableVertexAttribArray(attribID); + } + + // attribute(s) 3 to 7 = texture coordinates (optional) + nComponents = 2; + for (int i=0; i<std::min((int)tex_coords_.size(),(int)MAX_TEX_ATTRIBS); i++) { + attribID = 3+i; + if (tex_coords_[i].size()) { + glEnableVertexAttribArray(attribID); + glVertexAttribPointer(attribID, nComponents, GL_FLOAT, GL_FALSE, nComponents*sizeof(GLfloat), (char*)0 + texCoordsMemOffset[i]); + } + else { + glDisableVertexAttribArray(attribID); + } + } + + // attribute 8-11 (takes 4 vec4 attribs to represent a single mat4) = instance transform matrices (optional) + attribID = 8; + if (instance_xforms_.size()) { + glEnableVertexAttribArray(8); + glVertexAttribPointer(8, 4, GL_FLOAT, GL_FALSE, 16*sizeof(GLfloat), (char*)0 + instanceXformsMemOffset); + glVertexAttribDivisor(8, 1); + glEnableVertexAttribArray(9); + glVertexAttribPointer(9, 4, GL_FLOAT, GL_FALSE, 16*sizeof(GLfloat), (char*)0 + instanceXformsMemOffset + 4*sizeof(GLfloat)); + glVertexAttribDivisor(9, 1); + glEnableVertexAttribArray(10); + glVertexAttribPointer(10, 4, GL_FLOAT, GL_FALSE, 16*sizeof(GLfloat), (char*)0 + instanceXformsMemOffset + 8*sizeof(GLfloat)); + glVertexAttribDivisor(10, 1); + glEnableVertexAttribArray(11); + glVertexAttribPointer(11, 4, GL_FLOAT, GL_FALSE, 16*sizeof(GLfloat), (char*)0 + instanceXformsMemOffset + 12*sizeof(GLfloat)); + glVertexAttribDivisor(11, 1); + } + else { + glDisableVertexAttribArray(8); + glDisableVertexAttribArray(9); + glDisableVertexAttribArray(10); + glDisableVertexAttribArray(11); + } + + glBindVertexArray(0); + + if (indices_.size()) { + glGenBuffers(1, &element_buffer_); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer_); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices_.size() * sizeof(unsigned int), &indices_[0], GL_STATIC_DRAW); + } + + + gpu_dirty_ = false; + } +} + + +void Mesh::BuildBVH() { + bvh_.CreateFromMesh(*this); + bvh_dirty_ = false; +} + + +BVH* Mesh::bvh_ptr() { + if (bvh_dirty_) { + BuildBVH(); + } + return &bvh_; +} + + +void Mesh::Draw() { + if (gpu_dirty_) { + UpdateGPUMemory(); + } + + // set defaults to pass to shaders any for optional attribs + glVertexAttrib3f(1, 0.0, 0.0, 1.0); // normal = +Z + glVertexAttrib4f(2, 1.0, 1.0, 1.0, 1.0); // color = opaque white + glVertexAttrib2f(3, 0.0, 0.0); // uv = 0,0 for texture unit 0 + glVertexAttrib2f(4, 0.0, 0.0); // uv = 0,0 for texture unit 1 + glVertexAttrib2f(5, 0.0, 0.0); // uv = 0,0 for texture unit 2 + glVertexAttrib2f(6, 0.0, 0.0); // uv = 0,0 for texture unit 3 + glVertexAttrib2f(7, 0.0, 0.0); // uv = 0,0 for texture unit 4 + glVertexAttrib4f(8, 1.0, 0.0, 0.0, 0.0); // instance transform col 1 + glVertexAttrib4f(9, 0.0, 1.0, 0.0, 0.0); // instance transform col 2 + glVertexAttrib4f(10, 0.0, 0.0, 1.0, 0.0); // instance transform col 3 + glVertexAttrib4f(11, 0.0, 0.0, 0.0, 1.0); // instance transform col 4 + + + glBindVertexArray(vertex_array_); + + if (instance_xforms_.size()) { + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer_); + glDrawElementsInstanced(GL_TRIANGLES, (GLsizei)indices_.size(), GL_UNSIGNED_INT, (void*)0, (GLsizei)instance_xforms_.size()/16); + } + else if (indices_.size()) { + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer_); + glDrawElements(GL_TRIANGLES, (GLsizei)indices_.size(), GL_UNSIGNED_INT, (void*)0); + } + else { + glDrawArrays(GL_TRIANGLES, 0, num_vertices()); + } + + glBindVertexArray(0); +} + + +int Mesh::num_vertices() const { + return (int)verts_.size()/3; +} + +int Mesh::num_triangles() const { + if (indices_.size()) { + return (int)indices_.size()/3; + } + else { + return (int)verts_.size()/9; + } +} + + + +void Mesh::LoadFromOBJ(const std::string &filename) { + std::fstream file(filename.c_str(), std::ios::in); + if (!file) { + std::cerr << "Failed to load " + filename << std::endl; + exit(1); + } + + // tmp arrays + std::vector<Point3> vertices; + std::vector<Vector3> normals; + std::vector<Point2> texCoords; + + while (file) { + std::string line; + do + getline(file, line); + while (file && (line.length() == 0 || line[0] == '#')); + std::stringstream linestream(line); + std::string keyword; + linestream >> keyword; + if (keyword == "v") { + Point3 vertex; + linestream >> vertex[0] >> vertex[1] >> vertex[2]; + vertices.push_back(vertex); + } else if (keyword == "vn") { + Vector3 normal; + linestream >> normal[0] >> normal[1] >> normal[2]; + normals.push_back(normal); + } else if (keyword == "vt") { + Point2 texCoord; + linestream >> texCoord[0] >> texCoord[1]; + texCoords.push_back(texCoord); + } else if (keyword == "f") { + std::vector<int> polygon; + std::string word; + while (linestream >> word) { + std::stringstream wstream(word); + int v; + wstream >> v; + polygon.push_back(v-1); // In OBJ files, indices start from 1 + } + for (int i = 2; i < polygon.size(); i++) { + //triangles.push_back(ivec3(polygon[0], polygon[i-1], polygon[i])); + int i1 = polygon[0]; + int i2 = polygon[(size_t)i-1]; + int i3 = polygon[i]; + //int t = AddTriangle(vertices[i1], vertices[i2], vertices[i3]); + //if (normals.size()) { + // SetNormals(t, normals[i1], normals[i2], normals[i3]); + //} + //if (texCoords.size()) { + // SetTexCoords(t, 0, texCoords[i1], texCoords[i2], texCoords[i3]); + //} + + indices_.push_back(i1); + indices_.push_back(i2); + indices_.push_back(i3); + } + } + } + + gpu_dirty_ = true; + std::vector<float> verts, norms, uvs; + for (int i=0;i<vertices.size();i++) { + verts_.push_back(vertices[i][0]); + verts_.push_back(vertices[i][1]); + verts_.push_back(vertices[i][2]); + if (normals.size()) { + norms_.push_back(normals[i][0]); + norms_.push_back(normals[i][1]); + norms_.push_back(normals[i][2]); + } + if (texCoords.size()) { + uvs.push_back(texCoords[i][0]); + uvs.push_back(texCoords[i][1]); + } + } + if (uvs.size()) { + tex_coords_.push_back(uvs); + } +} + + + +Point3 Mesh::read_vertex_data(int i) const { + return Point3(verts_[(size_t)3*i], verts_[(size_t)3*i+1], verts_[(size_t)3*i+2]); +} + +Vector3 Mesh::read_normal_data(int i) const { + return Vector3(norms_[(size_t)3*i], norms_[(size_t)3*i+1], norms_[(size_t)3*i+2]); +} + +Color Mesh::read_color_data(int i) const { + return Color(colors_[(size_t)4*i], colors_[(size_t)4*i+1], colors_[(size_t)4*i+2], colors_[(size_t)4*i+3]); +} + +Point2 Mesh::read_tex_coords_data(int textureUnit, int i) const { + return Point2(tex_coords_[textureUnit][(size_t)2*i], tex_coords_[textureUnit][(size_t)2*i+1]); +} + +std::vector<unsigned int> Mesh::read_triangle_indices_data(int triangle_id) const { + std::vector<unsigned int> tri; + int i = 3*triangle_id; + if (indices_.size()) { + // indexed faces mode + tri.push_back(indices_[(size_t)i+0]); + tri.push_back(indices_[(size_t)i+1]); + tri.push_back(indices_[(size_t)i+2]); + } + else { + // ordered faces mode + tri.push_back(i); + tri.push_back(i+1); + tri.push_back(i+2); + } + return tri; +} + + +void Mesh::CalcPerFaceNormals() { + std::vector<Vector3> norms(num_vertices()); + for (int i=0; i<num_triangles(); i++) { + std::vector<unsigned int> indices = read_triangle_indices_data(i); + Point3 a = read_vertex_data(indices[0]); + Point3 b = read_vertex_data(indices[1]); + Point3 c = read_vertex_data(indices[2]); + Vector3 n = Vector3::Cross(b-a, c-a).ToUnit(); + norms[indices[0]] = n; + norms[indices[1]] = n; + norms[indices[2]] = n; + } + SetNormals(norms); +} + + +void Mesh::CalcPerVertexNormals() { + std::vector<Vector3> norms(num_vertices()); + for (int i=0; i<num_triangles(); i++) { + std::vector<unsigned int> indices = read_triangle_indices_data(i); + Point3 a = read_vertex_data(indices[0]); + Point3 b = read_vertex_data(indices[1]); + Point3 c = read_vertex_data(indices[2]); + Vector3 n = Vector3::Cross(b-a, c-a); + norms[indices[0]] = norms[indices[0]] + n; + norms[indices[1]] = norms[indices[1]] + n; + norms[indices[2]] = norms[indices[2]] + n; + } + + for (int i=0; i<norms.size(); i++) { + norms[i] = norms[i].ToUnit(); + } + + SetNormals(norms); +} + + +} // end namespace diff --git a/dev/MinGfx/src/mesh.h b/dev/MinGfx/src/mesh.h new file mode 100644 index 0000000..74f2a2f --- /dev/null +++ b/dev/MinGfx/src/mesh.h @@ -0,0 +1,337 @@ +/* + This file is part of the MinGfx Project. + + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + + Original Author(s) of this File: + Dan Keefe, 2018, University of Minnesota + + Author(s) of Significant Updates/Modifications to the File: + ... + */ + +#ifndef SRC_MESH_H_ +#define SRC_MESH_H_ + + + +#include "bvh.h" +#include "color.h" +#include "opengl_headers.h" +#include "point2.h" +#include "point3.h" +#include "vector3.h" + +#include <vector> + + +namespace mingfx { + +class Matrix4; + +/** A triangle mesh data structure that can be rendered with a ShaderProgram + like DefaultShader. The mesh can be created algorithmically by adding triangles + one at a time or it can be loaded from an .obj file. + + Vertices are required -- you cannot have a mesh without vertices, but other + attributes (normals, colors, texture coordinates) are optional. When Draw() + is called the mesh will automatically set these other attributes if available. + + Example of loading from a file: + ~~~ + // during initialization + Mesh m; + m.LoadFromOBJ(Platform::FindMinGfxDataFile("teapot.obj")); + // also create a shader to draw it. + DefaultShader s; + + + // later to draw + Matrix4 M; + Matrix4 V = Matrix4::LookAt(Point3(0,0,3), Point3(0,0,0), Vector3(0,1,0)); + Matrix4 P = Matrix4::Perspective(60.0, aspect_ratio(), 0.1, 10.0); + s.Draw(M, V, P, m, DefaultShader::MaterialProperties()); + ~~~ + + Example of creating a mesh algorithmically: + ~~~ + Mesh mesh1; + int tri_id; + // add a first triangle + tri_id = mesh1.AddTriangle(Point3(0,0,0), Point3(1,0,0), Point3(1,1,0)); + // set attributes for the vertices + mesh1.SetNormals(tri_id, Vector3(0,0,1), Vector3(0,0,1), Vector3(0,0,1)); + mesh1.SetTexCoords(tri_id, 0, Point2(0,1), Point2(1,1), Point2(1,0)); + + // add a second triangle and attributes + tri_id = mesh1.AddTriangle(Point3(0,0,0), Point3(1,1,0), Point3(0,1,0)); + mesh1.SetNormals(tri_id, Vector3(0,0,1), Vector3(0,0,1), Vector3(0,0,1)); + mesh1.SetTexCoords(tri_id, 0, Point2(0,1), Point2(1,0), Point2(0,0)); + + // call this when done to save to the GPU + mesh1.UpdateGPUMemory(); + + // then you can draw the same way as in the previous example. + ~~~ + In the mode used above where you add one triangle at a time there is no way to + reuse vertices in multiple triangles. If you need to do this for efficiency + or other reasons, then you can use an indexed faces mode where you set the + mesh data structures directly. + + Example of creating a mesh that renders in an indexed faces mode: + ~~~ + std::vector<unsigned int> indices; + std::vector<Point3> vertices; + std::vector<Vector3> normals; + std::vector<Point2> texcoords; + + // four vertices, each requires 3 floats: (x,y,z) + vertices.push_back(Point3(0,0,0)); + vertices.push_back(Point3(1,0,0)); + vertices.push_back(Point3(1,1,0)); + vertices.push_back(Point3(0,1,0)); + + // four normals, each requires 3 floats: (x,y,z) + normals.push_back(Vector3(0,0,1)); + normals.push_back(Vector3(0,0,1)); + normals.push_back(Vector3(0,0,1)); + normals.push_back(Vector3(0,0,1)); + + // four texture coords, each requires 2 floats: (u,v) + texcoords.push_back(Point2(0,1)); + texcoords.push_back(Point2(1,1)); + texcoords.push_back(Point2(1,0)); + texcoords.push_back(Point2(0,0)); + + // indices into the arrays above for the first triangle + indices.push_back(0); + indices.push_back(1); + indices.push_back(2); + + // indices for the second triangle, note some are reused + indices.push_back(0); + indices.push_back(2); + indices.push_back(3); + + Mesh mesh1; + mesh1.SetVertices(vertices); + mesh1.SetNormals(normals); + mesh1.SetTexCoords(0, texcoords); + mesh1.SetIndices(indices); + mesh1.UpdateGPUMemory(); + + // then you can draw the same way as in the previous example. + ~~~ + */ +class Mesh { +public: + /// Creates an empty mesh. + Mesh(); + + /// Copies all data and sets GPU dirty bit for the new mesh. + Mesh(const Mesh &other); + + virtual ~Mesh(); + + + /** This reads a mesh stored in the common Wavefront Obj file format. The + loader here is simplistic and not guaranteed to work on all valid .obj + files, but it should work on many simple ones. UpdateGPUMemory() is + called automatically after the model is loaded. */ + void LoadFromOBJ(const std::string &filename); + + + + // ---- TRIANGLE LIST MODE ---- + // No indices are stored, each set of 3 vertices forms a triangle, and if the + // triangles share vertices, those vertices need to be repeated. + + /** Adds a triangle to the mesh datastructure and returns a triangle ID. + The ID should then be used as the first argument for follow-on calls to + SetNormals(), SetColors(), and SetTexCoords(). The vertices must be + specified in counter-clockwise order so that the normal of the triangle + can be determined following the right-hand rule. */ + int AddTriangle(Point3 v1, Point3 v2, Point3 v3); + + /** Updates the vertex positions for a triangle that has already been added + to the mesh. */ + void UpdateTriangle(int triangle_id, Point3 v1, Point3 v2, Point3 v3); + + /** Sets the normals for the three vertices of a triangle that has already + been added to the mesh */ + void SetNormals(int triangle_id, Vector3 n1, Vector3 n2, Vector3 n3); + + /** Sets per-vertex colors for the three vertices of a triangle that has already + been added to the mesh */ + void SetColors(int triangle_id, Color c1, Color c2, Color c3); + + /** Sets the texture coordinates for the three vertices of a triangle that + has already been added to the mesh. The first textureUnit is 0, and you + should always use 0 for this parameter unless you are doing multi-texturing. */ + void SetTexCoords(int triangle_id, int texture_unit, Point2 uv1, Point2 uv2, Point2 uv3); + + + + // ---- INDEXED TRIANGLES MODE ---- + // Vertices are stored in an array and indices are stored in a separate array + // each set of 3 indices into the vertex array defines one triangle. Here, + // you cannot add one triangle at a time to the mesh. Instead you must set + // the arrays of indices, vertices, and other attributes for the mesh at + // once. + + /// Sets the vertex array for the mesh directly. + void SetVertices(const std::vector<Point3> &verts); + + /// Sets the normal array for the mesh directly. + void SetNormals(const std::vector<Vector3> &norms); + + /// Sets the per-vertex colors array for the mesh directly. + void SetColors(const std::vector<Color> &colors); + + /// Sets a texture coordinates array for the mesh directly. + void SetTexCoords(int texture_unit, const std::vector<Point2> &tex_coords); + + /// Sets the indices into the vertex array to use to create the triangles. + /// Each consecutive set of 3 indices forms one triangle: + /// (v1,v2,v3), (v1,v2,v3), (v1,v2,v3), ... + void SetIndices(const std::vector<unsigned int> index_array); + + + void SetInstanceTransforms(const std::vector<Matrix4> &xforms); + + + // ---- These functions can be used instead of the above if you are working with + // regular C-style arrays and floats rather than the higher level types like + // Point3 and Vector3. ---- + + /// Sets the vertex array for the mesh directly. Vertices are stored as + /// (x,y,z), (x,y,z), (x,y,z), ... + /// This version of the function accepts a C-style array rather than std::vector<> + void SetVertices(float *verts_array, int num_verts); + + /// Sets the normal array for the mesh directly. Normals are stored as + /// (x,y,z), (x,y,z), (x,y,z), ... following the same ordering as was used + /// for SetVertices(). + /// This version of the function accepts a C-style array rather than std::vector<> + void SetNormals(float *norms_array, int num_norms); + + /// Sets the per-vertex colors array for the mesh directly. Colors are stored as + /// (r,g,b,a), (r,g,b,a), (r,g,b,a), ... following the same ordering as was used + /// for SetVertices(). + /// This version of the function accepts a C-style array rather than std::vector<> + void SetColors(float *colors_array, int num_colors); + + /// Sets a texture coordinates array for the mesh directly. Tex coords are stored as + /// (u,v), (u,v), (u,v), ... following the same ordering as was used + /// for SetVertices(). + /// This version of the function accepts a C-style array rather than std::vector<> + void SetTexCoords(int texture_unit, float *tex_coords_array, int num_tex_coords); + + /// Sets the indices into the vertex array to use to create the triangles. + /// Each consecutive set of 3 indices forms one triangle: + /// (v1,v2,v3), (v1,v2,v3), (v1,v2,v3), ... + /// This version of the function accepts a C-style array rather than std::vector<> + void SetIndices(unsigned int *index_array, int num_indices); + + + + /** This copies the entire mesh data structure to a vertex array in GPU memory, + which must happen before you can draw the mesh. For large meshes, this can + take some time, so you may want to call this during initialization immediately + after generating the mesh. If you do not, it will be called automatically + for you the first time Draw() is called. If the mesh contains normals, per- + vertex colors and/or texture coordinates these are added as attributes within + the vertex array. */ + void UpdateGPUMemory(); + + /** This sends the mesh vertices and attributes down the graphics pipe using + glDrawArrays() for the non-indexed mode and glDrawElements() for the indexed + mode. This is just the geometry -- for anything to show up on the screen, + you must already have a ShaderProgram enabled before calling this function. */ + void Draw(); + + + + /** This (re)calculates the normals for the mesh and stores them with the mesh + data structure. It assumes a faceted mesh, like a cube, where each triangle + has separate vertices. The normal is calculated for each triangle face and + then the result is associated with each vertex that makes up the triangle. + If you have a smooth mesh where vertices are shared between multiple faces + then use CalcPerVertexNormals() instead. */ + void CalcPerFaceNormals(); + + /** This (re)calculates the normals for the mesh and stores them with the mesh + data structure. It assumes a smooth mesh, like a sphere, where each vertex + belongs to one or more triangles. Each vertex normal is calculated as a + weighted sum of the face normals for adjacent faces. The weighting is based + upon the relative areas of the neighboring faces (i.e., a large neighboring + triangle contributes more to the vertex normal than a small one). */ + void CalcPerVertexNormals(); + + + /** This (re)calculates a Bounding Volume Hierarchy for the mesh, which can + be used together with Ray::FastIntersectMesh() to do faster ray-mesh + intersection testing. */ + void BuildBVH(); + + /** Returns a pointer to the underlying BVH data structure. If the data + struture has not yet been build or needs to be updated due to a change in + the geometry of the mesh, then the BVH is recalculated before returning + the pointer. */ + BVH* bvh_ptr(); + + // Access to properties indexed by vertex number + + /// The total number of vertices in the mesh. + int num_vertices() const; + + /// Read only access to the vertex position data. Data are returned as a Point3. Indexed by vertex number. Also see num_vertices(). + /// Use the SetVertices() function to set (or edit) vertex data. + Point3 read_vertex_data(int vertex_id) const; + + /// Read only access to per-vertex normal data. Data are returned as a Vector3. Indexed by vertex number. Also see num_vertices(). + /// Use the SetNormals() function to set (or edit) per-vertex normal data. + Vector3 read_normal_data(int vertex_id) const; + + /// Read only access to per-vertex color data. Data are returned as a Color. Indexed by vertex number. Also see num_vertices(). + /// Use the SetColors() function to set (or edit) per-vertex color data. + Color read_color_data(int vertex_id) const; + + /// Read only access to per-vertex texture coordinates data. Data are returned as a Point2. Indexed by vertex number. Also see num_vertices(). + /// Use the SetTexCoords() function to set (or edit) per-vertex tex coords. + Point2 read_tex_coords_data(int texture_unit, int vertex_id) const; + + + // Access to triangles + + /// The total number of triangles in the mesh. + int num_triangles() const; + + /// Read only access to the indices that make up a particular triangle. Data are returned as a 3-element array + // of unsigned ints. Use the SetIndices() function to set (or edit) the indices for the mesh. + std::vector<unsigned int> read_triangle_indices_data(int triangle_id) const; + + +private: + std::vector<float> verts_; + std::vector<float> norms_; + std::vector<float> colors_; + std::vector< std::vector<float> > tex_coords_; + std::vector<unsigned int> indices_; + std::vector<float> instance_xforms_; + + bool gpu_dirty_; + GLuint vertex_buffer_; + GLuint vertex_array_; + GLuint element_buffer_; + + bool bvh_dirty_; + BVH bvh_; +}; + + +} // end namespace + + +#endif diff --git a/dev/MinGfx/src/mingfx.h b/dev/MinGfx/src/mingfx.h new file mode 100644 index 0000000..51ea0f8 --- /dev/null +++ b/dev/MinGfx/src/mingfx.h @@ -0,0 +1,46 @@ +/* + This file is part of the MinGfx Project. + + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + + Original Author(s) of this File: + Dan Keefe, 2018, University of Minnesota + + Author(s) of Significant Updates/Modifications to the File: + ... + */ + + +/// \file mingfx.h Includes the entire MinGfx library and calls using namespace mingfx + +#ifndef SRC_MINGFX_H_ +#define SRC_MINGFX_H_ + +#include "aabb.h" +#include "bvh.h" +#include "color.h" +#include "craft_cam.h" +#include "default_shader.h" +#include "gfxmath.h" +#include "graphics_app.h" +#include "matrix4.h" +#include "mesh.h" +#include "mingfx_config.h" +#include "opengl_headers.h" +#include "platform.h" +#include "point2.h" +#include "point3.h" +#include "quaternion.h" +#include "quick_shapes.h" +#include "ray.h" +#include "shader_program.h" +#include "text_shader.h" +#include "texture2d.h" +#include "unicam.h" +#include "vector2.h" +#include "vector3.h" + +//using namespace mingfx; + +#endif diff --git a/dev/MinGfx/src/mingfx_config.h.in b/dev/MinGfx/src/mingfx_config.h.in new file mode 100644 index 0000000..eeaf230 --- /dev/null +++ b/dev/MinGfx/src/mingfx_config.h.in @@ -0,0 +1,25 @@ +/* + This file is part of the MinGfx Project. + + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + + Original Author(s) of this File: + Dan Keefe, 2018, University of Minnesota + + Author(s) of Significant Updates/Modifications to the File: + ... + */ + +// The file config.h.in is processed by cmake to produce config.h. This +// replaces strings of the form "at"CMAKE_VARIABLE_NAME"at" with the value +// of the corresponding cmake variable, allowing us to pass directory paths +// and other information configured with cmake into our C++ code. + + +#define MINGFX_DATA_DIR_BUILD "@CMAKE_CURRENT_SOURCE_DIR@/data" +#define MINGFX_DATA_DIR_INSTALL "@CMAKE_INSTALL_PREFIX@/@INSTALL_DATA_DEST@" + +#define MINGFX_SHADERS_DIR_BUILD "@CMAKE_CURRENT_SOURCE_DIR@/src/shaders" +#define MINGFX_SHADERS_DIR_INSTALL "@CMAKE_INSTALL_PREFIX@/@INSTALL_SHADERS_DEST@" + diff --git a/dev/MinGfx/src/opengl_headers.h b/dev/MinGfx/src/opengl_headers.h new file mode 100644 index 0000000..a220277 --- /dev/null +++ b/dev/MinGfx/src/opengl_headers.h @@ -0,0 +1,48 @@ +/* + This file is part of the MinGfx Project. + + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + + Original Author(s) of this File: + Dan Keefe, 2018, University of Minnesota + + Author(s) of Significant Updates/Modifications to the File: + ... + */ + +// We often use the code that is commented out below to load opengl headers in a cross-platform way, +// but since nanogui uses glad internally, we will just use their approach to load opengl headers +// so that everything is consistent. + +// disable warnings for this 3rd party code +#pragma warning ( push, 0 ) +#include <nanogui/opengl.h> +#pragma warning ( pop ) + + +/*** Our typical (non-nanogui) appraoch: + +// GLEW is needed on Windows and Linux +#ifdef _WIN32 +#include "GL/glew.h" +#include "GL/wglew.h" +#elif (!defined(__APPLE__)) +#include "GL/glxew.h" +#endif + +// OpenGL Headers +#if defined(WIN32) +#define NOMINMAX +#include <windows.h> +#include <GL/gl.h> +#elif defined(__APPLE__) +#define GL_GLEXT_PROTOTYPES +#include <OpenGL/gl3.h> +#include <OpenGL/glext.h> +#else +#define GL_GLEXT_PROTOTYPES +#include <GL/gl.h> +#endif + +***/
\ No newline at end of file diff --git a/dev/MinGfx/src/platform.cc b/dev/MinGfx/src/platform.cc new file mode 100644 index 0000000..5d941b6 --- /dev/null +++ b/dev/MinGfx/src/platform.cc @@ -0,0 +1,78 @@ +/* + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + See corresponding header file for details. + */ + +#include "platform.h" + +#include "mingfx_config.h" + +#include <vector> +#include <sstream> + +#ifdef WIN32 + #include <windows.h> +#else + #include <sys/stat.h> +#endif + + +namespace mingfx { + +bool Platform::FileExists(const std::string &filename) { +#ifdef WIN32 + LPCTSTR szPath = (LPCTSTR)filename.c_str(); + DWORD dwAttrib = GetFileAttributes(szPath); + return (dwAttrib != INVALID_FILE_ATTRIBUTES && + !(dwAttrib & FILE_ATTRIBUTE_DIRECTORY)); +#else + struct stat buf; + return (stat(filename.c_str(), &buf) == 0); +#endif +} + + +std::string Platform::FindFile(const std::string &basename, const std::vector<std::string> &searchpath) { + for (int i=0; i<searchpath.size(); i++) { + std::string fname = searchpath[i] + "/" + basename; + if (Platform::FileExists(fname)) { + return fname; + } + } + return basename; +} + +std::string Platform::FindFile(const std::string &basename, const std::string &searchpath) { + std::vector<std::string> paths; + std::stringstream ss(searchpath); + std::string path; + while (ss >> path) { + paths.push_back(path); + if (ss.peek() == ';') + ss.ignore(); + } + return FindFile(basename, paths); +} + + +std::string Platform::FindMinGfxDataFile(const std::string &basename) { + std::vector<std::string> searchpath; + searchpath.push_back("."); + searchpath.push_back("data"); + searchpath.push_back(MINGFX_DATA_DIR_INSTALL); + searchpath.push_back(MINGFX_DATA_DIR_BUILD); + return FindFile(basename, searchpath); +} + +std::string Platform::FindMinGfxShaderFile(const std::string &basename) { + std::vector<std::string> searchpath; + searchpath.push_back("."); + searchpath.push_back("shaders"); + searchpath.push_back(MINGFX_SHADERS_DIR_INSTALL); + searchpath.push_back(MINGFX_SHADERS_DIR_BUILD); + return FindFile(basename, searchpath); +} + + +} // end namespace diff --git a/dev/MinGfx/src/platform.h b/dev/MinGfx/src/platform.h new file mode 100644 index 0000000..e609756 --- /dev/null +++ b/dev/MinGfx/src/platform.h @@ -0,0 +1,83 @@ +/* + This file is part of the MinGfx Project. + + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + + Original Author(s) of this File: + Dan Keefe, 2018, University of Minnesota + + Author(s) of Significant Updates/Modifications to the File: + ... + */ + +#ifndef SRC_PLATFORM_H_ +#define SRC_PLATFORM_H_ + +#include <string> +#include <vector> + +namespace mingfx { + + +/** Provides access to the underlying file system and other platform-specific + routines. + */ +class Platform { +public: + + /// True if filename is found and can be opened for reading on the system + static bool FileExists(const std::string &filename); + + /* Looks for a file named basename in each of the paths specified. If found, + the full path to the file is returned. If not found, then basename is returned. + Example: + ~~~ + std::vector<std::string> search_path; + search_path.push_back("."); + search_path.push_back("./data"); + search_path.push_back("./shaders"); + search_path.push_back("/usr/local/share/blah/blah/data"); + + std::string file = Platform::findFile("mydata.csv", search_path); + ~~~ + */ + static std::string FindFile(const std::string &basename, const std::vector<std::string> &searchpath); + + /* Looks for a file named basename in each of the paths specified in a semi-colon + separated list. If found, the full path to the file is returned. If not found, + then basename is returned. Example: + ~~~ + std::string search_path = ".;./data;./shaders;/usr/local/share/blah/blah/data"; + std::string file = Platform::findFile("mydata.csv", search_path); + ~~~ + */ + static std::string FindFile(const std::string &basename, const std::string &searchpath); + + /** Searches for a data file that ships with MinGfx. This will look in the + following locations in order: + 1. the current working directory. + 2. a subdirectory called data within the current working directory. + 3. the installed data directory INSTALL_PREFIX/share/MinGfx-1.0/data. + 4. the data directory in the MinGfx build tree. + If the file is found, the full path is returned, else basename is returned. + */ + static std::string FindMinGfxDataFile(const std::string &basename); + + /** Searches for a shader file that ships with MinGfx. This will look in the + following locations in order: + 1. the current working directory. + 2. a subdirectory called shaders within the current working directory. + 3. the installed shaders directory INSTALL_PREFIX/share/MinGfx-1.0/shaders. + 4. the shaders directory in the MinGfx build tree. + If the file is found, the full path is returned, else basename is returned. + */ + static std::string FindMinGfxShaderFile(const std::string &basename); + +private: +}; + + +} // end namespace + +#endif diff --git a/dev/MinGfx/src/point2.cc b/dev/MinGfx/src/point2.cc new file mode 100644 index 0000000..9d98777 --- /dev/null +++ b/dev/MinGfx/src/point2.cc @@ -0,0 +1,104 @@ +/* + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + See corresponding header file for details. + */ + +#include "point2.h" + +#include <math.h> + +namespace mingfx { + +static const Point2 s_zerop2d = Point2(0,0); +static const Point2 s_onep2d = Point2(1,1); + +const Point2& Point2::Origin() { return s_zerop2d; } +const Point2& Point2::Zero() { return s_zerop2d; } +const Point2& Point2::One() { return s_onep2d; } + + +Point2::Point2() { + p[0] = 0.0; + p[1] = 0.0; +} + +Point2::Point2(float x, float y) { + p[0] = x; + p[1] = y; +} + +Point2::Point2(float *ptr) { + p[0] = ptr[0]; + p[1] = ptr[1]; +} + +Point2::Point2(const Point2& other) { + p[0] = other[0]; + p[1] = other[1]; +} + +Point2::~Point2() { +} + +bool Point2::operator==(const Point2& other) const { + return (fabs(other[0] - p[0]) < MINGFX_MATH_EPSILON && + fabs(other[1] - p[1]) < MINGFX_MATH_EPSILON); +} + +bool Point2::operator!=(const Point2& other) const { + return (fabs(other[0] - p[0]) >= MINGFX_MATH_EPSILON || + fabs(other[1] - p[1]) >= MINGFX_MATH_EPSILON); +} + +Point2& Point2::operator=(const Point2& other) { + p[0] = other[0]; + p[1] = other[1]; + return *this; +} + +float Point2::operator[](const int i) const { + if ((i>=0) && (i<=1)) { + return p[i]; + } + else { + // w component of a point is 1 so return the constant 1.0 + return 1.0; + } +} + +float& Point2::operator[](const int i) { + return p[i]; +} + + +Point2 Point2::Lerp(const Point2 &b, float alpha) const { + float x = (1.0f-alpha)*(*this)[0] + alpha*b[0]; + float y = (1.0f-alpha)*(*this)[1] + alpha*b[1]; + return Point2(x,y); +} + +Point2 Point2::Lerp(const Point2 &a, const Point2 &b, float alpha) { + float x = (1.0f-alpha)*a[0] + alpha*b[0]; + float y = (1.0f-alpha)*a[1] + alpha*b[1]; + return Point2(x,y); +} + + +const float * Point2::value_ptr() const { + return p; +} + + +std::ostream & operator<< ( std::ostream &os, const Point2 &p) { + return os << "(" << p[0] << ", " << p[1] << ")"; +} + +std::istream & operator>> ( std::istream &is, Point2 &p) { + // format: (x, y) + char dummy; + return is >> dummy >> p[0] >> dummy >> p[1] >> dummy; +} + + +} // end namespace diff --git a/dev/MinGfx/src/point2.h b/dev/MinGfx/src/point2.h new file mode 100644 index 0000000..954f313 --- /dev/null +++ b/dev/MinGfx/src/point2.h @@ -0,0 +1,112 @@ +/* + This file is part of the MinGfx Project. + + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + + Original Author(s) of this File: + Dan Keefe, 2018, University of Minnesota + + Author(s) of Significant Updates/Modifications to the File: + ... + */ + +#ifndef SRC_POINT2_H_ +#define SRC_POINT2_H_ + +#include <iostream> + +namespace mingfx { + +/// Epsilon value used for == and != comparisons within MinGfx +#define MINGFX_MATH_EPSILON 1e-8 + + +/** A 2D Point with floating point coordinates, used for storing 2D texture + coordinates, screen-space graphics, and mouse input. + */ +class Point2 { +public: + /// Default point at the origin + Point2(); + + /// Constructs a point given (x,y,1), where the 1 comes from the use of + /// homogeneous coordinates in computer graphics. + Point2(float x, float y); + + /// Constructs a point given a pointer to x,y data + Point2(float *p); + + /// Copy constructor for point + Point2(const Point2& p); + + /// Point destructor + virtual ~Point2(); + + /// Check for "equality", taking floating point imprecision into account + bool operator==(const Point2& p) const; + + /// Check for "inequality", taking floating point imprecision into account + bool operator!=(const Point2& p) const; + + /// Assignment operator + Point2& operator=(const Point2& p); + + /// Read only access to the ith coordinate of the point. + float operator[](const int i) const; + + /// Returns a reference to the ith coordinate of the point. Use this + /// accessor if you wish to set the coordinate rather than just request + /// its value. Example: + /// ~~~ + /// Point2 a; + /// a[0] = 5.0; // set the x-coordinate of the point + /// ~~~ + float& operator[](const int i); + + /// Read only access to the x coordinate. Can also use my_point[0]. Use + /// the my_point[0] = 1.0; form if you need to set the value. + float x() const { return p[0]; } + + /// Read only access to the y coordinate. Can also use my_point[1]. Use + /// the my_point[1] = 1.0; form if you need to set the value. + float y() const { return p[1]; } + + /// In homogeneous coordinates, the w coordinate for all points is 1.0. + float w() const { return 1.0; } + + + /// Returns a const pointer to the raw data array + const float * value_ptr() const; + + /// Linear interpolation between this point and another. Alpha=0.0 returns + /// this point, and alpha=1.0 returns the other point, other values blend + /// between the two. + Point2 Lerp(const Point2 &b, float alpha) const; + + + /// (0,0) - a shortcut for a special point that is frequently needed + static const Point2& Origin(); + + /// (0,0) - a shortcut for a special point that is frequently needed + static const Point2& Zero(); + + /// (1,1) - a shortcut for a special point that is frequently needed + static const Point2& One(); + + /// Linear interpolation between two points. Alpha=0.0 returns 'a' and + /// alpha=1.0 returns 'b', other values blend between the two. + static Point2 Lerp(const Point2 &a, const Point2 &b, float alpha); + +private: + float p[2]; +}; + + +std::ostream & operator<< ( std::ostream &os, const Point2 &p); +std::istream & operator>> ( std::istream &is, Point2 &p); + + +} // namespace + +#endif diff --git a/dev/MinGfx/src/point3.cc b/dev/MinGfx/src/point3.cc new file mode 100644 index 0000000..c75a18e --- /dev/null +++ b/dev/MinGfx/src/point3.cc @@ -0,0 +1,144 @@ +/* + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + See corresponding header file for details. + */ + +#include "point3.h" +#include "vector3.h" + +#include <math.h> + +namespace mingfx { + +static const Point3 s_zerop3d = Point3(0,0,0); +static const Point3 s_onep3d = Point3(1,1,1); + +const Point3& Point3::Origin() { return s_zerop3d; } +const Point3& Point3::Zero() { return s_zerop3d; } +const Point3& Point3::One() { return s_onep3d; } + + +Point3::Point3() { + p[0] = 0.0; + p[1] = 0.0; + p[2] = 0.0; +} + +Point3::Point3(float x, float y, float z) { + p[0] = x; + p[1] = y; + p[2] = z; +} + +Point3::Point3(float *ptr) { + p[0] = ptr[0]; + p[1] = ptr[1]; + p[2] = ptr[2]; +} + +Point3::Point3(const Point3& other) { + p[0] = other[0]; + p[1] = other[1]; + p[2] = other[2]; +} + +Point3::~Point3() { +} + +bool Point3::operator==(const Point3& other) const { + return (fabs(other[0] - p[0]) < MINGFX_MATH_EPSILON && + fabs(other[1] - p[1]) < MINGFX_MATH_EPSILON && + fabs(other[2] - p[2]) < MINGFX_MATH_EPSILON); +} + +bool Point3::operator!=(const Point3& other) const { + return (fabs(other[0] - p[0]) >= MINGFX_MATH_EPSILON || + fabs(other[1] - p[1]) >= MINGFX_MATH_EPSILON || + fabs(other[2] - p[2]) >= MINGFX_MATH_EPSILON); +} + +Point3& Point3::operator=(const Point3& other) { + p[0] = other[0]; + p[1] = other[1]; + p[2] = other[2]; + return *this; +} + +float Point3::operator[](const int i) const { + if ((i>=0) && (i<=2)) { + return p[i]; + } + else { + // w component of a point is 1 so return the constant 1.0 + return 1.0; + } +} + +float& Point3::operator[](const int i) { + return p[i]; +} + + +const float * Point3::value_ptr() const { + return p; +} + +Point3 Point3::Lerp(const Point3 &b, float alpha) const { + float x = (1.0f-alpha)*(*this)[0] + alpha*b[0]; + float y = (1.0f-alpha)*(*this)[1] + alpha*b[1]; + float z = (1.0f-alpha)*(*this)[2] + alpha*b[2]; + return Point3(x,y,z); +} + +Point3 Point3::Lerp(const Point3 &a, const Point3 &b, float alpha) { + float x = (1.0f-alpha)*a[0] + alpha*b[0]; + float y = (1.0f-alpha)*a[1] + alpha*b[1]; + float z = (1.0f-alpha)*a[2] + alpha*b[2]; + return Point3(x,y,z); +} + + +float Point3::DistanceToPlane(const Point3 &plane_origin, const Vector3 &plane_normal) { + return ((*this) - ClosestPointOnPlane(plane_origin, plane_normal)).Length(); +} + + +Point3 Point3::ClosestPointOnPlane(const Point3 &plane_origin, const Vector3 &plane_normal) { + Vector3 to_plane_origin = plane_origin - (*this); + Vector3 inv_n = -plane_normal; + if (to_plane_origin.Dot(inv_n) < 0.0) { + inv_n = -inv_n; + } + + Vector3 to_plane = inv_n * to_plane_origin.Dot(inv_n); + return (*this) + to_plane; +} + +Point3 Point3::ClosestPoint(const std::vector<Point3> &point_list) { + int closest_id = 0; + float closest_dist = (point_list[0] - *this).Length(); + for (int i=1; i<point_list.size(); i++) { + float d = (point_list[i] - *this).Length(); + if (d < closest_dist) { + closest_id = i; + closest_dist = d; + } + } + return point_list[closest_id]; +} + + + +std::ostream & operator<< ( std::ostream &os, const Point3 &p) { + return os << "(" << p[0] << ", " << p[1] << ", " << p[2] << ")"; +} + +std::istream & operator>> ( std::istream &is, Point3 &p) { + // format: (x, y, z) + char dummy; + return is >> dummy >> p[0] >> dummy >> p[1] >> dummy >> p[2] >> dummy; +} + + +} // end namespace diff --git a/dev/MinGfx/src/point3.h b/dev/MinGfx/src/point3.h new file mode 100644 index 0000000..50783f0 --- /dev/null +++ b/dev/MinGfx/src/point3.h @@ -0,0 +1,154 @@ +/* + This file is part of the MinGfx Project. + + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + + Original Author(s) of this File: + Dan Keefe, 2018, University of Minnesota + + Author(s) of Significant Updates/Modifications to the File: + ... + */ + +#ifndef SRC_POINT3_H_ +#define SRC_POINT3_H_ + +#include <iostream> +#include <vector> + +namespace mingfx { + +/// Epsilon value used for == and != comparisons within MinGfx +#define MINGFX_MATH_EPSILON 1e-8 + +// forward declaration +class Vector3; + + +/** A 3D Point with floating point coordinates, used for storing vertices and + all sorts of other 3D graphics operations. Point3s can be transformed by a + Matrix4. Example: + ~~~ + Point3 a(0,0,1); + std::cout << a << std::endl; + + Matrix4 M = Matrix4::Translation(Vector3(0,0,-1)); + Point3 b = M * a; + std::cout << b << std::endl; + + // you can access the individual components of the point in two ways: + Point3 p(1,2,3); + float option1 = p.x(); + float option2 = p[0]; + + // to set an individual component of the point use the [] operator: + Point3 p2; + p2[0] = 0.4; + p2[1] = 1.2; + p2[2] = 3.1; + ~~~ + */ +class Point3 { +public: + /// Default point at the origin + Point3(); + + /// Constructs a point given (x,y,z,1), where the 1 comes from the use of + /// homogeneous coordinates in computer graphics. + Point3(float x, float y, float z); + + /// Constructs a point given a pointer to x,y,z data + Point3(float *p); + + /// Copy constructor for point + Point3(const Point3& p); + + /// Point destructor + virtual ~Point3(); + + /// Check for "equality", taking floating point imprecision into account + bool operator==(const Point3& p) const; + + /// Check for "inequality", taking floating point imprecision into account + bool operator!=(const Point3& p) const; + + /// Assignment operator + Point3& operator=(const Point3& p); + + /// Read only access to the ith coordinate of the point. + float operator[](const int i) const; + + /// Returns a reference to the ith coordinate of the point. Use this + /// accessor if you wish to set the coordinate rather than just request + /// its value. Example: + /// ~~~ + /// Point3 a; + /// a[0] = 5.0; // set the x-coordinate of the point + /// ~~~ + float& operator[](const int i); + + /// Read only access to the x coordinate. Can also use my_point[0]. Use + /// the my_point[0] = 1.0; form if you need to set the value. + float x() const { return p[0]; } + + /// Read only access to the y coordinate. Can also use my_point[1]. Use + /// the my_point[1] = 1.0; form if you need to set the value. + float y() const { return p[1]; } + + /// Read only access to the z coordinate. Can also use my_point[2]. Use + /// the my_point[2] = 1.0; form if you need to set the value. + float z() const { return p[2]; } + + /// In homogeneous coordinates, the w coordinate for all points is 1.0. + float w() const { return 1.0; } + + /// Returns a const pointer to the raw data array + const float * value_ptr() const; + + + /// Linear interpolation between this point and another. Alpha=0.0 returns + /// this point, and alpha=1.0 returns the other point, other values blend + /// between the two. + Point3 Lerp(const Point3 &b, float alpha) const; + + /// Returns the shortest (i.e., perpendicular) distance from this point to + /// a plane defined by a point and a normal. + float DistanceToPlane(const Point3 &plane_origin, const Vector3 &plane_normal); + + /// Returns the perpendicular projection of this point onto a plane defined + /// by a point and a normal. + Point3 ClosestPointOnPlane(const Point3 &plane_origin, const Vector3 &plane_normal); + + /// Given a list of points, returns the closest in the last to the current point. + Point3 ClosestPoint(const std::vector<Point3> &point_list); + + + + /// (0,0,0) - a shortcut for a special point that is frequently needed + static const Point3& Origin(); + + /// (0,0,0) - a shortcut for a special point that is frequently needed + static const Point3& Zero(); + + /// (1,1,1) - a shortcut for a special point that is frequently needed + static const Point3& One(); + + /// Linear interpolation between two points. Alpha=0.0 returns 'a' and + /// alpha=1.0 returns 'b', other values blend between the two. + static Point3 Lerp(const Point3 &a, const Point3 &b, float alpha); + + + +private: + float p[3]; +}; + + +std::ostream & operator<< ( std::ostream &os, const Point3 &p); +std::istream & operator>> ( std::istream &is, Point3 &p); + + +} // namespace + +#endif diff --git a/dev/MinGfx/src/quaternion.cc b/dev/MinGfx/src/quaternion.cc new file mode 100644 index 0000000..4f2998f --- /dev/null +++ b/dev/MinGfx/src/quaternion.cc @@ -0,0 +1,260 @@ +/* +Copyright (c) 2017,2018 Regents of the University of Minnesota. +All Rights Reserved. +See corresponding header file for details. +*/ + +#define _USE_MATH_DEFINES +#include "quaternion.h" + +#include "gfxmath.h" + +namespace mingfx { + + +Quaternion::Quaternion() { + q[0] = 0.0; + q[1] = 0.0; + q[2] = 0.0; + q[3] = 1.0; +} + +Quaternion::Quaternion(float qx, float qy, float qz, float qw) { + q[0] = qx; + q[1] = qy; + q[2] = qz; + q[3] = qw; +} + +Quaternion::Quaternion(float *ptr) { + q[0] = ptr[0]; + q[1] = ptr[1]; + q[2] = ptr[2]; + q[3] = ptr[3]; +} + +Quaternion::Quaternion(const Quaternion& other) { + q[0] = other[0]; + q[1] = other[1]; + q[2] = other[2]; + q[3] = other[3]; +} + +Quaternion::~Quaternion() { +} + +bool Quaternion::operator==(const Quaternion& other) const { + return (fabs(other[0] - q[0]) < MINGFX_MATH_EPSILON && + fabs(other[1] - q[1]) < MINGFX_MATH_EPSILON && + fabs(other[2] - q[2]) < MINGFX_MATH_EPSILON && + fabs(other[3] - q[3]) < MINGFX_MATH_EPSILON); +} + +bool Quaternion::operator!=(const Quaternion& other) const { + return (fabs(other[0] - q[0]) >= MINGFX_MATH_EPSILON || + fabs(other[1] - q[1]) >= MINGFX_MATH_EPSILON || + fabs(other[2] - q[2]) >= MINGFX_MATH_EPSILON || + fabs(other[3] - q[3]) >= MINGFX_MATH_EPSILON); +} + +Quaternion& Quaternion::operator=(const Quaternion& other) { + q[0] = other[0]; + q[1] = other[1]; + q[2] = other[2]; + q[3] = other[3]; + return *this; +} + +float Quaternion::operator[](const int i) const { + if ((i>=0) && (i<=3)) { + return q[i]; + } + else { + // this is an error! + return 0.0; + } +} + +float& Quaternion::operator[](const int i) { + return q[i]; +} + + +const float * Quaternion::value_ptr() const { + return q; +} + +Quaternion Quaternion::Slerp(const Quaternion &other, float alpha) const { + // https://en.wikipedia.org/wiki/Slerp + + Quaternion v0 = *this; + Quaternion v1 = other; + + // Only unit quaternions are valid rotations. + // Normalize to avoid undefined behavior. + v0.Normalize(); + v1.Normalize(); + + // Compute the cosine of the angle between the two vectors. + float dot = v0.Dot(v1); + + // If the dot product is negative, the quaternions + // have opposite handed-ness and slerp won't take + // the shorter path. Fix by reversing one quaternion. + if (dot < 0.0f) { + v1 = -v1; + dot = -dot; + } + + const double DOT_THRESHOLD = 0.9995; + if (dot > DOT_THRESHOLD) { + // If the inputs are too close for comfort, linearly interpolate + // and normalize the result. + + Quaternion result = v0 + alpha*(v1 - v0); + result.Normalize(); + return result; + } + + GfxMath::Clamp(dot, -1, 1); // Robustness: Stay within domain of acos() + float theta_0 = acos(dot); // theta_0 = angle between input vectors + float theta = theta_0 * alpha; // theta = angle between v0 and result + + float s0 = cos(theta) - dot * sin(theta) / sin(theta_0); // == sin(theta_0 - theta) / sin(theta_0) + float s1 = sin(theta) / sin(theta_0); + + return (s0 * v0) + (s1 * v1); +} + +Quaternion Quaternion::Slerp(const Quaternion &a, const Quaternion &b, float alpha) { + return a.Slerp(b, alpha); +} + + +std::ostream & operator<< ( std::ostream &os, const Quaternion &q) { + return os << "<" << q[0] << ", " << q[1] << ", " << q[2] << ", " << q[3] << ")"; +} + +std::istream & operator>> ( std::istream &is, Quaternion &q) { + // format: <qx, qy, qz, qw> + char dummy; + return is >> dummy >> q[0] >> dummy >> q[1] >> dummy >> q[2] >> dummy >> q[3] >> dummy; +} + + +float Quaternion::Dot(const Quaternion& other) const { + return q[0]*other[0] + q[1]*other[1] + q[2]*other[2] + q[3]*other[3]; + +} + +float Quaternion::Length() const { + return sqrt(q[0]*q[0] + q[1]*q[1] + q[2]*q[2] + q[3]*q[3]); +} + +void Quaternion::Normalize() { + float sizeSq = + q[0]*q[0] + q[1]*q[1] + q[2]*q[2] + q[3]*q[3]; + if (sizeSq < MINGFX_MATH_EPSILON) { + return; // do nothing to zero quats + } + float scaleFactor = (float)1.0/(float)sqrt(sizeSq); + q[0] *= scaleFactor; + q[1] *= scaleFactor; + q[2] *= scaleFactor; + q[3] *= scaleFactor; +} + +Quaternion Quaternion::ToUnit() const { + Quaternion qtmp(*this); + qtmp.Normalize(); + return qtmp; +} + +/// Returns the conjugate of the quaternion. +Quaternion Quaternion::Conjugate() const { + return Quaternion(-q[0], -q[1], -q[2], q[3]); +} + + +Quaternion Quaternion::FromAxisAngle(const Vector3 &axis, float angle) { + // [qx, qy, qz, qw] = [sin(a/2) * vx, sin(a/2)* vy, sin(a/2) * vz, cos(a/2)] + float x = sin(angle/2.0f) * axis[0]; + float y = sin(angle/2.0f) * axis[1]; + float z = sin(angle/2.0f) * axis[2]; + float w = cos(angle/2.0f); + return Quaternion(x,y,z,w); +} + + +Quaternion Quaternion::FromEulerAnglesZYX(const Vector3 &angles) { + Quaternion rot_x = Quaternion::FromAxisAngle(Vector3::UnitX(), angles[0]); + Quaternion rot_y = Quaternion::FromAxisAngle(Vector3::UnitY(), angles[1]); + Quaternion rot_z = Quaternion::FromAxisAngle(Vector3::UnitZ(), angles[2]); + return rot_z * rot_y * rot_x; +} + +Vector3 Quaternion::ToEulerAnglesZYX() const { + // https://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles + + Vector3 angles; + + // roll (x-axis rotation) + float sinr = +2.0f * (w() * x() + y() * z()); + float cosr = +1.0f - 2.0f * (x() * x() + y() * y()); + angles[0] = std::atan2(sinr, cosr); + + // pitch (y-axis rotation) + float sinp = +2.0f * (w() * y() - z() * x()); + if (std::fabs(sinp) >= 1.f) + angles[1] = std::copysign(GfxMath::HALF_PI, sinp); // use 90 degrees if out of range + else + angles[1] = std::asin(sinp); + + // yaw (z-axis rotation) + float siny = +2.0f * (w() * z() + x() * y()); + float cosy = +1.0f - 2.0f * (y() * y() + z() * z()); + angles[2] = std::atan2(siny, cosy); + + return angles; +} + + +Quaternion operator*(const Quaternion& q1, const Quaternion& q2) { + float real1 = q1[3]; + Vector3 imag1 = Vector3(q1[0], q1[1], q1[2]); + + float real2 = q2[3]; + Vector3 imag2 = Vector3(q2[0], q2[1], q2[2]); + + float real = real1*real2 - imag1.Dot(imag2); + Vector3 imag = real1*imag2 + real2*imag1 + imag1.Cross(imag2); + + return Quaternion(imag[0], imag[1], imag[2], real); +} + + +Quaternion operator/(const Quaternion& q, const float s) { + const float invS = 1.0f / s; + return Quaternion(q[0]*invS, q[1]*invS, q[2]*invS, q[3]*invS); +} + +Quaternion operator*(const float s, const Quaternion& q) { + return Quaternion(q[0]*s, q[1]*s, q[2]*s, q[3]*s); +} + +Quaternion operator*(const Quaternion& q, const float s) { + return Quaternion(q[0]*s, q[1]*s, q[2]*s, q[3]*s); +} + +Quaternion operator-(const Quaternion& q) { + return Quaternion(-q[0], -q[1], -q[2], -q[3]); +} + +Quaternion operator+(const Quaternion& q1, const Quaternion& q2) { + return Quaternion(q1[0] + q2[0], q1[1] + q2[1], q1[2] + q2[2], q1[3] + q2[3]); +} + +Quaternion operator-(const Quaternion& q1, const Quaternion& q2) { + return Quaternion(q1[0] - q2[0], q1[1] - q2[1], q1[2] - q2[2], q1[3] - q2[3]); +} + +} // end namespace diff --git a/dev/MinGfx/src/quaternion.h b/dev/MinGfx/src/quaternion.h new file mode 100644 index 0000000..fc69ba7 --- /dev/null +++ b/dev/MinGfx/src/quaternion.h @@ -0,0 +1,142 @@ +/* + This file is part of the MinGfx Project. + + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + + Original Author(s) of this File: + Dan Keefe, 2018, University of Minnesota + + Author(s) of Significant Updates/Modifications to the File: + ... + */ + +#ifndef SRC_QUATERNION_H_ +#define SRC_QUATERNION_H_ + +#include <iostream> + +#include "vector3.h" + +namespace mingfx { + +/** A quaternion to represent rotations in 3D space. The main use of quaternions + within the library is to support smooth interpolation between rotations, since + this is not possible using Euler angles or rotation matrices. The class includes + a Slerp routine for spherical interpolation between rotations. Example use: + ~~~ + // find a rotation 1/2 way between r1 and r2, both originally expressed in Euler angles + + Vector3 euler1 = GfxMath::ToRadians(Vector3(0,0,60)); + Vector3 euler2 = GfxMath::ToRadians(Vector3(45,45,60)); + + Quaternion q1 = Quaternion::FromEulerAnglesZYX(euler1); + Quaternion q2 = Quaternion::FromEulerAnglesZYX(euler2); + + float alpha = 0.5; + Quaternion q_half_way = q1.Slerp(q2, alpha); + Vector3 new_euler_angles = GfxMath::ToDegrees(q_half_way.ToEulerAnglesZYX()); + ~~~ + */ +class Quaternion { +public: + /// Creates a quat with the identity rotation + Quaternion(); + + /// Creates a quat from the 4 parameters + Quaternion(float qx, float qy, float qz, float qw); + + /// Creates a quate from a pointer to 4 floating point numbers in the order + /// qx, qy, qz, qw. + Quaternion(float *ptr); + + /// Copy constructor + Quaternion(const Quaternion& other); + + virtual ~Quaternion(); + + /// Check for "equality", taking floating point imprecision into account + bool operator==(const Quaternion& q) const; + + /// Check for "inequality", taking floating point imprecision into account + bool operator!=(const Quaternion& q) const; + + /// Assignment operator + Quaternion& operator=(const Quaternion& q); + + /// Read only access to the ith coordinate of the quaternion (qx, qy, qz, qw). + float operator[](const int i) const; + + /// Writable access the ith coordinate of the quaternion (qx, qy, qz, qw). + float& operator[](const int i); + + /// Read only access to the x coordinate of the imaginary part of the quaternion. + float x() const { return q[0]; } + + /// Read only access to the y coordinate of the imaginary part of the quaternion. + float y() const { return q[1]; } + + /// Read only access to the z coordinate of the imaginary part of the quaternion. + float z() const { return q[2]; } + + /// Read only access to the w, real part, of the quaternion. + float w() const { return q[3]; } + + /// Returns a const pointer to the raw data array, stored in the order qx, qy, qz, qw. + const float * value_ptr() const; + + /// Returns the dot product of this quaternion with another. + float Dot(const Quaternion& q) const; + + /// Returns the length of the quaternion. + float Length() const; + + /// Normalizes the quat by making it unit length. + void Normalize(); + + /// Returns a normalized (i.e., unit length) version of the quaternion without + /// modifying the original. + Quaternion ToUnit() const; + + /// Returns the conjugate of the quaternion. + Quaternion Conjugate() const; + + /// Converts the rotation specified by the quaternion into Euler angles. + Vector3 ToEulerAnglesZYX() const; + + /// Uses spherical interpolation to interpoloate between the rotation stored + /// in this quaternion and the rotation stored in another. + Quaternion Slerp(const Quaternion &other, float alpha) const; + + /// Creates a new quaternion that describes a rotation by angle radians about + // the specified axis. + static Quaternion FromAxisAngle(const Vector3 &axis, float angle); + + /// Creates a new quaternion from a rotation defined in Euler angles. + static Quaternion FromEulerAnglesZYX(const Vector3 &angles); + + /// Uses spherical interpolation to interpoloate between the rotations + /// specified by two quaternions. + static Quaternion Slerp(const Quaternion &a, const Quaternion &b, float alpha); + +private: + float q[4]; +}; + + +Quaternion operator*(const Quaternion& q1, const Quaternion& q2); +Quaternion operator/(const Quaternion& q, const float s); +Quaternion operator*(const float s, const Quaternion& q); +Quaternion operator*(const Quaternion& q, const float s); +Quaternion operator-(const Quaternion& q); +Quaternion operator+(const Quaternion& q1, const Quaternion& q2); +Quaternion operator-(const Quaternion& q1, const Quaternion& q2); + +std::ostream & operator<< ( std::ostream &os, const Quaternion &q); +std::istream & operator>> ( std::istream &is, Quaternion &q); + + +} // end namespace + + +#endif diff --git a/dev/MinGfx/src/quick_shapes.cc b/dev/MinGfx/src/quick_shapes.cc new file mode 100644 index 0000000..388aa3d --- /dev/null +++ b/dev/MinGfx/src/quick_shapes.cc @@ -0,0 +1,727 @@ +/* + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + See corresponding header file for details. + */ + +#include "quick_shapes.h" +#include "platform.h" + +#include <cmath> +#include <iostream> +#include <string> + + +namespace mingfx { + + + +#define PI 3.14159265359f +#define TWOPI 6.28318530718f + + + +// Helper datastructure for building shapes algorithmically +class Vertex { +public: + Vertex(GLfloat xx, GLfloat yy, GLfloat zz, GLfloat nnx, GLfloat nny, GLfloat nnz) : + x(xx), y(yy), z(zz), nx(nnx), ny(nny), nz(nnz) {} + + GLfloat x; + GLfloat y; + GLfloat z; + GLfloat nx; + GLfloat ny; + GLfloat nz; +}; + + + + +QuickShapes::QuickShapes() { +} + +QuickShapes::~QuickShapes() { +} + + + + +// ------------ CUBE ------------ + + +void QuickShapes::initCube() { + GLfloat vertices[] = { + 1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f,-1.0f, 1.0f, // v0-v1-v2 (front) + -1.0f,-1.0f, 1.0f, 1.0f,-1.0f, 1.0f, 1.0f, 1.0f, 1.0f, // v2-v3-v0 + + 1.0f, 1.0f, 1.0f, 1.0f,-1.0f, 1.0f, 1.0f,-1.0f,-1.0f, // v0-v3-v4 (right) + 1.0f,-1.0f,-1.0f, 1.0f, 1.0f,-1.0f, 1.0f, 1.0f, 1.0f, // v4-v5-v0 + + 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,-1.0f, -1.0f, 1.0f,-1.0f, // v0-v5-v6 (top) + -1.0f, 1.0f,-1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, // v6-v1-v0 + + -1.0f, 1.0f, 1.0f, -1.0f, 1.0f,-1.0f, -1.0f,-1.0f,-1.0f, // v1-v6-v7 (left) + -1.0f,-1.0f,-1.0f, -1.0f,-1.0f, 1.0f, -1.0f, 1.0f, 1.0f, // v7-v2-v1.0 + + -1.0f,-1.0f,-1.0f, 1.0f,-1.0f,-1.0f, 1.0f,-1.0f, 1.0f, // v7-v4-v3 (bottom) + 1.0f,-1.0f, 1.0f, -1.0f,-1.0f, 1.0f, -1.0f,-1.0f,-1.0f, // v3-v2-v7 + + 1.0f,-1.0f,-1.0f, -1.0f,-1.0f,-1.0f, -1.0f, 1.0f,-1.0f, // v4-v7-v6 (back) + -1.0f, 1.0f,-1.0f, 1.0f, 1.0f,-1.0f, 1.0f,-1.0f,-1.0f // v6-v5-v4 + }; + + GLfloat normals[] = { + 0, 0, 1, 0, 0, 1, 0, 0, 1, // v0-v1-v2 (front) + 0, 0, 1, 0, 0, 1, 0, 0, 1, // v2-v3-v0 + + 1, 0, 0, 1, 0, 0, 1, 0, 0, // v0-v3-v4 (right) + 1, 0, 0, 1, 0, 0, 1, 0, 0, // v4-v5-v0 + + 0, 1, 0, 0, 1, 0, 0, 1, 0, // v0-v5-v6 (top) + 0, 1, 0, 0, 1, 0, 0, 1, 0, // v6-v1-v0 + + -1, 0, 0, -1, 0, 0, -1, 0, 0, // v1-v6-v7 (left) + -1, 0, 0, -1, 0, 0, -1, 0, 0, // v7-v2-v1 + + 0,-1, 0, 0,-1, 0, 0,-1, 0, // v7-v4-v3 (bottom) + 0,-1, 0, 0,-1, 0, 0,-1, 0, // v3-v2-v7 + + 0, 0,-1, 0, 0,-1, 0, 0,-1, // v4-v7-v6 (back) + 0, 0,-1, 0, 0,-1, 0, 0,-1 // v6-v5-v4 + }; + + cubeMesh_.SetVertices(vertices, 36); + cubeMesh_.SetNormals(normals, 36); + cubeMesh_.UpdateGPUMemory(); +} + + +void QuickShapes::DrawCube(const Matrix4 &modelMatrix, const Matrix4 &viewMatrix, + const Matrix4 &projectionMatrix, const Color &color) +{ + if (cubeMesh_.num_vertices() == 0) { + initCube(); + } + defaultMaterial_.ambient_reflectance = color; + defaultMaterial_.diffuse_reflectance = color; + defaultMaterial_.surface_texture = emptyTex_; + defaultShader_.Draw(modelMatrix, viewMatrix, projectionMatrix, &cubeMesh_, defaultMaterial_); +} + + + + +// ------------ SQUARE ------------ + + +void QuickShapes::initSquare() { + GLfloat vertices[] = { + 1.0f, 0.0f, 1.0f, 1.0f, 0.0f,-1.0f, -1.0f, 0.0f,-1.0f, // v0-v5-v6 (top) + -1.0f, 0.0f,-1.0f, -1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f // v6-v1-v0 + }; + + GLfloat normals[] = { + 0, 1, 0, 0, 1, 0, 0, 1, 0, + 0, 1, 0, 0, 1, 0, 0, 1, 0 + }; + + GLfloat texcoords[] = { + 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f + }; + + squareMesh_.SetVertices(vertices, 6); + squareMesh_.SetNormals(normals, 6); + squareMesh_.SetTexCoords(0, texcoords, 6); + squareMesh_.UpdateGPUMemory(); +} + + +void QuickShapes::DrawSquare(const Matrix4 &modelMatrix, const Matrix4 &viewMatrix, + const Matrix4 &projectionMatrix, const Color &color) +{ + if (squareMesh_.num_vertices() == 0) { + initSquare(); + } + defaultMaterial_.ambient_reflectance = color; + defaultMaterial_.diffuse_reflectance = color; + defaultMaterial_.surface_texture = emptyTex_; + defaultShader_.Draw(modelMatrix, viewMatrix, projectionMatrix, &squareMesh_, defaultMaterial_); +} + + +void QuickShapes::DrawSquare(const Matrix4 &modelMatrix, const Matrix4 &viewMatrix, + const Matrix4 &projectionMatrix, const Color &color, + const Texture2D &tex) +{ + if (squareMesh_.num_vertices() == 0) { + initSquare(); + } + defaultMaterial_.ambient_reflectance = color; + defaultMaterial_.diffuse_reflectance = color; + defaultMaterial_.surface_texture = tex; + defaultShader_.Draw(modelMatrix, viewMatrix, projectionMatrix, &squareMesh_, defaultMaterial_); +} + + + + + +// ------------ CYLINDER ------------ + + +void QuickShapes::initCyl() { + + std::vector<Vertex> verts; + + Vertex top(0,1,0, 0,1,0); + Vertex bot(0,-1,0, 0,-1,0); + + const int nslices = 20; + for (int s=1; s<nslices+1; s++) { + int slast = s - 1; + GLfloat xlast = std::cosf(-TWOPI * (float)slast / (float)nslices); + GLfloat zlast = std::sinf(-TWOPI * (float)slast/(float)nslices); + GLfloat xnew = std::cosf(-TWOPI * (float)(s)/(float)nslices); + GLfloat znew = std::sinf(-TWOPI * (float)(s)/(float)nslices); + + // one triangle on the top + verts.push_back(top); + verts.push_back(Vertex(xlast,1,zlast, 0,1,0)); + verts.push_back(Vertex(xnew,1,znew, 0,1,0)); + + // two triangles to create a rect on the side + verts.push_back(Vertex(xlast,1,zlast, xlast,0,zlast)); + verts.push_back(Vertex(xlast,-1,zlast, xlast,0,zlast)); + verts.push_back(Vertex(xnew,1,znew, xnew,0,znew)); + + verts.push_back(Vertex(xnew,-1,znew, xnew,0,znew)); + verts.push_back(Vertex(xnew,1,znew, xnew,0,znew)); + verts.push_back(Vertex(xlast,-1,zlast, xlast,0,zlast)); + + // one triangle on the bottom + verts.push_back(bot); + verts.push_back(Vertex(xnew,-1,znew, 0,-1,0)); + verts.push_back(Vertex(xlast,-1,zlast, 0,-1,0)); + } + + std::vector<Point3> vertices; + std::vector<Vector3> normals; + for (int i=0; i<verts.size(); i++) { + vertices.push_back(Point3(verts[i].x, verts[i].y, verts[i].z)); + normals.push_back(Vector3(verts[i].nx, verts[i].ny, verts[i].nz)); + } + cylMesh_.SetVertices(vertices); + cylMesh_.SetNormals(normals); + cylMesh_.UpdateGPUMemory(); +} + + +void QuickShapes::DrawCylinder(const Matrix4 &modelMatrix, const Matrix4 &viewMatrix, + const Matrix4 &projectionMatrix, const Color &color) +{ + if (cylMesh_.num_vertices() == 0) { + initCyl(); + } + defaultMaterial_.ambient_reflectance = color; + defaultMaterial_.diffuse_reflectance = color; + defaultMaterial_.surface_texture = emptyTex_; + defaultShader_.Draw(modelMatrix, viewMatrix, projectionMatrix, &cylMesh_, defaultMaterial_); +} + + + + +// ------------ CONE ------------ + + +void QuickShapes::initCone() { + + std::vector<Vertex> verts; + + Vertex top(0,1,0, 0,1,0); + Vertex bot(0,-1,0, 0,-1,0); + + const int nslices = 20; + for (int s=1; s<nslices+1; s++) { + int slast = s - 1; + GLfloat xlast = std::cosf(-TWOPI * (float)slast/(float)nslices); + GLfloat zlast = std::sinf(-TWOPI * (float)slast/(float)nslices); + GLfloat xnew = std::cosf(-TWOPI * (float)(s)/(float)nslices); + GLfloat znew = std::sinf(-TWOPI * (float)(s)/(float)nslices); + + // one triangle on the side + // normals are a bit more complex than for other shapes... + Vector3 nlast = Vector3(xlast, 2, zlast).ToUnit(); + Vector3 nnew = Vector3(xnew, 2, znew).ToUnit(); + Vector3 ntop = 0.5*(nlast + nnew); + + verts.push_back(Vertex(top.x, top.y, top.z, ntop[0], ntop[1], ntop[2])); + verts.push_back(Vertex(xlast,-1,zlast, nlast[0], nlast[1], nlast[2])); + verts.push_back(Vertex(xnew,-1,znew, nnew[0], nnew[1], nnew[2])); + + // one triangle on the bottom + verts.push_back(bot); + verts.push_back(Vertex(xnew,-1,znew, 0,-1,0)); + verts.push_back(Vertex(xlast,-1,zlast, 0,-1,0)); + } + + std::vector<Point3> vertices; + std::vector<Vector3> normals; + for (int i = 0; i < verts.size(); i++) { + vertices.push_back(Point3(verts[i].x, verts[i].y, verts[i].z)); + normals.push_back(Vector3(verts[i].nx, verts[i].ny, verts[i].nz)); + } + + coneMesh_.SetVertices(vertices); + coneMesh_.SetNormals(normals); + coneMesh_.UpdateGPUMemory(); +} + + +void QuickShapes::DrawCone(const Matrix4 &modelMatrix, const Matrix4 &viewMatrix, + const Matrix4 &projectionMatrix, const Color &color) +{ + if (coneMesh_.num_vertices() == 0) { + initCone(); + } + defaultMaterial_.ambient_reflectance = color; + defaultMaterial_.diffuse_reflectance = color; + defaultMaterial_.surface_texture = emptyTex_; + defaultShader_.Draw(modelMatrix, viewMatrix, projectionMatrix, &coneMesh_, defaultMaterial_); +} + + + + + +// ------------ SPHERE ------------ + + +void QuickShapes::initSph() { + + std::vector<Vertex> verts; + + Vertex top(0,1,0, 0,1,0); + Vertex bot(0,-1,0, 0,-1,0); + + const int nslices = 40; + const int nstacks = 40; + for (int s=1; s<nslices+1; s++) { + int slast = s - 1; + GLfloat xlast = std::cosf(-TWOPI * (float)slast/(float)nslices); + GLfloat zlast = std::sinf(-TWOPI * (float)slast/(float)nslices); + GLfloat xnew = std::cosf(-TWOPI * (float)(s)/(float)nslices); + GLfloat znew = std::sinf(-TWOPI * (float)(s)/(float)nslices); + + float stackstep = PI/(float)nstacks; + + // one triangle on the top + verts.push_back(top); + verts.push_back(Vertex(std::sinf(stackstep)*xlast,std::cosf(stackstep),std::sinf(stackstep)*zlast, + std::sinf(stackstep)*xlast,std::cosf(stackstep),std::sinf(stackstep)*zlast)); + verts.push_back(Vertex(std::sinf(stackstep)*xnew,std::cosf(stackstep),std::sinf(stackstep)*znew, + std::sinf(stackstep)*xnew,std::cosf(stackstep),std::sinf(stackstep)*znew)); + + for (int t=2; t<nstacks; t++) { + int tlast = t - 1; + GLfloat ylast = std::cosf(PI*(float)(tlast)/(float)nstacks); + GLfloat ynew = std::cosf(PI*(float)(t)/(float)nstacks); + + GLfloat rlast = std::sinf(PI * (float)(tlast)/(float)nstacks); + GLfloat rnew = std::sinf(PI * (float)(t)/(float)nstacks); + + // two triangles to create a rect on the side + verts.push_back(Vertex(rlast*xlast,ylast,rlast*zlast, rlast*xlast,ylast,rlast*zlast)); + verts.push_back(Vertex(rnew*xlast,ynew,rnew*zlast, rnew*xlast,ynew,rnew*zlast)); + verts.push_back(Vertex(rnew*xnew,ynew,rnew*znew, rnew*xnew,ynew,rnew*znew)); + + verts.push_back(Vertex(rnew*xnew,ynew,rnew*znew, rnew*xnew,ynew,rnew*znew)); + verts.push_back(Vertex(rlast*xnew,ylast,rlast*znew, rlast*xnew,ylast,rlast*znew)); + verts.push_back(Vertex(rlast*xlast,ylast,rlast*zlast, rlast*xlast,ylast,rlast*zlast)); + } + + // one triangle on the bottom + verts.push_back(bot); + verts.push_back(Vertex(std::sinf(stackstep)*xnew,std::cosf(PI-stackstep),std::sinf(stackstep)*znew, + std::sinf(stackstep)*xnew,std::cosf(PI-stackstep),std::sinf(stackstep)*znew)); + verts.push_back(Vertex(std::sinf(stackstep)*xlast,std::cosf(PI-stackstep),std::sinf(stackstep)*zlast, + std::sinf(stackstep)*xlast,std::cosf(PI-stackstep),std::sinf(stackstep)*zlast)); + } + + std::vector<Point3> vertices; + std::vector<Vector3> normals; + for (int i = 0; i < verts.size(); i++) { + vertices.push_back(Point3(verts[i].x, verts[i].y, verts[i].z)); + normals.push_back(Vector3(verts[i].nx, verts[i].ny, verts[i].nz)); + } + sphereMesh_.SetVertices(vertices); + sphereMesh_.SetNormals(normals); + sphereMesh_.UpdateGPUMemory(); +} + + +void QuickShapes::DrawSphere(const Matrix4 &modelMatrix, const Matrix4 &viewMatrix, + const Matrix4 &projectionMatrix, const Color &color) +{ + if (sphereMesh_.num_vertices() == 0) { + initSph(); + } + defaultMaterial_.ambient_reflectance = color; + defaultMaterial_.diffuse_reflectance = color; + defaultMaterial_.surface_texture = emptyTex_; + defaultShader_.Draw(modelMatrix, viewMatrix, projectionMatrix, &sphereMesh_, defaultMaterial_); +} + + + + +// ------------ BRUSH ------------ + + +void QuickShapes::initBrush() { + + // Raw vertices -- points that make up the brush geometry + const GLfloat v[19][3] = { + { 0.5f, 0.0f, 0.0f}, // 0 + {-0.5f, 0.0f, 0.0f}, // 1 + + { 0.5f, 0.1f, 0.25f}, // 2 + {-0.5f, 0.1f, 0.25f}, // 3 + { 0.5f, 0.1f, 0.75f}, // 4 + {-0.5f, 0.1f, 0.75f}, // 5 + { 0.1f, 0.06f, 1.0f}, // 6 + {-0.1f, 0.06f, 1.0f}, // 7 + { 0.15f, 0.1f, 1.75f}, // 8 + {-0.15f, 0.1f, 1.75f}, // 9 + + { 0.0f, 0.0f, 1.85f}, // 10 + + { 0.5f, -0.1f, 0.25f}, // 11 + {-0.5f, -0.1f, 0.25f}, // 12 + { 0.5f, -0.1f, 0.75f}, // 13 + {-0.5f, -0.1f, 0.75f}, // 14 + { 0.1f, -0.06f, 1.0f}, // 15 + {-0.1f, -0.06f, 1.0f}, // 16 + { 0.15f, -0.1f, 1.75f}, // 17 + {-0.15f, -0.1f, 1.75f} // 18 + }; + + + // Vertices arranged into triangles + const GLfloat verts[34][3][3] = { + // top + {{v[0][0], v[0][1], v[0][2]}, {v[1][0], v[1][1], v[1][2]}, {v[2][0], v[2][1], v[2][2]}}, + {{v[1][0], v[1][1], v[1][2]}, {v[3][0], v[3][1], v[3][2]}, {v[2][0], v[2][1], v[2][2]}}, + + {{v[2][0], v[2][1], v[2][2]}, {v[3][0], v[3][1], v[3][2]}, {v[4][0], v[4][1], v[4][2]}}, + {{v[3][0], v[3][1], v[3][2]}, {v[5][0], v[5][1], v[5][2]}, {v[4][0], v[4][1], v[4][2]}}, + + {{v[4][0], v[4][1], v[4][2]}, {v[5][0], v[5][1], v[5][2]}, {v[6][0], v[6][1], v[6][2]}}, + {{v[5][0], v[5][1], v[5][2]}, {v[7][0], v[7][1], v[7][2]}, {v[6][0], v[6][1], v[6][2]}}, + + {{v[6][0], v[6][1], v[6][2]}, {v[7][0], v[7][1], v[7][2]}, {v[8][0], v[8][1], v[8][2]}}, + {{v[7][0], v[7][1], v[7][2]}, {v[9][0], v[9][1], v[9][2]}, {v[8][0], v[8][1], v[8][2]}}, + + {{v[8][0], v[8][1], v[8][2]}, {v[9][0], v[9][1], v[9][2]}, {v[10][0], v[10][1], v[10][2]}}, + + // bottom + {{v[0][0], v[0][1], v[0][2]}, {v[12][0], v[12][1], v[12][2]}, {v[1][0], v[1][1], v[1][2]}}, + {{v[11][0], v[11][1], v[11][2]}, {v[12][0], v[12][1], v[12][2]}, {v[0][0], v[0][1], v[0][2]}}, + + {{v[11][0], v[11][1], v[11][2]}, {v[14][0], v[14][1], v[14][2]}, {v[12][0], v[12][1], v[12][2]}}, + {{v[13][0], v[13][1], v[13][2]}, {v[14][0], v[14][1], v[14][2]}, {v[11][0], v[11][1], v[11][2]}}, + + {{v[13][0], v[13][1], v[13][2]}, {v[16][0], v[16][1], v[16][2]}, {v[14][0], v[14][1], v[14][2]}}, + {{v[15][0], v[15][1], v[15][2]}, {v[16][0], v[16][1], v[16][2]}, {v[13][0], v[13][1], v[13][2]}}, + + {{v[15][0], v[15][1], v[15][2]}, {v[18][0], v[18][1], v[18][2]}, {v[16][0], v[16][1], v[16][2]}}, + {{v[17][0], v[17][1], v[17][2]}, {v[18][0], v[18][1], v[18][2]}, {v[15][0], v[15][1], v[15][2]}}, + + {{v[18][0], v[18][1], v[18][2]}, {v[17][0], v[17][1], v[17][2]}, {v[10][0], v[10][1], v[10][2]}}, + + // one side + {{v[11][0], v[11][1], v[11][2]}, {v[0][0], v[0][1], v[0][2]}, {v[2][0], v[2][1], v[2][2]}}, + + {{v[11][0], v[11][1], v[11][2]}, {v[2][0], v[2][1], v[2][2]}, {v[4][0], v[4][1], v[4][2]}}, + {{v[4][0], v[4][1], v[4][2]}, {v[13][0], v[13][1], v[13][2]}, {v[11][0], v[11][1], v[11][2]}}, + + {{v[13][0], v[13][1], v[13][2]}, {v[4][0], v[4][1], v[4][2]}, {v[6][0], v[6][1], v[6][2]}}, + {{v[6][0], v[6][1], v[6][2]}, {v[15][0], v[15][1], v[15][2]}, {v[13][0], v[13][1], v[13][2]}}, + + {{v[15][0], v[15][1], v[15][2]}, {v[6][0], v[6][1], v[6][2]}, {v[8][0], v[8][1], v[8][2]}}, + {{v[8][0], v[8][1], v[8][2]}, {v[17][0], v[17][1], v[17][2]}, {v[15][0], v[15][1], v[15][2]}}, + + {{v[17][0], v[17][1], v[17][2]}, {v[8][0], v[8][1], v[8][2]}, {v[10][0], v[10][1], v[10][2]}}, + + // other side + {{v[3][0], v[3][1], v[3][2]}, {v[1][0], v[1][1], v[1][2]}, {v[12][0], v[12][1], v[12][2]}}, + + {{v[3][0], v[3][1], v[3][2]}, {v[12][0], v[12][1], v[12][2]}, {v[14][0], v[14][1], v[14][2]}}, + {{v[14][0], v[14][1], v[14][2]}, {v[5][0], v[5][1], v[5][2]}, {v[3][0], v[3][1], v[3][2]}}, + + {{v[5][0], v[5][1], v[5][2]}, {v[14][0], v[14][1], v[14][2]}, {v[16][0], v[16][1], v[16][2]}}, + {{v[16][0], v[16][1], v[16][2]}, {v[7][0], v[7][1], v[7][2]}, {v[5][0], v[5][1], v[5][2]}}, + + {{v[7][0], v[7][1], v[7][2]}, {v[16][0], v[16][1], v[16][2]}, {v[18][0], v[18][1], v[18][2]}}, + {{v[18][0], v[18][1], v[18][2]}, {v[9][0], v[9][1], v[9][2]}, {v[7][0], v[7][1], v[7][2]}}, + + {{v[9][0], v[9][1], v[9][2]}, {v[18][0], v[18][1], v[18][2]}, {v[10][0], v[10][1], v[10][2]}} + + }; + + + // Normals defined so as to make each face of the brush a flat surface + const GLfloat norms[34][3][3] = { + // top + {{0.0f, 0.93f, -0.37f}, {0.0f, 0.93f, -0.37f}, {0.0f, 0.93f, -0.37f}}, + {{0.0f, 0.93f, -0.37f}, {0.0f, 0.93f, -0.37f}, {0.0f, 0.93f, -0.37f}}, + + {{0.0f, 1.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {0.0f, 1.0f, 0.0f}}, + {{0.0f, 1.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {0.0f, 1.0f, 0.0f}}, + + {{0.0f, 0.988f, 0.158f}, {0.0f, 0.988f, 0.158f}, {0.0f, 0.988f, 0.158f}}, + {{0.0f, 0.988f, 0.158f}, {0.0f, 0.988f, 0.158f}, {0.0f, 0.988f, 0.158f}}, + + {{0.0f, 0.999f, -0.0533f}, {0.0f, 0.999f, -0.0533f}, {0.0f, 0.999f, -0.0533f}}, + {{0.0f, 0.999f, -0.0533f}, {0.0f, 0.999f, -0.0533f}, {0.0f, 0.999f, -0.0533f}}, + + {{0.0f, 0.709f, 0.709f}, {0.0f, 0.709f, 0.709f}, {0.0f, 0.709f, 0.709f}}, + + // bottom + {{0.0f, -0.93f, -0.37f}, {0.0f, -0.93f, -0.37f}, {0.0f, -0.93f, -0.37f}}, + {{0.0f, -0.93f, -0.37f}, {0.0f, -0.93f, -0.37f}, {0.0f, -0.93f, -0.37f}}, + + {{0.0f, -1.0f, 0.0f}, {0.0f, -1.0f, 0.0f}, {0.0f, -1.0f, 0.0f}}, + {{0.0f, -1.0f, 0.0f}, {0.0f, -1.0f, 0.0f}, {0.0f, -1.0f, 0.0f}}, + + {{0.0f, -0.988f, 0.158f}, {0.0f, -0.988f, 0.158f}, {0.0f, -0.988f, 0.158f}}, + {{0.0f, -0.988f, 0.158f}, {0.0f, -0.988f, 0.158f}, {0.0f, -0.988f, 0.158f}}, + + {{0.0f, -0.999f, -0.0533f}, {0.0f, -0.999f, -0.0533f}, {0.0f, -0.999f, -0.0533f}}, + {{0.0f, -0.999f, -0.0533f}, {0.0f, -0.999f, -0.0533f}, {0.0f, -0.999f, -0.0533f}}, + + {{0.0f, -0.709f, 0.709f}, {0.0f, -0.709f, 0.709f}, {0.0f, -0.709f, 0.709f}}, + + // one side + {{1.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}}, + + {{1.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}}, + {{1.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}}, + + {{0.848f, 0.0f, 0.530f}, {0.848f, 0.0f, 0.530f}, {0.848f, 0.0f, 0.530f}}, + {{0.848f, 0.0f, 0.530f}, {0.848f, 0.0f, 0.530f}, {0.848f, 0.0f, 0.530f}}, + + {{1.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}}, + {{1.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}}, + + {{0.709f, 0.0f, 0.709f}, {0.709f, 0.0f, 0.709f}, {0.709f, 0.0f, 0.709f}}, + + // other side + {{-1.0f, 0.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}}, + + {{-1.0f, 0.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}}, + {{-1.0f, 0.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}}, + + {{-0.848f, 0.0f, 0.530f}, {-0.848f, 0.0f, 0.530f}, {-0.848f, 0.0f, 0.530f}}, + {{-0.848f, 0.0f, 0.530f}, {-0.848f, 0.0f, 0.530f}, {-0.848f, 0.0f, 0.530f}}, + + {{-1.0f, 0.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}}, + {{-1.0f, 0.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}}, + + {{-0.709f, 0.0f, 0.709f}, {-0.709f, 0.0f, 0.709f}, {-0.709f, 0.0f, 0.709f}} + }; + + brushMesh_.SetVertices((float*)verts, 102); + brushMesh_.SetNormals((float*)norms, 102); + brushMesh_.UpdateGPUMemory(); +} + + +void QuickShapes::DrawBrush(const Matrix4 &modelMatrix, const Matrix4 &viewMatrix, + const Matrix4 &projectionMatrix, const Color &color) +{ + if (brushMesh_.num_vertices() == 0) { + initBrush(); + } + defaultMaterial_.ambient_reflectance = color; + defaultMaterial_.diffuse_reflectance = color; + defaultMaterial_.surface_texture = emptyTex_; + defaultShader_.Draw(modelMatrix, viewMatrix, projectionMatrix, &brushMesh_, defaultMaterial_); +} + + + +// ---------------- + + +void QuickShapes::DrawLineSegment(const Matrix4 &modelMatrix, + const Matrix4 &viewMatrix, + const Matrix4 &projectionMatrix, + const Color &color, + const Point3 &p1, + const Point3 &p2, + float radius) +{ + Matrix4 S = Matrix4::Scale(Vector3(radius, 0.5f*(p2-p1).Length(), radius)); + Vector3 y = (p2-p1).ToUnit(); + Vector3 z = Vector3(1,0,0).Cross(y).ToUnit(); + if (z == Vector3(0,0,0)) { + z = Vector3(0,0,1).Cross(y).ToUnit(); + } + Vector3 x = y.Cross(z); + Matrix4 R = Matrix4::FromRowMajorElements( + x[0], y[0], z[0], 0, + x[1], y[1], z[1], 0, + x[2], y[2], z[2], 0, + 0, 0, 0, 1 + ); + Matrix4 T = Matrix4::Translation(0.5 * Vector3(p1[0]+p2[0], p1[1]+p2[1], p1[2]+p2[2])); + + Matrix4 M = T * R * S; + + DrawCylinder(modelMatrix * M, viewMatrix, projectionMatrix, color); +} + + + +void QuickShapes::DrawLines(const Matrix4 &modelMatrix, + const Matrix4 &viewMatrix, + const Matrix4 &projectionMatrix, + const Color &color, + const std::vector<Point3> &points, + LinesType ltype, + float radius) +{ + if (ltype == LinesType::LINES) { + for (size_t i=0; i<points.size(); i+=2) { + DrawLineSegment(modelMatrix, viewMatrix, projectionMatrix, color, points[i], points[i+1], radius); + } + } + else { + for (size_t i=0; i<points.size()-1; i++) { + DrawLineSegment(modelMatrix, viewMatrix, projectionMatrix, color, points[i], points[i+1], radius); + } + if (ltype == LinesType::LINE_LOOP) { + DrawLineSegment(modelMatrix, viewMatrix, projectionMatrix, color, points[points.size()-1], points[0], radius); + } + } +} + + + +void QuickShapes::DrawArrow(const Matrix4 &modelMatrix, + const Matrix4 &viewMatrix, + const Matrix4 &projectionMatrix, + const Color &color, + Point3 p, Vector3 dir, float radius) +{ + float d = dir.Length() - 8.0f*radius; + DrawLineSegment(modelMatrix, viewMatrix, projectionMatrix, color, p, p + d*dir.ToUnit(), radius); + + Matrix4 S = Matrix4::Scale(Vector3(radius*3.0f, radius*4.0f, radius*3.0f)); + Vector3 y = dir.ToUnit(); + Vector3 z = Vector3(1,0,0).Cross(y).ToUnit(); + if (z == Vector3(0,0,0)) { + z = Vector3(0,0,1).Cross(y).ToUnit(); + } + Vector3 x = y.Cross(z); + Matrix4 R = Matrix4::FromRowMajorElements( + x[0], y[0], z[0], 0, + x[1], y[1], z[1], 0, + x[2], y[2], z[2], 0, + 0, 0, 0, 1 + ); + Matrix4 T = Matrix4::Translation((p + d*dir.ToUnit()) - Point3::Origin()); + + Matrix4 M = T * R * S * Matrix4::Translation(Vector3(0,1,0)); + + DrawCone(modelMatrix * M, viewMatrix, projectionMatrix, color); +} + + +void QuickShapes::DrawAxes(const Matrix4 &modelMatrix, + const Matrix4 &viewMatrix, + const Matrix4 &projectionMatrix) +{ + DrawArrow(modelMatrix, viewMatrix, projectionMatrix, Color(1.0f, 0.6f, 0.6f), Point3::Origin(), Vector3::UnitX(), 0.02f); + DrawArrow(modelMatrix, viewMatrix, projectionMatrix, Color(0.6f, 1.0f, 0.6f), Point3::Origin(), Vector3::UnitY(), 0.02f); + DrawArrow(modelMatrix, viewMatrix, projectionMatrix, Color(0.6f, 0.6f, 1.0f), Point3::Origin(), Vector3::UnitZ(), 0.02f); + +} + + +void QuickShapes::initFull() { + GLfloat vertices[] = { + -1, -1, 0, 1, -1, 0, 1, 1, 0, + -1, -1, 0, 1, 1, 0, -1, 1, 0 + }; + + GLfloat normals[] = { + 0, 0, 1, 0, 0, 1, 0, 0, 1, + 0, 0, 1, 0, 0, 1, 0, 0, 1 + }; + + GLfloat texcoords[] = { + 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, + 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f + }; + + fullMesh_.SetVertices(vertices, 6); + fullMesh_.SetNormals(normals, 6); + fullMesh_.SetTexCoords(0, texcoords, 6); + fullMesh_.UpdateGPUMemory(); +} + + +void QuickShapes::DrawFullscreenTexture(const Color &color, const Texture2D &tex) { + if (fullMesh_.num_vertices() == 0) { + initFull(); + } + DrawWithFullscreen(color, &fullMesh_, tex); +} + + + + + +void QuickShapes::DrawWithFullscreen(const Color &color, Mesh *mesh, const Texture2D &tex) { + if (!fullscreenShader_.initialized()) { + fullscreenShader_.AddVertexShaderFromFile(Platform::FindMinGfxShaderFile("fullscreen.vert")); + fullscreenShader_.AddFragmentShaderFromFile(Platform::FindMinGfxShaderFile("fullscreen.frag")); + fullscreenShader_.LinkProgram(); + } + + glDisable(GL_DEPTH_TEST); + glDepthMask(GL_FALSE); + + // Activate the shader program + fullscreenShader_.UseProgram(); + + // Pass uniforms and textures from C++ to the GPU Shader Program + fullscreenShader_.SetUniform("TintColor", color); + fullscreenShader_.BindTexture("SurfaceTexture", tex); + + // Draw the mesh using the shader program + mesh->Draw(); + + // Deactivate the shader program + fullscreenShader_.StopProgram(); + + glEnable(GL_DEPTH_TEST); + glDepthMask(GL_TRUE); +} + + +DefaultShader* QuickShapes::default_shader() { + return &defaultShader_; +} + + +DefaultShader::MaterialProperties* QuickShapes::material() { + return &defaultMaterial_; +} + + +} // end namespace diff --git a/dev/MinGfx/src/quick_shapes.h b/dev/MinGfx/src/quick_shapes.h new file mode 100644 index 0000000..c38ac52 --- /dev/null +++ b/dev/MinGfx/src/quick_shapes.h @@ -0,0 +1,254 @@ +/* + This file is part of the MinGfx Project. + + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + + Original Author(s) of this File: + Dan Keefe, 2017, University of Minnesota + + Author(s) of Significant Updates/Modifications to the File: + ... + */ + +#ifndef SRC_QUICK_SHAPES_H_ +#define SRC_QUICK_SHAPES_H_ + +#include "color.h" +#include "default_shader.h" +#include "mesh.h" +#include "point3.h" +#include "shader_program.h" +#include "texture2d.h" +#include "vector3.h" +#include "matrix4.h" + +#include <vector> + + + +namespace mingfx { + +/** This class provides a quick way to draw shapes for use in debugging or + simple scenes. You can specify the color for each shape as part of the + Draw...() call. Other lighting parameters (the intensity of the light, + advanced material properties) are pre-set to reasonable defaults that apply + to all of the shapes drawn. You can edit these if you wish, but note that + the intent of this class is just to provide a quick way to draw shapes -- + this is not the right tool to use if you wish to do quality renderings and + use multiple types of materials. + + Example usage: + ~~~ + // define a new QuickShapes object during initialization, or as a class + // member variable + QuickShapes quick_shapes; + + + void DrawUsingOpenGL() { + // later, in your draw routine, use it to draw shapes + Matrix4 view = Matrix4::LookAt(Point3(0,0,3), Point3(0,0,0), Vector3(0,1,0)); + Matrix4 proj = Matrix4::Perspective(60.0, aspect_ratio(), 0.1, 10.0); + + Matrix4 m_cube = Matrix4::Translation(Vector3(-2.5,0,0)) * Matrix4::Scale(Vector3(0.5, 0.5, 0.5)); + quick_shapes.DrawCube(m_cube, view, proj, Color(1,1,1)); + + Matrix4 m_sphere = Matrix4::Scale(Vector3(2.5, 2.5, 2.5)); + quick_shapes.DrawSphere(m_sphere, view, proj, Color(1,1,1)); + + Matrix4 m_loop; + std::vector<Point3> loop; + loop.push_back(Point3( 4.0, 4.0, -4.0)); + loop.push_back(Point3(-4.0, 4.0, -4.0)); + loop.push_back(Point3(-4.0, 4.0, 4.0)); + loop.push_back(Point3( 4.0, 4.0, 4.0)); + quick_shapes.DrawLines(m_loop, view, proj, Color(1,1,1), loop, QuickShapes::LinesType::LINE_LOOP, 0.1); + } + ~~~ + */ +class QuickShapes { +public: + + QuickShapes(); + virtual ~QuickShapes(); + + + // -------- 3D PRIMITIVES -------- + + /** Draws a cube with extents -1 to 1 given the model, view, and projection + matrices provided and using the supplied color as a material property. + */ + void DrawCube(const Matrix4 &modelMatrix, + const Matrix4 &viewMatrix, + const Matrix4 &projectionMatrix, + const Color &color); + + /** Draws a cylinder with radius 1 and height y=-1 to 1 given the model, + view, and projection matrices provided and using the supplied color as a + material property. + */ + void DrawCylinder(const Matrix4 &modelMatrix, + const Matrix4 &viewMatrix, + const Matrix4 &projectionMatrix, + const Color &color); + + /** Draws a cone with radius 1 and height y=-1 to 1 given the model, + view, and projection matrices provided and using the supplied color as a + material property. + */ + void DrawCone(const Matrix4 &modelMatrix, + const Matrix4 &viewMatrix, + const Matrix4 &projectionMatrix, + const Color &color); + + /** Draws a sphere with radius 1 given the model, view, and projection + matrices provided and using the supplied color as a material property. + */ + void DrawSphere(const Matrix4 &modelMatrix, + const Matrix4 &viewMatrix, + const Matrix4 &projectionMatrix, + const Color &color); + + /** Draws the classic 3D paintbrush cursor from the 2001 Keefe et al. + CavePainting paper. The tip of the brush is at (0,0,0), the front + flat edge runs along the X axis, and the handle runs in the +Z direction. + */ + void DrawBrush(const Matrix4 &modelMatrix, + const Matrix4 &viewMatrix, + const Matrix4 &projectionMatrix, + const Color &color); + + + // -------- 3D COMPOSITE SHAPES -------- + + /** Draws a cylinder between the two points. + */ + void DrawLineSegment(const Matrix4 &modelMatrix, + const Matrix4 &viewMatrix, + const Matrix4 &projectionMatrix, + const Color &color, + const Point3 &p1, + const Point3 &p2, + float radius); + + enum class LinesType { + LINES, + LINE_STRIP, + LINE_LOOP + }; + + /** Draws a series of line segments. Using linesType=LINES connects each + consecutive pair of points in the points array with a line. A linesType=LINE_STRIP + will connect each point to the next. And, a linesType=LINE_LOOP will connect + each point to the next and in addition connect the last to the first. Example: + ~~~ + Matrix4 model; + Matrix4 view = Matrix4::LookAt(Point3(0,0,3), Point3(0,0,0), Vector3(0,1,0)); + Matrix4 proj = Matrix4::Perspective(60.0, aspect_ratio(), 0.1, 10.0); + std::vector<Point3> loop; + loop.push_back(Point3( 4.0, 4.0, -4.0)); + loop.push_back(Point3(-4.0, 4.0, -4.0)); + loop.push_back(Point3(-4.0, 4.0, 4.0)); + loop.push_back(Point3( 4.0, 4.0, 4.0)); + quick_shapes.DrawLines(model, view, proj, Color(1,1,1), loop, QuickShapes::LinesType::LINE_LOOP, 0.1); + ~~~ + */ + void DrawLines(const Matrix4 &modelMatrix, + const Matrix4 &viewMatrix, + const Matrix4 &projectionMatrix, + const Color &color, + const std::vector<Point3> &points, + LinesType linesType, + float radius); + + /** Draws an arrow originating at point p and extending in the direction and + length specified by dir. radius is the radius of the cylinder used to + draw the shaft of the arrow. + */ + void DrawArrow(const Matrix4 &modelMatrix, + const Matrix4 &viewMatrix, + const Matrix4 &projectionMatrix, + const Color &color, + Point3 p, Vector3 dir, float radius); + + /** Draws a right handed set of axes at the coordinate frame specified by + the modelMatrix. The arrows are 1 unit in length and colored based + on the axis: X=red, Y=green, Z=blue. + */ + void DrawAxes(const Matrix4 &modelMatrix, + const Matrix4 &viewMatrix, + const Matrix4 &projectionMatrix); + + + // -------- 2D PRIMITIVES -------- + + /** Draws a square in the X-Y plane with extents -1 to 1 and normal in the + +Y direction. Uses the model, view, and projection matrices provided + and the supplied color as a material property. + */ + void DrawSquare(const Matrix4 &modelMatrix, const Matrix4 &viewMatrix, + const Matrix4 &projectionMatrix, const Color &color); + + /** Draws a square, which you can deform into some other shape by adjusting + the model matrix, and applies a texture to it. The texture must already + be bound to the OpenGL textureID provided. The square lies in the X-Y + plane with extents -1 to 1 and normal in the +Y direction. No lighting + is applied. + */ + void DrawSquare(const Matrix4 &modelMatrix, const Matrix4 &viewMatrix, + const Matrix4 &projectionMatrix, const Color &color, + const Texture2D &texture); + + /** Draws a background texture across the whole screen. Typically, you will + want to do this before any other draw calls. + */ + void DrawFullscreenTexture(const Color &color, const Texture2D &texture); + + + /** Returns a pointer to the default shader used internally by the Draw class + so that you may change the default lighting properties if you wish. + */ + DefaultShader* default_shader(); + + /** Returns a pointer to the default material properties for the shapes so + that you may adjust the reflectance properties used by all the shapes + if needed. + */ + DefaultShader::MaterialProperties* material(); + + +private: + + void DrawWithFullscreen(const Color &color, Mesh *mesh, const Texture2D &tex); + + Mesh cubeMesh_; + void initCube(); + + Mesh squareMesh_; + void initSquare(); + + Mesh fullMesh_; + void initFull(); + + Mesh cylMesh_; + void initCyl(); + + Mesh coneMesh_; + void initCone(); + + Mesh sphereMesh_; + void initSph(); + + Mesh brushMesh_; + void initBrush(); + + DefaultShader defaultShader_; + DefaultShader::MaterialProperties defaultMaterial_; + Texture2D emptyTex_; + + ShaderProgram fullscreenShader_; +}; + +} // end namespace + +#endif
\ No newline at end of file diff --git a/dev/MinGfx/src/ray.cc b/dev/MinGfx/src/ray.cc new file mode 100644 index 0000000..5d406ef --- /dev/null +++ b/dev/MinGfx/src/ray.cc @@ -0,0 +1,313 @@ +/* + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + See corresponding header file for details. + */ + +#include "ray.h" + +namespace mingfx { + + Ray::Ray() : p_(Point3::Origin()), d_(-Vector3::UnitZ()) { + } + + Ray::Ray(const Point3 &origin, const Vector3 &direction) : p_(origin), d_(direction) { + } + + Ray::~Ray() { + } + + + bool Ray::operator==(const Ray& other) const { + return (p_ == other.origin()) && (d_ == other.direction()); + } + + + bool Ray::operator!=(const Ray& other) const { + return (p_ != other.origin()) || (d_ != other.direction()); + } + + + float Ray::Length() const { + return d_.Length(); + } + + + Point3 Ray::origin() const { + return p_; + } + + + Vector3 Ray::direction() const { + return d_; + } + + + + bool Ray::IntersectPlane(const Point3 &planePt, const Vector3 &planeNormal, + float *iTime, Point3 *iPoint) const + { + float dot = planeNormal.Dot(d_); + + // return false if we would hit the back face of the plane + if (dot > 0.0) { + return false; + } + + // return false if the ray and plane are parallel + if (std::fabs(dot) < MINGFX_MATH_EPSILON) { + return false; + } + + float denom = planeNormal.Dot(d_); + if (std::abs(denom) > MINGFX_MATH_EPSILON) { + *iTime = (planePt - p_).Dot(planeNormal) / denom; + if (*iTime >= 0) { + *iPoint = p_ + (*iTime)*d_; + return true; + } + } + return false; + } + + + bool Ray::IntersectTriangle(const Point3 &vertex0, const Point3 &vertex1, const Point3 &vertex2, + float *iTime, Point3 *iPoint) const + { + // Implementation of the Möller–Trumbore intersection algorithm + // https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm + + Vector3 edge1, edge2, h, s, q; + float a,f,u,v; + edge1 = vertex1 - vertex0; + edge2 = vertex2 - vertex0; + h = d_.Cross(edge2); + a = edge1.Dot(h); + if (a > -MINGFX_MATH_EPSILON && a < MINGFX_MATH_EPSILON) + return false; + f = 1.f/a; + s = p_ - vertex0; + u = f * (s.Dot(h)); + if (u < 0.0 || u > 1.f) + return false; + q = s.Cross(edge1); + v = f * d_.Dot(q); + if ((v < 0.0) || (u + v > 1.0f)) + return false; + // At this stage we can compute t to find out where the intersection point is on the line. + *iTime = f * edge2.Dot(q); + if (*iTime > MINGFX_MATH_EPSILON) { // ray intersection + *iPoint = p_ + d_ * (*iTime); + return true; + } + else // This means that there is a line intersection but not a ray intersection. + return false; + + + /*** + // A basic implementation + + // find the point of intersection of the ray with the plane of the triangle + Vector3 AB = B - A; + Vector3 AC = C - A; + Vector3 cross = AB.Cross(AC); + Vector3 N = cross.ToUnit(); + if (!IntersectPlane(A, N, iTime, iPoint)) { + return false; + } + + // check to see if iPoint lies within the triangle + Vector3 edge1 = B - A; + Vector3 v1 = *iPoint - A; + Vector3 check1 = edge1.Cross(v1); + if (N.Dot(check1) < 0.0) { + return false; + } + + Vector3 edge2 = C - B; + Vector3 v2 = *iPoint - B; + Vector3 check2 = edge2.Cross(v2); + if (N.Dot(check2) < 0.0) { + return false; + } + + Vector3 edge3 = A - C; + Vector3 v3 = *iPoint - C; + Vector3 check3 = edge3.Cross(v3); + if (N.Dot(check3) < 0.0) { + return false; + } + + return true; + ***/ + } + + + bool Ray::IntersectQuad(const Point3 &A, const Point3 &B, const Point3 &C, const Point3 &D, + float *iTime, Point3 *iPoint) const + { + if (IntersectTriangle(A, B, C, iTime, iPoint)) { + return true; + } + else if (IntersectTriangle(A, C, D, iTime, iPoint)) { + return true; + } + else { + return false; + } + } + + + bool Ray::IntersectSphere(const Point3 ¢er, float radius, + float *iTime, Point3 *iPoint) const + { + Point3 P = p_ + (Point3::Origin() - center); + Vector3 D = d_; + + // A = (Dx^2 + Dy^2 + Dz^2) + const double A = ((double)D[0]*D[0] + (double)D[1]*D[1] + (double)D[2]*D[2]); + + // B = (Px * Dx + Py * Dy + Pz * Dz) + const double B = ((double)P[0]*D[0] + (double)P[1]*D[1] + (double)P[2]*D[2]); + + // C = (Px^2 + Py^2 + Pz^2 - (sphere radius)^2) + const double C = ((double)P[0]*P[0] + (double)P[1]*P[1] + (double)P[2]*P[2] - (double)radius*radius); + + // Discriminant of quadratic = B^2 - A * C + double discriminant = B*B - A*C; + + if (discriminant < 0.0) { + return false; + } + else { + double discRoot = sqrt(discriminant); + double t1 = (-B - discRoot) / A; + double t2 = (-B + discRoot) / A; + bool hit1 = false; + bool hit2 = false; + if (t1 > MINGFX_MATH_EPSILON) { + hit1 = true; + *iTime = (float)t1; + } + if (t2 > MINGFX_MATH_EPSILON) { + hit2 = true; + *iTime = (float)t2; + } + if ((!hit1) && (!hit2)) { + return false; + } + if ((hit1) && (hit2)) { + if (t1 < t2) { + *iTime = (float)t1; + } + } + + *iPoint = p_ + (*iTime)*d_; + return true; + } + } + + + bool Ray::IntersectMesh(const Mesh &mesh, + float *iTime, Point3 *iPoint, int *iTriangleID) const + { + *iTime = -1.0; + for (int i=0; i<mesh.num_triangles(); i++) { + Point3 p; + float t; + std::vector<unsigned int> indices = mesh.read_triangle_indices_data(i); + if (IntersectTriangle(mesh.read_vertex_data(indices[0]), mesh.read_vertex_data(indices[1]), mesh.read_vertex_data(indices[2]), &t, &p)) { + if ((*iTime < 0.0) || (t < *iTime)) { + *iPoint = p; + *iTime = t; + *iTriangleID = i; + } + } + } + return (*iTime > 0.0); + } + + bool Ray::FastIntersectMesh(Mesh *mesh, float *iTime, + Point3 *iPoint, int *iTriangleID) const + { + std::vector<int> tri_ids = mesh->bvh_ptr()->IntersectAndReturnUserData(*this); + if (tri_ids.size()) { + *iTime = -1.0; + for (int i=0; i<tri_ids.size(); i++) { + Point3 p; + float t; + std::vector<unsigned int> indices = mesh->read_triangle_indices_data(tri_ids[i]); + if (IntersectTriangle(mesh->read_vertex_data(indices[0]), mesh->read_vertex_data(indices[1]), mesh->read_vertex_data(indices[2]), &t, &p)) { + if ((*iTime < 0.0) || (t < *iTime)) { + *iPoint = p; + *iTime = t; + *iTriangleID = i; + } + } + } + return (*iTime > 0.0); + } + else { + return false; + } + } + + + bool Ray::IntersectAABB(const AABB &box, float *iTime) const { + // https://gamedev.stackexchange.com/questions/18436/most-efficient-aabb-vs-ray-collision-algorithms + + Point3 origin = p_; + + Vector3 invdir = d_; + invdir[0] = 1.0f / invdir[0]; + invdir[1] = 1.0f / invdir[1]; + invdir[2] = 1.0f / invdir[2]; + + float t1 = (box.min()[0] - origin[0])*invdir[0]; + float t2 = (box.max()[0] - origin[0])*invdir[0]; + float t3 = (box.min()[1] - origin[1])*invdir[1]; + float t4 = (box.max()[1] - origin[1])*invdir[1]; + float t5 = (box.min()[2] - origin[2])*invdir[2]; + float t6 = (box.max()[2] - origin[2])*invdir[2]; + + float tmin = std::max(std::max(std::min(t1, t2), std::min(t3, t4)), std::min(t5, t6)); + float tmax = std::min(std::min(std::max(t1, t2), std::max(t3, t4)), std::max(t5, t6)); + + // if tmax < 0, ray (line) is intersecting AABB, but the whole AABB is behind us + if (tmax < 0) { + *iTime = tmax; + return false; + } + + // if tmin > tmax, ray doesn't intersect AABB + if (tmin > tmax) { + *iTime = tmax; + return false; + } + + *iTime = tmin; + return true; + } + + + void Ray::set(Point3 newOrigin, Vector3 newDir) { + p_ = newOrigin; + d_ = newDir; + } + + std::ostream & operator<< ( std::ostream &os, const Ray &r) { + return os << r.origin() << " " << r.direction(); + } + + std::istream & operator>> ( std::istream &is, Ray &r) { + // format: (x, y, z) + char dummy; + Point3 p; + Vector3 d; + is >> p >> dummy >> d; + r.set(p, d); + return is; + } + + +} // end namespace diff --git a/dev/MinGfx/src/ray.h b/dev/MinGfx/src/ray.h new file mode 100644 index 0000000..8e84546 --- /dev/null +++ b/dev/MinGfx/src/ray.h @@ -0,0 +1,167 @@ +/* + This file is part of the MinGfx Project. + + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + + Original Author(s) of this File: + Dan Keefe, 2018, University of Minnesota + + Author(s) of Significant Updates/Modifications to the File: + ... + */ + +#ifndef SRC_RAY_H_ +#define SRC_RAY_H_ + +#include <iostream> + +#include "aabb.h" +#include "point3.h" +#include "vector3.h" +#include "mesh.h" + + +namespace mingfx { + + +/** Stores the mathematical object of a ray that begins at an origin (a 3D + point) and points in a direction (a unit 3D vector). Rays can intersect + a variety of other computer graphics objects, such as planes, triangles, + spheres, 3D meshes, etc. These intersections can be tested with the + Intersect...() methods. The Ray can also be transformed by a Matrix4. + Example: + ~~~ + // Create a pick ray from the mouse position + void MyGraphicsApp::OnLeftMouseDown(const Point2 &pos) { + Point2 mouse_xy = PixelsToNormalizedDeviceCoords(pos); + float mouse_z = ReadZValueAtPixel(pos); + Point3 mouse_3d = GfxMath::ScreenToNearPlane(view_matrix, proj_matrix, mouse_xy, mouse_z); + Matrix4 camera_matrix = view_matrix.Inverse(); + Point3 eye = camera_matrix.ColumnToPoint3(3); + + Ray pick_ray(eye, mouse_3d - eye); + + // check to see if the ray intersects a sphere + float t; + Point3 p; + if (pick_ray.IntersectSphere(Point3(0,0,0), 2.0, &t, &p)) { + std::cout << "Mouse pointing at sphere! Intersection point = " << p << std::endl; + } + } + ~~~ + */ +class Ray { +public: + + /// Defaults to a ray at the origin and pointing in the -Z direction + Ray(); + + /// Creates a ray from a 3D origin and direction + Ray(const Point3 &origin, const Vector3 &direction); + + /// Ray destructor + virtual ~Ray(); + + /// Check for "equality", taking floating point imprecision into account + bool operator==(const Ray& other) const; + + /// Check for "inequality", taking floating point imprecision into account + bool operator!=(const Ray& other) const; + + /// Returns the length of the direction vector + float Length() const; + + /** Checks to see if the ray intersects a plane defined by a point and a normal. + If there was an intersection, true is returned, iTime is set to the intersection + time, and iPoint is set to the intersection point. The plane is considered + to be 1-sided. That is the intersection will only occur if the ray hits the + plane from its front side as determined by the plane's normal. + */ + bool IntersectPlane(const Point3 &planePt, const Vector3 &planeNormal, + float *iTime, Point3 *iPoint) const; + + /** Checks to see if the ray intersects a triangle defined by the vertices v1, v2, and v3. + The vertices must be provided in counter-clockwise order so that the normal of the + triangle can be determined via the right-hand rule. The intersection will only happen + if the ray hits the front side of the triangle. If there was an intersection, + true is returned, iTime is set to the intersection time, and iPoint is set to the intersection point. + */ + bool IntersectTriangle(const Point3 &v1, const Point3 &v2, const Point3 &v3, + float *iTime, Point3 *iPoint) const; + + /** Checks to see if the ray intersects a quad defined by the vertices v1, v2, v3, and v4. + The vertices must be provided in counter-clockwise order so that the normal of the + triangle can be determined via the right-hand rule. The intersection will only happen + if the ray hits the front side of the triangle. If there was an intersection, + true is returned, iTime is set to the intersection time, and iPoint is set to the intersection point. + */ + bool IntersectQuad(const Point3 &v1, const Point3 &v2, const Point3 &v3, const Point3 &v4, + float *iTime, Point3 *iPoint) const; + + /** Checks to see if the ray intersects a sphere defined by a center point and a radius. + If there was an intersection, true is returned, iTime is set to the intersection time, + and iPoint is set to the intersection point. + */ + bool IntersectSphere(const Point3 ¢er, float radius, + float *iTime, Point3 *iPoint) const; + + /** Checks to see if the ray intersects a triangle mesh. This is a brute-force + check over each triangle in the mesh. If there was an intersection, true is returned, + iTime is set to the intersection time, iPoint is set to the intersection point, + and iTriangleID is set to the ID of the closest intersected triangle along the ray. + */ + bool IntersectMesh(const Mesh &mesh, float *iTime, + Point3 *iPoint, int *iTriangleID) const; + + /** Checks to see if the ray intersects a triangle mesh. This uses a BVH + (Bounding Volume Hierarchy) to accelerate the ray-triangle intersection tests. + Each mesh can optionally store a BVH. If a BVH has already been calculated + for the mesh (done with Mesh::CalculateBVH()), then this function will be + much faster than the brute-force IntersectMesh() function. If a BVH has + not already been calculated for the mesh, the first call to FastIntersectMesh() + will trigger the mesh to create a BVH (not a fast operation) but then + subsequent calls to FastIntersectMesh() will be fast. + */ + bool FastIntersectMesh(Mesh *mesh, float *iTime, + Point3 *iPoint, int *iTriangleID) const; + + /** Checks to see if the ray intersects an AABB (Axis-Aligned Bounding Box). + Typically, this is the first step of a more detailed intersection test and + we don't care about the actual point of intersection, just whether it + intersects or not. So, we don't bother calculating the iPoint. We get the + iTime for free though, so we do return that. You can calc the iPoint if + you want using: + ~~~ + float t; + if (ray.IntersectAABB(box, &t)) { + Point3 iPoint = ray.origin() + t*ray.direction(); + } + ~~~ + */ + bool IntersectAABB(const AABB &box, float *iTime) const; + + /// Returns the origin + Point3 origin() const; + + /// Returns the direction + Vector3 direction() const; + + /// Sets a new origin and direction + void set(Point3 newOrigin, Vector3 newDir); + +private: + Point3 p_; + Vector3 d_; +}; + + +// --- Stream operators --- + +std::ostream & operator<< ( std::ostream &os, const Ray &r); +std::istream & operator>> ( std::istream &is, Ray &r); + + +} // end namespace + +#endif diff --git a/dev/MinGfx/src/shader_program.cc b/dev/MinGfx/src/shader_program.cc new file mode 100644 index 0000000..faeb82e --- /dev/null +++ b/dev/MinGfx/src/shader_program.cc @@ -0,0 +1,389 @@ +/* + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + See corresponding header file for details. + */ + +#include "shader_program.h" + +#include "opengl_headers.h" + +#include <vector> +#include <fstream> + + +namespace mingfx { + + +ShaderProgram::ShaderProgram() : vertexShader_(0), fragmentShader_(0), program_(0) { +} + +ShaderProgram::~ShaderProgram() { +} + +bool ShaderProgram::initialized() { + return (program_ != 0); +} + +bool ShaderProgram::AddVertexShaderFromSource(const std::string &vertexSource) { + // https://www.khronos.org/opengl/wiki/Shader_Compilation + + // Create an empty vertex shader handle + vertexShader_ = glCreateShader(GL_VERTEX_SHADER); + + // Send the vertex shader source code to GL + // Note that std::string's .c_str is NULL character terminated. + const GLchar *source = (const GLchar *)vertexSource.c_str(); + glShaderSource(vertexShader_, 1, &source, 0); + + // Compile the vertex shader + glCompileShader(vertexShader_); + + GLint isCompiled = 0; + glGetShaderiv(vertexShader_, GL_COMPILE_STATUS, &isCompiled); + if (isCompiled == GL_FALSE) { + GLint maxLength = 0; + glGetShaderiv(vertexShader_, GL_INFO_LOG_LENGTH, &maxLength); + + // The maxLength includes the NULL character + std::vector<GLchar> infoLog(maxLength); + glGetShaderInfoLog(vertexShader_, maxLength, &maxLength, &infoLog[0]); + + // We don't need the shader anymore. + glDeleteShader(vertexShader_); + + std::cerr << "ShaderProgram: Error compiling vertex shader program: " << std::endl; + std::cerr << &infoLog[0] << std::endl; + std::cerr << vertexSource << std::endl; + return false; + } + return true; +} + +bool ShaderProgram::AddVertexShaderFromFile(const std::string &file) { + std::cout << "Loading vertex shader from file: " << file << std::endl; + std::string source; + std::string line; + std::ifstream myfile (file); + if (myfile.is_open()) { + while (std::getline(myfile, line)) { + source += line + "\n"; + } + myfile.close(); + return AddVertexShaderFromSource(source); + } + else { + std::cerr << "ShaderProgram: Cannot open file " << file << std::endl; + return false; + } +} + + + +bool ShaderProgram::AddFragmentShaderFromSource(const std::string &fragmentSource) { + // https://www.khronos.org/opengl/wiki/Shader_Compilation + + // Create an empty fragment shader handle + fragmentShader_ = glCreateShader(GL_FRAGMENT_SHADER); + + // Send the fragment shader source code to GL + // Note that std::string's .c_str is NULL character terminated. + const GLchar *source = (const GLchar *)fragmentSource.c_str(); + glShaderSource(fragmentShader_, 1, &source, 0); + + // Compile the fragment shader + glCompileShader(fragmentShader_); + + GLint isCompiled = 0; + glGetShaderiv(fragmentShader_, GL_COMPILE_STATUS, &isCompiled); + if (isCompiled == GL_FALSE) { + GLint maxLength = 0; + glGetShaderiv(fragmentShader_, GL_INFO_LOG_LENGTH, &maxLength); + + // The maxLength includes the NULL character + std::vector<GLchar> infoLog(maxLength); + glGetShaderInfoLog(fragmentShader_, maxLength, &maxLength, &infoLog[0]); + + // We don't need the shader anymore. + glDeleteShader(fragmentShader_); + + std::cerr << "ShaderProgram: Error compiling fragment shader program: " << std::endl; + std::cerr << &infoLog[0] << std::endl; + std::cerr << fragmentSource << std::endl; + return false; + } + return true; +} + + +bool ShaderProgram::AddFragmentShaderFromFile(const std::string &file) { + std::cout << "Loading fragment shader from file: " << file << std::endl; + std::string source; + std::string line; + std::ifstream myfile (file); + if (myfile.is_open()) { + while (std::getline(myfile, line)) { + source += line + "\n"; + } + myfile.close(); + return AddFragmentShaderFromSource(source); + } + else { + std::cerr << "ShaderProgram: Cannot open file " << file << std::endl; + return false; + } +} + + +bool ShaderProgram::LinkProgram() { + if (!vertexShader_) { + std::cerr << "ShaderProgram: Error linking program. A vertex shader must be added and successfully compiled before the program can be linked." << std::endl; + return false; + } + if (!fragmentShader_) { + std::cerr << "ShaderProgram: Error linking program. A fragment shader must be added and successfully compiled before the program can be linked." << std::endl; + return false; + } + + // Vertex and fragment shaders are successfully compiled. + // Now time to link them together into a program. + // Get a program object. + program_ = glCreateProgram(); + + // Attach our shaders to our program + glAttachShader(program_, vertexShader_); + glAttachShader(program_, fragmentShader_); + + // Link our program + glLinkProgram(program_); + + // Note the different functions here: glGetProgram* instead of glGetShader*. + GLint isLinked = 0; + glGetProgramiv(program_, GL_LINK_STATUS, (int *)&isLinked); + if (isLinked == GL_FALSE) { + GLint maxLength = 0; + glGetProgramiv(program_, GL_INFO_LOG_LENGTH, &maxLength); + + // The maxLength includes the NULL character + std::vector<GLchar> infoLog(maxLength); + glGetProgramInfoLog(program_, maxLength, &maxLength, &infoLog[0]); + + // We don't need the program anymore. + glDeleteProgram(program_); + // Don't leak shaders either. + glDeleteShader(vertexShader_); + glDeleteShader(fragmentShader_); + + std::cerr << "ShaderProgram: Error linking program: " << std::endl; + std::cerr << &infoLog[0] << std::endl; + return false; + } + + // Always detach shaders after a successful link. + glDetachShader(program_, vertexShader_); + glDetachShader(program_, fragmentShader_); + + return true; +} + + +void ShaderProgram::UseProgram() { + if (!initialized()) { + std::cerr << "ShaderProgram: Warning cannot UseProgram() until it shaders have been added and linked. Calling LinkProgram() for you now." << std::endl; + LinkProgram(); + } + glUseProgram(program_); +} + + +void ShaderProgram::StopProgram() { + glUseProgram(0); +} + + +// MinGfx math types +void ShaderProgram::SetUniform(const std::string &name, const Point2 &p) { + UseProgram(); + GLint loc = glGetUniformLocation(program_, name.c_str()); + glUniform2f(loc, p[0], p[1]); +} + +void ShaderProgram::SetUniform(const std::string &name, const Vector2 &v) { + UseProgram(); + GLint loc = glGetUniformLocation(program_, name.c_str()); + glUniform2f(loc, v[0], v[1]); +} + +void ShaderProgram::SetUniform(const std::string &name, const Point3 &p) { + UseProgram(); + GLint loc = glGetUniformLocation(program_, name.c_str()); + glUniform3f(loc, p[0], p[1], p[2]); +} + +void ShaderProgram::SetUniform(const std::string &name, const Vector3 &v) { + UseProgram(); + GLint loc = glGetUniformLocation(program_, name.c_str()); + glUniform3f(loc, v[0], v[1], v[2]); +} + +void ShaderProgram::SetUniform(const std::string &name, const Matrix4 &m) { + UseProgram(); + GLint loc = glGetUniformLocation(program_, name.c_str()); + glUniformMatrix4fv(loc, 1, GL_FALSE, m.value_ptr()); +} + +void ShaderProgram::SetUniform(const std::string &name, const Color &c) { + UseProgram(); + GLint loc = glGetUniformLocation(program_, name.c_str()); + glUniform4f(loc, c[0], c[1], c[2], c[3]); +} + + +// built-in types +void ShaderProgram::SetUniform(const std::string &name, int i) { + UseProgram(); + GLint loc = glGetUniformLocation(program_, name.c_str()); + glUniform1i(loc, i); +} + +void ShaderProgram::SetUniform(const std::string &name, unsigned int ui) { + UseProgram(); + GLint loc = glGetUniformLocation(program_, name.c_str()); + glUniform1ui(loc, ui); +} + +void ShaderProgram::SetUniform(const std::string &name, float f) { + UseProgram(); + GLint loc = glGetUniformLocation(program_, name.c_str()); + glUniform1f(loc, f); +} + + + +// built-in types - arrays +void ShaderProgram::SetUniformArray1(const std::string &name, int *i, int count) { + UseProgram(); + GLint loc = glGetUniformLocation(program_, name.c_str()); + glUniform1iv(loc, count, i); +} + +void ShaderProgram::SetUniformArray1(const std::string &name, unsigned int *ui, int count) { + UseProgram(); + GLint loc = glGetUniformLocation(program_, name.c_str()); + glUniform1uiv(loc, count, ui); +} + +void ShaderProgram::SetUniformArray1(const std::string &name, float *f, int count) { + UseProgram(); + GLint loc = glGetUniformLocation(program_, name.c_str()); + glUniform1fv(loc, count, f); +} + + +void ShaderProgram::SetUniformArray2(const std::string &name, int *i, int count) { + UseProgram(); + GLint loc = glGetUniformLocation(program_, name.c_str()); + glUniform2iv(loc, count, i); +} + +void ShaderProgram::SetUniformArray2(const std::string &name, unsigned int *ui, int count) { + UseProgram(); + GLint loc = glGetUniformLocation(program_, name.c_str()); + glUniform2uiv(loc, count, ui); +} + +void ShaderProgram::SetUniformArray2(const std::string &name, float *f, int count) { + UseProgram(); + GLint loc = glGetUniformLocation(program_, name.c_str()); + glUniform2fv(loc, count, f); +} + + +void ShaderProgram::SetUniformArray3(const std::string &name, int *i, int count) { + UseProgram(); + GLint loc = glGetUniformLocation(program_, name.c_str()); + glUniform3iv(loc, count, i); +} + +void ShaderProgram::SetUniformArray3(const std::string &name, unsigned int *ui, int count) { + UseProgram(); + GLint loc = glGetUniformLocation(program_, name.c_str()); + glUniform3uiv(loc, count, ui); +} + +void ShaderProgram::SetUniformArray3(const std::string &name, float *f, int count) { + UseProgram(); + GLint loc = glGetUniformLocation(program_, name.c_str()); + glUniform3fv(loc, count, f); +} + + +void ShaderProgram::SetUniformArray4(const std::string &name, int *i, int count) { + UseProgram(); + GLint loc = glGetUniformLocation(program_, name.c_str()); + glUniform4iv(loc, count, i); +} + +void ShaderProgram::SetUniformArray4(const std::string &name, unsigned int *ui, int count) { + UseProgram(); + GLint loc = glGetUniformLocation(program_, name.c_str()); + glUniform4uiv(loc, count, ui); +} + +void ShaderProgram::SetUniformArray4(const std::string &name, float *f, int count) { + UseProgram(); + GLint loc = glGetUniformLocation(program_, name.c_str()); + glUniform4fv(loc, count, f); +} + + + +void ShaderProgram::BindTexture(const std::string &name, const Texture2D &tex) { + UseProgram(); + + int texUnit = 0; + + std::map<std::string,int>::const_iterator it = texBindings_.find(name); + if (it != texBindings_.end()) { + // Found: This sampler was already bound to a tex unit, so use the same one + texUnit = texBindings_[name]; + } + else { + // Not found: This sampler was not already bound to a tex unit, so loop through the + // past/current bindings and pick the next available texUnit + std::map<std::string,int>::const_iterator it2; + for (it2 = texBindings_.begin(); it2 != texBindings_.end(); ++it2) { + if (texUnit <= it2->second) { + texUnit = it2->second + 1; + } + } + // save it for future reference + texBindings_[name] = texUnit; + } + + // associate the named shader program sampler variable with the selected texture unit + GLint loc = glGetUniformLocation(program_, name.c_str()); + glUniform1i(loc, texUnit); + // bind the opengl texture handle to the same texture unit + glActiveTexture(GL_TEXTURE0 + texUnit); + glBindTexture(GL_TEXTURE_2D, tex.opengl_id()); +} + +void ShaderProgram::BindTexture(const std::string &name, const Texture2D &tex, int texUnit) { + UseProgram(); + + texBindings_[name] = texUnit; + + // associate the named shader program sampler variable with the selected texture unit + GLint loc = glGetUniformLocation(program_, name.c_str()); + glUniform1i(loc, texUnit); + // bind the opengl texture handle to the same texture unit + glActiveTexture(GL_TEXTURE0 + texUnit); + glBindTexture(GL_TEXTURE_2D, tex.opengl_id()); +} + + + +} // end namespace + + diff --git a/dev/MinGfx/src/shader_program.h b/dev/MinGfx/src/shader_program.h new file mode 100644 index 0000000..2bf5f87 --- /dev/null +++ b/dev/MinGfx/src/shader_program.h @@ -0,0 +1,238 @@ +/* + This file is part of the MinGfx Project. + + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + + Original Author(s) of this File: + Dan Keefe, 2018, University of Minnesota + + Author(s) of Significant Updates/Modifications to the File: + ... + */ + +#ifndef SRC_SHADERPROGRAM_H_ +#define SRC_SHADERPROGRAM_H_ + +#include "color.h" +#include "matrix4.h" +#include "opengl_headers.h" +#include "point2.h" +#include "point3.h" +#include "texture2d.h" +#include "vector2.h" +#include "vector3.h" + +#include <string> +#include <map> + +namespace mingfx { + +/** A wrapper around GLSL shader programs. This class supports loading vertex + and fragment shaders from files or strings, compiling them, and then linking + them into a shader program. Uniform variables within the shader programs can + be set in order to pass parameters from C++ code into the shader program. + Textures can also be bound to the shader. Example usage: + ~~~ + + ShaderProgram shader_prog; + + void MyGraphicsApp::InitOpenGL() { + shader_prog.AddVertexShaderFromFile(Platform::findFile("my_shader.vert", searchPath)); + shader_prog.AddFragmentShaderFromFile(Platform::findFile("my_shader.frag", searchPath)); + shader_prog.LinkProgram(); + } + + void MyGraphicsApp::DrawUsingOpenGL() { + // Activate the shader program + shader_prog.UseProgram(); + + // Pass uniforms and textures from C++ to the GPU Shader Program + shader_prog.SetUniform("ModelMatrix", modelMat); + shader_prog.SetUniform("ViewMatrix", viewMat); + shader_prog.SetUniform("ProjectionMatrix", projMat); + shader_prog.SetUniform("LightPosition", Point3(2,2,2)); + shader_prog.BindTexture("SurfaceTexture", my_tex); + + // Draw whatever geometry you want now + mesh1.Draw(); + mesh2.Draw(); + + // Deactivate the shader program + shader_prog.StopProgram(); + } + ~~~ + */ +class ShaderProgram { +public: + /// Creates an empty ShaderProgram object. + ShaderProgram(); + + virtual ~ShaderProgram(); + + + // ---- These should be called during startup (e.g., in InitOpenGL()) ---- + + /// Call during initialization but after the OpenGL context has been created + /// (e.g., inside InitOpenGL()). This loads the shader from the file and + /// compiles it. An error will be printed to stderr if there are any + /// compilation errors. + bool AddVertexShaderFromFile(const std::string &file); + + /// This loads and compiles a shader from a string. An error will be printed + /// to stderr if there are any compilation errors. + bool AddVertexShaderFromSource(const std::string &code); + + /// Call during initialization but after the OpenGL context has been created + /// (e.g., inside InitOpenGL()). This loads the shader from the file and + /// compiles it. An error will be printed to stderr if there are any + /// compilation errors. + bool AddFragmentShaderFromFile(const std::string &file); + + /// This loads and compiles a shader from a string. An error will be printed + /// to stderr if there are any compilation errors. + bool AddFragmentShaderFromSource(const std::string &code); + + /// Call this after adding vertex and fragment shaders in order to link them + /// together to create the full shader program. An error will be printed to + /// stderr if there are any linking errors. + bool LinkProgram(); + + + + // ---- These should be called during rendering (e.g., in DrawUsingOpenGL()) ---- + + /// Call this first to make the shader program active, then call SetUniform() to + /// pass data from your C++ program into the shader code via the named uniform + /// variables that appear in the code. Then render whatever geometry you wish + /// with your own glDrawArrays() call(s). Finally, call StopProgram() to turn + /// off the shader program. + void UseProgram(); + + // Set Uniform Variables in the Shader + + // MinGfx types + + /// Passes the x,y values of point p to the shader program and stores the + /// result in the shader variable named name, which should be of type vec2. + void SetUniform(const std::string &name, const Point2 &p); + + /// Passes the x,y values of vector v to the shader program and stores the + /// result in the shader variable named name, which should be of type vec2. + void SetUniform(const std::string &name, const Vector2 &v); + + /// Passes the x,y,z,1 values of point p to the shader program and stores the + /// result in the shader variable named name, which should be of type vec4. + void SetUniform(const std::string &name, const Point3 &p); + + /// Passes the x,y,z,0 values of vector v to the shader program and stores the + /// result in the shader variable named name, which should be of type vec4. + void SetUniform(const std::string &name, const Vector3 &v); + + /// Passes the column-major 16 float values of matrix m to the shader program + /// and stores the result in the shader variable named name, which should be of type mat4. + void SetUniform(const std::string &name, const Matrix4 &m); + + /// Passes the r,g,b,a values of color c to the shader program and stores the + /// result in the shader variable named name, which should be of type vec4. + void SetUniform(const std::string &name, const Color &c); + + + // built-in types + + /// Passes the int to the shader program and stores the result in the shader + /// variable named name, which should be of type int. + void SetUniform(const std::string &name, int i); + + /// Passes the unsigned int to the shader program and stores the result in the shader + /// variable named name, which should be of type uint. + void SetUniform(const std::string &name, unsigned int ui); + + /// Passes the float to the shader program and stores the result in the shader + /// variable named name, which should be of type float. + void SetUniform(const std::string &name, float f); + + + // built-in types (arrays) + + /// Passes an array of count ints to the shader program and stores the result + /// in the shader variable named name, which should be of type int name[count]. + void SetUniformArray1(const std::string &name, int *i, int count); + + /// Passes an array of count unsigned ints to the shader program and stores the result + /// in the shader variable named name, which should be of type uint name[count]. + void SetUniformArray1(const std::string &name, unsigned int *ui, int count); + + /// Passes an array of count floats to the shader program and stores the result + /// in the shader variable named name, which should be of type float name[count]. + void SetUniformArray1(const std::string &name, float *f, int count); + + + /// Passes an array of count 2D int arrays to the shader program and stores the result + /// in the shader variable named name, which should be of type ivec2 name[count]. + void SetUniformArray2(const std::string &name, int *i, int count); + + /// Passes an array of count 2D unsigned int arrays to the shader program and stores the result + /// in the shader variable named name, which should be of type uivec2 name[count]. + void SetUniformArray2(const std::string &name, unsigned int *ui, int count); + + /// Passes an array of count 2D float arrays to the shader program and stores the result + /// in the shader variable named name, which should be of type vec2 name[count]. + void SetUniformArray2(const std::string &name, float *f, int count); + + + /// Passes an array of count 3D int arrays to the shader program and stores the result + /// in the shader variable named name, which should be of type ivec3 name[count]. + void SetUniformArray3(const std::string &name, int *i, int count); + + /// Passes an array of count 3D unsigned int arrays to the shader program and stores the result + /// in the shader variable named name, which should be of type uivec3 name[count]. + void SetUniformArray3(const std::string &name, unsigned int *ui, int count); + + /// Passes an array of count 3D float arrays to the shader program and stores the result + /// in the shader variable named name, which should be of type vec3 name[count]. + void SetUniformArray3(const std::string &name, float *f, int count); + + + /// Passes an array of count 4D int arrays to the shader program and stores the result + /// in the shader variable named name, which should be of type ivec4 name[count]. + void SetUniformArray4(const std::string &name, int *i, int count); + + /// Passes an array of count 4D unsigned int arrays to the shader program and stores the result + /// in the shader variable named name, which should be of type uivec4 name[count]. + void SetUniformArray4(const std::string &name, unsigned int *ui, int count); + + /// Passes an array of count 4D float arrays to the shader program and stores the result + /// in the shader variable named name, which should be of type vec4 name[count]. + void SetUniformArray4(const std::string &name, float *f, int count); + + + // Set Textures (Sampler Variables in the Shader) + + /// Binds a Texture2D to a sampler2D in the shader program. + /// This version automatically selects an available texture unit, i.e., one + /// not already used by this shader program. + void BindTexture(const std::string &name, const Texture2D &tex); + + /// Binds a Texture2D to a sampler2D in the shader program. + /// This version allows you to specify the texture unit to use. + void BindTexture(const std::string &name, const Texture2D &tex, int texUnit); + + + /// Call this after rendering geometry to deactivate the shader. + void StopProgram(); + + /// Returns true if the shader program has been successfully compiled and linked. + bool initialized(); + +private: + GLuint vertexShader_; + GLuint fragmentShader_; + GLuint program_; + std::map<std::string, int> texBindings_; +}; + + +} // end namespace + +#endif diff --git a/dev/MinGfx/src/shaders/default.frag b/dev/MinGfx/src/shaders/default.frag new file mode 100644 index 0000000..59b9f27 --- /dev/null +++ b/dev/MinGfx/src/shaders/default.frag @@ -0,0 +1,76 @@ +#version 330 + +/* + This file is part of the MinGfx Project. + + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + + Original Author(s) of this File: + Dan Keefe, 2018, University of Minnesota + + Author(s) of Significant Updates/Modifications to the File: + ... + */ + +const int MAX_LIGHTS = 10; + +in vec3 N; +in vec3 v; +in vec2 uv; +in vec4 col_interp; + +out vec4 fragColor; + +uniform int NumLights; +uniform vec3 LightPositions[MAX_LIGHTS]; +uniform vec4 LightIntensitiesAmbient[MAX_LIGHTS]; +uniform vec4 LightIntensitiesDiffuse[MAX_LIGHTS]; +uniform vec4 LightIntensitiesSpecular[MAX_LIGHTS]; + +uniform vec4 MatReflectanceAmbient; +uniform vec4 MatReflectanceDiffuse; +uniform vec4 MatReflectanceSpecular; +uniform float MatReflectanceShininess; + +uniform int UseSurfaceTexture; +uniform sampler2D SurfaceTexture; + +void main() { + + // initialize the fragment color to the interpolated value of per-vertex colors + // since some meshes will have color specified per-vertex. if there are no + // per vertex colors, then this will default to white. + fragColor = col_interp; + + + // if there is a surface texture, then factor this into the base color of this + // fragment as well + if (UseSurfaceTexture != 0) { + fragColor *= texture(SurfaceTexture, uv); + } + + // modulate this base color by the additive intensity from all light sources + vec3 Ia = vec3(0,0,0); + vec3 Id = vec3(0,0,0); + vec3 Is = vec3(0,0,0); + + vec3 n = normalize(N); + + for (int i=0; i<NumLights; i++) { + vec3 L = normalize(LightPositions[i] - v); + vec3 V = normalize(-v); // eye is at (0,0,0) + vec3 R = normalize(-reflect(L,N)); + + Ia += MatReflectanceAmbient.rgb * LightIntensitiesAmbient[i].rgb; + + if (dot(n,L) > 0.0) { + Id += clamp(MatReflectanceDiffuse.rgb * LightIntensitiesDiffuse[i].rgb * max(dot(n, L), 0.0), 0.0, 1.0); + + Is += MatReflectanceSpecular.rgb * LightIntensitiesSpecular[i].rgb * pow(max(dot(R, V), 0.0), MatReflectanceShininess); + Is = clamp(Is, 0.0, 1.0); + } + } + fragColor.rgb *= Ia + Id + Is; +} + diff --git a/dev/MinGfx/src/shaders/default.vert b/dev/MinGfx/src/shaders/default.vert new file mode 100644 index 0000000..c961992 --- /dev/null +++ b/dev/MinGfx/src/shaders/default.vert @@ -0,0 +1,39 @@ +#version 330 + +/* + This file is part of the MinGfx Project. + + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + + Original Author(s) of this File: + Dan Keefe, 2018, University of Minnesota + + Author(s) of Significant Updates/Modifications to the File: + ... + */ + +layout(location = 0) in vec3 position; +layout(location = 1) in vec3 normal; +layout(location = 2) in vec4 color; +layout(location = 3) in vec2 texcoord; + +layout(location = 8) in mat4 instance_xform; + +uniform mat4 ModelMatrix; +uniform mat4 ViewMatrix; +uniform mat4 ProjectionMatrix; +uniform mat4 NormalMatrix; + +out vec3 N; +out vec3 v; +out vec2 uv; +out vec4 col_interp; + +void main() { + v = (ViewMatrix * ModelMatrix * vec4(position, 1)).xyz; + N = normalize((NormalMatrix * vec4(normal, 0)).xyz); + uv = texcoord.xy; + gl_Position = ProjectionMatrix * ViewMatrix * instance_xform * ModelMatrix * vec4(position, 1); + col_interp = color; +}
\ No newline at end of file diff --git a/dev/MinGfx/src/shaders/fullscreen.frag b/dev/MinGfx/src/shaders/fullscreen.frag new file mode 100644 index 0000000..ce3d624 --- /dev/null +++ b/dev/MinGfx/src/shaders/fullscreen.frag @@ -0,0 +1,23 @@ +#version 330 + +/* + This file is part of the MinGfx Project. + + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + + Original Author(s) of this File: + Dan Keefe, 2018, University of Minnesota + + Author(s) of Significant Updates/Modifications to the File: + ... + */ + +in vec2 uv; +uniform sampler2D SurfaceTexture; + +out vec4 fragColor; + +void main() { + fragColor = texture(SurfaceTexture, uv); +}
\ No newline at end of file diff --git a/dev/MinGfx/src/shaders/fullscreen.vert b/dev/MinGfx/src/shaders/fullscreen.vert new file mode 100644 index 0000000..db96659 --- /dev/null +++ b/dev/MinGfx/src/shaders/fullscreen.vert @@ -0,0 +1,24 @@ +#version 330 + +/* + This file is part of the MinGfx Project. + + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + + Original Author(s) of this File: + Dan Keefe, 2018, University of Minnesota + + Author(s) of Significant Updates/Modifications to the File: + ... + */ + +layout(location = 0) in vec3 position; +layout(location = 3) in vec2 texcoord; + +out vec2 uv; + +void main() { + uv = texcoord.xy; + gl_Position = vec4(position,1.0); +} diff --git a/dev/MinGfx/src/shaders/text.frag b/dev/MinGfx/src/shaders/text.frag new file mode 100644 index 0000000..2642061 --- /dev/null +++ b/dev/MinGfx/src/shaders/text.frag @@ -0,0 +1,25 @@ +#version 330 + +/* + This file is part of the MinGfx Project. + + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + + Original Author(s) of this File: + Dan Keefe, 2018, University of Minnesota + + Author(s) of Significant Updates/Modifications to the File: + ... + */ + +in vec2 uv; +uniform vec4 color; +uniform sampler2D font_atlas; + +out vec4 frag_color; + +void main() { + frag_color = color * texture(font_atlas, uv); + // frag_color.a = frag_color.r; +}
\ No newline at end of file diff --git a/dev/MinGfx/src/shaders/text.vert b/dev/MinGfx/src/shaders/text.vert new file mode 100644 index 0000000..36c6b03 --- /dev/null +++ b/dev/MinGfx/src/shaders/text.vert @@ -0,0 +1,28 @@ +#version 330 + +/* + This file is part of the MinGfx Project. + + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + + Original Author(s) of this File: + Dan Keefe, 2018, University of Minnesota + + Author(s) of Significant Updates/Modifications to the File: + ... + */ + +layout(location = 0) in vec3 vertex; +layout(location = 3) in vec2 texcoord; + +uniform mat4 mvp_matrix; +uniform float scale; +uniform vec3 offset; + +out vec2 uv; + +void main() { + uv = texcoord.xy; + gl_Position = mvp_matrix * vec4(scale*(vertex+offset),1.0); +} diff --git a/dev/MinGfx/src/stb_rect_pack.h b/dev/MinGfx/src/stb_rect_pack.h new file mode 100644 index 0000000..9faf578 --- /dev/null +++ b/dev/MinGfx/src/stb_rect_pack.h @@ -0,0 +1,624 @@ +// stb_rect_pack.h - v0.11 - public domain - rectangle packing +// Sean Barrett 2014 +// +// Useful for e.g. packing rectangular textures into an atlas. +// Does not do rotation. +// +// Not necessarily the awesomest packing method, but better than +// the totally naive one in stb_truetype (which is primarily what +// this is meant to replace). +// +// Has only had a few tests run, may have issues. +// +// More docs to come. +// +// No memory allocations; uses qsort() and assert() from stdlib. +// Can override those by defining STBRP_SORT and STBRP_ASSERT. +// +// This library currently uses the Skyline Bottom-Left algorithm. +// +// Please note: better rectangle packers are welcome! Please +// implement them to the same API, but with a different init +// function. +// +// Credits +// +// Library +// Sean Barrett +// Minor features +// Martins Mozeiko +// github:IntellectualKitty +// +// Bugfixes / warning fixes +// Jeremy Jaussaud +// +// Version history: +// +// 0.11 (2017-03-03) return packing success/fail result +// 0.10 (2016-10-25) remove cast-away-const to avoid warnings +// 0.09 (2016-08-27) fix compiler warnings +// 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0) +// 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0) +// 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort +// 0.05: added STBRP_ASSERT to allow replacing assert +// 0.04: fixed minor bug in STBRP_LARGE_RECTS support +// 0.01: initial release +// +// LICENSE +// +// See end of file for license information. + +////////////////////////////////////////////////////////////////////////////// +// +// INCLUDE SECTION +// + +#ifndef STB_INCLUDE_STB_RECT_PACK_H +#define STB_INCLUDE_STB_RECT_PACK_H + +#define STB_RECT_PACK_VERSION 1 + +#ifdef STBRP_STATIC +#define STBRP_DEF static +#else +#define STBRP_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct stbrp_context stbrp_context; +typedef struct stbrp_node stbrp_node; +typedef struct stbrp_rect stbrp_rect; + +#ifdef STBRP_LARGE_RECTS +typedef int stbrp_coord; +#else +typedef unsigned short stbrp_coord; +#endif + +STBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects); +// Assign packed locations to rectangles. The rectangles are of type +// 'stbrp_rect' defined below, stored in the array 'rects', and there +// are 'num_rects' many of them. +// +// Rectangles which are successfully packed have the 'was_packed' flag +// set to a non-zero value and 'x' and 'y' store the minimum location +// on each axis (i.e. bottom-left in cartesian coordinates, top-left +// if you imagine y increasing downwards). Rectangles which do not fit +// have the 'was_packed' flag set to 0. +// +// You should not try to access the 'rects' array from another thread +// while this function is running, as the function temporarily reorders +// the array while it executes. +// +// To pack into another rectangle, you need to call stbrp_init_target +// again. To continue packing into the same rectangle, you can call +// this function again. Calling this multiple times with multiple rect +// arrays will probably produce worse packing results than calling it +// a single time with the full rectangle array, but the option is +// available. +// +// The function returns 1 if all of the rectangles were successfully +// packed and 0 otherwise. + +struct stbrp_rect +{ + // reserved for your use: + int id; + + // input: + stbrp_coord w, h; + + // output: + stbrp_coord x, y; + int was_packed; // non-zero if valid packing + +}; // 16 bytes, nominally + + +STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes); +// Initialize a rectangle packer to: +// pack a rectangle that is 'width' by 'height' in dimensions +// using temporary storage provided by the array 'nodes', which is 'num_nodes' long +// +// You must call this function every time you start packing into a new target. +// +// There is no "shutdown" function. The 'nodes' memory must stay valid for +// the following stbrp_pack_rects() call (or calls), but can be freed after +// the call (or calls) finish. +// +// Note: to guarantee best results, either: +// 1. make sure 'num_nodes' >= 'width' +// or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1' +// +// If you don't do either of the above things, widths will be quantized to multiples +// of small integers to guarantee the algorithm doesn't run out of temporary storage. +// +// If you do #2, then the non-quantized algorithm will be used, but the algorithm +// may run out of temporary storage and be unable to pack some rectangles. + +STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem); +// Optionally call this function after init but before doing any packing to +// change the handling of the out-of-temp-memory scenario, described above. +// If you call init again, this will be reset to the default (false). + + +STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic); +// Optionally select which packing heuristic the library should use. Different +// heuristics will produce better/worse results for different data sets. +// If you call init again, this will be reset to the default. + +enum +{ + STBRP_HEURISTIC_Skyline_default=0, + STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default, + STBRP_HEURISTIC_Skyline_BF_sortHeight +}; + + +////////////////////////////////////////////////////////////////////////////// +// +// the details of the following structures don't matter to you, but they must +// be visible so you can handle the memory allocations for them + +struct stbrp_node +{ + stbrp_coord x,y; + stbrp_node *next; +}; + +struct stbrp_context +{ + int width; + int height; + int align; + int init_mode; + int heuristic; + int num_nodes; + stbrp_node *active_head; + stbrp_node *free_head; + stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2' +}; + +#ifdef __cplusplus +} +#endif + +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// IMPLEMENTATION SECTION +// + +#ifdef STB_RECT_PACK_IMPLEMENTATION +#ifndef STBRP_SORT +#include <stdlib.h> +#define STBRP_SORT qsort +#endif + +#ifndef STBRP_ASSERT +#include <assert.h> +#define STBRP_ASSERT assert +#endif + +#ifdef _MSC_VER +#define STBRP__NOTUSED(v) (void)(v) +#else +#define STBRP__NOTUSED(v) (void)sizeof(v) +#endif + +enum +{ + STBRP__INIT_skyline = 1 +}; + +STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic) +{ + switch (context->init_mode) { + case STBRP__INIT_skyline: + STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight); + context->heuristic = heuristic; + break; + default: + STBRP_ASSERT(0); + } +} + +STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem) +{ + if (allow_out_of_mem) + // if it's ok to run out of memory, then don't bother aligning them; + // this gives better packing, but may fail due to OOM (even though + // the rectangles easily fit). @TODO a smarter approach would be to only + // quantize once we've hit OOM, then we could get rid of this parameter. + context->align = 1; + else { + // if it's not ok to run out of memory, then quantize the widths + // so that num_nodes is always enough nodes. + // + // I.e. num_nodes * align >= width + // align >= width / num_nodes + // align = ceil(width/num_nodes) + + context->align = (context->width + context->num_nodes-1) / context->num_nodes; + } +} + +STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes) +{ + int i; +#ifndef STBRP_LARGE_RECTS + STBRP_ASSERT(width <= 0xffff && height <= 0xffff); +#endif + + for (i=0; i < num_nodes-1; ++i) + nodes[i].next = &nodes[i+1]; + nodes[i].next = NULL; + context->init_mode = STBRP__INIT_skyline; + context->heuristic = STBRP_HEURISTIC_Skyline_default; + context->free_head = &nodes[0]; + context->active_head = &context->extra[0]; + context->width = width; + context->height = height; + context->num_nodes = num_nodes; + stbrp_setup_allow_out_of_mem(context, 0); + + // node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly) + context->extra[0].x = 0; + context->extra[0].y = 0; + context->extra[0].next = &context->extra[1]; + context->extra[1].x = (stbrp_coord) width; +#ifdef STBRP_LARGE_RECTS + context->extra[1].y = (1<<30); +#else + context->extra[1].y = 65535; +#endif + context->extra[1].next = NULL; +} + +// find minimum y position if it starts at x1 +static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste) +{ + stbrp_node *node = first; + int x1 = x0 + width; + int min_y, visited_width, waste_area; + + STBRP__NOTUSED(c); + + STBRP_ASSERT(first->x <= x0); + + #if 0 + // skip in case we're past the node + while (node->next->x <= x0) + ++node; + #else + STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency + #endif + + STBRP_ASSERT(node->x <= x0); + + min_y = 0; + waste_area = 0; + visited_width = 0; + while (node->x < x1) { + if (node->y > min_y) { + // raise min_y higher. + // we've accounted for all waste up to min_y, + // but we'll now add more waste for everything we've visted + waste_area += visited_width * (node->y - min_y); + min_y = node->y; + // the first time through, visited_width might be reduced + if (node->x < x0) + visited_width += node->next->x - x0; + else + visited_width += node->next->x - node->x; + } else { + // add waste area + int under_width = node->next->x - node->x; + if (under_width + visited_width > width) + under_width = width - visited_width; + waste_area += under_width * (min_y - node->y); + visited_width += under_width; + } + node = node->next; + } + + *pwaste = waste_area; + return min_y; +} + +typedef struct +{ + int x,y; + stbrp_node **prev_link; +} stbrp__findresult; + +static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height) +{ + int best_waste = (1<<30), best_x, best_y = (1 << 30); + stbrp__findresult fr; + stbrp_node **prev, *node, *tail, **best = NULL; + + // align to multiple of c->align + width = (width + c->align - 1); + width -= width % c->align; + STBRP_ASSERT(width % c->align == 0); + + node = c->active_head; + prev = &c->active_head; + while (node->x + width <= c->width) { + int y,waste; + y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste); + if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL + // bottom left + if (y < best_y) { + best_y = y; + best = prev; + } + } else { + // best-fit + if (y + height <= c->height) { + // can only use it if it first vertically + if (y < best_y || (y == best_y && waste < best_waste)) { + best_y = y; + best_waste = waste; + best = prev; + } + } + } + prev = &node->next; + node = node->next; + } + + best_x = (best == NULL) ? 0 : (*best)->x; + + // if doing best-fit (BF), we also have to try aligning right edge to each node position + // + // e.g, if fitting + // + // ____________________ + // |____________________| + // + // into + // + // | | + // | ____________| + // |____________| + // + // then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned + // + // This makes BF take about 2x the time + + if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) { + tail = c->active_head; + node = c->active_head; + prev = &c->active_head; + // find first node that's admissible + while (tail->x < width) + tail = tail->next; + while (tail) { + int xpos = tail->x - width; + int y,waste; + STBRP_ASSERT(xpos >= 0); + // find the left position that matches this + while (node->next->x <= xpos) { + prev = &node->next; + node = node->next; + } + STBRP_ASSERT(node->next->x > xpos && node->x <= xpos); + y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste); + if (y + height < c->height) { + if (y <= best_y) { + if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) { + best_x = xpos; + STBRP_ASSERT(y <= best_y); + best_y = y; + best_waste = waste; + best = prev; + } + } + } + tail = tail->next; + } + } + + fr.prev_link = best; + fr.x = best_x; + fr.y = best_y; + return fr; +} + +static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height) +{ + // find best position according to heuristic + stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height); + stbrp_node *node, *cur; + + // bail if: + // 1. it failed + // 2. the best node doesn't fit (we don't always check this) + // 3. we're out of memory + if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) { + res.prev_link = NULL; + return res; + } + + // on success, create new node + node = context->free_head; + node->x = (stbrp_coord) res.x; + node->y = (stbrp_coord) (res.y + height); + + context->free_head = node->next; + + // insert the new node into the right starting point, and + // let 'cur' point to the remaining nodes needing to be + // stiched back in + + cur = *res.prev_link; + if (cur->x < res.x) { + // preserve the existing one, so start testing with the next one + stbrp_node *next = cur->next; + cur->next = node; + cur = next; + } else { + *res.prev_link = node; + } + + // from here, traverse cur and free the nodes, until we get to one + // that shouldn't be freed + while (cur->next && cur->next->x <= res.x + width) { + stbrp_node *next = cur->next; + // move the current node to the free list + cur->next = context->free_head; + context->free_head = cur; + cur = next; + } + + // stitch the list back in + node->next = cur; + + if (cur->x < res.x + width) + cur->x = (stbrp_coord) (res.x + width); + +#ifdef _DEBUG + cur = context->active_head; + while (cur->x < context->width) { + STBRP_ASSERT(cur->x < cur->next->x); + cur = cur->next; + } + STBRP_ASSERT(cur->next == NULL); + + { + stbrp_node *L1 = NULL, *L2 = NULL; + int count=0; + cur = context->active_head; + while (cur) { + L1 = cur; + cur = cur->next; + ++count; + } + cur = context->free_head; + while (cur) { + L2 = cur; + cur = cur->next; + ++count; + } + STBRP_ASSERT(count == context->num_nodes+2); + } +#endif + + return res; +} + +static int rect_height_compare(const void *a, const void *b) +{ + const stbrp_rect *p = (const stbrp_rect *) a; + const stbrp_rect *q = (const stbrp_rect *) b; + if (p->h > q->h) + return -1; + if (p->h < q->h) + return 1; + return (p->w > q->w) ? -1 : (p->w < q->w); +} + +static int rect_original_order(const void *a, const void *b) +{ + const stbrp_rect *p = (const stbrp_rect *) a; + const stbrp_rect *q = (const stbrp_rect *) b; + return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed); +} + +#ifdef STBRP_LARGE_RECTS +#define STBRP__MAXVAL 0xffffffff +#else +#define STBRP__MAXVAL 0xffff +#endif + +STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects) +{ + int i, all_rects_packed = 1; + + // we use the 'was_packed' field internally to allow sorting/unsorting + for (i=0; i < num_rects; ++i) { + rects[i].was_packed = i; + #ifndef STBRP_LARGE_RECTS + STBRP_ASSERT(rects[i].w <= 0xffff && rects[i].h <= 0xffff); + #endif + } + + // sort according to heuristic + STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare); + + for (i=0; i < num_rects; ++i) { + if (rects[i].w == 0 || rects[i].h == 0) { + rects[i].x = rects[i].y = 0; // empty rect needs no space + } else { + stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h); + if (fr.prev_link) { + rects[i].x = (stbrp_coord) fr.x; + rects[i].y = (stbrp_coord) fr.y; + } else { + rects[i].x = rects[i].y = STBRP__MAXVAL; + } + } + } + + // unsort + STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order); + + // set was_packed flags and all_rects_packed status + for (i=0; i < num_rects; ++i) { + rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL); + if (!rects[i].was_packed) + all_rects_packed = 0; + } + + // return the all_rects_packed status + return all_rects_packed; +} +#endif + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/dev/MinGfx/src/text_shader.cc b/dev/MinGfx/src/text_shader.cc new file mode 100644 index 0000000..ca99953 --- /dev/null +++ b/dev/MinGfx/src/text_shader.cc @@ -0,0 +1,238 @@ +/* + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + See corresponding header file for details. + */ + +#include "text_shader.h" + +#include "platform.h" +#include <fstream> + +// disable warnings for this 3rd party code +#pragma warning (push, 0) +#define STB_RECT_PACK_IMPLEMENTATION +#include "stb_rect_pack.h" +#define STB_TRUETYPE_IMPLEMENTATION +#include <stb_truetype.h> +#pragma warning (pop) + + +namespace mingfx { + +// Reference implementation: https://github.com/nothings/stb/blob/master/tests/oversample/main.c + + +TextShader::TextShader() : native_font_size_(0.0) +{ + for (int i = 0; i < 128; i++) { + chardata_[i] = stbtt_packedchar(); + } +} + +TextShader::~TextShader() { +} + + +bool TextShader::Init(const std::string &filename, int font_size) { + // load shader + shader_.AddVertexShaderFromFile(Platform::FindMinGfxShaderFile("text.vert")); + shader_.AddFragmentShaderFromFile(Platform::FindMinGfxShaderFile("text.frag")); + shader_.LinkProgram(); + + // load font + native_font_size_ = (float)font_size; + std::ifstream is(filename.c_str(), std::ifstream::binary); + if (is) { + is.seekg(0, is.end); + int length = (int)is.tellg(); + is.seekg(0, is.beg); + + char *ttf_buffer = new char[length]; + is.read(ttf_buffer, length); + if (is) + std::cout << "all characters read successfully."; + else + std::cout << "error: only " << is.gcount() << " could be read"; + is.close(); + + // todo: calc an appropriate pow of 2 size given the font_size + int atlas_width = 1024; + int atlas_height = 1024; + + stbtt_pack_context pc; + unsigned char *bitmap = new unsigned char[(size_t)atlas_width * atlas_height]; + + stbtt_PackBegin(&pc, bitmap, atlas_width, atlas_height, 0, 1, NULL); + stbtt_PackSetOversampling(&pc, 2, 2); + stbtt_PackFontRange(&pc, (unsigned char*)ttf_buffer, 0, (float)font_size, 32, 95, chardata_+32); + stbtt_PackEnd(&pc); + + // convert to 4-channel since that is all that Texture2D currently supports + unsigned char *bitmap4D = new unsigned char[(size_t)4 * atlas_width * atlas_height]; + for (int i=0; i < atlas_width * atlas_height; i++) { + bitmap4D[4*i + 0] = bitmap[i]; + bitmap4D[4*i + 1] = bitmap[i]; + bitmap4D[4*i + 2] = bitmap[i]; + bitmap4D[4*i + 3] = bitmap[i]; + } + + atlas_.InitFromBytes(atlas_width, atlas_height, bitmap4D); + + delete [] ttf_buffer; + delete [] bitmap; + delete [] bitmap4D; + + return true; + } + else { + std::cerr << "TextShader: Error font file does not exist: " << filename << std::endl; + return false; + } + +} + + +void TextShader::Draw3D(const Matrix4 &model, const Matrix4 &view, const Matrix4 &projection, + const std::string &text, TextFormat format, bool cache) +{ + MeshData *md = NULL; + std::map<std::string, MeshData>::iterator it = cache_.find(text); + if (it != cache_.end()) { + // use an existing cached mesh + md = &(it->second); + } + else { + // need to create a new mesh, add a new one to the cache or use the tmp_mesh + if (cache) { + MeshData new_md; + cache_[text] = new_md; + md = &(cache_[text]); + } + else { + md = &tmp_md_; + } + + // set appropriate vertices and texcoords for this text string + SetTextMesh(text, md); + } + + Vector3 offset; + if (format.h_align == HorizAlign::HORIZ_ALIGN_LEFT) { + offset[0] = 0; + } + else if (format.h_align == HorizAlign::HORIZ_ALIGN_CENTER) { + offset[0] = -0.5f * (md->max[0] - md->min[0]); + } + else if (format.h_align == HorizAlign::HORIZ_ALIGN_RIGHT) { + offset[0] = -(md->max[0] - md->min[0]); + } + + if (format.v_align == VertAlign::VERT_ALIGN_TOP) { + offset[1] = -md->max[1]; + } + else if (format.v_align == VertAlign::VERT_ALIGN_CENTER) { + offset[1] = -0.5f * md->max[1]; + } + else if (format.v_align == VertAlign::VERT_ALIGN_BASELINE) { + offset[1] = 0; + } + else if (format.v_align == VertAlign::VERT_ALIGN_BOTTOM) { + offset[1] = -md->min[1]; + } + + glDisable(GL_CULL_FACE); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + shader_.UseProgram(); + Matrix4 mvp = projection * view * model; + shader_.SetUniform("mvp_matrix", mvp); + shader_.SetUniform("scale", format.size / native_font_size_); + shader_.SetUniform("offset", offset); + shader_.SetUniform("color", format.color); + shader_.BindTexture("font_atlas", atlas_); + md->mesh.Draw(); + shader_.StopProgram(); + + glEnable(GL_CULL_FACE); +} + + +void TextShader::SetTextMesh(const std::string &text, MeshData *md) { + std::vector<Point3> verts; + std::vector<Point2> uvs; + std::vector<unsigned int> indices; + + const char *c = text.c_str(); + float x = 0.0; + float y = 0.0; + while (*c) { + stbtt_aligned_quad q; + stbtt_GetPackedQuad(chardata_, atlas_.width(), atlas_.height(), *c++, &x, &y, &q, 0); + + // top left + verts.push_back(Point3(q.x0, -q.y0, 0.0)); + uvs.push_back(Point2(q.s0, q.t0)); + // top right + verts.push_back(Point3(q.x1, -q.y0, 0.0)); + uvs.push_back(Point2(q.s1, q.t0)); + // bot right + verts.push_back(Point3(q.x1, -q.y1, 0.0)); + uvs.push_back(Point2(q.s1, q.t1)); + // bot left + verts.push_back(Point3(q.x0, -q.y1, 0.0)); + uvs.push_back(Point2(q.s0, q.t1)); + + + indices.push_back((unsigned int)verts.size()-2); + indices.push_back((unsigned int)verts.size()-3); + indices.push_back((unsigned int)verts.size()-4); + + indices.push_back((unsigned int)verts.size()-2); + indices.push_back((unsigned int)verts.size()-4); + indices.push_back((unsigned int)verts.size()-1); + } + + md->mesh.SetVertices(verts); + md->mesh.SetTexCoords(0, uvs); + md->mesh.SetIndices(indices); + + md->min = Point2(verts[0][0], verts[0][1]); + md->max = md->min; + for (int i=0; i<verts.size(); i++) { + Point3 p = verts[i]; + if (p[0] < md->min[0]) md->min[0] = p[0]; + if (p[0] > md->max[0]) md->max[0] = p[0]; + if (p[1] < md->min[1]) md->min[1] = p[1]; + if (p[1] > md->max[1]) md->max[1] = p[1]; + } +} + +Vector2 TextShader::TextExtents(const std::string &text, TextFormat format, bool cache) { + MeshData *md = NULL; + std::map<std::string, MeshData>::iterator it = cache_.find(text); + if (it != cache_.end()) { + // use an existing cached mesh + md = &(it->second); + } + else { + // need to create a new mesh, add a new one to the cache or use the tmp_mesh + if (cache) { + MeshData new_md; + cache_[text] = new_md; + md = &(cache_[text]); + } + else { + md = &tmp_md_; + } + + // set appropriate vertices and texcoords for this text string + SetTextMesh(text, md); + } + + return format.size / native_font_size_ * (md->max - md->min); +} + + +} // end namespace
\ No newline at end of file diff --git a/dev/MinGfx/src/text_shader.h b/dev/MinGfx/src/text_shader.h new file mode 100644 index 0000000..a075cd2 --- /dev/null +++ b/dev/MinGfx/src/text_shader.h @@ -0,0 +1,110 @@ +/* + This file is part of the MinGfx Project. + + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + + Original Author(s) of this File: + Dan Keefe, 2018, University of Minnesota + + Author(s) of Significant Updates/Modifications to the File: + ... + */ + +#ifndef SRC_TEXT_SHADER_H_ +#define SRC_TEXT_SHADER_H_ + +#include <string> +#include <map> + +#include "matrix4.h" +#include "mesh.h" +#include "shader_program.h" +#include "texture2d.h" + +// disable warnings for this 3rd party code +#pragma warning ( push, 0 ) +#include <stb_truetype.h> +#pragma warning ( pop ) + +namespace mingfx { + + + +/** + */ +class TextShader { +public: + TextShader(); + virtual ~TextShader(); + + /// Call this from within the InitOpenGL() function since it will initialize + /// not just the Font's internal data but also an OpenGL texture to be + /// stored on the graphics card. Internally, this uses the stb_truetype + /// library to load true type fonts (files with a .ttf extension). + bool Init(const std::string &font_file, int native_font_size); + + enum class HorizAlign { + HORIZ_ALIGN_LEFT, + HORIZ_ALIGN_CENTER, + HORIZ_ALIGN_RIGHT + }; + + enum class VertAlign { + VERT_ALIGN_TOP, + VERT_ALIGN_CENTER, + VERT_ALIGN_BASELINE, + VERT_ALIGN_BOTTOM + }; + + class TextFormat { + public: + // constructor sets defaults + TextFormat() : + size(0.1f), + color(1,1,1,1), + h_align(HorizAlign::HORIZ_ALIGN_CENTER), + v_align(VertAlign::VERT_ALIGN_BASELINE) {} + + float size; + Color color; + HorizAlign h_align; + VertAlign v_align; + }; + + + //void Draw2D(const Point2 &pos, + // const std::string &text, TextFormat format, bool cache=false); + + + void Draw3D(const Matrix4 &model, const Matrix4 &view, const Matrix4 &projection, + const std::string &text, TextFormat format, bool cache=false); + + + Vector2 TextExtents(const std::string &text, TextFormat format, bool cache=false); + + float native_font_size(); + +private: + Texture2D atlas_; + float native_font_size_; + + stbtt_packedchar chardata_[128]; + + struct MeshData { + Mesh mesh; + Point2 min; + Point2 max; + }; + + void SetTextMesh(const std::string &text, MeshData *md); + + std::map<std::string, MeshData> cache_; + MeshData tmp_md_; + + ShaderProgram shader_; +}; + +} // end namespace + +#endif diff --git a/dev/MinGfx/src/texture2d.cc b/dev/MinGfx/src/texture2d.cc new file mode 100644 index 0000000..230ad5f --- /dev/null +++ b/dev/MinGfx/src/texture2d.cc @@ -0,0 +1,232 @@ +/* + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + See corresponding header file for details. + */ + +#include "texture2d.h" +#include "platform.h" + +#pragma warning (push) +#pragma warning (disable : 6001) +#pragma warning (disable : 6011) +#pragma warning (disable : 6262) +#pragma warning (disable : 6385) +#pragma warning (disable : 6387) +#pragma warning (disable : 26450) +#pragma warning (disable : 26451) +#pragma warning (disable : 26453) +#pragma warning (disable : 26495) +#pragma warning (disable : 26812) + +#ifdef WIN32 + // this is not needed on OSX or Linux, it must pick up the symbols from + // libnanogui.so, but it appears to be needed on Windows. + #define STB_IMAGE_IMPLEMENTATION +#endif +#include <stb_image.h> + +#pragma warning (pop) + +#include <iostream> + + +namespace mingfx { + + + +Texture2D::Texture2D(GLenum wrapMode, GLenum filterMode) : + dataType_(GL_UNSIGNED_BYTE), data_ubyte_(NULL), data_float_(NULL), + width_(0), height_(0), handleMemInternally_(true), texID_(0), + wrapMode_(wrapMode), filterMode_(filterMode) +{ +} + +Texture2D::~Texture2D() { + + // Mem handled internally is always of type data_ubyte_ because that is + // what the stbi image loading library returns + if ((handleMemInternally_) && (data_ubyte_ != NULL)) { + // BUG, TODO: Not sure why the call below does not seem to work. + // There will be a mem leak unless we can call this somehow. + //stbi_image_free(data_); + } + + // This is how to delete GL's version of the texture on the GPU + // but you have to be very careful with this. For example, if we cause + // C++ to make a tmp copy of the Texture2D or we do an assignment tex1=tex2 + // we now have two Texture2D objects pointing to the same OpenGL texture id. + // If one of them is deleted before the other, then the other will not be + // able to draw itself because the OpenGL tex id will be invalid. For now, + // this is "addressed" by simply skipping the glDeleteTextures call. This + // leads to some wasted OpenGL memory, and that would be a good thing to + // fix in the future, maybe via a shared_ptr or static refcount that maps + // opengl texids to a count of Texture2D objects that reference them. Then, + // only delete the opengl tex if the refcount would go to 0. + //glDeleteTextures(1, &texID_); +} + + +bool Texture2D::InitFromFile(const std::string &filename) { + handleMemInternally_ = true; + dataType_ = GL_UNSIGNED_BYTE; + + + std::cout << "Loading texture from file: " << filename << std::endl; + + if (Platform::FileExists(filename)) { + stbi_set_unpremultiply_on_load(1); + stbi_convert_iphone_png_to_rgb(1); + int numChannels; + data_ubyte_ = stbi_load(filename.c_str(), &width_, &height_, &numChannels, 4); + if (data_ubyte_ == NULL) { + std::cerr << "Texture2D: Failed to load file " << filename << " - " << stbi_failure_reason() << std::endl; + return false; + } + } + else { + std::cerr << "Texture2D: File " << filename << " does not exist." << std::endl; + return false; + } + + return InitOpenGL(); +} + +bool Texture2D::InitFromBytes(int width, int height, const unsigned char * data) { + handleMemInternally_ = false; + width_ = width; + height_ = height; + data_ubyte_ = data; + dataType_ = GL_UNSIGNED_BYTE; + + return InitOpenGL(); +} + +bool Texture2D::InitFromFloats(int width, int height, const float * data) { + handleMemInternally_ = false; + width_ = width; + height_ = height; + data_float_ = data; + dataType_ = GL_FLOAT; + + return InitOpenGL(); +} + +bool Texture2D::InitOpenGL() { + glGenTextures(1, &texID_); + glBindTexture(GL_TEXTURE_2D, texID_); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode_); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode_); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filterMode_); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filterMode_); + + if (dataType_ == GL_UNSIGNED_BYTE) { + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width_, height_, 0, GL_RGBA, dataType_, data_ubyte_); + } + else if (dataType_ == GL_FLOAT) { + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width_, height_, 0, GL_RGBA, dataType_, data_float_); + } + else { + std::cerr << "Texture2D: Unsupported texture data type " << dataType_ << "." << std::endl; + return false; + } + + return true; +} + + +bool Texture2D::UpdateFromBytes(const unsigned char * data) { + dataType_ = GL_UNSIGNED_BYTE; + data_ubyte_ = data; + glBindTexture(GL_TEXTURE_2D, texID_); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width_, height_, 0, GL_RGBA, dataType_, data_ubyte_); + // presumably glTexSubImage2D is faster, but this crashes on OSX for some reason + //glActiveTexture(texID_); + //glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width_, height_, GL_RGBA, dataType_, data_ubyte_); + return true; +} + +bool Texture2D::UpdateFromFloats(const float * data) { + dataType_ = GL_FLOAT; + data_float_ = data; + glBindTexture(GL_TEXTURE_2D, texID_); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width_, height_, 0, GL_RGBA, dataType_, data_float_); + // presumably glTexSubImage2D is faster, but this crashes on OSX for some reason + //glActiveTexture(texID_); + //glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width_, height_, GL_RGBA, dataType_, data_ubyte_); + return true; +} + + + +int Texture2D::width() const { + return width_; +} + +int Texture2D::height() const { + return height_; +} + + +GLuint Texture2D::opengl_id() const { + if (!initialized()) { + std::cerr << "Texture2D: Warning, accessing opengl_id() before it has been initialized." << std::endl + << "You might be calling opengl_id() before InitOpenGL(). Or, there might have been a" << std::endl + << "error loading texture data or binding it to OpenGL." << std::endl; + } + return texID_; +} + +GLenum Texture2D::wrap_mode() const { + return wrapMode_; +} + +GLenum Texture2D::filter_mode() const { + return filterMode_; +} + +void Texture2D::set_wrap_mode(GLenum wrapMode) { + wrapMode_ = wrapMode; + glBindTexture(GL_TEXTURE_2D, texID_); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode_); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode_); +} + +void Texture2D::set_filter_mode(GLenum filterMode) { + filterMode_ = filterMode; + glBindTexture(GL_TEXTURE_2D, texID_); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filterMode_); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filterMode_); +} + +bool Texture2D::initialized() const { + return texID_ != 0; +} + + +Color Texture2D::Pixel(int x, int y) const { + int index = y*4*width() + x*4; + + if (dataType_ == GL_UNSIGNED_BYTE) { + unsigned char r = data_ubyte_[index+0]; + unsigned char g = data_ubyte_[index+1]; + unsigned char b = data_ubyte_[index+2]; + unsigned char a = data_ubyte_[index+3]; + return Color((float)r/255.0f, (float)g/255.0f, (float)b/255.0f, (float)a/255.0f); + } + else if (dataType_ == GL_FLOAT) { + float r = data_float_[index+0]; + float g = data_float_[index+1]; + float b = data_float_[index+2]; + float a = data_float_[index+3]; + return Color(r, g, b, a); + } + else { + std::cerr << "Texture2D: Unsupported texture data type " << dataType_ << "." << std::endl; + return Color(); + } +} + +} // end namespace + diff --git a/dev/MinGfx/src/texture2d.h b/dev/MinGfx/src/texture2d.h new file mode 100644 index 0000000..a07d80f --- /dev/null +++ b/dev/MinGfx/src/texture2d.h @@ -0,0 +1,139 @@ +/* + This file is part of the MinGfx Project. + + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + + Original Author(s) of this File: + Dan Keefe, 2018, University of Minnesota + + Author(s) of Significant Updates/Modifications to the File: + ... + */ + +#ifndef SRC_TEXTURE2D_H_ +#define SRC_TEXTURE2D_H_ + + +#include "opengl_headers.h" +#include "color.h" + +#include <string> + + +namespace mingfx { + +/** A wrapper around a 2D texture that supports loading images from files or + setting texture color data directly. Example: + ~~~ + Texture2D tex1; + Texture2D tex2(GL_CLAMP_TO_EDGE); + + void MyGraphicsApp::InitOpenGL() { + std::vector<std::string> search_path; + search_path.push_back("."); + search_path.push_back("./data"); + search_path.push_back("./shaders"); + tex1.InitFromFile(Platform::FindFile("earth-2k.png", search_path)); + tex2.InitFromFile(Platform::FindFile("toon-ramp.png", search_path)); + } + ~~~ + */ +class Texture2D { +public: + + /// Creates an empty texture. Optional parameters can be provided to set + /// the texture wrap mode and filter mode. + Texture2D(GLenum wrapMode=GL_REPEAT, GLenum filterMode=GL_LINEAR); + virtual ~Texture2D(); + + + /// Call this from within the InitOpenGL() function since it will initialize + /// not just the Texture2D's internal data but also an OpenGL texture to be + /// stored on the graphics card. Internally, this uses the stbi library to + /// load images. It supports png, jpg, bmp, and other file formats. + bool InitFromFile(const std::string &filename); + + /// Call this from within the InitOpenGL() function since it will initialize + /// not just the Texture2D's internal data but also an OpenGL texture to be + /// stored on the graphics card. + /// With this version of Init, you may pass in your own pointer to color data. + /// The data argument must point to an array of 4-channel color data stored as + /// unsigned chars in RGBA format. You are responsible for managing the memory + /// for this array. If you will never call Pixel(), then it is safe to free + /// data as soon as this function returns. Otherwise, you need to make sure + /// data does not change in memory until you destroy the Texture2D object. + bool InitFromBytes(int width, int height, const unsigned char * data); + + /// Call this from within the InitOpenGL() function since it will initialize + /// not just the Texture2D's internal data but also an OpenGL texture to be + /// stored on the graphics card. + /// With this version of Init, you may pass in your own pointer to color data. + /// The data argument must point to an array of 4-channel color data stored as + /// floats in RGBA format. You are responsible for managing the memory + /// for this array. If you will never call Pixel(), then it is safe to free + /// data as soon as this function returns. Otherwise, you need to make sure + /// data does not change in memory until you destroy the Texture2D object. + bool InitFromFloats(int width, int height, const float * data); + + + /// This function may be called to re-read the texture data from an array + /// formated the same as in InitFromBytes. The width and height of the + /// texture must remain the same. + bool UpdateFromBytes(const unsigned char * data); + + /// This function may be called to re-read the texture data from an array + /// formated the same as in InitFromFloats. The width and height of the + /// texture must remain the same. + bool UpdateFromFloats(const float * data); + + + /// Returns true if the texture data has been successfully transferred to OpenGL. + bool initialized() const; + + /// Returns the width in pixels of the texture. + int width() const; + + /// Returns the height in pixels of the texture. + int height() const; + + /// Returns the unsigned int used as the texture handle by OpenGL + GLuint opengl_id() const; + + /// Returns an enumerated constant for the OpenGL wrap mode used by the texture. + GLenum wrap_mode() const; + + /// Returns an enumerated constant for the OpenGL filter mode used by the texture. + GLenum filter_mode() const; + + /// Uses the OpenGL texture wrap mode arguments + void set_wrap_mode(GLenum wrapMode); + + /// Uses the OpenGL texture filter mode arguments + void set_filter_mode(GLenum filterMode); + + /// Returns the color at the specified pixel. The top left corner of the + /// image is (0,0) and the bottom right is (width()-1, height()-1). + Color Pixel(int x, int y) const; + +private: + + bool InitOpenGL(); + + GLenum dataType_; // GL_UNSIGNED_BYTE or GL_FLOAT + const unsigned char * data_ubyte_; + const float * data_float_; + + int width_; + int height_; + bool handleMemInternally_; + + GLuint texID_; + GLenum wrapMode_; + GLenum filterMode_; +}; + + +} // end namespace + +#endif
\ No newline at end of file diff --git a/dev/MinGfx/src/unicam.cc b/dev/MinGfx/src/unicam.cc new file mode 100644 index 0000000..0acb9c9 --- /dev/null +++ b/dev/MinGfx/src/unicam.cc @@ -0,0 +1,317 @@ +/* + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + See corresponding header file for details. + */ + +#include "unicam.h" + +#include "gfxmath.h" + +namespace mingfx { + + +UniCam::UniCam() : state_(UniCamState::START), defaultDepth_(4.0), boundingSphereRad_(1.0), + dollyFactor_(1.0), dollyInitialized_(false), elapsedTime_(0.0), hitGeometry_(false), + rotAngularVel_(0.0), rotInitialized_(false), rotLastTime_(0.0), showIcon_(false) +{ +} + +UniCam::UniCam(const Matrix4 &initialViewMatrix) : + state_(UniCamState::START), defaultDepth_(4.0), V_(initialViewMatrix), boundingSphereRad_(1.0), + dollyFactor_(1.0), dollyInitialized_(false), elapsedTime_(0.0), hitGeometry_(false), + rotAngularVel_(0.0), rotInitialized_(false), rotLastTime_(0.0), showIcon_(false) +{ +} + +UniCam::~UniCam() +{ +} + + +void UniCam::recalc_angular_vel() { + // update angular velocity + float cutoff = (float)elapsedTime_ - 0.2f; // look just at the last 0.2 secs + while ((rotAngularVelBuffer_.size()) && (rotAngularVelBuffer_[0].first < cutoff)) { + rotAngularVelBuffer_.erase(rotAngularVelBuffer_.begin()); + } + rotAngularVel_ = 0.0; + if (rotAngularVelBuffer_.size()) { + for (int i=0; i<rotAngularVelBuffer_.size(); i++) { + rotAngularVel_ += rotAngularVelBuffer_[i].second; + } + rotAngularVel_ /= rotAngularVelBuffer_.size(); + } + //std::cout << rotAngularVelBuffer_.size() << " " << rotAngularVel_ << std::endl; +} + + +void UniCam::OnButtonDown(const Point2 &mousePos, float mouseZ) { + if (state_ == UniCamState::START) { + initialClickPos_ = mousePos; + mouseLast_ = mousePos; + elapsedTime_ = 0.0; + rotInitialized_ = false; + dollyInitialized_ = false; + + hitGeometry_ = (mouseZ < 1.0); + if (hitGeometry_) { + hitPoint_ = GfxMath::ScreenToWorld(V_, Pdraw_, mousePos, mouseZ); + } + else { + hitPoint_ = GfxMath::ScreenToDepthPlane(V_, Pdraw_, Point2(0,0), defaultDepth_); + } + showIcon_ = true; + state_ = UniCamState::PAN_DOLLY_ROT_DECISION; + } + else if (state_ == UniCamState::ROT_WAIT_FOR_SECOND_CLICK) { + // we have the second click now, and we will start the trackball rotate interaction + state_ = UniCamState::ROT; + } + else if (state_ == UniCamState::SPINNING) { + // this click is to "catch" the model, stopping it from spinning. + state_ = UniCamState::START; + } + else { + std::cerr << "UniCam::OnButtonDown() unexpected state." << std::endl; + } +} + +void UniCam::OnDrag(const Point2 &mousePos) { + if (state_ == UniCamState::PAN_DOLLY_ROT_DECISION) { + const double panMovementThreshold = 0.01; + const double dollyMovementThreshold = 0.01; + if (fabs(mousePos[0] - initialClickPos_[0]) > panMovementThreshold) { + // already lots of horizontal movement, we can go right to pan + state_ = UniCamState::PAN; + showIcon_ = false; + } + else if (fabs(mousePos[1] - initialClickPos_[1]) > dollyMovementThreshold) { + // already lots of vertical movement, we can go right to dolly + state_ = UniCamState::DOLLY; + showIcon_ = false; + } + else if (elapsedTime_ > 1.0) { + // timeout, this was not a quick click to set a center of rotation, + // so there is no intent to rotate. instead we will be doing either + // pan or dolly. + state_ = UniCamState::PAN_DOLLY_DECISION; + showIcon_ = false; + } + } + else if (state_ == UniCamState::PAN_DOLLY_DECISION) { + const double panMovementThreshold = 0.01; + const double dollyMovementThreshold = 0.01; + if (fabs(mousePos[0] - initialClickPos_[0]) > panMovementThreshold) { + // lots of horizontal movement, go to pan + state_ = UniCamState::PAN; + } + else if (fabs(mousePos[1] - initialClickPos_[1]) > dollyMovementThreshold) { + // lots of vertical movement, go to dolly + state_ = UniCamState::DOLLY; + } + } + else if (state_ == UniCamState::PAN) { + Matrix4 camMat = V_.Inverse(); + Point3 eye = camMat.ColumnToPoint3(3); + Vector3 look = -camMat.ColumnToVector3(2); + float depth = (hitPoint_ - eye).Dot(look); + Point3 pWorld1 = GfxMath::ScreenToDepthPlane(V_, Pdraw_, mouseLast_, depth); + Point3 pWorld2 = GfxMath::ScreenToDepthPlane(V_, Pdraw_, mousePos, depth); + V_ = V_ * Matrix4::Translation(pWorld2 - pWorld1); + } + else if (state_ == UniCamState::DOLLY) { + if (!dollyInitialized_) { + // Setup dollyFactor so that if you move the mouse to the bottom of the screen, the point + // you clicked on will be right on top of the camera. + Matrix4 camMat = V_.Inverse(); + Point3 eye = camMat.ColumnToPoint3(3); + Vector3 look = -camMat.ColumnToVector3(2); + float depth = (hitPoint_ - eye).Dot(look); + float deltaYToBottom = initialClickPos_[1] + 1; + dollyFactor_ = depth / deltaYToBottom; + dollyInitialized_ = true; + } + Vector3 d(0, 0, -dollyFactor_ * (mousePos[1] - mouseLast_[1])); + V_ = Matrix4::Translation(d) * V_ ; + } + else if (state_ == UniCamState::ROT) { + if (!rotInitialized_) { + float depth = 0.0; + if (hitGeometry_) { + // if we hit some geometry, then make that the center of rotation + boundingSphereCtr_ = hitPoint_; + Matrix4 camMat = V_.Inverse(); + Point3 eye = camMat.ColumnToPoint3(3); + Vector3 look = -camMat.ColumnToVector3(2); + depth = (hitPoint_ - eye).Dot(look); + } + else { + // if we did not hit any geometry, then center the bounding sphere in front of + // the camera at a distance that can be configured by the user. + boundingSphereCtr_ = GfxMath::ScreenToDepthPlane(V_, Pdraw_, Point2(0,0), defaultDepth_); + depth = defaultDepth_; + } + + // determine the size of the bounding sphere by projecting a screen-space + // distance of 0.75 units to the depth of the sphere center + Point3 pWorld1 = GfxMath::ScreenToDepthPlane(V_, Pdraw_, Point2(0,0), depth); + Point3 pWorld2 = GfxMath::ScreenToDepthPlane(V_, Pdraw_, Point2(0.75,0), depth); + boundingSphereRad_ = (pWorld2-pWorld1).Length(); + + rotLastTime_ = elapsedTime_; + rotAngularVelBuffer_.clear(); + rotInitialized_ = true; + } + else { + // Do a trackball rotation based on the mouse movement and the bounding sphere + // setup earlier. + + Matrix4 camMat = V_.Inverse(); + Point3 eye = camMat.ColumnToPoint3(3); + + // last mouse pos + bool hit1 = false; + Point3 mouse3D1 = GfxMath::ScreenToNearPlane(V_, Pdraw_, mouseLast_); + Ray ray1(eye, mouse3D1 - eye); + float t1; + Point3 iPoint1; + if (ray1.IntersectSphere(boundingSphereCtr_, boundingSphereRad_, &t1, &iPoint1)) { + hit1 = true; + } + + // current mouse pos + bool hit2 = false; + Point3 mouse3D2 = GfxMath::ScreenToNearPlane(V_, Pdraw_, mousePos); + Ray ray2(eye, mouse3D2 - eye); + float t2; + Point3 iPoint2; + if (ray2.IntersectSphere(boundingSphereCtr_, boundingSphereRad_, &t2, &iPoint2)) { + hit2 = true; + } + rotLastIPoint_ = iPoint2; + + if (hit1 && hit2) { + Vector3 v1 = (iPoint1 - boundingSphereCtr_).ToUnit(); + Vector3 v2 = (iPoint2 - boundingSphereCtr_).ToUnit(); + + rotAxis_ = v1.Cross(v2).ToUnit(); + float angle = std::acos(v1.Dot(v2)); + + if (std::isfinite(angle)) { + Matrix4 R = Matrix4::Rotation(boundingSphereCtr_, rotAxis_, angle); + R = R.Orthonormal(); + V_ = V_ * R; + //V_ = V_.orthonormal(); + + // add a sample to the angular vel vector + double dt = elapsedTime_ - rotLastTime_; + double avel = angle / dt; + if (std::isfinite(avel)) { + rotAngularVelBuffer_.push_back(std::make_pair(elapsedTime_, avel)); + } + rotLastTime_ = elapsedTime_; + } + } + + recalc_angular_vel(); + } + } + else if (state_ == UniCamState::START) { + // picked up a little mouse movement after "catching" a spinning model + // nothing to do, just wait for the button up. + } + else { + std::cerr << "UniCam::OnDrag() unexpected state." << std::endl; + } + mouseLast_ = mousePos; +} + +void UniCam::OnButtonUp(const Point2 &mousePos) { + if (state_ == UniCamState::PAN_DOLLY_ROT_DECISION) { + // here, we got a quick click of the mouse to indicate a center of rotation + // so we now go into a mode of waiting for a second click to start rotating + // around that point. + state_ = UniCamState::ROT_WAIT_FOR_SECOND_CLICK; + } + else if (state_ == UniCamState::ROT) { + showIcon_ = false; + // if we are leaving the rotation state and the angular velocity is + // greater than some thresold, then the user has "thrown" the model + // keep rotating the same way by entering the spinning state. + + recalc_angular_vel(); + //std::cout << "check for spin: " << n-start << " " << rotAngularVel_ << " " << avel2 << std::endl; + + const float threshold = 0.2f; + if (std::fabs(rotAngularVel_) > threshold) { + state_ = UniCamState::SPINNING; + } + else { + state_ = UniCamState::START; + } + } + else { + showIcon_ = false; + // all other cases go back to the start state + state_ = UniCamState::START; + } +} + +void UniCam::AdvanceAnimation(double dt) { + elapsedTime_ += dt; + + if (state_ == UniCamState::SPINNING) { + double deltaT = elapsedTime_ - rotLastTime_; + rotLastTime_ = elapsedTime_; + double angle = (double)rotAngularVel_ * deltaT; + Matrix4 R = Matrix4::Rotation(boundingSphereCtr_, rotAxis_, (float)angle); + //R = R.orthonormal(); + V_ = V_ * R; + } +} + + +void UniCam::Draw(const Matrix4 &projectionMatrix) { + Pdraw_ = projectionMatrix; + + if (showIcon_) { + Matrix4 camMat = V_.Inverse(); + Point3 eye = camMat.ColumnToPoint3(3); + Vector3 look = -camMat.ColumnToVector3(2); + float depth = (hitPoint_ - eye).Dot(look); + Point3 pWorld1 = GfxMath::ScreenToDepthPlane(V_, Pdraw_, Point2(0.f,0.f), depth); + Point3 pWorld2 = GfxMath::ScreenToDepthPlane(V_, Pdraw_, Point2(0.015f,0.f), depth); + float rad = (pWorld2 - pWorld1).Length(); + Matrix4 M = Matrix4::Translation(hitPoint_ - Point3::Origin()) * Matrix4::Scale(Vector3(rad, rad, rad)); + quickShapes_.DrawSphere(M, V_, Pdraw_, Color(0,0,0)); + } +} + + +Matrix4 UniCam::view_matrix() { + return V_; +} + +void UniCam::set_view_matrix(Matrix4 viewMatrix) { + V_ = viewMatrix; +} + +void UniCam::set_default_depth(float d) { + defaultDepth_ = d; +} + +Point3 UniCam::eye() { + Matrix4 camMat = V_.Inverse(); + return camMat.ColumnToPoint3(3); +} + +Vector3 UniCam::look() { + Matrix4 camMat = V_.Inverse(); + return -camMat.ColumnToVector3(2); +} + + + + +} // end namespace diff --git a/dev/MinGfx/src/unicam.h b/dev/MinGfx/src/unicam.h new file mode 100644 index 0000000..999232b --- /dev/null +++ b/dev/MinGfx/src/unicam.h @@ -0,0 +1,256 @@ +/* + This file is part of the MinGfx Project. + + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + + Original Author(s) of this File: + Dan Keefe, 2018, University of Minnesota + + Author(s) of Significant Updates/Modifications to the File: + ... + */ + +#ifndef SRC_UNICAM_H_ +#define SRC_UNICAM_H_ + +#include "quick_shapes.h" +#include "point2.h" +#include "point3.h" +#include "vector2.h" +#include "vector3.h" + + +namespace mingfx { + + +/** This implements a user interface for controlling the camera with the mouse. + It is a special interface inspired by the "Unicam" technique developed by + Zeleznik et al. + + The key feature is that this interface makes it possible to control camera pan, + dolly, and rotation with only a single mouse button. That is quite useful + because it leaves the other mouse buttons free for pointing, sketching, or + other interaction techniques. + + The only downside of this technique is that it can take some time to learn. In + order to enjoy it, you will need to read these brief instructions on how to Pan, + Dolly, Rotate, and Spin: + + - Pan: Click and drag horizontally with the mouse. Once you make an initial + horizontal movement you can than pan up and down as well, but the key to entering + pan mode is to start with a horizontal movement. + + - Dolly: Click and drag vertically with the mouse. The initial movement must + be vertical. If you click on some object in the scene, then the speed of dollying + is set so that the object will come all the up to the camera lens if you drag + the mouse to the bottom of the screen. + + - Rotate: Start with a quick click and release to set the center of rotation. + This is most useful if you click on some object in the scene. You will see a + black dot appear to mark the center of rotation. If you click on the background + then a center of rotation will be selected for you. It will be a point straight + ahead and at a depth 4.0 units away. The depth can be adjusted for your application + with set_default_depth(). Once your center of rotation is established, move + your mouse away a bit and then click and drag to do a trackball rotatation of + the scene around this point. Come to a stop before letting go of the mouse + button in order to avoid entering the spin state! + + - Spin: For some fun, try "throwing" the scene so that it continues to rotate + even after you let go. To do this, start a rotation and then let go of the + mouse button while your mouse is still moving. To stop spinning just click and + release the mouse once to "catch" the scene. + + + Example usage: +~~~ +// Create a global or member variable in your MyGraphicsApp class: +UniCam unicam_; + + +void MyGraphicsApp::OnLeftMouseDown(const Point2 &pos) { + Point2 mouse_xy = PixelsToNormalizedDeviceCoords(pos); + float mouse_z = ReadZValueAtPixel(pos); + unicam_.OnButtonDown(mouse_xy, mouse_z); +} + +void MyGraphicsApp::OnLeftMouseDrag(const Point2 &pos, const Vector2 &delta) { + Point2 mouse_xy = PixelsToNormalizedDeviceCoords(pos); + unicam_.OnDrag(mouse_xy); +} + +void MyGraphicsApp::OnLeftMouseUp(const Point2 &pos) { + Point2 mouse_xy = PixelsToNormalizedDeviceCoords(pos); + unicam_.OnButtonUp(mouse_xy); +} + +void MyGraphicsApp::InitOpenGL() { + projMatrix_ = Matrix4::perspective(30, aspect_ratio(), 1, 20); + unicam_.set_view_matrix(Matrix4::lookAt(Point3(2.5,1,2.5), Point3(0,0,0), Vector3(0,1,0));); +} + +void MyGraphicsApp::DrawOpenGL() { + // draw your scene using the view matrix from UniCam + Matrix4 proj_matrix = Matrix4::Perspective(60, aspect_ratio(), 0.001, 10);; + Matrix4 view_matrix = uniCam.view_matrix(); + Matrix4 model_matrix = Matrix4::RotateY(to_radians(45.0)); + quickShapes.DrawCube(model_matrix, view_matirx, proj_matrix, Color(1,1,1)); + + // tell unicam to draw itself (i.e., the small sphere that marks the center of + // rotation when in rotation mode) + unicam_.Draw(proj_matrix); +} +~~~ +*/ +class UniCam { +public: + + /// Creates a UniCam object with an initial view matrix = identity. + UniCam(); + + /// Creates a UniCam object with the supplied initial view matrix. + UniCam(const Matrix4 &initialViewMatrix); + + virtual ~UniCam(); + + + // To make the interaction work, the following set of functions need to be + // called from your GraphicsApp or whatever main application class you use + // to receive user input events and a draw callback. + + /// Attach this to whatever mouse button you wish, for example, call this + /// from within GraphicsApp::OnRightMouseDown(). If your mousePos is reported + /// in pixels, you will need to convert it to normalized device coordinates + /// before passing it on to this routine. The depth buffer value for the + /// pixel under the mouse is also needed. If you are using GraphicsApp, you + /// can access both of these as follows: + /// ~~~ + /// Point2 mouse_xy = PixelsToNormalizedDeviceCoords(mouse_in_pixels); + /// float mouse_z = ReadZValueAtPixel(mouse_in_pixels); + /// uniCam.OnButtonDown(mouse_xy, mouse_z); + /// ~~~ + void OnButtonDown(const Point2 &normalizedMousePos, float mouseZ); + + /// Attach this to the corresponding mouse move event, for example, call this + /// from within GraphicsApp::OnRightMouseDrag(). If your mousePos is reported + /// in pixels, you will need to convert it to normalized device coordinates + /// before passing it on to this routine. Within GraphicsApp, use: + /// ~~~ + /// Point2 mouse_xy = PixelsToNormalizedDeviceCoords(mouse_in_pixels); + /// uniCam.OnDrag(mouse_xy); + /// ~~~ + void OnDrag(const Point2 &normalizedMousePos); + + /// Attach this to the corresponding button up event, for example, call this + /// from within GraphicsApp::OnRightMouseUp(). If your mousePos is reported + /// in pixels, you will need to convert it to normalized device coordinates + /// before passing it on to this routine. Within GraphicsApp, use: + /// ~~~ + /// Point2 mouse_xy = PixelsToNormalizedDeviceCoords(mouse_in_pixels); + /// uniCam.OnButtonUp(mouse_xy); + /// ~~~ + void OnButtonUp(const Point2 &normalizedMousePos); + + /// Attach this to a callback that can be used to control animation. Within + /// GraphicsApp::UpdateSimulation(), use: + /// ~~~ + /// uniCam.AdvanceAnimation(dt); + /// ~~~ + void AdvanceAnimation(double dt); + + /// Finally, attach this to your draw callback routine. Within + /// GraphicsApp::DrawUsingOpenGL(), use: + /// ~~~ + /// uniCam.Draw(projMatrix); + /// ~~~ + void Draw(const Matrix4 &projectionMatrix); + + + /// Access the camera view matrix created by the UniCam interactions via + /// this method and use it to draw the geometry in your scence. + /// For example, within GraphicsApp::DrawUsingOpenGL(), you might have: + /// ~~~ + /// Matrix4 P = Matrix4::Perspective(30, aspect_ratio(), 1, 20); + /// Matrix4 V = unicam.view_matrix(); + /// Matrix4 M = Matrix4::RotateY(GfxMath::ToRadians(45.0)); + /// quick_shapes.DrawCube(M, V, P, Color(1,1,1)); + /// ~~~ + Matrix4 view_matrix(); + + + /// Returns the "eye" point (i.e., focal point) of the camera in world + /// space coordinates. + Point3 eye(); + + /// Returns the look direction (i.e., -Z axis of the camera matrix) in world + /// space coordinates. + Vector3 look(); + + + // ------------- + + /// This is not required, but you may use this if you wish to set an initial + /// view matrix or reset the view matrix + void set_view_matrix(Matrix4 viewMatrix); + + /// This sets the depth of the center of rotation for the case when the user's + /// click does not intersect any geometry. It defaults to 4 units, but the + /// right value to use depends very much on the current scene. For example, + /// you could set a very good value by calculating the current centroid of + /// your scene and the finding the depth of this point (the distance along + /// the look vector) relative to the camera. + void set_default_depth(float d); + + +private: + + void recalc_angular_vel(); + + enum class UniCamState { + START, + PAN_DOLLY_ROT_DECISION, + PAN_DOLLY_DECISION, + ROT_WAIT_FOR_SECOND_CLICK, + PAN, + DOLLY, + ROT, + SPINNING + }; + UniCamState state_; + + Point2 mouseLast_; + double elapsedTime_; + + Point2 initialClickPos_; + bool hitGeometry_; + Point3 hitPoint_; + + bool rotInitialized_; + Point3 rotLastIPoint_; + float boundingSphereRad_; + Point3 boundingSphereCtr_; + double rotLastTime_; + std::vector<std::pair<double, double>> rotAngularVelBuffer_; + double rotAngularVel_; + Vector3 rotAxis_; + + bool dollyInitialized_; + float dollyFactor_; + float defaultDepth_; + + bool showIcon_; + QuickShapes quickShapes_; + + Matrix4 V_; + Matrix4 Vstart_; + + // saved from the last draw call in order to unproject the mouse pos + Matrix4 Pdraw_; +}; + + +} // end namespace + +#endif + + diff --git a/dev/MinGfx/src/vector2.cc b/dev/MinGfx/src/vector2.cc new file mode 100644 index 0000000..cada189 --- /dev/null +++ b/dev/MinGfx/src/vector2.cc @@ -0,0 +1,185 @@ +/* + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + See corresponding header file for details. + */ + +#include "vector2.h" + +#include <math.h> + +namespace mingfx { + + +static const Vector2 s_zerov2d = Vector2(0,0); +static const Vector2 s_onev2d = Vector2(1,1); +static const Vector2 s_unitxv2d = Vector2(1,0); +static const Vector2 s_unityv2d = Vector2(0,1); + +const Vector2& Vector2::Zero() { return s_zerov2d; } +const Vector2& Vector2::One() { return s_onev2d; } +const Vector2& Vector2::UnitX() { return s_unitxv2d; } +const Vector2& Vector2::UnitY() { return s_unityv2d; } + +Vector2::Vector2() { + v[0] = 0.0; + v[1] = 0.0; +} + +Vector2::Vector2(float x, float y) { + v[0] = x; + v[1] = y; +} + +Vector2::Vector2(float *ptr) { + v[0] = ptr[0]; + v[1] = ptr[1]; +} + +Vector2::Vector2(const Vector2& other) { + v[0] = other[0]; + v[1] = other[1]; +} + +Vector2::~Vector2() { +} + +bool Vector2::operator==(const Vector2& other) const { + return (fabs(other[0] - v[0]) < MINGFX_MATH_EPSILON && + fabs(other[1] - v[1]) < MINGFX_MATH_EPSILON); +} + +bool Vector2::operator!=(const Vector2& other) const { + return (fabs(other[0] - v[0]) >= MINGFX_MATH_EPSILON && + fabs(other[1] - v[1]) >= MINGFX_MATH_EPSILON); +} + +Vector2& Vector2::operator=(const Vector2& other) { + v[0] = other[0]; + v[1] = other[1]; + return *this; +} + +float Vector2::operator[](const int i) const { + if ((i>=0) && (i<=1)) { + return v[i]; + } + else { + // w component of a vector is 0 so return the constant 0.0 + return 0.0; + } +} + +float& Vector2::operator[](const int i) { + return v[i]; +} + +float Vector2::Dot(const Vector2& other) const { + return v[0]*other[0] + v[1]*other[1]; +} + +float Vector2::Length() const { + return sqrt(v[0]*v[0] + v[1]*v[1]); +} + +void Vector2::Normalize() { + // Hill & Kelley provide this: + float sizeSq = + v[0]*v[0] + v[1]*v[1]; + if (sizeSq < MINGFX_MATH_EPSILON) { + return; // do nothing to zero vectors; + } + float scaleFactor = (float)1.0/(float)sqrt(sizeSq); + v[0] *= scaleFactor; + v[1] *= scaleFactor; +} + +Vector2 Vector2::ToUnit() const { + Vector2 v(*this); + v.Normalize(); + return v; +} + + +Vector2 Vector2::Lerp(const Vector2 &b, float alpha) const { + float x = (1.0f-alpha)*(*this)[0] + alpha*b[0]; + float y = (1.0f-alpha)*(*this)[1] + alpha*b[1]; + return Vector2(x,y); +} + +Vector2 Vector2::Lerp(const Vector2 &a, const Vector2 &b, float alpha) { + float x = (1.0f-alpha)*a[0] + alpha*b[0]; + float y = (1.0f-alpha)*a[1] + alpha*b[1]; + return Vector2(x,y); +} + + +const float * Vector2::value_ptr() const { + return v; +} + + +Vector2 Vector2::Normalize(const Vector2 &v) { + return v.ToUnit(); +} + + +float Vector2::Dot(const Vector2 &v1, const Vector2 &v2) { + return v1.Dot(v2); +} + + + +Vector2 operator/(const Vector2& v, const float s) { + const float invS = 1 / s; + return Vector2(v[0]*invS, v[1]*invS); +} + +Vector2 operator*(const float s, const Vector2& v) { + return Vector2(v[0]*s, v[1]*s); +} + +Vector2 operator*(const Vector2& v, const float s) { + return Vector2(v[0]*s, v[1]*s); +} + +Vector2 operator-(const Vector2& v) { + return Vector2(-v[0], -v[1]); +} + +Point2 operator+(const Vector2& v, const Point2& p) { + return Point2(p[0] + v[0], p[1] + v[1]); +}; + +Point2 operator+(const Point2& p, const Vector2& v) { + return Point2(p[0] + v[0], p[1] + v[1]); +} + +Vector2 operator+(const Vector2& v1, const Vector2& v2) { + return Vector2(v1[0] + v2[0], v1[1] + v2[1]); +} + +Point2 operator-(const Point2& p, const Vector2& v) { + return Point2(p[0] - v[0], p[1] - v[1]); +} + +Vector2 operator-(const Vector2& v1, const Vector2& v2) { + return Vector2(v1[0] - v2[0], v1[1] - v2[1]); +} + +Vector2 operator-(const Point2& p1, const Point2& p2) { + return Vector2(p1[0] - p2[0], p1[1] - p2[1]); +} + + +std::ostream & operator<< ( std::ostream &os, const Vector2 &v) { + return os << "<" << v[0] << ", " << v[1] << ">"; +} + +std::istream & operator>> ( std::istream &is, Vector2 &v) { + // format: <x, y, z> + char dummy; + return is >> dummy >> v[0] >> dummy >> v[1] >> dummy; +} + + +} // end namespace diff --git a/dev/MinGfx/src/vector2.h b/dev/MinGfx/src/vector2.h new file mode 100644 index 0000000..f1a8d3e --- /dev/null +++ b/dev/MinGfx/src/vector2.h @@ -0,0 +1,186 @@ +/* + This file is part of the MinGfx Project. + + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + + Original Author(s) of this File: + Dan Keefe, 2018, University of Minnesota + + Author(s) of Significant Updates/Modifications to the File: + ... + */ + +#ifndef SRC_VECTOR2_H_ +#define SRC_VECTOR2_H_ + +#include <iostream> + +#include "point2.h" + + +namespace mingfx { + + +/** A 2D Vector with floating point coordinates, used for storing 2D translations, + mouse movements, and screen-space vectors. + */ +class Vector2 { +public: + + /// Default constructor to create zero vector + Vector2(); + + /// Constructs a vector (x,y,0), where the 0 comes from the use of + /// homogeneous coordinates in computer graphics. + Vector2(float x, float y); + + /// Constructs a vector given a pointer to x,y,z data + Vector2(float *v); + + /// Copy constructor for vector + Vector2(const Vector2& v); + + /// Vector destructor + virtual ~Vector2(); + + /// Check for "equality", taking floating point imprecision into account + bool operator==(const Vector2& v) const; + + /// Check for "inequality", taking floating point imprecision into account + bool operator!=(const Vector2& v) const; + + /// Vector assignment operator + Vector2& operator=(const Vector2& v); + + /// Read only access to the ith coordinate of the vector. + float operator[](const int i) const; + + /// Returns a reference to the ith coordinate of the vector. Use this + /// accessor if you wish to set the coordinate rather than just request + /// its value. Example: + /// ~~~ + /// Vector2 a; + /// a[0] = 5.0; // set the x-coordinate of the vector + /// ~~~ + float& operator[](const int i); + + /// Read only access to the x coordinate. Can also use my_vector[0]. Use + /// the my_vector[0] = 1.0; form if you need to set the value. + float x() const { return v[0]; } + + /// Read only access to the y coordinate. Can also use my_vector[1]. Use + /// the my_vector[1] = 1.0; form if you need to set the value. + float y() const { return v[1]; } + + /// In homogeneous coordinates, the w coordinate for all vectors is 0.0. + float w() const { return 0.0; } + + + // --- Vector operations --- + + /// Returns "this dot v" + float Dot(const Vector2& v) const; + + /// Returns the length of the vector + float Length() const; + + /// Normalizes the vector by making it unit length. + void Normalize(); + + /// Returns a normalized (i.e., unit length) version of the vector without + /// modifying the original ('this') vector. + Vector2 ToUnit() const; + + /// Linear interpolation between this vector and another. Alpha=0.0 returns + /// this vector, and alpha=1.0 returns the other vector, other values blend + /// between the two. + Vector2 Lerp(const Vector2 &b, float alpha) const; + + /// Returns a const pointer to the raw data array + const float * value_ptr() const; + + + + /// Returns a new vector that is the unit version of v. + static Vector2 Normalize(const Vector2 &v); + + /// Returns v1 dot v2 + static float Dot(const Vector2 &v1, const Vector2 &v2); + + /// (0,0) - a shortcut for a special vector that is frequently needed + static const Vector2& Zero(); + + /// (1,1) - a shortcut for a special vector that is frequently needed + static const Vector2& One(); + + /// (1,0) - a shortcut for a special vector that is frequently needed + static const Vector2& UnitX(); + + /// (0,1) - a shortcut for a special vector that is frequently needed + static const Vector2& UnitY(); + + /// Linear interpolation between two vectors. Alpha=0.0 returns 'a' and + /// alpha=1.0 returns 'b', other values blend between the two. + static Vector2 Lerp(const Vector2 &a, const Vector2 &b, float alpha); + +private: + float v[2]; +}; + + +// ---------- Operator Overloads for Working with Vectors ---------- + + +// --- Scalers --- + +/// Divide the vector by the scalar s +Vector2 operator/(const Vector2& v, const float s); + +/// Multiply the vector by the scalar s +Vector2 operator*(const float s, const Vector2& v); + +/// Multiply the vector by the scalar s +Vector2 operator*(const Vector2& v, const float s); + +/// Negate the vector +Vector2 operator-(const Vector2& v); + +// Note: no -(point) operator, that's an undefined operation + + +// --- Point and Vector Arithmetic --- + +/// Adds a vector and a point, returns a point +Point2 operator+(const Vector2& v, const Point2& p); + +/// Adds a point and a vector, returns a point +Point2 operator+(const Point2& p, const Vector2& v); + +/// Adds a vector and a vector, returns a vector +Vector2 operator+(const Vector2& v1, const Vector2& v2); + +// Note: no (point + point) operator, that's an undefined operation + +/// Subtracts a vector from a point, returns a point +Point2 operator-(const Point2& p, const Vector2& v); + +/// Subtracts v2 from v1, returns a vector +Vector2 operator-(const Vector2& v1, const Vector2& v2); + +/// Returns the vector spanning p1 and p2 +Vector2 operator-(const Point2& p1, const Point2& p2); + +// Note: no (vector - point) operator, that's an undefined operation + + +// --- Stream operators --- + +// Vector2 +std::ostream & operator<< ( std::ostream &os, const Vector2 &v); +std::istream & operator>> ( std::istream &is, Vector2 &v); + + +} // end namespace + +#endif diff --git a/dev/MinGfx/src/vector3.cc b/dev/MinGfx/src/vector3.cc new file mode 100644 index 0000000..19262c2 --- /dev/null +++ b/dev/MinGfx/src/vector3.cc @@ -0,0 +1,206 @@ +/* + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + See corresponding header file for details. + */ + +#include "vector3.h" + +#include <math.h> + +namespace mingfx { + +static const Vector3 s_zerov3d = Vector3(0,0,0); +static const Vector3 s_onev3d = Vector3(1,1,1); +static const Vector3 s_unitxv3d = Vector3(1,0,0); +static const Vector3 s_unityv3d = Vector3(0,1,0); +static const Vector3 s_unitzv3d = Vector3(0,0,1); + +const Vector3& Vector3::Zero() { return s_zerov3d; } +const Vector3& Vector3::One() { return s_onev3d; } +const Vector3& Vector3::UnitX() { return s_unitxv3d; } +const Vector3& Vector3::UnitY() { return s_unityv3d; } +const Vector3& Vector3::UnitZ() { return s_unitzv3d; } + + +Vector3::Vector3() { + v[0] = 0.0; + v[1] = 0.0; + v[2] = 0.0; +} + +Vector3::Vector3(float x, float y, float z) { + v[0] = x; + v[1] = y; + v[2] = z; +} + +Vector3::Vector3(float *ptr) { + v[0] = ptr[0]; + v[1] = ptr[1]; + v[2] = ptr[2]; +} + +Vector3::Vector3(const Vector3& other) { + v[0] = other[0]; + v[1] = other[1]; + v[2] = other[2]; +} + +Vector3::~Vector3() { +} + +bool Vector3::operator==(const Vector3& other) const { + return (fabs(other[0] - v[0]) < MINGFX_MATH_EPSILON && + fabs(other[1] - v[1]) < MINGFX_MATH_EPSILON && + fabs(other[2] - v[2]) < MINGFX_MATH_EPSILON); +} + +bool Vector3::operator!=(const Vector3& other) const { + return (fabs(other[0] - v[0]) >= MINGFX_MATH_EPSILON && + fabs(other[1] - v[1]) >= MINGFX_MATH_EPSILON && + fabs(other[2] - v[2]) >= MINGFX_MATH_EPSILON); +} + +Vector3& Vector3::operator=(const Vector3& other) { + v[0] = other[0]; + v[1] = other[1]; + v[2] = other[2]; + return *this; +} + +float Vector3::operator[](const int i) const { + if ((i>=0) && (i<=2)) { + return v[i]; + } + else { + // w component of a vector is 0 so return the constant 0.0 + return 0.0; + } +} + +float& Vector3::operator[](const int i) { + return v[i]; +} + +float Vector3::Dot(const Vector3& other) const { + return v[0]*other[0] + v[1]*other[1] + v[2]*other[2]; +} + +Vector3 Vector3::Cross(const Vector3& other) const { + return Vector3(v[1] * other[2] - v[2] * other[1], + v[2] * other[0] - v[0] * other[2], + v[0] * other[1] - v[1] * other[0]); +} + +float Vector3::Length() const { + return sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]); +} + +void Vector3::Normalize() { + // Hill & Kelley provide this: + float sizeSq = + v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; + if (sizeSq < MINGFX_MATH_EPSILON) { + return; // do nothing to zero vectors; + } + float scaleFactor = (float)1.0/(float)sqrt(sizeSq); + v[0] *= scaleFactor; + v[1] *= scaleFactor; + v[2] *= scaleFactor; +} + + +Vector3 Vector3::ToUnit() const { + Vector3 v(*this); + v.Normalize(); + return v; +} + +const float * Vector3::value_ptr() const { + return v; +} + + + +Vector3 Vector3::Normalize(const Vector3 &v) { + return v.ToUnit(); +} + +Vector3 Vector3::Cross(const Vector3 &v1, const Vector3 &v2) { + return v1.Cross(v2); +} + +float Vector3::Dot(const Vector3 &v1, const Vector3 &v2) { + return v1.Dot(v2); +} + +Vector3 Vector3::Lerp(const Vector3 &b, float alpha) const { + float x = (1.0f-alpha)*(*this)[0] + alpha*b[0]; + float y = (1.0f-alpha)*(*this)[1] + alpha*b[1]; + float z = (1.0f-alpha)*(*this)[2] + alpha*b[2]; + return Vector3(x,y,z); +} + +Vector3 Vector3::Lerp(const Vector3 &a, const Vector3 &b, float alpha) { + float x = (1.0f-alpha)*a[0] + alpha*b[0]; + float y = (1.0f-alpha)*a[1] + alpha*b[1]; + float z = (1.0f-alpha)*a[2] + alpha*b[2]; + return Vector3(x,y,z); +} + + + +Vector3 operator/(const Vector3& v, const float s) { + const float invS = 1 / s; + return Vector3(v[0]*invS, v[1]*invS, v[2]*invS); +} + +Vector3 operator*(const float s, const Vector3& v) { + return Vector3(v[0]*s, v[1]*s, v[2]*s); +} + +Vector3 operator*(const Vector3& v, const float s) { + return Vector3(v[0]*s, v[1]*s, v[2]*s); +} + +Vector3 operator-(const Vector3& v) { + return Vector3(-v[0], -v[1], -v[2]); +} + +Point3 operator+(const Vector3& v, const Point3& p) { + return Point3(p[0] + v[0], p[1] + v[1], p[2] + v[2]); +}; + +Point3 operator+(const Point3& p, const Vector3& v) { + return Point3(p[0] + v[0], p[1] + v[1], p[2] + v[2]); +} + +Vector3 operator+(const Vector3& v1, const Vector3& v2) { + return Vector3(v1[0] + v2[0], v1[1] + v2[1], v1[2] + v2[2]); +} + +Point3 operator-(const Point3& p, const Vector3& v) { + return Point3(p[0] - v[0], p[1] - v[1], p[2] - v[2]); +} + +Vector3 operator-(const Vector3& v1, const Vector3& v2) { + return Vector3(v1[0] - v2[0], v1[1] - v2[1], v1[2] - v2[2]); +} + +Vector3 operator-(const Point3& p1, const Point3& p2) { + return Vector3(p1[0] - p2[0], p1[1] - p2[1], p1[2] - p2[2]); +} + + +std::ostream & operator<< ( std::ostream &os, const Vector3 &v) { + return os << "<" << v[0] << ", " << v[1] << ", " << v[2] << ">"; +} + +std::istream & operator>> ( std::istream &is, Vector3 &v) { + // format: <x, y, z> + char dummy; + return is >> dummy >> v[0] >> dummy >> v[1] >> dummy >> v[2] >> dummy; +} + + +} // end namespace diff --git a/dev/MinGfx/src/vector3.h b/dev/MinGfx/src/vector3.h new file mode 100644 index 0000000..d12c819 --- /dev/null +++ b/dev/MinGfx/src/vector3.h @@ -0,0 +1,273 @@ +/* + This file is part of the MinGfx Project. + + Copyright (c) 2017,2018 Regents of the University of Minnesota. + All Rights Reserved. + + Original Author(s) of this File: + Dan Keefe, 2018, University of Minnesota + + Author(s) of Significant Updates/Modifications to the File: + ... + */ + +#ifndef SRC_VECTOR3_H_ +#define SRC_VECTOR3_H_ + +#include <iostream> + +#include "point3.h" + + +namespace mingfx { + + +/** A 3D Vector with floating point coordinates, used for storing normals and + all sorts of other 3D graphics operations. Vector3s can be transformed by a + Matrix4, and a Vector3 can be created by subtracting two Point3s. Example: + ~~~ + // subtracting two points creates a vector + Point3 a(0,0,0); + Point3 b(2,0,0); + Vector3 c = b - a; + + // vectors can be transformed by Matrix4s + Vector3 dir = c.ToUnit(); + Matrix4 M = Matrix4::RotateX(GfxMath::ToDegrees(30.0)); + Vector3 dir_transformed = M * dir; + + // vectors can be added and subtracted + Vector3 d(1,0,0); + Vector3 e = c + d; + + // and we can do the usual dot products and cross products too + float f = d.Dot(e); + Vector3 g = b.Cross(d); + + // you can access the individual components of the vector in two ways: + Vector3 v(1,2,3); + float option1 = v.x(); + float option2 = v[0]; + + // to set an individual component of the vector use the [] operator: + Vector3 w; + w[0] = 0.4; + w[1] = 1.2; + w[2] = 3.1; + + // you can print the vector by sending it to stdout: + std::cout << v << std::endl; + ~~~ + */ +class Vector3 { +public: + + /// Default constructor to create zero vector + Vector3(); + + /// Constructs a vector (x,y,z,0), where the 0 comes from the use of + /// homogeneous coordinates in computer graphics + Vector3(float x, float y, float z); + + /// Constructs a vector given a pointer to x,y,z data + Vector3(float *v); + + /// Copy constructor for vector + Vector3(const Vector3& v); + + /// Vector destructor + virtual ~Vector3(); + + /// Check for "equality", taking floating point imprecision into account + bool operator==(const Vector3& v) const; + + /// Check for "inequality", taking floating point imprecision into account + bool operator!=(const Vector3& v) const; + + /// Vector assignment operator + Vector3& operator=(const Vector3& v); + + /// Read only access to the ith coordinate of the vector. + float operator[](const int i) const; + + /// Returns a reference to the ith coordinate of the vector. Use this + /// accessor if you wish to set the coordinate rather than just request + /// its value. Example: + /// ~~~ + /// Vector3 a; + /// a[0] = 5.0; // set the x-coordinate of the vector + /// ~~~ + float& operator[](const int i); + + /// Read only access to the x coordinate. Can also use my_vector[0]. Use + /// the my_vector[0] = 1.0; form if you need to set the value. + float x() const { return v[0]; } + + /// Read only access to the y coordinate. Can also use my_vector[1]. Use + /// the my_vector[1] = 1.0; form if you need to set the value. + float y() const { return v[1]; } + + /// Read only access to the z coordinate. Can also use my_vector[2]. Use + /// the my_vector[2] = 1.0; form if you need to set the value. + float z() const { return v[2]; } + + /// In homogeneous coordinates, the w coordinate for all vectors is 0.0. + float w() const { return 0.0; } + + + // --- Vector operations --- + + /** Returns "this dot v", for example: + ~~~ + Vector3 a(1,0,0); + Vector3 b(0.5,0,0); + float c = a.Dot(b); + ~~~ + */ + float Dot(const Vector3& v) const; + + /** Returns "this cross v", for example: + ~~~ + Vector3 x(1,0,0); + Vector3 y(0,1,0); + Vector3 z = x.Cross(y); + ~~~ + */ + Vector3 Cross(const Vector3& v) const; + + /// Returns the length of the vector + float Length() const; + + /// Normalizes the vector by making it unit length. + void Normalize(); + + /// Returns a normalized (i.e., unit length) version of the vector without + /// modifying the original 'this' vector. + Vector3 ToUnit() const; + + /// Returns a const pointer to the raw data array + const float * value_ptr() const; + + /// Linear interpolation between this vector and another. Alpha=0.0 returns + /// this vector, and alpha=1.0 returns the other vector, other values blend + /// between the two. + Vector3 Lerp(const Vector3 &b, float alpha) const; + + + /// (0,0,0) - a shortcut for a special vector that is frequently needed + static const Vector3& Zero(); + + /// (1,1,1) - a shortcut for a special vector that is frequently needed + static const Vector3& One(); + + /// (1,0,0) - a shortcut for a special vector that is frequently needed + static const Vector3& UnitX(); + + /// (0,1,0) - a shortcut for a special vector that is frequently needed + static const Vector3& UnitY(); + + /// (0,0,1) - a shortcut for a special vector that is frequently needed + static const Vector3& UnitZ(); + + + /** Returns a new vector that is the unit version of v. This is just an + alternative syntax for ToUnit(). Example: + ~~~ + Vector3 a(100,150,80); + Vector3 b = Vector3::Normalize(a); + Vector3 c = a.ToUnit(); + // b and c are the same. + ~~~ + */ + static Vector3 Normalize(const Vector3 &v); + + /** Returns v1 cross v2. This is just an alternative syntax for Cross(). + Example: + ~~~ + Vector3 x(1,0,0); + Vector3 y(0,1,0); + Vector3 z1 = Vector3::Cross(x,y); + Vector3 z2 = x.Cross(y); + // z1 and z2 are the same. + ~~~ + */ + static Vector3 Cross(const Vector3 &v1, const Vector3 &v2); + + /** Returns v1 dot v2. This is just an alternative syntax for Dot(). + Example: + ~~~ + Vector3 a(1,0,0); + Vector3 b(0.5,0,0); + Vector3 c1 = a.Dot(b); + Vector3 c2 = Vector3::Dot(a,b); + // c1 and c2 are the same. + ~~~ + */ + static float Dot(const Vector3 &v1, const Vector3 &v2); + + /// Linear interpolation between two vectors. Alpha=0.0 returns 'a' and + /// alpha=1.0 returns 'b', other values blend between the two. + static Vector3 Lerp(const Vector3 &a, const Vector3 &b, float alpha); + +private: + float v[3]; +}; + + +// ---------- Operator Overloads for Working with Vectors ---------- + + +// --- Scalers --- + +/// Divide the vector by the scalar s +Vector3 operator/(const Vector3& v, const float s); + +/// Multiply the vector by the scalar s +Vector3 operator*(const float s, const Vector3& v); + +/// Multiply the vector by the scalar s +Vector3 operator*(const Vector3& v, const float s); + +/// Negate the vector +Vector3 operator-(const Vector3& v); + +// Note: no -(point) operator, that's an undefined operation + + +// --- Point and Vector Arithmetic --- + +/// Adds a vector and a point, returns a point +Point3 operator+(const Vector3& v, const Point3& p); + +/// Adds a point and a vector, returns a point +Point3 operator+(const Point3& p, const Vector3& v); + +/// Adds a vector and a vector, returns a vector +Vector3 operator+(const Vector3& v1, const Vector3& v2); + +// Note: no (point + point) operator, that's an undefined operation + +/// Subtracts a vector from a point, returns a point +Point3 operator-(const Point3& p, const Vector3& v); + +/// Subtracts v2 from v1, returns a vector +Vector3 operator-(const Vector3& v1, const Vector3& v2); + +/// Returns the vector spanning p1 and p2 +Vector3 operator-(const Point3& p1, const Point3& p2); + +// Note: no (vector - point) operator, that's an undefined operation + + + + +// --- Stream operators --- + +// Vector3 +std::ostream & operator<< ( std::ostream &os, const Vector3 &v); +std::istream & operator>> ( std::istream &is, Vector3 &v); + + +} // end namespace + +#endif |