diff options
Diffstat (limited to '')
-rw-r--r-- | dev/a6-harold/billboards.cc | 206 |
1 files changed, 206 insertions, 0 deletions
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(); +} + |