Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
boing.C
Go to the documentation of this file.
1/// \file
2/// \ingroup tutorial_eve_7
3/// The Amiga Boing ball, bouncing inside the 3D axis box, driven from the server.
4///
5/// Each timer tick sends only the transformations of the ball and its shadow.
6/// The ball also carries a trajectory, set with REveTrans::SetMotion(), which
7/// the client extrapolates between updates. The shadow carries none and steps
8/// to each new position. Run with a long period, e.g. boing(500), to see the
9/// difference. Select the ball to edit its REveSMorph parameters while it
10/// bounces. The Motion panel of the viewer's editor sets the update and redraw
11/// rates and switches the extrapolation off.
12///
13/// \macro_code
14///
15/// \author Matevz Tadel
16
17#include <ROOT/REveManager.hxx>
18#include <ROOT/REveScene.hxx>
19#include <ROOT/REveSMorph.hxx>
20#include <ROOT/REveViewer.hxx>
21#include <ROOT/REveTrans.hxx>
22
23#include <TTimer.h>
24#include <TMath.h>
25
26#include <chrono>
27
28using namespace ROOT::Experimental;
29
30// Room half-extents and ball radius. Y is up, which is the up axis of the
31// default REve camera, so the scene needs no camera setup.
32const Float_t kBX = 40, kBY = 30, kBZ = 40;
33const Float_t kR = 8;
34
35////////////////////////////////////////////////////////////////////////////////
36/// Moves the ball and its shadow on every timer tick.
37///
38/// Each tick integrates the motion and sets two transformation matrices with
39/// SetTransMatrix(). No geometry is rebuilt, so the cost per tick does not
40/// depend on how finely the ball is tessellated.
41
42class Boinger : public TTimer {
43 REveSMorph *fBall{nullptr};
44 REveSMorph *fShadow{nullptr};
45
46 Double_t fX{0}, fY{kBY - kR}, fZ{0}; // position; elastic, so fY is the apex
47 Double_t fVx{34}, fVy{0}, fVz{21}; // velocity; fVy is the falling one
48 Double_t fSpin{0}; // angle about the ball's polar axis
49
50 std::chrono::steady_clock::time_point fLast{std::chrono::steady_clock::now()};
51 std::chrono::steady_clock::time_point fT0{std::chrono::steady_clock::now()};
52 int fSent{0};
53
54 static constexpr Double_t kGrav = -160; // units / s^2, along -y
55 static constexpr Double_t kTilt = 0.30; // polar axis tipped out of vertical
56 static constexpr Double_t kSpinRate = 2.4; // rad / s
57
58 /// Time until the ball next hits a wall, the floor or the ceiling, in seconds.
59 /// SetMotion() gets it as the time the trajectory may be trusted, because a
60 /// bounce is the one change the client cannot predict.
61 Double_t TimeToNextBounce() const
62 {
63 Double_t t = 1e9;
64
65 auto linear = [&](Double_t p, Double_t v, Double_t lim) {
66 if (v > 1e-9)
67 t = TMath::Min(t, (lim - p) / v);
68 else if (v < -1e-9)
69 t = TMath::Min(t, (-lim - p) / v);
70 };
71 linear(fX, fVx, kBX - kR);
72 linear(fZ, fVz, kBZ - kR);
73
74 // y: solve 0.5*g*t^2 + v*t + (p - lim) = 0 for the next positive root,
75 // against whichever of floor or ceiling it is heading for.
76 const Double_t ylim = kBY - kR;
77 for (Double_t lim : {ylim, -ylim}) {
78 Double_t c = fY - lim, b = fVy, a = 0.5 * kGrav;
79 Double_t disc = b * b - 4 * a * c;
80 if (disc < 0)
81 continue;
82 Double_t sq = TMath::Sqrt(disc);
83 for (Double_t r : {(-b + sq) / (2 * a), (-b - sq) / (2 * a)})
84 if (r > 1e-6)
85 t = TMath::Min(t, r);
86 }
87
88 // Cap the window at 2 s, so that the ball stops soon after the timer does.
89 return TMath::Min(t, 2.0);
90 }
91
92 /// Reflects p and v off the wall at +-lim. The bounce is perfectly elastic,
93 /// so the ball returns to the same height every time.
94 static void Bounce(Double_t &p, Double_t &v, Double_t lim)
95 {
96 if (p > lim) {
97 p = 2 * lim - p;
98 v = -TMath::Abs(v);
99 } else if (p < -lim) {
100 p = -2 * lim - p;
101 v = TMath::Abs(v);
102 }
103 }
104
105public:
106 Boinger(REveSMorph *ball, REveSMorph *shadow, Long_t ms) : TTimer(ms, kTRUE), fBall(ball), fShadow(shadow) {}
107
108 int GetSent() const { return fSent; }
109
110 Bool_t Notify() override
111 {
112 // The timer needs no throttling of its own. A transformation-only change
113 // goes out on the motion channel, which skips a client that has not yet
114 // taken the previous message. Each message carries the absolute position,
115 // so the next one replaces a skipped one.
116 ++fSent;
117
118 // Integrate on the wall clock rather than the timer period, because
119 // timer ticks can arrive late.
120 auto now = std::chrono::steady_clock::now();
121 Double_t dt = std::chrono::duration<double>(now - fLast).count();
122 fLast = now;
123 // Clamp dt, so that a stall of the event loop does not make the ball jump.
124 if (dt > 0.1)
125 dt = 0.1;
126
127 // Report the tick rate every 100 ticks. Compare it with the timer period
128 // to see whether the event loop keeps up.
129 if (fSent % 100 == 0) {
130 Double_t el = std::chrono::duration<double>(now - fT0).count();
131 ::Info("boing", "%d ticks, %.1f/s over %.1f s", fSent, fSent / el, el);
132 }
133
134 // Integrate in fixed 5 ms sub-steps, so that the simulation does not
135 // depend on the timer period. One step over a long period can carry the
136 // ball past a wall, and the reflection then adds energy.
137 for (Double_t rem = dt; rem > 0;) {
138 const Double_t h = TMath::Min(rem, 0.005);
139 rem -= h;
140
141 fVy += kGrav * h;
142 fX += fVx * h;
143 fY += fVy * h;
144 fZ += fVz * h;
145
146 Bounce(fX, fVx, kBX - kR);
147 Bounce(fY, fVy, kBY - kR);
148 Bounce(fZ, fVz, kBZ - kR);
149
150 fSpin += kSpinRate * h;
151 }
152
153 // The ball spins at a constant rate about its polar axis, which for an
154 // REveSMorph is the local x. The axis is stood up and tilted by kTilt
155 // from vertical. The spin is independent of the flight, so the ball does
156 // not roll.
157 Double_t cs = TMath::Cos(fSpin), sn = TMath::Sin(fSpin);
158 // Rotate the polar axis by a quarter turn to stand it up (x -> y), then
159 // lean it over by kTilt.
160 Double_t al = TMath::PiOver2() + kTilt;
161 Double_t ct = TMath::Cos(al), st = TMath::Sin(al);
162
163 // Columns of Rz(al) * Rx(spin), scaled to the radius.
164 // clang-format off
165 Double_t e1[3] = { ct, st, 0 };
166 Double_t e2[3] = { -st * cs, ct * cs, sn };
167 Double_t e3[3] = { st * sn, -ct * sn, cs };
168 // clang-format on
169
171
172 REveTrans t;
173 t.SetBaseVec(1, kR * e1[0], kR * e1[1], kR * e1[2]);
174 t.SetBaseVec(2, kR * e2[0], kR * e2[1], kR * e2[2]);
175 t.SetBaseVec(3, kR * e3[0], kR * e3[1], kR * e3[2]);
176 t.SetPos(fX, fY, fZ);
177 fBall->SetTransMatrix(t.Array());
178
179 // Declare the trajectory as well as the position. The client evaluates it
180 // on its own frame clock, so the ball moves smoothly between updates.
181 // Under constant gravity the second-order form is the exact path. The
182 // trajectory is trusted until the next bounce, after which the client
183 // stops extrapolating until the next update.
184 REveVectorD vel(fVx, fVy, fVz);
185 REveVectorD acc(0., kGrav, 0.);
186
187 // The spin axis is in the ball's local frame, so it is the same (1,0,0)
188 // on every update. The orientation is already in the matrix.
189 REveVectorD spin_axis(1., 0., 0.);
190
191 fBall->RefMainTrans().SetMotion(vel, acc, spin_axis, kSpinRate, TimeToNextBounce());
192
193 // The shadow: a shallow dome under the ball that shrinks as the ball
194 // rises. It gets no SetMotion(), so the client moves it to each new
195 // matrix as it arrives, while the ball moves smoothly in between.
196 //
197 // It is a hemisphere (SetThetaMax(0.5)) flattened along its polar axis.
198 // A flattened whole sphere would z-fight with itself. The basis is set by
199 // hand because the polar axis, the local x, has to point up.
200 Double_t h = (fY + kBY) / (2 * kBY); // 0 at the floor, 1 at the ceiling
201
202 // Never wider than the ball, so the shadow stays inside the room when the
203 // ball is at a wall.
204 Double_t s = kR * (1.0 - 0.3 * h);
205
206 REveTrans sh;
207 sh.SetBaseVec(1, 0, 0.02 * kR, 0); // polar axis up, and squashed
208 sh.SetBaseVec(2, s, 0, 0);
209 sh.SetBaseVec(3, 0, 0, s);
210 // Raise the shadow by half its flattened thickness, 0.02 * kR, so the
211 // bottom of its bounding box is on the floor. REveSMorph's box spans the
212 // whole sphere, so its lower half is below the drawn dome.
213 sh.SetPos(fX, -kBY + 0.02 * kR, fZ);
214 fShadow->SetTransMatrix(sh.Array());
215
216 Reset();
217 return kTRUE;
218 }
219};
220
221void boing(Long_t period_ms = 40)
222{
223 auto eveMng = REveManager::Create();
224 eveMng->AllowMultipleRemoteConnections(false, false);
225
226 // The edge axes frame the room and carry the scale.
227 auto viewer = eveMng->GetDefaultViewer();
228 viewer->SetAxesType(REveViewer::kAxesEdge);
229 // Y is up, so the axes rule the floor, the surface the ball bounces off.
230 viewer->SetAxesUpAxis(1);
231 // Make the axes span the room. Otherwise they span the scene content, which
232 // here is only the ball and its shadow.
233 viewer->SetAxesBBox(-kBX, -kBY, -kBZ, kBX, kBY, kBZ);
234
235 auto scene = eveMng->GetEventScene();
236
237 auto ball = new REveSMorph("Boing ball");
238 ball->SetTLevel(32);
239 ball->SetPLevel(48);
240 ball->SetTexture("checker_8.png");
241 ball->SetMainColor(kWhite);
242 ball->SetPickable(kTRUE);
243 // Size it before the first frame, or the unit-size surface shows at the
244 // origin for the one tick before the timer first fires.
245 ball->SetRadius(kR);
246 scene->AddElement(ball);
247
248 auto shadow = new REveSMorph("Shadow");
249 shadow->SetTLevel(6);
250 shadow->SetPLevel(32);
251 shadow->SetThetaMax(0.5); // a hemisphere; see the comment in Notify()
252 shadow->SetMainColor(kBlack);
253 // Fairly opaque, because the lit surface's specular highlight lightens even
254 // a black shadow.
255 shadow->SetMainTransparency(20);
256 shadow->SetPickable(kFALSE);
257 scene->AddElement(shadow);
258
259 eveMng->Show();
260
261 (new Boinger(ball, shadow, period_ms))->TurnOn();
262}
#define b(i)
Definition RSha256.hxx:100
#define c(i)
Definition RSha256.hxx:101
#define a(i)
Definition RSha256.hxx:99
#define h(i)
Definition RSha256.hxx:106
#define e(i)
Definition RSha256.hxx:103
bool Bool_t
Boolean (0=false, 1=true) (bool)
Definition RtypesCore.h:78
long Long_t
Signed long integer 4 bytes (long). Size depends on architecture.
Definition RtypesCore.h:69
float Float_t
Float 4 bytes (float)
Definition RtypesCore.h:72
constexpr Bool_t kFALSE
Definition RtypesCore.h:109
double Double_t
Double 8 bytes.
Definition RtypesCore.h:74
constexpr Bool_t kTRUE
Definition RtypesCore.h:108
@ kBlack
Definition Rtypes.h:65
@ kWhite
Definition Rtypes.h:65
winID h TVirtualViewer3D TVirtualGLPainter p
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t r
RAII guard for locking Eve manager (ctor) and processing changes (dtor).
A parametric, texture-mapped surface of spherical topology: a sphere that can be twisted,...
REveTrans is a 4x4 transformation matrix for homogeneous coordinates stored internally in a column-ma...
virtual void Info(const char *method, const char *msgfmt,...) const
Issue info message.
Definition TObject.cxx:1070
Handles synchronous and a-synchronous timer events.
Definition TTimer.h:51
void Reset()
Reset the timer.
Definition TTimer.cxx:162
Bool_t Notify() override
Notify when timer times out.
Definition TTimer.cxx:148
Namespace for ROOT features in testing.
Definition TROOT.h:100
constexpr Double_t PiOver2()
Definition TMath.h:54
Double_t Sqrt(Double_t x)
Returns the square root of x.
Definition TMath.h:675
Short_t Min(Short_t a, Short_t b)
Returns the smallest of a and b.
Definition TMathBase.h:197
Double_t Cos(Double_t)
Returns the cosine of an angle of x radians.
Definition TMath.h:607
Double_t Sin(Double_t)
Returns the sine of an angle of x radians.
Definition TMath.h:601
Short_t Abs(Short_t d)
Returns the absolute value of parameter Short_t d.
Definition TMathBase.h:122