1 /*{# Copyright (c) 2012 Turbulenz Limited #}*/ 2 /* 3 * @title: 2D Physics 4 * @description: 5 * This sample shows how to create and use the Turbulenz 2D physics device. 6 * The sample creates several 2D static objects with surface velocity, 7 * two animated kinematic objects to push and lift objects around, 8 * and a hundred rigid bodies with different material properties. 9 * Left click to pick up and move the rigid bodies and right click to add new ones. 10 */ 11 /*{{ javascript("jslib/observer.js") }}*/ 12 /*{{ javascript("jslib/requesthandler.js") }}*/ 13 /*{{ javascript("jslib/utilities.js") }}*/ 14 /*{{ javascript("jslib/services/turbulenzservices.js") }}*/ 15 /*{{ javascript("jslib/services/turbulenzbridge.js") }}*/ 16 /*{{ javascript("jslib/services/gamesession.js") }}*/ 17 /*{{ javascript("jslib/services/mappingtable.js") }}*/ 18 /*{{ javascript("jslib/shadermanager.js") }}*/ 19 /*{{ javascript("jslib/physics2ddevice.js") }}*/ 20 /*{{ javascript("jslib/draw2d.js") }}*/ 21 /*{{ javascript("jslib/boxtree.js") }}*/ 22 /*{{ javascript("jslib/physics2ddebugdraw.js") }}*/ 23 /*{{ javascript("jslib/textureeffects.js") }}*/ 24 /*{{ javascript("scripts/htmlcontrols.js") }}*/ 25 /*global TurbulenzEngine: true */ 26 /*global TurbulenzServices: false */ 27 /*global RequestHandler: false */ 28 /*global Physics2DDevice: false */ 29 /*global Draw2D: false */ 30 /*global Draw2DSprite: false */ 31 /*global Physics2DDebugDraw: false */ 32 /*global HTMLControls: false */ 33 TurbulenzEngine.onload = function onloadFn() { 34 //========================================================================== 35 // HTML Controls 36 //========================================================================== 37 var htmlControls; 38 39 var debugEnabled = false; 40 var contactsEnabled = false; 41 42 //========================================================================== 43 // Turbulenz Initialization 44 //========================================================================== 45 var graphicsDevice = TurbulenzEngine.createGraphicsDevice({}); 46 var mathDevice = TurbulenzEngine.createMathDevice({}); 47 var requestHandler = RequestHandler.create({}); 48 49 var draw2DTexture; 50 var gameSession; 51 function sessionCreated(gameSession) { 52 TurbulenzServices.createMappingTable(requestHandler, gameSession, function (table) { 53 graphicsDevice.createTexture({ 54 src: table.getURL("textures/physics2d.png"), 55 mipmaps: true, 56 onload: function (texture) { 57 if (texture) { 58 draw2DTexture = texture; 59 } 60 } 61 }); 62 }); 63 } 64 gameSession = TurbulenzServices.createGameSession(requestHandler, sessionCreated); 65 66 //========================================================================== 67 // Physics2D/Draw2D 68 //========================================================================== 69 // set up. 70 var phys2D = Physics2DDevice.create(); 71 72 // size of physics stage. 73 var stageWidth = 30; 74 var stageHeight = 22; 75 76 var draw2D = Draw2D.create({ 77 graphicsDevice: graphicsDevice 78 }); 79 var debug = Physics2DDebugDraw.create({ 80 graphicsDevice: graphicsDevice 81 }); 82 83 // Configure draw2D viewport to the physics stage. 84 // As well as the physics2D debug-draw viewport. 85 draw2D.configure({ 86 viewportRectangle: [0, 0, stageWidth, stageHeight], 87 scaleMode: 'scale' 88 }); 89 debug.setPhysics2DViewport([0, 0, stageWidth, stageHeight]); 90 91 var world = phys2D.createWorld({ 92 gravity: [0, 20] 93 }); 94 95 var rubberMaterial = phys2D.createMaterial({ 96 elasticity: 0.9, 97 staticFriction: 6, 98 dynamicFriction: 4, 99 rollingFriction: 0.001 100 }); 101 102 var heavyMaterial = phys2D.createMaterial({ 103 density: 3 104 }); 105 106 var conveyorBeltMaterial = phys2D.createMaterial({ 107 elasticity: 0, 108 staticFriction: 10, 109 dynamicFriction: 8, 110 rollingFriction: 0.1 111 }); 112 113 var shapeSize = 0.8; 114 var shapeFactory = [ 115 phys2D.createCircleShape({ 116 radius: (shapeSize / 2), 117 material: rubberMaterial 118 }), 119 phys2D.createPolygonShape({ 120 vertices: phys2D.createBoxVertices(shapeSize, shapeSize), 121 material: heavyMaterial 122 }), 123 phys2D.createPolygonShape({ 124 vertices: phys2D.createRegularPolygonVertices(shapeSize, shapeSize, 3), 125 material: heavyMaterial 126 }), 127 phys2D.createPolygonShape({ 128 vertices: phys2D.createRegularPolygonVertices(shapeSize, shapeSize, 6), 129 material: rubberMaterial 130 }) 131 ]; 132 133 // texture rectangles for above shapes. 134 var textureRectangles = [ 135 [130, 130, 255, 255], 136 [5, 132, 125, 252], 137 [131, 3, 251, 123], 138 [5, 5, 125, 125] 139 ]; 140 141 // Create a static body at (0, 0) with no rotation 142 // which we add to the world to use as the first body 143 // in hand constraint. We set anchor for this body 144 // as the cursor position in physics coordinates. 145 var handReferenceBody = phys2D.createRigidBody({ 146 type: 'static' 147 }); 148 world.addRigidBody(handReferenceBody); 149 var handConstraint = null; 150 151 var animationState = 0; 152 var lift; 153 var pusher; 154 155 function reset() { 156 // Remove all bodies and constraints from world. 157 world.clear(); 158 handConstraint = null; 159 160 // Create a static border body around the stage to stop objects leaving the viewport. 161 var thickness = 0.01; 162 var border = phys2D.createRigidBody({ 163 type: 'static', 164 shapes: [ 165 phys2D.createPolygonShape({ 166 vertices: phys2D.createRectangleVertices(0, 0, thickness, stageHeight) 167 }), 168 phys2D.createPolygonShape({ 169 vertices: phys2D.createRectangleVertices(0, 0, stageWidth, thickness) 170 }), 171 phys2D.createPolygonShape({ 172 vertices: phys2D.createRectangleVertices((stageWidth - thickness), 0, stageWidth, stageHeight) 173 }), 174 phys2D.createPolygonShape({ 175 vertices: phys2D.createRectangleVertices(0, (stageHeight - thickness), stageWidth, stageHeight) 176 }) 177 ] 178 }); 179 world.addRigidBody(border); 180 181 var createBelt = function createBeltFn(x1, y1, x2, y2, radius, speed) { 182 var normal = mathDevice.v2Build(y2 - y1, x1 - x2); 183 mathDevice.v2ScalarMul(normal, radius / mathDevice.v2Length(normal), normal); 184 185 var shapes = [ 186 phys2D.createPolygonShape({ 187 vertices: [ 188 [x1 + normal[0], y1 + normal[1]], 189 [x2 + normal[0], y2 + normal[1]], 190 [x2 - normal[0], y2 - normal[1]], 191 [x1 - normal[0], y1 - normal[1]] 192 ], 193 material: conveyorBeltMaterial 194 }), 195 phys2D.createCircleShape({ 196 radius: radius, 197 origin: [x1, y1], 198 material: conveyorBeltMaterial 199 }), 200 phys2D.createCircleShape({ 201 radius: radius, 202 origin: [x2, y2], 203 material: conveyorBeltMaterial 204 }) 205 ]; 206 var body = phys2D.createRigidBody({ 207 type: 'static', 208 surfaceVelocity: [speed, 0], 209 shapes: shapes 210 }); 211 212 return body; 213 }; 214 215 var belt; 216 belt = createBelt(0, 11, 7, 14, 0.5, 2); 217 world.addRigidBody(belt); 218 219 belt = createBelt(7, 14, 14, 11, 0.5, 2); 220 world.addRigidBody(belt); 221 222 belt = createBelt(12, 19, 20.5, 17, 0.5, 2); 223 world.addRigidBody(belt); 224 225 belt = createBelt(20.5, 10.5, 10, 5, 0.5, -2); 226 world.addRigidBody(belt); 227 228 belt = createBelt(10, 5, 5, 5, 0.5, -2); 229 world.addRigidBody(belt); 230 231 // Create lift and pusher bodies. 232 lift = phys2D.createRigidBody({ 233 shapes: [ 234 phys2D.createPolygonShape({ 235 vertices: phys2D.createBoxVertices(9, 0.01) 236 }) 237 ], 238 type: 'kinematic', 239 position: [stageWidth - 4.5, stageHeight] 240 }); 241 pusher = phys2D.createRigidBody({ 242 shapes: [ 243 phys2D.createPolygonShape({ 244 vertices: phys2D.createBoxVertices(9, 10) 245 }) 246 ], 247 type: 'kinematic', 248 position: [stageWidth + 4.5, 5] 249 }); 250 world.addRigidBody(lift); 251 world.addRigidBody(pusher); 252 animationState = 0; 253 254 // Create piles of each factory shape. 255 var x, y; 256 var xCount = Math.floor(stageWidth / shapeSize); 257 for (x = 0; x < xCount; x += 1) { 258 for (y = 0; y < 4; y += 1) { 259 var index = (y % shapeFactory.length); 260 var shape = shapeFactory[index]; 261 var body = phys2D.createRigidBody({ 262 shapes: [shape.clone()], 263 position: [ 264 (x + 0.5) * (stageWidth / xCount), 265 (y + 0.5) * shapeSize 266 ], 267 userData: Draw2DSprite.create({ 268 width: shapeSize, 269 height: shapeSize, 270 origin: [shapeSize / 2, shapeSize / 2], 271 textureRectangle: textureRectangles[index], 272 texture: draw2DTexture 273 }) 274 }); 275 world.addRigidBody(body); 276 } 277 } 278 } 279 280 //========================================================================== 281 // Mouse/Keyboard controls 282 //========================================================================== 283 var inputDevice = TurbulenzEngine.createInputDevice({}); 284 var keyCodes = inputDevice.keyCodes; 285 var mouseCodes = inputDevice.mouseCodes; 286 287 var mouseX = 0; 288 var mouseY = 0; 289 var onMouseOver = function mouseOverFn(x, y) { 290 mouseX = x; 291 mouseY = y; 292 }; 293 inputDevice.addEventListener('mouseover', onMouseOver); 294 295 var onKeyUp = function onKeyUpFn(keynum) { 296 if (keynum === keyCodes.R) { 297 reset(); 298 } 299 }; 300 inputDevice.addEventListener('keyup', onKeyUp); 301 302 var onMouseDown = function onMouseDownFn(code, x, y) { 303 mouseX = x; 304 mouseY = y; 305 306 if (handConstraint) { 307 return; 308 } 309 310 var point = draw2D.viewportMap(x, y); 311 var body; 312 if (code === mouseCodes.BUTTON_0) { 313 var bodies = []; 314 var numBodies = world.bodyPointQuery(point, bodies); 315 var i; 316 for (i = 0; i < numBodies; i += 1) { 317 body = bodies[i]; 318 if (body.isDynamic()) { 319 handConstraint = phys2D.createPointConstraint({ 320 bodyA: handReferenceBody, 321 bodyB: body, 322 anchorA: point, 323 anchorB: body.transformWorldPointToLocal(point), 324 stiff: false, 325 maxForce: 1e5 326 }); 327 world.addConstraint(handConstraint); 328 break; 329 } 330 } 331 } else if (code === mouseCodes.BUTTON_1) { 332 var index = Math.floor(Math.random() * shapeFactory.length); 333 body = phys2D.createRigidBody({ 334 shapes: [shapeFactory[index].clone()], 335 position: point, 336 userData: Draw2DSprite.create({ 337 width: shapeSize, 338 height: shapeSize, 339 origin: [shapeSize / 2, shapeSize / 2], 340 textureRectangle: textureRectangles[index], 341 texture: draw2DTexture 342 }) 343 }); 344 world.addRigidBody(body); 345 } 346 }; 347 inputDevice.addEventListener('mousedown', onMouseDown); 348 349 var onMouseLeaveUp = function onMouseLeaveUpFn() { 350 if (handConstraint) { 351 world.removeConstraint(handConstraint); 352 handConstraint = null; 353 } 354 }; 355 inputDevice.addEventListener('mouseleave', onMouseLeaveUp); 356 inputDevice.addEventListener('mouseup', onMouseLeaveUp); 357 358 //========================================================================== 359 // Main loop. 360 //========================================================================== 361 var fpsElement = document.getElementById("fpscounter"); 362 var lastFPS = ""; 363 364 var bodiesElement = document.getElementById("bodiescounter"); 365 var lastNumBodies = 0; 366 367 var realTime = 0; 368 var prevTime = TurbulenzEngine.time; 369 370 function mainLoop() { 371 if (!graphicsDevice.beginFrame()) { 372 return; 373 } 374 375 inputDevice.update(); 376 graphicsDevice.clear([0.3, 0.3, 0.3, 1.0]); 377 378 var body; 379 if (handConstraint) { 380 body = handConstraint.bodyB; 381 handConstraint.setAnchorA(draw2D.viewportMap(mouseX, mouseY)); 382 383 // Additional angular dampening of body being dragged. 384 // Helps it to settle quicker instead of spinning around 385 // the cursor. 386 body.setAngularVelocity(body.getAngularVelocity() * 0.9); 387 } 388 389 var curTime = TurbulenzEngine.time; 390 var timeDelta = (curTime - prevTime); 391 392 if (timeDelta > (1 / 20)) { 393 timeDelta = (1 / 20); 394 } 395 realTime += timeDelta; 396 prevTime = curTime; 397 398 while (world.simulatedTime < realTime) { 399 if (animationState === 0) { 400 // Start of animatino, set velocity of lift to move up to the target 401 // in 3 seconds. 402 lift.setVelocityFromPosition([stageWidth - 4.5, 10], 0, 3); 403 animationState = 1; 404 } else if (animationState === 1) { 405 if (lift.getPosition()[1] <= 10) { 406 // Reached target position for lift. 407 // Set position incase it over-reached and zero velocity. 408 lift.setPosition([stageWidth - 4.5, 10]); 409 lift.setVelocity([0, 0]); 410 411 // Start pusher animation to move left. 412 pusher.setVelocityFromPosition([stageWidth - 4.5, 5], 0, 1.5); 413 animationState = 2; 414 } 415 } else if (animationState === 2) { 416 if (pusher.getPosition()[0] <= (stageWidth - 4.5)) { 417 // Reached target position for pusher. 418 // Set velocities of pusher and lift to both move right off-screen. 419 pusher.setVelocityFromPosition([stageWidth + 4.5, 5], 0, 1); 420 lift.setVelocityFromPosition([stageWidth + 4.5, 10], 0, 1); 421 animationState = 3; 422 } 423 } else if (animationState === 3) { 424 if (pusher.getPosition()[0] >= stageWidth + 4.5) { 425 // Reached target. 426 // Reset positions and velocities and begin animation afresh. 427 pusher.setPosition([stageWidth + 4.5, 5]); 428 pusher.setVelocity([0, 0]); 429 lift.setPosition([stageWidth - 4.5, stageHeight]); 430 lift.setVelocity([0, 0]); 431 animationState = 0; 432 } 433 } 434 435 world.step(1 / 60); 436 } 437 438 // draw2D sprite drawing. 439 var bodies = world.rigidBodies; 440 var limit = bodies.length; 441 var i; 442 if (!debugEnabled) { 443 draw2D.begin('alpha', 'deferred'); 444 var pos = []; 445 for (i = 0; i < limit; i += 1) { 446 body = bodies[i]; 447 if (body.userData) { 448 body.getPosition(pos); 449 var sprite = body.userData; 450 sprite.x = pos[0]; 451 sprite.y = pos[1]; 452 sprite.rotation = body.getRotation(); 453 draw2D.drawSprite(sprite); 454 } 455 } 456 draw2D.end(); 457 } 458 459 // physics2D debug drawing. 460 debug.setScreenViewport(draw2D.getScreenSpaceViewport()); 461 debug.showRigidBodies = debugEnabled; 462 debug.showContacts = contactsEnabled; 463 464 debug.begin(); 465 if (!debugEnabled) { 466 for (i = 0; i < limit; i += 1) { 467 body = bodies[i]; 468 if (!body.userData) { 469 debug.drawRigidBody(body); 470 } 471 } 472 } 473 debug.drawWorld(world); 474 debug.end(); 475 476 graphicsDevice.endFrame(); 477 478 if (fpsElement) { 479 var fpsText = (graphicsDevice.fps).toFixed(2); 480 if (lastFPS !== fpsText) { 481 lastFPS = fpsText; 482 483 fpsElement.innerHTML = fpsText + " fps"; 484 } 485 } 486 487 if (bodiesElement) { 488 if (lastNumBodies !== limit) { 489 lastNumBodies = limit; 490 491 bodiesElement.innerHTML = lastNumBodies + ""; 492 } 493 } 494 } 495 496 var intervalID; 497 function loadingLoop() { 498 if (!draw2DTexture) { 499 return; 500 } 501 502 reset(); 503 TurbulenzEngine.clearInterval(intervalID); 504 intervalID = TurbulenzEngine.setInterval(mainLoop, 1000 / 60); 505 } 506 intervalID = TurbulenzEngine.setInterval(loadingLoop, 10); 507 508 //========================================================================== 509 function loadHtmlControls() { 510 htmlControls = HTMLControls.create(); 511 htmlControls.addCheckboxControl({ 512 id: "enableDebug", 513 value: "debugEnabled", 514 isSelected: debugEnabled, 515 fn: function () { 516 debugEnabled = !debugEnabled; 517 return debugEnabled; 518 } 519 }); 520 htmlControls.addCheckboxControl({ 521 id: "enableContacts", 522 value: "contactsEnabled", 523 isSelected: contactsEnabled, 524 fn: function () { 525 contactsEnabled = !contactsEnabled; 526 return contactsEnabled; 527 } 528 }); 529 htmlControls.register(); 530 } 531 532 loadHtmlControls(); 533 534 // Create a scene destroy callback to run when the window is closed 535 TurbulenzEngine.onunload = function destroyScene() { 536 if (intervalID) { 537 TurbulenzEngine.clearInterval(intervalID); 538 } 539 540 if (gameSession) { 541 gameSession.destroy(); 542 gameSession = null; 543 } 544 }; 545 };