Line data Source code
1 : #include "catch.hpp"
2 : #include "Camera.h"
3 : #include "Matrix.h"
4 : #include "Vector.h"
5 :
6 11 : SCENARIO( "Camera state without GL or Lua", "[Camera]" ) {
7 19 : GIVEN( "A default camera" ) {
8 9 : Camera cam;
9 :
10 : // Camera::update always rebuilds rotationMatrix unconditionally (Camera.cpp:121),
11 : // unlike Object which gates the rebuild on rotationChanged_.
12 :
13 11 : WHEN( "first constructed" ) {
14 3 : THEN( "frustum pointer is the embedded instance" ) {
15 1 : REQUIRE(cam.getFrustum() == &cam.frustum);
16 1 : }
17 :
18 : // Camera::initialize calls worldRotation.buildFromEuler(0,0,0) — identity.
19 3 : THEN( "worldRotation is identity" ) {
20 1 : REQUIRE(cam.worldRotation.getW() == Approx(1.0f));
21 1 : REQUIRE(cam.worldRotation.getX() == Approx(0.0f));
22 1 : REQUIRE(cam.worldRotation.getY() == Approx(0.0f));
23 1 : REQUIRE(cam.worldRotation.getZ() == Approx(0.0f));
24 1 : }
25 2 : }
26 :
27 10 : WHEN( "setCamera is called with kinematic values" ) {
28 1 : Vector pos(3.0f, -1.0f, 2.0f);
29 1 : Vector vel(-4.0f, 0.5f, 0.25f);
30 1 : Quaternion rot;
31 1 : rot.buildFromEuler(0.2f, -0.3f, 0.4f);
32 1 : rot.normalize();
33 1 : Vector rotVel(0.01f, 0.02f, 0.03f);
34 1 : cam.setCamera(pos, vel, rot, rotVel);
35 :
36 2 : THEN( "position, velocity, rotation, and rotationVelocity are copied" ) {
37 1 : Vector p = cam.getPosition();
38 1 : REQUIRE(p.x == Approx(3.0f));
39 1 : REQUIRE(p.y == Approx(-1.0f));
40 1 : REQUIRE(p.z == Approx(2.0f));
41 :
42 1 : Vector v = cam.getVelocity();
43 1 : REQUIRE(v.x == Approx(-4.0f));
44 1 : REQUIRE(v.y == Approx(0.5f));
45 1 : REQUIRE(v.z == Approx(0.25f));
46 :
47 1 : Quaternion r = cam.getRotation();
48 1 : REQUIRE(r.getX() == Approx(rot.getX()));
49 1 : REQUIRE(r.getY() == Approx(rot.getY()));
50 1 : REQUIRE(r.getZ() == Approx(rot.getZ()));
51 1 : REQUIRE(r.getW() == Approx(rot.getW()));
52 :
53 1 : Vector rv = cam.getRotationVelocity();
54 1 : REQUIRE(rv.x == Approx(0.01f));
55 1 : REQUIRE(rv.y == Approx(0.02f));
56 1 : REQUIRE(rv.z == Approx(0.03f));
57 1 : }
58 1 : }
59 :
60 : // setCamera assigns rotation_ directly without setting rotationChanged_, like
61 : // Object::setRotationQuaternion. Camera::update rebuilds unconditionally so this
62 : // does not affect rendering, but callers using isRotationChanged() as a change
63 : // signal should use setRotation() instead.
64 10 : WHEN( "setCamera is called after clearing rotationChanged" ) {
65 1 : Quaternion q;
66 1 : q.buildFromEuler(0.1f, 0.0f, 0.0f);
67 1 : cam.setRotationChanged(false);
68 2 : cam.setCamera(Vector(0.0f, 0.0f, 0.0f), Vector(0.0f, 0.0f, 0.0f), q,
69 1 : Vector(0.0f, 0.0f, 0.0f));
70 :
71 2 : THEN( "rotationChanged remains false" ) {
72 1 : REQUIRE(cam.isRotationChanged() == false);
73 1 : }
74 1 : }
75 :
76 10 : WHEN( "update is called with zero elapsed time" ) {
77 1 : Quaternion q;
78 1 : q.buildFromEuler(0.1f, 0.15f, -0.05f);
79 1 : q.normalize();
80 2 : cam.setCamera(Vector(0.0f, 0.0f, 0.0f), Vector(0.0f, 0.0f, 0.0f), q,
81 1 : Vector(0.0f, 0.0f, 0.0f));
82 1 : cam.update(0.0f);
83 :
84 2 : THEN( "rotation matrix matches the quaternion" ) {
85 1 : Matrix expected;
86 1 : q.buildRotationMatrix(expected);
87 1 : Matrix actual;
88 1 : cam.getRotationMatrix(actual);
89 17 : for (int i = 0; i < NUM_CELLS; ++i) {
90 16 : REQUIRE(actual.data[i] == Approx(expected.data[i]));
91 16 : }
92 1 : }
93 1 : }
94 :
95 10 : WHEN( "update is called with a non-zero velocity" ) {
96 1 : Quaternion q;
97 1 : q.buildFromEuler(0.0f, 0.0f, 0.0f);
98 2 : cam.setCamera(Vector(0.0f, 0.0f, 0.0f), Vector(2.0f, 0.0f, -1.0f), q,
99 1 : Vector(0.0f, 0.0f, 0.0f));
100 1 : cam.update(0.5f);
101 :
102 2 : THEN( "position advances by velocity * elapsed time" ) {
103 1 : Vector p = cam.getPosition();
104 1 : REQUIRE(p.x == Approx(1.0f)); // 0 + 2.0 * 0.5
105 1 : REQUIRE(p.y == Approx(0.0f)); // velocity_.y == 0, unchanged
106 1 : REQUIRE(p.z == Approx(-0.5f)); // 0 + (-1.0) * 0.5
107 1 : }
108 1 : }
109 :
110 11 : WHEN( "update is called with a non-zero rotation velocity" ) {
111 2 : Quaternion q;
112 2 : q.buildFromEuler(0.0f, 0.0f, 0.0f);
113 4 : cam.setCamera(Vector(0.0f, 0.0f, 0.0f), Vector(0.0f, 0.0f, 0.0f), q,
114 2 : Vector(0.0f, 1.0f, 0.0f));
115 2 : cam.setRotationChanged(false);
116 2 : cam.update(0.5f);
117 :
118 3 : THEN( "rotationChanged is set" ) {
119 1 : REQUIRE(cam.isRotationChanged() == true);
120 1 : }
121 :
122 3 : THEN( "rotation is no longer identity" ) {
123 1 : REQUIRE(cam.getRotation().getW() < 1.0f);
124 1 : }
125 2 : }
126 :
127 : // Camera::update rotates baseDirection_ by rotationMatrix then negates direction_.z
128 : // (Camera.cpp:124). With identity rotation and baseDirection_ (0,0,1), the result
129 : // is direction_ = (0, 0, -1). See docs/MATH_AND_TESTING_CONVENTIONS.md.
130 10 : WHEN( "update is called with identity rotation" ) {
131 1 : Quaternion q;
132 1 : q.buildFromEuler(0.0f, 0.0f, 0.0f);
133 2 : cam.setCamera(Vector(0.0f, 0.0f, 0.0f), Vector(0.0f, 0.0f, 0.0f), q,
134 1 : Vector(0.0f, 0.0f, 0.0f));
135 1 : cam.update(0.0f);
136 :
137 2 : THEN( "direction z is negated relative to the rotated base direction" ) {
138 1 : Vector d = cam.getDirection();
139 1 : REQUIRE(d.x == Approx(0.0f));
140 1 : REQUIRE(d.y == Approx(0.0f));
141 1 : REQUIRE(d.z == Approx(-1.0f));
142 1 : }
143 1 : }
144 9 : }
145 :
146 11 : GIVEN( "A camera constructed with an id" ) {
147 1 : Camera cam(42);
148 :
149 2 : WHEN( "first constructed" ) {
150 2 : THEN( "id is set" ) {
151 1 : REQUIRE(cam.id_ == 42);
152 1 : }
153 1 : }
154 1 : }
155 10 : }
|