1 /* 2 * Copyright (c) 2007-2013 Scott Lembcke and Howling Moon Software 3 * 4 * Permission is hereby granted, free of charge, to any person obtaining a copy 5 * of this software and associated documentation files (the "Software"), to deal 6 * in the Software without restriction, including without limitation the rights 7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 * copies of the Software, and to permit persons to whom the Software is 9 * furnished to do so, subject to the following conditions: 10 * 11 * The above copyright notice and this permission notice shall be included in 12 * all copies or substantial portions of the Software. 13 * 14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 * SOFTWARE. 21 */ 22 module demo.ChipmunkDemo; 23 24 import core.memory; 25 26 import core.stdc.stdio; 27 import core.stdc.stdlib; 28 29 import std.exception; 30 import std.stdio; 31 import std.string; 32 33 alias stderr = std.stdio.stderr; 34 35 import glad.gl.all; 36 import glad.gl.loader; 37 38 import glwtf.input; 39 import glwtf.window; 40 41 import deimos.glfw.glfw3; 42 43 import demo.dchip; 44 45 import demo.Bench; 46 import demo.ChipmunkDebugDraw; 47 import demo.ChipmunkDemoTextSupport; 48 import demo.glu; 49 import demo.types; 50 51 import demo.LogoSmash; 52 import demo.PyramidStack; 53 import demo.Plink; 54 import demo.Tumble; 55 import demo.PyramidTopple; 56 import demo.Planet; 57 import demo.Springies; 58 import demo.Pump; 59 import demo.TheoJansen; 60 import demo.Query; 61 import demo.OneWay; 62 import demo.Joints; 63 import demo.Tank; 64 import demo.Chains; 65 import demo.Crane; 66 import demo.ContactGraph; 67 import demo.Buoyancy; 68 import demo.Player; 69 import demo.Slice; 70 import demo.Convex; 71 import demo.Unicycle; 72 import demo.Sticky; 73 import demo.Shatter; 74 import demo.RollBall; 75 76 ChipmunkDemo[] demo_list; 77 shared static this() 78 { 79 demo_list = [ 80 LogoSmash, 81 PyramidStack, 82 Plink, 83 BouncyHexagons, 84 Tumble, 85 PyramidTopple, 86 Planet, 87 Springies, 88 Pump, 89 TheoJansen, 90 Query, 91 OneWay, 92 Joints, 93 Tank, 94 Chains, 95 Crane, 96 ContactGraph, 97 Buoyancy, 98 Player, 99 Slice, 100 Convex, 101 Unicycle, 102 Sticky, 103 Shatter, 104 RollBall, 105 ]; 106 } 107 108 alias ChipmunkDemoInitFunc = cpSpace* function(); 109 alias ChipmunkDemoUpdateFunc = void function(cpSpace* space, double dt); 110 alias ChipmunkDemoDrawFunc = void function(cpSpace* space); 111 alias ChipmunkDemoDestroyFunc = void function(cpSpace* space); 112 113 __gshared GLFWwindow* window; 114 115 struct ChipmunkDemo 116 { 117 string name; 118 double timestep = 0; 119 120 ChipmunkDemoInitFunc initFunc; 121 ChipmunkDemoUpdateFunc updateFunc; 122 ChipmunkDemoDrawFunc drawFunc; 123 124 ChipmunkDemoDestroyFunc destroyFunc; 125 } 126 127 cpFloat frand() 128 { 129 return cast(cpFloat)rand() / cast(cpFloat)RAND_MAX; 130 } 131 132 cpVect frand_unit_circle() 133 { 134 cpVect v = cpv(frand() * 2.0f - 1.0f, frand() * 2.0f - 1.0f); 135 return (cpvlengthsq(v) < 1.0f ? v : frand_unit_circle()); 136 } 137 138 enum GRABABLE_MASK_BIT = (1 << 31); 139 enum NOT_GRABABLE_MASK = (~GRABABLE_MASK_BIT); 140 141 void ChipmunkDemoDefaultDrawImpl(cpSpace* space); 142 void ChipmunkDemoFreeSpaceChildren(cpSpace* space); 143 144 ChipmunkDemo* demos; 145 size_t demo_count = 0; 146 size_t demo_index = 0; 147 148 cpBool paused = cpFalse; 149 cpBool step = cpFalse; 150 151 cpSpace* space; 152 153 double Accumulator = 0.0; 154 double LastTime = 0.0; 155 int ChipmunkDemoTicks = 0; 156 double ChipmunkDemoTime = 0; 157 158 cpVect ChipmunkDemoMouse; 159 cpBool ChipmunkDemoRightClick = cpFalse; 160 cpBool ChipmunkDemoRightDown = cpFalse; 161 cpVect ChipmunkDemoKeyboard; 162 163 cpBody* mouse_body = null; 164 cpConstraint* mouse_joint = null; 165 166 char[] ChipmunkDemoMessageString; 167 168 cpVect translate = { 0, 0 }; 169 cpFloat scale = 1.0; 170 171 void ShapeFreeWrap(cpSpace* space, cpShape* shape, void* unused) 172 { 173 cpSpaceRemoveShape(space, shape); 174 cpShapeFree(shape); 175 } 176 177 void PostShapeFree(cpShape* shape, cpSpace* space) 178 { 179 cpSpaceAddPostStepCallback(space, safeCast!cpPostStepFunc(&ShapeFreeWrap), shape, null); 180 } 181 182 void ConstraintFreeWrap(cpSpace* space, cpConstraint* constraint, void* unused) 183 { 184 cpSpaceRemoveConstraint(space, constraint); 185 cpConstraintFree(constraint); 186 } 187 188 void PostConstraintFree(cpConstraint* constraint, cpSpace* space) 189 { 190 cpSpaceAddPostStepCallback(space, safeCast!cpPostStepFunc(&ConstraintFreeWrap), constraint, null); 191 } 192 193 void BodyFreeWrap(cpSpace* space, cpBody* body_, void* unused) 194 { 195 cpSpaceRemoveBody(space, body_); 196 cpBodyFree(body_); 197 } 198 199 void PostBodyFree(cpBody* body_, cpSpace* space) 200 { 201 cpSpaceAddPostStepCallback(space, safeCast!cpPostStepFunc(&BodyFreeWrap), body_, null); 202 } 203 204 // Safe and future proof way to remove and free all objects that have been added to the space. 205 void ChipmunkDemoFreeSpaceChildren(cpSpace* space) 206 { 207 // Must remove these BEFORE freeing the body_ or you will access dangling pointers. 208 cpSpaceEachShape(space, safeCast!cpSpaceShapeIteratorFunc(&PostShapeFree), space); 209 cpSpaceEachConstraint(space, safeCast!cpSpaceConstraintIteratorFunc(&PostConstraintFree), space); 210 211 cpSpaceEachBody(space, safeCast!cpSpaceBodyIteratorFunc(&PostBodyFree), space); 212 } 213 214 void ChipmunkDemoDefaultDrawImpl(cpSpace* space) 215 { 216 ChipmunkDebugDrawShapes(space); 217 ChipmunkDebugDrawConstraints(space); 218 ChipmunkDebugDrawCollisionPoints(space); 219 } 220 221 void DrawInstructions() 222 { 223 ChipmunkDemoTextDrawString(cpv(-300, 220), 224 "Controls:\n" 225 "A - * Switch demos. (return restarts)\n" 226 "Use the mouse to grab objects.\n" 227 ); 228 } 229 230 int max_arbiters = 0; 231 int max_points = 0; 232 int max_constraints = 0; 233 234 void DrawInfo() 235 { 236 int arbiters = space.arbiters.num; 237 int points = 0; 238 239 for (int i = 0; i < arbiters; i++) 240 points += (cast(cpArbiter*)(space.arbiters.arr[i])).numContacts; 241 242 int constraints = (space.constraints.num + points) * space.iterations; 243 244 max_arbiters = arbiters > max_arbiters ? arbiters : max_arbiters; 245 max_points = points > max_points ? points : max_points; 246 max_constraints = constraints > max_constraints ? constraints : max_constraints; 247 248 char[1024] buffer = 0; 249 string format = 250 "Arbiters: %d (%d) - " 251 "Contact Points: %d (%d)\n" 252 "Other Constraints: %d, Iterations: %d\n" 253 "Constraints x Iterations: %d (%d)\n" 254 "Time:% 5.2fs, KE:% 5.2e\0"; 255 256 cpArray* bodies = space.bodies; 257 cpFloat ke = 0.0f; 258 259 for (int i = 0; i < bodies.num; i++) 260 { 261 cpBody* body_ = cast(cpBody*)bodies.arr[i]; 262 263 if (body_.m == INFINITY || body_.i == INFINITY) 264 continue; 265 266 ke += body_.m * cpvdot(body_.v, body_.v) + body_.i * body_.w * body_.w; 267 } 268 269 sprintf(buffer.ptr, format.ptr, 270 arbiters, max_arbiters, 271 points, max_points, 272 space.constraints.num, space.iterations, 273 constraints, max_constraints, 274 ChipmunkDemoTime, (ke < 1e-10f ? 0.0f : ke) 275 ); 276 277 ChipmunkDemoTextDrawString(cpv(0, 220), buffer); 278 } 279 280 char PrintStringBuffer[1024 * 8] = 0; 281 size_t PrintStringCursor; 282 283 void ChipmunkDemoPrintString(Args...)(string fmt, Args args) 284 { 285 ChipmunkDemoMessageString = PrintStringBuffer[]; 286 PrintStringCursor += sformat(PrintStringBuffer[PrintStringCursor .. $], fmt, args).length; 287 } 288 289 void Tick(double dt) 290 { 291 if (!paused || step) 292 { 293 PrintStringBuffer[0] = 0; 294 PrintStringCursor = 0; 295 296 // Completely reset the renderer only at the beginning of a tick. 297 // That way it can always display at least the last ticks' debug drawing. 298 ChipmunkDebugDrawClearRenderer(); 299 ChipmunkDemoTextClearRenderer(); 300 301 cpVect new_point = cpvlerp(mouse_body.p, ChipmunkDemoMouse, 0.25f); 302 mouse_body.v = cpvmult(cpvsub(new_point, mouse_body.p), 60.0f); 303 mouse_body.p = new_point; 304 305 demos[demo_index].updateFunc(space, dt); 306 307 ChipmunkDemoTicks++; 308 ChipmunkDemoTime += dt; 309 310 step = cpFalse; 311 ChipmunkDemoRightDown = cpFalse; 312 313 ChipmunkDemoTextDrawString(cpv(-300, -200), ChipmunkDemoMessageString); 314 } 315 } 316 317 void Update() 318 { 319 double time = glfwGetTime(); 320 double dt = time - LastTime; 321 322 if (dt > 0.2) 323 dt = 0.2; 324 325 double fixed_dt = demos[demo_index].timestep; 326 327 for (Accumulator += dt; Accumulator > fixed_dt; Accumulator -= fixed_dt) 328 { 329 Tick(fixed_dt); 330 } 331 332 LastTime = time; 333 } 334 335 void Display() 336 { 337 glMatrixMode(GL_MODELVIEW); 338 glLoadIdentity(); 339 glTranslatef(cast(GLfloat)translate.x, cast(GLfloat)translate.y, 0.0f); 340 glScalef(cast(GLfloat)scale, cast(GLfloat)scale, 1.0f); 341 342 Update(); 343 344 ChipmunkDebugDrawPushRenderer(); 345 demos[demo_index].drawFunc(space); 346 347 // Highlight the shape under the mouse because it looks neat. 348 cpShape* nearest = cpSpaceNearestPointQueryNearest(space, ChipmunkDemoMouse, 0.0f, CP_ALL_LAYERS, CP_NO_GROUP, null); 349 350 if (nearest) 351 ChipmunkDebugDrawShape(nearest, RGBAColor(1.0f, 0.0f, 0.0f, 1.0f), LAColor(0.0f, 0.0f)); 352 353 // Draw the renderer contents and reset it back to the last tick's state. 354 ChipmunkDebugDrawFlushRenderer(); 355 ChipmunkDebugDrawPopRenderer(); 356 357 ChipmunkDemoTextPushRenderer(); 358 359 // Now render all the UI text. 360 DrawInstructions(); 361 DrawInfo(); 362 363 glMatrixMode(GL_MODELVIEW); 364 glPushMatrix(); 365 { 366 // Draw the text at fixed positions, 367 // but save the drawing matrix for the mouse picking 368 glLoadIdentity(); 369 370 ChipmunkDemoTextFlushRenderer(); 371 ChipmunkDemoTextPopRenderer(); 372 } 373 glPopMatrix(); 374 375 glfwSwapBuffers(window); 376 glClear(GL_COLOR_BUFFER_BIT); 377 } 378 379 extern(C) void Reshape(GLFWwindow* window, int width, int height) 380 { 381 glViewport(0, 0, width, height); 382 383 float scale = cast(float)cpfmin(width / 640.0, height / 480.0); 384 float hw = width * (0.5f / scale); 385 float hh = height * (0.5f / scale); 386 387 ChipmunkDebugDrawPointLineScale = scale; 388 glLineWidth(cast(GLfloat)scale); 389 390 glMatrixMode(GL_PROJECTION); 391 glLoadIdentity(); 392 gluOrtho2D(-hw, hw, -hh, hh); 393 } 394 395 char[] DemoTitle(size_t index) 396 { 397 static char[1024] title; 398 title[] = 0; 399 sformat(title, "Demo(%s): %s", cast(char)('a' + index), demos[demo_index].name); 400 return title; 401 } 402 403 void RunDemo(size_t index) 404 { 405 srand(45073); 406 407 demo_index = index; 408 409 ChipmunkDemoTicks = 0; 410 ChipmunkDemoTime = 0.0; 411 Accumulator = 0.0; 412 LastTime = glfwGetTime(); 413 414 mouse_joint = null; 415 ChipmunkDemoMessageString = "\0".dup; 416 max_arbiters = 0; 417 max_points = 0; 418 max_constraints = 0; 419 space = demos[demo_index].initFunc(); 420 421 enforce(window !is null); 422 glfwSetWindowTitle(window, DemoTitle(index).toStringz); 423 } 424 425 extern(C) void Keyboard(GLFWwindow* window, int key, int scancode, int state, int modifier) 426 { 427 if (state != GLFW_REPEAT) // we ignore repeat 428 switch (key) 429 { 430 case GLFW_KEY_UP: 431 ChipmunkDemoKeyboard.y += (state == GLFW_PRESS ? 1.0 : -1.0); 432 break; 433 434 case GLFW_KEY_DOWN: 435 ChipmunkDemoKeyboard.y += (state == GLFW_PRESS ? -1.0 : 1.0); 436 break; 437 438 case GLFW_KEY_RIGHT: 439 ChipmunkDemoKeyboard.x += (state == GLFW_PRESS ? 1.0 : -1.0); 440 break; 441 442 case GLFW_KEY_LEFT: 443 ChipmunkDemoKeyboard.x += (state == GLFW_PRESS ? -1.0 : 1.0); 444 break; 445 446 default: 447 break; 448 } 449 450 if (key == GLFW_KEY_ESCAPE && (state == GLFW_PRESS || state == GLFW_REPEAT)) 451 glfwSetWindowShouldClose(window, true); 452 453 // We ignore release for these next keys. 454 if (state == GLFW_RELEASE) 455 return; 456 457 int index = key - GLFW_KEY_A; 458 459 if (0 <= index && index < demo_count) 460 { 461 demos[demo_index].destroyFunc(space); 462 RunDemo(index); 463 } 464 else if (key == ' ') 465 { 466 demos[demo_index].destroyFunc(space); 467 RunDemo(demo_index); 468 } 469 else if (key == '`') 470 { 471 paused = !paused; 472 } 473 else if (key == '1') 474 { 475 step = cpTrue; 476 } 477 else if (key == '\\') 478 { 479 glDisable(GL_LINE_SMOOTH); 480 glDisable(GL_POINT_SMOOTH); 481 } 482 483 GLfloat translate_increment = 50.0f / cast(GLfloat)scale; 484 GLfloat scale_increment = 1.2f; 485 486 if (key == '5') 487 { 488 translate.x = 0.0f; 489 translate.y = 0.0f; 490 scale = 1.0f; 491 } 492 else if (key == '4') 493 { 494 translate.x += translate_increment; 495 } 496 else if (key == '6') 497 { 498 translate.x -= translate_increment; 499 } 500 else if (key == '2') 501 { 502 translate.y += translate_increment; 503 } 504 else if (key == '8') 505 { 506 translate.y -= translate_increment; 507 } 508 else if (key == '7') 509 { 510 scale /= scale_increment; 511 } 512 else if (key == '9') 513 { 514 scale *= scale_increment; 515 } 516 } 517 518 cpVect MouseToSpace(double x, double y) 519 { 520 GLdouble model[16] = 0; 521 glGetDoublev(GL_MODELVIEW_MATRIX, model.ptr); 522 523 GLdouble proj[16] = 0; 524 glGetDoublev(GL_PROJECTION_MATRIX, proj.ptr); 525 526 GLint view[4] = 0; 527 glGetIntegerv(GL_VIEWPORT, view.ptr); 528 529 int ww, wh; 530 glfwGetWindowSize(window, &ww, &wh); 531 532 GLdouble mx = 0, my = 0, mz = 0; 533 gluUnProject(x, wh - y, 0.0f, model, proj, view, &mx, &my, &mz); 534 535 return cpv(mx, my); 536 } 537 538 extern(C) void Mouse(GLFWwindow* window, double x, double y) 539 { 540 ChipmunkDemoMouse = MouseToSpace(x, y); 541 } 542 543 extern(C) void Click(GLFWwindow* window, int button, int state, int mods) 544 { 545 if (button == GLFW_MOUSE_BUTTON_1) 546 { 547 if (state == GLFW_PRESS) 548 { 549 cpShape* shape = cpSpacePointQueryFirst(space, ChipmunkDemoMouse, GRABABLE_MASK_BIT, CP_NO_GROUP); 550 551 if (shape && !cpBodyIsStatic(shape.body_)) 552 { 553 cpBody* body_ = shape.body_; 554 mouse_joint = cpPivotJointNew2(mouse_body, body_, cpvzero, cpBodyWorld2Local(body_, ChipmunkDemoMouse)); 555 mouse_joint.maxForce = 50000.0f; 556 mouse_joint.errorBias = cpfpow(1.0f - 0.15f, 60.0f); 557 cpSpaceAddConstraint(space, mouse_joint); 558 } 559 } 560 else if (mouse_joint) 561 { 562 cpSpaceRemoveConstraint(space, mouse_joint); 563 cpConstraintFree(mouse_joint); 564 mouse_joint = null; 565 } 566 } 567 else if (button == GLFW_MOUSE_BUTTON_2) 568 { 569 ChipmunkDemoRightDown = ChipmunkDemoRightClick = (state == GLFW_PRESS); 570 } 571 } 572 573 extern(C) void WindowClose(GLFWwindow* window) 574 { 575 glfwTerminate(); 576 glfwSetWindowShouldClose(window, true); 577 } 578 579 void SetupGL() 580 { 581 ChipmunkDebugDrawInit(); 582 ChipmunkDemoTextInit(); 583 584 glClearColor(52.0f / 255.0f, 62.0f / 255.0f, 72.0f / 255.0f, 1.0f); 585 glClear(GL_COLOR_BUFFER_BIT); 586 587 glEnable(GL_LINE_SMOOTH); 588 glEnable(GL_POINT_SMOOTH); 589 590 glHint(GL_LINE_SMOOTH_HINT, GL_DONT_CARE); 591 glHint(GL_POINT_SMOOTH_HINT, GL_DONT_CARE); 592 593 glEnable(GL_BLEND); 594 glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); 595 } 596 597 void TimeTrial(int index, int count) 598 { 599 space = demos[index].initFunc(); 600 601 double start_time = glfwGetTime(); 602 double dt = demos[index].timestep; 603 604 for (int i = 0; i < count; i++) 605 demos[index].updateFunc(space, dt); 606 607 double end_time = glfwGetTime(); 608 609 demos[index].destroyFunc(space); 610 611 printf("Time(%c) = %8.2f ms (%s)\n", index + 'a', (end_time - start_time) * 1e3f, demos[index].name.toStringz); 612 } 613 614 enum WindowMode 615 { 616 fullscreen, 617 windowed, 618 } 619 620 /* Wrapper around the glwtf API. */ 621 Window createWindow(string windowName, WindowMode windowMode, int width, int height) 622 { 623 auto window = new Window(); 624 auto monitor = windowMode == WindowMode.fullscreen ? glfwGetPrimaryMonitor() : null; 625 bool forward_compat = false; 626 auto cv = window.create_highest_available_context(width, height, windowName, monitor, null, GLFW_OPENGL_COMPAT_PROFILE, forward_compat); 627 return window; 628 } 629 630 void on_glfw_error(int code, string msg) 631 { 632 stderr.writefln("Error (%s): %s", code, msg); 633 } 634 635 int main(string[] args) 636 { 637 GC.disable(); 638 scope (exit) 639 GC.enable(); 640 641 // Segment/segment collisions need to be explicitly enabled currently. 642 // This will becoume enabled by default in future versions of Chipmunk. 643 cpEnableSegmentToSegmentCollisions(); 644 645 demos = demo_list.ptr; 646 demo_count = demo_list.length; 647 int trial = 0; 648 649 foreach (arg; args[1 .. $]) 650 { 651 if (arg == "-bench") 652 { 653 demos = cast(ChipmunkDemo*)bench_list.ptr; 654 demo_count = bench_count; 655 } 656 else 657 if (arg == "-trial") 658 { 659 trial = 1; 660 } 661 } 662 663 if (trial) 664 { 665 cpAssertHard(glfwInit(), "Error initializing GLFW."); 666 667 // sleep(1); 668 for (int i = 0; i < demo_count; i++) 669 TimeTrial(i, 1000); 670 671 // time_trial('d' - 'a', 10000); 672 return 0; 673 } 674 else 675 { 676 mouse_body = cpBodyNew(INFINITY, INFINITY); 677 678 // initialize glwf 679 auto res = glfwInit(); 680 enforce(res, format("glfwInit call failed with return code: '%s'", res)); 681 scope(exit) 682 glfwTerminate(); 683 684 int width = 640; 685 int height = 480; 686 687 window = enforce(createWindow("Hello World", WindowMode.windowed, width, height).window, 688 "glfwCreateWindow call failed."); 689 690 register_glfw_error_callback(&on_glfw_error); 691 692 // Make the window's context current 693 glfwMakeContextCurrent(window); 694 695 // Load all OpenGL function pointers via glad. 696 enforce(gladLoadGL()); 697 698 // Support only GL 3x+ 699 enforce(GLVersion.major >= 3); 700 701 SetupGL(); 702 703 // glfw3 doesn't want to automatically do this the first time the window is shown 704 Reshape(window, 640, 480); 705 706 glfwSetWindowSizeCallback(window, &Reshape); 707 glfwSetKeyCallback(window, &Keyboard); 708 709 glfwSetCursorPosCallback(window, &Mouse); 710 glfwSetMouseButtonCallback(window, &Click); 711 712 RunDemo(demo_index); 713 714 /* Loop until the user closes the window */ 715 while (!glfwWindowShouldClose(window)) 716 { 717 /* Poll for and process events */ 718 glfwPollEvents(); 719 720 /* Render here */ 721 Display(); 722 } 723 } 724 725 return 0; 726 }