aboutsummaryrefslogtreecommitdiffstats
path: root/dev/a6-harold/billboards.cc
blob: 5ec508a26a2936fbb64fe6f836c2e18968f06dfa (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
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();
}