added some comments to README
[b2ld.git] / xmain.d
blob9603baa483e1d4ad3dfc0e0a7ba051d0d750fbb7
1 /*
2 * Copyright (c) 2006-2007 Erin Catto http://box2d.org
4 * Permission to use, copy, modify, distribute and sell this software
5 * and its documentation for any purpose is hereby granted without fee,
6 * provided that the above copyright notice appear in all copies.
7 * Erin Catto makes no representations about the suitability
8 * of this software for any purpose.
9 * It is provided "as is" without express or implied warranty.
11 * Changes by Ketmar // Invisible Vector
13 module xmain;
15 import arsd.simpledisplay;
17 import iv.glbinds.utils;
18 import iv.vmath;
20 import b2dlite;
22 bool optShowTimes = false;
23 bool optDrawBVH = false;
26 // ////////////////////////////////////////////////////////////////////////// //
27 enum GWidth = 800;
28 enum GHeight = 600;
31 // ////////////////////////////////////////////////////////////////////////// //
32 __gshared BodyContact[] contacts;
35 // ////////////////////////////////////////////////////////////////////////// //
36 // random number in range [-1,1]
37 public VFloat rnd () {
38 pragma(inline, true);
39 import std.random : uniform;
40 return cast(VFloat)uniform!"[]"(-VFloatNum!1, VFloatNum!1);
43 public VFloat rnd (VFloat lo, VFloat hi) {
44 pragma(inline, true);
45 import std.random : uniform;
46 return cast(VFloat)uniform!"[]"(lo, hi);
50 // ////////////////////////////////////////////////////////////////////////// //
51 __gshared BodyBase bomb = null;
53 __gshared VFloat timeStep = VFloatNum!1/VFloatNum!60;
54 __gshared int iterations = 10;
55 __gshared Vec2 gravity = Vec2(VFloatNum!0, -VFloatNum!10);
57 __gshared int demoIndex = 0;
58 __gshared string demoTitle;
60 __gshared bool onlyOneBomb = false;
61 __gshared int launchesLeft = 0;
63 __gshared World world;
64 shared static this () { world = new World(gravity, iterations); }
67 // ////////////////////////////////////////////////////////////////////////// //
68 void launchBomb (SimpleWindow sdwin) {
69 if (!onlyOneBomb || bomb is null) {
70 Vec2[] vtx;
71 foreach (int ang; 0..10) {
72 import std.math : cos, sin;
73 import std.random : uniform;
74 Vec2 v = Vec2(0.6*cos(deg2rad(360/10*ang))*uniform!"[]"(0.7, 1.3), 0.6*sin(deg2rad(360/10*ang))*uniform!"[]"(0.7, 1.3));
75 vtx ~= v;
77 bomb = new PolyBody(world, vtx, 50);
78 //bomb = PolyBody.Box(Vec2(VFloatNum!1, VFloatNum!1), VFloatNum!50);
79 bomb.friction = VFloatNum!(0.2);
81 bomb.setPosition(rnd(-VFloatNum!15, VFloatNum!15), VFloatNum!15);
82 bomb.rotation = rnd(-VFloatNum!(1.5), VFloatNum!(1.5));
83 bomb.velocity = bomb.position*(-VFloatNum!(1.5));
84 bomb.angularVelocity = rnd(-VFloatNum!20, VFloatNum!20);
85 import std.format : format;
86 sdwin.title = "%s -- bodies: %s".format(demoTitle, world.bodies.length);
90 // ////////////////////////////////////////////////////////////////////////// //
91 void drawBody (BodyBase bodyb) {
92 if (auto booody = cast(PolyBody)bodyb) {
93 if (booody is bomb) {
94 glColor3f(VFloatNum!(0.4), VFloatNum!(0.9), VFloatNum!(0.4));
95 } else {
96 glColor3f(VFloatNum!(0.8), VFloatNum!(0.8), VFloatNum!(0.9));
98 auto rmt = booody.rmat;
99 glBegin(GL_LINE_LOOP);
100 foreach (const ref vx; booody.verts) {
101 auto vp = booody.position+rmt*vx;
102 glVertex2f(vp.x, vp.y);
104 glEnd();
109 void drawJoint (Joint joint) {
110 auto b1 = joint.body0;
111 auto b2 = joint.body1;
113 auto r1 = b1.rmat;
114 auto r2 = b2.rmat;
116 Vec2 x1 = b1.position;
117 Vec2 p1 = x1+r1*joint.localAnchor1;
119 Vec2 x2 = b2.position;
120 Vec2 p2 = x2+r2*joint.localAnchor2;
122 glColor3f(VFloatNum!(0.5), VFloatNum!(0.5), VFloatNum!(0.8));
123 glBegin(GL_LINES);
124 glVertex2f(x1.x, x1.y);
125 glVertex2f(p1.x, p1.y);
126 glVertex2f(x2.x, x2.y);
127 glVertex2f(p2.x, p2.y);
128 glEnd();
132 void drawWorld () {
133 glMatrixMode(GL_MODELVIEW);
134 glLoadIdentity();
135 glTranslatef(VFloatNum!0, -VFloatNum!7, -VFloatNum!25);
137 // draw world
138 foreach (BodyBase b; world.bodies) b.drawBody();
139 foreach (Joint j; world.joints) j.drawJoint();
141 // draw contacts
143 import iv.glbinds;
144 glPointSize(VFloatNum!4);
145 glColor3f(VFloatNum!1, VFloatNum!0, VFloatNum!0);
146 glBegin(GL_POINTS);
147 foreach (const ref cxy; contacts) glVertex2f(cxy.position.x, cxy.position.y);
148 glEnd();
149 glPointSize(VFloatNum!1);
152 if (optDrawBVH) {
153 // draw BVH
154 world.drawBVH((Vec2 min, Vec2 max) {
155 glColor3f(VFloatNum!1, VFloatNum!1, VFloatNum!0);
156 glBegin(GL_LINE_LOOP);
157 glVertex2f(min.x, min.y);
158 glVertex2f(max.x, min.y);
159 glVertex2f(max.x, max.y);
160 glVertex2f(min.x, max.y);
161 glEnd();
167 // ////////////////////////////////////////////////////////////////////////// //
168 // demos
169 enum BoxW = VFloatNum!100;
170 enum BoxH = VFloatNum!20;
172 struct DemoInfo { string dsc; }
175 // single box
176 @DemoInfo("A Single Box") void demo1 () {
177 PolyBody.Box(world, Vec2(BoxW, BoxH), VFloat.max, (b, size) {
178 b.setPosition(VFloatNum!0, -VFloatNum!(0.5)*size.y);
180 PolyBody.Box(world, Vec2(VFloatNum!1, VFloatNum!1), VFloatNum!200, (b, size) {
181 b.setPosition(VFloatNum!0, VFloatNum!4);
186 // a simple pendulum
187 @DemoInfo("Simple Pendulum") void demo2 () {
188 auto b1 = PolyBody.Box(world, Vec2(BoxW, BoxH), VFloat.max, (b, size) {
189 b.friction = VFloatNum!(0.2);
190 b.setPosition(VFloatNum!0, -VFloatNum!(0.5)*size.y);
191 b.rotation = VFloatNum!0;
194 auto b2 = PolyBody.Box(world, Vec2(VFloatNum!1, VFloatNum!1), VFloatNum!100, (b, size) {
195 b.friction = VFloatNum!(0.2);
196 b.setPosition(VFloatNum!9, VFloatNum!11);
197 b.rotation = VFloatNum!0;
200 new Joint(world, b1, b2, Vec2(VFloatNum!0, VFloatNum!11));
204 // varying friction coefficients
205 @DemoInfo("Varying Friction Coefficients") void demo3 () {
206 PolyBody.Box(world, Vec2(BoxW, BoxH), VFloat.max, (b, size) {
207 b.setPosition(VFloatNum!0, -VFloatNum!(0.5)*size.y);
210 PolyBody.Box(world, Vec2(VFloatNum!13, VFloatNum!(0.25)), VFloat.max, (b, size) {
211 b.setPosition(-VFloatNum!2, VFloatNum!11);
212 b.rotation = -VFloatNum!(0.25);
215 PolyBody.Box(world, Vec2(VFloatNum!(0.25), VFloatNum!1), VFloat.max, (b, size) {
216 b.setPosition(VFloatNum!(5.25), VFloatNum!(9.5));
219 PolyBody.Box(world, Vec2(VFloatNum!13, VFloatNum!(0.25)), VFloat.max, (b, size) {
220 b.setPosition(VFloatNum!2, VFloatNum!7);
221 b.rotation = VFloatNum!(0.25);
224 PolyBody.Box(world, Vec2(VFloatNum!(0.25), VFloatNum!1), VFloat.max, (b, size) {
225 b.setPosition(-VFloatNum!(5.25), VFloatNum!(5.5));
228 PolyBody.Box(world, Vec2(VFloatNum!13, VFloatNum!(0.25)), VFloat.max, (b, size) {
229 b.setPosition(-VFloatNum!2, VFloatNum!3);
230 b.rotation = -VFloatNum!(0.25);
233 static immutable VFloat[5] frictions = [
234 VFloatNum!(0.75),
235 VFloatNum!(0.5),
236 VFloatNum!(0.35),
237 VFloatNum!(0.1),
238 VFloatNum!0
240 foreach (immutable idx, VFloat frc; frictions) {
241 PolyBody.Box(world, Vec2(VFloatNum!(0.5), VFloatNum!(0.5)), VFloatNum!25, (b, size) {
242 b.friction = frc;
243 b.setPosition(-VFloatNum!(7.5)+VFloatNum!2*cast(int)idx, VFloatNum!14);
249 // a vertical stack
250 @DemoInfo("Randomized Stacking") void demo4 () {
251 PolyBody.Box(world, Vec2(BoxW, BoxH), VFloat.max, (b, size) {
252 b.friction = VFloatNum!(0.2);
253 b.setPosition(VFloatNum!0, -VFloatNum!(0.5)*size.y);
254 b.rotation = VFloatNum!0;
257 foreach (int idx; 0..10) {
258 PolyBody.Box(world, Vec2(VFloatNum!1, VFloatNum!1), VFloatNum!1, (b, size) {
259 b.friction = VFloatNum!(0.2);
260 VFloat x = rnd(-VFloatNum!(0.1), VFloatNum!(0.1));
261 b.setPosition(x, VFloatNum!(0.51)+VFloatNum!(1.05)*idx);
267 // a pyramid
268 @DemoInfo("Pyramid Stacking") void demo5 () {
269 PolyBody.Box(world, Vec2(BoxW, BoxH), VFloat.max, (b, size) {
270 b.friction = VFloatNum!(0.2);
271 b.setPosition(VFloatNum!0, -VFloatNum!(0.5)*size.y);
272 b.rotation = VFloatNum!0;
275 Vec2 x = Vec2(-VFloatNum!6, VFloatNum!(0.75));
276 foreach (int idx; 0..12) {
277 auto y = x;
278 foreach (int j; idx..12) {
279 PolyBody.Box(world, Vec2(VFloatNum!1, VFloatNum!1), VFloatNum!10, (b, size) {
280 b.friction = VFloatNum!(0.2);
281 b.position = y;
283 y += Vec2(VFloatNum!(1.125), VFloatNum!0);
285 //x += Vec2(VFloatNum!(0.5625), VFloatNum!(1.125));
286 x += Vec2(VFloatNum!(0.5625), VFloatNum!2);
291 // a teeter
292 @DemoInfo("A Teeter") void demo6 () {
293 auto b1 = PolyBody.Box(world, Vec2(BoxW, BoxH), VFloat.max, (b, size) {
294 b.setPosition(VFloatNum!0, -VFloatNum!(0.5)*size.y);
297 auto b2 = PolyBody.Box(world, Vec2(VFloatNum!12, VFloatNum!(0.25)), VFloatNum!100, (b, size) {
298 b.setPosition(VFloatNum!0, VFloatNum!1);
301 new Joint(world, b1, b2, Vec2(VFloatNum!0, VFloatNum!1));
303 PolyBody.Box(world, Vec2(VFloatNum!(0.5), VFloatNum!(0.5)), VFloatNum!25, (b, size) {
304 b.setPosition(-VFloatNum!5, VFloatNum!2);
307 PolyBody.Box(world, Vec2(VFloatNum!(0.5), VFloatNum!(0.5)), VFloatNum!25, (b, size) {
308 b.setPosition(-VFloatNum!(5.5), VFloatNum!2);
311 PolyBody.Box(world, Vec2(VFloatNum!1, VFloatNum!1), VFloatNum!100, (b, size) {
312 b.setPosition(VFloatNum!(5.5), VFloatNum!15);
317 // a suspension bridge
318 @DemoInfo("A Suspension Bridge") void demo7 () {
319 import std.math : PI;
321 PolyBody.Box(world, Vec2(BoxW, BoxH), VFloat.max, (b, size) {
322 b.friction = VFloatNum!(0.2);
323 b.setPosition(VFloatNum!0, -VFloatNum!(0.5)*size.y);
324 b.rotation = VFloatNum!0;
327 enum numPlanks = 15;
328 VFloat mass = VFloatNum!50;
329 foreach (int idx; 0..numPlanks) {
330 PolyBody.Box(world, Vec2(VFloatNum!1, VFloatNum!(0.25)), mass, (b, size) {
331 b.friction = VFloatNum!(0.2);
332 b.setPosition(-VFloatNum!(8.5)+VFloatNum!(1.25)*idx, VFloatNum!5);
336 // tuning
337 VFloat frequencyHz = VFloatNum!2;
338 VFloat dampingRatio = VFloatNum!(0.7);
339 // frequency in radians
340 VFloat omega = VFloatNum!2*PI*frequencyHz;
341 // damping coefficient
342 VFloat d = VFloatNum!2*mass*dampingRatio*omega;
343 // spring stifness
344 VFloat k = mass*omega*omega;
345 // magic formulas
346 VFloat softnesss = VFloatNum!1/(d+timeStep*k);
347 VFloat biasFactorr = timeStep*k/(d+timeStep*k);
349 foreach (int idx; 0..numPlanks) {
350 new Joint(world, world.bodies[idx], world.bodies[idx+1], Vec2(-VFloatNum!(9.125)+VFloatNum!(1.25)*idx, VFloatNum!5), (j) {
351 j.softness = softnesss;
352 j.biasFactor = biasFactorr;
356 new Joint(world, world.bodies[numPlanks], world.bodies[0], Vec2(-VFloatNum!(9.125)+VFloatNum!(1.25)*numPlanks, VFloatNum!5), (j) {
357 j.softness = softnesss;
358 j.biasFactor = biasFactorr;
363 // dominos
364 @DemoInfo("Dominos") void demo8 () {
365 auto b1 = PolyBody.Box(world, Vec2(BoxW, BoxH), VFloat.max, (b, size) {
366 b.setPosition(VFloatNum!0, -VFloatNum!(0.5)*size.y);
369 PolyBody.Box(world, Vec2(VFloatNum!12, VFloatNum!(0.5)), VFloat.max, (b, size) {
370 b.setPosition(-VFloatNum!(1.5), VFloatNum!10);
373 foreach (int idx; 0..10) {
374 PolyBody.Box(world, Vec2(VFloatNum!(0.2), VFloatNum!2), VFloatNum!100, (b, size) {
375 b.setPosition(-VFloatNum!6+VFloatNum!1*idx, VFloatNum!(11.125));
376 b.friction = VFloatNum!(0.1);
380 PolyBody.Box(world, Vec2(VFloatNum!14, VFloatNum!(0.5)), VFloat.max, (b, size) {
381 b.setPosition(VFloatNum!1, VFloatNum!6);
382 b.rotation = VFloatNum!(0.3);
385 auto b2 = PolyBody.Box(world, Vec2(VFloatNum!(0.5), VFloatNum!3), VFloat.max, (b, size) {
386 b.setPosition(-VFloatNum!7, VFloatNum!4);
389 auto b3 = PolyBody.Box(world, Vec2(VFloatNum!12, VFloatNum!(0.25)), VFloatNum!20, (b, size) {
390 b.setPosition(-VFloatNum!(0.9), VFloatNum!1);
393 new Joint(world, b1, b3, Vec2(-VFloatNum!2, VFloatNum!1));
395 auto b4 = PolyBody.Box(world, Vec2(VFloatNum!(0.5), VFloatNum!(0.5)), VFloatNum!10, (b, size) {
396 b.setPosition(-VFloatNum!10, VFloatNum!15);
399 new Joint(world, b2, b4, Vec2(-VFloatNum!7, VFloatNum!15));
401 auto b5 = PolyBody.Box(world, Vec2(VFloatNum!2, VFloatNum!2), VFloatNum!12, (b, size) {
402 b.setPosition(VFloatNum!6, VFloatNum!(2.5));
403 b.friction = VFloatNum!(0.1);
406 new Joint(world, b1, b5, Vec2(VFloatNum!6, VFloatNum!(2.6)));
408 // box cap
409 auto b6 = PolyBody.Box(world, Vec2(VFloatNum!2, VFloatNum!(0.2)), VFloatNum!10, (b, size) {
410 b.setPosition(VFloatNum!6, VFloatNum!(3.6));
413 new Joint(world, b5, b6, Vec2(VFloatNum!7, VFloatNum!(3.5)));
417 // a multi-pendulum
418 @DemoInfo("Multi-pendulum") void demo9 () {
419 import std.math : PI;
421 auto b1 = PolyBody.Box(world, Vec2(BoxW, BoxH), VFloat.max, (b, size) {
422 b.friction = VFloatNum!(0.2);
423 b.setPosition(VFloatNum!0, -VFloatNum!(0.5)*size.y);
424 b.rotation = VFloatNum!0;
427 enum VFloat mass = VFloatNum!10;
429 // tuning
430 enum VFloat frequencyHz = VFloatNum!4;
431 enum VFloat dampingRatio = VFloatNum!(0.7);
432 // frequency in radians
433 enum VFloat omega = VFloatNum!2*PI*frequencyHz;
434 // damping coefficient
435 enum VFloat d = VFloatNum!2*mass*dampingRatio*omega;
436 // spring stiffness
437 enum VFloat k = mass*omega*omega;
438 // magic formulas
439 VFloat softnesss = VFloatNum!1/(d+timeStep*k);
440 VFloat biasFactorr = timeStep*k/(d+timeStep*k);
442 enum VFloat y = VFloatNum!12;
443 foreach (int idx; 0..15) {
444 auto x = Vec2(VFloatNum!(0.5)+idx, y);
445 auto b = PolyBody.Box(world, Vec2(VFloatNum!(0.75), VFloatNum!(0.25)), mass, (b, size) {
446 b.friction = VFloatNum!(0.2);
447 b.position = x;
448 b.rotation = VFloatNum!0;
451 new Joint(world, b1, b, Vec2(idx, y), (j) {
452 j.softness = softnesss;
453 j.biasFactor = biasFactorr;
456 b1 = b;
461 // ////////////////////////////////////////////////////////////////////////// //
462 __gshared bool paused = false;
463 __gshared bool doOneStep = false;
464 __gshared bool slowmo = false;
465 __gshared int slowmocount = 0;
468 bool setupDemo (int index, SimpleWindow w) {
469 import std.traits;
470 alias sms = getSymbolsByUDA!(mixin(__MODULE__), DemoInfo);
471 foreach (immutable idx, const memb; sms) {
472 if (index == idx) {
473 demoTitle = getUDAs!(memb, DemoInfo)[0].dsc;
474 w.title = demoTitle;
475 world.clear();
476 bomb = null;
477 memb();
478 demoIndex = index;
479 return true;
482 return false;
486 // ////////////////////////////////////////////////////////////////////////// //
487 void simulationStep (SimpleWindow sdwin) {
488 if (launchesLeft > 0) {
489 --launchesLeft;
490 auto oob = onlyOneBomb;
491 onlyOneBomb = false;
492 scope(exit) onlyOneBomb = oob;
493 launchBomb(sdwin);
496 glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
498 glMatrixMode(GL_MODELVIEW);
499 glLoadIdentity();
500 glTranslatef(VFloatNum!0, -VFloatNum!7, -VFloatNum!25);
502 contacts.length = 0;
503 contacts.assumeSafeAppend;
505 import core.time;
506 MonoTime stt;
507 if (optShowTimes) stt = MonoTime.currTime;
508 world.step(timeStep);
509 if (optShowTimes) {
510 auto tm = (MonoTime.currTime-stt).total!"msecs";
511 { import core.stdc.stdio; printf("step: %d msecs\n", cast(int)tm); }
513 sdwin.redrawOpenGlSceneNow();
517 // ////////////////////////////////////////////////////////////////////////// //
518 void showOpts () {
519 import std.stdio;
520 writeln("simulation options:");
521 writeln(" accumutate impulses: ", World.accumulateImpulses);
522 writeln(" position correction: ", World.positionCorrection);
523 writeln(" warm starting : ", World.warmStarting);
527 // ////////////////////////////////////////////////////////////////////////// //
528 void main () {
529 //setOpenGLContextVersion(3, 2); // up to GLSL 150
530 //openGLContextCompatible = false;
532 b2dlDrawContactsCB = delegate (in ref BodyContact ctx) {
533 contacts ~= ctx;
536 auto sdwin = new SimpleWindow(GWidth, GHeight, "Box2DLite Physics", OpenGlOptions.yes, Resizablity.fixedSize);
537 //sdwin.hideCursor();
539 sdwin.redrawOpenGlScene = delegate () {
540 glClear(GL_COLOR_BUFFER_BIT);
541 drawWorld();
544 sdwin.visibleForTheFirstTime = delegate () {
545 showOpts();
546 setupDemo(0, sdwin);
547 sdwin.setAsCurrentOpenGlContext(); // make this window active
548 glViewport(0, 0, GWidth, GHeight);
549 glMatrixMode(GL_PROJECTION);
550 glLoadIdentity();
551 oglPerspective(45.0, cast(VFloat)GWidth/cast(VFloat)GHeight, 0.1, 100.0);
552 sdwin.redrawOpenGlScene();
555 sdwin.eventLoop(1000/60,
556 delegate () {
557 if (sdwin.closed || world is null) return;
558 if (!paused) {
559 doOneStep = false;
560 if (slowmo) {
561 if (--slowmocount < 0) {
562 simulationStep(sdwin);
563 slowmocount = 10;
565 } else {
566 simulationStep(sdwin);
567 slowmocount = 0;
569 } else if (doOneStep) {
570 doOneStep = false;
571 simulationStep(sdwin);
574 delegate (KeyEvent event) {
575 if (sdwin.closed) return;
576 if (!event.pressed) return;
577 switch (event.key) {
578 case Key.Escape: sdwin.close(); break;
579 default:
582 delegate (MouseEvent event) {
584 delegate (dchar ch) {
585 if (ch == 'q') { sdwin.close(); return; }
586 if (ch == 'r') { setupDemo(demoIndex, sdwin); return; }
587 if (ch == ' ') { paused = !paused; return; }
588 if (ch == 's') { slowmo = !slowmo; return; }
589 if (ch >= '1' && ch <= '9') { setupDemo(ch-'1', sdwin); return; }
590 if (ch == '0') { setupDemo(10, sdwin); return; }
591 if (ch == 'a') { World.accumulateImpulses = !World.accumulateImpulses; showOpts(); return; }
592 if (ch == 'p') { World.positionCorrection = !World.positionCorrection; showOpts(); return; }
593 if (ch == 'w') { World.warmStarting = !World.warmStarting; showOpts(); return; }
594 if (ch == '\n' || ch == '\r') { launchBomb(sdwin); return; }
595 if (ch == '+') { setupDemo(demoIndex+1, sdwin); return; }
596 if (ch == '-') { if (demoIndex > 0) setupDemo(demoIndex-1, sdwin); return; }
597 if (ch == 'z') { doOneStep = true; return; }
598 if (ch == 'T') { optShowTimes = !optShowTimes; return; }
599 if (ch == 'A') { optDrawBVH = !optDrawBVH; return; }
600 if (ch == 'O') { onlyOneBomb = !onlyOneBomb; return; }
601 if (ch == '!') { launchesLeft = 1100; return; }