1 /*{# Copyright (c) 2012 Turbulenz Limited #}*/ 2 /* 3 * @title: 2D Physics constraints 4 * @description: 5 * This sample shows how to create each of the 2D physics constraints 6 * (point, weld, distance, line, angle, motor, pulley and custom). 7 * Each object in the scene can be manipulated with the mouse to see how the constraints work. 8 */ 9 /*{{ javascript("jslib/observer.js") }}*/ 10 /*{{ javascript("jslib/requesthandler.js") }}*/ 11 /*{{ javascript("jslib/utilities.js") }}*/ 12 /*{{ javascript("jslib/services/turbulenzservices.js") }}*/ 13 /*{{ javascript("jslib/services/turbulenzbridge.js") }}*/ 14 /*{{ javascript("jslib/services/gamesession.js") }}*/ 15 /*{{ javascript("jslib/services/mappingtable.js") }}*/ 16 /*{{ javascript("jslib/shadermanager.js") }}*/ 17 /*{{ javascript("jslib/physics2ddevice.js") }}*/ 18 /*{{ javascript("jslib/draw2d.js") }}*/ 19 /*{{ javascript("jslib/boxtree.js") }}*/ 20 /*{{ javascript("jslib/physics2ddebugdraw.js") }}*/ 21 /*{{ javascript("jslib/fontmanager.js") }}*/ 22 /*{{ javascript("scripts/htmlcontrols.js") }}*/ 23 /*global TurbulenzEngine: true */ 24 /*global TurbulenzServices: false */ 25 /*global RequestHandler: false */ 26 /*global Physics2DDevice: false */ 27 /*global Draw2D: false */ 28 /*global FontManager: false */ 29 /*global ShaderManager: false */ 30 /*global Physics2DDebugDraw: false */ 31 /*global HTMLControls: false */ 32 TurbulenzEngine.onload = function onloadFn() { 33 //========================================================================== 34 // HTML Controls 35 //========================================================================== 36 var htmlControls; 37 38 var elasticConstraints = false; 39 var frequency = 1; 40 var damping = 0.1; 41 42 //========================================================================== 43 // Turbulenz Initialization 44 //========================================================================== 45 var graphicsDevice = TurbulenzEngine.createGraphicsDevice({}); 46 var mathDevice = TurbulenzEngine.createMathDevice({}); 47 var requestHandler = RequestHandler.create({}); 48 49 var fontManager = FontManager.create(graphicsDevice, requestHandler); 50 var shaderManager = ShaderManager.create(graphicsDevice, requestHandler); 51 52 var font, shader, gameSession; 53 function sessionCreated() { 54 TurbulenzServices.createMappingTable(requestHandler, gameSession, function (mappingTable) { 55 var urlMapping = mappingTable.urlMapping; 56 var assetPrefix = mappingTable.assetPrefix; 57 shaderManager.setPathRemapping(urlMapping, assetPrefix); 58 fontManager.setPathRemapping(urlMapping, assetPrefix); 59 fontManager.load('fonts/hero.fnt', function (fontObject) { 60 font = fontObject; 61 }); 62 shaderManager.load('shaders/font.cgfx', function (shaderObject) { 63 shader = shaderObject; 64 }); 65 }); 66 } 67 gameSession = TurbulenzServices.createGameSession(requestHandler, sessionCreated); 68 69 //========================================================================== 70 // Physics2D/Draw2D (Use Draw2D to define viewport scalings) 71 //========================================================================== 72 // set up. 73 var phys2D = Physics2DDevice.create(); 74 75 // size of physics stage. 76 var stageWidth = 40; 77 var stageHeight = 20; 78 79 var draw2D = Draw2D.create({ 80 graphicsDevice: graphicsDevice 81 }); 82 var debug = Physics2DDebugDraw.create({ 83 graphicsDevice: graphicsDevice 84 }); 85 86 // Configure draw2D viewport to the physics stage. 87 // As well as the physics2D debug-draw viewport. 88 draw2D.configure({ 89 viewportRectangle: [0, 0, stageWidth, stageHeight], 90 scaleMode: 'scale' 91 }); 92 debug.setPhysics2DViewport([0, 0, stageWidth, stageHeight]); 93 94 var world = phys2D.createWorld({ 95 gravity: [0, 20] 96 }); 97 98 // Create a static body at (0, 0) with no rotation 99 // which we add to the world to use as the first body 100 // in hand constraint. We set anchor for this body 101 // as the cursor position in physics coordinates. 102 var staticReferenceBody = phys2D.createRigidBody({ 103 type: 'static' 104 }); 105 world.addRigidBody(staticReferenceBody); 106 var handConstraint = null; 107 108 function reset() { 109 // Remove all bodies and constraints from world. 110 world.clear(); 111 handConstraint = null; 112 113 // Create a static body around the stage to stop objects leaving the viewport. 114 // And walls between each constraint section. 115 var border = phys2D.createRigidBody({ 116 type: 'static' 117 }); 118 119 var thickness = 0.01; 120 var i; 121 for (i = 0; i <= 4; i += 1) { 122 var x = (stageWidth / 4) * i; 123 border.addShape(phys2D.createPolygonShape({ 124 vertices: phys2D.createRectangleVertices(x - thickness, 0, x + thickness, stageHeight) 125 })); 126 } 127 for (i = 0; i <= 2; i += 1) { 128 var y = (stageHeight / 2) * i; 129 border.addShape(phys2D.createPolygonShape({ 130 vertices: phys2D.createRectangleVertices(0, y - thickness, stageWidth, y + thickness) 131 })); 132 } 133 134 world.addRigidBody(border); 135 136 function circle(x, y, radius, pinned) { 137 var body = phys2D.createRigidBody({ 138 shapes: [ 139 phys2D.createCircleShape({ 140 radius: radius 141 }) 142 ], 143 position: [x, y] 144 }); 145 world.addRigidBody(body); 146 147 if (pinned) { 148 var pin = phys2D.createPointConstraint({ 149 bodyA: staticReferenceBody, 150 bodyB: body, 151 anchorA: [x, y], 152 anchorB: [0, 0], 153 userData: "pin" 154 }); 155 world.addConstraint(pin); 156 } 157 158 return body; 159 } 160 161 var bodyA, bodyB, worldAnchor; 162 163 // ------------------------------------ 164 // Point Constraint 165 bodyA = circle(3.3, 5, 1); 166 bodyB = circle(6.6, 5, 1); 167 168 worldAnchor = [5, 5]; 169 var pointConstraint = phys2D.createPointConstraint({ 170 bodyA: bodyA, 171 bodyB: bodyB, 172 anchorA: bodyA.transformWorldPointToLocal(worldAnchor), 173 anchorB: bodyB.transformWorldPointToLocal(worldAnchor), 174 stiff: (!elasticConstraints), 175 frequency: frequency, 176 damping: damping 177 }); 178 world.addConstraint(pointConstraint); 179 180 // ------------------------------------ 181 // Weld Constraint 182 bodyA = circle(13.3, 5, 1); 183 bodyB = circle(16.6, 5, 1); 184 185 worldAnchor = [15, 5]; 186 var weldConstraint = phys2D.createWeldConstraint({ 187 bodyA: bodyA, 188 bodyB: bodyB, 189 anchorA: bodyA.transformWorldPointToLocal(worldAnchor), 190 anchorB: bodyB.transformWorldPointToLocal(worldAnchor), 191 phase: 0, 192 stiff: (!elasticConstraints), 193 frequency: frequency, 194 damping: damping 195 }); 196 world.addConstraint(weldConstraint); 197 198 // ------------------------------------ 199 // Distance Constraint 200 bodyA = circle(23.3, 5, 1); 201 bodyB = circle(26.6, 5, 1); 202 203 var distanceConstraint = phys2D.createDistanceConstraint({ 204 bodyA: bodyA, 205 bodyB: bodyB, 206 anchorA: [1, 0], 207 anchorB: [-1, 0], 208 lowerBound: 1, 209 upperBound: 3, 210 stiff: (!elasticConstraints), 211 frequency: frequency, 212 damping: damping 213 }); 214 world.addConstraint(distanceConstraint); 215 216 // ------------------------------------ 217 // Line Constraint 218 bodyA = circle(33.3, 5, 1); 219 bodyB = circle(36.6, 5, 1); 220 221 worldAnchor = [35, 5]; 222 var lineConstraint = phys2D.createLineConstraint({ 223 bodyA: bodyA, 224 bodyB: bodyB, 225 anchorA: bodyA.transformWorldPointToLocal(worldAnchor), 226 anchorB: bodyB.transformWorldPointToLocal(worldAnchor), 227 axis: [0, 1], 228 lowerBound: -1, 229 upperBound: 1, 230 stiff: (!elasticConstraints), 231 frequency: frequency, 232 damping: damping 233 }); 234 world.addConstraint(lineConstraint); 235 236 // ------------------------------------ 237 // Angle Constraint 238 bodyA = circle(3, 15, 1.5, true); 239 bodyB = circle(7, 15, 1.5, true); 240 241 var angleConstraint = phys2D.createAngleConstraint({ 242 bodyA: bodyA, 243 bodyB: bodyB, 244 ratio: 3, 245 lowerBound: -Math.PI * 2, 246 upperBound: Math.PI * 2, 247 stiff: (!elasticConstraints), 248 frequency: frequency, 249 damping: damping 250 }); 251 world.addConstraint(angleConstraint); 252 253 // ------------------------------------ 254 // Motor Constraint 255 bodyA = circle(13, 15, 1.5, true); 256 bodyB = circle(17, 15, 1.5, true); 257 258 var motorConstraint = phys2D.createMotorConstraint({ 259 bodyA: bodyA, 260 bodyB: bodyB, 261 ratio: 4, 262 rate: 20 263 }); 264 world.addConstraint(motorConstraint); 265 266 // ------------------------------------ 267 // Pulley Constraint 268 var bodyC; 269 bodyA = circle(23.3, 16.6, 0.5); 270 bodyB = circle(25, 13.3, 1, true); 271 bodyC = circle(26.6, 16.6, 0.5); 272 273 // Additional distance constraints to prevent pulley 274 // becoming degenerate when one side becomes 0 length. 275 var distanceA = phys2D.createDistanceConstraint({ 276 bodyA: bodyA, 277 bodyB: bodyB, 278 lowerBound: 0.25, 279 upperBound: Number.POSITIVE_INFINITY, 280 anchorA: [0, -0.5], 281 anchorB: [-1, 0], 282 userData: 'pin' 283 }); 284 world.addConstraint(distanceA); 285 286 var distanceB = phys2D.createDistanceConstraint({ 287 bodyA: bodyC, 288 bodyB: bodyB, 289 lowerBound: 0.25, 290 upperBound: Number.POSITIVE_INFINITY, 291 anchorA: [0, -0.5], 292 anchorB: [1, 0], 293 userData: 'pin' 294 }); 295 world.addConstraint(distanceB); 296 297 var pulleyConstraint = phys2D.createPulleyConstraint({ 298 bodyA: bodyA, 299 bodyB: bodyB, 300 bodyC: bodyB, 301 bodyD: bodyC, 302 anchorA: [0, -0.5], 303 anchorB: [-1, 0], 304 anchorC: [1, 0], 305 anchorD: [0, -0.5], 306 ratio: 2, 307 lowerBound: 6, 308 upperBound: 8, 309 stiff: (!elasticConstraints), 310 frequency: frequency, 311 damping: damping 312 }); 313 world.addConstraint(pulleyConstraint); 314 315 // ------------------------------------ 316 // Custom Constraint 317 bodyA = circle(35, 13.3, 1); 318 bodyB = circle(35, 16.6, 1, true); 319 320 // Additional line constraint to pin upper body to rack. 321 var line = phys2D.createLineConstraint({ 322 bodyA: staticReferenceBody, 323 bodyB: bodyA, 324 anchorA: [35, 13.3], 325 anchorB: [0, 0], 326 axis: [1, 0], 327 lowerBound: -5, 328 upperBound: 5, 329 userData: 'pin' 330 }); 331 world.addConstraint(line); 332 333 // Custom constraint defined so that the x-position of 334 // the first body, is equal to the rotation of the 335 // second body. 336 // 337 // Constraint equation: 338 // (pi / 5) * (bodyA.posX - 35) - bodyB.rotation = 0 339 // 340 // Time Derivative (Velocity constraint): 341 // (pi / 5) * bodyA.velX - bodyB.angularVel = 0 342 // 343 // Partial derivatives of velocity constraint (Jacobian) 344 // velAx velAy angVelA velBx velBy angVelB 345 // [ (pi / 5), 0, 0, 0, 0, -1 ] 346 // 347 var user = phys2D.createCustomConstraint({ 348 bodies: [bodyA, bodyB], 349 dimension: 1, 350 position: function positionFn(data, index) { 351 var bodyA = this.bodies[0]; 352 var bodyB = this.bodies[1]; 353 data[index] = (Math.PI / 5 * (bodyA.getPosition()[0] - 35)) - bodyB.getRotation(); 354 }, 355 jacobian: function jacobianFn(data, index) { 356 data[index] = (Math.PI / 5); 357 data[index + 1] = 0; 358 data[index + 2] = 0; 359 360 data[index + 3] = 0; 361 data[index + 4] = 0; 362 data[index + 5] = -1; 363 }, 364 debugDraw: function debugDrawFn(debug, stiff) { 365 if (stiff) { 366 return; 367 } 368 369 var bodyA = this.bodies[0]; 370 var bodyB = this.bodies[1]; 371 372 var posA = bodyA.getPosition(); 373 var posB = bodyB.getPosition(); 374 375 // target for x-position of bodyA 376 var targetX = ((bodyB.getRotation()) / (Math.PI / 5)) + 35; 377 378 // target for rotation of bodyB 379 var targetR = (Math.PI / 5 * (posA[0] - 35)); 380 381 // 3 pixel spring radius 382 var radius = 3 * debug.screenToPhysics2D; 383 debug.drawLinearSpring(posA[0], posA[1], targetX, posA[1], 3, radius, [1, 0, 0, 1]); 384 debug.drawSpiralSpring(posB[0], posB[1], targetR, bodyB.getRotation(), radius, radius * 2, [0, 0, 1, 1]); 385 }, 386 stiff: (!elasticConstraints), 387 frequency: frequency, 388 damping: damping 389 }); 390 world.addConstraint(user); 391 } 392 reset(); 393 394 function invalidateConstraints() { 395 var constraints = world.constraints; 396 var limit = constraints.length; 397 var i; 398 for (i = 0; i < limit; i += 1) { 399 var con = constraints[i]; 400 401 if (con === handConstraint || con.userData === "pin") { 402 continue; 403 } 404 405 con.configure({ 406 stiff: (!elasticConstraints), 407 frequency: frequency, 408 damping: damping 409 }); 410 } 411 } 412 413 //========================================================================== 414 // Mouse/Keyboard controls 415 //========================================================================== 416 var inputDevice = TurbulenzEngine.createInputDevice({}); 417 var keyCodes = inputDevice.keyCodes; 418 var mouseCodes = inputDevice.mouseCodes; 419 420 var mouseX = 0; 421 var mouseY = 0; 422 var onMouseOver = function mouseOverFn(x, y) { 423 mouseX = x; 424 mouseY = y; 425 }; 426 inputDevice.addEventListener('mouseover', onMouseOver); 427 428 var onKeyUp = function onKeyUpFn(keynum) { 429 if (keynum === keyCodes.R) { 430 reset(); 431 } 432 }; 433 inputDevice.addEventListener('keyup', onKeyUp); 434 435 var onMouseDown = function onMouseDownFn(code, x, y) { 436 mouseX = x; 437 mouseY = y; 438 439 if (handConstraint) { 440 return; 441 } 442 443 var point = draw2D.viewportMap(x, y); 444 var body; 445 if (code === mouseCodes.BUTTON_0) { 446 var bodies = []; 447 var numBodies = world.bodyPointQuery(point, bodies); 448 var i; 449 for (i = 0; i < numBodies; i += 1) { 450 body = bodies[i]; 451 if (body.isDynamic()) { 452 handConstraint = phys2D.createPointConstraint({ 453 bodyA: staticReferenceBody, 454 bodyB: body, 455 anchorA: point, 456 anchorB: body.transformWorldPointToLocal(point), 457 stiff: false, 458 maxForce: 1e5 459 }); 460 world.addConstraint(handConstraint); 461 } 462 } 463 } 464 }; 465 inputDevice.addEventListener('mousedown', onMouseDown); 466 467 var onMouseLeaveUp = function onMouseLeaveUpFn() { 468 if (handConstraint) { 469 world.removeConstraint(handConstraint); 470 handConstraint = null; 471 } 472 }; 473 inputDevice.addEventListener('mouseleave', onMouseLeaveUp); 474 inputDevice.addEventListener('mouseup', onMouseLeaveUp); 475 476 //========================================================================== 477 // Main loop. 478 //========================================================================== 479 var realTime = 0; 480 var prevTime = TurbulenzEngine.time; 481 482 var fontTechnique, fontTechniqueParameters; 483 function mainLoop() { 484 if (!graphicsDevice.beginFrame()) { 485 return; 486 } 487 488 inputDevice.update(); 489 graphicsDevice.clear([0.3, 0.3, 0.3, 1.0]); 490 491 if (handConstraint) { 492 handConstraint.setAnchorA(draw2D.viewportMap(mouseX, mouseY)); 493 } 494 495 var curTime = TurbulenzEngine.time; 496 var timeDelta = (curTime - prevTime); 497 498 if (timeDelta > (1 / 20)) { 499 timeDelta = (1 / 20); 500 } 501 realTime += timeDelta; 502 prevTime = curTime; 503 504 while (world.simulatedTime < realTime) { 505 world.step(1 / 60); 506 } 507 508 // physics2D debug drawing. 509 debug.setScreenViewport(draw2D.getScreenSpaceViewport()); 510 511 debug.begin(); 512 debug.drawWorld(world); 513 debug.end(); 514 515 // Draw fonts. 516 graphicsDevice.setTechnique(fontTechnique); 517 fontTechniqueParameters.clipSpace = mathDevice.v4Build(2 / graphicsDevice.width, -2 / graphicsDevice.height, -1, 1, fontTechniqueParameters.clipSpace); 518 graphicsDevice.setTechniqueParameters(fontTechniqueParameters); 519 520 function segmentFont(x, y, text, height) { 521 var topLeft = draw2D.viewportUnmap(x, y); 522 var bottomRight = draw2D.viewportUnmap(x + 10, y + height); 523 font.drawTextRect(text, { 524 rect: [topLeft[0], topLeft[1], bottomRight[0] - topLeft[0], bottomRight[1] - topLeft[1]], 525 scale: 1.0, 526 spacing: 0, 527 alignment: 1 528 }); 529 } 530 531 var titleHeight = 0.75; 532 segmentFont(0, 0, "Point", titleHeight); 533 segmentFont(10, 0, "Weld", titleHeight); 534 segmentFont(20, 0, "Distance", titleHeight); 535 segmentFont(30, 0, "Line", titleHeight); 536 segmentFont(0, 10, "Angle", titleHeight); 537 segmentFont(10, 10, "Motor", titleHeight); 538 segmentFont(20, 10, "Pulley", titleHeight); 539 segmentFont(30, 10, "Custom", titleHeight); 540 541 graphicsDevice.endFrame(); 542 } 543 544 var intervalID = 0; 545 function loadingLoop() { 546 if (font && shader) { 547 fontTechnique = shader.getTechnique('font'); 548 fontTechniqueParameters = graphicsDevice.createTechniqueParameters({ 549 clipSpace: mathDevice.v4BuildZero(), 550 alphaRef: 0.01, 551 color: mathDevice.v4BuildOne() 552 }); 553 554 TurbulenzEngine.clearInterval(intervalID); 555 intervalID = TurbulenzEngine.setInterval(mainLoop, 1000 / 60); 556 } 557 } 558 intervalID = TurbulenzEngine.setInterval(loadingLoop, 100); 559 560 //========================================================================== 561 function loadHtmlControls() { 562 htmlControls = HTMLControls.create(); 563 htmlControls.addCheckboxControl({ 564 id: "elasticConstraints", 565 value: "elasticConstraints", 566 isSelected: elasticConstraints, 567 fn: function () { 568 elasticConstraints = !elasticConstraints; 569 invalidateConstraints(); 570 return elasticConstraints; 571 } 572 }); 573 htmlControls.addSliderControl({ 574 id: "frequencySlider", 575 value: (frequency), 576 max: 10, 577 min: 0.25, 578 step: 0.25, 579 fn: function () { 580 frequency = this.value; 581 htmlControls.updateSlider("frequencySlider", frequency); 582 if (elasticConstraints) { 583 invalidateConstraints(); 584 } 585 } 586 }); 587 htmlControls.addSliderControl({ 588 id: "dampingSlider", 589 value: (damping), 590 max: 2, 591 min: 0, 592 step: 0.25, 593 fn: function () { 594 damping = this.value; 595 htmlControls.updateSlider("dampingSlider", damping); 596 if (elasticConstraints) { 597 invalidateConstraints(); 598 } 599 } 600 }); 601 htmlControls.register(); 602 } 603 604 loadHtmlControls(); 605 606 // Create a scene destroy callback to run when the window is closed 607 TurbulenzEngine.onunload = function destroyScene() { 608 if (intervalID) { 609 TurbulenzEngine.clearInterval(intervalID); 610 } 611 612 if (gameSession) { 613 gameSession.destroy(); 614 gameSession = null; 615 } 616 }; 617 };