-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtut01_vector.cpp
More file actions
251 lines (232 loc) · 13.2 KB
/
Copy pathtut01_vector.cpp
File metadata and controls
251 lines (232 loc) · 13.2 KB
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
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
/// @file tut01_vector.cpp
/// @brief Guided, runnable walkthrough of GSVector, GOST's 3D vector type (L0 math).
///
/// @page tutorial_01 Tutorial 01 — GSVector: 3D vectors
///
/// This tutorial introduces @c GSVector, the value type used throughout the
/// engine for ray origins, ray directions, surface normals, and points in
/// space. It is a self-contained program that links only the GOST core (no
/// ROOT) and walks through the type section by section, checking every claimed
/// result with a small @c Assert() helper — so it doubles as a smoke test.
///
/// Two domain contracts are demonstrated up front and are worth remembering:
/// - @b Pass-by-value @b idiom: the accessors @c X()/@c Y()/@c Z() (and
/// @c Unit(), @c Norm(), @c Print(), ...) are not const-correct, so the
/// codebase passes @c GSVector arguments by value rather than by
/// @c const& . All the helpers and free operators here follow that idiom.
/// - @b Unit-vector @b convention: a @c GSVector used as a direction (a ray's
/// travel direction, a surface normal) is assumed to be normalised unless
/// stated otherwise; build directions with @c .Unit() .
///
/// @section tut01_sec Contents
/// -# Constructing vectors
/// -# Component access (@c X/@c Y/@c Z, @c Get)
/// -# Arithmetic (@c + , @c - , scalar @c * , compound assignment)
/// -# Dot product (@c Dot and @c operator* on two vectors)
/// -# Cross product (@c Cross and @c operator^ )
/// -# Norm / Norm2 / Unit (length and normalisation)
/// -# Angle between vectors (@c GetAngle / @c GetAngleDegree )
/// -# Printing (@c Print )
///
/// Build/run: compiled as @c tut01_vector at the build root; runs standalone
/// and returns exit code 0 when all checks pass (non-zero otherwise).
// tut01_vector.cpp — GOST tutorial 01: the 3D vector type GSVector (L0 math).
//
// GSVector is the workhorse of the whole engine: ray origins, ray directions,
// surface normals, and points in space are all GSVector values. This tutorial
// is a guided walkthrough of its API. It is a runnable, self-contained program
// that links ONLY the GOST core (no ROOT). Every claim it makes about a result
// is checked with a tiny Assert() helper, so the tutorial doubles as a smoke
// test: it prints [OK]/[FAIL] lines and main() returns non-zero if anything is
// wrong.
//
// Build (from the project root, headers are on -Iinclude):
// g++ -std=c++11 -Wall -Iinclude tutorials/tut01_vector.cpp src/*.cpp -o tut01
// ./tut01
//
// ---------------------------------------------------------------------------
// TWO DOMAIN CONVENTIONS TO INTERNALISE BEFORE READING ON
// ---------------------------------------------------------------------------
//
// (1) GSVector is passed BY VALUE throughout this codebase.
// The component accessors X(), Y(), Z() (and Unit(), Norm(), Print(), ...)
// are NOT const-correct — they are declared non-const. That means you
// cannot call them on a `const GSVector&`. Rather than fight this, the
// project's idiom is simply to pass GSVector arguments BY VALUE (a cheap
// 3-double copy). You will see this everywhere, e.g. the free operators
// `operator+(GSVector a, GSVector b)` take their arguments by value, and
// the test/demo helpers take `GSVector` by value too. Follow the idiom.
//
// (2) Direction vectors are expected to be UNIT vectors unless stated otherwise.
// In the optics code a GSVector used as a *direction* (a ray's travel
// direction, a surface normal) is assumed to be normalised (length 1).
// Position vectors carry a real length and are not normalised. When you
// build a direction from two points, remember to call .Unit() on it.
#include "GSVector.hpp"
#include "cmath"
#include "iostream"
#include "iomanip"
#include "string"
// ------------------------------------------------------------------ utilities
static int gFail = 0;
/// Print a PASS/FAIL line and bump the global failure counter on failure.
static void Assert(bool cond, const std::string& what){
std::cout << (cond ? " [OK] " : " [FAIL] ") << what << std::endl;
if(!cond) ++gFail;
}
/// Floating-point comparison with a tolerance (never compare doubles with ==).
static bool approx(double a, double b, double tol = 1e-9){
return std::fabs(a - b) <= tol;
}
/// Component-wise vector equality. Note: takes GSVector BY VALUE (idiom above),
/// because it must call the non-const accessors X()/Y()/Z().
static bool veq(GSVector a, GSVector b, double tol = 1e-9){
return approx(a.X(), b.X(), tol) &&
approx(a.Y(), b.Y(), tol) &&
approx(a.Z(), b.Z(), tol);
}
int main(){
std::cout << "==========================================================\n";
std::cout << " GOST tutorial 01 — GSVector (3D vector, L0 math)\n";
std::cout << "==========================================================\n";
std::cout << std::fixed << std::setprecision(4);
// ==== 1. Constructing vectors =========================================
//
// Four constructors are available:
// GSVector() -> the zero vector (0,0,0)
// GSVector(x, y) -> a 2D vector; z is implicitly 0
// GSVector(x, y, z) -> the general 3D vector
// GSVector(const GSVector&) -> copy
// The 2-argument form is handy for planar work but still lives in 3D — the
// engine is 3D-native, there is no separate 2D vector type.
std::cout << "\n==== 1. Constructing vectors ====\n";
GSVector zero; // (0,0,0)
GSVector planar(3.0, 4.0); // (3,4,0) — z defaults to 0
GSVector v(1.0, 2.0, 2.0); // (1,2,2)
GSVector vCopy(v); // copy construction
std::cout << " zero = " << zero.Print(true) << "\n";
std::cout << " planar = " << planar.Print(true) << "\n";
std::cout << " v = " << v.Print(true) << "\n";
Assert(veq(zero, GSVector(0,0,0)), "default ctor is the zero vector");
Assert(veq(planar, GSVector(3,4,0)), "two-arg ctor leaves z = 0");
Assert(veq(vCopy, v), "copy ctor duplicates components");
// ==== 2. Component access =============================================
//
// X(), Y(), Z() return the individual components; Get(x,y,z) writes all
// three into references at once. REMEMBER: these accessors are non-const,
// which is exactly why we pass GSVector by value everywhere (convention 1).
std::cout << "\n==== 2. Component access ====\n";
std::cout << " v.X()=" << v.X() << " v.Y()=" << v.Y() << " v.Z()=" << v.Z() << "\n";
double gx, gy, gz;
v.Get(gx, gy, gz); // bulk read into three doubles
Assert(approx(v.X(),1.0) && approx(v.Y(),2.0) && approx(v.Z(),2.0),
"X()/Y()/Z() return the components");
Assert(approx(gx,1.0) && approx(gy,2.0) && approx(gz,2.0),
"Get(x,y,z) fills all three references");
// ==== 3. Arithmetic: + , - , scalar * =================================
//
// Addition and subtraction are provided as free operators taking arguments
// BY VALUE. Scalar multiplication works on either side (scalar*vec and
// vec*scalar) and scales every component. There are also compound
// assignment forms (+=, -=, *=) that mutate the left operand in place.
std::cout << "\n==== 3. Arithmetic (+, -, scalar *) ====\n";
GSVector a(1.0, 2.0, 3.0);
GSVector b(4.0, 5.0, 6.0);
// Wart worth knowing: GSVector declares operator+ in TWO forms (by value and
// by non-const reference), so adding two *named lvalues* directly (a + b) is
// ambiguous and will not compile. Feeding one temporary (an rvalue) selects
// the by-value overload cleanly. Subtraction has only the by-value form, so
// b - a with two lvalues is fine.
GSVector sum = a + GSVector(4.0, 5.0, 6.0); // == a + b -> (5,7,9)
GSVector diff = b - a; // (3,3,3)
GSVector twice = 2.0 * a; // (2,4,6) — scalar on the left
GSVector half = b * 0.5; // (2,2.5,3) — scalar on the right
std::cout << " a+b = " << sum.Print(true) << "\n";
std::cout << " b-a = " << diff.Print(true) << "\n";
std::cout << " 2*a = " << twice.Print(true) << "\n";
Assert(veq(sum, GSVector(5,7,9)), "operator+ adds component-wise");
Assert(veq(diff, GSVector(3,3,3)), "operator- subtracts component-wise");
Assert(veq(twice, GSVector(2,4,6)), "scalar*vector scales every component");
Assert(veq(half, GSVector(2,2.5,3)), "vector*scalar scales every component");
// Compound assignment mutates in place.
GSVector acc(1.0, 1.0, 1.0);
acc += GSVector(2.0, 3.0, 4.0); // acc becomes (3,4,5)
acc *= 2.0; // acc becomes (6,8,10)
Assert(veq(acc, GSVector(6,8,10)), "compound += and *= mutate in place");
// ==== 4. Dot product ==================================================
//
// The dot product a·b measures alignment: for UNIT vectors it equals the
// cosine of the angle between them. Two spellings exist and mean the same
// thing: the member a.Dot(b), and the free operator (GSVector*GSVector),
// which — note the overload — returns a *double*, not a vector. (The other
// operator* overloads, with a scalar, return a vector; see section 3.)
std::cout << "\n==== 4. Dot product ====\n";
double dot1 = a.Dot(b); // 1*4 + 2*5 + 3*6 = 32
double dot2 = a * b; // same thing via operator*
std::cout << " a.Dot(b) = " << dot1 << " a*b = " << dot2 << "\n";
Assert(approx(dot1, 32.0), "Dot: a.Dot(b) = 1*4+2*5+3*6 = 32");
Assert(approx(dot1, dot2), "operator* on two vectors is the dot product");
// Orthogonal vectors have zero dot product.
Assert(approx(GSVector(1,0,0).Dot(GSVector(0,1,0)), 0.0),
"perpendicular vectors have dot product 0");
// ==== 5. Cross product (and the ^ operator) ===========================
//
// The cross product a x b yields a vector perpendicular to BOTH inputs,
// following the right-hand rule; its magnitude is |a||b|sin(theta). GOST
// offers two spellings again: the member a.Cross(b), and the operator ^
// (chosen because '^' visually resembles a wedge). They are identical.
// Handy identity: x_hat x y_hat = z_hat.
std::cout << "\n==== 5. Cross product (Cross and ^) ====\n";
GSVector xhat(1,0,0), yhat(0,1,0);
GSVector cx = xhat.Cross(yhat); // (0,0,1)
GSVector cx2 = xhat ^ yhat; // same, via operator^
std::cout << " xhat x yhat = " << cx.Print(true) << "\n";
Assert(veq(cx, GSVector(0,0,1)), "Cross: xhat x yhat = zhat (right-hand rule)");
Assert(veq(cx2, cx), "operator^ equals Cross()");
// The result is perpendicular to both inputs (dot with each is 0).
GSVector c = a.Cross(b);
Assert(approx(c.Dot(a), 0.0) && approx(c.Dot(b), 0.0),
"a x b is perpendicular to both a and b");
// ==== 6. Norm (length) and Unit (normalisation) =======================
//
// Norm() is the Euclidean length sqrt(x^2+y^2+z^2); Norm2() is its square
// (cheaper — no sqrt — when you only need to compare magnitudes). Unit()
// returns a copy scaled to length 1, pointing the same way. This is how you
// turn any direction into the UNIT vector the optics code expects
// (convention 2). v = (1,2,2) is a classic 3-4-5-style triple: |v| = 3.
std::cout << "\n==== 6. Norm and Unit ====\n";
std::cout << " v.Norm() = " << v.Norm() << "\n";
std::cout << " v.Norm2() = " << v.Norm2() << "\n";
GSVector u = v.Unit();
std::cout << " v.Unit() = " << u.Print(true) << " (length "
<< u.Norm() << ")\n";
Assert(approx(v.Norm(), 3.0), "Norm of (1,2,2) is 3");
Assert(approx(v.Norm2(), 9.0), "Norm2 is the squared length (9)");
Assert(approx(u.Norm(), 1.0), "Unit() returns a length-1 vector");
// ==== 7. Angles between vectors =======================================
//
// GetAngle returns the angle in radians in [0, pi]; GetAngleDegree the same
// in degrees. Internally it normalises both operands, so the inputs need
// not be unit vectors. xhat and yhat are perpendicular -> 90 degrees.
std::cout << "\n==== 7. Angle between vectors ====\n";
double deg = xhat.GetAngleDegree(yhat);
std::cout << " angle(xhat, yhat) = " << deg << " deg\n";
Assert(approx(deg, 90.0, 1e-6), "GetAngleDegree(xhat, yhat) = 90 degrees");
// ==== 8. Printing =====================================================
//
// Print(quite) returns a "[x,y,z]" string. With quite=false (the default)
// it ALSO writes a line to std::cout as a side effect; with quite=true it
// only returns the string (what we have used above to build our own lines).
std::cout << "\n==== 8. Printing ====\n";
std::cout << " Print(false) prints and returns; watch the next line:\n ";
std::string s = v.Print(); // prints "[x,y,z] = [1,2,2]"
Assert(s == std::string("[1,2,2]"), "Print returns the bracketed string form");
// ------------------------------------------------------------------ done
std::cout << "\n----------------------------------------------------------\n";
if(gFail == 0){
std::cout << "TUTORIAL 01: all checks passed.\n";
return 0;
}
std::cout << "TUTORIAL 01: " << gFail << " check(s) FAILED.\n";
return 1;
}