unused code
[b2ld.git] / xmain.d
blob80aa72ef083a350b5f89d93c3bc007cbeb1be1bc
1 module xmain is aliced;
3 import arsd.simpledisplay;
4 import arsd.color;
5 import arsd.image;
7 import iv.glbinds.utils;
8 import iv.vmath;
10 import b2dlite;
13 // ////////////////////////////////////////////////////////////////////////// //
14 enum GWidth = 800;
15 enum GHeight = 600;
18 // ////////////////////////////////////////////////////////////////////////// //
19 struct ContactXY {
20 VFloat x, y;
23 __gshared ContactXY[] contacts;
26 // ////////////////////////////////////////////////////////////////////////// //
27 // random number in range [-1,1]
28 public VFloat rnd () {
29 pragma(inline, true);
30 import std.random : uniform;
31 return cast(VFloat)uniform!"[]"(-VFloatNum!(1.0), VFloatNum!(1.0));
34 public VFloat rnd (VFloat lo, VFloat hi) {
35 pragma(inline, true);
36 import std.random : uniform;
37 return cast(VFloat)uniform!"[]"(lo, hi);
41 // ////////////////////////////////////////////////////////////////////////// //
42 __gshared Body bomb = null;
44 __gshared VFloat timeStep = VFloatNum!(1.0)/VFloatNum!(60.0);
45 __gshared int iterations = 10;
46 __gshared Vec2 gravity = Vec2(VFloatNum!(0.0), -VFloatNum!(10.0));
48 __gshared int demoIndex = 0;
50 __gshared World world;
51 shared static this () { world = new World(gravity, iterations); }
54 // ////////////////////////////////////////////////////////////////////////// //
55 void launchBomb () {
56 if (bomb is null) {
57 bomb = new Body();
58 bomb.set(Vec2(VFloatNum!(1.0), VFloatNum!(1.0)), VFloatNum!(50.0));
59 bomb.friction = VFloatNum!(0.2);
60 world ~= bomb;
62 bomb.position.set(rnd(-VFloatNum!(15.0), VFloatNum!(15.0)), VFloatNum!(15.0));
63 bomb.rotation = rnd(-VFloatNum!(1.5), VFloatNum!(1.5));
64 bomb.velocity = bomb.position*(-VFloatNum!(1.5));
65 bomb.angularVelocity = rnd(-VFloatNum!(20.0), VFloatNum!(20.0));
69 // ////////////////////////////////////////////////////////////////////////// //
70 void drawBody (Body body) {
71 auto R = Mat22(body.rotation);
72 Vec2 x = body.position;
73 Vec2 h = body.width*VFloatNum!(0.5);
75 Vec2 v1 = x+R*Vec2(-h.x, -h.y);
76 Vec2 v2 = x+R*Vec2( h.x, -h.y);
77 Vec2 v3 = x+R*Vec2( h.x, h.y);
78 Vec2 v4 = x+R*Vec2(-h.x, h.y);
80 if (body is bomb) {
81 glColor3f(VFloatNum!(0.4), VFloatNum!(0.9), VFloatNum!(0.4));
82 } else {
83 glColor3f(VFloatNum!(0.8), VFloatNum!(0.8), VFloatNum!(0.9));
86 glBegin(GL_LINE_LOOP);
87 glVertex2f(v1.x, v1.y);
88 glVertex2f(v2.x, v2.y);
89 glVertex2f(v3.x, v3.y);
90 glVertex2f(v4.x, v4.y);
91 glEnd();
95 void drawJoint (Joint joint) {
96 Body b1 = joint.body0;
97 Body b2 = joint.body1;
99 auto R1 = Mat22(b1.rotation);
100 auto R2 = Mat22(b2.rotation);
102 Vec2 x1 = b1.position;
103 Vec2 p1 = x1+R1*joint.localAnchor1;
105 Vec2 x2 = b2.position;
106 Vec2 p2 = x2+R2*joint.localAnchor2;
108 glColor3f(VFloatNum!(0.5), VFloatNum!(0.5), VFloatNum!(0.8));
109 glBegin(GL_LINES);
110 glVertex2f(x1.x, x1.y);
111 glVertex2f(p1.x, p1.y);
112 glVertex2f(x2.x, x2.y);
113 glVertex2f(p2.x, p2.y);
114 glEnd();
118 // ////////////////////////////////////////////////////////////////////////// //
119 // demos
120 struct DemoInfo { string dsc; }
123 // single box
124 @DemoInfo("A Single Box") void demo1 () {
125 with (auto b = new Body()) {
126 b.set(Vec2(VFloatNum!(100.0), VFloatNum!(20.0)), VFloat.max);
127 b.position.set(VFloatNum!(0.0), -VFloatNum!(0.5)*b.width.y);
128 world ~= b;
130 with (auto b = new Body()) {
131 b.set(Vec2(VFloatNum!(1.0), VFloatNum!(1.0)), VFloatNum!(200.0));
132 b.position.set(VFloatNum!(0.0), VFloatNum!(4.0));
133 world ~= b;
138 // a simple pendulum
139 @DemoInfo("Simple Pendulum") void demo2 () {
140 auto b1 = new Body();
141 b1.set(Vec2(VFloatNum!(100.0), VFloatNum!(20.0)), VFloat.max);
142 b1.friction = VFloatNum!(0.2);
143 b1.position.set(VFloatNum!(0.0), -VFloatNum!(0.5)*b1.width.y);
144 b1.rotation = VFloatNum!(0.0);
145 world ~= b1;
147 auto b2 = new Body();
148 b2.set(Vec2(VFloatNum!(1.0), VFloatNum!(1.0)), VFloatNum!(100.0));
149 b2.friction = VFloatNum!(0.2);
150 b2.position.set(VFloatNum!(9.0), VFloatNum!(11.0));
151 b2.rotation = VFloatNum!(0.0);
152 world ~= b2;
154 with (auto j = new Joint()) {
155 j.set(b1, b2, Vec2(VFloatNum!(0.0), VFloatNum!(11.0)));
156 world ~= j;
161 // varying friction coefficients
162 @DemoInfo("Varying Friction Coefficients") void demo3 () {
163 with (auto b = new Body()) {
164 b.set(Vec2(VFloatNum!(100.0), VFloatNum!(20.0)), VFloat.max);
165 b.position.set(VFloatNum!(0.0), -VFloatNum!(0.5)*b.width.y);
166 world ~= b;
169 with (auto b = new Body()) {
170 b.set(Vec2(VFloatNum!(13.0), VFloatNum!(0.25)), VFloat.max);
171 b.position.set(-VFloatNum!(2.0), VFloatNum!(11.0));
172 b.rotation = -VFloatNum!(0.25);
173 world ~= b;
176 with (auto b = new Body()) {
177 b.set(Vec2(VFloatNum!(0.25), VFloatNum!(1.0)), VFloat.max);
178 b.position.set(VFloatNum!(5.25), VFloatNum!(9.5));
179 world ~= b;
182 with (auto b = new Body()) {
183 b.set(Vec2(VFloatNum!(13.0), VFloatNum!(0.25)), VFloat.max);
184 b.position.set(VFloatNum!(2.0), VFloatNum!(7.0));
185 b.rotation = VFloatNum!(0.25);
186 world ~= b;
189 with (auto b = new Body()) {
190 b.set(Vec2(VFloatNum!(0.25), VFloatNum!(1.0)), VFloat.max);
191 b.position.set(-VFloatNum!(5.25), VFloatNum!(5.5));
192 world ~= b;
195 with (auto b = new Body()) {
196 b.set(Vec2(VFloatNum!(13.0), VFloatNum!(0.25)), VFloat.max);
197 b.position.set(-VFloatNum!(2.0), VFloatNum!(3.0));
198 b.rotation = -VFloatNum!(0.25);
199 world ~= b;
202 static immutable VFloat[5] frictions = [VFloatNum!(0.75), VFloatNum!(0.5), VFloatNum!(0.35), VFloatNum!(0.1), VFloatNum!(0.0)];
203 for (int idx = 0; idx < 5; ++idx) {
204 with (auto b = new Body()) {
205 b.set(Vec2(VFloatNum!(0.5), VFloatNum!(0.5)), VFloatNum!(25.0));
206 b.friction = frictions[idx];
207 b.position.set(-VFloatNum!(7.5)+VFloatNum!(2.0)*idx, VFloatNum!(14.0));
208 world ~= b;
214 // a vertical stack
215 @DemoInfo("Randomized Stacking") void demo4 () {
216 with (auto b = new Body()) {
217 b.set(Vec2(VFloatNum!(100.0), VFloatNum!(20.0)), VFloat.max);
218 b.friction = VFloatNum!(0.2);
219 b.position.set(VFloatNum!(0.0), -VFloatNum!(0.5)*b.width.y);
220 b.rotation = VFloatNum!(0.0);
221 world ~= b;
224 for (int idx = 0; idx < 10; ++idx) {
225 with (auto b = new Body()) {
226 b.set(Vec2(VFloatNum!(1.0), VFloatNum!(1.0)), VFloatNum!(1.0));
227 b.friction = VFloatNum!(0.2);
228 VFloat x = rnd(-VFloatNum!(0.1), VFloatNum!(0.1));
229 b.position.set(x, VFloatNum!(0.51)+VFloatNum!(1.05)*idx);
230 world ~= b;
236 // a pyramid
237 @DemoInfo("Pyramid Stacking") void demo5 () {
238 with (auto b = new Body()) {
239 b.set(Vec2(VFloatNum!(100.0), VFloatNum!(20.0)), VFloat.max);
240 b.friction = VFloatNum!(0.2);
241 b.position.set(VFloatNum!(0.0), -VFloatNum!(0.5)*b.width.y);
242 b.rotation = VFloatNum!(0.0);
243 world ~= b;
246 Vec2 x = Vec2(-VFloatNum!(6.0), VFloatNum!(0.75));
247 Vec2 y;
249 for (int idx = 0; idx < 12; ++idx) {
250 y = x;
251 for (int j = idx; j < 12; ++j) {
252 with (auto b = new Body()) {
253 b.set(Vec2(VFloatNum!(1.0), VFloatNum!(1.0)), VFloatNum!(10.0));
254 b.friction = VFloatNum!(0.2);
255 b.position = y;
256 world ~= b;
258 y += Vec2(VFloatNum!(1.125), VFloatNum!(0.0));
260 //x += Vec2(VFloatNum!(0.5625), VFloatNum!(1.125));
261 x += Vec2(VFloatNum!(0.5625), VFloatNum!(2.0));
266 // a teeter
267 @DemoInfo("A Teeter") void demo6 () {
268 Body b1 = new Body();
269 b1.set(Vec2(VFloatNum!(100.0), VFloatNum!(20.0)), VFloat.max);
270 b1.position.set(VFloatNum!(0.0), -VFloatNum!(0.5)*b1.width.y);
271 world ~= b1;
273 Body b2 = new Body();
274 b2.set(Vec2(VFloatNum!(12.0), VFloatNum!(0.25)), VFloatNum!(100.0));
275 b2.position.set(VFloatNum!(0.0), VFloatNum!(1.0));
276 world ~= b2;
278 Body b3 = new Body();
279 b3.set(Vec2(VFloatNum!(0.5), VFloatNum!(0.5)), VFloatNum!(25.0));
280 b3.position.set(-VFloatNum!(5.0), VFloatNum!(2.0));
281 world ~= b3;
283 Body b4 = new Body();
284 b4.set(Vec2(VFloatNum!(0.5), VFloatNum!(0.5)), VFloatNum!(25.0));
285 b4.position.set(-VFloatNum!(5.5), VFloatNum!(2.0));
286 world ~= b4;
288 Body b5 = new Body();
289 b5.set(Vec2(VFloatNum!(1.0), VFloatNum!(1.0)), VFloatNum!(100.0));
290 b5.position.set(VFloatNum!(5.5), VFloatNum!(15.0));
291 world ~= b5;
293 with (auto j = new Joint()) {
294 j.set(b1, b2, Vec2(VFloatNum!(0.0), VFloatNum!(1.0)));
295 world ~= j;
300 // a suspension bridge
301 @DemoInfo("A Suspension Bridge") void demo7 () {
302 import std.math : PI;
304 with (auto b = new Body()) {
305 b.set(Vec2(VFloatNum!(100.0), VFloatNum!(20.0)), VFloat.max);
306 b.friction = VFloatNum!(0.2);
307 b.position.set(VFloatNum!(0.0), -VFloatNum!(0.5)*b.width.y);
308 b.rotation = VFloatNum!(0.0);
309 world ~= b;
312 enum numPlanks = 15;
313 VFloat mass = VFloatNum!(50.0);
315 for (int idx = 0; idx < numPlanks; ++idx) {
316 auto b = new Body();
317 b.set(Vec2(VFloatNum!(1.0), VFloatNum!(0.25)), mass);
318 b.friction = VFloatNum!(0.2);
319 b.position.set(-VFloatNum!(8.5)+VFloatNum!(1.25)*idx, VFloatNum!(5.0));
320 world ~= b;
323 // tuning
324 VFloat frequencyHz = VFloatNum!(2.0);
325 VFloat dampingRatio = VFloatNum!(0.7);
327 // frequency in radians
328 VFloat omega = VFloatNum!(2.0)*PI*frequencyHz;
330 // damping coefficient
331 VFloat d = VFloatNum!(2.0)*mass*dampingRatio*omega;
333 // spring stifness
334 VFloat k = mass*omega*omega;
336 // magic formulas
337 VFloat softnesss = VFloatNum!(1.0)/(d+timeStep*k);
338 VFloat biasFactorr = timeStep*k/(d+timeStep*k);
340 for (int idx = 0; idx < numPlanks; ++idx) {
341 auto j = new Joint();
342 j.set(world.bodies[idx], world.bodies[idx+1], Vec2(-VFloatNum!(9.125)+VFloatNum!(1.25)*idx, VFloatNum!(5.0)));
343 j.softness = softnesss;
344 j.biasFactor = biasFactorr;
345 world ~= j;
348 with (auto j = new Joint()) {
349 j.set(world.bodies[numPlanks], world.bodies[0], Vec2(-VFloatNum!(9.125)+VFloatNum!(1.25)*numPlanks, VFloatNum!(5.0)));
350 j.softness = softnesss;
351 j.biasFactor = biasFactorr;
352 world ~= j;
357 // dominos
358 @DemoInfo("Dominos") void demo8 () {
359 Body b1 = new Body();
360 b1.set(Vec2(VFloatNum!(100.0), VFloatNum!(20.0)), VFloat.max);
361 b1.position.set(VFloatNum!(0.0), -VFloatNum!(0.5)*b1.width.y);
362 world ~= b1;
364 with (auto b = new Body()) {
365 b.set(Vec2(VFloatNum!(12.0), VFloatNum!(0.5)), VFloat.max);
366 b.position.set(-VFloatNum!(1.5), VFloatNum!(10.0));
367 world ~= b;
370 for (int idx = 0; idx < 10; ++idx) {
371 with (auto b = new Body()) {
372 b.set(Vec2(VFloatNum!(0.2), VFloatNum!(2.0)), VFloatNum!(10.0));
373 b.position.set(-VFloatNum!(6.0)+VFloatNum!(1.0)*idx, VFloatNum!(11.125));
374 b.friction = VFloatNum!(0.1);
375 world ~= b;
379 with (auto b = new Body()) {
380 b.set(Vec2(VFloatNum!(14.0), VFloatNum!(0.5)), VFloat.max);
381 b.position.set(VFloatNum!(1.0), VFloatNum!(6.0));
382 b.rotation = VFloatNum!(0.3);
383 world ~= b;
386 Body b2 = new Body();
387 b2.set(Vec2(VFloatNum!(0.5), VFloatNum!(3.0)), VFloat.max);
388 b2.position.set(-VFloatNum!(7.0), VFloatNum!(4.0));
389 world ~= b2;
391 Body b3 = new Body();
392 b3.set(Vec2(VFloatNum!(12.0), VFloatNum!(0.25)), VFloatNum!(20.0));
393 b3.position.set(-VFloatNum!(0.9), VFloatNum!(1.0));
394 world ~= b3;
396 with (auto j = new Joint()) {
397 j.set(b1, b3, Vec2(-VFloatNum!(2.0), VFloatNum!(1.0)));
398 world ~= j;
401 Body b4 = new Body();
402 b4.set(Vec2(VFloatNum!(0.5), VFloatNum!(0.5)), VFloatNum!(10.0));
403 b4.position.set(-VFloatNum!(10.0), VFloatNum!(15.0));
404 world ~= b4;
406 with (auto j = new Joint()) {
407 j.set(b2, b4, Vec2(-VFloatNum!(7.0), VFloatNum!(15.0)));
408 world ~= j;
411 Body b5 = new Body();
412 b5.set(Vec2(VFloatNum!(2.0), VFloatNum!(2.0)), VFloatNum!(20.0));
413 b5.position.set(VFloatNum!(6.0), VFloatNum!(2.5));
414 b5.friction = VFloatNum!(0.1);
415 world ~= b5;
417 with (auto j = new Joint()) {
418 j.set(b1, b5, Vec2(VFloatNum!(6.0), VFloatNum!(2.6)));
419 world ~= j;
422 // box cap
423 Body b6 = new Body();
424 b6.set(Vec2(VFloatNum!(2.0), VFloatNum!(0.2)), VFloatNum!(10.0));
425 b6.position.set(VFloatNum!(6.0), VFloatNum!(3.6));
426 world ~= b6;
428 with (auto j = new Joint()) {
429 j.set(b5, b6, Vec2(VFloatNum!(7.0), VFloatNum!(3.5)));
430 world ~= j;
435 // a multi-pendulum
436 @DemoInfo("Multi-pendulum") void demo9 () {
437 import std.math : PI;
439 Body b1 = new Body();
440 b1.set(Vec2(VFloatNum!(100.0), VFloatNum!(20.0)), VFloat.max);
441 b1.friction = VFloatNum!(0.2);
442 b1.position.set(VFloatNum!(0.0), -VFloatNum!(0.5)*b1.width.y);
443 b1.rotation = VFloatNum!(0.0);
444 world ~= b1;
446 VFloat mass = VFloatNum!(10.0);
448 // tuning
449 VFloat frequencyHz = VFloatNum!(4.0);
450 VFloat dampingRatio = VFloatNum!(0.7);
452 // frequency in radians
453 VFloat omega = VFloatNum!(2.0)*PI*frequencyHz;
455 // damping coefficient
456 VFloat d = VFloatNum!(2.0)*mass*dampingRatio*omega;
458 // spring stiffness
459 VFloat k = mass*omega*omega;
461 // magic formulas
462 VFloat softnesss = VFloatNum!(1.0)/(d+timeStep*k);
463 VFloat biasFactorr = timeStep*k/(d+timeStep*k);
465 const VFloat y = VFloatNum!(12.0);
467 for (int idx = 0; idx < 15; ++idx) {
468 Vec2 x = Vec2(VFloatNum!(0.5)+idx, y);
469 auto b = new Body();
470 b.set(Vec2(VFloatNum!(0.75), VFloatNum!(0.25)), mass);
471 b.friction = VFloatNum!(0.2);
472 b.position = x;
473 b.rotation = VFloatNum!(0.0);
474 world ~= b;
476 with (auto j = new Joint()) {
477 j.set(b1, b, Vec2(idx, y));
478 j.softness = softnesss;
479 j.biasFactor = biasFactorr;
480 world ~= j;
483 b1 = b;
488 // ////////////////////////////////////////////////////////////////////////// //
489 __gshared bool paused = false;
490 __gshared bool slowmo = false;
491 __gshared int slowmocount = 0;
494 bool setupDemo (int index, SimpleWindow w) {
495 import std.traits;
496 alias sms = getSymbolsByUDA!(mixin(__MODULE__), DemoInfo);
497 foreach (immutable idx, auto memb; sms) {
498 if (index == idx) {
499 w.title = getUDAs!(memb, DemoInfo)[0].dsc;
500 world.clear();
501 bomb = null;
502 memb();
503 demoIndex = index;
504 return true;
507 return false;
511 // ////////////////////////////////////////////////////////////////////////// //
512 void simulationStep () {
513 static double accumulator = 0;
515 glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
518 char[128] buf;
520 snprintf(buf, sizeof(buf), "Demo %d: %s", demoIndex+1, demos[demoIndex].name);
521 DrawText(5, 15, buf);
522 DrawText(5, 45, "Keys: 1-9 Demos, Space to Launch the Bomb");
524 static char buffer[512];
525 sprintf(buffer, "(A)ccumulation %s", (World::accumulateImpulses ? "ON" : "OFF"));
526 DrawText(5, 75, buffer);
528 sprintf(buffer, "(P)osition Correction %s", (World::positionCorrection ? "ON" : "OFF"));
529 DrawText(5, 105, buffer);
531 sprintf(buffer, "(W)arm Starting %s", (World::warmStarting ? "ON" : "OFF"));
532 DrawText(5, 135, buffer);
535 glMatrixMode(GL_MODELVIEW);
536 glLoadIdentity();
537 glTranslatef(VFloatNum!(0.0), -VFloatNum!(7.0), -VFloatNum!(25.0));
539 contacts.length = 0;
540 contacts.assumeSafeAppend;
542 world.step(timeStep);
546 // ////////////////////////////////////////////////////////////////////////// //
547 void drawWorld () {
548 glMatrixMode(GL_MODELVIEW);
549 glLoadIdentity();
550 glTranslatef(VFloatNum!(0.0), -VFloatNum!(7.0), -VFloatNum!(25.0));
552 // draw world
553 foreach (Body b; world.bodies) b.drawBody();
554 foreach (Joint j; world.joints) j.drawJoint();
556 // draw contacts
558 import iv.glbinds;
559 glPointSize(VFloatNum!(4.0));
560 glColor3f(VFloatNum!(1.0), VFloatNum!(0.0), VFloatNum!(0.0));
561 glBegin(GL_POINTS);
562 foreach (const ref cxy; contacts) glVertex2f(cxy.x, cxy.y);
563 glEnd();
564 glPointSize(VFloatNum!(1.0));
569 // ////////////////////////////////////////////////////////////////////////// //
570 void main () {
571 //setOpenGLContextVersion(3, 2); // up to GLSL 150
572 //openGLContextCompatible = false;
574 b2dlDrawContactsCB = delegate (VFloat x, VFloat y) {
575 contacts ~= ContactXY(x, y);
578 auto sdwindow = new SimpleWindow(GWidth, GHeight, "Box2DLite Physics", OpenGlOptions.yes, Resizablity.fixedSize);
579 //sdwindow.hideCursor();
581 //sdwindow.closeQuery = delegate () { concmd("quit"); };
583 sdwindow.redrawOpenGlScene = delegate () {
584 glClear(GL_COLOR_BUFFER_BIT);
585 drawWorld();
588 sdwindow.visibleForTheFirstTime = delegate () {
589 setupDemo(0, sdwindow);
590 sdwindow.setAsCurrentOpenGlContext(); // make this window active
591 // init matrices
593 glMatrixMode(GL_PROJECTION);
594 glLoadIdentity();
595 glOrtho(0, GWidth, GHeight, 0, -1, 1);
596 glMatrixMode(GL_MODELVIEW);
597 glLoadIdentity();
599 glViewport(0, 0, GWidth, GHeight);
600 glMatrixMode(GL_PROJECTION);
601 glLoadIdentity();
602 oglPerspective(45.0, cast(VFloat)GWidth/cast(VFloat)GHeight, 0.1, 100.0);
603 sdwindow.redrawOpenGlScene();
606 sdwindow.eventLoop(1000/60,
607 delegate () {
608 if (sdwindow.closed || world is null) return;
609 if (!paused) {
610 if (slowmo) {
611 if (--slowmocount < 0) {
612 simulationStep();
613 slowmocount = 10;
615 } else {
616 simulationStep();
617 slowmocount = 0;
620 sdwindow.redrawOpenGlSceneNow();
622 delegate (KeyEvent event) {
623 if (sdwindow.closed) return;
624 if (!event.pressed) return;
625 switch (event.key) {
626 case Key.Escape: sdwindow.close(); break;
627 default:
630 delegate (MouseEvent event) {
632 delegate (dchar ch) {
633 if (ch == 'q') { sdwindow.close(); return; }
634 if (ch == 'r') { setupDemo(demoIndex, sdwindow); return; }
635 if (ch == ' ') { paused = !paused; return; }
636 if (ch == 's') { slowmo = !slowmo; return; }
637 if (ch >= '1' && ch <= '9') { setupDemo(ch-'1', sdwindow); return; }
638 if (ch == '0') { setupDemo(10, sdwindow); return; }
639 if (ch == 'a') { World.accumulateImpulses = !World.accumulateImpulses; return; }
640 if (ch == 'p') { World.positionCorrection = !World.positionCorrection; return; }
641 if (ch == 'w') { World.warmStarting = !World.warmStarting; return; }
642 if (ch == '\n' || ch == '\r') { launchBomb(); return; }
643 if (ch == '+') { setupDemo(demoIndex+1, sdwindow); return; }
644 if (ch == '-') { if (demoIndex > 0) setupDemo(demoIndex-1, sdwindow); return; }