diff options
author | Matt Strapp <matt@mattstrapp.net> | 2021-12-17 13:17:58 -0600 |
---|---|---|
committer | Matt Strapp <matt@mattstrapp.net> | 2021-12-17 13:17:58 -0600 |
commit | e0a8f6b97c473d28af96f28ca6b46d41b91646a3 (patch) | |
tree | 8faef1da18b809c9585c11ba5917f1f8268cddf6 /dev/a6-harold | |
parent | Add README (diff) | |
parent | Clarify how to get eye point in Sky::ScreenPtHitsSky (diff) | |
download | csci4611-e0a8f6b97c473d28af96f28ca6b46d41b91646a3.tar csci4611-e0a8f6b97c473d28af96f28ca6b46d41b91646a3.tar.gz csci4611-e0a8f6b97c473d28af96f28ca6b46d41b91646a3.tar.bz2 csci4611-e0a8f6b97c473d28af96f28ca6b46d41b91646a3.tar.lz csci4611-e0a8f6b97c473d28af96f28ca6b46d41b91646a3.tar.xz csci4611-e0a8f6b97c473d28af96f28ca6b46d41b91646a3.tar.zst csci4611-e0a8f6b97c473d28af96f28ca6b46d41b91646a3.zip |
Merge branch 'support-code' of https://github.umn.edu/umn-csci-4611-f21/shared-upstream
Diffstat (limited to '')
29 files changed, 2016 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 ¤t_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 ¤t_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 Binary files differnew file mode 100644 index 0000000..d2e40d9 --- /dev/null +++ b/dev/a6-harold/data/toonDiffuse.png diff --git a/dev/a6-harold/data/toonSpecular.png b/dev/a6-harold/data/toonSpecular.png Binary files differnew file mode 100644 index 0000000..c13a32f --- /dev/null +++ b/dev/a6-harold/data/toonSpecular.png 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..bc08ca0 --- /dev/null +++ b/dev/a6-harold/sky.cc @@ -0,0 +1,79 @@ +/** 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) +{ + // You will need the eye point, this can be + // determined from the view matrix as follows: + Matrix4 camera_matrix = view_matrix.Inverse(); + Point3 eye = camera_matrix.ColumnToPoint3(3); + + // 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 |