summaryrefslogtreecommitdiffstats
path: root/dev/a6-harold/harold_app.cc
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dev/a6-harold/harold_app.cc278
1 files changed, 278 insertions, 0 deletions
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();
+ }
+}
+