it works!
[b2ld.git] / xmain.d
blob6355cb42162e3a129a11f65a8f6c4dd25a69ad73
1 module xmain is aliced;
3 import iv.glbinds;
4 import arsd.color;
5 import arsd.png;
7 import b2dlite;
10 // Replaces gluPerspective. Sets the frustum to perspective mode.
11 // fovY - Field of vision in degrees in the y direction
12 // aspect - Aspect ratio of the viewport
13 // zNear - The near clipping distance
14 // zFar - The far clipping distance
15 void perspectiveGL (GLdouble fovY, GLdouble aspect, GLdouble zNear, GLdouble zFar) {
16 import std.math : tan;
17 //Very long (& in theory accurate!) version of Pi. Hopefully an optimizing compiler will replace references to this with the value!
18 enum GLdouble pi = 3.1415926535897932384626433832795;
19 GLdouble fW, fH; // Half of the size of the x and y clipping planes.
20 // Calculate the distance from 0 of the y clipping plane. Basically trig to calculate position of clipper at zNear.
21 // Note: tan( double ) uses radians but OpenGL works in degrees so we convert degrees to radians by dividing by 360 then multiplying by pi.
22 // Formula below corrected by Carsten Jurenz:
23 //fH = tan( (fovY / 2) / 180 * pi ) * zNear;
24 // Which can be reduced to:
25 fH = tan( fovY / 360 * pi ) * zNear;
26 // Calculate the distance from 0 of the x clipping plane based on the aspect ratio.
27 fW = fH * aspect;
28 // Finally call glFrustum, this is all gluPerspective does anyway! This is why we calculate half the distance between the clipping planes
29 // glFrustum takes an offset from zero for each clipping planes distance. (Saves 2 divides)
30 glFrustum( -fW, fW, -fH, fH, zNear, zFar );
34 enum GWidth = 800;
35 enum GHeight = 600;
38 //Body[200] bodies;
39 //Joint[100] joints;
41 Body bomb = null;
43 float timeStep = 1.0f/60.0f;
44 int iterations = 10;
45 Vec2 gravity = Vec2(0.0f, -10.0f);
47 int demoIndex = 0;
49 World world;
50 static this () { world = new World(gravity, iterations); }
53 static void LaunchBomb () {
54 if (bomb is null) {
55 bomb = new Body();
56 bomb.Set(Vec2(1.0f, 1.0f), 50.0f);
57 bomb.friction = 0.2f;
58 world.Add(bomb);
60 bomb.position.Set(Random(-15.0f, 15.0f), 15.0f);
61 bomb.rotation = Random(-1.5f, 1.5f);
62 bomb.velocity = -1.5f*bomb.position;
63 bomb.angularVelocity = Random(-20.0f, 20.0f);
67 static void DrawBody (Body body) {
68 auto R = Mat22(body.rotation);
69 Vec2 x = body.position;
70 Vec2 h = 0.5f*body.width;
72 Vec2 v1 = x+R*Vec2(-h.x, -h.y);
73 Vec2 v2 = x+R*Vec2( h.x, -h.y);
74 Vec2 v3 = x+R*Vec2( h.x, h.y);
75 Vec2 v4 = x+R*Vec2(-h.x, h.y);
77 if (body is bomb) {
78 glColor3f(0.4f, 0.9f, 0.4f);
79 } else {
80 glColor3f(0.8f, 0.8f, 0.9f);
83 glBegin(GL_LINE_LOOP);
84 glVertex2f(v1.x, v1.y);
85 glVertex2f(v2.x, v2.y);
86 glVertex2f(v3.x, v3.y);
87 glVertex2f(v4.x, v4.y);
88 glEnd();
92 static void DrawJoint (Joint joint) {
93 Body b1 = joint.body1;
94 Body b2 = joint.body2;
96 auto R1 = Mat22(b1.rotation);
97 auto R2 = Mat22(b2.rotation);
99 Vec2 x1 = b1.position;
100 Vec2 p1 = x1+R1*joint.localAnchor1;
102 Vec2 x2 = b2.position;
103 Vec2 p2 = x2+R2*joint.localAnchor2;
105 glColor3f(0.5f, 0.5f, 0.8f);
106 glBegin(GL_LINES);
107 glVertex2f(x1.x, x1.y);
108 glVertex2f(p1.x, p1.y);
109 glVertex2f(x2.x, x2.y);
110 glVertex2f(p2.x, p2.y);
111 glEnd();
115 // ////////////////////////////////////////////////////////////////////////// //
116 // demos
117 struct DemoInfo { string descr; }
119 {"A Single Box", Demo1},
120 {"Simple Pendulum", Demo2},
121 {"Varying Friction Coefficients", Demo3},
122 {"Randomized Stacking", Demo4},
123 {"Pyramid Stacking", Demo5},
124 {"A Teeter", Demo6},
125 {"A Suspension Bridge", Demo7},
126 {"Dominos", Demo8},
127 {"Multi-pendulum", Demo9},
130 // single box
131 @DemoInfo("A Single Box") void Demo1 () {
132 with (auto b = new Body()) {
133 b.Set(Vec2(100.0f, 20.0f), float.max);
134 b.position.Set(0.0f, -0.5f*b.width.y);
135 world.Add(b);
137 with (auto b = new Body()) {
138 b.Set(Vec2(1.0f, 1.0f), 200.0f);
139 b.position.Set(0.0f, 4.0f);
140 world.Add(b);
145 // a simple pendulum
146 @DemoInfo("Simple Pendulum") void Demo2 () {
147 auto b1 = new Body();
148 b1.Set(Vec2(100.0f, 20.0f), float.max);
149 b1.friction = 0.2f;
150 b1.position.Set(0.0f, -0.5f*b1.width.y);
151 b1.rotation = 0.0f;
152 world.Add(b1);
154 auto b2 = new Body();
155 b2.Set(Vec2(1.0f, 1.0f), 100.0f);
156 b2.friction = 0.2f;
157 b2.position.Set(9.0f, 11.0f);
158 b2.rotation = 0.0f;
159 world.Add(b2);
161 with (auto j = new Joint()) {
162 j.Set(b1, b2, Vec2(0.0f, 11.0f));
163 world.Add(j);
168 // varying friction coefficients
169 @DemoInfo("Varying Friction Coefficients") void Demo3 () {
170 with (auto b = new Body()) {
171 b.Set(Vec2(100.0f, 20.0f), float.max);
172 b.position.Set(0.0f, -0.5f*b.width.y);
173 world.Add(b);
176 with (auto b = new Body()) {
177 b.Set(Vec2(13.0f, 0.25f), float.max);
178 b.position.Set(-2.0f, 11.0f);
179 b.rotation = -0.25f;
180 world.Add(b);
183 with (auto b = new Body()) {
184 b.Set(Vec2(0.25f, 1.0f), float.max);
185 b.position.Set(5.25f, 9.5f);
186 world.Add(b);
189 with (auto b = new Body()) {
190 b.Set(Vec2(13.0f, 0.25f), float.max);
191 b.position.Set(2.0f, 7.0f);
192 b.rotation = 0.25f;
193 world.Add(b);
196 with (auto b = new Body()) {
197 b.Set(Vec2(0.25f, 1.0f), float.max);
198 b.position.Set(-5.25f, 5.5f);
199 world.Add(b);
202 with (auto b = new Body()) {
203 b.Set(Vec2(13.0f, 0.25f), float.max);
204 b.position.Set(-2.0f, 3.0f);
205 b.rotation = -0.25f;
206 world.Add(b);
209 static immutable float[5] frictions = [0.75f, 0.5f, 0.35f, 0.1f, 0.0f];
210 for (int i = 0; i < 5; ++i) {
211 with (auto b = new Body()) {
212 b.Set(Vec2(0.5f, 0.5f), 25.0f);
213 b.friction = frictions[i];
214 b.position.Set(-7.5f+2.0f*i, 14.0f);
215 world.Add(b);
221 // a vertical stack
222 @DemoInfo("Randomized Stacking") void Demo4 () {
223 with (auto b = new Body()) {
224 b.Set(Vec2(100.0f, 20.0f), float.max);
225 b.friction = 0.2f;
226 b.position.Set(0.0f, -0.5f*b.width.y);
227 b.rotation = 0.0f;
228 world.Add(b);
231 for (int i = 0; i < 10; ++i) {
232 with (auto b = new Body()) {
233 b.Set(Vec2(1.0f, 1.0f), 1.0f);
234 b.friction = 0.2f;
235 float x = Random(-0.1f, 0.1f);
236 b.position.Set(x, 0.51f+1.05f*i);
237 world.Add(b);
243 // a pyramid
244 @DemoInfo("Pyramid Stacking") void Demo5 () {
245 with (auto b = new Body()) {
246 b.Set(Vec2(100.0f, 20.0f), float.max);
247 b.friction = 0.2f;
248 b.position.Set(0.0f, -0.5f*b.width.y);
249 b.rotation = 0.0f;
250 world.Add(b);
253 Vec2 x = Vec2(-6.0f, 0.75f);
254 Vec2 y;
256 for (int i = 0; i < 12; ++i) {
257 y = x;
258 for (int j = i; j < 12; ++j) {
259 with (auto b = new Body()) {
260 b.Set(Vec2(1.0f, 1.0f), 10.0f);
261 b.friction = 0.2f;
262 b.position = y;
263 world.Add(b);
265 y += Vec2(1.125f, 0.0f);
267 //x += Vec2(0.5625f, 1.125f);
268 x += Vec2(0.5625f, 2.0f);
273 // a teeter
274 @DemoInfo("A Teeter") void Demo6 () {
275 Body b1 = new Body();
276 b1.Set(Vec2(100.0f, 20.0f), float.max);
277 b1.position.Set(0.0f, -0.5f*b1.width.y);
278 world.Add(b1);
280 Body b2 = new Body();
281 b2.Set(Vec2(12.0f, 0.25f), 100.0f);
282 b2.position.Set(0.0f, 1.0f);
283 world.Add(b2);
285 Body b3 = new Body();
286 b3.Set(Vec2(0.5f, 0.5f), 25.0f);
287 b3.position.Set(-5.0f, 2.0f);
288 world.Add(b3);
290 Body b4 = new Body();
291 b4.Set(Vec2(0.5f, 0.5f), 25.0f);
292 b4.position.Set(-5.5f, 2.0f);
293 world.Add(b4);
295 Body b5 = new Body();
296 b5.Set(Vec2(1.0f, 1.0f), 100.0f);
297 b5.position.Set(5.5f, 15.0f);
298 world.Add(b5);
300 with (auto j = new Joint()) {
301 j.Set(b1, b2, Vec2(0.0f, 1.0f));
302 world.Add(j);
307 // a suspension bridge
308 @DemoInfo("A Suspension Bridge") void Demo7 () {
309 with (auto b = new Body()) {
310 b.Set(Vec2(100.0f, 20.0f), float.max);
311 b.friction = 0.2f;
312 b.position.Set(0.0f, -0.5f*b.width.y);
313 b.rotation = 0.0f;
314 world.Add(b);
317 enum numPlanks = 15;
318 float mass = 50.0f;
320 for (int i = 0; i < numPlanks; ++i) {
321 auto b = new Body();
322 b.Set(Vec2(1.0f, 0.25f), mass);
323 b.friction = 0.2f;
324 b.position.Set(-8.5f+1.25f*i, 5.0f);
325 world.Add(b);
328 // tuning
329 float frequencyHz = 2.0f;
330 float dampingRatio = 0.7f;
332 // frequency in radians
333 float omega = 2.0f*k_pi*frequencyHz;
335 // damping coefficient
336 float d = 2.0f*mass*dampingRatio*omega;
338 // spring stifness
339 float k = mass*omega*omega;
341 // magic formulas
342 float softnesss = 1.0f/(d+timeStep*k);
343 float biasFactorr = timeStep*k/(d+timeStep*k);
345 for (int i = 0; i < numPlanks; ++i) {
346 auto j = new Joint();
347 j.Set(world.bodies[i], world.bodies[i+1], Vec2(-9.125f+1.25f*i, 5.0f));
348 j.softness = softnesss;
349 j.biasFactor = biasFactorr;
350 world.Add(j);
353 with (auto j = new Joint()) {
354 j.Set(world.bodies[numPlanks], world.bodies[0], Vec2(-9.125f+1.25f*numPlanks, 5.0f));
355 j.softness = softnesss;
356 j.biasFactor = biasFactorr;
357 world.Add(j);
362 // dominos
363 @DemoInfo("Dominos") void Demo8 () {
364 Body b1 = new Body();
365 b1.Set(Vec2(100.0f, 20.0f), float.max);
366 b1.position.Set(0.0f, -0.5f*b1.width.y);
367 world.Add(b1);
369 with (auto b = new Body()) {
370 b.Set(Vec2(12.0f, 0.5f), float.max);
371 b.position.Set(-1.5f, 10.0f);
372 world.Add(b);
375 for (int i = 0; i < 10; ++i) {
376 with (auto b = new Body()) {
377 b.Set(Vec2(0.2f, 2.0f), 10.0f);
378 b.position.Set(-6.0f+1.0f*i, 11.125f);
379 b.friction = 0.1f;
380 world.Add(b);
384 with (auto b = new Body()) {
385 b.Set(Vec2(14.0f, 0.5f), float.max);
386 b.position.Set(1.0f, 6.0f);
387 b.rotation = 0.3f;
388 world.Add(b);
391 Body b2 = new Body();
392 b2.Set(Vec2(0.5f, 3.0f), float.max);
393 b2.position.Set(-7.0f, 4.0f);
394 world.Add(b2);
396 Body b3 = new Body();
397 b3.Set(Vec2(12.0f, 0.25f), 20.0f);
398 b3.position.Set(-0.9f, 1.0f);
399 world.Add(b3);
401 with (auto j = new Joint()) {
402 j.Set(b1, b3, Vec2(-2.0f, 1.0f));
403 world.Add(j);
406 Body b4 = new Body();
407 b4.Set(Vec2(0.5f, 0.5f), 10.0f);
408 b4.position.Set(-10.0f, 15.0f);
409 world.Add(b4);
411 with (auto j = new Joint()) {
412 j.Set(b2, b4, Vec2(-7.0f, 15.0f));
413 world.Add(j);
416 Body b5 = new Body();
417 b5.Set(Vec2(2.0f, 2.0f), 20.0f);
418 b5.position.Set(6.0f, 2.5f);
419 b5.friction = 0.1f;
420 world.Add(b5);
422 with (auto j = new Joint()) {
423 j.Set(b1, b5, Vec2(6.0f, 2.6f));
424 world.Add(j);
427 Body b6 = new Body();
428 b6.Set(Vec2(2.0f, 0.2f), 10.0f);
429 b6.position.Set(6.0f, 3.6f);
430 world.Add(b6);
432 with (auto j = new Joint()) {
433 j.Set(b5, b6, Vec2(7.0f, 3.5f));
434 world.Add(j);
439 // a multi-pendulum
440 @DemoInfo("Multi-pendulum") void Demo9 () {
441 Body b1 = new Body();
442 b1.Set(Vec2(100.0f, 20.0f), float.max);
443 b1.friction = 0.2f;
444 b1.position.Set(0.0f, -0.5f*b1.width.y);
445 b1.rotation = 0.0f;
446 world.Add(b1);
448 float mass = 10.0f;
450 // tuning
451 float frequencyHz = 4.0f;
452 float dampingRatio = 0.7f;
454 // frequency in radians
455 float omega = 2.0f*k_pi*frequencyHz;
457 // damping coefficient
458 float d = 2.0f*mass*dampingRatio*omega;
460 // spring stiffness
461 float k = mass*omega*omega;
463 // magic formulas
464 float softnesss = 1.0f/(d+timeStep*k);
465 float biasFactorr = timeStep*k/(d+timeStep*k);
467 const float y = 12.0f;
469 for (int i = 0; i < 15; ++i) {
470 Vec2 x = Vec2(0.5f+i, y);
471 auto b = new Body();
472 b.Set(Vec2(0.75f, 0.25f), mass);
473 b.friction = 0.2f;
474 b.position = x;
475 b.rotation = 0.0f;
476 world.Add(b);
478 with (auto j = new Joint()) {
479 j.Set(b1, b, Vec2(i, y));
480 j.softness = softnesss;
481 j.biasFactor = biasFactorr;
482 world.Add(j);
485 b1 = b;
490 // ////////////////////////////////////////////////////////////////////////// //
491 __gshared bool paused = false;
492 __gshared bool slowmo = false;
493 __gshared int slowmocount = 0;
496 void InitDemo (int index) {
497 import std.traits;
498 alias sms = getSymbolsByUDA!(mixin(__MODULE__), DemoInfo);
499 foreach (immutable idx, auto memb; sms) {
500 //pragma(msg, idx, " ", memb);
501 if (index == idx) {
502 //mixin(memb~"();");
503 //{ import std.stdio; writeln(idx); }
504 world.Clear();
505 bomb = null;
506 memb();
507 demoIndex = index;
508 return;
512 foreach (string memb; __traits(allMembers, mixin(__MODULE__))) {
513 static if (is(typeof(__traits(getMember, mixin(__MODULE__), memb)))) {
514 static if (hasUDA!(__traits(getMember, mixin(__MODULE__), memb), DemoInfo)) {
515 pragma(msg, memb);
520 //demoIndex = -1;
524 void SimulationLoop () {
525 //static uint64_t t_start = 0;
526 static double accumulator = 0;
527 //uint64_t t_cur;
528 char[128] buf;
530 glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
533 snprintf(buf, sizeof(buf), "Demo %d: %s", demoIndex+1, demos[demoIndex].name);
534 DrawText(5, 15, buf);
535 DrawText(5, 45, "Keys: 1-9 Demos, Space to Launch the Bomb");
537 static char buffer[512];
538 sprintf(buffer, "(A)ccumulation %s", (World::accumulateImpulses ? "ON" : "OFF"));
539 DrawText(5, 75, buffer);
541 sprintf(buffer, "(P)osition Correction %s", (World::positionCorrection ? "ON" : "OFF"));
542 DrawText(5, 105, buffer);
544 sprintf(buffer, "(W)arm Starting %s", (World::warmStarting ? "ON" : "OFF"));
545 DrawText(5, 135, buffer);
548 glMatrixMode(GL_MODELVIEW);
549 glLoadIdentity();
550 glTranslatef(0.0f, -7.0f, -25.0f);
553 t_cur = k8clock_msec(NULL);
554 if (t_start == 0) t_start = t_cur;
556 //world.Step(timeStep);
557 accumulator += (t_cur-t_start)/1000.0;
558 t_start = t_cur;
559 // clamp
560 if (accumulator < 0.0) accumulator = 0.0;
561 else if (accumulator > 0.1) accumulator = 0.1;
562 // process physics
563 while (accumulator >= timeStep) {
564 if (!frameStepping) {
565 world.Step(timeStep);
566 } else {
567 if (canStep) {
568 world.Step(timeStep);
569 canStep = false;
572 accumulator -= timeStep;
575 world.Step(timeStep);
579 void drawWorld () {
580 glMatrixMode(GL_MODELVIEW);
581 glLoadIdentity();
582 glTranslatef(0.0f, -7.0f, -25.0f);
584 // draw world
585 foreach (Body b; world.bodies) b.DrawBody();
586 foreach (Joint j; world.joints) j.DrawJoint();
590 void main () {
591 //setOpenGLContextVersion(3, 2); // up to GLSL 150
592 //openGLContextCompatible = false;
594 auto sdwindow = new SimpleWindow(GWidth, GHeight, "Verlet Physics", OpenGlOptions.yes, Resizablity.fixedSize);
595 //sdwindow.hideCursor();
597 //sdwindow.closeQuery = delegate () { concmd("quit"); };
599 sdwindow.redrawOpenGlScene = delegate () {
600 glClear(GL_COLOR_BUFFER_BIT);
601 drawWorld();
603 world.render();
604 if (dragVertex !is null) {
605 glPointSize(6.0f);
606 glColor3f(1.0f, 1.0f, 0.0f);
607 glBegin(GL_POINTS);
608 glVertex2f(dragVertex.position.x, dragVertex.position.y);
609 glEnd();
612 //glFlush();
615 sdwindow.visibleForTheFirstTime = delegate () {
616 InitDemo(0);
617 sdwindow.setAsCurrentOpenGlContext(); // make this window active
618 glbindLoadFunctions();
619 // init matrices
621 glMatrixMode(GL_PROJECTION);
622 glLoadIdentity();
623 glOrtho(0, GWidth, GHeight, 0, -1, 1);
624 glMatrixMode(GL_MODELVIEW);
625 glLoadIdentity();
627 glViewport(0, 0, GWidth, GHeight);
628 glMatrixMode(GL_PROJECTION);
629 glLoadIdentity();
630 perspectiveGL(45.0, cast(float)GWidth/cast(float)GHeight, 0.1, 100.0);
631 sdwindow.redrawOpenGlScene();
634 sdwindow.eventLoop(cast(int)(1000.0f/60.0f),
635 delegate () {
636 if (sdwindow.closed || world is null) return;
637 if (!paused) {
638 if (slowmo) {
639 if (--slowmocount < 0) {
640 SimulationLoop();
641 slowmocount = 10;
643 } else {
644 SimulationLoop();
645 slowmocount = 0;
648 sdwindow.redrawOpenGlSceneNow();
650 delegate (KeyEvent event) {
651 if (sdwindow.closed) return;
652 if (!event.pressed) return;
653 switch (event.key) {
654 case Key.Escape: sdwindow.close(); break;
655 default:
658 delegate (MouseEvent event) {
660 if (event.type == MouseEventType.buttonPressed) {
661 if (event.button == MouseButton.left) dragVertex = world.FindVertex(event.x, event.y);
662 if (event.button == MouseButton.right) dragVertex = null;
663 } else if (event.type == MouseEventType.buttonReleased) {
664 if (event.button == MouseButton.left) dragVertex = null;
666 if (dragVertex !is null) dragVertex.position = vec2(cast(float)event.x, cast(float)event.y); // sets the position of the dragVertex to the mouse position to drag it around
669 delegate (dchar ch) {
670 if (ch == 'q') { sdwindow.close(); return; }
671 if (ch == 'r') { InitDemo(demoIndex); return; }
672 if (ch == ' ') { paused = !paused; return; }
673 if (ch == 's') { slowmo = !slowmo; return; }
674 if (ch >= '1' && ch <= '9') { InitDemo(ch-'1'); return; }
675 if (ch == '0') { InitDemo(10); return; }
676 if (ch == 'a') { World.accumulateImpulses = !World.accumulateImpulses; return; }
677 if (ch == 'p') { World.positionCorrection = !World.positionCorrection; return; }
678 if (ch == 'w') { World.warmStarting = !World.warmStarting; return; }
679 if (ch == '\n' || ch == '\r') { LaunchBomb(); }