summaryrefslogtreecommitdiffstats
path: root/dev
diff options
context:
space:
mode:
authorKiet Tran <kietatr@gmail.com>2021-11-29 13:36:54 -0600
committerKiet Tran <kietatr@gmail.com>2021-11-29 13:36:54 -0600
commit3e757dbc71682972899a48651710c5d8280c3cee (patch)
treed0c5ebba6dbad61920e07b2eae5843c1a2a2766d /dev
parentUpdate artrender_app.cc (diff)
downloadcsci4611-3e757dbc71682972899a48651710c5d8280c3cee.tar
csci4611-3e757dbc71682972899a48651710c5d8280c3cee.tar.gz
csci4611-3e757dbc71682972899a48651710c5d8280c3cee.tar.bz2
csci4611-3e757dbc71682972899a48651710c5d8280c3cee.tar.lz
csci4611-3e757dbc71682972899a48651710c5d8280c3cee.tar.xz
csci4611-3e757dbc71682972899a48651710c5d8280c3cee.tar.zst
csci4611-3e757dbc71682972899a48651710c5d8280c3cee.zip
Publish A6
Diffstat (limited to '')
-rw-r--r--dev/a6-harold/.gitignore2
-rw-r--r--dev/a6-harold/CMakeLists.txt209
-rw-r--r--dev/a6-harold/README.md0
-rw-r--r--dev/a6-harold/billboards.cc206
-rw-r--r--dev/a6-harold/billboards.h78
-rw-r--r--dev/a6-harold/cmake/DownloadHelper.txt.in26
-rw-r--r--dev/a6-harold/cmake/ExternalProjectDownloadBuildInstall.cmake98
-rw-r--r--dev/a6-harold/cmake/MessageMacros.cmake17
-rw-r--r--dev/a6-harold/cmake/UseOpenGL.cmake52
-rw-r--r--dev/a6-harold/config.h.in15
-rw-r--r--dev/a6-harold/data/toonDiffuse.pngbin0 -> 685 bytes
-rw-r--r--dev/a6-harold/data/toonSpecular.pngbin0 -> 654 bytes
-rw-r--r--dev/a6-harold/edge_mesh.cc235
-rw-r--r--dev/a6-harold/edge_mesh.h82
-rw-r--r--dev/a6-harold/ground.cc283
-rw-r--r--dev/a6-harold/ground.h63
-rw-r--r--dev/a6-harold/harold_app.cc278
-rw-r--r--dev/a6-harold/harold_app.h120
-rw-r--r--dev/a6-harold/main.cc10
-rw-r--r--dev/a6-harold/shaders/artsy.frag29
-rw-r--r--dev/a6-harold/shaders/artsy.vert17
-rw-r--r--dev/a6-harold/shaders/outline.frag7
-rw-r--r--dev/a6-harold/shaders/outline.vert23
-rw-r--r--dev/a6-harold/shaders/stroke2d.frag9
-rw-r--r--dev/a6-harold/shaders/stroke2d.vert7
-rw-r--r--dev/a6-harold/shaders/stroke3d.frag9
-rw-r--r--dev/a6-harold/shaders/stroke3d.vert10
-rw-r--r--dev/a6-harold/sky.cc74
-rw-r--r--dev/a6-harold/sky.h52
29 files changed, 2011 insertions, 0 deletions
diff --git a/dev/a6-harold/.gitignore b/dev/a6-harold/.gitignore
new file mode 100644
index 0000000..dd1a9a8
--- /dev/null
+++ b/dev/a6-harold/.gitignore
@@ -0,0 +1,2 @@
+config.h
+build
diff --git a/dev/a6-harold/CMakeLists.txt b/dev/a6-harold/CMakeLists.txt
new file mode 100644
index 0000000..83ca82d
--- /dev/null
+++ b/dev/a6-harold/CMakeLists.txt
@@ -0,0 +1,209 @@
+# Original Author(s) of this File:
+# Daniel Keefe, 2017, University of Minnesota
+#
+# Author(s) of Significant Updates/Modifications to the File:
+# ...
+
+
+
+# You are encouraged to copy this example, move it outside of the MinGfx directory, and use
+# it as a starting point for your project. When you do this, you'll have to edit the
+# following line as needed to point to the MinGfx install prefix used on your system.
+
+# !!!!!!!!!!!!! EDIT THE FOLLOWING LINE AS NEEDED !!!!!!!!!!!!!
+list(APPEND CMAKE_PREFIX_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../build/install ../..)
+
+
+
+#### BASIC PROJECT SETUP ####
+
+project(a6-harold)
+
+# Using 3.9 to get a modern version of FindOpenGL.cmake
+cmake_minimum_required (VERSION 3.9)
+
+# Dependencies that are auto-downloaded, built, and installed for you will go in the
+# directory pointed to by the CMAKE_INSTALL_PREFIX. It defaults to a location inside
+# the build directory.
+if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT OR "${CMAKE_INSTALL_PREFIX}" STREQUAL "")
+ set (CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/install" CACHE PATH "default install path" FORCE )
+endif()
+
+# Add to paths cmake uses to search for scripts, modules, and config packages
+list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake ${CMAKE_INSTALL_PREFIX})
+list(INSERT CMAKE_PREFIX_PATH 0 ${CMAKE_INSTALL_PREFIX})
+
+include(MessageMacros)
+h1("Building ${PROJECT_NAME}")
+h2("Configuring paths")
+
+message(STATUS "Module path: ${CMAKE_MODULE_PATH}")
+message(STATUS "Prefix path: ${CMAKE_PREFIX_PATH}")
+message(STATUS "Install prefix: ${CMAKE_INSTALL_PREFIX}")
+
+set(DATA_DIR_BUILD ${CMAKE_CURRENT_SOURCE_DIR}/data)
+set(DATA_DIR_INSTALL ${CMAKE_INSTALL_PREFIX}/share/${PROJECT_NAME}/data)
+
+message(STATUS "Data dir (in build tree): ${DATA_DIR_BUILD}")
+message(STATUS "Data dir (in install tree): ${DATA_DIR_INSTALL}")
+
+set(SHADERS_DIR_BUILD ${CMAKE_CURRENT_SOURCE_DIR}/shaders)
+set(SHADERS_DIR_INSTALL ${CMAKE_INSTALL_PREFIX}/share/${PROJECT_NAME}/shaders)
+
+message(STATUS "Shaders dir (in build tree): ${SHADERS_DIR_BUILD}")
+message(STATUS "Shaders dir (in install tree): ${SHADERS_DIR_INSTALL}")
+
+
+# Configure a header file to pass some of the CMake settings to the source code
+configure_file(
+ ${CMAKE_CURRENT_SOURCE_DIR}/config.h.in
+ ${CMAKE_CURRENT_SOURCE_DIR}/config.h
+)
+
+#### SOURCE FOR THIS PROJECT ####
+h2("Configuring source files")
+
+set(SOURCEFILES
+ billboards.cc
+ harold_app.cc
+ edge_mesh.cc
+ ground.cc
+ main.cc
+ sky.cc
+)
+
+set(HEADERFILES
+ billboards.h
+ harold_app.h
+ config.h
+ edge_mesh.h
+ ground.h
+ sky.h
+)
+
+set(EXTRAFILES
+ config.h.in
+ README.md
+)
+
+set(SHADERFILES
+ shaders/artsy.vert
+ shaders/artsy.frag
+ shaders/outline.vert
+ shaders/outline.frag
+ shaders/stroke2d.vert
+ shaders/stroke2d.frag
+ shaders/stroke3d.vert
+ shaders/stroke3d.frag
+)
+
+set_source_files_properties(${EXTRAFILES} PROPERTIES HEADER_FILE_ONLY TRUE)
+set_source_files_properties(${SHADERFILES} PROPERTIES HEADER_FILE_ONLY TRUE)
+
+source_group("Shaders" FILES ${SHADERFILES})
+
+
+#### COMPILE OPTIONS ####
+
+h2("Configuring Compiler Options")
+
+
+
+message(STATUS "Building for " ${CMAKE_SYSTEM_NAME} ".")
+
+# Linux specific
+if (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
+ add_definitions(-DLINUX)
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC")
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14")
+endif()
+
+
+# Apple specific
+if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
+ add_definitions(-DOSX)
+
+ # RPATH settings, see https://cmake.org/Wiki/CMake_RPATH_handling
+ set(CMAKE_MACOSX_RPATH ON)
+
+ # use, i.e. don't skip the full RPATH for the build tree
+ SET(CMAKE_SKIP_BUILD_RPATH FALSE)
+
+ # when building, don't use the install RPATH already
+ # (but later on when installing)
+ SET(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE)
+
+ SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib")
+
+ # add the automatically determined parts of the RPATH
+ # which point to directories outside the build tree to the install RPATH
+ SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
+
+ # the RPATH to be used when installing, but only if it's not a system directory
+ LIST(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_PREFIX}/lib" isSystemDir)
+ IF("${isSystemDir}" STREQUAL "-1")
+ SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib")
+ ENDIF("${isSystemDir}" STREQUAL "-1")
+
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14")
+endif()
+
+
+# Windows specific
+if (WIN32)
+ add_definitions(-DWIN32)
+
+ if(NOT CMAKE_SIZEOF_VOID_P EQUAL 8)
+ message(FATAL_ERROR
+ "You must use the 64 bit version of the compiler. Be sure to set the correct generator when configuring through CMake.")
+ endif()
+endif()
+
+
+
+
+#### DEFINE TARGET(S) ####
+
+h2("Defining Target(s)")
+
+add_executable(${PROJECT_NAME} ${SOURCEFILES} ${HEADERFILES} ${EXTRAFILES} ${SHADERFILES})
+
+
+
+#### FIND AND ADD DEPENDENCIES ####
+
+h2("Adding Dependencies")
+set(EXTERNAL_DIR external)
+
+
+# MinGfx (linked with an imported cmake target so no need to specify include dirs)
+# This will try to find MinGfxConfig.cmake, which should have been installed under
+# CMAKE_INSTALL_PREFIX/lib/cmake/MinGfx when you installed the MinGfx Toolkit.
+find_package(MinGfx REQUIRED)
+target_link_libraries(${PROJECT_NAME} PUBLIC MinGfx::MinGfx)
+
+
+# Add dependency on OpenGL
+include(UseOpenGL)
+UseOpenGL(${PROJECT_NAME} PUBLIC ${EXTERNAL_DIR})
+
+
+
+#### INSTALL TARGET(S) ####
+
+h2("Configuring Install Target")
+
+# The install locations are relative to the CMAKE_INSTALL_PREFIX variable
+install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin)
+
+install(
+ DIRECTORY data/
+ DESTINATION ${DATA_DIR_INSTALL}
+ OPTIONAL
+)
+
+install(
+ DIRECTORY shaders/
+ DESTINATION ${SHADERS_DIR_INSTALL}
+ OPTIONAL
+)
diff --git a/dev/a6-harold/README.md b/dev/a6-harold/README.md
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/dev/a6-harold/README.md
diff --git a/dev/a6-harold/billboards.cc b/dev/a6-harold/billboards.cc
new file mode 100644
index 0000000..5ec508a
--- /dev/null
+++ b/dev/a6-harold/billboards.cc
@@ -0,0 +1,206 @@
+/** CSci-4611 Assignment 6: Harold
+ */
+
+#include "billboards.h"
+
+Billboards::Billboards() {
+
+}
+
+
+Billboards::~Billboards() {
+
+}
+
+
+void Billboards::Init(ShaderProgram *stroke3d_shaderprog) {
+ stroke3d_shaderprog_ = stroke3d_shaderprog;
+}
+
+
+/// Projects a 2D normalized screen point (e.g., the mouse position in normalized
+/// device coordinates) to a 3D point on a plane defined by an "origin", which can
+/// really be any point coincident with the plane, and the plane normal. Returns
+/// true if the screen point projects onto the plane and stores the result in
+/// plane_point. Returns false if the screen point does not project onto the plane.
+bool Billboards::ScreenPtHitsPlane(const Matrix4 &view_matrix, const Matrix4 &proj_matrix,
+ const Point3 &plane_origin, const Vector3 &plane_normal,
+ const Point2 &normalized_screen_pt, Point3 *plane_point)
+{
+ Matrix4 camera_matrix = view_matrix.Inverse();
+ Point3 eye = camera_matrix.ColumnToPoint3(3);
+
+ Point3 pt3d = GfxMath::ScreenToNearPlane(view_matrix, proj_matrix, normalized_screen_pt);
+ Ray ray(eye, (pt3d - eye).ToUnit());
+ float t;
+ return ray.IntersectPlane(plane_origin, plane_normal, &t, plane_point);
+}
+
+
+/// Checks to see if a ray starting at the eye point and passing through 2D
+/// normalized screen point projects onto any of the billboards in the scene. If
+/// so, returns the id of the closest billboard intersected. If not, returns -1.
+int Billboards::IntersectBillboard(const Matrix4 &view_matrix, const Matrix4 &proj_matrix,
+ const Point2 &normalized_screen_pt)
+{
+ Matrix4 camera_matrix = view_matrix.Inverse();
+ Point3 eye = camera_matrix.ColumnToPoint3(3);
+
+ int id = -1;
+ float best_i_time = -1.0;
+
+ Point3 pt3d = GfxMath::ScreenToNearPlane(view_matrix, proj_matrix, normalized_screen_pt);
+ Ray ray(eye, (pt3d - eye).ToUnit());
+
+ Point3 i_point;
+ float i_time;
+ int i_tri_id;
+ for (int i=0; i<billboards_.size(); i++) {
+ Matrix4 to_billboard_space = billboards_[i].transform.Inverse();
+ Ray ray_billboard_space = to_billboard_space * ray;
+ if ((ray_billboard_space.IntersectAABB(billboards_[i].bounding_box, &i_time)) &&
+ (ray_billboard_space.IntersectMesh(billboards_[i].mesh, &i_time, &i_point, &i_tri_id)) &&
+ ((best_i_time == -1.0) || (i_time < best_i_time))) {
+ id = i;
+ }
+ }
+
+ return id;
+}
+
+
+/// Adds a new stroke as a billboard by projecting it onto a plane parallel
+/// to the filmplane that intersects with the base point, which should lie
+/// on the ground.
+void Billboards::AddBillboardStroke(const Matrix4 &view_matrix, const Matrix4 &proj_matrix,
+ const std::vector<Point2> &stroke2d, const Mesh &stroke2d_mesh,
+ const Color &stroke_color, Ground *ground_ptr)
+{
+ Matrix4 camera_matrix = view_matrix.Inverse();
+ Point3 eye = camera_matrix.ColumnToPoint3(3);
+
+ // Create a new billboard
+ Billboard b;
+ b.color = stroke_color;
+ b.transform = Matrix4();
+ b.mesh = stroke2d_mesh;
+
+ // find the base point for the billboard
+ ground_ptr->ScreenPtHitsGround(view_matrix, proj_matrix, stroke2d[0], &b.anchor_pt);
+ Vector3 norm = (eye - b.anchor_pt);
+ norm[1] = 0.0; // with 0 change in Y so the billboard does not tilt
+ norm.Normalize(); // convert to a unit vector
+
+ // project the stroke into 3D to lie on the projection plane
+ std::vector<Point3> verts;
+ for (int i=0; i<b.mesh.num_vertices(); i++) {
+ Point2 pscreen = Point2(b.mesh.read_vertex_data(i)[0], b.mesh.read_vertex_data(i)[1]);
+ Point3 pplane;
+ ScreenPtHitsPlane(view_matrix, proj_matrix, b.anchor_pt, norm, pscreen, &pplane);
+ verts.push_back(pplane);
+ }
+
+ Matrix4 to_canonical_billboard = Matrix4::Align(b.anchor_pt, Vector3::UnitY(), norm,
+ Point3::Origin(), Vector3::UnitY(), Vector3::UnitZ());
+
+ for (int i=0; i<verts.size(); i++) {
+ verts[i] = to_canonical_billboard * verts[i];
+ }
+
+ b.mesh.SetVertices(verts);
+
+ for (int i=0; i<b.mesh.num_vertices(); i++) {
+ b.bounding_box = b.bounding_box + AABB(b.mesh.read_vertex_data(i));
+ }
+ b.bounding_box.set_user_data((int)billboards_.size());
+
+ billboards_.push_back(b);
+}
+
+
+/// Edits an existing billboard by adding an additional stroke to it.
+void Billboards::AddToBillboard(const Matrix4 &view_matrix, const Matrix4 &proj_matrix,
+ int billboard_id, const Mesh &stroke2d_mesh,
+ const Color &stroke_color)
+{
+ Matrix4 camera_matrix = view_matrix.Inverse();
+ Point3 eye = camera_matrix.ColumnToPoint3(3);
+
+ // Really what this does is add a new billboard that lies in the same plane
+ // as the one we are "editing" and has the same anchor point so that they
+ // will both rotate the same way.
+ Billboard b;
+ b.color = stroke_color;
+ b.transform = Matrix4();
+ b.mesh = stroke2d_mesh;
+
+ b.anchor_pt = billboards_[billboard_id].anchor_pt;
+
+
+ Vector3 norm = (eye - b.anchor_pt);
+ norm[1] = 0.0; // with 0 change in Y so the billboard does not tilt
+ norm.Normalize(); // convert to a unit vector
+
+ // project the stroke into 3D to lie on the projection plane
+ std::vector<Point3> verts;
+ for (int i=0; i<b.mesh.num_vertices(); i++) {
+ Point2 pscreen = Point2(b.mesh.read_vertex_data(i)[0], b.mesh.read_vertex_data(i)[1]);
+ Point3 pplane;
+ ScreenPtHitsPlane(view_matrix, proj_matrix, b.anchor_pt, norm, pscreen, &pplane);
+ verts.push_back(pplane);
+ }
+
+ Matrix4 to_canonical_billboard = Matrix4::Align(b.anchor_pt, Vector3::UnitY(), norm,
+ Point3::Origin(), Vector3::UnitY(), Vector3::UnitZ());
+
+ for (int i=0; i<verts.size(); i++) {
+ verts[i] = to_canonical_billboard * verts[i];
+ }
+
+ b.mesh.SetVertices(verts);
+
+ for (int i=0; i<b.mesh.num_vertices(); i++) {
+ b.bounding_box = b.bounding_box + AABB(b.mesh.read_vertex_data(i));
+ }
+ b.bounding_box.set_user_data((int)billboards_.size());
+
+ billboards_.push_back(b);
+}
+
+
+void Billboards::UpdateBillboardRotations(const Point3 &current_eye_point) {
+ // Whenver the camera moves, we also need to rotate the billboards so
+ // that they always face the viewer.
+ for (int i=0; i<billboards_.size(); i++) {
+ // billboards are stored in a coordinate system where the base is
+ // at (0,0,0), rotation happens about the Y axis, and the billboard's
+ // normal is +Z.
+ Point3 a_p = Point3(0,0,0); // 'anchor point' in billboard coordinates
+ Vector3 a_v1 = Vector3::UnitY(); // 'up' in billboard coordinates
+ Vector3 a_v2 = Vector3::UnitZ(); // 'normal' in billboard coordinates
+
+ Point3 b_p = billboards_[i].anchor_pt; // 'anchor point' in world coordinates
+ Vector3 b_v1 = Vector3::UnitY(); // 'up' in world coordinates
+ Vector3 b_v2 = (current_eye_point - b_p); // 'normal' in world coordinates
+ b_v2[1] = 0.0; // force 0 change in Y so the billboard does not tilt
+ b_v2.Normalize(); // convert to a unit vector
+
+ billboards_[i].transform = Matrix4::Align(a_p,a_v1,a_v2, b_p,b_v1,b_v2);
+ }
+}
+
+
+void Billboards::Draw(const Matrix4 &view_matrix, const Matrix4 &proj_matrix) {
+ // Draw billboard meshes
+ stroke3d_shaderprog_->UseProgram();
+ stroke3d_shaderprog_->SetUniform("projectionMatrix", proj_matrix);
+ for (int i=0; i<billboards_.size(); i++) {
+ Matrix4 model_matrix = billboards_[i].transform;
+ Matrix4 modelview_matrix = view_matrix * model_matrix;
+ stroke3d_shaderprog_->SetUniform("modelViewMatrix", modelview_matrix);
+ stroke3d_shaderprog_->SetUniform("strokeColor", billboards_[i].color);
+ billboards_[i].mesh.Draw();
+ }
+ stroke3d_shaderprog_->StopProgram();
+}
+
diff --git a/dev/a6-harold/billboards.h b/dev/a6-harold/billboards.h
new file mode 100644
index 0000000..7de2799
--- /dev/null
+++ b/dev/a6-harold/billboards.h
@@ -0,0 +1,78 @@
+/** CSci-4611 Assignment 6: Harold
+ */
+
+#ifndef BILLBOARDS_H_
+#define BILLBOARDS_H_
+
+#include <mingfx.h>
+using namespace mingfx;
+
+#include "ground.h"
+
+/** Billboards are strokes planted in the ground that rotate automatically to
+ face the viewer. This class holds the entire collection of billboard strokes.
+ */
+class Billboards {
+public:
+ Billboards();
+ virtual ~Billboards();
+
+ void Init(ShaderProgram *stroke3d_shaderprog);
+
+ /// Projects a 2D normalized screen point (e.g., the mouse position in normalized
+ /// device coordinates) to a 3D point on a plane defined by an "origin", which can
+ /// really be any point coincident with the plane, and the plane normal. Returns
+ /// true if the screen point projects onto the plane and stores the result in
+ /// plane_point. Returns false if the screen point does not project onto the plane.
+ bool ScreenPtHitsPlane(const Matrix4 &view_matrix, const Matrix4 &proj_matrix,
+ const Point3 &plane_origin, const Vector3 &plane_normal,
+ const Point2 &normalized_screen_pt, Point3 *plane_point);
+
+
+ /// Checks to see if a ray starting at the eye point and passing through 2D
+ /// normalized screen point projects onto any of the billboards in the scene. If
+ /// so, returns the id of the closest billboard intersected. If not, returns -1.
+ int IntersectBillboard(const Matrix4 &view_matrix, const Matrix4 &proj_matrix,
+ const Point2 &normalized_screen_pt);
+
+
+ /// Adds a new stroke as a billboard by projecting it onto a plane parallel
+ /// to the filmplane that intersects with the anchor point, which should lie
+ /// on the ground.
+ void AddBillboardStroke(const Matrix4 &view_matrix, const Matrix4 &proj_matrix,
+ const std::vector<Point2> &stroke2d, const Mesh &stroke2d_mesh,
+ const Color &stroke_color, Ground *ground_ptr);
+
+ /// Edits an existing billboard by adding an additional stroke to it.
+ void AddToBillboard(const Matrix4 &view_matrix, const Matrix4 &proj_matrix,
+ int billboard_id, const Mesh &stroke2d_mesh,
+ const Color &stroke_color);
+
+
+ /// Adjusts the transformation matrix used to draw each billboard so that the
+ /// billboard will face the camera. Needs to be called each time the camera
+ /// is moved within the scene.
+ void UpdateBillboardRotations(const Point3 &current_eye_point);
+
+
+ /// Draws all of the billboards
+ void Draw(const Matrix4 &view_matrix, const Matrix4 &proj_matrix);
+
+private:
+
+ // each billboard stores the following
+ struct Billboard {
+ Mesh mesh;
+ Color color;
+ Point3 anchor_pt;
+ AABB bounding_box;
+ Matrix4 transform;
+ };
+
+ // the array of all active billboards
+ std::vector<Billboard> billboards_;
+
+ ShaderProgram *stroke3d_shaderprog_;
+};
+
+#endif
diff --git a/dev/a6-harold/cmake/DownloadHelper.txt.in b/dev/a6-harold/cmake/DownloadHelper.txt.in
new file mode 100644
index 0000000..fb29bff
--- /dev/null
+++ b/dev/a6-harold/cmake/DownloadHelper.txt.in
@@ -0,0 +1,26 @@
+# This file is part of the MinGfx cmake build system.
+# See the main MinGfx/CMakeLists.txt file for authors, copyright, and license info.
+
+# This is a "helper" cmake project -- the only thing this project does is download
+# the external project. So, the configure, build, install, and test commands for
+# ExternalProject_Add() are intentionally set as NOPs.
+
+cmake_minimum_required (VERSION 3.9)
+
+project(@EXT_PROJECT_NAME@-download)
+
+include(ExternalProject)
+ExternalProject_Add(
+ @EXT_PROJECT_NAME@
+ SOURCE_DIR "@DOWNLOAD_DIR@/@EXT_PROJECT_NAME@/src"
+ BINARY_DIR "@DOWNLOAD_DIR@/@EXT_PROJECT_NAME@/download-helper"
+ @DOWNLOAD_OPTIONS@
+ CONFIGURE_COMMAND ""
+ BUILD_COMMAND ""
+ INSTALL_COMMAND ""
+ TEST_COMMAND ""
+ LOG_DOWNLOAD ON
+ GIT_PROGRESS 1
+)
+
+
diff --git a/dev/a6-harold/cmake/ExternalProjectDownloadBuildInstall.cmake b/dev/a6-harold/cmake/ExternalProjectDownloadBuildInstall.cmake
new file mode 100644
index 0000000..ce12d1d
--- /dev/null
+++ b/dev/a6-harold/cmake/ExternalProjectDownloadBuildInstall.cmake
@@ -0,0 +1,98 @@
+# This file is part of the MinGfx cmake build system.
+# See the main MinGfx/CMakeLists.txt file for authors, copyright, and license info.
+
+
+# Calling CMAKE_CURRENT_LIST_DIR inside a function returns the list dir of the calling script
+# but we want the list dir of this file in order to find the DownloadHelper.txt.in file, which
+# should be stored right next to this one. So, defining this variable outside the scope of the
+# functions below.
+set(DIR_OF_THIS_FILE ${CMAKE_CURRENT_LIST_DIR})
+
+
+
+# Usage:
+# ExternalProject_Download(
+# # This first argument is the name of the project to download. It is required:
+# glm
+#
+# # Additional arguments specify how to download the project using GIT, SVN, CVS, or URL.
+# # These can be any of the arguments used for the downloading step of the cmake builtin
+# # ExternalProject_Add command.
+# GIT_REPOSITORY "https://github.com/g-truc/glm.git"
+# GIT_TAG master
+# etc..
+# )
+function(ExternalProject_Download EXT_PROJECT_NAME DOWNLOAD_DIR)
+
+ include(MessageMacros)
+ h1("BEGIN EXTERNAL PROJECT DOWNLOAD (${EXT_PROJECT_NAME}).")
+
+ h2("Creating a download helper project for ${EXT_PROJECT_NAME}.")
+
+ set(DOWNLOAD_OPTIONS ${ARGN})
+ string (REGEX REPLACE "(^|[^\\\\]);" "\\1 " DOWNLOAD_OPTIONS "${DOWNLOAD_OPTIONS}")
+
+
+ file(MAKE_DIRECTORY ${DOWNLOAD_DIR}/${EXT_PROJECT_NAME})
+ configure_file(
+ ${DIR_OF_THIS_FILE}/DownloadHelper.txt.in
+ ${DOWNLOAD_DIR}/${EXT_PROJECT_NAME}/download-helper/CMakeLists.txt
+ )
+
+ h2("Generating build files for the ${EXT_PROJECT_NAME} download helper project.")
+ execute_process(COMMAND "${CMAKE_COMMAND}" -G "${CMAKE_GENERATOR}" . WORKING_DIRECTORY "${DOWNLOAD_DIR}/${EXT_PROJECT_NAME}/download-helper")
+
+ h2("Building the ${EXT_PROJECT_NAME} download helper project. (This actually performs the download and may take some time...)")
+ execute_process(COMMAND "${CMAKE_COMMAND}" --build . WORKING_DIRECTORY "${DOWNLOAD_DIR}/${EXT_PROJECT_NAME}/download-helper")
+
+ h2("Completed download of external project ${EXT_PROJECT_NAME}.")
+
+endfunction()
+
+
+# Usage:
+# ExternalProject_BuildAndInstallNow(
+# # This first argument is the name of the external project to download. It is required:
+# VRPN
+# # This second argument is the relative path from ${EXTERNAL_DIR_NAME}/projectname/ to the project's
+# # main CMakeLists.txt file:
+# src
+#
+# # Additional arguments are passed on as options to the cmake build file generator
+# -DVRPN_BUILD_DIRECTSHOW_VIDEO_SERVER=OFF
+# -DVRPN_BUILD_HID_GUI=OFF
+# etc..
+# )
+function(ExternalProject_BuildAndInstallNow EXT_PROJECT_NAME DOWNLOAD_DIR RELPATH_TO_CMAKELISTS)
+
+ include(MessageMacros)
+ h1("BEGIN EXTERNAL PROJECT BUILD AND INSTALL (${EXT_PROJECT_NAME}).")
+
+ # any extra args to the function are interpreted as arguments for the cmake config process
+ set(CMAKE_CONFIG_OPTIONS ${ARGN})
+
+ # always set the install prefix to be the same as for the main project
+ list(APPEND CMAKE_CONFIG_OPTIONS -DCMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX})
+
+ #string (REGEX REPLACE "(^|[^\\\\]);" "\\1 " CMAKE_CONFIG_OPTIONS "${CMAKE_CONFIG_OPTIONS}")
+
+
+ set(SRC_DIR "${DOWNLOAD_DIR}/${EXT_PROJECT_NAME}/${RELPATH_TO_CMAKELISTS}")
+ set(BUILD_DIR "${CMAKE_BINARY_DIR}/external/${EXT_PROJECT_NAME}")
+
+ file(MAKE_DIRECTORY ${BUILD_DIR})
+
+ h2("Generating build files for external project ${EXT_PROJECT_NAME}.")
+ message(STATUS "Using source dir: ${SRC_DIR}")
+ message(STATUS "Using build dir: ${BUILD_DIR}")
+ message(STATUS "Config options: ${CMAKE_CONFIG_OPTIONS}")
+
+ execute_process(COMMAND "${CMAKE_COMMAND}" -G "${CMAKE_GENERATOR}" ${SRC_DIR} ${CMAKE_CONFIG_OPTIONS} WORKING_DIRECTORY ${BUILD_DIR})
+
+ h2("Building external project ${EXT_PROJECT_NAME}. (This may take some time...)")
+ execute_process(COMMAND "${CMAKE_COMMAND}" --build ${BUILD_DIR} --target install)
+
+ h2("Completed external build of ${EXT_PROJECT_NAME}.")
+
+endfunction()
+
diff --git a/dev/a6-harold/cmake/MessageMacros.cmake b/dev/a6-harold/cmake/MessageMacros.cmake
new file mode 100644
index 0000000..4628e5c
--- /dev/null
+++ b/dev/a6-harold/cmake/MessageMacros.cmake
@@ -0,0 +1,17 @@
+# This file is part of the MinVR cmake build system.
+# See the main MinVR/CMakeLists.txt file for authors, copyright, and license info.
+
+
+macro(h1 TITLE)
+ string(TOUPPER ${TITLE} TITLE)
+ message(STATUS "\n\n==== ${TITLE} ====")
+endmacro()
+
+macro(h2 TITLE)
+ message(STATUS "\n* ${TITLE}")
+endmacro()
+
+macro(h3 TITLE)
+ message(STATUS "- ${TITLE}")
+endmacro()
+
diff --git a/dev/a6-harold/cmake/UseOpenGL.cmake b/dev/a6-harold/cmake/UseOpenGL.cmake
new file mode 100644
index 0000000..2ec5ffb
--- /dev/null
+++ b/dev/a6-harold/cmake/UseOpenGL.cmake
@@ -0,0 +1,52 @@
+# This file is part of the MinGfx cmake build system.
+# See the main MinGfx/CMakeLists.txt file for authors, copyright, and license info.
+
+# Either finds a pre-installed version or complains.
+
+# Usage: In your CMakeLists.txt, somewhere after you define the target that depends
+# on the OpenGL library (typical with something like add_executable(${PROJECT_NAME} ...)
+# or add_library(${PROJECT_NAME} ...)), add the following two lines:
+
+# include(UseOpenGL)
+# UseOpenGL(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/external)
+
+# The second argument can be either PUBLIC, PRIVATE, or INTERFACE, following the keyword
+# usage described here:
+# https://cmake.org/cmake/help/latest/command/target_include_directories.html
+
+# The third argument is the directory to use for downloading the external project if
+# autobuild is used.
+
+
+
+macro(UseOpenGL YOUR_TARGET INTERFACE_PUBLIC_OR_PRIVATE DOWNLOAD_DIR)
+
+ message(STATUS "Searching for OpenGL...")
+
+ # Check to see if the library is already installed on the system
+ # CMake ships with FindOpenGL.cmake and in CMake 3.9+ it defines
+ # the imported targets OpenGL::GL and OpenGL::GLU. Using these is
+ # now the preferred way to link with OpenGL and all of its dependencies.
+ # See https://cmake.org/cmake/help/v3.9/module/FindOpenGL.html
+ find_package(OpenGL)
+
+ if (NOT ${OPENGL_FOUND})
+ message(FATAL_ERROR "OpenGL was not found on the system. MinGfx can auto-download and build many dependencies for you, but not OpenGL. It should come pre-installed on your system.")
+ endif()
+
+ message(STATUS "Ok: OpenGL Found.")
+ message(STATUS "OpenGL headers: ${OPENGL_INCLUDE_DIR}")
+ message(STATUS "OpenGL libs: ${OPENGL_LIBRARIES}")
+
+
+ message(STATUS "Linking target ${YOUR_TARGET} with ${INTERFACE_PUBLIC_OR_PRIVATE} dependency OpenGL::GL.")
+ target_link_libraries(${YOUR_TARGET} ${INTERFACE_PUBLIC_OR_PRIVATE} OpenGL::GL)
+
+ if (${OPENGL_GLU_FOUND})
+ message(STATUS "Linking target ${YOUR_TARGET} with ${INTERFACE_PUBLIC_OR_PRIVATE} dependency OpenGL::GLU.")
+ target_link_libraries(${YOUR_TARGET} ${INTERFACE_PUBLIC_OR_PRIVATE} OpenGL::GLU)
+ endif()
+
+ target_compile_definitions(${YOUR_TARGET} ${INTERFACE_PUBLIC_OR_PRIVATE} -DUSE_OPENGL)
+
+endmacro()
diff --git a/dev/a6-harold/config.h.in b/dev/a6-harold/config.h.in
new file mode 100644
index 0000000..9798f4c
--- /dev/null
+++ b/dev/a6-harold/config.h.in
@@ -0,0 +1,15 @@
+/** CSci-4611 Assignment 6: Harold
+ */
+
+
+// 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 DATA_DIR_BUILD "@DATA_DIR_BUILD@"
+#define DATA_DIR_INSTALL "@DATA_DIR_INSTALL@"
+
+#define SHADERS_DIR_BUILD "@SHADERS_DIR_BUILD@"
+#define SHADERS_DIR_INSTALL "@SHADERS_DIR_INSTALL@"
diff --git a/dev/a6-harold/data/toonDiffuse.png b/dev/a6-harold/data/toonDiffuse.png
new file mode 100644
index 0000000..d2e40d9
--- /dev/null
+++ b/dev/a6-harold/data/toonDiffuse.png
Binary files differ
diff --git a/dev/a6-harold/data/toonSpecular.png b/dev/a6-harold/data/toonSpecular.png
new file mode 100644
index 0000000..c13a32f
--- /dev/null
+++ b/dev/a6-harold/data/toonSpecular.png
Binary files differ
diff --git a/dev/a6-harold/edge_mesh.cc b/dev/a6-harold/edge_mesh.cc
new file mode 100644
index 0000000..1abe2e0
--- /dev/null
+++ b/dev/a6-harold/edge_mesh.cc
@@ -0,0 +1,235 @@
+/** CSci-4611 Assignment 5: Art Render
+ */
+
+#include "edge_mesh.h"
+
+EdgeMesh::EdgeMesh() : gpuDirty_(true) {
+
+}
+
+EdgeMesh::~EdgeMesh() {
+}
+
+
+
+void EdgeMesh::CreateFromMesh(const Mesh &mesh) {
+ verts_.clear();
+ norms_.clear();
+ indices_.clear();
+ leftNorms_.clear();
+ rightNorms_.clear();
+
+ // temp data holders for use while constructing the mesh in an indexed primitives mode
+ std::vector<Point3> vertices; // vertex positions
+ std::vector<Vector3> normals; // vertex normals
+ std::vector<Vector3> leftNormals, rightNormals; // normals of adj. faces
+ std::vector< std::vector<unsigned int> > triangles; // fin triangles
+
+ for (int t = 0; t < mesh.num_triangles(); t++) {
+ std::vector<unsigned int> tri = mesh.read_triangle_indices_data(t);
+ Point3 a = mesh.read_vertex_data(tri[0]);
+ Point3 b = mesh.read_vertex_data(tri[1]);
+ Point3 c = mesh.read_vertex_data(tri[2]);
+ Vector3 e1 = b-a;
+ e1.Normalize();
+ Vector3 e2 = c-a;
+ e2.Normalize();
+ Vector3 n = e1.Cross(e2);
+
+ //Vector3 n = ((b-a).cross(c-a)).to_unit();
+ //std::cout << n << std::endl;
+ addEdge(&vertices, &normals, &leftNormals, &rightNormals, &triangles, mesh, tri[0], tri[1], n);
+ addEdge(&vertices, &normals, &leftNormals, &rightNormals, &triangles, mesh, tri[1], tri[2], n);
+ addEdge(&vertices, &normals, &leftNormals, &rightNormals, &triangles, mesh, tri[2], tri[0], n);
+ }
+
+ // now transfer data to the member variable data holders
+ 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]);
+ norms_.push_back(normals[i][0]);
+ norms_.push_back(normals[i][1]);
+ norms_.push_back(normals[i][2]);
+ leftNorms_.push_back(leftNormals[i][0]);
+ leftNorms_.push_back(leftNormals[i][1]);
+ leftNorms_.push_back(leftNormals[i][2]);
+ rightNorms_.push_back(rightNormals[i][0]);
+ rightNorms_.push_back(rightNormals[i][1]);
+ rightNorms_.push_back(rightNormals[i][2]);
+ }
+
+ for (int i=0; i<triangles.size(); i++) {
+ indices_.push_back(triangles[i][0]);
+ indices_.push_back(triangles[i][1]);
+ indices_.push_back(triangles[i][2]);
+ }
+ gpuDirty_ = true;
+}
+
+
+void EdgeMesh::addEdge(std::vector<Point3> *vertices,
+ std::vector<Vector3> *normals,
+ std::vector<Vector3> *leftNormals,
+ std::vector<Vector3> *rightNormals,
+ std::vector< std::vector<unsigned int> > *triangles,
+ const Mesh &mesh, int v0, int v1, Vector3 n)
+{
+ // edgeMap contains the index of the first of four consecutive vertices
+ // that form the quad fin
+
+ EdgeMap::iterator it = edgeMap.find(std::make_pair(v1, v0));
+ if (it != edgeMap.end()) { // found
+ int v = it->second;
+ (*rightNormals)[v+0]
+ = (*rightNormals)[v+1]
+ = (*rightNormals)[v+2]
+ = (*rightNormals)[v+3]
+ = n;
+ }
+ else {
+ int v = (int)vertices->size();
+ edgeMap[std::make_pair(v0, v1)] = v;
+
+ vertices->push_back(mesh.read_vertex_data(v0));
+ vertices->push_back(mesh.read_vertex_data(v0));
+ vertices->push_back(mesh.read_vertex_data(v1));
+ vertices->push_back(mesh.read_vertex_data(v1));
+
+ normals->push_back(Vector3());
+ normals->push_back(mesh.read_normal_data(v0));
+ normals->push_back(Vector3());
+ normals->push_back(mesh.read_normal_data(v1));
+
+ leftNormals->push_back(n);
+ leftNormals->push_back(n);
+ leftNormals->push_back(n);
+ leftNormals->push_back(n);
+
+ rightNormals->push_back(-n);
+ rightNormals->push_back(-n);
+ rightNormals->push_back(-n);
+ rightNormals->push_back(-n);
+
+ std::vector<unsigned int> tri1;
+ tri1.push_back(v+0);
+ tri1.push_back(v+2);
+ tri1.push_back(v+3);
+ triangles->push_back(tri1);
+
+ std::vector<unsigned int> tri2;
+ tri2.push_back(v+0);
+ tri2.push_back(v+3);
+ tri2.push_back(v+1);
+ triangles->push_back(tri2);
+ }
+}
+
+
+
+void EdgeMesh::UpdateGPUMemory() {
+ if (gpuDirty_) {
+ // sanity check -- for each attribute that is added make sure the number
+ // of triangles is equal to the number of tris in the verts array.
+ if (norms_.size() / 3 != num_vertices()) {
+ std::cerr << "EdgeMesh::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 (leftNorms_.size() / 3 != num_vertices()) {
+ std::cerr << "EdgeMesh::UpdateGPUMemory() -- warning: the number of per vertex left-normals in the mesh is not equal to the number vertices in the mesh. (LN = " << leftNorms_.size() / 3 << ", V = " << num_vertices() << ")" << std::endl;
+ }
+ if (rightNorms_.size() / 3 != num_vertices()) {
+ std::cerr << "EdgeMesh::UpdateGPUMemory() -- warning: the number of per vertex right-normals in the mesh is not equal to the number vertices in the mesh. (RN = " << rightNorms_.size() / 3 << ", 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 leftNormsMemSize = leftNorms_.size() * sizeof(float);
+ GLsizeiptr leftNormsMemOffset = totalMemSize;
+ totalMemSize += leftNormsMemSize;
+
+ GLsizeiptr rightNormsMemSize = rightNorms_.size() * sizeof(float);
+ GLsizeiptr rightNormsMemOffset = totalMemSize;
+ totalMemSize += rightNormsMemSize;
+
+ glGenBuffers(1, &vertexBuffer_);
+ glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer_);
+ glBufferData(GL_ARRAY_BUFFER, totalMemSize, NULL, GL_STATIC_DRAW);
+
+ glBufferSubData(GL_ARRAY_BUFFER, vertsMemOffset, vertsMemSize, &verts_[0]);
+ glBufferSubData(GL_ARRAY_BUFFER, normsMemOffset, normsMemSize, &norms_[0]);
+ glBufferSubData(GL_ARRAY_BUFFER, leftNormsMemOffset, leftNormsMemSize, &leftNorms_[0]);
+ glBufferSubData(GL_ARRAY_BUFFER, rightNormsMemOffset, rightNormsMemSize, &rightNorms_[0]);
+
+ glGenVertexArrays(1, &vertexArray_);
+ glBindVertexArray(vertexArray_);
+ glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer_);
+
+ // 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 (required)
+ attribID = 1;
+ nComponents = 3;
+ glEnableVertexAttribArray(attribID);
+ glVertexAttribPointer(attribID, nComponents, GL_FLOAT, GL_TRUE, nComponents*sizeof(GLfloat), (char*)0 + normsMemOffset);
+
+ // attribute 2 = left normals (required)
+ attribID = 2;
+ nComponents = 3;
+ glEnableVertexAttribArray(attribID);
+ glVertexAttribPointer(attribID, nComponents, GL_FLOAT, GL_TRUE, nComponents*sizeof(GLfloat), (char*)0 + leftNormsMemOffset);
+
+ // attribute 3 = right normals (required)
+ attribID = 3;
+ nComponents = 3;
+ glEnableVertexAttribArray(attribID);
+ glVertexAttribPointer(attribID, nComponents, GL_FLOAT, GL_TRUE, nComponents*sizeof(GLfloat), (char*)0 + rightNormsMemOffset);
+
+ glBindVertexArray(0);
+
+
+ glGenBuffers(1, &elementBuffer_);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementBuffer_);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices_.size() * sizeof(unsigned int), &indices_[0], GL_STATIC_DRAW);
+
+
+ gpuDirty_ = false;
+ }
+}
+
+
+void EdgeMesh::Draw() {
+ if (gpuDirty_) {
+ UpdateGPUMemory();
+ }
+
+ // set defaults to pass to shaders any for optional attribs
+ glVertexAttrib3f(1, 0.0, 0.0, 1.0); // normal = +Z
+ glVertexAttrib3f(2, 0.0, 0.0, 1.0); // left-normal = +Z
+ glVertexAttrib3f(3, 0.0, 0.0, 1.0); // right-normal = +Z
+
+ glBindVertexArray(vertexArray_);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementBuffer_);
+ glDrawElements(GL_TRIANGLES, (GLsizei)indices_.size(), GL_UNSIGNED_INT, (void*)0);
+ glBindVertexArray(0);
+}
+
+
+int EdgeMesh::num_vertices() const {
+ return (int)verts_.size()/3;
+}
+
+int EdgeMesh::num_triangles() const {
+ return (int)indices_.size()/3;
+}
diff --git a/dev/a6-harold/edge_mesh.h b/dev/a6-harold/edge_mesh.h
new file mode 100644
index 0000000..5120d0b
--- /dev/null
+++ b/dev/a6-harold/edge_mesh.h
@@ -0,0 +1,82 @@
+/** CSci-4611 Assignment 5: Art Render
+ */
+
+#ifndef EDGE_MESH_H
+#define EDGE_MESH_H
+
+#include <mingfx.h>
+using namespace mingfx;
+
+#include <vector>
+
+
+/** This special kind of mesh stores two triangles that form a quadralateral
+ along each edge of an existing mesh. The quad initially has a width=0, but
+ when rendered, two of the vertices are extended along the surfaces normal
+ direction, which creates a "fin" that can be drawn as a thick border. This
+ can be used to create a silhouette edge renderer if the shader only extends
+ the edges that lie along a silhouette of the mesh.
+ */
+class EdgeMesh {
+public:
+ EdgeMesh();
+ virtual ~EdgeMesh();
+
+ /// Creates two triangles along each edge of the mesh passed in.
+ void CreateFromMesh(const Mesh &mesh);
+
+ /// Saves the mesh data to the GPU - must be called with InitOpenGL or Draw.
+ void UpdateGPUMemory();
+
+ /// Num vertices in the edge mesh
+ int num_vertices() const;
+
+ /// Num triangles in the edge mesh
+ int num_triangles() const;
+
+ /// Access to vertex position by vertex number
+ Point3 vertex(int vertexID) const;
+
+ /// Access to vertex normal by vertex number
+ Vector3 normal(int vertexID) const;
+
+ /// Access to vertex color by vertex number
+ Color color(int vertexID) const;
+
+ /// Access to vertex texture coords by vertex number
+ Point2 tex_coords(int textureUnit, int vertexID) const;
+
+
+ /// Draws the mesh assuming a shader has already been bound.
+ void Draw();
+
+private:
+
+ // Some routines and variables are needed internally to construct the edge
+ // mesh from a regular mesh.
+
+ typedef std::map<std::pair<int,int>,int> EdgeMap;
+ EdgeMap edgeMap;
+
+ void addEdge(std::vector<Point3> *vertices,
+ std::vector<Vector3> *normals,
+ std::vector<Vector3> *leftNormals,
+ std::vector<Vector3> *rightNormals,
+ std::vector< std::vector<unsigned int> > *triangles,
+ const Mesh &mesh, int v0, int v1, Vector3 n);
+
+ std::vector<float> verts_; // vertex positions
+ std::vector<float> norms_; // normals
+ std::vector<unsigned int> indices_; // indices
+ std::vector<float> leftNorms_; // normals of adjacent triangles
+ std::vector<float> rightNorms_;
+
+ GLuint vertexBuffer_;
+ GLuint vertexArray_;
+ GLuint elementBuffer_;
+
+ bool gpuDirty_;
+};
+
+#endif
+
diff --git a/dev/a6-harold/ground.cc b/dev/a6-harold/ground.cc
new file mode 100644
index 0000000..51d4d92
--- /dev/null
+++ b/dev/a6-harold/ground.cc
@@ -0,0 +1,283 @@
+/** CSci-4611 Assignment 6: Harold
+ */
+
+#include "ground.h"
+
+
+Ground::Ground() : diffuse_ramp_(GL_CLAMP_TO_EDGE),
+ specular_ramp_(GL_CLAMP_TO_EDGE), light_pos_(30,30,30)
+{
+
+}
+
+Ground::~Ground() {
+
+}
+
+Mesh* Ground::mesh_ptr() { return &ground_mesh_; }
+
+
+void Ground::Init(const std::vector<std::string> &search_path) {
+ // init ground geometry, a simple grid is used. if it is running too slow,
+ // you can turn down the resolution by decreasing nx and ny, but this will
+ // make the hills look more jaggy.
+ const int nx = 150;
+ const int ny = 150;
+ const float size = 100.0;
+ std::vector<Point3> verts;
+ std::vector<Vector3> norms;
+ for (int j = 0; j <= ny; j++) {
+ for (int i = 0; i <= nx; i++) {
+ float x = size*(float)j/nx - size/2.0f;
+ float y = size*(float)i/ny - size/2.0f;
+ verts.push_back(Point3(x, 0, y));
+ norms.push_back(Vector3(0,1,0));
+ }
+ }
+ std::vector<unsigned int> indices;
+ for (int j = 0; j < ny; j++) {
+ for (int i = 0; i < nx; i++) {
+ // L\ triangle
+ indices.push_back((i+0)+(j+0)*(nx+1));
+ indices.push_back((i+1)+(j+0)*(nx+1));
+ indices.push_back((i+0)+(j+1)*(nx+1));
+ // \7 triangle
+ indices.push_back((i+1)+(j+0)*(nx+1));
+ indices.push_back((i+1)+(j+1)*(nx+1));
+ indices.push_back((i+0)+(j+1)*(nx+1));
+ }
+ }
+ ground_mesh_.SetIndices(indices);
+ ground_mesh_.SetVertices(verts);
+ ground_mesh_.SetNormals(norms);
+ ground_mesh_.UpdateGPUMemory();
+ ground_edge_mesh_.CreateFromMesh(ground_mesh_);
+
+
+ // load textures and shaders
+ diffuse_ramp_.InitFromFile(Platform::FindFile("toonDiffuse.png", search_path));
+ specular_ramp_.InitFromFile(Platform::FindFile("toonSpecular.png", search_path));
+
+ artsy_shaderprog_.AddVertexShaderFromFile(Platform::FindFile("artsy.vert", search_path));
+ artsy_shaderprog_.AddFragmentShaderFromFile(Platform::FindFile("artsy.frag", search_path));
+ artsy_shaderprog_.LinkProgram();
+
+ outline_shaderprog_.AddVertexShaderFromFile(Platform::FindFile("outline.vert", search_path));
+ outline_shaderprog_.AddFragmentShaderFromFile(Platform::FindFile("outline.frag", search_path));
+ outline_shaderprog_.LinkProgram();
+}
+
+
+
+
+// Projects a 2D normalized screen point (e.g., the mouse position in normalized
+// device coordinates) to a 3D point on the ground. Returns true and sets ground_point
+// to be equal to the result if the conversion is successful. Returns false if
+// the screen point does not project onto the ground.
+bool Ground::ScreenPtHitsGround(const Matrix4 &view_matrix, const Matrix4 &proj_matrix,
+ const Point2 &normalized_screen_pt, Point3 *ground_point)
+{
+ Matrix4 camera_matrix = view_matrix.Inverse();
+ Point3 eye = camera_matrix.ColumnToPoint3(3);
+
+ Point3 pt3d = GfxMath::ScreenToNearPlane(view_matrix, proj_matrix, normalized_screen_pt);
+ Ray ray(eye, (pt3d - eye).ToUnit());
+ float i_time;
+ int i_tri;
+ return ray.FastIntersectMesh(&ground_mesh_, &i_time, ground_point, &i_tri);
+}
+
+
+
+
+/** This implements the "h" term used in the equations described in section 4.5 of the
+ paper. Three arguments are needed:
+ 1. projection_plane_normal: We need to know where the projection plane is in 3-space
+ Since a plane can be defined by a point within the plane and a normal, we use
+ this normal together with the 3rd argument to the function to define the projection
+ plane described in the paper.
+ 2. silhouette_curve: As described in the paper, the silhouette curve is a 3D version
+ of the curve the user draws with the mouse. It is formed by projecting the
+ original 2D screen-space curve onto the 3D projection plane.
+ 3. closest_pt_in_plane: As described in the paper, this is the closest point within
+ the projection plane to the vertex of the mesh that we want to modify. In other
+ words, it is the perpendicular projection of the vertex we want to modify onto
+ the projection plane.
+ */
+float hfunc(const Vector3 projection_plane_normal, const std::vector<Point3> &silhouette_curve, const Point3 &closest_pt_in_plane) {
+ // define the y axis for a "plane space" coordinate system as a world space vector
+ Vector3 plane_y = Vector3(0,1,0);
+ // define the x axis for a "plane space" coordinate system as a world space vector
+ Vector3 plane_x = plane_y.Cross(projection_plane_normal).ToUnit();
+ // define the origin for a "plane space" coordinate system as the first point in the curve
+ Point3 origin = silhouette_curve[0];
+
+ // loop over line segments in the curve, find the one that lies over the point by
+ // comparing the "plane space" x value for the start and end of the line segment
+ // to the "plane space" x value for the closest point to the vertex that lies
+ // in the projection plane.
+ float x_target = (closest_pt_in_plane - origin).Dot(plane_x);
+ for (int i=1; i<silhouette_curve.size(); i++) {
+ float x_start = (silhouette_curve[(size_t)i-1] - origin).Dot(plane_x);
+ float x_end = (silhouette_curve[i] - origin).Dot(plane_x);
+ if ((x_start <= x_target) && (x_target <= x_end)) {
+ float alpha = (x_target - x_start) / (x_end - x_start);
+ float y_curve = silhouette_curve[(size_t)i-1][1] + alpha*(silhouette_curve[i][1] - silhouette_curve[(size_t)i-1][1]);
+ return y_curve - closest_pt_in_plane[1];
+ }
+ else if ((x_end <= x_target) && (x_target <= x_start)) {
+ float alpha = (x_target - x_end) / (x_start - x_end);
+ float y_curve = silhouette_curve[i][1] + alpha*(silhouette_curve[(size_t)i-1][1] - silhouette_curve[i][1]);
+ return y_curve - closest_pt_in_plane[1];
+ }
+ }
+
+ // here return 0 because the point does not lie under the curve.
+ return 0.0;
+}
+
+
+
+
+/// Modifies the vertices of the ground mesh to create a hill or valley based
+/// on the input stroke. The 2D path of the stroke on the screen is passed
+/// in, this is the centerline of the stroke mesh that is actually drawn on
+/// the screen while the user is drawing.
+void Ground::ReshapeGround(const Matrix4 &view_matrix, const Matrix4 &proj_matrix,
+ const std::vector<Point2> &stroke2d)
+{
+ // TODO: Deform the 3D ground mesh according to the algorithm described in the
+ // Cohen et al. Harold paper.
+
+ // You might need the eye point and the look vector, these can be determined
+ // from the view matrix as follows:
+ Matrix4 camera_matrix = view_matrix.Inverse();
+ Point3 eye = camera_matrix.ColumnToPoint3(3);
+ Vector3 look = -camera_matrix.ColumnToVector3(2);
+
+
+
+ // There are 3 major steps to the algorithm, outlined here:
+
+ // 1. Define a plane to project the stroke onto. The first and last points
+ // of the stroke are guaranteed to project onto the ground plane. The plane
+ // should pass through these two points on the ground. The plane should also
+ // have a normal vector that points toward the camera and is parallel to the
+ // ground plane.
+
+
+
+
+
+ // 2. Project the 2D stroke into 3D so that it lies on the "projection plane"
+ // defined in step 1.
+
+
+
+
+
+ // 3. Loop through all of the vertices of the ground mesh, and adjust the
+ // height of each based on the equations in section 4.5 of the paper, also
+ // repeated in the assignment handout. The equations rely upon a function
+ // h(), and we have implemented that for you as hfunc() defined above in
+ // this file. The basic structure of the loop you will need is here:
+ std::vector<Point3> new_verts;
+ for (int i=0; i<ground_mesh_.num_vertices(); i++) {
+ Point3 P = ground_mesh_.read_vertex_data(i); // original vertex
+
+ // adjust P according to equations...
+
+
+
+
+
+ new_verts.push_back(P);
+ }
+ ground_mesh_.SetVertices(new_verts);
+ ground_mesh_.CalcPerVertexNormals();
+ ground_mesh_.UpdateGPUMemory();
+ ground_edge_mesh_.CreateFromMesh(ground_mesh_);
+}
+
+
+
+
+/// Draws the ground mesh with toon shading
+void Ground::Draw(const Matrix4 &view_matrix, const Matrix4 &proj_matrix, const Color &ground_color) {
+ // Lighting parameters
+ Color Ia(1.0f, 1.0f, 1.0f, 1.0f);
+ Color Id(1.0f, 1.0f, 1.0f, 1.0f);
+ Color Is(1.0f, 1.0f, 1.0f, 1.0f);
+
+ // Material parameters
+ Color ka = ground_color;
+ Color kd(0.4f, 0.4f, 0.4f, 1.0f);
+ Color ks(0.6f, 0.6f, 0.6f, 1.0f);
+ float s = 50.0f;
+
+ // Precompute matrices needed in the shader
+ Matrix4 model_matrix; // identity
+ Matrix4 modelview_matrix = view_matrix * model_matrix;
+ Matrix4 normal_matrix = modelview_matrix.Inverse().Transpose();
+ Point3 light_in_eye_space = view_matrix * light_pos_;
+
+ // Make sure the default option to only draw front facing triangles is set
+ glEnable(GL_CULL_FACE);
+
+
+ // Draw the ground using the artsy shader
+ artsy_shaderprog_.UseProgram();
+ artsy_shaderprog_.SetUniform("modelViewMatrix", modelview_matrix);
+ artsy_shaderprog_.SetUniform("normalMatrix", normal_matrix);
+ artsy_shaderprog_.SetUniform("projectionMatrix", proj_matrix);
+ artsy_shaderprog_.SetUniform("ka", ka);
+ artsy_shaderprog_.SetUniform("kd", kd);
+ artsy_shaderprog_.SetUniform("ks", ks);
+ artsy_shaderprog_.SetUniform("s", s);
+ artsy_shaderprog_.SetUniform("lightPosition", light_in_eye_space);
+ artsy_shaderprog_.SetUniform("Ia", Ia);
+ artsy_shaderprog_.SetUniform("Id", Id);
+ artsy_shaderprog_.SetUniform("Is", Is);
+ artsy_shaderprog_.BindTexture("diffuseRamp", diffuse_ramp_);
+ artsy_shaderprog_.BindTexture("specularRamp", specular_ramp_);
+ ground_mesh_.Draw();
+ artsy_shaderprog_.StopProgram();
+
+ // And, draw silhouette edges for the ground using the outline shader
+ glDisable(GL_CULL_FACE);
+ glEnable(GL_POLYGON_OFFSET_FILL);
+ glPolygonOffset(1,1);
+ static const float thickness = 0.2f;
+ outline_shaderprog_.UseProgram();
+ outline_shaderprog_.SetUniform("modelViewMatrix", modelview_matrix);
+ outline_shaderprog_.SetUniform("normalMatrix", normal_matrix);
+ outline_shaderprog_.SetUniform("projectionMatrix", proj_matrix);
+ outline_shaderprog_.SetUniform("thickness", thickness);
+ ground_edge_mesh_.Draw();
+ outline_shaderprog_.StopProgram();
+
+
+
+ // This can be useful for debugging, but it is extremely slow to draw.
+ // Before uncommenting this, it's recommended to turn down the resolution
+ // of the ground mesh by adjusting the nx and ny constants inside Init().
+ /**
+ // draw lines around each triangle
+ for (int t=0; t<ground_mesh_.num_triangles(); t++) {
+ std::vector<unsigned int> indices = ground_mesh_.triangle_vertices(t);
+ std::vector<Point3> loop;
+ loop.push_back(ground_mesh_.vertex(indices[0]));
+ loop.push_back(ground_mesh_.vertex(indices[1]));
+ loop.push_back(ground_mesh_.vertex(indices[2]));
+ qs_.DrawLines(model_matrix, view_matrix, proj_matrix, Color(0.7,0.7,0.7), loop, QuickShapes::LinesType::LINE_LOOP, 0.01);
+ }
+
+ // draw normals
+ for (int i=0; i<ground_mesh_.num_vertices(); i++) {
+ Point3 p1 = ground_mesh_.vertex(i);
+ Point3 p2 = p1 + 0.5*ground_mesh_.normal(i);
+ qs_.DrawLineSegment(model_matrix, view_matrix, proj_matrix, Color(0.7,0.7,0.7), p1, p2, 0.01);
+ }
+ **/
+}
+
diff --git a/dev/a6-harold/ground.h b/dev/a6-harold/ground.h
new file mode 100644
index 0000000..0cc18dd
--- /dev/null
+++ b/dev/a6-harold/ground.h
@@ -0,0 +1,63 @@
+/** CSci-4611 Assignment 6: Harold
+ */
+
+#ifndef GROUND_H_
+#define GROUND_H_
+
+#include <mingfx.h>
+using namespace mingfx;
+
+#include "edge_mesh.h"
+
+/** The ground is represented with a triangle mesh. 2D "screen space" strokes
+ are used to modify the vertices based on user input so that the user can create
+ a 3D landscape of hills and valleys.
+ */
+class Ground {
+public:
+ Ground();
+ virtual ~Ground();
+
+ /// Call from InitOpenGL() to initialize shaders, etc.
+ void Init(const std::vector<std::string> &search_path);
+
+ /// Projects a 2D normalized screen point (e.g., the mouse position in normalized
+ /// device coordinates) to a 3D point on the ground. Returns true and sets ground_point
+ /// to be equal to the result if the conversion is successful. Returns false if
+ /// the screen point does not project onto the ground.
+ bool ScreenPtHitsGround(const Matrix4 &view_matrix, const Matrix4 &proj_matrix,
+ const Point2 &normalized_screen_pt, Point3 *ground_point);
+
+ /// Modifies the vertices of the ground mesh to create a hill or valley based
+ /// on the input stroke. The 2D path of the stroke on the screen is passed
+ /// in, this is the centerline of the stroke mesh that is actually drawn on
+ /// the screen while the user is drawing.
+ void ReshapeGround(const Matrix4 &view_matrix, const Matrix4 &proj_matrix,
+ const std::vector<Point2> &stroke2d);
+
+ /// Draws the ground mesh with toon shading
+ void Draw(const Matrix4 &view_matrix, const Matrix4 &proj_matrix, const Color &ground_color);
+
+
+ Mesh* mesh_ptr();
+
+private:
+
+ // This is the actual triangle mesh for the ground
+ Mesh ground_mesh_;
+ // We also maintain a corresponding "edge mesh" in order to do the
+ // silhouette outlines like in assignment 5
+ EdgeMesh ground_edge_mesh_;
+
+ // The ground rendering is based on the artsy shader from assignment 5
+ ShaderProgram artsy_shaderprog_;
+ ShaderProgram outline_shaderprog_;
+ Texture2D diffuse_ramp_;
+ Texture2D specular_ramp_;
+ Point3 light_pos_;
+
+ // for debugging only
+ QuickShapes qs_;
+};
+
+#endif
diff --git a/dev/a6-harold/harold_app.cc b/dev/a6-harold/harold_app.cc
new file mode 100644
index 0000000..abf5c4d
--- /dev/null
+++ b/dev/a6-harold/harold_app.cc
@@ -0,0 +1,278 @@
+/** CSci-4611 Assignment 6: Harold
+ */
+
+#include "harold_app.h"
+#include "config.h"
+
+#include <iostream>
+#include <sstream>
+
+
+HaroldApp::HaroldApp() : GraphicsApp(1024,768, "Harold"),
+ drawing_state_(DRAWING_NONE),
+ sky_color_(1,1,1), ground_color_(0.25, 0, 0.25), crayon_color_(0.5,0,0.5)
+{
+ // Define a search path for finding data files (images and shaders)
+ search_path_.push_back(".");
+ search_path_.push_back("./data");
+ search_path_.push_back("./shaders");
+ search_path_.push_back(DATA_DIR_INSTALL);
+ search_path_.push_back(DATA_DIR_BUILD);
+ search_path_.push_back(SHADERS_DIR_INSTALL);
+ search_path_.push_back(SHADERS_DIR_BUILD);
+}
+
+
+HaroldApp::~HaroldApp() {
+}
+
+
+void HaroldApp::InitNanoGUI() {
+ // Setup the GUI window
+ nanogui::Window *window = new nanogui::Window(screen(), "Harold's Crayons");
+ window->setPosition(Eigen::Vector2i(10, 10));
+ window->setSize(Eigen::Vector2i(200,100));
+ window->setLayout(new nanogui::GroupLayout());
+
+ new nanogui::Label(window, "Crayon Color", "sans-bold");
+ auto cp1 = new nanogui::ColorPicker(window,
+ nanogui::Color((int)(255.0*crayon_color_[0]),
+ (int)(255.0*crayon_color_[1]),
+ (int)(255.0*crayon_color_[2]), 255)
+ );
+ cp1->setFixedSize({100, 20});
+ cp1->setFinalCallback([this](const nanogui::Color &c) {
+ crayon_color_ = Color(c.r(), c.g(), c.b(), c.w());
+ });
+
+ new nanogui::Label(window, "Sky Color", "sans-bold");
+ auto cp2 = new nanogui::ColorPicker(window,
+ nanogui::Color((int)(255.0*sky_color_[0]),
+ (int)(255.0*sky_color_[1]),
+ (int)(255.0*sky_color_[2]), 255)
+ );
+ cp2->setFixedSize({100, 20});
+ cp2->setFinalCallback([this](const nanogui::Color &c) {
+ sky_color_ = Color(c.r(), c.g(), c.b(), c.w());
+ });
+
+ new nanogui::Label(window, "Ground Color", "sans-bold");
+ auto cp3 = new nanogui::ColorPicker(window,
+ nanogui::Color((int)(255.0*ground_color_[0]),
+ (int)(255.0*ground_color_[1]),
+ (int)(255.0*ground_color_[2]), 255)
+ );
+ cp3->setFixedSize({100, 20});
+ cp3->setFinalCallback([this](const nanogui::Color &c) {
+ ground_color_ = Color(c.r(), c.g(), c.b(), c.w());
+ });
+
+ screen()->performLayout();
+}
+
+
+void HaroldApp::InitOpenGL() {
+ // Set up the camera in a good position to see the entire field
+ cam_.set_view_matrix(Matrix4::LookAt(Point3(0,2,10), Point3(0,2,0), Vector3(0,1,0)));
+ proj_matrix_ = Matrix4::Perspective(60, aspect_ratio(), 0.1f, 1600.0f);
+ glClearColor(0.7f, 0.7f, 0.7f, 1.0f);
+
+ stroke2d_shaderprog_.AddVertexShaderFromFile(Platform::FindFile("stroke2d.vert", search_path_));
+ stroke2d_shaderprog_.AddFragmentShaderFromFile(Platform::FindFile("stroke2d.frag", search_path_));
+ stroke2d_shaderprog_.LinkProgram();
+
+ stroke3d_shaderprog_.AddVertexShaderFromFile(Platform::FindFile("stroke3d.vert", search_path_));
+ stroke3d_shaderprog_.AddFragmentShaderFromFile(Platform::FindFile("stroke3d.frag", search_path_));
+ stroke3d_shaderprog_.LinkProgram();
+
+ ground_.Init(search_path_);
+ sky_.Init(&stroke3d_shaderprog_);
+ billboards_.Init(&stroke3d_shaderprog_);
+}
+
+
+
+void HaroldApp::AddToStroke(const Point2 &normalized_screen_pt) {
+ // the stroke2d_ array stores the raw 2D screen coordinates of the
+ // centerline of the stroke
+ stroke2d_.push_back(normalized_screen_pt);
+
+ // the mesh is a triangle strip that follows the centerline
+ // we need at least 2 samples before we can create triangles
+ if (stroke2d_.size() >= 2) {
+ const float half_stroke_width = 0.01f;
+ std::vector<Point3> verts;
+ std::vector<unsigned int> indices;
+ Point3 last_pt = Point3(stroke2d_[0][0], stroke2d_[0][1], 0);
+ Point3 pt = Point3(stroke2d_[1][0], stroke2d_[1][1], 0);
+ Vector3 tangent = (pt - last_pt).ToUnit();
+ Vector3 cotangent = tangent.Cross(Vector3::UnitZ());
+ verts.push_back(last_pt - half_stroke_width*cotangent);
+ verts.push_back(last_pt + half_stroke_width*cotangent);
+ for (int i=1; i<stroke2d_.size(); i++) {
+ pt = Point3(stroke2d_[i][0], stroke2d_[i][1], 0);
+ tangent = (pt - last_pt).ToUnit();
+ cotangent = tangent.Cross(Vector3::UnitZ());
+
+ verts.push_back(pt - half_stroke_width*cotangent);
+ verts.push_back(pt + half_stroke_width*cotangent);
+
+ indices.push_back((int)verts.size()-4);
+ indices.push_back((int)verts.size()-3);
+ indices.push_back((int)verts.size()-2);
+
+ indices.push_back((int)verts.size()-3);
+ indices.push_back((int)verts.size()-1);
+ indices.push_back((int)verts.size()-2);
+
+ last_pt = pt;
+ }
+ stroke2d_mesh_.SetVertices(verts);
+ stroke2d_mesh_.SetIndices(indices);
+ stroke2d_mesh_.UpdateGPUMemory();
+ }
+}
+
+
+
+// This function is called at the start of each new crayon stroke
+void HaroldApp::OnLeftMouseDown(const Point2 &mouse_in_pixels) {
+ // Add to the stroke_mesh_, which is a 2D triangle strip used to draw the user's
+ // crayon stroke on the screen.
+ Point2 mouse_in_ndc = PixelsToNormalizedDeviceCoords(mouse_in_pixels);
+ AddToStroke(mouse_in_ndc);
+
+
+ // Next, try to figure out what we are drawing based on where the stroke originated.
+ Point3 i_point;
+ edit_billboard_id_ = billboards_.IntersectBillboard(cam_.view_matrix(), proj_matrix_, mouse_in_ndc);
+ if (edit_billboard_id_ >= 0) {
+ // If the mouse starts on an existing billboard, then we are editing the billboard.
+ drawing_state_ = DrawingState::DRAWING_BILLBOARD_EDIT;
+ }
+ else if (ground_.ScreenPtHitsGround(cam_.view_matrix(), proj_matrix_, mouse_in_ndc, &i_point)) {
+ // If the mouse starts on the ground, then we could be about to edit the
+ // ground, OR we might be creating a new billboard. We won't know for sure
+ // until the user releases the mouse and we can check to see whether the
+ // stroke also ends on the ground.
+ drawing_state_ = DrawingState::DRAWING_GROUND_OR_BILLBOARD;
+ }
+ else {
+ // Otherwise, we must be drawing a stroke in the sky.
+ drawing_state_ = DrawingState::DRAWING_SKY;
+ }
+}
+
+
+// This function is called once each frame while the user is drawing with the crayon
+void HaroldApp::OnLeftMouseDrag(const Point2 &mouse_in_pixels, const Vector2 &delta_in_pixels) {
+ // Add to the stroke_mesh_, which is a 2D triangle strip used to draw the user's
+ // crayon stroke on the screen.
+ Point2 mouse_in_ndc = PixelsToNormalizedDeviceCoords(mouse_in_pixels);
+ AddToStroke(mouse_in_ndc);
+}
+
+
+// This function is called at the end of each stroke
+void HaroldApp::OnLeftMouseUp(const Point2 &mouse_in_pixels) {
+
+ // If we are in the temporary drawing_ground_or_billboard state, then we need
+ // to do a final check now to see if the stroke ended on the ground or not.
+ // If it did, then we interpret the stroke as drawing_ground. Otherwise, we
+ // treat it as creating a new billboard.
+ if (drawing_state_ == DrawingState::DRAWING_GROUND_OR_BILLBOARD) {
+ // The stroke was started on the ground, does it also end on the ground?
+ Point2 mouse_in_ndc = PixelsToNormalizedDeviceCoords(mouse_in_pixels);
+ Point3 i_point;
+ if (ground_.ScreenPtHitsGround(cam_.view_matrix(), proj_matrix_, mouse_in_ndc, &i_point)) {
+ drawing_state_ = DrawingState::DRAWING_GROUND;
+ }
+ else {
+ drawing_state_ = DrawingState::DRAWING_BILLBOARD;
+ }
+ }
+
+
+ // Now, the action to take in terms of what geometry to add or modify in
+ // the scene depends entirely on the drawing state:
+ if (drawing_state_ == DrawingState::DRAWING_SKY) {
+ sky_.AddSkyStroke(cam_.view_matrix(), proj_matrix_, stroke2d_mesh_, crayon_color_);
+ }
+ else if (drawing_state_ == DrawingState::DRAWING_BILLBOARD) {
+ billboards_.AddBillboardStroke(cam_.view_matrix(), proj_matrix_, stroke2d_, stroke2d_mesh_, crayon_color_, &ground_);
+ }
+ else if (drawing_state_ == DrawingState::DRAWING_BILLBOARD_EDIT) {
+ billboards_.AddToBillboard(cam_.view_matrix(), proj_matrix_, edit_billboard_id_, stroke2d_mesh_, crayon_color_);
+ }
+ else if (drawing_state_ == DrawingState::DRAWING_GROUND) {
+ if (stroke2d_.size() < 6) {
+ std::cout << "Stroke is too short, try again." << std::endl;
+ }
+ else {
+ ground_.ReshapeGround(cam_.view_matrix(), proj_matrix_, stroke2d_);
+ }
+ }
+
+
+ // Done with this stroke. Clear the 2d stroke and its mesh and reset the drawing state
+ stroke2d_.clear();
+ stroke2d_mesh_ = Mesh();
+ drawing_state_ = DrawingState::DRAWING_NONE;
+}
+
+
+// You can look around, like in minecraft, by dragging with the right mouse button.
+void HaroldApp::OnRightMouseDrag(const Point2 &mouse_in_pixels, const Vector2 &delta_in_pixels) {
+ Vector2 delta_in_ndc = PixelsToNormalizedDeviceCoords(delta_in_pixels);
+ cam_.OnMouseMove(delta_in_ndc);
+}
+
+
+void HaroldApp::UpdateSimulation(double dt) {
+ if (drawing_state_ == DrawingState::DRAWING_NONE) {
+ // When walking around using the arrow keys we need to adjust the height
+ // of the virtual camera when we walk up a hill. To do that, we shoot
+ // a ray straight down from the eye point to the ground, find the point
+ // of intersection on the ground, and then set the camera height to be
+ // 2.0 meters above this.
+ Ray ray(cam_.eye(), -Vector3::UnitY());
+ float i_time;
+ Point3 i_pt;
+ int i_tri;
+ if (ray.FastIntersectMesh(ground_.mesh_ptr(), &i_time, &i_pt, &i_tri)) {
+ float height = 2.0f + i_pt[1]; // 2 meters above the gound
+ cam_.UpdateHeight(height);
+ }
+ cam_.UpdateSimulation(dt, window());
+
+
+ // The billboards also need to be updated to face the current camera
+ billboards_.UpdateBillboardRotations(cam_.eye());
+ }
+}
+
+
+void HaroldApp::DrawUsingOpenGL() {
+ // Clear the screen using the current sky color
+ glClearColor(sky_color_[0], sky_color_[1], sky_color_[2], 1);
+
+ // Draw the sky strokes
+ sky_.Draw(cam_.view_matrix(), proj_matrix_);
+
+ // Draw the ground mesh
+ ground_.Draw(cam_.view_matrix(), proj_matrix_, ground_color_);
+
+ // Draw the billboards
+ billboards_.Draw(cam_.view_matrix(), proj_matrix_);
+
+
+ // If we are currently drawing (indicated by the stroke mesh containing >0
+ // triangles), then draw the 2D triangle strip mesh for the crayon stroke
+ if (stroke2d_mesh_.num_triangles() > 0) {
+ stroke2d_shaderprog_.UseProgram();
+ stroke2d_shaderprog_.SetUniform("strokeColor", crayon_color_);
+ stroke2d_mesh_.Draw();
+ stroke2d_shaderprog_.StopProgram();
+ }
+}
+
diff --git a/dev/a6-harold/harold_app.h b/dev/a6-harold/harold_app.h
new file mode 100644
index 0000000..801a49a
--- /dev/null
+++ b/dev/a6-harold/harold_app.h
@@ -0,0 +1,120 @@
+/** CSci-4611 Assignment 6: Harold
+ */
+
+#ifndef HAROLD_APP_H_
+#define HAROLD_APP_H_
+
+#include <mingfx.h>
+using namespace mingfx;
+
+#include "billboards.h"
+#include "edge_mesh.h"
+#include "ground.h"
+#include "sky.h"
+
+#include <string>
+#include <vector>
+
+
+/** Main application class for the Harold assignment, which is a partial
+ implementation of the Cohen et al. paper.
+ */
+class HaroldApp : public GraphicsApp {
+public:
+
+ HaroldApp();
+ virtual ~HaroldApp();
+
+ // These are used for drawing
+ void OnLeftMouseDown(const Point2 &pos);
+ void OnLeftMouseDrag(const Point2 &pos, const Vector2 &delta);
+ void OnLeftMouseUp(const Point2 &pos);
+
+ // These are used for walking around and looking (i.e., camera movement)
+ void OnRightMouseDrag(const Point2 &pos, const Vector2 &delta);
+ void UpdateSimulation(double dt);
+
+ // Adds the 2D point to stroke2d_, which holds the centerline of the user's
+ // drawn stroke. Also updates stroke2d_mesh_, which is a 2D triangle strip
+ // used to actually draw the stroke on the screen.
+ void AddToStroke(const Point2 &normalized_screen_pt);
+
+ void InitNanoGUI();
+ void InitOpenGL();
+
+ void DrawUsingOpenGL();
+
+private:
+
+ // user input from the mouse is interpreted differently based upon where the
+ // stroke originates and where it ends. we can model this as a simple state
+ // machine
+ enum DrawingState {
+ // left mouse button is up, not currently drawing
+ DRAWING_NONE,
+
+ // stroke started in the sky, currently adding a sky stroke
+ DRAWING_SKY,
+
+ // stroke started on a billboard, currently editing that billboard
+ DRAWING_BILLBOARD_EDIT,
+
+ // temporary state for between mouse down and mouse up -- here, we know
+ // the stroke started on the ground, but not yet sure if it will end on
+ // the ground or in the sky
+ DRAWING_GROUND_OR_BILLBOARD,
+
+ // stroke ended on the ground, currently editing the ground
+ DRAWING_GROUND,
+
+ // stroke ended in the sky, currently creating a new billboard
+ DRAWING_BILLBOARD,
+ };
+ DrawingState drawing_state_;
+
+ // the centerline of the stroke dragged out by the mouse on the screen
+ std::vector<Point2> stroke2d_;
+
+ // a 2d triangle strip mesh for drawing the stroke on the filmplane
+ Mesh stroke2d_mesh_;
+
+ // The ground mesh and functionality to draw and edit the ground.
+ Ground ground_;
+
+ // Sky strokes and functionality to add more strokes to the sky
+ Sky sky_;
+
+ // Billboard strokes and functionality to add more billboards, including
+ // editing them by adding on to an existing billboard
+ Billboards billboards_;
+
+ // when in the DRAWING_BILLBOARD_EDIT state, this holds the id of the
+ // billboard that is currently being edited.
+ int edit_billboard_id_;
+
+
+ // Stroke shaders do not use any lighting, just transform vertices and then
+ // render the triangle strip for the stroke in the appropriate color. The
+ // 2D shader program is used in this class. The 3D is created here so that
+ // there is only one copy for the whole application, but it is used both the
+ // sky and billboard classes.
+ ShaderProgram stroke2d_shaderprog_;
+ ShaderProgram stroke3d_shaderprog_;
+
+ // This implements something like the camera controls in minecraft. You can
+ // walk around using the arrow keys or ASWZ keys. You can also move your
+ // head to look around by dragging with the right mouse button. Internally,
+ // the camera creates a view matrix.
+ CraftCam cam_;
+ Matrix4 proj_matrix_;
+
+ // A list of paths to search for data files (images and shaders)
+ std::vector<std::string> search_path_;
+
+ // Current colors set by the GUI
+ Color sky_color_;
+ Color ground_color_;
+ Color crayon_color_;
+};
+
+#endif \ No newline at end of file
diff --git a/dev/a6-harold/main.cc b/dev/a6-harold/main.cc
new file mode 100644
index 0000000..b672b01
--- /dev/null
+++ b/dev/a6-harold/main.cc
@@ -0,0 +1,10 @@
+/** CSci-4611 Assignment 6: Harold
+ */
+
+#include "harold_app.h"
+
+int main(int argc, const char *argv[]) {
+ HaroldApp app;
+ app.Run();
+ return 0;
+}
diff --git a/dev/a6-harold/shaders/artsy.frag b/dev/a6-harold/shaders/artsy.frag
new file mode 100644
index 0000000..403009d
--- /dev/null
+++ b/dev/a6-harold/shaders/artsy.frag
@@ -0,0 +1,29 @@
+#version 330
+
+in vec3 Position;
+in vec3 Normal;
+
+out vec4 color;
+
+uniform vec3 lightPosition;
+uniform vec4 Ia, Id, Is;
+
+uniform vec4 ka, kd, ks;
+uniform float s;
+
+uniform sampler2D diffuseRamp;
+uniform sampler2D specularRamp;
+
+
+void main() {
+ vec3 l = normalize(lightPosition.xyz - Position);
+ vec3 n = normalize(Normal);
+ vec3 e = normalize(-Position);
+ vec3 h = normalize(e + l);
+ float diffuse = (dot(l,n) + 1)/2;
+ float specular = pow(max(dot(n,h),0), s);
+ color = ka*Ia
+ + kd*Id*texture(diffuseRamp, vec2(diffuse,0))
+ + ks*Is*texture(specularRamp, vec2(specular,0));
+ color.a = 1;
+}
diff --git a/dev/a6-harold/shaders/artsy.vert b/dev/a6-harold/shaders/artsy.vert
new file mode 100644
index 0000000..cd9b998
--- /dev/null
+++ b/dev/a6-harold/shaders/artsy.vert
@@ -0,0 +1,17 @@
+#version 330
+
+uniform mat4 modelViewMatrix;
+uniform mat4 normalMatrix;
+uniform mat4 projectionMatrix;
+
+layout(location = 0) in vec3 vertex;
+layout(location = 1) in vec3 normal;
+
+out vec3 Position;
+out vec3 Normal;
+
+void main() {
+ Position = (modelViewMatrix * vec4(vertex,1)).xyz;
+ Normal = normalize((normalMatrix * vec4(normal,0)).xyz);
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(vertex,1);
+}
diff --git a/dev/a6-harold/shaders/outline.frag b/dev/a6-harold/shaders/outline.frag
new file mode 100644
index 0000000..096721d
--- /dev/null
+++ b/dev/a6-harold/shaders/outline.frag
@@ -0,0 +1,7 @@
+#version 330
+
+out vec4 color;
+
+void main() {
+ color = vec4(0,0,0, 1);
+}
diff --git a/dev/a6-harold/shaders/outline.vert b/dev/a6-harold/shaders/outline.vert
new file mode 100644
index 0000000..a6073e5
--- /dev/null
+++ b/dev/a6-harold/shaders/outline.vert
@@ -0,0 +1,23 @@
+#version 330
+
+uniform mat4 modelViewMatrix;
+uniform mat4 normalMatrix;
+uniform mat4 projectionMatrix;
+uniform float thickness;
+
+layout(location = 0) in vec3 vertex;
+layout(location = 1) in vec3 normal;
+layout(location = 2) in vec3 leftNormal;
+layout(location = 3) in vec3 rightNormal;
+
+void main() {
+ vec3 p = (modelViewMatrix * vec4(vertex,1)).xyz;
+ vec3 e = -p;
+ vec3 nl = (normalMatrix * vec4(leftNormal,0)).xyz;
+ vec3 nr = (normalMatrix * vec4(rightNormal,0)).xyz;
+ vec3 v = vertex;
+ if (dot(e,nl) * dot(e,nr) < 0.0) {
+ v += thickness*normal;
+ }
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(v,1);
+}
diff --git a/dev/a6-harold/shaders/stroke2d.frag b/dev/a6-harold/shaders/stroke2d.frag
new file mode 100644
index 0000000..2dd7efa
--- /dev/null
+++ b/dev/a6-harold/shaders/stroke2d.frag
@@ -0,0 +1,9 @@
+#version 330
+
+uniform vec4 strokeColor;
+
+out vec4 color;
+
+void main() {
+ color = strokeColor;
+}
diff --git a/dev/a6-harold/shaders/stroke2d.vert b/dev/a6-harold/shaders/stroke2d.vert
new file mode 100644
index 0000000..e40fdec
--- /dev/null
+++ b/dev/a6-harold/shaders/stroke2d.vert
@@ -0,0 +1,7 @@
+#version 330
+
+layout(location = 0) in vec3 vertex;
+
+void main() {
+ gl_Position = vec4(vertex,1);
+}
diff --git a/dev/a6-harold/shaders/stroke3d.frag b/dev/a6-harold/shaders/stroke3d.frag
new file mode 100644
index 0000000..2dd7efa
--- /dev/null
+++ b/dev/a6-harold/shaders/stroke3d.frag
@@ -0,0 +1,9 @@
+#version 330
+
+uniform vec4 strokeColor;
+
+out vec4 color;
+
+void main() {
+ color = strokeColor;
+}
diff --git a/dev/a6-harold/shaders/stroke3d.vert b/dev/a6-harold/shaders/stroke3d.vert
new file mode 100644
index 0000000..d333c4f
--- /dev/null
+++ b/dev/a6-harold/shaders/stroke3d.vert
@@ -0,0 +1,10 @@
+#version 330
+
+uniform mat4 modelViewMatrix;
+uniform mat4 projectionMatrix;
+
+layout(location = 0) in vec3 vertex;
+
+void main() {
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(vertex,1);
+}
diff --git a/dev/a6-harold/sky.cc b/dev/a6-harold/sky.cc
new file mode 100644
index 0000000..0819ebe
--- /dev/null
+++ b/dev/a6-harold/sky.cc
@@ -0,0 +1,74 @@
+/** CSci-4611 Assignment 6: Harold
+ */
+
+#include "sky.h"
+
+
+Sky::Sky() {
+
+}
+
+
+Sky::~Sky() {
+
+}
+
+void Sky::Init(ShaderProgram *stroke3d_shaderprog) {
+ stroke3d_shaderprog_ = stroke3d_shaderprog;
+}
+
+
+/// Projects a 2D normalized screen point (e.g., the mouse position in normalized
+/// device coordinates) to a 3D point on the "sky", which is really a huge sphere
+/// (radius = 1500) that the viewer is inside. This function should always return
+/// true since any screen point can successfully be projected onto the sphere.
+/// sky_point is set to the resulting 3D point. Note, this function only checks
+/// to see if the ray passing through the screen point intersects the sphere; it
+/// does not check to see if the ray hits the ground or anything else first.
+bool Sky::ScreenPtHitsSky(const Matrix4 &view_matrix, const Matrix4 &proj_matrix,
+ const Point2 &normalized_screen_pt, Point3 *sky_point)
+{
+ // TODO: Stitch together your worksheet implementation of this method
+ return true;
+}
+
+
+
+
+/// Creates a new sky stroke mesh by projecting each vertex of the 2D mesh
+/// onto the sky dome and saving the result as a new 3D mesh.
+void Sky::AddSkyStroke(const Matrix4 &view_matrix, const Matrix4 &proj_matrix,
+ const Mesh &stroke2d_mesh, const Color &stroke_color)
+{
+ // TODO: Create a new SkyStroke and add it to the strokes_ array.
+
+
+
+
+
+
+
+
+
+
+}
+
+
+/// Draws all of the sky strokes
+void Sky::Draw(const Matrix4 &view_matrix, const Matrix4 &proj_matrix) {
+
+ // Precompute matrices needed in the shader
+ Matrix4 model_matrix; // identity
+ Matrix4 modelview_matrix = view_matrix * model_matrix;
+
+ // Draw sky meshes
+ stroke3d_shaderprog_->UseProgram();
+ stroke3d_shaderprog_->SetUniform("modelViewMatrix", modelview_matrix);
+ stroke3d_shaderprog_->SetUniform("projectionMatrix", proj_matrix);
+ for (int i=0; i<strokes_.size(); i++) {
+ stroke3d_shaderprog_->SetUniform("strokeColor", strokes_[i].color);
+ strokes_[i].mesh.Draw();
+ }
+ stroke3d_shaderprog_->StopProgram();
+}
+
diff --git a/dev/a6-harold/sky.h b/dev/a6-harold/sky.h
new file mode 100644
index 0000000..06fcca1
--- /dev/null
+++ b/dev/a6-harold/sky.h
@@ -0,0 +1,52 @@
+/** CSci-4611 Assignment 6: Harold
+ */
+
+#ifndef SKY_H_
+#define SKY_H_
+
+#include <mingfx.h>
+using namespace mingfx;
+
+/** Creates, holds, and draws all of the strokes on the sky.
+ */
+class Sky {
+public:
+ Sky();
+ virtual ~Sky();
+
+ void Init(ShaderProgram *stroke3d_shaderprog);
+
+ /// Projects a 2D normalized screen point (e.g., the mouse position in normalized
+ /// device coordinates) to a 3D point on the "sky", which is really a huge sphere
+ /// (radius = 1500) that the viewer is inside. This function should always return
+ /// true since any screen point can successfully be projected onto the sphere.
+ /// sky_point is set to the resulting 3D point. Note, this function only checks
+ /// to see if the ray passing through the screen point intersects the sphere; it
+ /// does not check to see if the ray hits the ground or anything else first.
+ bool ScreenPtHitsSky(const Matrix4 &view_matrix, const Matrix4 &proj_matrix,
+ const Point2 &normalized_screen_pt, Point3 *sky_point);
+
+ /// Creates a new sky stroke mesh by projecting each vertex of the 2D mesh
+ /// onto the sky dome and saving the result as a new 3D mesh.
+ void AddSkyStroke(const Matrix4 &view_matrix, const Matrix4 &proj_matrix,
+ const Mesh &stroke2d_mesh, const Color &stroke_color);
+
+ /// Draws all of the sky strokes
+ void Draw(const Matrix4 &view_matrix, const Matrix4 &proj_matrix);
+
+private:
+
+ // Each stroke has a 3D mesh and a color
+ struct SkyStroke {
+ Mesh mesh;
+ Color color;
+ };
+
+ // To store a new stroke to draw, add it to this array
+ std::vector<SkyStroke> strokes_;
+
+
+ ShaderProgram *stroke3d_shaderprog_;
+};
+
+#endif